/**
* Copyright (C) 2015 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/platform/basic.h"
#include
#include "mongo/base/status.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/repl/vote_requester.h"
#include "mongo/db/repl/repl_set_request_votes_args.h"
#include "mongo/db/repl/replication_executor.h"
#include "mongo/executor/network_interface_mock.h"
#include "mongo/stdx/functional.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
namespace repl {
namespace {
using executor::NetworkInterfaceMock;
using executor::RemoteCommandRequest;
using executor::RemoteCommandResponse;
using unittest::assertGet;
bool stringContains(const std::string& haystack, const std::string& needle) {
return haystack.find(needle) != std::string::npos;
}
class VoteRequesterTest : public mongo::unittest::Test {
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 << "priority" << 0)
<< BSON("_id" << 4 << "host"
<< "host4"
<< "votes" << 0 << "priority" << 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,
false, // not a dryRun
lastOplogEntry));
}
virtual void tearDown() {
_requester.reset(NULL);
}
protected:
int64_t countLogLinesContaining(const std::string& needle) {
return std::count_if(getCapturedLogMessages().begin(),
getCapturedLogMessages().end(),
stdx::bind(stringContains, stdx::placeholders::_1, needle));
}
bool hasReceivedSufficientResponses() {
return _requester->hasReceivedSufficientResponses();
}
void processResponse(const RemoteCommandRequest& request, const ResponseStatus& response) {
_requester->processResponse(request, response);
}
int getNumResponders() {
return _requester->getResponders().size();
}
VoteRequester::Result getResult() {
return _requester->getResult();
}
RemoteCommandRequest requestFrom(std::string hostname) {
return RemoteCommandRequest(HostAndPort(hostname),
"", // fields do not matter in VoteRequester
BSONObj(),
Milliseconds(0));
}
ResponseStatus badResponseStatus() {
return ResponseStatus(ErrorCodes::NodeNotFound, "not on my watch");
}
ResponseStatus votedYes() {
ReplSetRequestVotesResponse response;
response.setVoteGranted(true);
response.setTerm(1);
return ResponseStatus(
NetworkInterfaceMock::Response(response.toBSON(), BSONObj(), Milliseconds(10)));
}
ResponseStatus votedNoBecauseConfigVersionDoesNotMatch() {
ReplSetRequestVotesResponse response;
response.setVoteGranted(false);
response.setTerm(1);
response.setReason("candidate's config version differs from mine");
return ResponseStatus(
NetworkInterfaceMock::Response(response.toBSON(), BSONObj(), Milliseconds(10)));
}
ResponseStatus votedNoBecauseSetNameDiffers() {
ReplSetRequestVotesResponse response;
response.setVoteGranted(false);
response.setTerm(1);
response.setReason("candidate's set name differs from mine");
return ResponseStatus(
NetworkInterfaceMock::Response(response.toBSON(), BSONObj(), Milliseconds(10)));
}
ResponseStatus votedNoBecauseLastOpTimeIsGreater() {
ReplSetRequestVotesResponse response;
response.setVoteGranted(false);
response.setTerm(1);
response.setReason("candidate's data is staler than mine");
return ResponseStatus(
NetworkInterfaceMock::Response(response.toBSON(), BSONObj(), Milliseconds(10)));
}
ResponseStatus votedNoBecauseTermIsGreater() {
ReplSetRequestVotesResponse response;
response.setVoteGranted(false);
response.setTerm(3);
response.setReason("candidate's term is lower than mine");
return ResponseStatus(
NetworkInterfaceMock::Response(response.toBSON(), BSONObj(), Milliseconds(10)));
}
ResponseStatus votedNoBecauseAlreadyVoted() {
ReplSetRequestVotesResponse response;
response.setVoteGranted(false);
response.setTerm(2);
response.setReason("already voted for another candidate this term");
return ResponseStatus(
NetworkInterfaceMock::Response(response.toBSON(), BSONObj(), Milliseconds(10)));
}
std::unique_ptr _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 << "priority" << 0)
<< BSON("_id" << 4 << "host"
<< "host4"
<< "votes" << 0 << "priority" << 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());
ASSERT_TRUE(hasReceivedSufficientResponses());
ASSERT(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(1, getNumResponders());
}
TEST_F(VoteRequesterTest, 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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
stopCapturingLogMessages();
}
TEST_F(VoteRequesterTest, 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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
stopCapturingLogMessages();
}
TEST_F(VoteRequesterTest, 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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
stopCapturingLogMessages();
}
TEST_F(VoteRequesterTest, 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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(1, getNumResponders());
stopCapturingLogMessages();
}
TEST_F(VoteRequesterTest, 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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
stopCapturingLogMessages();
}
TEST_F(VoteRequesterTest, StaleTermLoseElection) {
startCapturingLogMessages();
ASSERT_FALSE(hasReceivedSufficientResponses());
processResponse(requestFrom("host1"), votedNoBecauseTermIsGreater());
ASSERT_EQUALS(1, countLogLinesContaining("Got no vote from host1"));
ASSERT_TRUE(hasReceivedSufficientResponses());
ASSERT(VoteRequester::Result::kStaleTerm == getResult());
ASSERT_EQUALS(1, getNumResponders());
stopCapturingLogMessages();
}
TEST_F(VoteRequesterTest, 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(VoteRequester::Result::kInsufficientVotes == getResult());
ASSERT_EQUALS(1, getNumResponders());
stopCapturingLogMessages();
}
TEST_F(VoteRequesterDryRunTest, ImmediateGoodResponseWinElection) {
ASSERT_FALSE(hasReceivedSufficientResponses());
processResponse(requestFrom("host1"), votedYes());
ASSERT_TRUE(hasReceivedSufficientResponses());
ASSERT(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(1, getNumResponders());
}
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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(1, getNumResponders());
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(VoteRequester::Result::kSuccessfullyElected == getResult());
ASSERT_EQUALS(2, getNumResponders());
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(VoteRequester::Result::kStaleTerm == getResult());
ASSERT_EQUALS(1, getNumResponders());
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(VoteRequester::Result::kInsufficientVotes == getResult());
ASSERT_EQUALS(1, getNumResponders());
stopCapturingLogMessages();
}
} // namespace
} // namespace repl
} // namespace mongo