/Users/deen/code/yugabyte-db/src/yb/consensus/leader_election-test.cc
Line | Count | Source (jump to first uncovered line) |
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 | | |
33 | | #include <functional> |
34 | | #include <memory> |
35 | | #include <string> |
36 | | #include <vector> |
37 | | |
38 | | #include "yb/consensus/consensus-test-util.h" |
39 | | #include "yb/consensus/consensus_peers.h" |
40 | | #include "yb/consensus/leader_election.h" |
41 | | #include "yb/consensus/metadata.pb.h" |
42 | | |
43 | | #include "yb/gutil/casts.h" |
44 | | #include "yb/gutil/map-util.h" |
45 | | #include "yb/gutil/strings/substitute.h" |
46 | | |
47 | | #include "yb/util/status_log.h" |
48 | | #include "yb/util/test_macros.h" |
49 | | #include "yb/util/test_util.h" |
50 | | |
51 | | namespace yb { |
52 | | |
53 | | namespace rpc { |
54 | | class Messenger; |
55 | | } // namespace rpc |
56 | | |
57 | | namespace consensus { |
58 | | |
59 | | using std::shared_ptr; |
60 | | using std::string; |
61 | | using std::unordered_map; |
62 | | using std::vector; |
63 | | using strings::Substitute; |
64 | | |
65 | | namespace { |
66 | | |
67 | | const MonoDelta kLeaderElectionTimeout = MonoDelta::FromSeconds(10); |
68 | | |
69 | | // Generate list of voter uuids. |
70 | 199 | static vector<string> GenVoterUUIDs(int num_voters) { |
71 | 199 | vector<string> voter_uuids; |
72 | 2.09k | for (int i = 0; i < num_voters; i++) { |
73 | 1.89k | voter_uuids.push_back(Substitute("peer-$0", i)); |
74 | 1.89k | } |
75 | 199 | return voter_uuids; |
76 | 199 | } |
77 | | |
78 | | } // namespace |
79 | | |
80 | | //////////////////////////////////////// |
81 | | // LeaderElectionTest |
82 | | //////////////////////////////////////// |
83 | | |
84 | | typedef unordered_map<string, PeerProxy*> ProxyMap; |
85 | | |
86 | | // A proxy factory that serves proxies from a map. |
87 | | class FromMapPeerProxyFactory : public PeerProxyFactory { |
88 | | public: |
89 | | explicit FromMapPeerProxyFactory(const ProxyMap* proxy_map) |
90 | 5 | : proxy_map_(proxy_map) { |
91 | 5 | } |
92 | | |
93 | 5 | ~FromMapPeerProxyFactory() { |
94 | 5 | DeleteUnusedPeerProxies(); |
95 | 5 | } |
96 | | |
97 | 396 | PeerProxyPtr NewProxy(const RaftPeerPB& peer_pb) override { |
98 | 396 | PeerProxy* proxy_ptr = FindPtrOrNull(*proxy_map_, peer_pb.permanent_uuid()); |
99 | 396 | if (proxy_ptr == nullptr) { |
100 | 0 | return nullptr; |
101 | 0 | } |
102 | 396 | used_peer_proxy_.insert(peer_pb.permanent_uuid()); |
103 | 396 | return PeerProxyPtr(proxy_ptr); |
104 | 396 | } |
105 | | |
106 | 197 | void DeleteUnusedPeerProxies() { |
107 | 1.69k | for (auto item : *proxy_map_) { |
108 | 1.69k | if (used_peer_proxy_.count(item.first) == 0) { |
109 | 1.29k | delete item.second; |
110 | 1.29k | } |
111 | 1.69k | } |
112 | 197 | used_peer_proxy_.clear(); |
113 | 197 | } |
114 | | |
115 | 0 | Messenger* messenger() const override { |
116 | 0 | return nullptr; |
117 | 0 | } |
118 | | |
119 | | private: |
120 | | // FYI, the tests may add and remove nodes from this map while we hold a |
121 | | // reference to it. |
122 | | const ProxyMap* const proxy_map_; |
123 | | std::set<string> used_peer_proxy_; |
124 | | }; |
125 | | |
126 | | class LeaderElectionTest : public YBTest { |
127 | | public: |
128 | | LeaderElectionTest() |
129 | | : tablet_id_("test-tablet"), |
130 | | proxy_factory_(new FromMapPeerProxyFactory(&proxies_)), |
131 | 5 | latch_(1) { |
132 | 5 | CHECK_OK(ThreadPoolBuilder("test-peer-pool").set_max_threads(5).Build(&pool_)); |
133 | 5 | } |
134 | | |
135 | | void ElectionCallback(const ElectionResult& result); |
136 | | |
137 | | protected: |
138 | | void InitUUIDs(int num_voters, int non_voters); |
139 | | void InitNoOpPeerProxies(int num_voters, |
140 | | int num_pre_voters, |
141 | | int num_pre_observers, |
142 | | int num_observers); |
143 | | void InitDelayableMockedProxies(int num_voters, |
144 | | int num_pre_voters, |
145 | | int num_pre_observers, |
146 | | int num_observers, |
147 | | bool enable_delay); |
148 | | std::unique_ptr<VoteCounter> InitVoteCounter(int num_voters, int majority_size); |
149 | | |
150 | | // Voter 0 is the high-term voter. |
151 | | LeaderElectionPtr SetUpElectionWithHighTermVoter(ConsensusTerm election_term); |
152 | | |
153 | | // Predetermine the election results using the specified number of |
154 | | // grant / deny / error responses. |
155 | | // num_grant must be at least 1, for the candidate to vote for itself. |
156 | | // num_grant + num_deny + num_error must add up to an odd number. |
157 | | LeaderElectionPtr SetUpElectionWithGrantDenyErrorVotes(ConsensusTerm election_term, |
158 | | int num_grant, |
159 | | int num_deny, |
160 | | int num_error); |
161 | | |
162 | | const string tablet_id_; |
163 | | string candidate_uuid_; |
164 | | vector<string> voter_uuids_; |
165 | | |
166 | | RaftConfigPB config_; |
167 | | ProxyMap proxies_; |
168 | | std::unique_ptr<PeerProxyFactory> proxy_factory_; |
169 | | std::unique_ptr<ThreadPool> pool_; |
170 | | |
171 | | CountDownLatch latch_; |
172 | | std::unique_ptr<ElectionResult> result_; |
173 | | }; |
174 | | |
175 | 196 | void LeaderElectionTest::ElectionCallback(const ElectionResult& result) { |
176 | 196 | result_.reset(new ElectionResult(result)); |
177 | 196 | latch_.CountDown(); |
178 | 196 | } |
179 | | |
180 | 196 | void LeaderElectionTest::InitUUIDs(int num_voters, int non_voters) { |
181 | 196 | ASSERT_GT(num_voters, 0); |
182 | 196 | voter_uuids_ = GenVoterUUIDs(num_voters + non_voters); |
183 | 196 | candidate_uuid_ = voter_uuids_[0]; |
184 | 196 | voter_uuids_.erase(voter_uuids_.begin()); |
185 | 196 | } |
186 | | |
187 | | void LeaderElectionTest::InitNoOpPeerProxies(int num_voters, |
188 | | int num_pre_voters, |
189 | | int num_pre_observers, |
190 | 192 | int num_observers) { |
191 | 192 | ASSERT_GT(num_voters, 0); |
192 | 192 | ASSERT_GE(num_pre_voters, 0); |
193 | 192 | ASSERT_GE(num_pre_observers, 0); |
194 | 192 | ASSERT_GE(num_observers, 0); |
195 | 192 | config_.Clear(); |
196 | | |
197 | | // Remove candidate_uuid_. |
198 | 192 | num_voters--; |
199 | 192 | ASSERT_EQ(voter_uuids_.size(), num_voters + num_pre_voters + num_pre_observers + num_observers); |
200 | 1.68k | for (const string& uuid : voter_uuids_) { |
201 | 1.68k | RaftPeerPB* peer_pb = config_.add_peers(); |
202 | 1.68k | PeerMemberType member_type; |
203 | 1.68k | if (num_voters > 0) { |
204 | 384 | member_type = PeerMemberType::VOTER; |
205 | 384 | num_voters--; |
206 | 1.29k | } else if (num_pre_voters > 0) { |
207 | 432 | member_type = PeerMemberType::PRE_VOTER; |
208 | 432 | num_pre_voters--; |
209 | 864 | } else if (num_pre_observers > 0) { |
210 | 432 | member_type = PeerMemberType::PRE_OBSERVER; |
211 | 432 | num_pre_observers--; |
212 | 432 | } else if (num_observers > 0) { |
213 | 432 | member_type = PeerMemberType::OBSERVER; |
214 | 432 | num_observers--; |
215 | 0 | } else { |
216 | 0 | member_type = PeerMemberType::UNKNOWN_MEMBER_TYPE; |
217 | 0 | LOG(FATAL) << "Invalid member type"; |
218 | 0 | } |
219 | 1.68k | peer_pb->set_member_type(member_type); |
220 | 1.68k | peer_pb->set_permanent_uuid(uuid); |
221 | 1.68k | PeerProxy* proxy = new NoOpTestPeerProxy(pool_.get(), *peer_pb); |
222 | 1.68k | InsertOrDie(&proxies_, uuid, proxy); |
223 | 1.68k | } |
224 | 192 | } |
225 | | |
226 | | void LeaderElectionTest::InitDelayableMockedProxies(int num_voters, |
227 | | int num_pre_voters, |
228 | | int num_pre_observers, |
229 | | int num_observers, |
230 | 4 | bool enable_delay) { |
231 | 4 | ASSERT_GT(num_voters, 0); |
232 | 4 | ASSERT_GE(num_pre_voters, 0); |
233 | 4 | ASSERT_GE(num_pre_observers, 0); |
234 | 4 | ASSERT_GE(num_observers, 0); |
235 | 4 | config_.Clear(); |
236 | | |
237 | | // Remove candidate_uuid_. |
238 | 4 | num_voters--; |
239 | 4 | ASSERT_EQ(voter_uuids_.size(), num_voters + num_pre_voters + num_pre_observers + num_observers); |
240 | 12 | for (const string& uuid : voter_uuids_) { |
241 | 12 | RaftPeerPB* peer_pb = config_.add_peers(); |
242 | 12 | PeerMemberType member_type; |
243 | 12 | if (num_voters > 0) { |
244 | 12 | member_type = PeerMemberType::VOTER; |
245 | 12 | num_voters--; |
246 | 0 | } else if (num_pre_voters > 0) { |
247 | 0 | member_type = PeerMemberType::PRE_VOTER; |
248 | 0 | num_pre_voters--; |
249 | 0 | } else if (num_pre_observers >0) { |
250 | 0 | member_type = PeerMemberType::PRE_OBSERVER; |
251 | 0 | num_pre_observers--; |
252 | 0 | } else if (num_observers > 0) { |
253 | 0 | member_type = PeerMemberType::OBSERVER; |
254 | 0 | num_observers--; |
255 | 0 | } else { |
256 | 0 | member_type = PeerMemberType::UNKNOWN_MEMBER_TYPE; |
257 | 0 | LOG(FATAL) << "Invalid member type"; |
258 | 0 | } |
259 | 12 | peer_pb->set_member_type(member_type); |
260 | 12 | peer_pb->set_permanent_uuid(uuid); |
261 | 12 | auto proxy = new DelayablePeerProxy<MockedPeerProxy>(pool_.get(), |
262 | 12 | new MockedPeerProxy(pool_.get())); |
263 | 12 | if (enable_delay) { |
264 | 4 | proxy->DelayResponse(); |
265 | 4 | } |
266 | 12 | InsertOrDie(&proxies_, uuid, proxy); |
267 | 12 | } |
268 | 4 | } |
269 | | |
270 | | std::unique_ptr<VoteCounter> LeaderElectionTest::InitVoteCounter( |
271 | 196 | int num_voters, int majority_size) { |
272 | 196 | auto counter = std::make_unique<VoteCounter>(num_voters, majority_size); |
273 | 196 | bool duplicate; |
274 | 196 | CHECK_OK(counter->RegisterVote(candidate_uuid_, ElectionVote::kGranted, &duplicate)); |
275 | 196 | CHECK(!duplicate); |
276 | 196 | return counter; |
277 | 196 | } |
278 | | |
279 | 2 | LeaderElectionPtr LeaderElectionTest::SetUpElectionWithHighTermVoter(ConsensusTerm election_term) { |
280 | 2 | const int kNumVoters = 3; |
281 | 2 | const int kMajoritySize = 2; |
282 | | |
283 | 2 | InitUUIDs(kNumVoters, 0); |
284 | 2 | InitDelayableMockedProxies(kNumVoters, 0, 0, 0, true); |
285 | 2 | auto counter = InitVoteCounter(kNumVoters, kMajoritySize); |
286 | | |
287 | 2 | VoteResponsePB response; |
288 | 2 | response.set_responder_uuid(voter_uuids_[0]); |
289 | 2 | response.set_responder_term(election_term + 1); |
290 | 2 | response.set_vote_granted(false); |
291 | 2 | response.mutable_consensus_error()->set_code(ConsensusErrorPB::INVALID_TERM); |
292 | 2 | StatusToPB(STATUS(InvalidArgument, "Bad term"), |
293 | 2 | response.mutable_consensus_error()->mutable_status()); |
294 | 2 | down_cast<DelayablePeerProxy<MockedPeerProxy>*>(proxies_[voter_uuids_[0]]) |
295 | 2 | ->proxy()->set_vote_response(response); |
296 | | |
297 | 2 | response.Clear(); |
298 | 2 | response.set_responder_uuid(voter_uuids_[1]); |
299 | 2 | response.set_responder_term(election_term); |
300 | 2 | response.set_vote_granted(true); |
301 | 2 | down_cast<DelayablePeerProxy<MockedPeerProxy>*>(proxies_[voter_uuids_[1]]) |
302 | 2 | ->proxy()->set_vote_response(response); |
303 | | |
304 | 2 | VoteRequestPB request; |
305 | 2 | request.set_candidate_uuid(candidate_uuid_); |
306 | 2 | request.set_candidate_term(election_term); |
307 | 2 | request.set_tablet_id(tablet_id_); |
308 | | |
309 | 2 | return make_scoped_refptr<LeaderElection>( |
310 | 2 | config_, proxy_factory_.get(), request, std::move(counter), kLeaderElectionTimeout, |
311 | 2 | PreElection::kFalse, TEST_SuppressVoteRequest::kFalse, |
312 | 2 | std::bind(&LeaderElectionTest::ElectionCallback, this, std::placeholders::_1)); |
313 | 2 | } |
314 | | |
315 | | LeaderElectionPtr LeaderElectionTest::SetUpElectionWithGrantDenyErrorVotes( |
316 | 2 | ConsensusTerm election_term, int num_grant, int num_deny, int num_error) { |
317 | 2 | const int kNumVoters = num_grant + num_deny + num_error; |
318 | 2 | CHECK_GE(num_grant, 1); // Gotta vote for yourself. |
319 | 2 | CHECK_EQ(1, kNumVoters % 2); // RaftConfig size must be odd. |
320 | 2 | const int kMajoritySize = (kNumVoters / 2) + 1; |
321 | | |
322 | 2 | InitUUIDs(kNumVoters, 0); |
323 | 2 | InitDelayableMockedProxies(kNumVoters, 0, 0, 0, false); // Don't delay the vote responses. |
324 | 2 | auto counter = InitVoteCounter(kNumVoters, kMajoritySize); |
325 | 2 | int num_grant_followers = num_grant - 1; |
326 | | |
327 | | // Set up mocked responses based on the params specified in the method arguments. |
328 | 2 | size_t voter_index = 0; |
329 | 10 | while (voter_index < voter_uuids_.size()) { |
330 | 8 | VoteResponsePB response; |
331 | 8 | if (num_grant_followers > 0) { |
332 | 1 | response.set_responder_uuid(voter_uuids_[voter_index]); |
333 | 1 | response.set_responder_term(election_term); |
334 | 1 | response.set_vote_granted(true); |
335 | 1 | --num_grant_followers; |
336 | 7 | } else if (num_deny > 0) { |
337 | 3 | response.set_responder_uuid(voter_uuids_[voter_index]); |
338 | 3 | response.set_responder_term(election_term); |
339 | 3 | response.set_vote_granted(false); |
340 | 3 | response.mutable_consensus_error()->set_code(ConsensusErrorPB::LAST_OPID_TOO_OLD); |
341 | 3 | StatusToPB(STATUS(InvalidArgument, "Last OpId"), |
342 | 3 | response.mutable_consensus_error()->mutable_status()); |
343 | 3 | --num_deny; |
344 | 4 | } else if (num_error > 0) { |
345 | 4 | response.mutable_error()->set_code(tserver::TabletServerErrorPB::TABLET_NOT_FOUND); |
346 | 4 | StatusToPB(STATUS(NotFound, "Unknown Tablet"), |
347 | 4 | response.mutable_error()->mutable_status()); |
348 | 4 | --num_error; |
349 | 0 | } else { |
350 | 0 | LOG(FATAL) << "Unexpected fallthrough"; |
351 | 0 | } |
352 | | |
353 | 8 | down_cast<DelayablePeerProxy<MockedPeerProxy>*>(proxies_[voter_uuids_[voter_index]]) |
354 | 8 | ->proxy()->set_vote_response(response); |
355 | 8 | ++voter_index; |
356 | 8 | } |
357 | | |
358 | 2 | VoteRequestPB request; |
359 | 2 | request.set_candidate_uuid(candidate_uuid_); |
360 | 2 | request.set_candidate_term(election_term); |
361 | 2 | request.set_tablet_id(tablet_id_); |
362 | | |
363 | 2 | return make_scoped_refptr<LeaderElection>( |
364 | 2 | config_, proxy_factory_.get(), request, std::move(counter), kLeaderElectionTimeout, |
365 | 2 | PreElection::kFalse, TEST_SuppressVoteRequest::kFalse, |
366 | 2 | std::bind(&LeaderElectionTest::ElectionCallback, this, std::placeholders::_1)); |
367 | 2 | } |
368 | | |
369 | | // All peers respond "yes", no failures. |
370 | 1 | TEST_F(LeaderElectionTest, TestPerfectElection) { |
371 | | // Try configuration sizes of 1, 3, 5. |
372 | 1 | const vector<int> pre_voters_config_sizes = { 0, 1, 3, 5 }; |
373 | 1 | const vector<int> pre_observers_config_sizes = { 0, 1, 3, 5 }; |
374 | 1 | const vector<int> observers_config_sizes = { 0, 1, 3, 5 }; |
375 | 1 | const vector<int> voters_config_sizes = { 1, 3, 5 }; |
376 | | |
377 | 4 | for (int num_pre_voters : pre_voters_config_sizes) { |
378 | 16 | for (int num_pre_observers : pre_observers_config_sizes) { |
379 | 64 | for (int num_observers : observers_config_sizes) { |
380 | 192 | for (int num_voters : voters_config_sizes) { |
381 | 192 | LOG(INFO) << "Testing election with config { voters: " << num_voters |
382 | 192 | << ", pre_voters: " << num_pre_voters |
383 | 192 | << ", pre_observers: " << num_pre_observers |
384 | 192 | << ", observers: " << num_observers << " }"; |
385 | 192 | int majority_size = (num_voters / 2) + 1; |
386 | 192 | ConsensusTerm election_term = 10 + num_voters; // Just to be able to differentiate. |
387 | | |
388 | 192 | InitUUIDs(num_voters, num_pre_voters + num_pre_observers + num_observers); |
389 | 192 | InitNoOpPeerProxies(num_voters, num_pre_voters, num_pre_observers, num_observers); |
390 | 192 | auto counter = InitVoteCounter(num_voters, majority_size); |
391 | | |
392 | 192 | VoteRequestPB request; |
393 | 192 | LOG(INFO) << "candidate_uuid_: " << candidate_uuid_; |
394 | 192 | request.set_candidate_uuid(candidate_uuid_); |
395 | 192 | request.set_candidate_term(election_term); |
396 | 192 | request.set_tablet_id(tablet_id_); |
397 | | |
398 | 192 | auto election = make_scoped_refptr<LeaderElection>( |
399 | 192 | config_, proxy_factory_.get(), request, std::move(counter), kLeaderElectionTimeout, |
400 | 192 | PreElection::kFalse, TEST_SuppressVoteRequest::kFalse, |
401 | 192 | std::bind(&LeaderElectionTest::ElectionCallback, this, std::placeholders::_1)); |
402 | 192 | election->Run(); |
403 | 192 | latch_.Wait(); |
404 | | |
405 | 192 | ASSERT_EQ(election_term, result_->election_term); |
406 | 192 | ASSERT_EQ(ElectionVote::kGranted, result_->decision); |
407 | | |
408 | 192 | pool_->Wait(); |
409 | 192 | FromMapPeerProxyFactory *from_map_proxy_factory = |
410 | 192 | down_cast<FromMapPeerProxyFactory*>(proxy_factory_.get()); |
411 | 192 | from_map_proxy_factory->DeleteUnusedPeerProxies(); |
412 | 192 | proxies_.clear(); // We don't delete them; The election VoterState object |
413 | | // ends up owning them. |
414 | 192 | latch_.Reset(1); |
415 | 192 | } |
416 | 64 | } |
417 | 16 | } |
418 | 4 | } |
419 | 1 | } |
420 | | |
421 | | // Test leader election when we encounter a peer with a higher term before we |
422 | | // have arrived at a majority decision. |
423 | 1 | TEST_F(LeaderElectionTest, TestHigherTermBeforeDecision) { |
424 | 1 | const ConsensusTerm kElectionTerm = 2; |
425 | 1 | auto election = SetUpElectionWithHighTermVoter(kElectionTerm); |
426 | 1 | election->Run(); |
427 | | |
428 | | // This guy has a higher term. |
429 | 1 | down_cast<DelayablePeerProxy<MockedPeerProxy>*>(proxies_[voter_uuids_[0]]) |
430 | 1 | ->Respond(TestPeerProxy::kRequestVote); |
431 | 1 | latch_.Wait(); |
432 | | |
433 | 1 | ASSERT_EQ(kElectionTerm, result_->election_term); |
434 | 1 | ASSERT_EQ(ElectionVote::kDenied, result_->decision); |
435 | 1 | ASSERT_TRUE(result_->higher_term); |
436 | 1 | ASSERT_EQ(kElectionTerm + 1, *result_->higher_term); |
437 | 1 | LOG(INFO) << "Election lost. Reason: " << result_->message; |
438 | | |
439 | | // This guy will vote "yes". |
440 | 1 | down_cast<DelayablePeerProxy<MockedPeerProxy>*>(proxies_[voter_uuids_[1]]) |
441 | 1 | ->Respond(TestPeerProxy::kRequestVote); |
442 | | |
443 | 1 | pool_->Wait(); // Wait for the election callbacks to finish before we destroy proxies. |
444 | 1 | } |
445 | | |
446 | | // Test leader election when we encounter a peer with a higher term after we |
447 | | // have arrived at a majority decision of "yes". |
448 | 1 | TEST_F(LeaderElectionTest, TestHigherTermAfterDecision) { |
449 | 1 | const ConsensusTerm kElectionTerm = 2; |
450 | 1 | auto election = SetUpElectionWithHighTermVoter(kElectionTerm); |
451 | 1 | election->Run(); |
452 | | |
453 | | // This guy will vote "yes". |
454 | 1 | down_cast<DelayablePeerProxy<MockedPeerProxy>*>(proxies_[voter_uuids_[1]]) |
455 | 1 | ->Respond(TestPeerProxy::kRequestVote); |
456 | 1 | latch_.Wait(); |
457 | | |
458 | 1 | ASSERT_EQ(kElectionTerm, result_->election_term); |
459 | 1 | ASSERT_EQ(ElectionVote::kGranted, result_->decision); |
460 | 1 | ASSERT_FALSE(result_->higher_term); |
461 | 1 | ASSERT_TRUE(result_->message.empty()); |
462 | 1 | LOG(INFO) << "Election won."; |
463 | | |
464 | | // This guy has a higher term. |
465 | 1 | down_cast<DelayablePeerProxy<MockedPeerProxy>*>(proxies_[voter_uuids_[0]]) |
466 | 1 | ->Respond(TestPeerProxy::kRequestVote); |
467 | | |
468 | 1 | pool_->Wait(); // Wait for the election callbacks to finish before we destroy proxies. |
469 | 1 | } |
470 | | |
471 | | // Out-of-date OpId "vote denied" case. |
472 | 1 | TEST_F(LeaderElectionTest, TestWithDenyVotes) { |
473 | 1 | const ConsensusTerm kElectionTerm = 2; |
474 | 1 | const int kNumGrant = 2; |
475 | 1 | const int kNumDeny = 3; |
476 | 1 | const int kNumError = 0; |
477 | 1 | auto election = SetUpElectionWithGrantDenyErrorVotes( |
478 | 1 | kElectionTerm, kNumGrant, kNumDeny, kNumError); |
479 | 1 | LOG(INFO) << "Running"; |
480 | 1 | election->Run(); |
481 | | |
482 | 1 | latch_.Wait(); |
483 | 1 | ASSERT_EQ(kElectionTerm, result_->election_term); |
484 | 1 | ASSERT_EQ(ElectionVote::kDenied, result_->decision); |
485 | 1 | ASSERT_FALSE(result_->higher_term); |
486 | 1 | ASSERT_TRUE(result_->message.empty()); |
487 | 1 | LOG(INFO) << "Election denied."; |
488 | | |
489 | 1 | pool_->Wait(); // Wait for the election callbacks to finish before we destroy proxies. |
490 | 1 | } |
491 | | |
492 | | // Count errors as denied votes. |
493 | 1 | TEST_F(LeaderElectionTest, TestWithErrorVotes) { |
494 | 1 | const ConsensusTerm kElectionTerm = 2; |
495 | 1 | const int kNumGrant = 1; |
496 | 1 | const int kNumDeny = 0; |
497 | 1 | const int kNumError = 4; |
498 | 1 | auto election = SetUpElectionWithGrantDenyErrorVotes( |
499 | 1 | kElectionTerm, kNumGrant, kNumDeny, kNumError); |
500 | 1 | election->Run(); |
501 | | |
502 | 1 | latch_.Wait(); |
503 | 1 | ASSERT_EQ(kElectionTerm, result_->election_term); |
504 | 1 | ASSERT_EQ(ElectionVote::kDenied, result_->decision); |
505 | 1 | ASSERT_FALSE(result_->higher_term); |
506 | 1 | ASSERT_TRUE(result_->message.empty()); |
507 | 1 | LOG(INFO) << "Election denied."; |
508 | | |
509 | 1 | pool_->Wait(); // Wait for the election callbacks to finish before we destroy proxies. |
510 | 1 | } |
511 | | |
512 | | //////////////////////////////////////// |
513 | | // VoteCounterTest |
514 | | //////////////////////////////////////// |
515 | | |
516 | | class VoteCounterTest : public YBTest { |
517 | | protected: |
518 | | static void AssertUndecided(const VoteCounter& counter); |
519 | | static void AssertVoteCount(const VoteCounter& counter, int yes_votes, int no_votes); |
520 | | }; |
521 | | |
522 | 14 | void VoteCounterTest::AssertUndecided(const VoteCounter& counter) { |
523 | 14 | ElectionVote decision = counter.GetDecision(); |
524 | 14 | ASSERT_EQ(decision, ElectionVote::kUnknown); |
525 | 14 | } |
526 | | |
527 | 20 | void VoteCounterTest::AssertVoteCount(const VoteCounter& counter, int yes_votes, int no_votes) { |
528 | 20 | ASSERT_EQ(yes_votes, counter.yes_votes_); |
529 | 20 | ASSERT_EQ(no_votes, counter.no_votes_); |
530 | 20 | ASSERT_EQ(yes_votes + no_votes, counter.GetTotalVotesCounted()); |
531 | 20 | } |
532 | | |
533 | | // Test basic vote counting functionality with an early majority. |
534 | 1 | TEST_F(VoteCounterTest, TestVoteCounter_EarlyDecision) { |
535 | 1 | const int kNumVoters = 3; |
536 | 1 | const int kMajoritySize = 2; |
537 | 1 | vector<string> voter_uuids = GenVoterUUIDs(kNumVoters); |
538 | | |
539 | | // "Yes" decision. |
540 | 1 | { |
541 | | // Start off undecided. |
542 | 1 | VoteCounter counter(kNumVoters, kMajoritySize); |
543 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
544 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 0)); |
545 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
546 | | |
547 | | // First yes vote. |
548 | 1 | bool duplicate; |
549 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[0], ElectionVote::kGranted, &duplicate)); |
550 | 1 | ASSERT_FALSE(duplicate); |
551 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
552 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 1, 0)); |
553 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
554 | | |
555 | | // Second yes vote wins it in a configuration of 3. |
556 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[1], ElectionVote::kGranted, &duplicate)); |
557 | 1 | ASSERT_FALSE(duplicate); |
558 | 1 | ASSERT_EQ(counter.GetDecision(), ElectionVote::kGranted); |
559 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 2, 0)); |
560 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
561 | 1 | } |
562 | | |
563 | | // "No" decision. |
564 | 1 | { |
565 | | // Start off undecided. |
566 | 1 | VoteCounter counter(kNumVoters, kMajoritySize); |
567 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
568 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 0)); |
569 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
570 | | |
571 | | // First no vote. |
572 | 1 | bool duplicate; |
573 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[0], ElectionVote::kDenied, &duplicate)); |
574 | 1 | ASSERT_FALSE(duplicate); |
575 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
576 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 1)); |
577 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
578 | | |
579 | | // Second no vote loses it in a configuration of 3. |
580 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[1], ElectionVote::kDenied, &duplicate)); |
581 | 1 | ASSERT_FALSE(duplicate); |
582 | 1 | ASSERT_EQ(counter.GetDecision(), ElectionVote::kDenied); |
583 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 2)); |
584 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
585 | 1 | } |
586 | 1 | } |
587 | | |
588 | | // Test basic vote counting functionality with the last vote being the deciding vote. |
589 | 1 | TEST_F(VoteCounterTest, TestVoteCounter_LateDecision) { |
590 | 1 | const int kNumVoters = 5; |
591 | 1 | const int kMajoritySize = 3; |
592 | 1 | vector<string> voter_uuids = GenVoterUUIDs(kNumVoters); |
593 | | |
594 | | // Start off undecided. |
595 | 1 | VoteCounter counter(kNumVoters, kMajoritySize); |
596 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
597 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 0)); |
598 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
599 | | |
600 | | // Add single yes vote, still undecided. |
601 | 1 | bool duplicate; |
602 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[0], ElectionVote::kGranted, &duplicate)); |
603 | 1 | ASSERT_FALSE(duplicate); |
604 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
605 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 1, 0)); |
606 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
607 | | |
608 | | // Attempt duplicate vote. |
609 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[0], ElectionVote::kGranted, &duplicate)); |
610 | 1 | ASSERT_TRUE(duplicate); |
611 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
612 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 1, 0)); |
613 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
614 | | |
615 | | // Attempt to change vote. |
616 | 1 | Status s = counter.RegisterVote(voter_uuids[0], ElectionVote::kDenied, &duplicate); |
617 | 1 | ASSERT_TRUE(s.IsInvalidArgument()); |
618 | 1 | ASSERT_STR_CONTAINS(s.ToString(), "voted a different way twice"); |
619 | 1 | LOG(INFO) << "Expected vote-changed error: " << s.ToString(); |
620 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
621 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 1, 0)); |
622 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
623 | | |
624 | | // Add more votes... |
625 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[1], ElectionVote::kDenied, &duplicate)); |
626 | 1 | ASSERT_FALSE(duplicate); |
627 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
628 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 1, 1)); |
629 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
630 | | |
631 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[2], ElectionVote::kGranted, &duplicate)); |
632 | 1 | ASSERT_FALSE(duplicate); |
633 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
634 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 2, 1)); |
635 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
636 | | |
637 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[3], ElectionVote::kDenied, &duplicate)); |
638 | 1 | ASSERT_FALSE(duplicate); |
639 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
640 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 2, 2)); |
641 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
642 | | |
643 | | // Win the election. |
644 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[4], ElectionVote::kGranted, &duplicate)); |
645 | 1 | ASSERT_FALSE(duplicate); |
646 | 1 | ASSERT_EQ(counter.GetDecision(), ElectionVote::kGranted); |
647 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 3, 2)); |
648 | 1 | ASSERT_TRUE(counter.AreAllVotesIn()); |
649 | | |
650 | | // Attempt to vote with > the whole configuration. |
651 | 1 | s = counter.RegisterVote("some-random-node", ElectionVote::kGranted, &duplicate); |
652 | 1 | ASSERT_TRUE(s.IsInvalidArgument()); |
653 | 1 | ASSERT_STR_CONTAINS(s.ToString(), "cause the number of votes to exceed the expected number"); |
654 | 1 | LOG(INFO) << "Expected voters-exceeded error: " << s.ToString(); |
655 | 1 | ASSERT_NE(counter.GetDecision(), ElectionVote::kUnknown); |
656 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 3, 2)); |
657 | 1 | ASSERT_TRUE(counter.AreAllVotesIn()); |
658 | 1 | } |
659 | | |
660 | | // Test vote counting with an even number of voters. |
661 | 1 | TEST_F(VoteCounterTest, TestVoteCounter_EvenVoters) { |
662 | 1 | const int kNumVoters = 2; |
663 | 1 | const int kMajoritySize = 2; |
664 | 1 | vector<string> voter_uuids = GenVoterUUIDs(kNumVoters); |
665 | | |
666 | | // "Yes" decision. |
667 | 1 | { |
668 | 1 | VoteCounter counter(kNumVoters, kMajoritySize); |
669 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
670 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 0)); |
671 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
672 | | |
673 | | // Initial yes vote. |
674 | 1 | bool duplicate; |
675 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[0], ElectionVote::kGranted, &duplicate)); |
676 | 1 | ASSERT_FALSE(duplicate); |
677 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
678 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 1, 0)); |
679 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
680 | | |
681 | | // Second yes vote wins it. |
682 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[1], ElectionVote::kGranted, &duplicate)); |
683 | 1 | ASSERT_FALSE(duplicate); |
684 | 1 | ASSERT_EQ(counter.GetDecision(), ElectionVote::kGranted); |
685 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 2, 0)); |
686 | 1 | ASSERT_TRUE(counter.AreAllVotesIn()); |
687 | 1 | } |
688 | | |
689 | | // "No" decision. |
690 | 1 | { |
691 | 1 | VoteCounter counter(kNumVoters, kMajoritySize); |
692 | 1 | ASSERT_NO_FATALS(AssertUndecided(counter)); |
693 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 0)); |
694 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
695 | | |
696 | | // The first "no" vote guarantees a failed election when num voters == 2. |
697 | 1 | bool duplicate; |
698 | 1 | ASSERT_OK(counter.RegisterVote(voter_uuids[0], ElectionVote::kDenied, &duplicate)); |
699 | 1 | ASSERT_FALSE(duplicate); |
700 | 1 | ASSERT_EQ(counter.GetDecision(), ElectionVote::kDenied); |
701 | 1 | ASSERT_NO_FATALS(AssertVoteCount(counter, 0, 1)); |
702 | 1 | ASSERT_FALSE(counter.AreAllVotesIn()); |
703 | 1 | } |
704 | 1 | } |
705 | | |
706 | | } // namespace consensus |
707 | | } // namespace yb |