summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiyuan Zhou <siyuan.zhou@mongodb.com>2020-04-24 17:07:25 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-05-13 18:40:45 +0000
commitf8ca54b5fc64397be653e379fd1bc2b222620a40 (patch)
tree173df70430540c8890b42ab328f30dab09c53e6e
parent72432502fba43c6e10e54e94a0d69c751b21ddb7 (diff)
downloadmongo-f8ca54b5fc64397be653e379fd1bc2b222620a40.tar.gz
SERVER-47648 Simplify single node replset stepup on initiate and reconfig
(cherry picked from commit 49cc63fbb37f8a51f845c8d35bdb2f9c11219dab)
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl.cpp29
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl.h7
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp5
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp43
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp4
-rw-r--r--src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp39
-rw-r--r--src/mongo/db/repl/replication_coordinator_test_fixture.h14
-rw-r--r--src/mongo/db/repl/topology_coordinator.cpp28
-rw-r--r--src/mongo/db/repl/topology_coordinator.h19
-rw-r--r--src/mongo/db/repl/topology_coordinator_v1_test.cpp60
10 files changed, 126 insertions, 122 deletions
diff --git a/src/mongo/db/repl/replication_coordinator_impl.cpp b/src/mongo/db/repl/replication_coordinator_impl.cpp
index 9aaa120dcde..9fee5cc1f05 100644
--- a/src/mongo/db/repl/replication_coordinator_impl.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl.cpp
@@ -3835,12 +3835,6 @@ ReplicationCoordinatorImpl::_updateMemberStateFromTopologyCoordinator(WithLock l
const MemberState newState = _topCoord->getMemberState();
if (newState == _memberState) {
- if (_topCoord->getRole() == TopologyCoordinator::Role::kCandidate) {
- invariant(_rsConfig.getNumMembers() == 1 && _selfIndex == 0 &&
- _rsConfig.getMemberAt(0).isElectable());
- // Start election in protocol version 1
- return kActionStartSingleNodeElection;
- }
return kActionNone;
}
@@ -3890,13 +3884,10 @@ ReplicationCoordinatorImpl::_updateMemberStateFromTopologyCoordinator(WithLock l
_readWriteAbility->setCanServeNonLocalReads_UNSAFE(1U);
}
- if (newState.secondary() && _topCoord->getRole() == TopologyCoordinator::Role::kCandidate) {
- // When transitioning to SECONDARY, the only way for _topCoord to report the candidate
- // role is if the configuration represents a single-node replica set. In that case, the
- // overriding requirement is to elect this singleton node primary.
- invariant(_rsConfig.getNumMembers() == 1 && _selfIndex == 0 &&
- _rsConfig.getMemberAt(0).isElectable());
- // Start election in protocol version 1
+ if (newState.secondary() && result != kActionSteppedDown &&
+ _topCoord->isElectableNodeInSingleNodeReplicaSet()) {
+ // When transitioning from other follower states to SECONDARY, run for election on a
+ // single-node replica set.
result = kActionStartSingleNodeElection;
}
@@ -3982,9 +3973,7 @@ void ReplicationCoordinatorImpl::_performPostMemberStateUpdateAction(
_externalState->stopAsyncUpdatesOfAndClearOplogTruncateAfterPoint();
break;
case kActionStartSingleNodeElection:
- // In protocol version 1, single node replset will run an election instead of
- // kActionWinElection as in protocol version 0.
- _startElectSelfV1(StartElectionReasonEnum::kElectionTimeout);
+ _startElectSelfIfEligibleV1(StartElectionReasonEnum::kElectionTimeout);
break;
default:
LOGV2_FATAL(26010,
@@ -4338,7 +4327,13 @@ ReplicationCoordinatorImpl::_setCurrentRSConfig(WithLock lk,
_cancelPriorityTakeover_inlock();
_cancelAndRescheduleElectionTimeout_inlock();
- const PostMemberStateUpdateAction action = _updateMemberStateFromTopologyCoordinator(lk);
+ PostMemberStateUpdateAction action = _updateMemberStateFromTopologyCoordinator(lk);
+ if (_topCoord->isElectableNodeInSingleNodeReplicaSet()) {
+ // If the new config describes an electable one-node replica set, we need to start an
+ // election.
+ action = PostMemberStateUpdateAction::kActionStartSingleNodeElection;
+ }
+
if (_selfIndex >= 0) {
// Don't send heartbeats if we're not in the config, if we get re-added one of the
// nodes in the set will contact us.
diff --git a/src/mongo/db/repl/replication_coordinator_impl.h b/src/mongo/db/repl/replication_coordinator_impl.h
index ac5d76919bd..392d8bd31f6 100644
--- a/src/mongo/db/repl/replication_coordinator_impl.h
+++ b/src/mongo/db/repl/replication_coordinator_impl.h
@@ -450,13 +450,13 @@ public:
long long term, TopologyCoordinator::UpdateTermResult* updateResult);
/**
- * If called after _startElectSelfV1(), blocks until all asynchronous
+ * If called after _startElectSelfV1_inlock(), blocks until all asynchronous
* activities associated with election complete.
*/
void waitForElectionFinish_forTest();
/**
- * If called after _startElectSelfV1(), blocks until all asynchronous
+ * If called after _startElectSelfV1_inlock(), blocks until all asynchronous
* activities associated with election dry run complete, including writing
* last vote and scheduling the real election.
*/
@@ -1069,7 +1069,7 @@ private:
* For proper concurrency, start methods must be called while holding _mutex.
*
* For V1 (raft) style elections the election path is:
- * _startElectSelfV1() or _startElectSelfV1_inlock()
+ * _startElectSelfIfEligibleV1()
* _processDryRunResult() (may skip)
* _startRealElection_inlock()
* _writeLastVoteForMyElection()
@@ -1077,7 +1077,6 @@ private:
* _onVoteRequestComplete()
*/
void _startElectSelfV1_inlock(StartElectionReasonEnum reason);
- void _startElectSelfV1(StartElectionReasonEnum reason);
/**
* Callback called when the dryRun VoteRequester has completed; checks the results and
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 849d83e4084..671563ea058 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_elect_v1.cpp
@@ -91,11 +91,6 @@ public:
}
};
-void ReplicationCoordinatorImpl::_startElectSelfV1(StartElectionReasonEnum reason) {
- stdx::lock_guard<Latch> lk(_mutex);
- _startElectSelfV1_inlock(reason);
-}
-
void ReplicationCoordinatorImpl::_startElectSelfV1_inlock(StartElectionReasonEnum reason) {
invariant(!_voteRequester);
diff --git a/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp
index ef8a7332b66..038c2568290 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_elect_v1_test.cpp
@@ -160,7 +160,7 @@ TEST_F(ReplCoordTest, ElectionSucceedsWhenNodeIsTheOnlyElectableNode) {
}
net->exitNetwork();
- // _startElectSelfV1 is called when election timeout expires, so election
+ // _startElectSelfV1_inlock is called when election timeout expires, so election
// finished event has been set.
getReplCoord()->waitForElectionFinish_forTest();
@@ -904,6 +904,47 @@ TEST_F(ReplCoordTest, ElectionFailsWhenTermChangesDuringActualElection) {
countTextFormatLogLinesContaining("Not becoming primary, we have been superseded already"));
}
+TEST_F(ReplCoordTest, StartElectionOnSingleNodeInitiate) {
+ init("mySet");
+ start(HostAndPort("node1", 12345));
+ auto opCtx = makeOperationContext();
+ BSONObj configObj = configWithMembers(1, 0, BSON_ARRAY(member(1, "node1:12345")));
+
+ // Initialize my last applied, so that the node is electable.
+ replCoordSetMyLastAppliedAndDurableOpTime({Timestamp(100, 1), 0});
+
+ BSONObjBuilder result;
+ ASSERT_OK(getReplCoord()->processReplSetInitiate(opCtx.get(), configObj, &result));
+ ASSERT_EQUALS(MemberState::RS_RECOVERING, getReplCoord()->getMemberState().s);
+ // Oplog applier attempts to transition the state to Secondary, which triggers
+ // election on single node.
+ getReplCoord()->finishRecoveryIfEligible(opCtx.get());
+ // Run pending election operations on executor.
+ getNet()->enterNetwork();
+ getNet()->runReadyNetworkOperations();
+ getNet()->exitNetwork();
+ ASSERT_EQUALS(MemberState::RS_PRIMARY, getReplCoord()->getMemberState().s);
+}
+
+TEST_F(ReplCoordTest, StartElectionOnSingleNodeStartup) {
+ BSONObj configObj = configWithMembers(1, 0, BSON_ARRAY(member(1, "node1:12345")));
+ assertStartSuccess(configObj, HostAndPort("node1", 12345));
+
+ // Initialize my last applied, so that the node is electable.
+ replCoordSetMyLastAppliedAndDurableOpTime({Timestamp(100, 1), 0});
+
+ ASSERT_OK(getReplCoord()->setFollowerMode(MemberState::RS_RECOVERING));
+ // Oplog applier attempts to transition the state to Secondary, which triggers
+ // election on single node.
+ auto opCtx = makeOperationContext();
+ getReplCoord()->finishRecoveryIfEligible(opCtx.get());
+ // Run pending election operations on executor.
+ getNet()->enterNetwork();
+ getNet()->runReadyNetworkOperations();
+ getNet()->exitNetwork();
+ ASSERT_EQUALS(MemberState::RS_PRIMARY, getReplCoord()->getMemberState().s);
+}
+
class TakeoverTest : public ReplCoordTest {
public:
/*
diff --git a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp
index 0a9fd580a06..5521ded0cf8 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp
@@ -1000,7 +1000,9 @@ void ReplicationCoordinatorImpl::_startElectSelfIfEligibleV1(WithLock,
StartElectionReasonEnum reason) {
// If it is not a single node replica set, no need to start an election after stepdown timeout.
if (reason == StartElectionReasonEnum::kSingleNodePromptElection &&
- _rsConfig.getNumMembers() != 1) {
+ !_topCoord->isElectableNodeInSingleNodeReplicaSet()) {
+ LOGV2_FOR_ELECTION(
+ 4764800, 0, "Not starting an election, since we are not an electable single node");
return;
}
diff --git a/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp b/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp
index 0e7fee0157f..b110f93b5f6 100644
--- a/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp
+++ b/src/mongo/db/repl/replication_coordinator_impl_reconfig_test.cpp
@@ -779,17 +779,6 @@ public:
unittest::MinimumLoggedSeverityGuard severityGuard{logv2::LogComponent::kDefault,
logv2::LogSeverity::Debug(3)};
- BSONObj member(int id, std::string host) {
- return BSON("_id" << id << "host" << host);
- }
-
- BSONObj configWithMembers(int version, long long term, BSONArray members) {
- return BSON("_id"
- << "mySet"
- << "protocolVersion" << 1 << "version" << version << "term" << term << "members"
- << members);
- }
-
void respondToHeartbeat() {
auto net = getNet();
auto noi = net->getNextReadyRequest();
@@ -1386,6 +1375,34 @@ TEST_F(ReplCoordReconfigTest, StepdownShouldInterruptConfigWrite) {
ASSERT_EQ(status.reason(), "Stepped down when persisting new config");
}
+TEST_F(ReplCoordReconfigTest, StartElectionOnReconfigToSingleNode) {
+ // Start up as a secondary.
+ init();
+ assertStartSuccess(
+ configWithMembers(
+ 1, 0, BSON_ARRAY(member(1, "n1:1") << member(2, "n2:1") << member(3, "n3:1"))),
+ HostAndPort("n1", 1));
+ ASSERT_OK(getReplCoord()->setFollowerMode(MemberState::RS_SECONDARY));
+
+ // Simulate application of one oplog entry.
+ replCoordSetMyLastAppliedAndDurableOpTime(OpTime(Timestamp(1, 1), 0));
+
+ // Construct the new config of single node replset.
+ auto configVersion = 2;
+ ReplSetReconfigArgs args;
+ args.force = true;
+ args.newConfigObj = configWithMembers(configVersion, 1, BSON_ARRAY(member(1, "n1:1")));
+
+ BSONObjBuilder result;
+ const auto opCtx = makeOperationContext();
+ ASSERT_OK(getReplCoord()->processReplSetReconfig(opCtx.get(), args, &result));
+ getNet()->enterNetwork();
+ getNet()->runReadyNetworkOperations();
+ getNet()->exitNetwork();
+
+ ASSERT_EQUALS(MemberState::RS_PRIMARY, getReplCoord()->getMemberState().s);
+}
+
} // anonymous namespace
} // namespace repl
} // namespace mongo
diff --git a/src/mongo/db/repl/replication_coordinator_test_fixture.h b/src/mongo/db/repl/replication_coordinator_test_fixture.h
index cef52cd81e9..e74e89bc6ee 100644
--- a/src/mongo/db/repl/replication_coordinator_test_fixture.h
+++ b/src/mongo/db/repl/replication_coordinator_test_fixture.h
@@ -76,6 +76,20 @@ public:
*/
static BSONObj addProtocolVersion(const BSONObj& configDoc, int protocolVersion);
+ /**
+ * Helpers to construct a config.
+ */
+ static BSONObj member(int id, std::string host) {
+ return BSON("_id" << id << "host" << host);
+ }
+
+ static BSONObj configWithMembers(int version, long long term, BSONArray members) {
+ return BSON("_id"
+ << "mySet"
+ << "protocolVersion" << 1 << "version" << version << "term" << term << "members"
+ << members);
+ }
+
protected:
ReplCoordTest();
virtual ~ReplCoordTest();
diff --git a/src/mongo/db/repl/topology_coordinator.cpp b/src/mongo/db/repl/topology_coordinator.cpp
index 96f02005bfb..ad7373215da 100644
--- a/src/mongo/db/repl/topology_coordinator.cpp
+++ b/src/mongo/db/repl/topology_coordinator.cpp
@@ -2196,13 +2196,6 @@ void TopologyCoordinator::updateConfig(const ReplSetConfig& newConfig, int selfI
// By this point we know we are in Role::kFollower
_currentPrimaryIndex = -1; // force secondaries to re-detect who the primary is
-
- if (_isElectableNodeInSingleNodeReplicaSet()) {
- // If the new config describes a one-node replica set, we're the one member,
- // we're electable, we're not in maintenance mode and we are currently in followerMode
- // SECONDARY, we must transition to candidate, in leiu of heartbeats.
- _role = Role::kCandidate;
- }
}
std::string TopologyCoordinator::_getHbmsg(Date_t now) const {
// ignore messages over 2 minutes old
@@ -2646,24 +2639,13 @@ void TopologyCoordinator::setFollowerMode(MemberState::MS newMode) {
default:
MONGO_UNREACHABLE;
}
-
- if (_followerMode != MemberState::RS_SECONDARY) {
- return;
- }
-
- // When a single node replica set transitions to SECONDARY, we must check if we should
- // be a candidate here. This is necessary because a single node replica set has no
- // heartbeats that would normally change the role to candidate.
-
- if (_isElectableNodeInSingleNodeReplicaSet()) {
- _role = Role::kCandidate;
- }
}
-bool TopologyCoordinator::_isElectableNodeInSingleNodeReplicaSet() const {
- return _followerMode == MemberState::RS_SECONDARY && _rsConfig.getNumMembers() == 1 &&
- _selfIndex == 0 && _rsConfig.getMemberAt(_selfIndex).isElectable() &&
- _maintenanceModeCalls == 0;
+bool TopologyCoordinator::isElectableNodeInSingleNodeReplicaSet() const {
+ auto isSingleNode = _rsConfig.getNumMembers() == 1 && _selfIndex == 0;
+ // Single node replset must be electable.
+ invariant(!isSingleNode || _rsConfig.getMemberAt(_selfIndex).isElectable());
+ return (getMemberState() == MemberState::RS_SECONDARY) && isSingleNode;
}
void TopologyCoordinator::finishUnconditionalStepDown() {
diff --git a/src/mongo/db/repl/topology_coordinator.h b/src/mongo/db/repl/topology_coordinator.h
index 0e9f90aef21..752b9b52b2f 100644
--- a/src/mongo/db/repl/topology_coordinator.h
+++ b/src/mongo/db/repl/topology_coordinator.h
@@ -171,6 +171,16 @@ public:
enum class UpdateTermResult { kAlreadyUpToDate, kTriggerStepDown, kUpdatedTerm };
+ /**
+ * Returns true if we are a one-node replica set, we're the one member,
+ * we're electable, we're not in maintenance mode, and we are currently in followerMode
+ * SECONDARY.
+ *
+ * This is used to decide if we should start an election in a one-node replica set.
+ */
+ bool isElectableNodeInSingleNodeReplicaSet() const;
+
+
////////////////////////////////////////////////////////////
//
// Basic state manipulation methods.
@@ -945,15 +955,6 @@ private:
**/
bool _memberIsBlacklisted(const MemberConfig& memberConfig, Date_t now) const;
- /**
- * Returns true if we are a one-node replica set, we're the one member,
- * we're electable, we're not in maintenance mode, and we are currently in followerMode
- * SECONDARY.
- *
- * This is used to decide if we should transition to Role::candidate in a one-node replica set.
- */
- bool _isElectableNodeInSingleNodeReplicaSet() const;
-
// Returns a string representation of the current replica set status for logging purposes.
std::string _getReplSetStatusString();
diff --git a/src/mongo/db/repl/topology_coordinator_v1_test.cpp b/src/mongo/db/repl/topology_coordinator_v1_test.cpp
index 2ef75f15402..96b9f3a9cda 100644
--- a/src/mongo/db/repl/topology_coordinator_v1_test.cpp
+++ b/src/mongo/db/repl/topology_coordinator_v1_test.cpp
@@ -2893,24 +2893,6 @@ TEST_F(TopoCoordTest, RespondToHeartbeatsWithNullLastAppliedAndLastDurableWhileI
ASSERT_EQUALS(OpTime(), response.getDurableOpTime());
}
-TEST_F(TopoCoordTest, BecomeCandidateWhenBecomingSecondaryInSingleNodeSet) {
- ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole());
- ASSERT_EQUALS(MemberState::RS_STARTUP, getTopoCoord().getMemberState().s);
- updateConfig(BSON("_id"
- << "rs0"
- << "version" << 1 << "members"
- << BSON_ARRAY(BSON("_id" << 1 << "host"
- << "hself"))),
- 0);
- ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s);
-
- // if we are the only node, we should become a candidate when we transition to SECONDARY
- ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
- getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY);
- ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
- ASSERT_EQUALS(MemberState::RS_SECONDARY, getTopoCoord().getMemberState().s);
-}
-
TEST_F(TopoCoordTest, DoNotBecomeCandidateWhenBecomingSecondaryInSingleNodeSetIfInMaintenanceMode) {
ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole());
ASSERT_EQUALS(MemberState::RS_STARTUP, getTopoCoord().getMemberState().s);
@@ -2938,36 +2920,6 @@ TEST_F(TopoCoordTest, DoNotBecomeCandidateWhenBecomingSecondaryInSingleNodeSetIf
ASSERT_EQUALS(MemberState::RS_SECONDARY, getTopoCoord().getMemberState().s);
}
-TEST_F(TopoCoordTest, BecomeCandidateWhenReconfigToBeElectableInSingleNodeSet) {
- ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole());
- ASSERT_EQUALS(MemberState::RS_STARTUP, getTopoCoord().getMemberState().s);
- ReplSetConfig cfg;
- cfg.initialize(BSON("_id"
- << "rs0"
- << "version" << 1 << "protocolVersion" << 1 << "members"
- << BSON_ARRAY(BSON("_id" << 1 << "host"
- << "hself"
- << "priority" << 0))))
- .transitional_ignore();
- getTopoCoord().updateConfig(cfg, 0, now()++);
- ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s);
-
- ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
- getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY);
- ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
- ASSERT_EQUALS(MemberState::RS_SECONDARY, getTopoCoord().getMemberState().s);
-
- // we should become a candidate when we reconfig to become electable
-
- updateConfig(BSON("_id"
- << "rs0"
- << "version" << 1 << "members"
- << BSON_ARRAY(BSON("_id" << 1 << "host"
- << "hself"))),
- 0);
- ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
-}
-
TEST_F(TopoCoordTest,
DoNotBecomeCandidateWhenReconfigToBeElectableInSingleNodeSetIfInMaintenanceMode) {
ASSERT_TRUE(TopologyCoordinator::Role::kFollower == getTopoCoord().getRole());
@@ -3095,7 +3047,9 @@ TEST_F(TopoCoordTest, NodeTransitionsToRemovedWhenRemovedFromConfigEvenWhenPrima
ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s);
getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY);
- ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
+ setMyOpTime({Timestamp(1, 1), 0});
+ ASSERT_OK(getTopoCoord().becomeCandidateIfElectable(Date_t(),
+ StartElectionReasonEnum::kElectionTimeout));
// win election and primary
getTopoCoord().processWinElection(OID::gen(), Timestamp());
@@ -3128,7 +3082,9 @@ TEST_F(TopoCoordTest, NodeTransitionsToSecondaryWhenReconfiggingToBeUnelectable)
ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s);
getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY);
- ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
+ setMyOpTime({Timestamp(1, 1), 0});
+ ASSERT_OK(getTopoCoord().becomeCandidateIfElectable(Date_t(),
+ StartElectionReasonEnum::kElectionTimeout));
// win election and primary
getTopoCoord().processWinElection(OID::gen(), Timestamp());
@@ -3164,7 +3120,9 @@ TEST_F(TopoCoordTest, NodeMaintainsPrimaryStateAcrossReconfigIfNodeRemainsElecta
ASSERT_FALSE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
ASSERT_EQUALS(MemberState::RS_STARTUP2, getTopoCoord().getMemberState().s);
getTopoCoord().setFollowerMode(MemberState::RS_SECONDARY);
- ASSERT_TRUE(TopologyCoordinator::Role::kCandidate == getTopoCoord().getRole());
+ setMyOpTime({Timestamp(1, 1), 0});
+ ASSERT_OK(getTopoCoord().becomeCandidateIfElectable(Date_t(),
+ StartElectionReasonEnum::kElectionTimeout));
// win election and primary
getTopoCoord().processWinElection(OID::gen(), Timestamp());