summaryrefslogtreecommitdiff
path: root/jstests/replsets/awaitable_ismaster_errors_on_horizon_change.js
blob: e2ac2eda372268ff2b2456cb6416ab9eaed56b8e (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
/**
 * Tests that doing a reconfig that changes the SplitHorizon will cause the server to disconnect
 * from clients with waiting isMaster requests.
 */
(function() {
"use strict";
load("jstests/libs/parallel_shell_helpers.js");
load("jstests/libs/fail_point_util.js");

// Never retry on network error, because this test needs to detect the network error.
TestData.skipRetryOnNetworkError = true;

const replTest = new ReplSetTest({nodes: [{}, {rsConfig: {priority: 0, votes: 0}}]});
replTest.startSet();
replTest.initiate();

const dbName = "awaitable_ismaster_horizon_change";
const primary = replTest.getPrimary();
const primaryDB = primary.getDB(dbName);
const secondary = replTest.getSecondary();
const secondaryDB = secondary.getDB(dbName);

function runAwaitableIsMasterBeforeHorizonChange(topologyVersionField) {
    let res = assert.throws(() => db.runCommand({
        isMaster: 1,
        topologyVersion: topologyVersionField,
        maxAwaitTimeMS: 99999999,
    }));
    assert(isNetworkError(res));

    // We should automatically reconnect after the failed command.
    assert.commandWorked(db.adminCommand({ping: 1}));
}

function runAwaitableIsMaster(topologyVersionField) {
    const result = assert.commandWorked(db.runCommand({
        isMaster: 1,
        topologyVersion: topologyVersionField,
        maxAwaitTimeMS: 99999999,
    }));
    assert.eq(topologyVersionField.counter + 1, result.topologyVersion.counter);
}

const primaryFirstResponse = assert.commandWorked(primaryDB.runCommand({isMaster: 1}));
const primaryTopologyVersion = primaryFirstResponse.topologyVersion;

const secondaryFirstResponse = assert.commandWorked(secondaryDB.runCommand({isMaster: 1}));
const secondaryTopologyVersion = secondaryFirstResponse.topologyVersion;

// A failpoint signalling that the server has received the isMaster request and is waiting for a
// topology change.
let primaryFailPoint = configureFailPoint(primary, "waitForIsMasterResponse");
let secondaryFailPoint = configureFailPoint(secondary, "waitForIsMasterResponse");
// Send an awaitable isMaster request. This will block until there is a topology change.
const awaitIsMasterHorizonChangeOnPrimary = startParallelShell(
    funWithArgs(runAwaitableIsMasterBeforeHorizonChange, primaryTopologyVersion), primary.port);
const awaitIsMasterHorizonChangeOnSecondary = startParallelShell(
    funWithArgs(runAwaitableIsMasterBeforeHorizonChange, secondaryTopologyVersion), secondary.port);
primaryFailPoint.wait();
secondaryFailPoint.wait();

// Each node has one isMaster request waiting on a topology change.
let numAwaitingTopologyChangeOnPrimary =
    primaryDB.serverStatus().connections.awaitingTopologyChanges;
let numAwaitingTopologyChangeOnSecondary =
    secondaryDB.serverStatus().connections.awaitingTopologyChanges;
assert.eq(1, numAwaitingTopologyChangeOnPrimary);
assert.eq(1, numAwaitingTopologyChangeOnSecondary);

// Doing a reconfig that changes the horizon should respond to all waiting isMasters with an error.
let rsConfig = primary.getDB("local").system.replset.findOne();
let idx = 0;
rsConfig.members.forEach(function(member) {
    member.horizons = {specialHorizon: 'horizon.com:100' + idx};
    idx++;
});
rsConfig.version++;

jsTest.log('Calling replSetReconfig with config: ' + tojson(rsConfig));
assert.commandWorked(primary.adminCommand({replSetReconfig: rsConfig}));
awaitIsMasterHorizonChangeOnPrimary();
awaitIsMasterHorizonChangeOnSecondary();

// All isMaster requests should have been responded to after the reconfig.
numAwaitingTopologyChangeOnPrimary = primaryDB.serverStatus().connections.awaitingTopologyChanges;
numAwaitingTopologyChangeOnSecondary =
    secondaryDB.serverStatus().connections.awaitingTopologyChanges;
assert.eq(0, numAwaitingTopologyChangeOnPrimary);
assert.eq(0, numAwaitingTopologyChangeOnSecondary);

const primaryRespAfterHorizonChange = assert.commandWorked(primaryDB.runCommand({isMaster: 1}));
const secondaryRespAfterHorizonChange = assert.commandWorked(secondaryDB.runCommand({isMaster: 1}));
const primaryTopVersionAfterHorizonChange = primaryRespAfterHorizonChange.topologyVersion;
const secondaryTopVersionAfterHorizonChange = secondaryRespAfterHorizonChange.topologyVersion;

// Doing a reconfig that doesn't change the horizon should increment the topologyVersion and reply
// to waiting isMasters with a successful response.
rsConfig = primary.getDB("local").system.replset.findOne();
rsConfig.members.forEach(function(member) {
    if (member.host == primary.host) {
        member.tags = {dc: 'ny'};
    } else {
        member.tags = {dc: 'sf'};
    }
});
rsConfig.version++;

// Reconfigure the failpoint to refresh the number of times the failpoint has been entered.
primaryFailPoint = configureFailPoint(primary, "waitForIsMasterResponse");
secondaryFailPoint = configureFailPoint(primary, "waitForIsMasterResponse");
// Send an awaitable isMaster request. This will block until maxAwaitTimeMS has elapsed or a
// topology change happens.
let primaryAwaitIsMasterBeforeAddingTags = startParallelShell(
    funWithArgs(runAwaitableIsMaster, primaryTopVersionAfterHorizonChange), primary.port);
let secondaryAaitIsMasterBeforeAddingTags = startParallelShell(
    funWithArgs(runAwaitableIsMaster, secondaryTopVersionAfterHorizonChange), secondary.port);
primaryFailPoint.wait();
secondaryFailPoint.wait();

jsTest.log('Calling replSetReconfig with config: ' + tojson(rsConfig));
assert.commandWorked(primary.adminCommand({replSetReconfig: rsConfig}));
primaryAwaitIsMasterBeforeAddingTags();
secondaryAaitIsMasterBeforeAddingTags();

replTest.stopSet();
})();