summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavithra Vetriselvan <pavithra.vetriselvan@mongodb.com>2020-01-23 17:27:40 +0000
committerevergreen <evergreen@mongodb.com>2020-01-23 17:27:40 +0000
commitdbdef20f64f4a9e6cee83967cbb4019af58d92cb (patch)
tree025cbc0edf034cd8f08d27a1fe909f134c8333ff
parent3cd3a6da3fe0b6f022b721094bda0b97c3527d23 (diff)
downloadmongo-dbdef20f64f4a9e6cee83967cbb4019af58d92cb.tar.gz
SERVER-45080 ignore voteRequest from nodes with lower configs
-rw-r--r--src/mongo/db/repl/repl_set_request_votes_args.cpp20
-rw-r--r--src/mongo/db/repl/repl_set_request_votes_args.h5
-rw-r--r--src/mongo/db/repl/topology_coordinator.cpp59
-rw-r--r--src/mongo/db/repl/topology_coordinator_v1_test.cpp137
-rw-r--r--src/mongo/db/repl/vote_requester.cpp1
5 files changed, 173 insertions, 49 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 57256d71554..3c8538606ed 100644
--- a/src/mongo/db/repl/repl_set_request_votes_args.cpp
+++ b/src/mongo/db/repl/repl_set_request_votes_args.cpp
@@ -33,6 +33,7 @@
#include "mongo/bson/util/bson_extract.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/repl/bson_extract_optime.h"
+#include "mongo/db/server_options.h"
namespace mongo {
namespace repl {
@@ -41,6 +42,7 @@ namespace {
const std::string kCandidateIndexFieldName = "candidateIndex";
const std::string kCommandName = "replSetRequestVotes";
const std::string kConfigVersionFieldName = "configVersion";
+const std::string kConfigTermFieldName = "configTerm";
const std::string kDryRunFieldName = "dryRun";
// The underlying field name is inaccurate, but changing it requires a fair amount of cross
// compatibility work for no real benefit.
@@ -56,6 +58,7 @@ const std::string kLegalArgsFieldNames[] = {
kCandidateIndexFieldName,
kCommandName,
kConfigVersionFieldName,
+ kConfigTermFieldName,
kDryRunFieldName,
kLastDurableOpTimeFieldName,
kSetNameFieldName,
@@ -84,6 +87,13 @@ Status ReplSetRequestVotesArgs::initialize(const BSONObj& argsObj) {
if (!status.isOK())
return status;
+ // In order to be compatible with FCV 4.2, default the config term to -1 if we are unable
+ // parse a configTerm field from the args.
+ status = bsonExtractIntegerFieldWithDefault(
+ argsObj, kConfigTermFieldName, OpTime::kUninitializedTerm, &_cfgterm);
+ if (!status.isOK())
+ return status;
+
status = bsonExtractStringField(argsObj, kSetNameFieldName, &_setName);
if (!status.isOK())
return status;
@@ -115,6 +125,10 @@ long long ReplSetRequestVotesArgs::getConfigVersion() const {
return _cfgver;
}
+long long ReplSetRequestVotesArgs::getConfigTerm() const {
+ return _cfgterm;
+}
+
OpTime ReplSetRequestVotesArgs::getLastDurableOpTime() const {
return _lastDurableOpTime;
}
@@ -130,6 +144,12 @@ void ReplSetRequestVotesArgs::addToBSON(BSONObjBuilder* builder) const {
builder->append(kTermFieldName, _term);
builder->appendIntOrLL(kCandidateIndexFieldName, _candidateIndex);
builder->appendIntOrLL(kConfigVersionFieldName, _cfgver);
+ // Only append the config term field if we are in FCV 4.4
+ if (serverGlobalParams.featureCompatibility.isVersionInitialized() &&
+ serverGlobalParams.featureCompatibility.getVersion() ==
+ ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44) {
+ builder->appendIntOrLL(kConfigTermFieldName, _cfgterm);
+ }
_lastDurableOpTime.append(builder, kLastDurableOpTimeFieldName);
}
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 dfecbdba4d4..1dc74eb4f12 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 @@ public:
long long getTerm() const;
long long getCandidateIndex() const;
long long getConfigVersion() const;
+ long long getConfigTerm() const;
OpTime getLastDurableOpTime() const;
bool isADryRun() const;
@@ -58,7 +59,9 @@ private:
long long _term = -1; // Current known term of the command issuer.
// replSet config index of the member who sent the replSetRequestVotesCmd.
long long _candidateIndex = -1;
- long long _cfgver = -1; // replSet config version known to the command issuer.
+ long long _cfgver = -1; // replSet config version known to the command issuer.
+ // replSet config term known to the command issuer.
+ long long _cfgterm = OpTime::kUninitializedTerm;
OpTime _lastDurableOpTime; // The last known durable op of the command issuer.
bool _dryRun = false; // Indicates this is a pre-election check when true.
};
diff --git a/src/mongo/db/repl/topology_coordinator.cpp b/src/mongo/db/repl/topology_coordinator.cpp
index df4e7132293..8f7dca8cdcd 100644
--- a/src/mongo/db/repl/topology_coordinator.cpp
+++ b/src/mongo/db/repl/topology_coordinator.cpp
@@ -2748,41 +2748,60 @@ void TopologyCoordinator::processReplSetRequestVotes(const ReplSetRequestVotesAr
return;
}
+ // If either config term is -1, ignore the config term entirely and compare config versions.
+ bool compareConfigTerms = args.getConfigTerm() != -1 && _rsConfig.getConfigTerm() != -1;
+
if (args.getTerm() < _term) {
response->setVoteGranted(false);
- response->setReason(str::stream() << "candidate's term (" << args.getTerm()
- << ") is lower than mine (" << _term << ")");
- } else if (args.getConfigVersion() != _rsConfig.getConfigVersion()) {
+ response->setReason(str::stream() << "candidate's term ({}) is lower than mine ({})"_format(
+ args.getTerm(), _term));
+ } else if (compareConfigTerms && args.getConfigTerm() < _rsConfig.getConfigTerm()) {
+ response->setVoteGranted(false);
+ response->setReason(str::stream()
+ << "candidate's term in config(term, version): ({}, {}) is lower "
+ "than mine ({}, {})"_format(args.getConfigTerm(),
+ args.getConfigVersion(),
+ _rsConfig.getConfigTerm(),
+ _rsConfig.getConfigVersion()));
+ } else if ((!compareConfigTerms || args.getConfigTerm() == _rsConfig.getConfigTerm()) &&
+ args.getConfigVersion() < _rsConfig.getConfigVersion()) {
+ // If the terms should not be compared or if the terms are equal, fall back to version
+ // comparison.
response->setVoteGranted(false);
response->setReason(str::stream()
- << "candidate's config version (" << args.getConfigVersion()
- << ") differs from mine (" << _rsConfig.getConfigVersion() << ")");
+ << "ignoring term of -1 for comparison, candidate's version in "
+ "config(term, version): ({}, {}) is lower than mine ({}, {})"_format(
+ args.getConfigTerm(),
+ args.getConfigVersion(),
+ _rsConfig.getConfigTerm(),
+ _rsConfig.getConfigVersion()));
} else if (args.getSetName() != _rsConfig.getReplSetName()) {
response->setVoteGranted(false);
response->setReason(str::stream()
- << "candidate's set name (" << args.getSetName()
- << ") differs from mine (" << _rsConfig.getReplSetName() << ")");
+ << "candidate's set name ({}) differs from mine ({})"_format(
+ args.getSetName(), _rsConfig.getReplSetName()));
} else if (args.getLastDurableOpTime() < getMyLastAppliedOpTime()) {
response->setVoteGranted(false);
- response
- ->setReason(str::stream()
- << "candidate's data is staler than mine. candidate's last applied OpTime: "
- << args.getLastDurableOpTime().toString()
- << ", my last applied OpTime: " << getMyLastAppliedOpTime().toString());
+ response->setReason(str::stream()
+ << "candidate's data is staler than mine. candidate's last applied "
+ "OpTime: {}, my last applied OpTime: {}"_format(
+ args.getLastDurableOpTime().toString(),
+ getMyLastAppliedOpTime().toString()));
} else if (!args.isADryRun() && _lastVote.getTerm() == args.getTerm()) {
response->setVoteGranted(false);
- response->setReason(str::stream()
- << "already voted for another candidate ("
- << _rsConfig.getMemberAt(_lastVote.getCandidateIndex()).getHostAndPort()
- << ") this term (" << _lastVote.getTerm() << ")");
+ response->setReason(
+ str::stream() << "already voted for another candidate ({}) this "
+ "term ({})"_format(_rsConfig.getMemberAt(_lastVote.getCandidateIndex())
+ .getHostAndPort(),
+ _lastVote.getTerm()));
} else {
int betterPrimary = _findHealthyPrimaryOfEqualOrGreaterPriority(args.getCandidateIndex());
if (_selfConfig().isArbiter() && betterPrimary >= 0) {
response->setVoteGranted(false);
- response->setReason(str::stream()
- << "can see a healthy primary ("
- << _rsConfig.getMemberAt(betterPrimary).getHostAndPort()
- << ") of equal or greater priority");
+ response
+ ->setReason(str::stream()
+ << "can see a healthy primary ({}) of equal or greater priority"_format(
+ _rsConfig.getMemberAt(betterPrimary).getHostAndPort()));
} else {
if (!args.isADryRun()) {
_lastVote.setTerm(args.getTerm());
diff --git a/src/mongo/db/repl/topology_coordinator_v1_test.cpp b/src/mongo/db/repl/topology_coordinator_v1_test.cpp
index 5a6da50ee33..54519ab8390 100644
--- a/src/mongo/db/repl/topology_coordinator_v1_test.cpp
+++ b/src/mongo/db/repl/topology_coordinator_v1_test.cpp
@@ -2484,7 +2484,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVotesToTwoDifferentNodesInTheSameTerm) {
args.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 0LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2498,7 +2499,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVotesToTwoDifferentNodesInTheSameTerm) {
.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 1LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response2;
@@ -2562,7 +2564,7 @@ TEST_F(TopoCoordTest, DryRunVoteRequestShouldNotPreventSubsequentDryRunsForThatT
<< "rs0"
<< "dryRun" << true << "term" << 1LL
<< "candidateIndex" << 0LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2578,7 +2580,7 @@ TEST_F(TopoCoordTest, DryRunVoteRequestShouldNotPreventSubsequentDryRunsForThatT
<< "rs0"
<< "dryRun" << true << "term" << 1LL
<< "candidateIndex" << 0LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response2;
@@ -2594,7 +2596,7 @@ TEST_F(TopoCoordTest, DryRunVoteRequestShouldNotPreventSubsequentDryRunsForThatT
<< "rs0"
<< "dryRun" << false << "term" << 1LL
<< "candidateIndex" << 0LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response3;
@@ -2610,7 +2612,7 @@ TEST_F(TopoCoordTest, DryRunVoteRequestShouldNotPreventSubsequentDryRunsForThatT
<< "rs0"
<< "dryRun" << false << "term" << 1LL
<< "candidateIndex" << 0LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response4;
@@ -2640,7 +2642,7 @@ TEST_F(TopoCoordTest, VoteRequestShouldNotPreventDryRunsForThatTerm) {
<< "rs0"
<< "dryRun" << false << "term" << 1LL
<< "candidateIndex" << 0LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2656,7 +2658,7 @@ TEST_F(TopoCoordTest, VoteRequestShouldNotPreventDryRunsForThatTerm) {
<< "rs0"
<< "dryRun" << false << "term" << 1LL
<< "candidateIndex" << 0LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response2;
@@ -2685,7 +2687,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenReplSetNameDoesNotMatch) {
args.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "wrongName"
<< "term" << 1LL << "candidateIndex" << 0LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2695,10 +2698,10 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenReplSetNameDoesNotMatch) {
ASSERT_FALSE(response.getVoteGranted());
}
-TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenConfigVersionDoesNotMatch) {
+TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenConfigVersionIsLower) {
updateConfig(BSON("_id"
<< "rs0"
- << "version" << 1 << "members"
+ << "version" << 1 << "term" << 1LL << "members"
<< BSON_ARRAY(BSON("_id" << 10 << "host"
<< "hself")
<< BSON("_id" << 20 << "host"
@@ -2713,13 +2716,47 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenConfigVersionDoesNotMatch) {
args.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 1LL
- << "configVersion" << 0LL << "lastCommittedOp"
+ << "configVersion" << 0LL << "configTerm" << 1LL
+ << "lastCommittedOp"
+ << BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
+ .transitional_ignore();
+ ReplSetRequestVotesResponse response;
+
+ getTopoCoord().processReplSetRequestVotes(args, &response);
+ ASSERT_EQUALS(
+ "ignoring term of -1 for comparison, candidate's version in config(term, version): (1, 0) "
+ "is lower than mine (1, 1)",
+ response.getReason());
+ ASSERT_FALSE(response.getVoteGranted());
+}
+
+TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenConfigTermIsLower) {
+ updateConfig(BSON("_id"
+ << "rs0"
+ << "version" << 1 << "term" << 2LL << "members"
+ << BSON_ARRAY(BSON("_id" << 10 << "host"
+ << "hself")
+ << BSON("_id" << 20 << "host"
+ << "h2")
+ << BSON("_id" << 30 << "host"
+ << "h3"))),
+ 0);
+ setSelfMemberState(MemberState::RS_SECONDARY);
+
+ // lower configTerm
+ ReplSetRequestVotesArgs args;
+ args.initialize(BSON("replSetRequestVotes" << 1 << "setName"
+ << "rs0"
+ << "term" << 1LL << "candidateIndex" << 1LL
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response);
- ASSERT_EQUALS("candidate's config version (0) differs from mine (1)", response.getReason());
+ ASSERT_EQUALS("candidate's term in config(term, version): (1, 1) is lower than mine (2, 1)",
+ response.getReason());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -2745,7 +2782,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenTermIsStale) {
args.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 1LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2775,7 +2813,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantVoteWhenOpTimeIsStale) {
args.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 3LL << "candidateIndex" << 1LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2811,7 +2850,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenReplSetNameDoesNotMatch) {
.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 0LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse responseForRealVote;
@@ -2827,7 +2867,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenReplSetNameDoesNotMatch) {
<< "wrongName"
<< "dryRun" << true << "term" << 2LL
<< "candidateIndex" << 0LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2838,10 +2878,10 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenReplSetNameDoesNotMatch) {
ASSERT_FALSE(response.getVoteGranted());
}
-TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenConfigVersionDoesNotMatch) {
+TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenConfigVersionIsLower) {
updateConfig(BSON("_id"
<< "rs0"
- << "version" << 1 << "members"
+ << "version" << 1 << "term" << 1LL << "members"
<< BSON_ARRAY(BSON("_id" << 10 << "host"
<< "hself")
<< BSON("_id" << 20 << "host"
@@ -2859,7 +2899,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenConfigVersionDoesNotMatch) {
.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 0LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse responseForRealVote;
@@ -2875,13 +2916,16 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenConfigVersionDoesNotMatch) {
<< "rs0"
<< "dryRun" << true << "term" << 2LL
<< "candidateIndex" << 1LL << "configVersion" << 0LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
getTopoCoord().processReplSetRequestVotes(args, &response);
- ASSERT_EQUALS("candidate's config version (0) differs from mine (1)", response.getReason());
+ ASSERT_EQUALS(
+ "ignoring term of -1 for comparison, candidate's version in config(term, version): (1, 0) "
+ "is lower than mine (1, 1)",
+ response.getReason());
ASSERT_EQUALS(1, response.getTerm());
ASSERT_FALSE(response.getVoteGranted());
}
@@ -2907,7 +2951,8 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenTermIsStale) {
.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 0LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse responseForRealVote;
@@ -2922,7 +2967,7 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenTermIsStale) {
<< "rs0"
<< "dryRun" << true << "term" << 0LL
<< "candidateIndex" << 1LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -2933,6 +2978,40 @@ TEST_F(TopoCoordTest, NodeDoesNotGrantDryRunVoteWhenTermIsStale) {
ASSERT_FALSE(response.getVoteGranted());
}
+TEST_F(TopoCoordTest, NodeGrantsVoteWhenTermIsHigherButConfigVersionIsLower) {
+ updateConfig(BSON("_id"
+ << "rs0"
+ << "version" << 2 << "term" << 1LL << "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 2
+ ASSERT(TopologyCoordinator::UpdateTermResult::kUpdatedTerm ==
+ getTopoCoord().updateTerm(2, now()));
+
+ // mismatched configVersion
+ ReplSetRequestVotesArgs args;
+ args.initialize(BSON("replSetRequestVotes" << 1 << "setName"
+ << "rs0"
+ << "term" << 2LL << "candidateIndex" << 1LL
+ << "configVersion" << 1LL << "configTerm" << 2LL
+ << "lastCommittedOp"
+ << BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
+ .transitional_ignore();
+ ReplSetRequestVotesResponse response;
+
+ getTopoCoord().processReplSetRequestVotes(args, &response);
+ // Candidates config(t, v) is (2, 1) and our config is (1, 2). Even though the candidate's
+ // config version is lower, we grant our vote because the candidate's config term is higher.
+ ASSERT_TRUE(response.getVoteGranted());
+}
+
TEST_F(TopoCoordTest, GrantDryRunVoteEvenWhenTermHasBeenSeen) {
updateConfig(BSON("_id"
<< "rs0"
@@ -2954,7 +3033,8 @@ TEST_F(TopoCoordTest, GrantDryRunVoteEvenWhenTermHasBeenSeen) {
.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 0LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse responseForRealVote;
@@ -2970,7 +3050,7 @@ TEST_F(TopoCoordTest, GrantDryRunVoteEvenWhenTermHasBeenSeen) {
<< "rs0"
<< "dryRun" << true << "term" << 1LL
<< "candidateIndex" << 1LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
@@ -3002,7 +3082,8 @@ TEST_F(TopoCoordTest, DoNotGrantDryRunVoteWhenOpTimeIsStale) {
.initialize(BSON("replSetRequestVotes" << 1 << "setName"
<< "rs0"
<< "term" << 1LL << "candidateIndex" << 0LL
- << "configVersion" << 1LL << "lastCommittedOp"
+ << "configVersion" << 1LL << "configTerm" << 1LL
+ << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse responseForRealVote;
@@ -3018,7 +3099,7 @@ TEST_F(TopoCoordTest, DoNotGrantDryRunVoteWhenOpTimeIsStale) {
<< "rs0"
<< "dryRun" << true << "term" << 3LL
<< "candidateIndex" << 1LL << "configVersion" << 1LL
- << "lastCommittedOp"
+ << "configTerm" << 1LL << "lastCommittedOp"
<< BSON("ts" << Timestamp(10, 0) << "term" << 0LL)))
.transitional_ignore();
ReplSetRequestVotesResponse response;
diff --git a/src/mongo/db/repl/vote_requester.cpp b/src/mongo/db/repl/vote_requester.cpp
index ad29d5f5725..fbef232b5b2 100644
--- a/src/mongo/db/repl/vote_requester.cpp
+++ b/src/mongo/db/repl/vote_requester.cpp
@@ -87,6 +87,7 @@ std::vector<RemoteCommandRequest> VoteRequester::Algorithm::getRequests() const
requestVotesCmdBuilder.append("term", _term);
requestVotesCmdBuilder.append("candidateIndex", _candidateIndex);
requestVotesCmdBuilder.append("configVersion", _rsConfig.getConfigVersion());
+ requestVotesCmdBuilder.append("configTerm", _rsConfig.getConfigTerm());
_lastDurableOpTime.append(&requestVotesCmdBuilder, "lastCommittedOp");