summaryrefslogtreecommitdiff
path: root/jstests/replsets/standalone_replication_recovery_idempotent.js
blob: 3ced688da2d291512f0a5d7cb1f026c96f16daaf (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
 * Tests that a 'recoverFromOplogAsStandalone' with 'takeUnstableCheckpointOnShutdown' is
 * idempotent.
 *
 * This test only makes sense for storage engines that support recover to stable timestamp.
 * @tags: [requires_wiredtiger, requires_persistence, requires_journaling, requires_replication,
 * requires_majority_read_concern, uses_transactions, uses_prepare_transaction,
 * # Restarting as a standalone is not supported in multiversion tests.
 * multiversion_incompatible]
 */

(function() {
"use strict";
load("jstests/replsets/rslib.js");
load("jstests/libs/write_concern_util.js");
load("jstests/core/txns/libs/prepare_helpers.js");

const name = jsTestName();
const dbName = name;
const collName1 = 'srri_coll1';
const collName2 = 'srri_coll2';

const logLevel = tojson({storage: {recovery: 2}});

const rst = new ReplSetTest({
    nodes: 2,
});

function getColl1(conn) {
    return conn.getDB(dbName)[collName1];
}

function getColl2(conn) {
    return conn.getDB(dbName)[collName2];
}

function assertDocsInColl1(node, nums) {
    let results = getColl1(node).find().sort({_id: 1}).toArray();
    let expected = nums.map((i) => ({_id: i}));
    if (!friendlyEqual(results, expected)) {
        rst.dumpOplog(node, {}, 100);
    }
    assert.eq(results, expected, "actual (left) != expected (right)");
}

function assertPrepareConflictColl2(node, id) {
    assert.sameMembers(getColl2(node).find().toArray(), [{_id: id}]);
    assert.commandFailedWithCode(
        node.getDB(dbName).runCommand(
            {update: collName2, updates: [{q: {_id: id}, u: {$inc: {x: 1}}}], maxTimeMS: 1000}),
        ErrorCodes.MaxTimeMSExpired);
}

jsTestLog("Initiating as a replica set.");
let nodes = rst.startSet({setParameter: {logComponentVerbosity: logLevel}});
let node = nodes[0];
let secondary = nodes[1];
rst.initiate(
    {_id: name, members: [{_id: 0, host: node.host}, {_id: 1, host: secondary.host, priority: 0}]});

// The default WC is majority and stopServerReplication will prevent satisfying any majority writes.
assert.commandWorked(rst.getPrimary().adminCommand(
    {setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}, writeConcern: {w: "majority"}}));

// Create two collections with w:majority and then perform a clean restart to ensure that
// the collections are in a stable checkpoint.
assert.commandWorked(getColl1(node).insert({_id: 3}, {writeConcern: {w: "majority"}}));
assert.commandWorked(getColl2(node).insert({_id: 1}, {writeConcern: {w: "majority"}}));

node = rst.restart(node, {"noReplSet": false});
reconnect(node);
assert.eq(rst.getPrimary(), node);
assertDocsInColl1(node, [3]);
assert.sameMembers(getColl2(node).find().toArray(), [{_id: 1}]);

// Keep node 0 the primary, but prevent it from committing any writes.
stopServerReplication(secondary);
assert.commandWorked(getColl1(node).insert({_id: 4}, {writeConcern: {w: 1, j: 1}}));
assertDocsInColl1(node, [3, 4]);

let session = node.startSession();
const sessionDB = session.getDatabase(dbName);
const sessionColl2 = sessionDB.getCollection(collName2);
session.startTransaction();
const txnNumber = session.getTxnNumber_forTesting();
assert.commandWorked(sessionColl2.update({_id: 1}, {_id: 1, a: 1}));
let prepareTimestamp = PrepareHelpers.prepareTransaction(session, {w: 1, j: 1});
jsTestLog("Prepared a transaction at " + tojson(prepareTimestamp));
assertPrepareConflictColl2(node, 1);

jsTestLog("Test that on restart with just 'recoverFromOplogAsStandalone' set we play recovery.");
node = rst.restart(node, {
    noReplSet: true,
    setParameter: {recoverFromOplogAsStandalone: true, logComponentVerbosity: logLevel}
});
reconnect(node);
assertDocsInColl1(node, [3, 4]);
assertPrepareConflictColl2(node, 1);
assert.commandFailedWithCode(getColl1(node).insert({_id: 7}), ErrorCodes.IllegalOperation);

jsTestLog("Test that on restart with just 'recoverFromOplogAsStandalone' we succeed" +
          " idempotently.");
node = rst.restart(node, {
    noReplSet: true,
    setParameter: {recoverFromOplogAsStandalone: true, logComponentVerbosity: logLevel}
});
reconnect(node);
assertDocsInColl1(node, [3, 4]);
assertPrepareConflictColl2(node, 1);
assert.commandFailedWithCode(getColl1(node).insert({_id: 7}), ErrorCodes.IllegalOperation);

jsTestLog("Test that on restart with both flags we succeed.");
node = rst.restart(node, {
    noReplSet: true,
    setParameter: {
        recoverFromOplogAsStandalone: true,
        takeUnstableCheckpointOnShutdown: true,
        logComponentVerbosity: logLevel
    }
});
reconnect(node);
assertDocsInColl1(node, [3, 4]);
assertPrepareConflictColl2(node, 1);
assert.commandFailedWithCode(getColl1(node).insert({_id: 7}), ErrorCodes.IllegalOperation);

jsTestLog("Test that on restart with both flags we succeed idempotently.");
node = rst.restart(node, {
    noReplSet: true,
    setParameter: {
        recoverFromOplogAsStandalone: true,
        takeUnstableCheckpointOnShutdown: true,
        logComponentVerbosity: logLevel
    }
});
reconnect(node);
assertDocsInColl1(node, [3, 4]);
assertPrepareConflictColl2(node, 1);
assert.commandFailedWithCode(getColl1(node).insert({_id: 7}), ErrorCodes.IllegalOperation);

jsTestLog("Restart as a replica set node so that we can commit the transaction");
node = rst.restart(node, {
    noReplSet: false,
    setParameter: {
        recoverFromOplogAsStandalone: false,
        takeUnstableCheckpointOnShutdown: false,
        logComponentVerbosity: logLevel
    }
});
reconnect(node);
assert.eq(rst.getPrimary(), node);
assertDocsInColl1(node, [3, 4]);
assertPrepareConflictColl2(node, 1);

restartServerReplication(secondary);
PrepareHelpers.awaitMajorityCommitted(rst, prepareTimestamp);

assertDocsInColl1(node, [3, 4]);
assertPrepareConflictColl2(node, 1);

assert.commandWorked(node.adminCommand({
    commitTransaction: 1,
    commitTimestamp: prepareTimestamp,
    lsid: session.getSessionId(),
    txnNumber: NumberLong(txnNumber),
    autocommit: false
}));
assert.sameMembers(getColl2(node).find().toArray(), [{_id: 1, a: 1}]);

rst.stopSet();
})();