summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authormatt dannenberg <matt.dannenberg@10gen.com>2015-06-11 09:47:34 -0400
committermatt dannenberg <matt.dannenberg@10gen.com>2015-06-16 11:56:16 -0400
commit64573bc33c058231bed22bd5fd44c609431b5a62 (patch)
tree6daab898e0eb28cb37ac68621f7f24baeb8297af /src
parent22dd61cfe60e05b13f9bb92aee3cb4660392435b (diff)
downloadmongo-64573bc33c058231bed22bd5fd44c609431b5a62.tar.gz
SERVER-18385 add DryRun support to RequestVotes, VoteRequester, and TopologyCoordinator
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/repl/repl_set_request_votes_args.cpp11
-rw-r--r--src/mongo/db/repl/repl_set_request_votes_args.h2
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl.cpp4
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp1
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl.cpp8
-rw-r--r--src/mongo/db/repl/topology_coordinator_impl_test.cpp194
-rw-r--r--src/mongo/db/repl/vote_requester.cpp6
-rw-r--r--src/mongo/db/repl/vote_requester.h3
-rw-r--r--src/mongo/db/repl/vote_requester_test.cpp119
9 files changed, 342 insertions, 6 deletions
diff --git a/src/mongo/db/repl/repl_set_request_votes_args.cpp b/src/mongo/db/repl/repl_set_request_votes_args.cpp
index da8123ab9f6..8c29e37b29d 100644
--- a/src/mongo/db/repl/repl_set_request_votes_args.cpp
+++ b/src/mongo/db/repl/repl_set_request_votes_args.cpp
@@ -39,6 +39,7 @@ namespace {
const std::string kCandidateIdFieldName = "candidateId";
const std::string kCommandName = "replSetRequestVotes";
const std::string kConfigVersionFieldName = "configVersion";
+ const std::string kDryRunFieldName = "dryRun";
const std::string kLastCommittedOpFieldName = "lastCommittedOp";
const std::string kOkFieldName = "ok";
const std::string kOpTimeFieldName = "ts";
@@ -51,6 +52,7 @@ namespace {
kCandidateIdFieldName,
kCommandName,
kConfigVersionFieldName,
+ kDryRunFieldName,
kLastCommittedOpFieldName,
kOpTimeFieldName,
kSetNameFieldName,
@@ -90,6 +92,10 @@ namespace {
if (!status.isOK())
return status;
+ status = bsonExtractBooleanField(argsObj, kDryRunFieldName, &_dryRun);
+ if (!status.isOK())
+ return status;
+
// extracting the lastCommittedOp is a bit of a process
BSONObj lastCommittedOp = argsObj[kLastCommittedOpFieldName].Obj();
Timestamp ts;
@@ -126,9 +132,14 @@ namespace {
return _lastCommittedOp;
}
+ bool ReplSetRequestVotesArgs::isADryRun() const {
+ return _dryRun;
+ }
+
void ReplSetRequestVotesArgs::addToBSON(BSONObjBuilder* builder) const {
builder->append(kCommandName, 1);
builder->append(kSetNameFieldName, _setName);
+ builder->append(kDryRunFieldName, _dryRun);
builder->append(kTermFieldName, _term);
builder->appendIntOrLL(kCandidateIdFieldName, _candidateId);
builder->appendIntOrLL(kConfigVersionFieldName, _cfgver);
diff --git a/src/mongo/db/repl/repl_set_request_votes_args.h b/src/mongo/db/repl/repl_set_request_votes_args.h
index f7152fe838e..a0f2dc2157b 100644
--- a/src/mongo/db/repl/repl_set_request_votes_args.h
+++ b/src/mongo/db/repl/repl_set_request_votes_args.h
@@ -47,6 +47,7 @@ namespace repl {
long long getCandidateId() const;
long long getConfigVersion() const;
OpTime getLastCommittedOp() const;
+ bool isADryRun() const;
void addToBSON(BSONObjBuilder* builder) const;
@@ -56,6 +57,7 @@ namespace repl {
long long _candidateId = -1; // replSet id of the member who sent the replSetRequestVotesCmd
long long _cfgver = -1; // replSet config version known to the command issuer
OpTime _lastCommittedOp; // The last known committed op of the command issuer
+ bool _dryRun = false; // Indicates this is a pre-election check when true
};
class ReplSetRequestVotesResponse {
diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp
index 23e15702299..a225b7f454d 100644
--- a/src/mongo/db/repl/replication_coordinator_impl.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl.cpp
@@ -2563,7 +2563,9 @@ namespace {
return {ErrorCodes::BadValue, "not using election protocol v1"};
}
- updateTerm(args.getTerm());
+ if (!args.isADryRun()) {
+ updateTerm(args.getTerm());
+ }
Status result{ErrorCodes::InternalError, "didn't set status in processReplSetRequestVotes"};
CBHStatus cbh = _replExecutor.scheduleWork(
diff --git a/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp b/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
index 25559eb5987..d774b1ee820 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
@@ -147,6 +147,7 @@ namespace {
_rsConfig,
_rsConfig.getMemberAt(_selfIndex).getId(),
_topCoord->getTerm(),
+ false, // TODO(dannenberg): make this the second election stage by adding a dryrun
getMyLastOptime(),
stdx::bind(&ReplicationCoordinatorImpl::_onVoteRequestComplete, this));
if (nextPhaseEvh.getStatus() == ErrorCodes::ShutdownInProgress) {
diff --git a/src/mongo/db/repl/topology_coordinator_impl.cpp b/src/mongo/db/repl/topology_coordinator_impl.cpp
index f600e266001..08d7ac7198c 100644
--- a/src/mongo/db/repl/topology_coordinator_impl.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl.cpp
@@ -2272,13 +2272,15 @@ namespace {
response->setVoteGranted(false);
response->setReason("candidate's data is staler than mine");
}
- else if (_lastVote.getTerm() == args.getTerm()) {
+ else if (!args.isADryRun() && _lastVote.getTerm() == args.getTerm()) {
response->setVoteGranted(false);
response->setReason("already voted for another candidate this term");
}
else {
- _lastVote.setTerm(args.getTerm());
- _lastVote.setCandidateId(args.getCandidateId());
+ if (!args.isADryRun()) {
+ _lastVote.setTerm(args.getTerm());
+ _lastVote.setCandidateId(args.getCandidateId());
+ }
response->setVoteGranted(true);
}
diff --git a/src/mongo/db/repl/topology_coordinator_impl_test.cpp b/src/mongo/db/repl/topology_coordinator_impl_test.cpp
index 49179bd4f49..7378feb4067 100644
--- a/src/mongo/db/repl/topology_coordinator_impl_test.cpp
+++ b/src/mongo/db/repl/topology_coordinator_impl_test.cpp
@@ -4600,6 +4600,83 @@ namespace {
}
+ TEST_F(TopoCoordTest, ProcessRequestVotesDryRunsDoNotDisallowFutureRequestVotes) {
+ updateConfig(BSON("_id" << "rs0" <<
+ "version" << 1 <<
+ "members" << BSON_ARRAY(
+ BSON("_id" << 10 << "host" << "hself") <<
+ BSON("_id" << 20 << "host" << "h2") <<
+ BSON("_id" << 30 << "host" << "h3"))),
+ 0);
+ setSelfMemberState(MemberState::RS_SECONDARY);
+
+ // dry run
+ ReplSetRequestVotesArgs args;
+ args.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << true
+ << "term" << 1LL
+ << "candidateId" << 10LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response;
+ OpTime lastAppliedOpTime;
+
+ getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
+ ASSERT_EQUALS("", response.getReason());
+ ASSERT_TRUE(response.getVoteGranted());
+
+ // second dry run fine
+ ReplSetRequestVotesArgs args2;
+ args2.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << true
+ << "term" << 1LL
+ << "candidateId" << 10LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response2;
+
+ getTopoCoord().processReplSetRequestVotes(args2, &response2, lastAppliedOpTime);
+ ASSERT_EQUALS("", response2.getReason());
+ ASSERT_TRUE(response2.getVoteGranted());
+
+ // real request fine
+ ReplSetRequestVotesArgs args3;
+ args3.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << false
+ << "term" << 1LL
+ << "candidateId" << 10LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response3;
+
+ getTopoCoord().processReplSetRequestVotes(args3, &response3, lastAppliedOpTime);
+ ASSERT_EQUALS("", response3.getReason());
+ ASSERT_TRUE(response3.getVoteGranted());
+
+ // dry post real, fails
+ ReplSetRequestVotesArgs args4;
+ args4.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << false
+ << "term" << 1LL
+ << "candidateId" << 10LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response4;
+
+ getTopoCoord().processReplSetRequestVotes(args4, &response4, lastAppliedOpTime);
+ ASSERT_EQUALS("already voted for another candidate this term", response4.getReason());
+ ASSERT_FALSE(response4.getVoteGranted());
+
+ }
+
TEST_F(TopoCoordTest, ProcessRequestVotesBadCommands) {
updateConfig(BSON("_id" << "rs0" <<
"version" << 1 <<
@@ -4685,6 +4762,123 @@ namespace {
ASSERT_FALSE(response4.getVoteGranted());
}
+ TEST_F(TopoCoordTest, ProcessRequestVotesBadCommandsDryRun) {
+ updateConfig(BSON("_id" << "rs0" <<
+ "version" << 1 <<
+ "members" << BSON_ARRAY(
+ BSON("_id" << 10 << "host" << "hself") <<
+ BSON("_id" << 20 << "host" << "h2") <<
+ BSON("_id" << 30 << "host" << "h3"))),
+ 0);
+ setSelfMemberState(MemberState::RS_SECONDARY);
+ // set term to 1
+ ASSERT(getTopoCoord().updateTerm(1));
+ // and make sure we voted in term 1
+ ReplSetRequestVotesArgs argsForRealVote;
+ argsForRealVote.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "term" << 1LL
+ << "candidateId" << 10LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse responseForRealVote;
+ OpTime lastAppliedOpTime;
+
+ getTopoCoord().processReplSetRequestVotes(argsForRealVote,
+ &responseForRealVote,
+ lastAppliedOpTime);
+ ASSERT_EQUALS("", responseForRealVote.getReason());
+ ASSERT_TRUE(responseForRealVote.getVoteGranted());
+
+
+ // mismatched setName
+ ReplSetRequestVotesArgs args;
+ args.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "wrongName"
+ << "dryRun" << true
+ << "term" << 2LL
+ << "candidateId" << 10LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response;
+
+ getTopoCoord().processReplSetRequestVotes(args, &response, lastAppliedOpTime);
+ ASSERT_EQUALS("candidate's set name differs from mine", response.getReason());
+ ASSERT_EQUALS(1, response.getTerm());
+ ASSERT_FALSE(response.getVoteGranted());
+
+ // mismatched configVersion
+ ReplSetRequestVotesArgs args2;
+ args2.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << true
+ << "term" << 2LL
+ << "candidateId" << 20LL
+ << "configVersion" << 0LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response2;
+
+ getTopoCoord().processReplSetRequestVotes(args2, &response2, lastAppliedOpTime);
+ ASSERT_EQUALS("candidate's config version differs from mine", response2.getReason());
+ ASSERT_EQUALS(1, response2.getTerm());
+ ASSERT_FALSE(response2.getVoteGranted());
+
+ // stale term
+ ReplSetRequestVotesArgs args3;
+ args3.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << true
+ << "term" << 0LL
+ << "candidateId" << 20LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response3;
+
+ getTopoCoord().processReplSetRequestVotes(args3, &response3, lastAppliedOpTime);
+ ASSERT_EQUALS("candidate's term is lower than mine", response3.getReason());
+ ASSERT_EQUALS(1, response3.getTerm());
+ ASSERT_FALSE(response3.getVoteGranted());
+
+ // repeat term
+ ReplSetRequestVotesArgs args4;
+ args4.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << true
+ << "term" << 1LL
+ << "candidateId" << 20LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response4;
+
+ getTopoCoord().processReplSetRequestVotes(args4, &response4, lastAppliedOpTime);
+ ASSERT_EQUALS("", response4.getReason());
+ ASSERT_EQUALS(1, response4.getTerm());
+ ASSERT_TRUE(response4.getVoteGranted());
+
+ // stale OpTime
+ ReplSetRequestVotesArgs args5;
+ args5.initialize(BSON("replSetRequestVotes" << 1
+ << "setName" << "rs0"
+ << "dryRun" << true
+ << "term" << 3LL
+ << "candidateId" << 20LL
+ << "configVersion" << 1LL
+ << "lastCommittedOp" << BSON ("ts" << Timestamp(10, 0)
+ << "term" << 0LL)));
+ ReplSetRequestVotesResponse response5;
+ OpTime lastAppliedOpTime2 = {Timestamp(20, 0), 0};
+
+ getTopoCoord().processReplSetRequestVotes(args5, &response5, lastAppliedOpTime2);
+ ASSERT_EQUALS("candidate's data is staler than mine", response5.getReason());
+ ASSERT_EQUALS(1, response5.getTerm());
+ ASSERT_FALSE(response5.getVoteGranted());
+ }
+
TEST_F(TopoCoordTest, ProcessDeclareElectionWinner) {
updateConfig(BSON("_id" << "rs0" <<
"version" << 1 <<
diff --git a/src/mongo/db/repl/vote_requester.cpp b/src/mongo/db/repl/vote_requester.cpp
index 5c51df02eb1..81691edf5d7 100644
--- a/src/mongo/db/repl/vote_requester.cpp
+++ b/src/mongo/db/repl/vote_requester.cpp
@@ -45,10 +45,12 @@ namespace repl {
VoteRequester::Algorithm::Algorithm(const ReplicaSetConfig& rsConfig,
long long candidateId,
long long term,
+ bool dryRun,
OpTime lastOplogEntry) :
_rsConfig(rsConfig),
_candidateId(candidateId),
_term(term),
+ _dryRun(dryRun),
_lastOplogEntry(lastOplogEntry) {
// populate targets with all voting members that aren't this node
@@ -66,6 +68,7 @@ namespace repl {
BSONObjBuilder requestVotesCmdBuilder;
requestVotesCmdBuilder.append("replSetRequestVotes", 1);
requestVotesCmdBuilder.append("setName", _rsConfig.getReplSetName());
+ requestVotesCmdBuilder.append("dryRun", _dryRun);
requestVotesCmdBuilder.append("term", _term);
requestVotesCmdBuilder.append("candidateId", _candidateId);
requestVotesCmdBuilder.append("configVersion", _rsConfig.getConfigVersion());
@@ -112,7 +115,6 @@ namespace repl {
_staleTerm = true;
}
}
-
}
bool VoteRequester::Algorithm::hasReceivedSufficientResponses() const {
@@ -141,12 +143,14 @@ namespace repl {
const ReplicaSetConfig& rsConfig,
long long candidateId,
long long term,
+ bool dryRun,
OpTime lastOplogEntry,
const stdx::function<void ()>& onCompletion) {
_algorithm.reset(new Algorithm(rsConfig,
candidateId,
term,
+ dryRun,
lastOplogEntry));
_runner.reset(new ScatterGatherRunner(_algorithm.get()));
return _runner->start(executor, onCompletion);
diff --git a/src/mongo/db/repl/vote_requester.h b/src/mongo/db/repl/vote_requester.h
index a1659349bea..a5102192ebd 100644
--- a/src/mongo/db/repl/vote_requester.h
+++ b/src/mongo/db/repl/vote_requester.h
@@ -63,6 +63,7 @@ namespace repl {
Algorithm(const ReplicaSetConfig& rsConfig,
long long candidateId,
long long term,
+ bool dryRun,
OpTime lastOplogEntry);
virtual ~Algorithm();
virtual std::vector<RemoteCommandRequest> getRequests() const;
@@ -82,6 +83,7 @@ namespace repl {
const ReplicaSetConfig _rsConfig;
const long long _candidateId;
const long long _term;
+ bool _dryRun = false; // this bool indicates this is a mock election when true
const OpTime _lastOplogEntry;
std::vector<HostAndPort> _targets;
bool _staleTerm = false;
@@ -106,6 +108,7 @@ namespace repl {
const ReplicaSetConfig& rsConfig,
long long candidateId,
long long term,
+ bool dryRun,
OpTime lastOplogEntry,
const stdx::function<void ()>& onCompletion = stdx::function<void ()>());
diff --git a/src/mongo/db/repl/vote_requester_test.cpp b/src/mongo/db/repl/vote_requester_test.cpp
index 90d0843d843..d5b9d9ce3d5 100644
--- a/src/mongo/db/repl/vote_requester_test.cpp
+++ b/src/mongo/db/repl/vote_requester_test.cpp
@@ -75,6 +75,7 @@ namespace {
_requester.reset(new VoteRequester::Algorithm(config,
candidateId,
term,
+ false, // not a dryRun
lastOplogEntry));
}
@@ -173,10 +174,36 @@ namespace {
Milliseconds(10)));
}
- private:
std::unique_ptr<VoteRequester::Algorithm> _requester;
};
+ class VoteRequesterDryRunTest : public VoteRequesterTest {
+ public:
+ virtual void setUp() {
+ ReplicaSetConfig config;
+ ASSERT_OK(config.initialize(
+ BSON("_id" << "rs0" <<
+ "version" << 2 <<
+ "members" << BSON_ARRAY(
+ BSON("_id" << 0 << "host" << "host0") <<
+ BSON("_id" << 1 << "host" << "host1") <<
+ BSON("_id" << 2 << "host" << "host2") <<
+ BSON("_id" << 3 << "host" << "host3" << "votes" << 0) <<
+ BSON("_id" << 4 << "host" << "host4" << "votes" << 0)))));
+ ASSERT_OK(config.validate());
+ long long candidateId = 0;
+ long long term = 2;
+ OpTime lastOplogEntry = OpTime(Timestamp(999,0), 1);
+
+ _requester.reset(new VoteRequester::Algorithm(config,
+ candidateId,
+ term,
+ true, // dryRun
+ lastOplogEntry));
+ }
+
+ };
+
TEST_F(VoteRequesterTest, ImmediateGoodResponseWinElection) {
ASSERT_FALSE(hasReceivedSufficientResponses());
processResponse(requestFrom("host1"), votedYes());
@@ -267,6 +294,96 @@ namespace {
stopCapturingLogMessages();
}
+ TEST_F(VoteRequesterDryRunTest, ImmediateGoodResponseWinElection) {
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), votedYes());
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::SuccessfullyElected, getResult());
+ }
+
+ TEST_F(VoteRequesterDryRunTest, BadConfigVersionWinElection) {
+ startCapturingLogMessages();
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), votedNoBecauseConfigVersionDoesNotMatch());
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got no vote from host1"));
+ processResponse(requestFrom("host2"), votedYes());
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::SuccessfullyElected, getResult());
+ stopCapturingLogMessages();
+ }
+
+ TEST_F(VoteRequesterDryRunTest, SetNameDiffersWinElection) {
+ startCapturingLogMessages();
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), votedNoBecauseSetNameDiffers());
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got no vote from host1"));
+ processResponse(requestFrom("host2"), votedYes());
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::SuccessfullyElected, getResult());
+ stopCapturingLogMessages();
+ }
+
+ TEST_F(VoteRequesterDryRunTest, LastOpTimeIsGreaterWinElection) {
+ startCapturingLogMessages();
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), votedNoBecauseLastOpTimeIsGreater());
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got no vote from host1"));
+ processResponse(requestFrom("host2"), votedYes());
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::SuccessfullyElected, getResult());
+ stopCapturingLogMessages();
+ }
+
+ TEST_F(VoteRequesterDryRunTest, FailedToContactWinElection) {
+ startCapturingLogMessages();
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), badResponseStatus());
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got failed response from host1"));
+ processResponse(requestFrom("host2"), votedYes());
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::SuccessfullyElected, getResult());
+ stopCapturingLogMessages();
+ }
+
+ TEST_F(VoteRequesterDryRunTest, AlreadyVotedWinElection) {
+ startCapturingLogMessages();
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), votedNoBecauseAlreadyVoted());
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got no vote from host1"));
+ processResponse(requestFrom("host2"), votedYes());
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::SuccessfullyElected, getResult());
+ stopCapturingLogMessages();
+ }
+
+ TEST_F(VoteRequesterDryRunTest, StaleTermLoseElection) {
+ startCapturingLogMessages();
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), votedNoBecauseTermIsGreater());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got no vote from host1"));
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::StaleTerm, getResult());
+ stopCapturingLogMessages();
+ }
+
+ TEST_F(VoteRequesterDryRunTest, NotEnoughVotesLoseElection) {
+ startCapturingLogMessages();
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ processResponse(requestFrom("host1"), votedNoBecauseSetNameDiffers());
+ ASSERT_FALSE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got no vote from host1"));
+ processResponse(requestFrom("host2"), badResponseStatus());
+ ASSERT_EQUALS(1, countLogLinesContaining("Got failed response from host2"));
+ ASSERT_TRUE(hasReceivedSufficientResponses());
+ ASSERT_EQUALS(VoteRequester::InsufficientVotes, getResult());
+ stopCapturingLogMessages();
+ }
+
} // namespace
} // namespace repl
} // namespace mongo