diff options
author | A. Jesse Jiryu Davis <jesse@mongodb.com> | 2021-05-04 19:29:17 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-05-13 17:48:10 +0000 |
commit | 500a405a5ce235507f56fb47e8d5d4b368d3458d (patch) | |
tree | eecb1020c3049a816d28ffd5461b8d3ecab8686a | |
parent | 2070fc76b3604f8c04997862159b0fc721eeb465 (diff) | |
download | mongo-500a405a5ce235507f56fb47e8d5d4b368d3458d.tar.gz |
SERVER-56550 Require consistent API params in getMore and txns
Transaction-continuing commands must use the same API parameters as the
transaction's first command (it is no longer optional), and similarly
getMore must use the same as the cursor-creating command.
29 files changed, 204 insertions, 205 deletions
diff --git a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml index 06c09009e68..4a5f3d1d24a 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_local_read_write_multi_stmt_txn_jscore_passthrough.yml @@ -273,8 +273,8 @@ selector: # it, since we won't know whether the cursor was advanced or not. - requires_getmore - does_not_support_transactions - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters executor: diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml index 6316711660c..3c5c0651e7d 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_jscore_passthrough.yml @@ -291,8 +291,8 @@ selector: # Retrying a query can change whether a plan cache entry is active. - inspects_whether_plan_cache_entry_is_active - does_not_support_transactions - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters executor: diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml index 53261e8c935..3f05c322ffd 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_kill_primary_jscore_passthrough.yml @@ -341,6 +341,9 @@ selector: # Retrying a query can change whether a plan cache entry is active. - inspects_whether_plan_cache_entry_is_active - does_not_support_transactions + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. + - uses_api_parameters executor: archive: diff --git a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml index 5d39cf739f8..379ec1c5036 100644 --- a/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/multi_shard_multi_stmt_txn_stepdown_primary_jscore_passthrough.yml @@ -341,7 +341,9 @@ selector: # Retrying a query can change whether a plan cache entry is active. - inspects_whether_plan_cache_entry_is_active - does_not_support_transactions - + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. + - uses_api_parameters executor: archive: hooks: diff --git a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml index 24dfae74d2e..bd280facfe3 100644 --- a/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml +++ b/buildscripts/resmokeconfig/suites/multi_stmt_txn_jscore_passthrough_with_migration.yml @@ -301,8 +301,8 @@ selector: # Cannot retry a getMore command if a transient transaction or network error occurs during # it, since we won't know whether the cursor was advanced or not. - requires_getmore - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters executor: diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml index 9046aab17d3..13b15a3d081 100644 --- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_jscore_passthrough.yml @@ -233,8 +233,8 @@ selector: # Retrying a query can change whether a plan cache entry is active. - inspects_whether_plan_cache_entry_is_active - does_not_support_transactions - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters executor: diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml index 65d73da462c..40650b6e04b 100644 --- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_kill_primary_jscore_passthrough.yml @@ -301,8 +301,8 @@ selector: # operation and cluster times aren't shared between shells. # "Cowardly refusing to run test with network retries enabled when it uses startParallelShell()" - uses_parallel_shell - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters executor: diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml index 4d22a4b2009..330171262db 100644 --- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_stepdown_jscore_passthrough.yml @@ -282,8 +282,8 @@ selector: # operation and cluster times aren't shared between shells. # "Cowardly refusing to run test with network retries enabled when it uses startParallelShell()" - uses_parallel_shell - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters executor: diff --git a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml index 696f3642ca4..6acd3672558 100644 --- a/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/replica_sets_multi_stmt_txn_terminate_primary_jscore_passthrough.yml @@ -290,8 +290,8 @@ selector: # operation and cluster times aren't shared between shells. # "Cowardly refusing to run test with network retries enabled when it uses startParallelShell()" - uses_parallel_shell - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters diff --git a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml index 64bcbde630d..9689d16ec5d 100644 --- a/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/sharded_multi_stmt_txn_jscore_passthrough.yml @@ -257,8 +257,8 @@ selector: # Retrying a query can change whether a plan cache entry is active. - inspects_whether_plan_cache_entry_is_active - does_not_support_transactions - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters executor: diff --git a/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml b/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml index fe45a3fd6b6..8b480876583 100644 --- a/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml +++ b/buildscripts/resmokeconfig/suites/tenant_migration_multi_stmt_txn_jscore_passthrough.yml @@ -347,8 +347,8 @@ selector: - inspects_whether_plan_cache_entry_is_active # $out is not supported in transactions - uses_$out - # Transaction-continuing commands cannot specify API parameters, so tests that use API parameters - # cannot be run with transactions. + # Transaction-continuing commands must use the same API parameters as the first command, so tests + # that use API parameters cannot be run with transactions. - uses_api_parameters - does_not_support_transactions diff --git a/jstests/core/api_params_getmore.js b/jstests/core/api_params_getmore.js index 0d4a0df3e58..40b4454bab1 100644 --- a/jstests/core/api_params_getmore.js +++ b/jstests/core/api_params_getmore.js @@ -38,8 +38,7 @@ for (const initParams of apiParamCombos) { for (const continueParams of apiParamCombos) { const findCmd = addApiParams({find: testColl.getName(), batchSize: 1}, initParams); const cursorId = assert.commandWorked(testDB.runCommand(findCmd)).cursor.id; - // TODO (SERVER-56550): Remove "!continueParams.apiVersion". - const compatibleParams = !continueParams.apiVersion || continueParams === initParams; + const compatibleParams = (continueParams === initParams); const getMoreCmd = addApiParams({getMore: cursorId, collection: testColl.getName()}, continueParams); diff --git a/jstests/core/api_version_unstable_indexes.js b/jstests/core/api_version_unstable_indexes.js index a4e3b1f3d20..8c23b56acab 100644 --- a/jstests/core/api_version_unstable_indexes.js +++ b/jstests/core/api_version_unstable_indexes.js @@ -5,7 +5,7 @@ * * @tags: [ * requires_fcv_50, - * uses_api_parameters, + * uses_api_parameters * ] */ @@ -44,7 +44,7 @@ assert.commandFailedWithCode(db.runCommand({ }), ErrorCodes.NoQueryExecutionPlans); -// Can not hint a sparse index which is exclued from API version 1 with 'apiStrict: true'. +// Can not hint a sparse index which is excluded from API version 1 with 'apiStrict: true'. assert.commandFailedWithCode(db.runCommand({ "find": collName, "filter": {views: 50}, diff --git a/jstests/core/txns/api_params_transaction.js b/jstests/core/txns/api_params_transaction.js index 929f5087a5e..6b141b280ff 100644 --- a/jstests/core/txns/api_params_transaction.js +++ b/jstests/core/txns/api_params_transaction.js @@ -38,9 +38,7 @@ function addApiParams(obj, params) { for (const txnInitiatingParams of apiParamCombos) { for (const txnContinuingParams of apiParamCombos) { for (const txnEndingCmdName of ["commitTransaction", "abortTransaction"]) { - // TODO (SERVER-56550): Remove "!txnContinuingParams.apiVersion". - const compatibleParams = - !txnContinuingParams.apiVersion || txnContinuingParams === txnInitiatingParams; + const compatibleParams = (txnContinuingParams === txnInitiatingParams); const session = db.getMongo().startSession(); const sessionDb = session.getDatabase(dbName); @@ -80,8 +78,20 @@ for (const txnInitiatingParams of apiParamCombos) { checkCommand(session.getDatabase("admin"), txnEndingCmd); - // Clean up. - session.abortTransaction(); + if (!compatibleParams) { + jsTestLog('Cleaning up'); + // Clean up by calling abortTransaction with the right API parameters. + const abortCmd = { + abortTransaction: 1, + txnNumber: session.getTxnNumber_forTesting(), + autocommit: false + }; + const cleanupReply = session.getDatabase("admin").runCommand( + addApiParams(abortCmd, txnInitiatingParams)); + jsTestLog(`Cleanup reply ${tojson(cleanupReply)}`); + } + + session.endSession(); } } } diff --git a/jstests/noPassthrough/require_api_version.js b/jstests/noPassthrough/require_api_version.js index 640a149adab..aafe763f3a4 100644 --- a/jstests/noPassthrough/require_api_version.js +++ b/jstests/noPassthrough/require_api_version.js @@ -14,7 +14,7 @@ (function() { "use strict"; -function runTest(db, supportsTransctions, isMongos, writeConcern = {}, secondaries = []) { +function runTest(db, supportsTransctions, writeConcern = {}, secondaries = []) { assert.commandWorked(db.runCommand({setParameter: 1, requireApiVersion: true})); for (const secondary of secondaries) { assert.commandWorked(secondary.adminCommand({setParameter: 1, requireApiVersion: true})); @@ -38,65 +38,82 @@ function runTest(db, supportsTransctions, isMongos, writeConcern = {}, secondari /* * "getMore" accepts apiVersion. - * - * TODO (SERVER-56550): Test that getMore fails *without* API params. */ assert.commandWorked(db.runCommand( {insert: "collection", documents: [{}, {}, {}], apiVersion: "1", writeConcern})); let reply = db.runCommand({find: "collection", batchSize: 1, apiVersion: "1"}); assert.commandWorked(reply); - assert.commandWorked( - db.runCommand({getMore: reply.cursor.id, collection: "collection", batchSize: 1})); + assert.commandFailedWithCode( + db.runCommand({getMore: reply.cursor.id, collection: "collection"}), 498870); assert.commandWorked( db.runCommand({getMore: reply.cursor.id, collection: "collection", apiVersion: "1"})); if (supportsTransctions) { /* - * Transaction-starting commands must have apiVersion, transaction-continuing commands may. - * - * TODO (SERVER-56550): Test that transaction-continuing commands fail *without* API params. + * Commands in transactions require API version. */ const session = db.getMongo().startSession({causalConsistency: false}); const sessionDb = session.getDatabase(db.getName()); + assert.commandFailedWithCode(sessionDb.runCommand({ + find: "collection", + batchSize: 1, + txnNumber: NumberLong(0), + stmtId: NumberInt(2), + startTransaction: true, + autocommit: false + }), + 498870); reply = sessionDb.runCommand({ find: "collection", batchSize: 1, apiVersion: "1", - txnNumber: NumberLong(0), + txnNumber: NumberLong(1), stmtId: NumberInt(0), startTransaction: true, autocommit: false }); assert.commandWorked(reply); - assert.commandWorked(sessionDb.runCommand({ + assert.commandFailedWithCode(sessionDb.runCommand({ getMore: reply.cursor.id, collection: "collection", - txnNumber: NumberLong(0), + txnNumber: NumberLong(1), stmtId: NumberInt(1), autocommit: false - })); + }), + 498870); assert.commandWorked(sessionDb.runCommand({ - find: "collection", - batchSize: 1, - txnNumber: NumberLong(0), - stmtId: NumberInt(2), - autocommit: false + getMore: reply.cursor.id, + collection: "collection", + txnNumber: NumberLong(1), + stmtId: NumberInt(1), + autocommit: false, + apiVersion: "1" })); + + assert.commandFailedWithCode( + sessionDb.runCommand( + {commitTransaction: 1, txnNumber: NumberLong(1), autocommit: false}), + 498870); + assert.commandWorked(sessionDb.runCommand( - {commitTransaction: 1, apiVersion: "1", txnNumber: NumberLong(0), autocommit: false})); + {commitTransaction: 1, apiVersion: "1", txnNumber: NumberLong(1), autocommit: false})); // Start a new txn so we can test abortTransaction. reply = sessionDb.runCommand({ find: "collection", apiVersion: "1", - txnNumber: NumberLong(1), + txnNumber: NumberLong(2), stmtId: NumberInt(0), startTransaction: true, autocommit: false }); assert.commandWorked(reply); + assert.commandFailedWithCode( + sessionDb.runCommand( + {abortTransaction: 1, txnNumber: NumberLong(2), autocommit: false}), + 498870); assert.commandWorked(sessionDb.runCommand( - {abortTransaction: 1, apiVersion: "1", txnNumber: NumberLong(1), autocommit: false})); + {abortTransaction: 1, apiVersion: "1", txnNumber: NumberLong(2), autocommit: false})); } assert.commandWorked( @@ -139,7 +156,7 @@ function requireApiVersionOnShardOrConfigServerTest() { requireApiVersionOnShardOrConfigServerTest(); const mongod = MongoRunner.runMongod(); -runTest(mongod.getDB("admin"), false /* supportsTransactions */, false /* isMongos */); +runTest(mongod.getDB("admin"), false /* supportsTransactions */); MongoRunner.stopMongod(mongod); const rst = new ReplSetTest({nodes: 3}); @@ -148,12 +165,11 @@ rst.initiateWithHighElectionTimeout(); runTest(rst.getPrimary().getDB("admin"), true /* supportsTransactions */, - false /* isMongos */, {w: "majority"} /* writeConcern */, rst.getSecondaries()); rst.stopSet(); const st = new ShardingTest({}); -runTest(st.s0.getDB("admin"), true /* supportsTransactions */, true /* isMongos */); +runTest(st.s0.getDB("admin"), true /* supportsTransactions */); st.stop(); }()); diff --git a/jstests/sharding/libs/mongos_api_params_util.js b/jstests/sharding/libs/mongos_api_params_util.js index 45999197cf0..97c5b57f099 100644 --- a/jstests/sharding/libs/mongos_api_params_util.js +++ b/jstests/sharding/libs/mongos_api_params_util.js @@ -1394,7 +1394,7 @@ let MongosAPIParametersUtil = (function() { } })(); - function checkPrimaryLog(conn, commandName, apiVersion, apiStrict, apiDeprecationErrors) { + function checkPrimaryLog(conn, commandName, apiParameters) { let msg; assert.soon( () => { @@ -1412,8 +1412,9 @@ let MongosAPIParametersUtil = (function() { continue; lastCommandInvocation = args; - if (args.apiVersion !== apiVersion || args.apiStrict !== apiStrict || - args.apiDeprecationErrors !== apiDeprecationErrors) + if (args.apiVersion !== apiParameters.apiVersion || + args.apiStrict !== apiParameters.apiStrict || + args.apiDeprecationErrors !== apiParameters.apiDeprecationErrors) continue; // Found a match. @@ -1421,16 +1422,13 @@ let MongosAPIParametersUtil = (function() { } if (lastCommandInvocation === undefined) { - msg = `Primary didn't log ${commandName} with apiVersion ${apiVersion},` + - ` apiStrict ${apiStrict},` + - ` apiDeprecationErrors ${apiDeprecationErrors}.`; + msg = `Primary didn't log ${commandName} with API parameters ` + + `${tojson(apiParameters)}.`; return false; } - msg = `Primary didn't log ${commandName} with apiVersion ${apiVersion},` + - ` apiStrict ${apiStrict},` + - ` apiDeprecationErrors ${apiDeprecationErrors}.` + - ` Last invocation of ${commandName} was` + + msg = `Primary didn't log ${commandName} with API parameters ` + + `${tojson(apiParameters)}. Last invocation of ${commandName} was` + ` ${tojson(lastCommandInvocation)}`; return false; }, @@ -1446,17 +1444,17 @@ let MongosAPIParametersUtil = (function() { // progress. let testInstances = []; - for (const [apiVersion, apiStrict, apiDeprecationErrors] of [ - [undefined, undefined, undefined], - ["1", undefined, undefined], - ["1", undefined, false], - ["1", undefined, true], - ["1", false, undefined], - ["1", false, false], - ["1", false, true], - ["1", true, undefined], - ["1", true, false], - ["1", true, true], + for (const apiParameters + of [{}, + {apiVersion: "1"}, + {apiVersion: "1", apiDeprecationErrors: false}, + {apiVersion: "1", apiDeprecationErrors: true}, + {apiVersion: "1", apiStrict: false}, + {apiVersion: "1", apiStrict: false, apiDeprecationErrors: false}, + {apiVersion: "1", apiStrict: false, apiDeprecationErrors: true}, + {apiVersion: "1", apiStrict: true}, + {apiVersion: "1", apiStrict: true, apiDeprecationErrors: false}, + {apiVersion: "1", apiStrict: true, apiDeprecationErrors: true}, ]) { for (const testCase of testCases) { if (testCase.skip) @@ -1478,13 +1476,11 @@ let MongosAPIParametersUtil = (function() { if (!supportsCommittedReads && runOrExplain.requiresCommittedReads) continue; - if (apiStrict && !runOrExplain.inAPIVersion1) + if (apiParameters.apiStrict && !runOrExplain.inAPIVersion1) continue; testInstances.push({ - apiVersion: apiVersion, - apiStrict: apiStrict, - apiDeprecationErrors: apiDeprecationErrors, + apiParameters: apiParameters, commandName: testCase.commandName, runOrExplain: runOrExplain }); @@ -1493,8 +1489,7 @@ let MongosAPIParametersUtil = (function() { } for (let i = 0; i < testInstances.length; ++i) { - const {apiVersion, apiStrict, apiDeprecationErrors, commandName, runOrExplain} = - testInstances[i]; + const {apiParameters, commandName, runOrExplain} = testInstances[i]; if (shardedCollection) { jsTestLog("Sharded setup"); @@ -1526,13 +1521,7 @@ let MongosAPIParametersUtil = (function() { const configPrimary = st.configRS.getPrimary(); const shardZeroPrimary = st.rs0.getPrimary(); - const context = { - apiParameters: { - apiVersion: apiVersion, - apiStrict: apiStrict, - apiDeprecationErrors: apiDeprecationErrors - } - }; + const context = {apiParameters: apiParameters}; const commandDbName = runOrExplain.runsAgainstAdminDb ? "admin" : "db"; if (inTransaction) { @@ -1551,18 +1540,8 @@ let MongosAPIParametersUtil = (function() { // Make a copy of the test's command body, and set its API parameters. const commandBody = runOrExplain.command(context); - const commandWithAPIParams = Object.assign({}, commandBody); - if (apiVersion !== undefined) { - commandWithAPIParams.apiVersion = apiVersion; - } - - if (apiStrict !== undefined) { - commandWithAPIParams.apiStrict = apiStrict; - } - - if (apiDeprecationErrors !== undefined) { - commandWithAPIParams.apiDeprecationErrors = apiDeprecationErrors; - } + const commandWithAPIParams = + Object.assign(Object.assign({}, commandBody), apiParameters); assert.commandWorked(configPrimary.adminCommand({clearLog: "global"})); assert.commandWorked(shardZeroPrimary.adminCommand({clearLog: "global"})); @@ -1583,7 +1562,14 @@ let MongosAPIParametersUtil = (function() { assert.commandWorked(res); if (inTransaction) { - assert.commandWorked(context.session.commitTransaction_forTesting()); + const commitCmd = { + commitTransaction: 1, + txnNumber: context.session.getTxnNumber_forTesting(), + autocommit: false + }; + + assert.commandWorked(context.session.getDatabase("admin").runCommand( + Object.assign(commitCmd, apiParameters))); } const configServerCommandName = runOrExplain.configServerCommandName; @@ -1591,20 +1577,12 @@ let MongosAPIParametersUtil = (function() { if (configServerCommandName) { jsTestLog(`Check for ${configServerCommandName} in config server's log`); - checkPrimaryLog(configPrimary, - configServerCommandName, - apiVersion, - apiStrict, - apiDeprecationErrors); + checkPrimaryLog(configPrimary, configServerCommandName, apiParameters); } if (shardCommandName) { jsTestLog(`Check for ${shardCommandName} in shard server's log`); - checkPrimaryLog(shardZeroPrimary, - shardCommandName, - apiVersion, - apiStrict, - apiDeprecationErrors); + checkPrimaryLog(shardZeroPrimary, shardCommandName, apiParameters); } setLogVerbosity([configPrimary, shardZeroPrimary, st.rs1.getPrimary()], diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp index 9624761a5e9..1772d5b3a25 100644 --- a/src/mongo/db/commands/getmore_cmd.cpp +++ b/src/mongo/db/commands/getmore_cmd.cpp @@ -225,18 +225,11 @@ void setUpOperationContextStateForGetMore(OperationContext* opCtx, opCtx->setWriteConcern(cursor.getWriteConcernOptions()); auto apiParamsFromClient = APIParameters::get(opCtx); - // TODO (SERVER-56550): Do this check even if !apiParamsFromClient.getParamsPassed(). - if (apiParamsFromClient.getParamsPassed()) { - uassert( - ErrorCodes::APIMismatchError, - "API param conflict: getMore used params {}, the cursor-creating command used {}"_format( - apiParamsFromClient.toBSON().toString(), - cursor.getAPIParameters().toBSON().toString()), - apiParamsFromClient == cursor.getAPIParameters()); - } - - // TODO (SERVER-56550): Remove. - APIParameters::get(opCtx) = cursor.getAPIParameters(); + uassert( + ErrorCodes::APIMismatchError, + "API parameter mismatch: getMore used params {}, the cursor-creating command used {}"_format( + apiParamsFromClient.toBSON().toString(), cursor.getAPIParameters().toBSON().toString()), + apiParamsFromClient == cursor.getAPIParameters()); setUpOperationDeadline(opCtx, cursor, cmd, disableAwaitDataFailpointActive); diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index d8c7582ad16..9c42dec4997 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -885,10 +885,8 @@ private: _sessionInfo.serialize(cmdBuilder); } - if (_state == TransactionState::kInit || !_isReplSet) { - // Set a default apiVersion for all UMC commands - cmdBuilder->append("apiVersion", kOne); - } + // Set a default apiVersion for all UMC commands + cmdBuilder->append("apiVersion", kOne); auto svcCtx = _client->getServiceContext(); auto sep = svcCtx->getServiceEntryPoint(); diff --git a/src/mongo/db/initialize_api_parameters.cpp b/src/mongo/db/initialize_api_parameters.cpp index 3962494fae6..812a452ea31 100644 --- a/src/mongo/db/initialize_api_parameters.cpp +++ b/src/mongo/db/initialize_api_parameters.cpp @@ -107,9 +107,7 @@ void enforceRequireAPIVersion(OperationContext* opCtx, Command* command) { auto isInternalClient = !client->session() || (client->session()->getTags() & transport::Session::kInternalClient); - // TODO (SERVER-56550): Don't excuse getMore or transaction-continuing commands. - if (gRequireApiVersion.load() && !opCtx->getClient()->isInDirectClient() && !isInternalClient && - command->getName() != "getMore" && !opCtx->isContinuingMultiDocumentTransaction()) { + if (gRequireApiVersion.load() && !opCtx->getClient()->isInDirectClient() && !isInternalClient) { uassert( 498870, "The apiVersion parameter is required, please configure your MongoClient's API version", diff --git a/src/mongo/db/s/transaction_coordinator.cpp b/src/mongo/db/s/transaction_coordinator.cpp index a551447964a..35d7f5f00f8 100644 --- a/src/mongo/db/s/transaction_coordinator.cpp +++ b/src/mongo/db/s/transaction_coordinator.cpp @@ -177,7 +177,7 @@ TransactionCoordinator::TransactionCoordinator(OperationContext* operationContex std::move(opTime)); }) .thenRunOn(Grid::get(_serviceContext)->getExecutorPool()->getFixedExecutor()) - .then([this] { + .then([this, apiParams] { { stdx::lock_guard<Latch> lg(_mutex); _participantsDurable = true; @@ -204,8 +204,12 @@ TransactionCoordinator::TransactionCoordinator(OperationContext* operationContex return Future<void>::makeReady(); } - return txn::sendPrepare( - _serviceContext, *_sendPrepareScheduler, _lsid, _txnNumber, *_participants) + return txn::sendPrepare(_serviceContext, + *_sendPrepareScheduler, + _lsid, + _txnNumber, + apiParams, + *_participants) .then([this](PrepareVoteConsensus consensus) mutable { { stdx::lock_guard<Latch> lg(_mutex); diff --git a/src/mongo/db/s/transaction_coordinator_test.cpp b/src/mongo/db/s/transaction_coordinator_test.cpp index 42a4da83b4c..5b060c8d968 100644 --- a/src/mongo/db/s/transaction_coordinator_test.cpp +++ b/src/mongo/db/s/transaction_coordinator_test.cpp @@ -409,7 +409,8 @@ TEST_F(TransactionCoordinatorDriverTest, TEST_F(TransactionCoordinatorDriverTest, SendPrepareReturnsAbortDecisionWhenFirstParticipantVotesAbortAndSecondVotesCommit) { txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); onCommands({[&](const executor::RemoteCommandRequest& request) { return kNoSuchTransaction; }, [&](const executor::RemoteCommandRequest& request) { return kPrepareOk; }}); @@ -424,7 +425,8 @@ TEST_F(TransactionCoordinatorDriverTest, TEST_F(TransactionCoordinatorDriverTest, SendPrepareReturnsAbortDecisionWhenFirstParticipantVotesCommitAndSecondVotesAbort) { txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); assertPrepareSentAndRespondWithSuccess(); assertPrepareSentAndRespondWithNoSuchTransaction(); @@ -438,7 +440,8 @@ TEST_F(TransactionCoordinatorDriverTest, TEST_F(TransactionCoordinatorDriverTest, SendPrepareReturnsAbortDecisionWhenBothParticipantsVoteAbort) { txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); onCommands({[&](const executor::RemoteCommandRequest& request) { return kNoSuchTransaction; }, [&](const executor::RemoteCommandRequest& request) { return kNoSuchTransaction; }}); @@ -455,7 +458,8 @@ TEST_F(TransactionCoordinatorDriverTest, const auto maxPrepareTimestamp = Timestamp(2, 1); txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); assertPrepareSentAndRespondWithSuccess(firstPrepareTimestamp); assertPrepareSentAndRespondWithSuccess(maxPrepareTimestamp); @@ -472,7 +476,8 @@ TEST_F(TransactionCoordinatorDriverTest, const auto maxPrepareTimestamp = Timestamp(2, 1); txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); assertPrepareSentAndRespondWithSuccess(maxPrepareTimestamp); assertPrepareSentAndRespondWithSuccess(firstPrepareTimestamp); @@ -489,7 +494,8 @@ TEST_F(TransactionCoordinatorDriverTest, const auto maxPrepareTimestamp = Timestamp(2, 1); txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); assertPrepareSentAndRespondWithSuccess(firstPrepareTimestamp); assertPrepareSentAndRespondWithSuccess(maxPrepareTimestamp); @@ -505,7 +511,8 @@ TEST_F(TransactionCoordinatorDriverTest, const auto timestamp = Timestamp(1, 1); txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); assertPrepareSentAndRespondWithSuccess(timestamp); assertCommandSentAndRespondWith( @@ -521,7 +528,8 @@ TEST_F(TransactionCoordinatorDriverTest, TEST_F(TransactionCoordinatorDriverTest, SendPrepareReturnsErrorWhenOneShardReturnsReadConcernMajorityNotEnabled) { txn::AsyncWorkScheduler aws(getServiceContext()); - auto future = txn::sendPrepare(getServiceContext(), aws, _lsid, _txnNumber, kTwoShardIdList); + auto future = txn::sendPrepare( + getServiceContext(), aws, _lsid, _txnNumber, APIParameters(), kTwoShardIdList); assertPrepareSentAndRespondWithSuccess(Timestamp(100, 1)); assertCommandSentAndRespondWith( diff --git a/src/mongo/db/s/transaction_coordinator_util.cpp b/src/mongo/db/s/transaction_coordinator_util.cpp index f8e7bbb42c9..0bc0d019c6c 100644 --- a/src/mongo/db/s/transaction_coordinator_util.cpp +++ b/src/mongo/db/s/transaction_coordinator_util.cpp @@ -233,12 +233,15 @@ Future<PrepareVoteConsensus> sendPrepare(ServiceContext* service, txn::AsyncWorkScheduler& scheduler, const LogicalSessionId& lsid, TxnNumber txnNumber, + const APIParameters& apiParams, const txn::ParticipantsList& participants) { PrepareTransaction prepareTransaction; prepareTransaction.setDbName(NamespaceString::kAdminDb); - auto prepareObj = prepareTransaction.toBSON( - BSON("lsid" << lsid.toBSON() << "txnNumber" << txnNumber << "autocommit" << false - << WriteConcernOptions::kWriteConcernField << WriteConcernOptions::Majority)); + BSONObjBuilder bob(BSON("lsid" << lsid.toBSON() << "txnNumber" << txnNumber << "autocommit" + << false << WriteConcernOptions::kWriteConcernField + << WriteConcernOptions::Majority)); + apiParams.appendInfo(&bob); + auto prepareObj = prepareTransaction.toBSON(bob.obj()); std::vector<Future<PrepareResponse>> responses; diff --git a/src/mongo/db/s/transaction_coordinator_util.h b/src/mongo/db/s/transaction_coordinator_util.h index 5563e5a29e0..36a2b2fcf3d 100644 --- a/src/mongo/db/s/transaction_coordinator_util.h +++ b/src/mongo/db/s/transaction_coordinator_util.h @@ -98,6 +98,7 @@ Future<PrepareVoteConsensus> sendPrepare(ServiceContext* service, txn::AsyncWorkScheduler& scheduler, const LogicalSessionId& lsid, TxnNumber txnNumber, + const APIParameters& apiParams, const txn::ParticipantsList& participants); /** diff --git a/src/mongo/db/s/txn_two_phase_commit_cmds.cpp b/src/mongo/db/s/txn_two_phase_commit_cmds.cpp index e8105484ca2..41d47497a77 100644 --- a/src/mongo/db/s/txn_two_phase_commit_cmds.cpp +++ b/src/mongo/db/s/txn_two_phase_commit_cmds.cpp @@ -51,6 +51,10 @@ MONGO_FAIL_POINT_DEFINE(participantReturnNetworkErrorForPrepareAfterExecutingPre class PrepareTransactionCmd : public TypedCommand<PrepareTransactionCmd> { public: + bool acceptsAnyApiVersionParameters() const override { + return true; + } + class PrepareTimestamp { public: PrepareTimestamp(Timestamp timestamp) : _timestamp(std::move(timestamp)) {} diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index e60c89a3b65..d539a17b0d3 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -901,18 +901,15 @@ void CheckoutSessionAndInvokeCommand::_checkOutSession() { _sessionTxnState = std::make_unique<MongoDOperationContextSession>(opCtx); _txnParticipant.emplace(TransactionParticipant::get(opCtx)); - // TODO (SERVER-56550): Do this check even if !apiParamsFromClient.getParamsPassed(). auto apiParamsFromClient = APIParameters::get(opCtx); - if (apiParamsFromClient.getParamsPassed()) { - auto apiParamsFromTxn = _txnParticipant->getAPIParameters(opCtx); - uassert( - ErrorCodes::APIMismatchError, - "API param conflict: {} used params {}, the transaction's first command used {}"_format( - invocation->definition()->getName(), - apiParamsFromClient.toBSON().toString(), - apiParamsFromTxn.toBSON().toString()), - apiParamsFromTxn == apiParamsFromClient); - } + auto apiParamsFromTxn = _txnParticipant->getAPIParameters(opCtx); + uassert( + ErrorCodes::APIMismatchError, + "API parameter mismatch: {} used params {}, the transaction's first command used {}"_format( + invocation->definition()->getName(), + apiParamsFromClient.toBSON().toString(), + apiParamsFromTxn.toBSON().toString()), + apiParamsFromTxn == apiParamsFromClient); if (!opCtx->getClient()->isInDirectClient()) { bool beganOrContinuedTxn{false}; @@ -1000,9 +997,6 @@ void CheckoutSessionAndInvokeCommand::_checkOutSession() { } } } - - // Use the API parameters that were stored when the transaction was initiated. - APIParameters::get(opCtx) = _txnParticipant->getAPIParameters(opCtx); } void CheckoutSessionAndInvokeCommand::_tapError(Status status) { diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp index 4429987d9f3..e16e8c7340a 100644 --- a/src/mongo/s/commands/strategy.cpp +++ b/src/mongo/s/commands/strategy.cpp @@ -600,6 +600,8 @@ Status ParseAndRunCommand::RunInvocation::_setup() { ClientMetadata::setFromMetadata(opCtx->getClient(), metaElem); } + enforceRequireAPIVersion(opCtx, command); + auto& apiParams = APIParameters::get(opCtx); auto& apiVersionMetrics = APIVersionMetrics::get(opCtx->getServiceContext()); if (auto clientMetadata = ClientMetadata::get(opCtx->getClient())) { @@ -814,9 +816,6 @@ Status ParseAndRunCommand::RunInvocation::_setup() { // the execution needs to adjust its behavior based on this. opCtx->setIsStartingMultiDocumentTransaction(startTransaction); - // Once API params and txn state are set on opCtx, enforce the "requireApiVersion" setting. - enforceRequireAPIVersion(opCtx, command); - command->incrementCommandsExecuted(); if (command->shouldAffectCommandCounter()) { diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp index 4f9f6ddd446..9f2665d95ac 100644 --- a/src/mongo/s/query/cluster_find.cpp +++ b/src/mongo/s/query/cluster_find.cpp @@ -455,18 +455,11 @@ Status setUpOperationContextStateForGetMore(OperationContext* opCtx, } auto apiParamsFromClient = APIParameters::get(opCtx); - // TODO (SERVER-56550): Do this check even if !apiParamsFromClient.getParamsPassed(). - if (apiParamsFromClient.getParamsPassed()) { - uassert( - ErrorCodes::APIMismatchError, - "API param conflict: getMore used params {}, the cursor-creating command used {}"_format( - apiParamsFromClient.toBSON().toString(), - cursor->getAPIParameters().toBSON().toString()), + uassert(ErrorCodes::APIMismatchError, + "API parameter mismatch: getMore used params {}, the cursor-creating command " + "used {}"_format(apiParamsFromClient.toBSON().toString(), + cursor->getAPIParameters().toBSON().toString()), apiParamsFromClient == cursor->getAPIParameters()); - } - - // TODO (SERVER-56550): Remove. - APIParameters::get(opCtx) = cursor->getAPIParameters(); // If the originating command had a 'comment' field, we extract it and set it on opCtx. Note // that if the 'getMore' command itself has a 'comment' field, we give precedence to it. diff --git a/src/mongo/s/transaction_router.cpp b/src/mongo/s/transaction_router.cpp index 57f5e636a13..160bce00191 100644 --- a/src/mongo/s/transaction_router.cpp +++ b/src/mongo/s/transaction_router.cpp @@ -31,6 +31,8 @@ #include "mongo/platform/basic.h" +#include <fmt/format.h> + #include "mongo/s/transaction_router.h" #include "mongo/client/read_preference.h" @@ -60,6 +62,8 @@ namespace mongo { namespace { +using namespace fmt::literals; + // TODO SERVER-39704: Remove this fail point once the router can safely retry within a transaction // on stale version and snapshot errors. MONGO_FAIL_POINT_DEFINE(enableStaleVersionAndSnapshotRetriesWithinTransactions); @@ -900,13 +904,13 @@ void TransactionRouter::Router::beginOrContinueTxn(OperationContext* opCtx, } else if (txnNumber == o().txnNumber) { // This is the same transaction as the one in progress. auto apiParamsFromClient = APIParameters::get(opCtx); - // TODO (SERVER-56550): Do this check even if !apiParamsFromClient.getParamsPassed(). - if (apiParamsFromClient.getParamsPassed() && - (action == TransactionActions::kContinue || action == TransactionActions::kCommit)) { - uassert(ErrorCodes::APIMismatchError, - "A transaction-continuing command must use the same API parameters as " - "the first command in the transaction", - apiParamsFromClient == o().apiParameters); + if (action == TransactionActions::kContinue || action == TransactionActions::kCommit) { + uassert( + ErrorCodes::APIMismatchError, + "API parameter mismatch: transaction-continuing command used {}, the transaction's" + " first command used {}"_format(apiParamsFromClient.toBSON().toString(), + o().apiParameters.toBSON().toString()), + apiParamsFromClient == o().apiParameters); } switch (action) { diff --git a/src/mongo/s/transaction_router_test.cpp b/src/mongo/s/transaction_router_test.cpp index b1edf1164d8..4e286306a4d 100644 --- a/src/mongo/s/transaction_router_test.cpp +++ b/src/mongo/s/transaction_router_test.cpp @@ -719,7 +719,6 @@ TEST_F(TransactionRouterTestWithDefaultSession, AttachTxnValidatesReadConcernIfA } } -// TODO (SERVER-56550): Test that API parameters are required in txn-continuing commands. TEST_F(TransactionRouterTestWithDefaultSession, SameAPIParametersAfterFirstStatement) { APIParameters apiParameters = APIParameters(); apiParameters.setAPIVersion("1"); @@ -758,6 +757,27 @@ TEST_F(TransactionRouterTestWithDefaultSession, DifferentAPIParametersAfterFirst ErrorCodes::APIMismatchError); } +TEST_F(TransactionRouterTestWithDefaultSession, NoAPIParametersAfterFirstStatement) { + APIParameters apiParameters = APIParameters(); + apiParameters.setAPIVersion("1"); + APIParameters::get(operationContext()) = apiParameters; + TxnNumber txnNum{3}; + + auto txnRouter = TransactionRouter::get(operationContext()); + txnRouter.beginOrContinueTxn( + operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); + txnRouter.setDefaultAtClusterTime(operationContext()); + + // Can't continue without params. (Must reset readConcern from "snapshot".) + APIParameters::get(operationContext()) = APIParameters(); + repl::ReadConcernArgs::get(operationContext()) = repl::ReadConcernArgs(); + ASSERT_THROWS_CODE( + txnRouter.beginOrContinueTxn( + operationContext(), txnNum, TransactionRouter::TransactionActions::kContinue), + DBException, + ErrorCodes::APIMismatchError); +} + TEST_F(TransactionRouterTestWithDefaultSession, CannotSpecifyReadConcernAfterFirstStatement) { TxnNumber txnNum{3}; @@ -2300,34 +2320,6 @@ TEST_F(TransactionRouterTestWithDefaultSession, ContinueOnlyOnStaleVersionOnFirs ASSERT_FALSE(txnRouter.canContinueOnStaleShardOrDbError("update", kStaleConfigStatus)); } -TEST_F(TransactionRouterTestWithDefaultSession, - ContinuingTransactionPlacesItsAPIParametersOnOpCtx) { - APIParameters apiParams = APIParameters(); - apiParams.setAPIVersion("2"); - apiParams.setAPIStrict(true); - apiParams.setAPIDeprecationErrors(true); - - APIParameters::get(operationContext()) = apiParams; - repl::ReadConcernArgs::get(operationContext()) = repl::ReadConcernArgs(); - - TxnNumber txnNum{3}; - - auto txnRouter = TransactionRouter::get(operationContext()); - - txnRouter.beginOrContinueTxn( - operationContext(), txnNum, TransactionRouter::TransactionActions::kStart); - txnRouter.setDefaultAtClusterTime(operationContext()); - - APIParameters::get(operationContext()) = APIParameters(); - txnRouter.beginOrContinueTxn( - operationContext(), txnNum, TransactionRouter::TransactionActions::kContinue); - - auto storedAPIParams = APIParameters::get(operationContext()); - ASSERT_EQ("2", *storedAPIParams.getAPIVersion()); - ASSERT_TRUE(*storedAPIParams.getAPIStrict()); - ASSERT_TRUE(*storedAPIParams.getAPIDeprecationErrors()); -} - TEST_F(TransactionRouterTestWithDefaultSession, ContinuingTransactionPlacesItsReadConcernOnOpCtx) { TxnNumber txnNum{3}; |