summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXueruiFa <xuerui.fa@mongodb.com>2020-10-06 17:28:45 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-10-13 16:14:32 +0000
commitbf614cb57059c74830633855e28b3f4677cd4f8d (patch)
tree8862fd43b78067e92b12f1b028cb196100728554
parentd9c1336c11e00b985ad9fcf5cd4917dca5d9e38c (diff)
downloadmongo-bf614cb57059c74830633855e28b3f4677cd4f8d.tar.gz
SERVER-43904: Filter unelectable nodes during election handoff
-rw-r--r--etc/backports_required_for_multiversion_tests.yml2
-rw-r--r--jstests/replsets/election_handoff_skips_unelectable_nodes.js25
-rw-r--r--jstests/sharding/move_chunk_with_session_helper.js6
-rw-r--r--src/mongo/db/repl/member_data.h7
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_response.cpp16
-rw-r--r--src/mongo/db/repl/repl_set_heartbeat_response.h11
-rw-r--r--src/mongo/db/repl/topology_coordinator.cpp6
7 files changed, 70 insertions, 3 deletions
diff --git a/etc/backports_required_for_multiversion_tests.yml b/etc/backports_required_for_multiversion_tests.yml
index 3c56b35a468..f3caa752b68 100644
--- a/etc/backports_required_for_multiversion_tests.yml
+++ b/etc/backports_required_for_multiversion_tests.yml
@@ -86,6 +86,8 @@ all:
test_file: jstests/replsets/read_operations_during_rollback.js
- ticket: SERVER-51262
test_file: jstests/replsets/transactions_on_secondaries_not_allowed.js
+ - ticket: SERVER-43904
+ test_file: jstests/replsets/election_handoff_skips_unelectable_nodes.js
# Tests that should only be excluded from particular suites should be listed under that suite.
suites:
diff --git a/jstests/replsets/election_handoff_skips_unelectable_nodes.js b/jstests/replsets/election_handoff_skips_unelectable_nodes.js
new file mode 100644
index 00000000000..d2bc8672e4a
--- /dev/null
+++ b/jstests/replsets/election_handoff_skips_unelectable_nodes.js
@@ -0,0 +1,25 @@
+/*
+ * Tests that election handoff will not attempt to step up a node that is unelectable.
+ */
+
+(function() {
+"use strict";
+
+load("jstests/replsets/libs/election_handoff.js");
+
+const rst = new ReplSetTest({name: jsTestName(), nodes: 3});
+rst.startSet();
+
+// Make sure there are no election timeouts firing for the duration of the test. This helps
+// ensure that the test will only pass if the election handoff succeeds.
+rst.initiateWithHighElectionTimeout();
+
+// Freeze one of the secondaries so that it cannot be elected. As a result, the other secondary
+// should always be chosen to run for primary via election handoff.
+const frozenSecondary = rst.getSecondaries()[0];
+assert.commandWorked(frozenSecondary.adminCommand({replSetFreeze: ReplSetTest.kForeverSecs}));
+
+ElectionHandoffTest.testElectionHandoff(rst, 0, 2);
+
+rst.stopSet();
+})();
diff --git a/jstests/sharding/move_chunk_with_session_helper.js b/jstests/sharding/move_chunk_with_session_helper.js
index bf4653eb18a..63d94a81c01 100644
--- a/jstests/sharding/move_chunk_with_session_helper.js
+++ b/jstests/sharding/move_chunk_with_session_helper.js
@@ -6,7 +6,7 @@ load("jstests/replsets/rslib.js");
* 2. Perform writes.
* 3. Migrate only chunk to other shard.
* 4. Retry writes.
- * 5. Step down primary and wait for new primary.
+ * 5. Step up secondary and wait for new primary.
* 6. Retry writes.
* 7. Migrate only chunk back to original shard.
* 8. Retry writes.
@@ -27,8 +27,8 @@ var testMoveChunkWithSession = function(
checkRetryResultFunc(result, assert.commandWorked(testDB.runCommand(cmdObj)));
checkDocumentsFunc(coll);
- assert.commandWorked(
- st.rs1.getPrimary().adminCommand({replSetStepDown: 60, secondaryCatchUpPeriodSecs: 30}));
+ const secondary = st.rs1.getSecondary();
+ st.rs1.stepUp(secondary);
st.rs1.awaitNodesAgreeOnPrimary();
st.configRS.nodes.concat([st.s]).forEach(function awaitNode(conn) {
diff --git a/src/mongo/db/repl/member_data.h b/src/mongo/db/repl/member_data.h
index b6344838d9b..1903a1b448d 100644
--- a/src/mongo/db/repl/member_data.h
+++ b/src/mongo/db/repl/member_data.h
@@ -151,6 +151,13 @@ public:
return _hostAndPort;
}
+ /*
+ * Returns true if the last heartbeat data explicilty stated that the node is not electable.
+ */
+ bool isUnelectable() const {
+ return _lastResponse.hasIsElectable() && !_lastResponse.isElectable();
+ }
+
/**
* Sets values in this object from the results of a successful heartbeat command.
* Returns true if the lastApplied/lastDurable values advanced or we've received a newer
diff --git a/src/mongo/db/repl/repl_set_heartbeat_response.cpp b/src/mongo/db/repl/repl_set_heartbeat_response.cpp
index 138f6c84e1e..c77d454f00c 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_response.cpp
+++ b/src/mongo/db/repl/repl_set_heartbeat_response.cpp
@@ -63,6 +63,7 @@ const std::string kReplSetFieldName = "set";
const std::string kSyncSourceFieldName = "syncingTo";
const std::string kTermFieldName = "term";
const std::string kTimestampFieldName = "ts";
+const std::string kIsElectableFieldName = "electable";
} // namespace
@@ -102,6 +103,9 @@ void ReplSetHeartbeatResponse::addToBSON(BSONObjBuilder* builder) const {
_appliedOpTime.append(builder, kAppliedOpTimeFieldName);
builder->appendDate(kAppliedWallTimeFieldName, _appliedWallTime);
}
+ if (_electableSet) {
+ *builder << kIsElectableFieldName << _electable;
+ }
}
BSONObj ReplSetHeartbeatResponse::toBSON() const {
@@ -180,6 +184,13 @@ Status ReplSetHeartbeatResponse::initialize(const BSONObj& doc, long long term)
_appliedWallTime = appliedWallTimeElement.Date();
_appliedOpTimeSet = true;
+ status = bsonExtractBooleanField(doc, kIsElectableFieldName, &_electable);
+ if (!status.isOK()) {
+ _electableSet = false;
+ } else {
+ _electableSet = true;
+ }
+
const BSONElement memberStateElement = doc[kMemberStateFieldName];
if (memberStateElement.eoo()) {
_stateSet = false;
@@ -300,5 +311,10 @@ OpTimeAndWallTime ReplSetHeartbeatResponse::getDurableOpTimeAndWallTime() const
return {_durableOpTime, _durableWallTime};
}
+bool ReplSetHeartbeatResponse::isElectable() const {
+ invariant(_electableSet);
+ return _electable;
+}
+
} // namespace repl
} // namespace mongo
diff --git a/src/mongo/db/repl/repl_set_heartbeat_response.h b/src/mongo/db/repl/repl_set_heartbeat_response.h
index 44fe581f23f..300e9afb84b 100644
--- a/src/mongo/db/repl/repl_set_heartbeat_response.h
+++ b/src/mongo/db/repl/repl_set_heartbeat_response.h
@@ -115,6 +115,10 @@ public:
}
OpTime getDurableOpTime() const;
OpTimeAndWallTime getDurableOpTimeAndWallTime() const;
+ bool hasIsElectable() const {
+ return _electableSet;
+ }
+ bool isElectable() const;
/**
* Sets _setName to "name".
@@ -185,6 +189,10 @@ public:
void setTerm(long long term) {
_term = term;
}
+ void setElectable(bool electable) {
+ _electableSet = true;
+ _electable = electable;
+ }
private:
bool _electionTimeSet = false;
@@ -212,6 +220,9 @@ private:
bool _primaryIdSet = false;
long long _primaryId = -1;
long long _term = -1;
+
+ bool _electableSet = false;
+ bool _electable = false;
};
} // namespace repl
diff --git a/src/mongo/db/repl/topology_coordinator.cpp b/src/mongo/db/repl/topology_coordinator.cpp
index 8cefab5e3bc..b81faaab756 100644
--- a/src/mongo/db/repl/topology_coordinator.cpp
+++ b/src/mongo/db/repl/topology_coordinator.cpp
@@ -846,6 +846,9 @@ Status TopologyCoordinator::prepareHeartbeatResponseV1(Date_t now,
return Status::OK();
}
+ response->setElectable(
+ !_getMyUnelectableReason(now, StartElectionReasonEnum::kElectionTimeout));
+
const long long v = _rsConfig.getConfigVersion();
const long long t = _rsConfig.getConfigTerm();
response->setConfigVersion(v);
@@ -2331,6 +2334,9 @@ TopologyCoordinator::UnelectableReasonMask TopologyCoordinator::_getUnelectableR
if (hbData.getState() != MemberState::RS_SECONDARY) {
result |= NotSecondary;
}
+ if (hbData.up() && hbData.isUnelectable()) {
+ result |= StepDownPeriodActive;
+ }
invariant(result || memberConfig.isElectable());
return result;
}