Files
mongo/jstests/replsets/rollback_via_refetch_prepare.js
Vesselina Ratcheva 12e8569d31 SERVER-38040 add tags
2018-12-13 10:39:41 -05:00

121 lines
4.8 KiB
JavaScript

/**
* Test that a node crashes if it tries to roll back a 'prepare' 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]
*/
(function() {
"use strict";
load("jstests/libs/write_concern_util.js");
load("jstests/core/txns/libs/prepare_helpers.js");
const dbName = "test";
const collName = "rollback_via_refetch_prepare";
const rst = new ReplSetTest({
name: collName,
nodes: 3,
useBridge: true,
nodeOptions: {setParameter: "forceRollbackViaRefetch=true"}
});
let nodes = rst.startSet();
let config = rst.getReplSetConfig();
config.members[2].priority = 0;
rst.initiate(config);
const rollbackNode = nodes[0];
const syncSource = nodes[1];
const tiebreakerNode = nodes[2];
// Wait for primary to be up and ready.
let primary = rst.getPrimary();
assert.eq(rollbackNode, primary);
// Also wait for the secondaries.
rst.awaitSecondaryNodes();
// Create the collection we're using beforehand.
assert.commandWorked(
primary.getDB(dbName).runCommand({create: collName, writeConcern: {w: 3}}));
// Stop replication on the tiebreaker node, but keep it connected to the primary.
stopServerReplication(tiebreakerNode);
// Partition out the future primary so it doesn't receive the oplog entry.
syncSource.disconnect(rollbackNode);
syncSource.disconnect(tiebreakerNode);
// Issue a 'prepareTransaction' command just to the current primary.
const session = primary.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 result = assert.commandWorked(
session.getDatabase('admin').adminCommand({prepareTransaction: 1, writeConcern: {w: 1}}));
assert(result.prepareTimestamp,
"prepareTransaction did not return a 'prepareTimestamp': " + tojson(result));
// TODO SERVER-38161: Remove this line once we have the functionality to step down without
// having to commit or abort our prepared transactions. This 'commitTransaction' is currently
// required to be able to proceed with the test. However, this makes the node crash on the
// commit entry before getting to the prepare entry, and the goal of this test is to verify
// it crashes on the prepare entry instead.
PrepareHelpers.commitTransactionAfterPrepareTS(session, result.prepareTimestamp);
// Shift the partition to only isolate the rollback node (current primary) instead.
rollbackNode.disconnect(tiebreakerNode);
// Issue an explicit stepdown command to save us some of the wait.
try {
rollbackNode.adminCommand({replSetStepDown: 60, force: true});
} catch (e) {
// Stepdown may fail if the node has already started stepping down. We might also
// get an exception when the node closes the connection. Both are acceptable.
print('Caught exception from replSetStepDown: ' + e);
}
// Wait for the old primary to finish stepping down and become a secondary.
rst.waitForState(rollbackNode, ReplSetTest.State.SECONDARY);
// Let the sync source step up in its place by giving it the third node's vote.
syncSource.reconnect(tiebreakerNode);
primary = rst.getPrimary();
assert.eq(syncSource, primary);
// Make sure the sync source has something new on it. We let the tiebreaker node replicate
// that for some added test robustness.
restartServerReplication(tiebreakerNode);
assert.writeOK(
syncSource.getDB(dbName)["syncSourceOnly"].insert({e: 1, writeConcern: {w: "majority"}}));
clearRawMongoProgramOutput();
// Reconnect the old primary to the rest of the set. We expect it to go into rollback.
rollbackNode.reconnect(syncSource);
rollbackNode.reconnect(tiebreakerNode);
// Verify that it crashed because it failed to roll back.
assert.soon(function() {
try {
rollbackNode.getDB("local").runCommand({ping: 1});
} catch (e) {
return true;
}
return false;
}, "Node did not fassert", ReplSetTest.kDefaultTimeoutMS);
rst.stop(rst.getNodeId(rollbackNode), undefined, {allowedExitCode: MongoRunner.EXIT_ABRUPT});
var msg = RegExp("Can't roll back this command yet: ");
assert.soon(function() {
return rawMongoProgramOutput().match(msg);
}, "Node did not fail to roll back entry.");
rst.stopSet();
}());