/** * Test that starting transactions and running commitTransaction and abortTransaction commands are * not allowed on replica set secondaries. * * @tags: [uses_transactions] */ import {ReplSetTest} from "jstests/libs/replsettest.js"; const dbName = "test"; const collName = "transactions_on_secondaries_not_allowed"; const rst = new ReplSetTest({name: collName, nodes: 2}); rst.startSet({verbose: 3}); // We want a stable topology, so make the secondary unelectable. let config = rst.getReplSetConfig(); config.members[1].priority = 0; rst.initiate(config); const primary = rst.getPrimary(); const secondary = rst.getSecondary(); const secondaryTestDB = secondary.getDB(dbName); // Do an initial write so we have something to find. const initialDoc = { _id: 0 }; assert.commandWorked(primary.getDB(dbName)[collName].insert(initialDoc)); rst.awaitLastOpCommitted(); // Disable the best-effort check for primary-ness in the service entry point, so that we // exercise the real check for primary-ness in TransactionParticipant::beginOrContinue. assert.commandWorked(secondary.adminCommand( {configureFailPoint: "skipCheckingForNotPrimaryInCommandDispatch", mode: "alwaysOn"})); // Initiate a session on the secondary. const sessionOptions = { causalConsistency: false, retryWrites: true }; const session = secondaryTestDB.getMongo().startSession(sessionOptions); const sessionDb = session.getDatabase(dbName); /** * Test starting a transaction and issuing a commitTransaction command. */ jsTestLog("Start a read-only transaction on the secondary."); session.startTransaction({readConcern: {level: "snapshot"}}); // Try to read a document (the first statement in the transaction) and verify that this fails. assert.commandFailedWithCode(sessionDb.runCommand({find: collName}), ErrorCodes.NotWritablePrimary); // The check for "NotWritablePrimary" supercedes the check for "NoSuchTransaction" in this case. jsTestLog("Make sure we are not allowed to run the commitTransaction command on the secondary."); assert.commandFailedWithCode(session.commitTransaction_forTesting(), ErrorCodes.NotWritablePrimary); /** * Test starting a transaction and issuing an abortTransaction command. */ jsTestLog("Start a different read-only transaction on the secondary."); session.startTransaction({readConcern: {level: "snapshot"}}); // Try to read a document (the first statement in the transaction) and verify that this fails. assert.commandFailedWithCode(sessionDb.runCommand({find: collName}), ErrorCodes.NotWritablePrimary); // The check for "NotWritablePrimary" supercedes the check for "NoSuchTransaction" in this case. jsTestLog("Make sure we are not allowed to run the abortTransaction command on the secondary."); assert.commandFailedWithCode(session.abortTransaction_forTesting(), ErrorCodes.NotWritablePrimary); /** * Test starting a retryable write. */ jsTestLog("Start a retryable write"); assert.commandFailedWithCode(sessionDb.foo.insert({_id: 0}), ErrorCodes.NotWritablePrimary); /** * Test starting a read with txnNumber, but without autocommit. This fails in general because * txnNumber isn't supported for the find command outside of transactions, but we check that * this fails on a secondary. */ jsTestLog("Start a read with txnNumber but without autocommit"); assert.commandFailedWithCode(sessionDb.runCommand({find: 'foo', txnNumber: NumberLong(10)}), 50768); session.endSession(); rst.stopSet(undefined, false, {skipValidation: true});