summaryrefslogtreecommitdiff
path: root/jstests/replsets/transactions_after_rollback_via_refetch.js
blob: 80ef4a8ded9b3adb2e9abfd2f1ed00436b2566e9 (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
/**
 * Basic test that transactions are able to run against a node immediately after it has executed a
 * refetch based rollback of a few basic CRUD and DDL ops. Local writes done during the rollback
 * process are not timestamped, so we want to ensure that transactions can be started against a
 * valid snapshot post-rollback and read data correctly.
 *
 * @tags: [uses_transactions]
 */
(function() {
'use strict';

load("jstests/replsets/libs/rollback_test.js");

let name = "transactions_after_rollback_via_refetch";
let dbName = name;
let crudCollName = "crudColl";
let collToDropName = "collToDrop";

let CommonOps = (node) => {
    // Insert a couple of documents that will initially be present on all nodes.
    let crudColl = node.getDB(dbName)[crudCollName];
    assert.commandWorked(crudColl.insert({_id: 0}));
    assert.commandWorked(crudColl.insert({_id: 1}));

    // Create a collection so it can be dropped on the rollback node.
    node.getDB(dbName)[collToDropName].insert({_id: 0});
};

// We want to have the rollback node perform some inserts, updates, and deletes locally
// during the rollback process, so we can ensure that transactions will read correct data
// post-rollback, even though these writes will be un-timestamped.
let RollbackOps = (node) => {
    let crudColl = node.getDB(dbName)[crudCollName];
    // Roll back an update (causes refetch and local update).
    assert.commandWorked(crudColl.update({_id: 0}, {$set: {rollbackNode: 0}}));
    // Roll back a delete (causes refetch and local insert).
    assert.commandWorked(crudColl.remove({_id: 1}));
    // Roll back an insert (causes local delete).
    assert.commandWorked(crudColl.insert({_id: 2}));

    // Roll back a drop (re-creates the collection).
    node.getDB(dbName)[collToDropName].drop();
};

let SyncSourceOps = (node) => {
    let coll = node.getDB(dbName)[crudCollName];
    // Update these docs so the rollback node will refetch them.
    assert.commandWorked(coll.update({_id: 0}, {$set: {syncSource: 0}}));
    assert.commandWorked(coll.update({_id: 1}, {$set: {syncSource: 1}}));
};

// Set up a replica set for use in RollbackTest. We disable majority reads on all nodes so that
// they will use the "rollbackViaRefetch" algorithm.
let replTest = new ReplSetTest({
    name,
    nodes: 3,
    useBridge: true,
    settings: {chainingAllowed: false},
    nodeOptions: {enableMajorityReadConcern: "false"}
});
replTest.startSet();
let config = replTest.getReplSetConfig();
config.members[2].priority = 0;
replTest.initiateWithHighElectionTimeout(config);

let rollbackTest = new RollbackTest(name, replTest);

CommonOps(rollbackTest.getPrimary());

let rollbackNode = rollbackTest.transitionToRollbackOperations();
RollbackOps(rollbackNode);

let syncSourceNode = rollbackTest.transitionToSyncSourceOperationsBeforeRollback();
SyncSourceOps(syncSourceNode);

// Wait for rollback to finish.
rollbackTest.transitionToSyncSourceOperationsDuringRollback();
rollbackTest.transitionToSteadyStateOperations();

// Make the rollback node primary so we can run transactions against it.
rollbackTest.getTestFixture().stepUp(rollbackNode);

jsTestLog("Testing transactions against the node that just rolled back.");
const sessionOptions = {
    causalConsistency: false
};
let session = rollbackNode.getDB(dbName).getMongo().startSession(sessionOptions);
let sessionDb = session.getDatabase(dbName);
let sessionColl = sessionDb[crudCollName];

// Make sure we can do basic CRUD ops inside a transaction and read the data back correctly, pre
// and post-commit.
session.startTransaction();
// Make sure we read from the snapshot correctly.
assert.docEq(sessionColl.find().sort({_id: 1}).toArray(),
             [{_id: 0, syncSource: 0}, {_id: 1, syncSource: 1}]);
// Do some basic ops.
assert.commandWorked(sessionColl.update({_id: 0}, {$set: {inTxn: 1}}));
assert.commandWorked(sessionColl.remove({_id: 1}));
assert.commandWorked(sessionColl.insert({_id: 2}));
// Make sure we read the updated data correctly.
assert.docEq(sessionColl.find().sort({_id: 1}).toArray(),
             [{_id: 0, syncSource: 0, inTxn: 1}, {_id: 2}]);
assert.commandWorked(session.commitTransaction_forTesting());

// Make sure data is visible after commit.
assert.docEq(sessionColl.find().sort({_id: 1}).toArray(),
             [{_id: 0, syncSource: 0, inTxn: 1}, {_id: 2}]);

// Run a transaction that touches the collection that was re-created during rollback.
sessionColl = sessionDb[collToDropName];
session.startTransaction();
assert.docEq(sessionColl.find().sort({_id: 1}).toArray(), [{_id: 0}]);
assert.commandWorked(sessionColl.update({_id: 0}, {$set: {inTxn: 1}}));
assert.commandWorked(session.commitTransaction_forTesting());

// Make sure data is visible after commit.
assert.docEq(sessionColl.find().sort({_id: 1}).toArray(), [{_id: 0, inTxn: 1}]);

// Check the replica set.
rollbackTest.stop();
}());