summaryrefslogtreecommitdiff
path: root/jstests/replsets/rollback_via_refetch_commit_transaction.js
blob: 317fc7b97f82ae601f75d5576b5ae8206c5d7664 (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
/**
 * Test that a node crashes if it tries to roll back a 'commit' oplog entry using refetch-based
 * rollback. The tests mimics the standard PSA rollback setup by using a PSS replica set where the
 * last node effectively acts as an arbiter without formally being one (this is necessary because
 * we disallow the 'prepareTransaction' command in sets with arbiters).
 *
 * @tags: [uses_transactions, uses_prepare_transaction]
 */

TestData.skipCheckDBHashes = true;

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

const dbName = "test";
const collName = "rollback_via_refetch_commit_transaction";

// Provide RollbackTest with custom ReplSetTest so we can set forceRollbackViaRefetch.
const rst = new ReplSetTest({
    name: collName,
    nodes: 3,
    useBridge: true,
    nodeOptions: {setParameter: "forceRollbackViaRefetch=true"}
});

rst.startSet();
const config = rst.getReplSetConfig();
config.members[2].priority = 0;
config.settings = {
    chainingAllowed: false
};
rst.initiate(config);

const primaryNode = rst.getPrimary();

// Create collection that exists on the sync source and rollback node.
assert.commandWorked(
    primaryNode.getDB(dbName).runCommand({create: collName, writeConcern: {w: 2}}));

// Issue a 'prepareTransaction' command just to the current primary.
const session = primaryNode.getDB(dbName).getMongo().startSession({causalConsistency: false});
const sessionDB = session.getDatabase(dbName);
const sessionColl = sessionDB.getCollection(collName);
session.startTransaction();
assert.commandWorked(sessionColl.insert({"prepare": "entry"}));
const prepareTimestamp = PrepareHelpers.prepareTransaction(session);

const rollbackTest = new RollbackTest(collName, rst);
// Stop replication from the current primary ("rollbackNode").
const rollbackNode = rollbackTest.transitionToRollbackOperations();

PrepareHelpers.commitTransaction(session, prepareTimestamp);

// Step down current primary and elect a node that lacks the commit.
rollbackTest.transitionToSyncSourceOperationsBeforeRollback();

// Verify the old primary crashes trying to roll back.
clearRawMongoProgramOutput();
rollbackTest.transitionToSyncSourceOperationsDuringRollback();
jsTestLog("Waiting for crash");
assert.soon(function() {
    try {
        rollbackNode.getDB("local").runCommand({ping: 1});
    } catch (e) {
        return true;
    }
    return false;
}, "Node did not fassert", ReplSetTest.kDefaultTimeoutMS);

// Let the ReplSetTest know the old primary is down.
rst.stop(rst.getNodeId(rollbackNode), undefined, {allowedExitCode: MongoRunner.EXIT_ABRUPT});

const msg = RegExp("Can't roll back this command yet: ");
assert.soon(function() {
    return rawMongoProgramOutput().match(msg);
}, "Node did not fail to roll back entry.");

// Transaction is still in prepared state and validation will be blocked, so skip it.
rst.stopSet(undefined, undefined, {skipValidation: true});
}());