summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/reconfig_for_psa_set_shell.js
blob: e828a0042aae908bc4bf8cf183c659125cd9b850 (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
/**
 * Tests the 'reconfigForPSASet()' shell function and makes sure that reconfig will succeed while
 * preserving majority reads.
 *
 * @tags: [requires_replication]
 */

(function() {
'use strict';

load("jstests/replsets/rslib.js");

// Start up a PSA set with the secondary having 'votes: 0' and 'priority: 0'.
const rst = new ReplSetTest({
    name: jsTestName(),
    nodes: [{}, {rsConfig: {votes: 0, priority: 0}}, {rsConfig: {arbiterOnly: true}}],
});
rst.startSet();
rst.initiateWithHighElectionTimeout();

const primary = rst.getPrimary();
assert.eq(primary, rst.nodes[0], "the primary should be the node at index 0");

// Store the old config so that we can reset to it after every successful reconfig.
const originalConfig = rst.getReplSetConfigFromNode();

// Verify that directly calling the standard 'rs.reconfig()' function to give the secondary 'votes:
// 1' and 'priority: 1' will fail.
let config = rst.getReplSetConfigFromNode();
config.members[1].votes = 1;
config.members[1].priority = 1;

jsTestLog("Testing standard rs.reconfig() function");
const reconfigScript = `assert.commandFailedWithCode(rs.reconfig(${
    tojson(config)}), ErrorCodes.NewReplicaSetConfigurationIncompatible)`;
const result = runMongoProgram('mongo', '--port', primary.port, '--eval', reconfigScript);
assert.eq(0, result, `reconfig did not fail with expected error code`);

const runReconfigForPSASet = (memberIndex, config, shouldSucceed, endPriority = 1) => {
    jsTestLog(`Testing with memberIndex ${memberIndex} and config ${tojson(config)}`);

    const reconfigScript =
        `assert.commandWorked(rs.reconfigForPSASet(${memberIndex}, ${tojson(config)}))`;
    const result = runMongoProgram('mongo', '--port', primary.port, '--eval', reconfigScript);
    if (shouldSucceed) {
        assert.eq(0, result, 'expected reconfigToPSASet to succeed, but it failed');

        // Wait for all 'newlyAdded' fields to be removed and for the new config to be committed.
        rst.waitForAllNewlyAddedRemovals();
        assert.soonNoExcept(() => isConfigCommitted(primary));

        const replSetGetConfig =
            assert.commandWorked(primary.adminCommand({replSetGetConfig: 1})).config;
        assert.eq(1, replSetGetConfig.members[1].votes);
        assert.eq(endPriority, replSetGetConfig.members[1].priority);

        // Reset the config back to the original config.
        originalConfig.members[memberIndex].votes = 0;
        originalConfig.members[memberIndex].priority = 0;
        const reconfigToOriginalConfig =
            `assert.commandWorked(rs.reconfig(${tojson(originalConfig)}))`;
        assert.eq(
            0,
            runMongoProgram('mongo', '--port', primary.port, '--eval', reconfigToOriginalConfig));
        assert.soonNoExcept(() => isConfigCommitted(primary));
    } else {
        assert.neq(0, result, 'expected reconfigToPSASet to fail, but it succeeded');
    }
};

// Succeed with a reconfig to a standard PSA set, where the secondary has 'votes: 1' and 'priority:
// 1'.
jsTestLog("Testing reconfigForPSASet() succeeded: standard PSA set");
runReconfigForPSASet(1, config, true /* shouldSucceed */);

// Fail when 'memberIndex' does not refer to a node in the new config.
jsTestLog("Testing reconfigForPSASet() failed: memberIndex out of bounds");
runReconfigForPSASet(3, config, false /* shouldSucceed */);

// Fail when the node in the new config at 'memberIndex' is not a voter.
jsTestLog("Testing reconfigForPSASet() failed: node at memberIndex is not a voter");
config.members[1].votes = 0;
config.members[1].priority = 0;
runReconfigForPSASet(1, config, false /* shouldSucceed */);

// Test that reconfigToPSASet() will succeed when we are adding a new node at a specific
// 'memberIndex'.
jsTestLog("Testing reconfigForPSASet() succeeded: adding new node");

// First remove the node at index 1 to simulate a two-node replica set.
config = rst.getReplSetConfigFromNode();
const filteredMembers = config.members.filter(member => member._id !== 1);
config.members = filteredMembers;
config.version += 1;
assert.commandWorked(primary.adminCommand({replSetReconfig: config}));

// Attempt to add a node and assert that the reconfigToPSASet() call is successful.
config.members = originalConfig.members;
config.members[1].votes = 1;
config.members[1].priority = 1;
config.version += 1;
runReconfigForPSASet(1, config, true /* shouldSucceed */);

// Test that reconfigToPSASet() will succeed even if the priority of the node is 0 in the final
// config.
jsTestLog("Testing reconfigForPSASet() succeeded: priority of node is 0 in final config");
config = rst.getReplSetConfigFromNode();
config.members[1].votes = 1;
config.members[1].priority = 0;
runReconfigForPSASet(1, config, true /* shouldSucceed */, 0 /* endPriority */);

// Test that reconfigToPSASet() will fail if the node at 'memberIndex' exists in the old config but
// was a voter. It should fail because this is not the use case of the helper function.
jsTestLog("Testing reconfigForPSASet() failed: node at memberIndex was a voter in old config");

// First turn the secondary into a voting node but with priority 0.
config = rst.getReplSetConfigFromNode();
config.members[1].votes = 1;
config.members[1].priority = 0;
config.version += 1;
assert.commandWorked(primary.adminCommand({replSetReconfig: config}));

// Now attempt a reconfig to give the secondary priority 1 via the reconfigForPSASet() function.
// This should fail because this function should not be used if the secondary is a voter in the old
// config.
config.members[1].priority = 1;
config.version += 1;
runReconfigForPSASet(1, config, false /* shouldSucceed */);

rst.stopSet();
})();