summaryrefslogtreecommitdiff
path: root/jstests/replsets/last_vote.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/replsets/last_vote.js')
-rw-r--r--jstests/replsets/last_vote.js208
1 files changed, 208 insertions, 0 deletions
diff --git a/jstests/replsets/last_vote.js b/jstests/replsets/last_vote.js
new file mode 100644
index 00000000000..bbb38bd4af3
--- /dev/null
+++ b/jstests/replsets/last_vote.js
@@ -0,0 +1,208 @@
+// 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]
+
+(function() {
+ "use strict";
+ load("jstests/replsets/rslib.js"); // For getLatestOp()
+
+ var name = "last_vote";
+ var rst = new ReplSetTest({
+ name: name,
+ nodes: 2,
+ });
+ rst.startSet();
+
+ // Lower the election timeout to make the test run faster since it waits for multiple elections.
+ var conf = rst.getReplSetConfig();
+ conf.settings = {
+ electionTimeoutMillis: 3000,
+ };
+ rst.initiate(conf);
+
+ 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) {
+ var newLastVote = {
+ term: term,
+ candidateIndex: rst.getNodeId(candidate)
+ };
+ return assert.writeOK(conn.getCollection(lastVoteNS).update({}, newLastVote));
+ }
+
+ function assertNodeHasLastVote(node, term, candidate) {
+ var 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) {
+ var 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");
+
+ for (var i = 0; i < 3; i++) {
+ var primary = rst.getPrimary();
+ var term = getLatestOp(primary).t;
+ jsTestLog("Last vote should have term: " + term + " and candidate: " + primary.host +
+ ", index: " + rst.getNodeId(primary));
+ rst.nodes.forEach(function(node) {
+ assertNodeHasLastVote(node, term, primary);
+ });
+ assert.throws(function() {
+ primary.adminCommand({replSetStepDown: 5, force: true});
+ });
+ rst.waitForState(primary, ReplSetTest.State.SECONDARY);
+ }
+
+ var term = getLatestOp(rst.getPrimary()).t + 100;
+
+ jsTestLog("Test that last vote is loaded on startup");
+ jsTestLog("Reconfiguring cluster to make node 0 unelectable so it stays SECONDARY on restart");
+ conf = rst.getReplSetConfigFromNode();
+ conf.version++;
+ conf.members[0].priority = 0;
+ reconfig(rst, conf);
+
+ jsTestLog("Restarting node 0 as a standalone");
+ var 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.
+ assertCurrentTerm(node0, term);
+
+ jsTestLog("Manually sending node 0 a dryRun replSetRequestVotes command, " +
+ "expecting failure in old term");
+ var response = assert.commandWorked(node0.adminCommand({
+ replSetRequestVotes: 1,
+ setName: name,
+ dryRun: true,
+ term: term - 1,
+ candidateIndex: 1,
+ configVersion: 2,
+ lastCommittedOp: 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));
+ assert.eq(response.reason,
+ "candidate's term is lower than mine",
+ "replSetRequestVotes response had the wrong reason: " + 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: 2,
+ lastCommittedOp: 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: 2,
+ lastCommittedOp: 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));
+ assert.eq(response.reason,
+ "already voted for another candidate this term",
+ "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 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: 2,
+ lastCommittedOp: 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: 2,
+ lastCommittedOp: 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();
+})();