summaryrefslogtreecommitdiff
path: root/jstests/replsets/remove_newly_added_member_index_swap_concurrent.js
blob: 3ee21bd1537b625cd7fc7060fdec6b4980075603 (plain)
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/**
 * Tests running a reconfig in the window where an automatic reconfig has been scheduled, but not
 * yet executed. The externally-issued reconfig swaps member positions (indexes) in the config so
 * that we can test that we target members by id and not index when removing 'newlyAdded' fields.
 *
 * @tags: [
 * ]
 */

(function() {
"use strict";

load("jstests/libs/fail_point_util.js");
load('jstests/replsets/rslib.js');

const testName = jsTestName();
const dbName = "testdb";
const collName = "testcoll";

const rst = new ReplSetTest({name: testName, nodes: 1, settings: {chainingAllowed: false}});
rst.startSet();
rst.initiateWithHighElectionTimeout();

const primary = rst.getPrimary();
const primaryDb = primary.getDB(dbName);
const primaryColl = primaryDb.getCollection(collName);

assert.commandWorked(primaryColl.insert({"starting": "doc"}));

const newNodeOne = rst.add({
    rsConfig: {priority: 0},
    setParameter: {
        'failpoint.initialSyncHangBeforeFinish': tojson({mode: 'alwaysOn'}),
        'numInitialSyncAttempts': 1,
    }
});

const newNodeTwo = rst.add({
    rsConfig: {priority: 0},
    setParameter: {
        'failpoint.initialSyncHangBeforeFinish': tojson({mode: 'alwaysOn'}),
        'numInitialSyncAttempts': 1,
    }
});

rst.reInitiate();

jsTestName("Checking that the member ids and indexes match in the current config");
let configOnDisk = primary.getDB("local").system.replset.findOne();
assert.eq(1, configOnDisk.members[1]._id, configOnDisk);
assert.eq(2, configOnDisk.members[2]._id, configOnDisk);

assert.commandWorked(newNodeOne.adminCommand({
    waitForFailPoint: "initialSyncHangBeforeFinish",
    timesEntered: 1,
    maxTimeMS: kDefaultWaitForFailPointTimeout
}));
assert.commandWorked(newNodeTwo.adminCommand({
    waitForFailPoint: "initialSyncHangBeforeFinish",
    timesEntered: 1,
    maxTimeMS: kDefaultWaitForFailPointTimeout
}));

jsTestLog("Checking that the 'newlyAdded' field is set on both new nodes");
assert(isMemberNewlyAdded(primary, 1));
assert(isMemberNewlyAdded(primary, 2));

jsTestLog("Checking vote counts while secondaries are still in initial sync");
assertVoteCount(primary, {
    votingMembersCount: 1,
    majorityVoteCount: 1,
    writableVotingMembersCount: 1,
    writeMajorityCount: 1,
    totalMembersCount: 3
});

jsTestLog("Allowing primary to initiate the 'newlyAdded' field removal for the first node");
let hangDuringAutomaticReconfigFP = configureFailPoint(primaryDb, "hangDuringAutomaticReconfig");
assert.commandWorked(
    newNodeOne.adminCommand({configureFailPoint: "initialSyncHangBeforeFinish", mode: "off"}));
rst.waitForState(newNodeOne, ReplSetTest.State.SECONDARY);

hangDuringAutomaticReconfigFP.wait();

jsTestLog("Swapping members in the config");
const config = assert.commandWorked(primary.adminCommand({replSetGetConfig: 1})).config;
const tempMemberOne = Object.assign({}, config.members[1]);
config.members[1] = config.members[2];
config.members[2] = tempMemberOne;
config.version++;
assert.commandWorked(primary.adminCommand({replSetReconfig: config}));
rst.waitForConfigReplication(primary);

jsTestLog("Making sure the config now has the ids and indexes flipped");
configOnDisk = primary.getDB("local").system.replset.findOne();
assert.eq(2, configOnDisk.members[1]._id, configOnDisk);
assert.eq(1, configOnDisk.members[2]._id, configOnDisk);

jsTestLog("Proceeding with the original 'newlyAdded' removal (_id 1, index 2)");
hangDuringAutomaticReconfigFP.off();
waitForNewlyAddedRemovalForNodeToBeCommitted(primary, 2 /* memberIndex */);

jsTestLog("Verifying the results of the 'newlyAdded' removal");
configOnDisk = primary.getDB("local").system.replset.findOne();
assert.eq(1, configOnDisk.members[2]._id, configOnDisk);
assert.eq(false, configOnDisk.members[2].hasOwnProperty("newlyAdded"), configOnDisk);
assertVoteCount(primary, {
    votingMembersCount: 2,
    majorityVoteCount: 2,
    writableVotingMembersCount: 2,
    writeMajorityCount: 2,
    totalMembersCount: 3,
});

jsTestLog("Making sure the other node still has its 'newlyAdded' field");
assert(isMemberNewlyAdded(primary, 1));
configOnDisk = primary.getDB("local").system.replset.findOne();
assert.eq(2, configOnDisk.members[1]._id, configOnDisk);
assert(configOnDisk.members[1]["newlyAdded"], configOnDisk);

jsTestLog("Letting the other secondary node finish initial sync");
assert.commandWorked(
    newNodeTwo.adminCommand({configureFailPoint: "initialSyncHangBeforeFinish", mode: "off"}));
rst.waitForState(newNodeTwo, ReplSetTest.State.SECONDARY);

jsTestLog("Waiting for the second 'newlyAdded' field to be removed (_id 2, index 1)");
waitForNewlyAddedRemovalForNodeToBeCommitted(primary, 1 /* memberIndex */);

jsTestLog("Verifying the results of the second 'newlyAdded' removal");
configOnDisk = primary.getDB("local").system.replset.findOne();
assert.eq(2, configOnDisk.members[1]._id, configOnDisk);
assert.eq(false, configOnDisk.members[1].hasOwnProperty("newlyAdded"), configOnDisk);
assertVoteCount(primary, {
    votingMembersCount: 3,
    majorityVoteCount: 2,
    writableVotingMembersCount: 3,
    writeMajorityCount: 2,
    totalMembersCount: 3,
});

jsTestLog("Making sure set can accept w:3 writes");
assert.commandWorked(primaryColl.insert({"steady": "state"}, {writeConcern: {w: 3}}));

rst.awaitReplication();
rst.stopSet();
})();