summaryrefslogtreecommitdiff
path: root/jstests/replsets/sync_source_selection_ignores_minvalid_after_rollback.js
blob: e4ab88ed90dcf45f156923caf11e67a1c764f0a7 (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
/**
 * Tests that the minValid optime being on a divergent branch of history does not impact sync source
 * selection after rollback. See SERVER-59721 for more details.
 *
 * TODO SERVER-49738: remove this test.
 */
(function() {
"use strict";
load("jstests/libs/fail_point_util.js");
load('jstests/libs/parallel_shell_helpers.js');
load('jstests/replsets/rslib.js');  // For syncFrom and awaitOpTime.

// Disable primary catchup since this test relies on new primaries not catching up to other nodes.
const rst = new ReplSetTest(
    {name: jsTestName(), nodes: 3, settings: {catchUpTimeoutMillis: 0}, useBridge: true});
const nodes = rst.startSet();
rst.initiateWithHighElectionTimeout();

const collName = jsTestName();
const node0 = rst.getPrimary();
const node1 = rst.getSecondaries()[0];
const node2 = rst.getSecondaries()[1];

const node0DB = node0.getDB("test");
const node0Coll = node0DB.getCollection(collName);

// The default WC is majority and various failpoints used in this test are incompatible with that.
assert.commandWorked(node0.adminCommand(
    {setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}, writeConcern: {w: "majority"}}));

// Make sure node 1 syncs from node 0 so that it will replicate entries that be rolled back.
syncFrom(node1, node0, rst);

jsTestLog("Do write that will become the new majority commit point");
assert.commandWorked(
    node0Coll.insert({_id: "majority committed"}, {writeConcern: {w: "majority"}}));

rst.awaitReplication();

jsTestLog("Disable snapshotting on all nodes");

// Disable snapshotting on all members of the replica set so that further operations do not
// enter the majority snapshot.
nodes.forEach(node => assert.commandWorked(node.adminCommand(
                  {configureFailPoint: "disableSnapshotting", mode: "alwaysOn"})));

// Stop replication on all nodes. We do this on node 0 and 1 so that they will vote for other nodes
// in future elections. We use a different failpoint for node 1 so that it won't switch sync sources
// when replication is unpaused. We stop replication on node 2 so that it doesn't receive any oplog
// entries from the diverging branch of history.
let node2StopRepl = configureFailPoint(node2, "stopReplProducer");
let node1StopRepl = configureFailPoint(node1, "hangBeforeProcessingSuccessfulBatch");
let node0StopRepl = configureFailPoint(node0, "stopReplProducer");
configureFailPoint(node1, "disableMaxSyncSourceLagSecs");

jsTestLog("Do write that will eventually be rolled back");

assert.commandWorked(node0Coll.insert({_id: "diverging point"}));

node1StopRepl.wait();
node2StopRepl.wait();

assert.commandWorked(node1.adminCommand({clearLog: 'global'}));

jsTestLog("Stepping up node 2");

// Make sure id:5972100 debug log is enabled.
setLogVerbosity([node1], {"replication": {"verbosity": 1}});

// Node 2 runs for election. This is needed before node 1 steps up because otherwise it will always
// lose future elections and will not be considered the proper branch of history.
const electionShell = startParallelShell(() => {
    const newPrimary = db.getMongo();
    const rst = new ReplSetTest(newPrimary.host);
    rst.stepUp(newPrimary, {awaitReplicationBeforeStepUp: false, awaitWritablePrimary: false});
}, node2.port);

jsTestLog("Waiting for node 1 to vote in election");
checkLog.containsJson(node1, 5972100);

jsTestLog("Waiting for node 2 to be writable primary");

// Wait for parallelShell to exit. This means that node 2 has successfully transitioned to primary.
electionShell();
assert.eq(rst.getPrimary(), node2);

jsTestLog("Waiting for node 1 to replicate diverging branch");
// Disconnect node 1 from node 2 so that node 1 won't switch sync sources from node 0 to node 2.
// It's okay if node 1 doesn't have a sync source since it should have already received the batch
// from node 0 by the time we stopped replication.
node1.disconnect(node2);
node1StopRepl.off();
awaitOpTime(node1, node0);
node1.reconnect(node2);

jsTestLog("Waiting for node 0 to step down");
rst.awaitSecondaryNodes(null, [node0]);

// Node 0 won't replicate node 2's new primary oplog entry, so it can elect node 1 again.
node0StopRepl.wait();

jsTestLog("Stepping node 1 up");
// Step up node 1, which causes an untimestamped write to the minValid collection.
rst.stepUp(node1, {awaitReplicationBeforeStepUp: false});

jsTestLog("Stepping node 2 up");

// Node 0 votes for node 2 in this eleciton. Node 2 is ahead of node 0 because of the previous
// election that it won.
rst.stepUp(node2, {awaitReplicationBeforeStepUp: false});

const node2Coll = node2.getDB("test").getCollection(collName);

node0StopRepl.off();
node2StopRepl.off();

jsTestLog("Doing a write on the proper branch of history");
assert.commandWorked(node2Coll.insert({_id: "proper branch of history"}));

jsTestLog("Waiting for node 1 to complete rollback");
rst.awaitSecondaryNodes();

jsTestLog("Node 1 completed rollback");

// awaitReplication will only succeed if node 1 was able to successfully choose a sync source.
rst.awaitReplication();

assert.eq(node2Coll.find({_id: "proper branch of history"}).itcount(), 1);
assert.eq(node2Coll.find({_id: "diverging point"}).itcount(), 0);

rst.stopSet();
})();