1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
"use strict";
/**
* This file is used for testing election handoff.
*/
var ElectionHandoffTest = (function() {
load("jstests/replsets/rslib.js");
const kStepDownPeriodSecs = 30;
const kSIGTERM = 15;
/**
* Exercises and validates an election handoff scenario by stepping down the primary and
* ensuring that the node at "expectedCandidateId" is stepped up in its place. The desired
* configuration of the replica set is passed in as its ReplSetTest instance.
*
* The options parameter contains extra options for the handoff. Currently supported options
* are:
* stepDownBySignal - When this option is set, the primary will be stepped down by stopping
* and restarting with sigterm, rather than with a replSetStepDown command
* stepDownPeriodSecs - The number of seconds to step down the primary.
* secondaryCatchUpPeriodSecs - The number of seconds that the mongod will wait for an
* electable secondary to catch up to the primary.
*/
function testElectionHandoff(rst, initialPrimaryId, expectedCandidateId, options = {}) {
const config = rst.getReplSetConfigFromNode();
const numNodes = config.members.length;
const memberInfo = config.members[expectedCandidateId];
assert.neq(
true, memberInfo["arbiterOnly"], "Election handoff candidate cannot be an arbiter.");
assert.neq(
0, memberInfo["priority"], "Election handoff candidate cannot have zero priority");
rst.awaitNodesAgreeOnPrimary();
const primary = rst.getPrimary();
assert.eq(rst.nodes[initialPrimaryId], primary);
// Store the term for future verification.
const status = assert.commandWorked(primary.adminCommand({replSetGetStatus: 1}));
const term = +status.term;
jsTestLog("Enabling election handoff...");
// Election handoff is enabled by default. This test explicitly configures it for safety
// purposes.
assert.commandWorked(primary.adminCommand({setParameter: 1, enableElectionHandoff: 1}));
jsTestLog("Stepping down primary...");
// Make sure all secondaries are ready before stepping down. We must additionally
// make sure that the primary is aware that the secondaries are ready and caught up
// to the primary's lastApplied, so we issue a dummy write and wait on its optime.
assert.writeOK(primary.getDB("test").secondariesMustBeCaughtUpToHere.insert(
{"a": 1}, {writeConcern: {w: rst.nodes.length}}));
rst.awaitNodesAgreeOnAppliedOpTime();
// Step down the current primary. Skip validation since it prevents election handoff.
if (options["stepDownBySignal"]) {
rst.stop(initialPrimaryId, kSIGTERM, {skipValidation: true}, {forRestart: true});
rst.start(initialPrimaryId, {}, true);
} else {
assert.commandWorked(primary.adminCommand({
replSetStepDown: options.stepDownPeriodSecs || kStepDownPeriodSecs,
secondaryCatchUpPeriodSecs:
options.secondaryCatchUpPeriodSecs || kStepDownPeriodSecs / 2
}));
}
jsTestLog(`Checking that the secondary with id ${expectedCandidateId} is stepped up...`);
const expectedCandidate = rst.nodes[expectedCandidateId];
// The checkLog() function blocks until the log line appears.
checkLog.contains(expectedCandidate, "Starting an election due to step up request");
// If there are only two nodes in the set, verify that the old primary voted "yes".
if (numNodes === 2) {
checkLog.contains(expectedCandidate,
`skipping dry run and running for election in term ${term + 1}`);
checkLog.contains(
expectedCandidate,
`VoteRequester(term ${term + 1}) received a yes vote from ${primary.host}`);
}
rst.awaitNodesAgreeOnPrimary();
assert.eq(rst.nodes[expectedCandidateId], rst.getPrimary());
}
return {testElectionHandoff: testElectionHandoff, stepDownPeriodSecs: kStepDownPeriodSecs};
})();
|