/* * Tests that nodes in initial sync are considered a part of the liveness majority. This test starts * with a two-node replica set. We then add a secondary and force it to hang in initial sync. We * proceed to remove all communication for the other secondary. We verify that the primary does not * step down and no elections were called, since the node in initial sync contributes to the * liveness majority. Finally, we verify that nodes in initial sync are able to vote in an election. * This is done by stepping down the primary, waiting for it to become a secondary, and allowing it * to run for primary again. Since the other secondary is disconnected, the primary must receive a * vote from the initial sync node to get elected again. */ (function() { "use strict"; load("jstests/libs/fail_point_util.js"); load("jstests/replsets/rslib.js"); const name = jsTestName(); const rst = new ReplSetTest({ name, nodes: [{}, {rsConfig: {priority: 0}}], settings: {electionTimeoutMillis: 3000, heartbeatIntervalMillis: 250}, useBridge: true }); rst.startSet(); rst.initiate(); const primary = rst.getPrimary(); const secondary = rst.getSecondaries()[0]; const initialSyncSecondary = rst.add({ rsConfig: {priority: 0, votes: 0}, setParameter: {'failpoint.initialSyncHangBeforeFinish': tojson({mode: 'alwaysOn'})}, }); rst.reInitiate(); // Add the new node with votes:0 and then give it votes:1 to avoid 'newlyAdded' and mimic a resync, // where a node is in initial sync with 1 vote. let nextConfig = rst.getReplSetConfigFromNode(0); nextConfig.members[2].votes = 1; reconfig(rst, nextConfig, false /* force */, true /* wait */); assert.commandWorked(initialSyncSecondary.adminCommand({ waitForFailPoint: "initialSyncHangBeforeFinish", timesEntered: 1, maxTimeMS: kDefaultWaitForFailPointTimeout })); secondary.disconnect(primary); // Verify that the primary should not step down due to not seeing a quorum. This is because the // primary should be receiving heartbeats from the initial sync node. We specifically look for the // 'Can't see a majority of the set, relinquishing primary' log message. assert.throws(() => checkLog.containsJson(primary, 21809, {} /* attrs */, 3000)); assert.eq(primary, rst.getPrimary()); // Verify that the term is still 1, so the primary did not call any elections. let primaryReplSetStatus = assert.commandWorked(primary.adminCommand({replSetGetStatus: 1})); assert.eq(1, primaryReplSetStatus.term); // Step down the primary and prevent it from running for elections with a high freeze timeout. assert.commandWorked( primary.adminCommand({replSetStepDown: ReplSetTest.kForeverSecs, force: true})); waitForState(primary, ReplSetTest.State.SECONDARY); // Unfreeze the primary so that it can run for election. It should immediately run for election as // no other nodes are able to call for an election. assert.commandWorked(primary.adminCommand({replSetFreeze: 0})); // Verify that the primary was elected. assert.eq(primary, rst.getPrimary()); // Verify that the term has incremented due to the last election. primaryReplSetStatus = assert.commandWorked(primary.adminCommand({replSetGetStatus: 1})); assert.gt(primaryReplSetStatus.term, 1); // Verify that initial sync node voted in the election and sets the correct term in its // electionParticipantMetrics field. const initialSyncSecondaryReplSetStatus = assert.commandWorked(initialSyncSecondary.adminCommand({replSetGetStatus: 1})); assert.eq(2, initialSyncSecondaryReplSetStatus.electionParticipantMetrics.electionTerm); // The disconnected secondary did not vote in the last election, so its electionParticipantMetrics // field should not be set. const disconnectedSecondaryReplSetStatus = assert.commandWorked(secondary.adminCommand({replSetGetStatus: 1})); assert.eq(undefined, disconnectedSecondaryReplSetStatus.electionParticipantMetrics); // Since the primary sends a shut down command to all secondaries in `rst.stopSet()`, we reconnect // the disconnected secondary to the primary to allow it to be shut down. secondary.reconnect(primary); assert.commandWorked(initialSyncSecondary.adminCommand( {configureFailPoint: "initialSyncHangBeforeFinish", mode: "off"})); waitForState(initialSyncSecondary, ReplSetTest.State.SECONDARY); rst.stopSet(); })();