Files
mongo/jstests/replsets/last_vote.js
Steve McClure 1ffbc6c2e9 SERVER-109432: Autofix JS var usage to favor let (#40637)
GitOrigin-RevId: 9674b7db36a0f3f650d39c1e3fb2ad6ff2141cfb
2025-08-28 19:21:01 +00:00

218 lines
8.0 KiB
JavaScript

// Tests that the last vote document is stored during elections and that it is loaded and used on
// startup.
//
// The test first runs a few elections and checks that the lastVote document is set correctly
// after each one.
//
// The test then restarts one node as a standalone, changes its last vote doc, and stops the
// other node. It then restarts the first node as a replicaset and manually runs
// replSetRequestVotes commands against it and checks that its response is correct.
//
// @tags: [
// requires_persistence,
// ]
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {getLatestOp, reconfig} from "jstests/replsets/rslib.js";
let name = "last_vote";
let rst = new ReplSetTest({
name: name,
nodes: 2,
});
rst.startSet();
// Initiate the set with a high election timeout so that there aren't any unplanned elections.
rst.initiate();
const lastVoteNS = "local.replset.election";
function getLastVoteDoc(conn) {
assert.eq(conn.getCollection(lastVoteNS).find().itcount(), 1, "last vote should be singleton");
return conn.getCollection(lastVoteNS).findOne();
}
function setLastVoteDoc(conn, term, candidate) {
let newLastVote = {term: term, candidateIndex: rst.getNodeId(candidate)};
return assert.commandWorked(conn.getCollection(lastVoteNS).update({}, newLastVote));
}
function assertNodeHasLastVote(node, term, candidate) {
let lastVoteDoc = getLastVoteDoc(node);
assert.eq(lastVoteDoc.term, term, node.host + " had wrong last vote term.");
assert.eq(lastVoteDoc.candidateIndex, rst.getNodeId(candidate), node.host + " had wrong last vote candidate.");
}
function assertCurrentTerm(node, term) {
let stat = assert.commandWorked(node.adminCommand({replSetGetStatus: 1}));
assert.eq(stat.term, term, "Term changed when it should not have");
}
jsTestLog("Test that last vote is set on successive elections");
// Run a few successive elections, alternating who becomes primary.
let numElections = 3;
for (let i = 0; i < numElections; i++) {
var primary = rst.getPrimary();
let secondary = rst.getSecondary();
var term = getLatestOp(primary).t;
// SERVER-20844 ReplSetTest starts up a single node replica set then reconfigures to the
// correct size, so secondaries didn't vote in the first election.
if (i > 0) {
jsTestLog(
"Last vote should have term: " +
term +
" and candidate: " +
primary.host +
", index: " +
rst.getNodeId(primary),
);
rst.nodes.forEach(function (node) {
assertNodeHasLastVote(node, term, primary);
});
}
rst.stepUp(secondary);
// Make sure a new primary has been established.
rst.awaitSecondaryNodes(null, [primary]);
rst.waitForState(secondary, ReplSetTest.State.PRIMARY);
// Reset election timeout for the old primary.
assert.commandWorked(primary.adminCommand({replSetFreeze: 0}));
}
var term = getLatestOp(rst.getPrimary()).t + 100;
jsTestLog("Test that last vote is loaded on startup");
// Ensure that all ops are replicated before stepping up node 1.
rst.awaitReplication();
// We cannot reconfig node 0 to have priority 0 if it is currently the primary,
// so we make sure node 1 is primary.
jsTestLog("Stepping up node 1");
rst.stepUp(rst.nodes[1]);
jsTestLog("Reconfiguring cluster to make node 0 unelectable so it stays SECONDARY on restart");
let conf = rst.getReplSetConfigFromNode();
conf.version++;
conf.members[0].priority = 0;
reconfig(rst, conf);
rst.awaitNodesAgreeOnConfigVersion();
jsTestLog("Restarting node 0 as a standalone");
let node0 = rst.restart(0, {noReplSet: true}); // Restart as a standalone node.
jsTestLog("Stopping node 1");
rst.stop(1); // Stop node 1 so that node 0 controls the term by itself.
jsTestLog("Setting the lastVote on node 0 to term: " + term + " candidate: " + rst.nodes[0].host + ", index: 0");
setLastVoteDoc(node0, term, rst.nodes[0]);
jsTestLog("Restarting node 0 in replica set mode");
node0 = rst.restart(0); // Restart in replSet mode again.
rst.awaitSecondaryNodes(null, [node0]);
assert.soonNoExcept(function () {
assertCurrentTerm(node0, term);
return true;
});
jsTestLog("Manually sending node 0 a dryRun replSetRequestVotes command, " + "expecting failure in old term");
let response = assert.commandWorked(
node0.adminCommand({
replSetRequestVotes: 1,
setName: name,
dryRun: true,
term: term - 1,
candidateIndex: 1,
configVersion: conf.version,
lastAppliedOpTime: getLatestOp(node0),
}),
);
assert.eq(response.term, term, "replSetRequestVotes response had the wrong term: " + tojson(response));
assert(!response.voteGranted, "node granted vote in term before last vote doc: " + tojson(response));
assertNodeHasLastVote(node0, term, rst.nodes[0]);
assertCurrentTerm(node0, term);
jsTestLog(
"Manually sending node 0 a dryRun replSetRequestVotes command in same term, " +
"expecting success but no recording of lastVote",
);
response = assert.commandWorked(
node0.adminCommand({
replSetRequestVotes: 1,
setName: name,
dryRun: true,
term: term,
candidateIndex: 1,
configVersion: conf.version,
lastAppliedOpTime: getLatestOp(node0),
}),
);
assert.eq(response.term, term, "replSetRequestVotes response had the wrong term: " + tojson(response));
assert(response.voteGranted, "node failed to grant dryRun vote in term equal to last vote doc: " + tojson(response));
assert.eq(response.reason, "", "replSetRequestVotes response had the wrong reason: " + tojson(response));
assertNodeHasLastVote(node0, term, rst.nodes[0]);
assertCurrentTerm(node0, term);
jsTestLog("Manually sending node 0 a replSetRequestVotes command, expecting failure in same term");
response = assert.commandWorked(
node0.adminCommand({
replSetRequestVotes: 1,
setName: name,
dryRun: false,
term: term,
candidateIndex: 1,
configVersion: conf.version,
lastAppliedOpTime: getLatestOp(node0),
}),
);
assert.eq(response.term, term, "replSetRequestVotes response had the wrong term: " + tojson(response));
assert(!response.voteGranted, "node granted vote in term of last vote doc: " + tojson(response));
assertNodeHasLastVote(node0, term, rst.nodes[0]);
assertCurrentTerm(node0, term);
jsTestLog(
"Manually sending node 0 a replSetRequestVotes command, " +
"expecting success with a recording of the new lastVote",
);
response = assert.commandWorked(
node0.adminCommand({
replSetRequestVotes: 1,
setName: name,
dryRun: false,
term: term + 1,
candidateIndex: 1,
configVersion: conf.version,
lastAppliedOpTime: getLatestOp(node0),
}),
);
assert.eq(response.term, term + 1, "replSetRequestVotes response had the wrong term: " + tojson(response));
assert(response.voteGranted, "node failed to grant vote in term greater than last vote doc: " + tojson(response));
assert.eq(response.reason, "", "replSetRequestVotes response had the wrong reason: " + tojson(response));
assertNodeHasLastVote(node0, term + 1, rst.nodes[1]);
assertCurrentTerm(node0, term + 1);
jsTestLog(
"Manually sending node 0 a dryRun replSetRequestVotes command in future term, " +
"expecting success but no recording of lastVote",
);
response = assert.commandWorked(
node0.adminCommand({
replSetRequestVotes: 1,
setName: name,
dryRun: true,
term: term + 2,
candidateIndex: 1,
configVersion: conf.version,
lastAppliedOpTime: getLatestOp(node0),
}),
);
assert.eq(response.term, term + 2, "replSetRequestVotes response had the wrong term: " + tojson(response));
assert(response.voteGranted, "node failed to grant vote in term greater than last vote doc: " + tojson(response));
assert.eq(response.reason, "", "replSetRequestVotes response had the wrong reason: " + tojson(response));
assertNodeHasLastVote(node0, term + 1, rst.nodes[1]);
assertCurrentTerm(node0, term + 2);
rst.stopSet();