diff options
author | XueruiFa <xuerui.fa@mongodb.com> | 2020-10-06 17:28:45 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-10-13 16:14:32 +0000 |
commit | bf614cb57059c74830633855e28b3f4677cd4f8d (patch) | |
tree | 8862fd43b78067e92b12f1b028cb196100728554 | |
parent | d9c1336c11e00b985ad9fcf5cd4917dca5d9e38c (diff) | |
download | mongo-bf614cb57059c74830633855e28b3f4677cd4f8d.tar.gz |
SERVER-43904: Filter unelectable nodes during election handoff
-rw-r--r-- | etc/backports_required_for_multiversion_tests.yml | 2 | ||||
-rw-r--r-- | jstests/replsets/election_handoff_skips_unelectable_nodes.js | 25 | ||||
-rw-r--r-- | jstests/sharding/move_chunk_with_session_helper.js | 6 | ||||
-rw-r--r-- | src/mongo/db/repl/member_data.h | 7 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_heartbeat_response.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/repl/repl_set_heartbeat_response.h | 11 | ||||
-rw-r--r-- | src/mongo/db/repl/topology_coordinator.cpp | 6 |
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; } |