summaryrefslogtreecommitdiff
path: root/jstests/replsets/rollback_transaction_table.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/replsets/rollback_transaction_table.js')
-rw-r--r--jstests/replsets/rollback_transaction_table.js426
1 files changed, 212 insertions, 214 deletions
diff --git a/jstests/replsets/rollback_transaction_table.js b/jstests/replsets/rollback_transaction_table.js
index e44894a5b4c..3c1a18b436e 100644
--- a/jstests/replsets/rollback_transaction_table.js
+++ b/jstests/replsets/rollback_transaction_table.js
@@ -17,218 +17,216 @@
* - A record for the third session id was created during oplog replay.
*/
(function() {
- "use strict";
-
- // This test drops a collection in the config database, which is not allowed under a session. It
- // also manually simulates a session, which is not compatible with implicit sessions.
- TestData.disableImplicitSessions = true;
-
- load("jstests/libs/retryable_writes_util.js");
-
- if (!RetryableWritesUtil.storageEngineSupportsRetryableWrites(jsTest.options().storageEngine)) {
- jsTestLog("Retryable writes are not supported, skipping test");
- return;
- }
-
- load("jstests/replsets/rslib.js");
-
- function assertSameRecordOnBothConnections(primary, secondary, lsid) {
- let primaryRecord = primary.getDB("config").transactions.findOne({"_id.id": lsid.id});
- let secondaryRecord = secondary.getDB("config").transactions.findOne({"_id.id": lsid.id});
-
- jsTestLog("Primary record: " + tojson(primaryRecord));
- jsTestLog("Secondary record: " + tojson(secondaryRecord));
-
- assert.eq(bsonWoCompare(primaryRecord, secondaryRecord),
- 0,
- "expected transaction records: " + tojson(primaryRecord) + " and " +
- tojson(secondaryRecord) + " to be the same for lsid: " + tojson(lsid));
- }
-
- function assertRecordHasTxnNumber(conn, lsid, txnNum) {
- let recordTxnNum = conn.getDB("config").transactions.findOne({"_id.id": lsid.id}).txnNum;
- assert.eq(recordTxnNum,
- txnNum,
- "expected node: " + conn + " to have txnNumber: " + txnNum + " for session id: " +
- lsid + " - instead found: " + recordTxnNum);
- }
-
- let testName = "rollback_transaction_table";
- let dbName = "test";
-
- let replTest = new ReplSetTest({
- name: testName,
- nodes: [
- // Primary flops between nodes 0 and 1.
- {},
- {},
- // Arbiter to sway elections.
- {rsConfig: {arbiterOnly: true}}
- ],
- useBridge: true,
- });
- let nodes = replTest.startSet();
- replTest.initiate();
-
- let downstream = nodes[0];
- let upstream = nodes[1];
- let arbiter = nodes[2];
-
- jsTestLog("Making sure 'downstream node' is the primary node.");
- assert.eq(downstream, replTest.getPrimary());
-
- // Renaming or dropping the transactions collection shouldn't crash if command is not rolled
- // back.
- assert.commandWorked(downstream.getDB("config").transactions.renameCollection("foo"));
- assert.commandWorked(downstream.getDB("config").foo.renameCollection("transactions"));
- assert(downstream.getDB("config").transactions.drop());
- assert.commandWorked(downstream.getDB("config").createCollection("transactions"));
-
- jsTestLog("Running a transaction on the 'downstream node' and waiting for it to replicate.");
- let firstLsid = {id: UUID()};
- let firstCmd = {
- insert: "foo",
- documents: [{_id: 10}, {_id: 30}],
- ordered: false,
- lsid: firstLsid,
- txnNumber: NumberLong(5)
- };
-
- assert.commandWorked(downstream.getDB(dbName).runCommand(firstCmd));
- replTest.awaitReplication();
-
- // Both data bearing nodes should have the same record for the first session id.
- assertSameRecordOnBothConnections(downstream, upstream, firstLsid);
-
- assert.eq(downstream.getDB("config").transactions.find().itcount(), 1);
- assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(5));
-
- assert.eq(upstream.getDB("config").transactions.find().itcount(), 1);
- assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
-
- jsTestLog(
- "Creating a partition between 'the downstream and arbiter node' and 'the upstream node.'");
- downstream.disconnect(upstream);
- arbiter.disconnect(upstream);
-
- jsTestLog(
- "Running a higher transaction for the existing session on only the 'downstream node.'");
- let higherTxnFirstCmd = {
- insert: "foo",
- documents: [{_id: 50}],
- ordered: false,
- lsid: firstLsid,
- txnNumber: NumberLong(20)
- };
-
- assert.commandWorked(downstream.getDB(dbName).runCommand(higherTxnFirstCmd));
-
- // Now the data bearing nodes should have different transaction table records for the first
- // session id.
- assert.eq(downstream.getDB("config").transactions.find().itcount(), 1);
- assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(20));
-
- assert.eq(upstream.getDB("config").transactions.find().itcount(), 1);
- assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
-
- jsTestLog("Running a transaction for a second session on the 'downstream node.'");
- let secondLsid = {id: UUID()};
- let secondCmd = {
- insert: "foo",
- documents: [{_id: 100}, {_id: 200}],
- ordered: false,
- lsid: secondLsid,
- txnNumber: NumberLong(100)
- };
-
- assert.commandWorked(downstream.getDB(dbName).runCommand(secondCmd));
-
- // Only the downstream node should have two transaction table records, one for the first and
- // second session ids.
- assert.eq(downstream.getDB("config").transactions.find().itcount(), 2);
- assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(20));
- assertRecordHasTxnNumber(downstream, secondLsid, NumberLong(100));
-
- assert.eq(upstream.getDB("config").transactions.find().itcount(), 1);
- assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
-
- // We do not disconnect the downstream node from the arbiter node at the same time as we
- // disconnect it from the upstream node. This prevents a race where the transaction using the
- // second session id must finish before the downstream node steps down from being the primary.
- jsTestLog(
- "Disconnecting the 'downstream node' from the 'arbiter node' and reconnecting the 'upstream node' to the 'arbiter node.'");
- downstream.disconnect(arbiter);
- upstream.reconnect(arbiter);
-
- jsTestLog("Waiting for the 'upstream node' to become the new primary.");
- waitForState(downstream, ReplSetTest.State.SECONDARY);
- waitForState(upstream, ReplSetTest.State.PRIMARY);
- assert.eq(upstream, replTest.getPrimary());
-
- jsTestLog("Running a new transaction for a third session on the 'upstream node.'");
- let thirdLsid = {id: UUID()};
- let thirdCmd = {
- insert: "foo",
- documents: [{_id: 1000}, {_id: 2000}],
- ordered: false,
- lsid: thirdLsid,
- txnNumber: NumberLong(1)
- };
-
- assert.commandWorked(upstream.getDB(dbName).runCommand(thirdCmd));
-
- // Now the upstream node also has two transaction table records, but for the first and third
- // session ids, not the first and second.
- assert.eq(downstream.getDB("config").transactions.find().itcount(), 2);
- assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(20));
- assertRecordHasTxnNumber(downstream, secondLsid, NumberLong(100));
-
- assert.eq(upstream.getDB("config").transactions.find().itcount(), 2);
- assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
- assertRecordHasTxnNumber(upstream, thirdLsid, NumberLong(1));
-
- // Gets the rollback ID of the downstream node before rollback occurs.
- let downstreamRBIDBefore = assert.commandWorked(downstream.adminCommand('replSetGetRBID')).rbid;
-
- jsTestLog("Reconnecting the 'downstream node.'");
- downstream.reconnect(upstream);
- downstream.reconnect(arbiter);
-
- jsTestLog("Waiting for the 'downstream node' to complete rollback.");
- replTest.awaitReplication();
- replTest.awaitSecondaryNodes();
-
- // Ensure that connection to the downstream node is re-established, since the connection should
- // have gotten killed during the downstream node's transition to ROLLBACK state.
- reconnect(downstream);
-
- jsTestLog(
- "Checking the rollback ID of the downstream node to confirm that a rollback occurred.");
- assert.neq(downstreamRBIDBefore,
- assert.commandWorked(downstream.adminCommand('replSetGetRBID')).rbid);
-
- // Verify the record for the first lsid rolled back to its original value, the record for the
- // second lsid was removed, and the record for the third lsid was created during oplog replay.
- jsTestLog("Verifying the transaction collection rolled back properly.");
-
- assertSameRecordOnBothConnections(downstream, upstream, firstLsid);
- assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(5));
- assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
-
- assert.isnull(downstream.getDB("config").transactions.findOne({"_id.id": secondLsid.id}));
- assert.isnull(upstream.getDB("config").transactions.findOne({"_id.id": secondLsid.id}));
-
- assertSameRecordOnBothConnections(downstream, upstream, thirdLsid);
- assertRecordHasTxnNumber(downstream, thirdLsid, NumberLong(1));
- assertRecordHasTxnNumber(upstream, thirdLsid, NumberLong(1));
-
- assert.eq(downstream.getDB("config").transactions.find().itcount(), 2);
- assert.eq(upstream.getDB("config").transactions.find().itcount(), 2);
-
- // Confirm the nodes are consistent.
- replTest.checkOplogs();
- replTest.checkReplicatedDataHashes(testName);
- replTest.checkCollectionCounts();
-
- replTest.stopSet();
+"use strict";
+
+// This test drops a collection in the config database, which is not allowed under a session. It
+// also manually simulates a session, which is not compatible with implicit sessions.
+TestData.disableImplicitSessions = true;
+
+load("jstests/libs/retryable_writes_util.js");
+
+if (!RetryableWritesUtil.storageEngineSupportsRetryableWrites(jsTest.options().storageEngine)) {
+ jsTestLog("Retryable writes are not supported, skipping test");
+ return;
+}
+
+load("jstests/replsets/rslib.js");
+
+function assertSameRecordOnBothConnections(primary, secondary, lsid) {
+ let primaryRecord = primary.getDB("config").transactions.findOne({"_id.id": lsid.id});
+ let secondaryRecord = secondary.getDB("config").transactions.findOne({"_id.id": lsid.id});
+
+ jsTestLog("Primary record: " + tojson(primaryRecord));
+ jsTestLog("Secondary record: " + tojson(secondaryRecord));
+
+ assert.eq(bsonWoCompare(primaryRecord, secondaryRecord),
+ 0,
+ "expected transaction records: " + tojson(primaryRecord) + " and " +
+ tojson(secondaryRecord) + " to be the same for lsid: " + tojson(lsid));
+}
+
+function assertRecordHasTxnNumber(conn, lsid, txnNum) {
+ let recordTxnNum = conn.getDB("config").transactions.findOne({"_id.id": lsid.id}).txnNum;
+ assert.eq(recordTxnNum,
+ txnNum,
+ "expected node: " + conn + " to have txnNumber: " + txnNum +
+ " for session id: " + lsid + " - instead found: " + recordTxnNum);
+}
+
+let testName = "rollback_transaction_table";
+let dbName = "test";
+
+let replTest = new ReplSetTest({
+ name: testName,
+ nodes: [
+ // Primary flops between nodes 0 and 1.
+ {},
+ {},
+ // Arbiter to sway elections.
+ {rsConfig: {arbiterOnly: true}}
+ ],
+ useBridge: true,
+});
+let nodes = replTest.startSet();
+replTest.initiate();
+
+let downstream = nodes[0];
+let upstream = nodes[1];
+let arbiter = nodes[2];
+
+jsTestLog("Making sure 'downstream node' is the primary node.");
+assert.eq(downstream, replTest.getPrimary());
+
+// Renaming or dropping the transactions collection shouldn't crash if command is not rolled
+// back.
+assert.commandWorked(downstream.getDB("config").transactions.renameCollection("foo"));
+assert.commandWorked(downstream.getDB("config").foo.renameCollection("transactions"));
+assert(downstream.getDB("config").transactions.drop());
+assert.commandWorked(downstream.getDB("config").createCollection("transactions"));
+
+jsTestLog("Running a transaction on the 'downstream node' and waiting for it to replicate.");
+let firstLsid = {id: UUID()};
+let firstCmd = {
+ insert: "foo",
+ documents: [{_id: 10}, {_id: 30}],
+ ordered: false,
+ lsid: firstLsid,
+ txnNumber: NumberLong(5)
+};
+
+assert.commandWorked(downstream.getDB(dbName).runCommand(firstCmd));
+replTest.awaitReplication();
+
+// Both data bearing nodes should have the same record for the first session id.
+assertSameRecordOnBothConnections(downstream, upstream, firstLsid);
+
+assert.eq(downstream.getDB("config").transactions.find().itcount(), 1);
+assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(5));
+
+assert.eq(upstream.getDB("config").transactions.find().itcount(), 1);
+assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
+
+jsTestLog(
+ "Creating a partition between 'the downstream and arbiter node' and 'the upstream node.'");
+downstream.disconnect(upstream);
+arbiter.disconnect(upstream);
+
+jsTestLog("Running a higher transaction for the existing session on only the 'downstream node.'");
+let higherTxnFirstCmd = {
+ insert: "foo",
+ documents: [{_id: 50}],
+ ordered: false,
+ lsid: firstLsid,
+ txnNumber: NumberLong(20)
+};
+
+assert.commandWorked(downstream.getDB(dbName).runCommand(higherTxnFirstCmd));
+
+// Now the data bearing nodes should have different transaction table records for the first
+// session id.
+assert.eq(downstream.getDB("config").transactions.find().itcount(), 1);
+assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(20));
+
+assert.eq(upstream.getDB("config").transactions.find().itcount(), 1);
+assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
+
+jsTestLog("Running a transaction for a second session on the 'downstream node.'");
+let secondLsid = {id: UUID()};
+let secondCmd = {
+ insert: "foo",
+ documents: [{_id: 100}, {_id: 200}],
+ ordered: false,
+ lsid: secondLsid,
+ txnNumber: NumberLong(100)
+};
+
+assert.commandWorked(downstream.getDB(dbName).runCommand(secondCmd));
+
+// Only the downstream node should have two transaction table records, one for the first and
+// second session ids.
+assert.eq(downstream.getDB("config").transactions.find().itcount(), 2);
+assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(20));
+assertRecordHasTxnNumber(downstream, secondLsid, NumberLong(100));
+
+assert.eq(upstream.getDB("config").transactions.find().itcount(), 1);
+assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
+
+// We do not disconnect the downstream node from the arbiter node at the same time as we
+// disconnect it from the upstream node. This prevents a race where the transaction using the
+// second session id must finish before the downstream node steps down from being the primary.
+jsTestLog(
+ "Disconnecting the 'downstream node' from the 'arbiter node' and reconnecting the 'upstream node' to the 'arbiter node.'");
+downstream.disconnect(arbiter);
+upstream.reconnect(arbiter);
+
+jsTestLog("Waiting for the 'upstream node' to become the new primary.");
+waitForState(downstream, ReplSetTest.State.SECONDARY);
+waitForState(upstream, ReplSetTest.State.PRIMARY);
+assert.eq(upstream, replTest.getPrimary());
+
+jsTestLog("Running a new transaction for a third session on the 'upstream node.'");
+let thirdLsid = {id: UUID()};
+let thirdCmd = {
+ insert: "foo",
+ documents: [{_id: 1000}, {_id: 2000}],
+ ordered: false,
+ lsid: thirdLsid,
+ txnNumber: NumberLong(1)
+};
+
+assert.commandWorked(upstream.getDB(dbName).runCommand(thirdCmd));
+
+// Now the upstream node also has two transaction table records, but for the first and third
+// session ids, not the first and second.
+assert.eq(downstream.getDB("config").transactions.find().itcount(), 2);
+assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(20));
+assertRecordHasTxnNumber(downstream, secondLsid, NumberLong(100));
+
+assert.eq(upstream.getDB("config").transactions.find().itcount(), 2);
+assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
+assertRecordHasTxnNumber(upstream, thirdLsid, NumberLong(1));
+
+// Gets the rollback ID of the downstream node before rollback occurs.
+let downstreamRBIDBefore = assert.commandWorked(downstream.adminCommand('replSetGetRBID')).rbid;
+
+jsTestLog("Reconnecting the 'downstream node.'");
+downstream.reconnect(upstream);
+downstream.reconnect(arbiter);
+
+jsTestLog("Waiting for the 'downstream node' to complete rollback.");
+replTest.awaitReplication();
+replTest.awaitSecondaryNodes();
+
+// Ensure that connection to the downstream node is re-established, since the connection should
+// have gotten killed during the downstream node's transition to ROLLBACK state.
+reconnect(downstream);
+
+jsTestLog("Checking the rollback ID of the downstream node to confirm that a rollback occurred.");
+assert.neq(downstreamRBIDBefore,
+ assert.commandWorked(downstream.adminCommand('replSetGetRBID')).rbid);
+
+// Verify the record for the first lsid rolled back to its original value, the record for the
+// second lsid was removed, and the record for the third lsid was created during oplog replay.
+jsTestLog("Verifying the transaction collection rolled back properly.");
+
+assertSameRecordOnBothConnections(downstream, upstream, firstLsid);
+assertRecordHasTxnNumber(downstream, firstLsid, NumberLong(5));
+assertRecordHasTxnNumber(upstream, firstLsid, NumberLong(5));
+
+assert.isnull(downstream.getDB("config").transactions.findOne({"_id.id": secondLsid.id}));
+assert.isnull(upstream.getDB("config").transactions.findOne({"_id.id": secondLsid.id}));
+
+assertSameRecordOnBothConnections(downstream, upstream, thirdLsid);
+assertRecordHasTxnNumber(downstream, thirdLsid, NumberLong(1));
+assertRecordHasTxnNumber(upstream, thirdLsid, NumberLong(1));
+
+assert.eq(downstream.getDB("config").transactions.find().itcount(), 2);
+assert.eq(upstream.getDB("config").transactions.find().itcount(), 2);
+
+// Confirm the nodes are consistent.
+replTest.checkOplogs();
+replTest.checkReplicatedDataHashes(testName);
+replTest.checkCollectionCounts();
+
+replTest.stopSet();
}());