/Users/deen/code/yugabyte-db/src/yb/consensus/consensus_meta-test.cc
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // |
18 | | // The following only applies to changes made to this file as part of YugaByte development. |
19 | | // |
20 | | // Portions Copyright (c) YugaByte, Inc. |
21 | | // |
22 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
23 | | // in compliance with the License. You may obtain a copy of the License at |
24 | | // |
25 | | // http://www.apache.org/licenses/LICENSE-2.0 |
26 | | // |
27 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
28 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
29 | | // or implied. See the License for the specific language governing permissions and limitations |
30 | | // under the License. |
31 | | // |
32 | | #include "yb/consensus/consensus_meta.h" |
33 | | |
34 | | #include <vector> |
35 | | |
36 | | #include <gtest/gtest.h> |
37 | | |
38 | | #include "yb/common/wire_protocol.h" |
39 | | #include "yb/consensus/metadata.pb.h" |
40 | | #include "yb/consensus/opid_util.h" |
41 | | #include "yb/consensus/quorum_util.h" |
42 | | #include "yb/fs/fs_manager.h" |
43 | | #include "yb/util/net/net_util.h" |
44 | | #include "yb/util/test_macros.h" |
45 | | #include "yb/util/test_util.h" |
46 | | |
47 | | #define ASSERT_VALUES_EQUAL(cmeta, opid_index, uuid, term) \ |
48 | 4 | ASSERT_NO_FATALS(AssertValuesEqual(cmeta, opid_index, uuid, term)) |
49 | | |
50 | | namespace yb { |
51 | | namespace consensus { |
52 | | |
53 | | using std::string; |
54 | | using std::vector; |
55 | | |
56 | | const char* kTabletId = "test-consensus-metadata"; |
57 | | const int64_t kInitialTerm = 3; |
58 | | |
59 | | class ConsensusMetadataTest : public YBTest { |
60 | | public: |
61 | | ConsensusMetadataTest() |
62 | 6 | : fs_manager_(env_.get(), GetTestPath("fs_root"), "tserver_test") { |
63 | 6 | } |
64 | | |
65 | 6 | void SetUp() override { |
66 | 6 | YBTest::SetUp(); |
67 | 6 | ASSERT_OK(fs_manager_.CreateInitialFileSystemLayout()); |
68 | 6 | ASSERT_OK(fs_manager_.Open()); |
69 | | |
70 | | // Initialize test configuration. |
71 | 6 | config_.set_opid_index(kInvalidOpIdIndex); |
72 | 6 | RaftPeerPB* peer = config_.add_peers(); |
73 | 6 | peer->set_permanent_uuid(fs_manager_.uuid()); |
74 | 6 | peer->set_member_type(PeerMemberType::VOTER); |
75 | 6 | HostPortToPB(HostPort("fake-host", 0), peer->mutable_last_known_private_addr()->Add()); |
76 | 6 | } |
77 | | |
78 | | protected: |
79 | | // Assert that the given cmeta has a single configuration with the given metadata values. |
80 | | void AssertValuesEqual(const ConsensusMetadata& cmeta, |
81 | | int64_t opid_index, const string& permanent_uuid, int64_t term); |
82 | | |
83 | | FsManager fs_manager_; |
84 | | RaftConfigPB config_; |
85 | | }; |
86 | | |
87 | | void ConsensusMetadataTest::AssertValuesEqual(const ConsensusMetadata& cmeta, |
88 | | int64_t opid_index, |
89 | | const string& permanent_uuid, |
90 | 4 | int64_t term) { |
91 | | // Sanity checks. |
92 | 4 | ASSERT_EQ(1, cmeta.committed_config().peers_size()); |
93 | | |
94 | | // Value checks. |
95 | 4 | ASSERT_EQ(opid_index, cmeta.committed_config().opid_index()); |
96 | 4 | ASSERT_EQ(permanent_uuid, cmeta.committed_config().peers().begin()->permanent_uuid()); |
97 | 4 | ASSERT_EQ(term, cmeta.current_term()); |
98 | 4 | } |
99 | | |
100 | | // Test the basic "happy case" of creating and then loading a file. |
101 | 1 | TEST_F(ConsensusMetadataTest, TestCreateLoad) { |
102 | | // Create the file. |
103 | 1 | { |
104 | 1 | std::unique_ptr<ConsensusMetadata> cmeta; |
105 | 1 | ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, fs_manager_.uuid(), |
106 | 1 | config_, kInitialTerm, &cmeta)); |
107 | 1 | ASSERT_VALUES_EQUAL(*cmeta, kInvalidOpIdIndex, fs_manager_.uuid(), kInitialTerm); |
108 | 1 | } |
109 | | |
110 | | // Load the file. |
111 | 1 | std::unique_ptr<ConsensusMetadata> cmeta; |
112 | 1 | ASSERT_OK(ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta)); |
113 | 1 | ASSERT_VALUES_EQUAL(*cmeta, kInvalidOpIdIndex, fs_manager_.uuid(), kInitialTerm); |
114 | 1 | } |
115 | | |
116 | | // Ensure that we get an error when loading a file that doesn't exist. |
117 | 1 | TEST_F(ConsensusMetadataTest, TestFailedLoad) { |
118 | 1 | std::unique_ptr<ConsensusMetadata> cmeta; |
119 | 1 | Status s = ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta); |
120 | 2 | ASSERT_TRUE(s.IsNotFound()) << "Unexpected status: " << s.ToString(); |
121 | 1 | LOG(INFO) << "Expected failure: " << s.ToString(); |
122 | 1 | } |
123 | | |
124 | | // Check that changes are not written to disk until Flush() is called. |
125 | 1 | TEST_F(ConsensusMetadataTest, TestFlush) { |
126 | 1 | const int64_t kNewTerm = 4; |
127 | 1 | std::unique_ptr<ConsensusMetadata> cmeta; |
128 | 1 | ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, fs_manager_.uuid(), |
129 | 1 | config_, kInitialTerm, &cmeta)); |
130 | 1 | cmeta->set_current_term(kNewTerm); |
131 | | |
132 | | // We are sort of "breaking the rules" by having multiple ConsensusMetadata |
133 | | // objects in flight that point to the same file, but for a test this is fine |
134 | | // since it's read-only. |
135 | 1 | { |
136 | 1 | std::unique_ptr<ConsensusMetadata> cmeta_read; |
137 | 1 | ASSERT_OK(ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta_read)); |
138 | 1 | ASSERT_VALUES_EQUAL(*cmeta_read, kInvalidOpIdIndex, fs_manager_.uuid(), kInitialTerm); |
139 | 1 | } |
140 | | |
141 | 1 | ASSERT_OK(cmeta->Flush()); |
142 | | |
143 | 1 | { |
144 | 1 | std::unique_ptr<ConsensusMetadata> cmeta_read; |
145 | 1 | ASSERT_OK(ConsensusMetadata::Load(&fs_manager_, kTabletId, fs_manager_.uuid(), &cmeta_read)); |
146 | 1 | ASSERT_VALUES_EQUAL(*cmeta_read, kInvalidOpIdIndex, fs_manager_.uuid(), kNewTerm); |
147 | 1 | } |
148 | 1 | } |
149 | | |
150 | | // Builds a distributed configuration of voters with the given uuids. |
151 | 9 | RaftConfigPB BuildConfig(const vector<string>& uuids) { |
152 | 9 | RaftConfigPB config; |
153 | 36 | for (const string& uuid : uuids) { |
154 | 36 | RaftPeerPB* peer = config.add_peers(); |
155 | 36 | peer->set_permanent_uuid(uuid); |
156 | 36 | peer->set_member_type(PeerMemberType::VOTER); |
157 | 36 | HostPortToPB(HostPort("255.255.255.255", 0), peer->mutable_last_known_private_addr()->Add()); |
158 | 36 | } |
159 | 9 | return config; |
160 | 9 | } |
161 | | |
162 | | // Test ConsensusMetadata active role calculation. |
163 | 1 | TEST_F(ConsensusMetadataTest, TestActiveRole) { |
164 | 1 | vector<string> uuids = { "a", "b", "c", "d" }; |
165 | 1 | string peer_uuid = "e"; |
166 | 1 | RaftConfigPB config1 = BuildConfig(uuids); // We aren't a member of this config... |
167 | 1 | config1.set_opid_index(1); |
168 | | |
169 | 1 | std::unique_ptr<ConsensusMetadata> cmeta; |
170 | 1 | ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, peer_uuid, |
171 | 1 | config1, kInitialTerm, &cmeta)); |
172 | | |
173 | | // Not a participant. |
174 | 1 | ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role()); |
175 | | |
176 | | // Follower. |
177 | 1 | uuids.push_back(peer_uuid); |
178 | 1 | RaftConfigPB config2 = BuildConfig(uuids); // But we are a member of this one. |
179 | 1 | config2.set_opid_index(1); |
180 | 1 | cmeta->set_committed_config(config2); |
181 | 1 | ASSERT_EQ(PeerRole::FOLLOWER, cmeta->active_role()); |
182 | | |
183 | | // Pending should mask committed. |
184 | 1 | cmeta->set_pending_config(config1); |
185 | 1 | ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role()); |
186 | 1 | cmeta->clear_pending_config(); |
187 | 1 | ASSERT_EQ(PeerRole::FOLLOWER, cmeta->active_role()); |
188 | | |
189 | | // Leader. |
190 | 1 | cmeta->set_leader_uuid(peer_uuid); |
191 | 1 | ASSERT_EQ(PeerRole::LEADER, cmeta->active_role()); |
192 | | |
193 | | // Again, pending should mask committed. |
194 | 1 | cmeta->set_pending_config(config1); |
195 | 1 | ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role()); |
196 | 1 | cmeta->set_pending_config(config2); // pending == committed. |
197 | 1 | ASSERT_EQ(PeerRole::LEADER, cmeta->active_role()); |
198 | 1 | cmeta->set_committed_config(config1); // committed now excludes this node, but is masked... |
199 | 1 | ASSERT_EQ(PeerRole::LEADER, cmeta->active_role()); |
200 | | |
201 | | // ... until we clear pending, then we find committed now excludes us. |
202 | 1 | cmeta->clear_pending_config(); |
203 | 1 | ASSERT_EQ(PeerRole::NON_PARTICIPANT, cmeta->active_role()); |
204 | 1 | } |
205 | | |
206 | | // Ensure that invocations of ToConsensusStatePB() return the expected state |
207 | | // in the returned object. |
208 | 1 | TEST_F(ConsensusMetadataTest, TestToConsensusStatePB) { |
209 | 1 | vector<string> uuids = { "a", "b", "c", "d" }; |
210 | 1 | string peer_uuid = "e"; |
211 | | |
212 | 1 | RaftConfigPB committed_config = BuildConfig(uuids); // We aren't a member of this config... |
213 | 1 | committed_config.set_opid_index(1); |
214 | 1 | std::unique_ptr<ConsensusMetadata> cmeta; |
215 | 1 | ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, peer_uuid, |
216 | 1 | committed_config, kInitialTerm, &cmeta)); |
217 | | |
218 | 1 | uuids.push_back(peer_uuid); |
219 | 1 | RaftConfigPB pending_config = BuildConfig(uuids); |
220 | | |
221 | | // Set the pending configuration to be one containing the current leader (who is not |
222 | | // in the committed configuration). Ensure that the leader shows up when we ask for |
223 | | // the active consensus state. |
224 | 1 | cmeta->set_pending_config(pending_config); |
225 | 1 | cmeta->set_leader_uuid(peer_uuid); |
226 | 1 | ConsensusStatePB active_cstate = cmeta->ToConsensusStatePB(CONSENSUS_CONFIG_ACTIVE); |
227 | 1 | ASSERT_TRUE(active_cstate.has_leader_uuid()); |
228 | 1 | ASSERT_OK(VerifyConsensusState(active_cstate, UNCOMMITTED_QUORUM)); |
229 | | |
230 | | // Without changing anything, ask for the committed consensus state. |
231 | | // Since the current leader is not a voter in the committed configuration, the |
232 | | // returned consensus state should not list a leader. |
233 | 1 | ConsensusStatePB committed_cstate = cmeta->ToConsensusStatePB(CONSENSUS_CONFIG_COMMITTED); |
234 | 1 | ASSERT_FALSE(committed_cstate.has_leader_uuid()); |
235 | 1 | ASSERT_OK(VerifyConsensusState(committed_cstate, COMMITTED_QUORUM)); |
236 | | |
237 | | // Set a new leader to be a member of the committed configuration. Now the committed |
238 | | // consensus state should list a leader. |
239 | 1 | cmeta->set_leader_uuid("a"); |
240 | 1 | ConsensusStatePB new_committed_cstate = cmeta->ToConsensusStatePB(CONSENSUS_CONFIG_COMMITTED); |
241 | 1 | ASSERT_TRUE(new_committed_cstate.has_leader_uuid()); |
242 | 1 | ASSERT_OK(VerifyConsensusState(new_committed_cstate, COMMITTED_QUORUM)); |
243 | 1 | } |
244 | | |
245 | | namespace { |
246 | | |
247 | | // Helper for TestMergeCommittedConsensusStatePB. |
248 | | void AssertConsensusMergeExpected(const ConsensusMetadata& cmeta, |
249 | | const ConsensusStatePB& cstate, |
250 | | int64_t expected_term, |
251 | 3 | const string& expected_voted_for) { |
252 | | // See header docs for ConsensusMetadata::MergeCommittedConsensusStatePB() for |
253 | | // a "spec" of these assertions. |
254 | 3 | ASSERT_TRUE(!cmeta.has_pending_config()); |
255 | 3 | ASSERT_EQ(cmeta.committed_config().ShortDebugString(), cstate.config().ShortDebugString()); |
256 | 3 | ASSERT_EQ("", cmeta.leader_uuid()); |
257 | 3 | ASSERT_EQ(expected_term, cmeta.current_term()); |
258 | 3 | if (expected_voted_for.empty()) { |
259 | 1 | ASSERT_FALSE(cmeta.has_voted_for()); |
260 | 2 | } else { |
261 | 2 | ASSERT_EQ(expected_voted_for, cmeta.voted_for()); |
262 | 2 | } |
263 | 3 | } |
264 | | |
265 | | } // namespace |
266 | | |
267 | | // Ensure that MergeCommittedConsensusStatePB() works as advertised. |
268 | 1 | TEST_F(ConsensusMetadataTest, TestMergeCommittedConsensusStatePB) { |
269 | 1 | vector<string> uuids = { "a", "b", "c", "d" }; |
270 | | |
271 | 1 | RaftConfigPB committed_config = BuildConfig(uuids); // We aren't a member of this config... |
272 | 1 | committed_config.set_opid_index(1); |
273 | 1 | std::unique_ptr<ConsensusMetadata> cmeta; |
274 | 1 | ASSERT_OK(ConsensusMetadata::Create(&fs_manager_, kTabletId, "e", |
275 | 1 | committed_config, 1, &cmeta)); |
276 | | |
277 | 1 | uuids.push_back("e"); |
278 | 1 | RaftConfigPB pending_config = BuildConfig(uuids); |
279 | 1 | cmeta->set_pending_config(pending_config); |
280 | 1 | cmeta->set_leader_uuid("e"); |
281 | 1 | cmeta->set_voted_for("e"); |
282 | | |
283 | | // Keep the term and votes because the merged term is lower. |
284 | 1 | ConsensusStatePB remote_state; |
285 | 1 | remote_state.set_current_term(0); |
286 | 1 | *remote_state.mutable_config() = BuildConfig({ "x", "y", "z" }); |
287 | 1 | cmeta->MergeCommittedConsensusStatePB(remote_state); |
288 | 1 | ASSERT_NO_FATALS(AssertConsensusMergeExpected(*cmeta, remote_state, 1, "e")); |
289 | | |
290 | | // Same as above because the merged term is the same as the cmeta term. |
291 | 1 | remote_state.set_current_term(1); |
292 | 1 | *remote_state.mutable_config() = BuildConfig({ "f", "g", "h" }); |
293 | 1 | cmeta->MergeCommittedConsensusStatePB(remote_state); |
294 | 1 | ASSERT_NO_FATALS(AssertConsensusMergeExpected(*cmeta, remote_state, 1, "e")); |
295 | | |
296 | | // Higher term, so wipe out the prior state. |
297 | 1 | remote_state.set_current_term(2); |
298 | 1 | *remote_state.mutable_config() = BuildConfig({ "i", "j", "k" }); |
299 | 1 | cmeta->set_pending_config(pending_config); |
300 | 1 | cmeta->MergeCommittedConsensusStatePB(remote_state); |
301 | 1 | ASSERT_NO_FATALS(AssertConsensusMergeExpected(*cmeta, remote_state, 2, "")); |
302 | 1 | } |
303 | | |
304 | | } // namespace consensus |
305 | | } // namespace yb |