summaryrefslogtreecommitdiff
path: root/jstests/noPassthrough/txn_override_causal_consistency.js
diff options
context:
space:
mode:
authorJack Mulrow <jack.mulrow@mongodb.com>2018-12-11 17:29:29 -0500
committerJack Mulrow <jack.mulrow@mongodb.com>2018-12-17 18:53:53 -0500
commit9d9ac978ebe4a2fb11a6c278daff204b54e02e7b (patch)
tree3b1dd15a7ac36ce6c1faaa950f75ba4329951c24 /jstests/noPassthrough/txn_override_causal_consistency.js
parent0c6f72ea1828b9dbbcc86e61d04e1f925ca870fc (diff)
downloadmongo-9d9ac978ebe4a2fb11a6c278daff204b54e02e7b.tar.gz
SERVER-38590 Use txn_override.js without causal consistency in suites that don't require it
Diffstat (limited to 'jstests/noPassthrough/txn_override_causal_consistency.js')
-rw-r--r--jstests/noPassthrough/txn_override_causal_consistency.js204
1 files changed, 204 insertions, 0 deletions
diff --git a/jstests/noPassthrough/txn_override_causal_consistency.js b/jstests/noPassthrough/txn_override_causal_consistency.js
new file mode 100644
index 00000000000..78d3f6feed1
--- /dev/null
+++ b/jstests/noPassthrough/txn_override_causal_consistency.js
@@ -0,0 +1,204 @@
+/**
+ * Verifies the txn_override passthrough respects the causal consistency setting on TestData when
+ * starting a transaction.
+ *
+ * @tags: [requires_replication, uses_transactions]
+ */
+(function() {
+ "use strict";
+
+ const dbName = "test";
+ const collName = "foo";
+
+ const rst = new ReplSetTest({nodes: 1});
+ rst.startSet();
+ rst.initiate();
+ const conn = new Mongo(rst.getPrimary().host);
+
+ // Create the collection so the override doesn't try to when it is not expected.
+ assert.commandWorked(conn.getDB(dbName).createCollection(collName));
+
+ // Override runCommand to add each command it sees to a global array that can be inspected by
+ // this test and to allow mocking certain responses.
+ let cmdObjsSeen = [];
+ let mockNetworkError, mockFirstResponse, mockFirstCommitResponse;
+ const mongoRunCommandOriginal = Mongo.prototype.runCommand;
+ Mongo.prototype.runCommand = function runCommandSpy(dbName, cmdObj, options) {
+ cmdObjsSeen.push(cmdObj);
+
+ if (mockNetworkError) {
+ mockNetworkError = undefined;
+ throw new Error("network error");
+ }
+
+ if (mockFirstResponse) {
+ const mockedRes = mockFirstResponse;
+ mockFirstResponse = undefined;
+ return mockedRes;
+ }
+
+ const cmdName = Object.keys(cmdObj)[0];
+ if (cmdName === "commitTransaction" && mockFirstCommitResponse) {
+ const mockedRes = mockFirstCommitResponse;
+ mockFirstCommitResponse = undefined;
+ return mockedRes;
+ }
+
+ return mongoRunCommandOriginal.apply(this, arguments);
+ };
+
+ // Runs the given function with a collection from a session made with the sessionOptions on
+ // TestData and asserts the seen commands that would start a transaction have or do not have
+ // afterClusterTime.
+ function inspectFirstCommandForAfterClusterTime(conn, cmdName, isCausal, expectRetry, func) {
+ const session = conn.startSession(TestData.sessionOptions);
+ const sessionDB = session.getDatabase(dbName);
+ const sessionColl = sessionDB[collName];
+
+ cmdObjsSeen = [];
+ func(sessionColl);
+
+ // Find all requests sent with the expected command name, in case the scenario allows
+ // retrying more than once or expects to end with a commit.
+ let cmds = [];
+ if (!expectRetry) {
+ assert.eq(1, cmdObjsSeen.length);
+ cmds.push(cmdObjsSeen[0]);
+ } else {
+ assert.lt(1, cmdObjsSeen.length);
+ cmds = cmdObjsSeen.filter(obj => Object.keys(obj)[0] === cmdName);
+ }
+
+ for (let cmd of cmds) {
+ if (isCausal) {
+ assert(cmd.hasOwnProperty("readConcern"),
+ "Expected " + tojson(cmd) + " to have a read concern.");
+ assert(cmd.readConcern.hasOwnProperty("afterClusterTime"),
+ "Expected " + tojson(cmd) + " to have an afterClusterTime.");
+ } else {
+ if (TestData.hasOwnProperty("enableMajorityReadConcern") &&
+ TestData.enableMajorityReadConcern === false) {
+ // Commands not allowed in a transaction without causal consistency will not
+ // have a read concern on variants that don't enable majority read concern.
+ continue;
+ }
+
+ assert(cmd.hasOwnProperty("readConcern"),
+ "Expected " + tojson(cmd) + " to have a read concern.");
+ assert(!cmd.readConcern.hasOwnProperty("afterClusterTime"),
+ "Expected " + tojson(cmd) + " to not have an afterClusterTime.");
+ }
+ }
+
+ // Run a command not runnable in a transaction to reset the override's transaction state.
+ assert.commandWorked(sessionDB.runCommand({ping: 1}));
+
+ session.endSession();
+ }
+
+ // Helper methods for testing specific commands.
+
+ function testInsert(conn, isCausal, expectRetry) {
+ inspectFirstCommandForAfterClusterTime(conn, "insert", isCausal, expectRetry, (coll) => {
+ assert.writeOK(coll.insert({x: 1}));
+ });
+ }
+
+ function testFind(conn, isCausal, expectRetry) {
+ inspectFirstCommandForAfterClusterTime(conn, "find", isCausal, expectRetry, (coll) => {
+ assert.eq(0, coll.find({y: 1}).itcount());
+ });
+ }
+
+ function testCount(conn, isCausal, expectRetry) {
+ inspectFirstCommandForAfterClusterTime(conn, "count", isCausal, expectRetry, (coll) => {
+ assert.eq(0, coll.count({y: 1}));
+ });
+ }
+
+ function testCommit(conn, isCausal, expectRetry) {
+ inspectFirstCommandForAfterClusterTime(conn, "count", isCausal, expectRetry, (coll) => {
+ assert.eq(0, coll.count({y: 1}));
+ assert.commandWorked(coll.getDB().runCommand({ping: 1})); // commits the transaction.
+ });
+ }
+
+ // Load the txn_override after creating the spy, so the spy will see commands after being
+ // transformed by the override. Also load auto_retry_on_network_error because several suites use
+ // both.
+ load("jstests/libs/txns/txn_override.js");
+ load("jstests/libs/override_methods/auto_retry_on_network_error.js");
+
+ TestData.logRetryAttempts = true;
+
+ // Run a command to guarantee operation time is initialized on the database's session.
+ assert.commandWorked(conn.getDB(dbName).runCommand({ping: 1}));
+
+ function runTest() {
+ for (let isCausal of[false, true]) {
+ TestData.sessionOptions = {causalConsistency: isCausal};
+
+ // Commands that accept read and write concern allowed in a transaction.
+ testInsert(conn, isCausal, false /*expectRetry*/);
+ testFind(conn, isCausal, false /*expectRetry*/);
+
+ // Command that can accept read concern not allowed in a transaction.
+ testCount(conn, isCausal, false /*expectRetry*/);
+
+ // Command that attempts to implicitly create a collection.
+ conn.getDB(dbName)[collName].drop();
+ testInsert(conn, isCausal, true /*expectRetry*/);
+
+ // Command that can accept read concern with retryable error.
+ mockFirstResponse = {ok: 0, code: ErrorCodes.CursorKilled};
+ testFind(conn, isCausal, true /*expectRetry*/);
+
+ // Commands that can accept read and write concern with network error.
+ mockNetworkError = true;
+ testInsert(conn, isCausal, true /*expectRetry*/);
+
+ mockNetworkError = true;
+ testFind(conn, isCausal, true /*expectRetry*/);
+
+ // Command that can accept read concern not allowed in a transaction with network error.
+ mockNetworkError = true;
+ testCount(conn, isCausal, true /*expectRetry*/);
+
+ // Commands that can accept read and write concern with transient transaction error.
+ mockFirstResponse = {
+ ok: 0,
+ code: ErrorCodes.NoSuchTransaction,
+ errorLabels: ["TransientTransactionError"]
+ };
+ testFind(conn, isCausal, true /*expectRetry*/);
+
+ mockFirstResponse = {
+ ok: 0,
+ code: ErrorCodes.NoSuchTransaction,
+ errorLabels: ["TransientTransactionError"]
+ };
+ testInsert(conn, isCausal, true /*expectRetry*/);
+
+ // Transient transaction error on commit attempt.
+ mockFirstCommitResponse = {
+ ok: 0,
+ code: ErrorCodes.NoSuchTransaction,
+ errorLabels: ["TransientTransactionError"]
+ };
+ testCommit(conn, isCausal, true /*expectRetry*/);
+
+ // Network error on commit attempt.
+ mockFirstCommitResponse = {ok: 0, code: ErrorCodes.NotMaster};
+ testCommit(conn, isCausal, true /*expectRetry*/);
+ }
+ }
+
+ runTest();
+
+ // With read concern majority disabled.
+ TestData.enableMajorityReadConcern = false;
+ runTest();
+ delete TestData.enableMajorityReadConcern;
+
+ rst.stopSet();
+})();