diff options
19 files changed, 1668 insertions, 264 deletions
diff --git a/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml b/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml index ce3749db4bd..acafd931353 100644 --- a/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml +++ b/buildscripts/resmokeconfig/suites/concurrency_sharded_with_stepdowns.yml @@ -169,6 +169,9 @@ selector: - jstests/concurrency/fsm_workloads/snapshot_read_kill_op_only.js - jstests/concurrency/fsm_workloads/snapshot_read_kill_operations.js + # Expects same CSRS primary and shard primary throughout the test + - jstests/sharding/mongos_forwards_api_parameters_to_shards.js + exclude_with_any_tags: - requires_replication - requires_non_retryable_writes diff --git a/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml b/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml index 8385be22650..ede8d787908 100644 --- a/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml +++ b/buildscripts/resmokeconfig/suites/sharding_continuous_config_stepdown.yml @@ -222,6 +222,9 @@ selector: - jstests/sharding/conversion_of_replica_set_to_sharded_cluster.js - jstests/sharding/move_primary_with_writes.js + # Expects same CSRS primary and shard primary throughout the test + - jstests/sharding/mongos_forwards_api_parameters_to_shards.js + executor: config: shell_options: diff --git a/jstests/sharding/api_params_nontransaction_sharded.js b/jstests/sharding/api_params_nontransaction_sharded.js new file mode 100644 index 00000000000..0744f38be96 --- /dev/null +++ b/jstests/sharding/api_params_nontransaction_sharded.js @@ -0,0 +1,12 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [multiversion_incompatible] + */ + +(function() { +'use strict'; + +load('jstests/sharding/libs/mongos_api_params_util.js'); +MongosAPIParametersUtil.runTests({inTransaction: false, shardedCollection: true}); +})(); diff --git a/jstests/sharding/api_params_nontransaction_unsharded.js b/jstests/sharding/api_params_nontransaction_unsharded.js new file mode 100644 index 00000000000..d9eb64570cd --- /dev/null +++ b/jstests/sharding/api_params_nontransaction_unsharded.js @@ -0,0 +1,12 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [multiversion_incompatible] + */ + +(function() { +'use strict'; + +load('jstests/sharding/libs/mongos_api_params_util.js'); +MongosAPIParametersUtil.runTests({inTransaction: false, shardedCollection: false}); +})(); diff --git a/jstests/sharding/api_params_transaction_sharded.js b/jstests/sharding/api_params_transaction_sharded.js new file mode 100644 index 00000000000..24d88079182 --- /dev/null +++ b/jstests/sharding/api_params_transaction_sharded.js @@ -0,0 +1,12 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [multiversion_incompatible] + */ + +(function() { +'use strict'; + +load('jstests/sharding/libs/mongos_api_params_util.js'); +MongosAPIParametersUtil.runTests({inTransaction: true, shardedCollection: true}); +})(); diff --git a/jstests/sharding/api_params_transaction_unsharded.js b/jstests/sharding/api_params_transaction_unsharded.js new file mode 100644 index 00000000000..c171c10d920 --- /dev/null +++ b/jstests/sharding/api_params_transaction_unsharded.js @@ -0,0 +1,12 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [multiversion_incompatible] + */ + +(function() { +'use strict'; + +load('jstests/sharding/libs/mongos_api_params_util.js'); +MongosAPIParametersUtil.runTests({inTransaction: true, shardedCollection: false}); +})(); diff --git a/jstests/sharding/libs/mongos_api_params_util.js b/jstests/sharding/libs/mongos_api_params_util.js new file mode 100644 index 00000000000..1da16ff8d52 --- /dev/null +++ b/jstests/sharding/libs/mongos_api_params_util.js @@ -0,0 +1,1496 @@ +/* + * Utilities for checking that mongos commands forward their API version parameters to config + * servers and shards. + */ + +let MongosAPIParametersUtil = (function() { + 'use strict'; + + load('jstests/replsets/rslib.js'); + load('jstests/sharding/libs/last_lts_mongos_commands.js'); + load('jstests/sharding/libs/sharded_transactions_helpers.js'); + + function validateTestCase(testCase) { + assert(testCase.skip || testCase.run, + "must specify exactly one of 'skip' or 'run' for test case " + tojson(testCase)); + + if (testCase.skip) { + for (let key of Object.keys(testCase)) { + assert( + key === "commandName" || key === "skip" || key === "conditional", + `if a test case specifies 'skip', it must not specify any other fields besides` + + ` 'commandName' and 'conditional': ${key}: ${tojson(testCase)}`); + } + return; + } + + validateCommandTestCase(testCase.run); + + if (testCase.explain) { + validateCommandTestCase(testCase.explain); + } + } + + function validateCommandTestCase(testCase) { + assert(testCase.command, "must specify 'command' for test case " + tojson(testCase)); + + // Check that required fields are present. + assert(testCase.hasOwnProperty("inAPIVersion1"), + "must specify 'inAPIVersion1' for test case " + tojson(testCase)); + + // Check that all present fields are of the correct type. + assert(typeof (testCase.command) === "function"); + assert(typeof (testCase.inAPIVersion1) === "boolean"); + + for (const [propertyName, defaultValue] of [["runsAgainstAdminDb", false], + ["permittedInTxn", true], + ["permittedOnShardedCollection", true], + ["requiresShardedCollection", false]]) { + if (testCase.hasOwnProperty(propertyName)) { + assert(typeof testCase[propertyName] === "boolean", + `${propertyName} must be a boolean: ${tojson(testCase)}`); + } else { + testCase[propertyName] = defaultValue; + } + } + + assert(testCase.shardCommandName ? typeof (testCase.shardCommandName) === "string" : true); + assert(testCase.configServerCommandName + ? typeof (testCase.configServerCommandName) === "string" + : true); + assert(testCase.shardCommandName || testCase.configServerCommandName, + "must specify shardCommandName and/or configServerCommandName: " + tojson(testCase)); + assert(testCase.setUp ? typeof (testCase.setUp) === "function" : true, + "setUp must be a function: " + tojson(testCase)); + assert(testCase.cleanUp ? typeof (testCase.cleanUp) === "function" : true, + "cleanUp must be a function: " + tojson(testCase)); + } + + function awaitRemoveShard(shardName) { + assert.commandWorked(st.startBalancer()); + st.waitForBalancer(true, 60000); + assert.soon(() => { + const res = st.s.adminCommand({removeShard: shardName}); + jsTestLog(`removeShard result: ${tojson(res)}`); + if (!res.ok && res.code === ErrorCodes.ShardNotFound) { + return true; + } + + return 'completed' === res.state; + }, "removeShard never completed for shard " + shardName, 10 * 60 * 1000, 1000); + assert.commandWorked(st.stopBalancer()); + } + + // Each test case is potentially run with any combination of API parameters, in + // sharded/unsharded collection, inside or outside of a multi-document transaction. The "db" + // database is dropped and recreated between test cases, so most tests don't need custom setUp + // or cleanUp. Test cases are not 1-1 with commands, e.g. "count" has two cases. + let testCases = [ + { + commandName: "_hashBSONElement", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "_isSelf", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "_killOperations", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "_mergeAuthzCollections", skip: "internal API"}, + {commandName: "abortTransaction", skip: "prohibits API parameters"}, + { + commandName: "addShard", + run: { + inAPIVersion1: false, + runsAgainstAdminDb: true, + configServerCommandName: "_configsvrAddShard", + shardCommandName: "_addShard", + permittedInTxn: false, + setUp: () => { + // Remove shard0 so we can add it back. + assert.commandWorked(st.s0.getDB("db").dropDatabase()); + awaitRemoveShard(st.shard0.shardName); + }, + command: () => ({addShard: st.rs0.getURL()}) + } + }, + { + commandName: "addShardToZone", + run: { + inAPIVersion1: false, + runsAgainstAdminDb: true, + configServerCommandName: "_configsvrAddShardToZone", + permittedInTxn: false, + command: () => ({addShardToZone: st.shard0.shardName, zone: "foo"}), + cleanUp: () => assert.commandWorked(st.s0.getDB("admin").runCommand( + {removeShardFromZone: st.shard0.shardName, zone: "foo"})) + } + }, + { + commandName: "aggregate", + run: { + inAPIVersion1: true, + shardCommandName: "aggregate", + command: () => ({aggregate: "collection", pipeline: [], cursor: {}}) + }, + explain: { + inAPIVersion1: true, + shardCommandName: "explain", + permittedInTxn: false, + command: () => ({explain: {aggregate: "collection", pipeline: [], cursor: {}}}) + } + }, + { + commandName: "authenticate", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "availableQueryOptions", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "balancerCollectionStatus", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "balancerStart", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "balancerStatus", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "balancerStop", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "buildInfo", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "clearJumboFlag", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "clearLog", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "collMod", + run: { + inAPIVersion1: true, + shardCommandName: "collMod", + permittedInTxn: false, + command: () => ({collMod: "collection"}), + } + }, + { + commandName: "collStats", + run: { + inAPIVersion1: false, + shardCommandName: "collStats", + permittedInTxn: false, + command: () => ({collStats: "collection"}), + } + }, + {commandName: "commitTransaction", skip: "prohibits API parameters"}, + {commandName: "compact", skip: "not allowed through mongos"}, + { + commandName: "configureFailPoint", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "connPoolStats", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "connPoolSync", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "connectionStatus", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "convertToCapped", + run: { + inAPIVersion1: false, + shardCommandName: "convertToCapped", + permittedOnShardedCollection: false, + permittedInTxn: false, + command: () => ({convertToCapped: "collection", size: 8192}), + } + }, + // The count command behaves differently if it has a query or no query. + { + commandName: "count", + run: { + inAPIVersion1: true, + shardCommandName: "count", + permittedInTxn: false, + command: () => ({count: "collection"}) + }, + explain: { + inAPIVersion1: true, + shardCommandName: "explain", + permittedInTxn: false, + command: () => ({explain: {count: "collection"}}) + } + }, + { + commandName: "count", + run: { + inAPIVersion1: true, + shardCommandName: "count", + permittedInTxn: false, + command: () => ({count: "collection", query: {x: 1}}) + }, + explain: { + inAPIVersion1: true, + shardCommandName: "explain", + permittedInTxn: false, + command: () => ({explain: {count: "collection", query: {x: 1}}}) + } + }, + { + commandName: "create", + run: { + inAPIVersion1: true, + shardCommandName: "create", + command: () => ({create: "collection2"}) + } + }, + { + commandName: "createIndexes", + run: { + inAPIVersion1: true, + shardCommandName: "createIndexes", + permittedInTxn: false, + command: () => + ({createIndexes: "collection", indexes: [{key: {a: 1}, name: "index"}]}) + } + }, + { + commandName: "createRole", + run: { + inAPIVersion1: false, + configServerCommandName: "createRole", + permittedInTxn: false, + command: () => ({createRole: "foo", privileges: [], roles: []}), + cleanUp: () => assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "foo"})) + } + }, + { + commandName: "createUser", + run: { + inAPIVersion1: false, + configServerCommandName: "createUser", + permittedInTxn: false, + command: () => ({createUser: "foo", pwd: "bar", roles: []}), + cleanUp: () => assert.commandWorked(st.s0.getDB("db").runCommand({dropUser: "foo"})) + } + }, + { + commandName: "currentOp", + run: { + inAPIVersion1: false, + shardCommandName: "aggregate", + permittedInTxn: false, + runsAgainstAdminDb: true, + command: () => ({currentOp: 1}) + } + }, + { + commandName: "dataSize", + run: { + inAPIVersion1: false, + shardCommandName: "dataSize", + permittedInTxn: false, + command: () => ({dataSize: "db.collection"}), + } + }, + { + commandName: "dbStats", + run: { + inAPIVersion1: false, + shardCommandName: "dbStats", + permittedInTxn: false, + command: () => ({dbStats: 1, scale: 1}) + } + }, + { + commandName: "delete", + run: { + inAPIVersion1: true, + shardCommandName: "delete", + command: () => ({delete: "collection", deletes: [{q: {_id: 1}, limit: 1}]}) + }, + explain: { + inAPIVersion1: true, + shardCommandName: "explain", + permittedInTxn: false, + command: () => + ({explain: {delete: "collection", deletes: [{q: {_id: 1}, limit: 1}]}}) + } + }, + { + commandName: "distinct", + run: { + inAPIVersion1: false, + shardCommandName: "distinct", + permittedInTxn: false, + command: () => ({distinct: "collection", key: "x"}) + }, + explain: { + inAPIVersion1: false, + shardCommandName: "explain", + permittedInTxn: false, + command: () => ({explain: {distinct: "collection", key: "x"}}) + }, + }, + { + commandName: "drop", + run: { + inAPIVersion1: true, + shardCommandName: "drop", + permittedInTxn: false, + command: () => ({drop: "collection"}) + } + }, + { + commandName: "dropAllRolesFromDatabase", + run: { + inAPIVersion1: false, + configServerCommandName: "dropAllRolesFromDatabase", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})), + command: () => ({dropAllRolesFromDatabase: 1}) + } + }, + { + commandName: "dropAllUsersFromDatabase", + run: { + inAPIVersion1: false, + configServerCommandName: "dropAllUsersFromDatabase", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {createUser: "foo", pwd: "bar", roles: [], writeConcern: {w: 1}})), + command: () => ({dropAllUsersFromDatabase: 1}) + } + }, + { + commandName: "dropConnections", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "dropDatabase", + run: { + inAPIVersion1: true, + configServerCommandName: "_configsvrDropDatabase", + shardCommandName: "dropDatabase", + permittedInTxn: false, + command: () => ({dropDatabase: 1}) + } + }, + { + commandName: "dropIndexes", + run: { + inAPIVersion1: true, + shardCommandName: "dropIndexes", + permittedInTxn: false, + command: () => ({dropIndexes: "collection", index: "*"}), + } + }, + { + commandName: "dropRole", + run: { + inAPIVersion1: false, + configServerCommandName: "dropRole", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})), + command: () => ({dropRole: "foo"}) + } + }, + { + commandName: "dropUser", + run: { + inAPIVersion1: false, + configServerCommandName: "dropUser", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {createUser: "foo", pwd: "bar", roles: [], writeConcern: {w: 1}})), + command: () => ({dropUser: "foo"}) + } + }, + {commandName: "echo", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "enableSharding", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "endSessions", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "explain", skip: "tested by other means"}, + {commandName: "features", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "filemd5", + run: { + inAPIVersion1: false, + shardCommandName: "filemd5", + permittedInTxn: false, + command: () => ({filemd5: ObjectId(), root: "collection"}) + } + }, + { + commandName: "find", + run: { + inAPIVersion1: true, + shardCommandName: "find", + command: () => ({find: "collection", filter: {x: 1}}) + }, + explain: { + inAPIVersion1: true, + shardCommandName: "explain", + permittedInTxn: false, + command: () => ({explain: {find: "collection", filter: {x: 1}}}) + } + }, + { + commandName: "findAndModify", + run: { + inAPIVersion1: true, + shardCommandName: "findAndModify", + command: () => ({findAndModify: "collection", query: {_id: 0}, remove: true}) + }, + explain: { + inAPIVersion1: true, + shardCommandName: "explain", + permittedInTxn: false, + command: () => + ({explain: {findAndModify: "collection", query: {_id: 0}, remove: true}}) + } + }, + { + commandName: "flushRouterConfig", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "fsync", + run: { + inAPIVersion1: false, + shardCommandName: "fsync", + runsAgainstAdminDb: true, + permittedInTxn: false, + command: () => ({fsync: 1}) + } + }, + { + commandName: "getCmdLineOpts", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "getDefaultRWConcern", + run: { + inAPIVersion1: false, + configServerCommandName: "getDefaultRWConcern", + runsAgainstAdminDb: true, + permittedInTxn: false, + command: () => ({getDefaultRWConcern: 1}) + } + }, + { + commandName: "getDiagnosticData", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "getLastError", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "getLog", skip: "executes locally on mongos (not sent to any remote node)"}, + {commandName: "getMore", skip: "prohibits API parameters"}, + { + commandName: "getParameter", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "getShardMap", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "getShardVersion", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "getnonce", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "grantPrivilegesToRole", + run: { + inAPIVersion1: false, + configServerCommandName: "grantPrivilegesToRole", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})), + command: () => ({ + grantPrivilegesToRole: "foo", + privileges: + [{resource: {db: "db", collection: "collection"}, actions: ["find"]}] + }), + cleanUp: () => + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "foo"})), + } + }, + { + commandName: "grantRolesToRole", + run: { + inAPIVersion1: false, + configServerCommandName: "grantRolesToRole", + permittedInTxn: false, + setUp: function() { + assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})); + assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "bar", privileges: [], roles: [], writeConcern: {w: 1}})); + }, + command: () => ({grantRolesToRole: "foo", roles: [{role: "bar", db: "db"}]}), + cleanUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "foo"})); + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "bar"})); + } + } + }, + { + commandName: "grantRolesToUser", + run: { + inAPIVersion1: false, + configServerCommandName: "grantRolesToUser", + permittedInTxn: false, + setUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand( + {createUser: "foo", pwd: "bar", roles: [], writeConcern: {w: 1}})); + assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})); + }, + command: () => ({grantRolesToUser: "foo", roles: [{role: "foo", db: "db"}]}), + cleanUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand({dropUser: "foo"})); + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "foo"})); + } + } + }, + {commandName: "hello", skip: "executes locally on mongos (not sent to any remote node)"}, + {commandName: "hostInfo", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "insert", + run: { + inAPIVersion1: true, + shardCommandName: "insert", + command: () => ({insert: "collection", documents: [{_id: 1}]}), + } + }, + { + commandName: "invalidateUserCache", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "isdbgrid", skip: "executes locally on mongos (not sent to any remote node)"}, + {commandName: "isMaster", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "killCursors", + run: + { + inAPIVersion1: true, + shardCommandName: "killCursors", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {insert: "collection", documents: [{}, {}, {}]})), + command: + () => { + const res = assert.commandWorked( + st.s0.getDB("db").runCommand({find: "collection", batchSize: 1})); + jsTestLog(`res: ${res}`); + const cursorId = res.cursor.id; + return {killCursors: "collection", cursors: [cursorId]}; + } + } + }, + { + commandName: "killAllSessions", + run: { + inAPIVersion1: false, + shardCommandName: "killAllSessionsByPattern", + runsAgainstAdminDb: true, + permittedInTxn: false, + command: () => ({killAllSessions: []}) + } + }, + { + commandName: "killAllSessionsByPattern", + run: { + inAPIVersion1: false, + shardCommandName: "killAllSessionsByPattern", + runsAgainstAdminDb: true, + permittedInTxn: false, + command: () => ({killAllSessionsByPattern: []}) + } + }, + { + commandName: "killOp", + run: { + inAPIVersion1: false, + shardCommandName: "killOp", + permittedInTxn: false, + runsAgainstAdminDb: true, + setUp: (context) => { + function threadRoutine({connStr}) { + const client = new Mongo(connStr); + jsTestLog(`Calling find on "${connStr}" from thread`); + const res = client.getDB("db").runCommand({ + find: "collection", + filter: {$where: "sleep(99999999); return true;"}, + comment: "foo" + }); + jsTestLog(`Called find command: ${tojson(res)}`); + } + + context.thread = new Thread(threadRoutine, {connStr: st.s0.host}); + context.thread.start(); + const adminDb = st.s0.getDB("admin"); + + assert.soon(() => { + const inprog = adminDb.currentOp({"command.comment": "foo"}).inprog; + if (inprog.length === 1) { + context.findOpId = inprog[0].opid; + return true; + } + }); + }, + command: (context) => ({killOp: 1, op: context.findOpId}), + cleanUp: (context) => { + context.thread.join(); + } + } + }, + { + commandName: "killSessions", + run: { + inAPIVersion1: false, + shardCommandName: "killAllSessionsByPattern", + runsAgainstAdminDb: true, + permittedInTxn: false, + setUp: (context) => { + const session = st.s0.startSession(); + context.lsid = session.getSessionId(); + }, + command: (context) => ({killSessions: [context.lsid]}) + } + }, + { + commandName: "listCollections", + run: { + inAPIVersion1: true, + shardCommandName: "listCollections", + permittedInTxn: false, + command: () => ({listCollections: 1}) + } + }, + { + commandName: "listCommands", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "listDatabases", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "listIndexes", + run: { + inAPIVersion1: true, + shardCommandName: "listIndexes", + permittedInTxn: false, + command: () => ({listIndexes: "collection"}), + } + }, + { + commandName: "listShards", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "logApplicationMessage", + skip: "executes locally on mongos (not sent to any remote node)", + conditional: true + }, + { + commandName: "logMessage", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "logRotate", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "logout", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "mapReduce", + run: { + inAPIVersion1: false, + permittedInTxn: false, + shardCommandName: "aggregate", + command: () => ({ + mapReduce: "collection", + map: function mapFunc() { + emit(this.x, 1); + }, + reduce: function reduceFunc(key, values) { + return Array.sum(values); + }, + out: "inline" + }), + }, + explain: { + inAPIVersion1: false, + shardCommandName: "explain", + permittedInTxn: false, + command: () => ({ + explain: { + mapReduce: "collection", + map: function mapFunc() { + emit(this.x, 1); + }, + reduce: function reduceFunc(key, values) { + return Array.sum(values); + }, + out: "inline" + } + }), + } + }, + { + commandName: "mergeChunks", + run: { + inAPIVersion1: false, + shardCommandName: "mergeChunks", + configServerCommandName: "_configsvrCommitChunkMerge", + runsAgainstAdminDb: true, + permittedInTxn: false, + requiresShardedCollection: true, + setUp: () => { + // Collection is already split into chunks [MinKey, 10], (10, MaxKey]. + assert.commandWorked( + st.s.adminCommand({split: "db.collection", middle: {_id: -5}})); + // Now the chunks are: [MinKey, -5], (-5, 10], (10, MaxKey]. + }, + command: () => ({mergeChunks: "db.collection", bounds: [{_id: MinKey}, {_id: 10}]}) + } + }, + { + commandName: "moveChunk", + run: { + inAPIVersion1: false, + shardCommandName: "moveChunk", + configServerCommandName: "_configsvrMoveChunk", + runsAgainstAdminDb: true, + permittedInTxn: false, + requiresShardedCollection: true, + command: () => ({ + moveChunk: "db.collection", + find: {_id: 1}, + to: st.shard1.shardName, + // Don't interfere with the next test case. + _waitForDelete: true + }) + } + }, + { + commandName: "movePrimary", + run: { + inAPIVersion1: false, + configServerCommandName: "_configsvrMovePrimary", + shardCommandName: "_shardsvrMovePrimary", + runsAgainstAdminDb: true, + permittedInTxn: false, + command: () => ({movePrimary: "db", to: st.shard1.shardName}) + } + }, + { + commandName: "multicast", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "netstat", skip: "executes locally on mongos (not sent to any remote node)"}, + {commandName: "ping", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "planCacheClear", + run: { + inAPIVersion1: false, + shardCommandName: "planCacheClear", + permittedInTxn: false, + command: () => ({planCacheClear: "collection"}) + } + }, + { + commandName: "planCacheClearFilters", + run: { + inAPIVersion1: false, + shardCommandName: "planCacheClearFilters", + permittedInTxn: false, + command: () => ({planCacheClearFilters: "collection"}) + } + }, + { + commandName: "planCacheListFilters", + run: { + inAPIVersion1: false, + shardCommandName: "planCacheListFilters", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {planCacheSetFilter: "collection", query: {_id: 1}, indexes: [{_id: 1}]})), + command: () => ({planCacheListFilters: "collection"}) + } + }, + { + commandName: "planCacheSetFilter", + run: { + inAPIVersion1: false, + shardCommandName: "planCacheSetFilter", + permittedInTxn: false, + command: () => + ({planCacheSetFilter: "collection", query: {_id: 1}, indexes: [{_id: 1}]}), + } + }, + {commandName: "profile", skip: "not supported in mongos"}, + {commandName: "reapLogicalSessionCacheNow", skip: "is a no-op on mongos"}, + { + commandName: "refineCollectionShardKey", + run: { + inAPIVersion1: false, + configServerCommandName: "_configsvrRefineCollectionShardKey", + runsAgainstAdminDb: true, + permittedInTxn: false, + requiresShardedCollection: true, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand({ + createIndexes: "collection", + indexes: [{key: {_id: 1, x: 1}, name: "_id-1-x-1"}] + })), + command: () => ({refineCollectionShardKey: "db.collection", key: {_id: 1, x: 1}}) + } + }, + { + commandName: "refreshLogicalSessionCacheNow", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "refreshSessions", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "removeShard", + run: { + inAPIVersion1: false, + runsAgainstAdminDb: true, + configServerCommandName: "_configsvrRemoveShard", + permittedInTxn: false, + command: () => ({removeShard: st.shard0.shardName}), + cleanUp: () => { + // Wait for the shard to be removed completely before re-adding it. + awaitRemoveShard(st.shard0.shardName); + assert.commandWorked(st.s0.getDB("admin").runCommand( + {addShard: st.rs0.getURL(), name: st.shard0.shardName})); + } + } + }, + { + commandName: "removeShardFromZone", + run: { + inAPIVersion1: false, + runsAgainstAdminDb: true, + configServerCommandName: "_configsvrRemoveShardFromZone", + permittedInTxn: false, + setup: () => + assert.commandWorked({addShardToZone: st.shard0.shardName, zone: "foo"}), + command: () => ({removeShardFromZone: st.shard0.shardName, zone: "foo"}) + } + }, + { + commandName: "renameCollection", + run: { + inAPIVersion1: false, + shardCommandName: "renameCollection", + permittedOnShardedCollection: false, + permittedInTxn: false, + runsAgainstAdminDb: true, + command: () => ({renameCollection: "db.collection", to: "db.collection_renamed"}) + } + }, + {commandName: "replSetGetStatus", skip: "not supported in mongos"}, + { + commandName: "resetError", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "reshardCollection", + run: { + inAPIVersion1: false, + permittedInTxn: false, + configServerCommandName: "_configsvrReshardCollection", + requiresShardedCollection: true, + runsAgainstAdminDb: true, + command: () => ({reshardCollection: "db.collection", key: {_id: 1}}) + } + }, + { + commandName: "revokePrivilegesFromRole", + run: { + inAPIVersion1: false, + permittedInTxn: false, + configServerCommandName: "revokePrivilegesFromRole", + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand({ + createRole: "foo", + privileges: + [{resource: {db: "db", collection: "collection"}, actions: ["find"]}], + roles: [], + writeConcern: {w: 1} + })), + command: () => ({ + revokePrivilegesFromRole: "foo", + privileges: + [{resource: {db: "db", collection: "collection"}, actions: ["find"]}] + }), + cleanUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "foo"})); + } + } + }, + { + commandName: "revokeRolesFromRole", + run: { + inAPIVersion1: false, + permittedInTxn: false, + configServerCommandName: "revokeRolesFromRole", + setUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})); + assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "bar", privileges: [], roles: [], writeConcern: {w: 1}})); + assert.commandWorked(st.s0.getDB("db").runCommand( + {grantRolesToRole: "foo", roles: [{role: "bar", db: "db"}]})); + }, + command: () => ({revokeRolesFromRole: "foo", roles: [{role: "bar", db: "db"}]}), + cleanUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "foo"})); + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "bar"})); + } + } + }, + { + commandName: "revokeRolesFromUser", + run: { + inAPIVersion1: false, + configServerCommandName: "revokeRolesFromUser", + permittedInTxn: false, + setUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand( + {createUser: "foo", pwd: "bar", roles: [], writeConcern: {w: 1}})); + assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})); + assert.commandWorked(st.s0.getDB("db").runCommand( + {grantRolesToUser: "foo", roles: [{role: "foo", db: "db"}]})); + }, + command: () => ({revokeRolesFromUser: "foo", roles: [{role: "foo", db: "db"}]}), + cleanUp: () => { + assert.commandWorked(st.s0.getDB("db").runCommand({dropUser: "foo"})); + assert.commandWorked(st.s0.getDB("db").runCommand({dropRole: "foo"})); + } + } + }, + { + commandName: "rolesInfo", + run: { + inAPIVersion1: false, + configServerCommandName: "rolesInfo", + permittedInTxn: false, + command: () => ({rolesInfo: 1}) + } + }, + { + commandName: "rotateCertificates", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "saslContinue", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "saslStart", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "serverStatus", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "setDefaultRWConcern", + run: { + inAPIVersion1: false, + configServerCommandName: "setDefaultRWConcern", + runsAgainstAdminDb: true, + permittedInTxn: false, + command: () => ({setDefaultRWConcern: 1, defaultWriteConcern: {w: 1}}) + } + }, + { + commandName: "setIndexCommitQuorum", + run: { + inAPIVersion1: false, + shardCommandName: "setIndexCommitQuorum", + permittedInTxn: false, + command: () => ({ + setIndexCommitQuorum: "collection", + indexNames: ["index"], + commitQuorum: "majority" + }), + } + }, + { + commandName: "setFeatureCompatibilityVersion", + run: { + inAPIVersion1: false, + configServerCommandName: "setFeatureCompatibilityVersion", + permittedInTxn: false, + runsAgainstAdminDb: true, + command: () => ({setFeatureCompatibilityVersion: latestFCV}) + } + }, + { + commandName: "setFreeMonitoring", + skip: "explicitly fails for mongos, primary mongod only", + conditional: true + }, + { + commandName: "setParameter", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "shardCollection", + run: { + inAPIVersion1: false, + configServerCommandName: "_configsvrShardCollection", + shardCommandName: "_shardsvrShardCollection", + runsAgainstAdminDb: true, + permittedInTxn: false, + permittedOnShardedCollection: false, + setUp: () => { + assert.commandWorked(st.s.adminCommand({enableSharding: "db"})); + st.ensurePrimaryShard("db", st.shard0.shardName); + }, + command: () => ({shardCollection: "db.collection", key: {_id: 1}}) + } + }, + { + commandName: "shardConnPoolStats", + skip: "executes locally on mongos (not sent to any remote node)" + }, + {commandName: "shutdown", skip: "executes locally on mongos (not sent to any remote node)"}, + { + commandName: "split", + run: { + inAPIVersion1: false, + configServerCommandName: "_configsvrCommitChunkSplit", + shardCommandName: "splitChunk", + runsAgainstAdminDb: true, + permittedInTxn: false, + requiresShardedCollection: true, + command: () => ({split: "db.collection", middle: {_id: 5}}) + } + }, + { + commandName: "splitVector", + run: { + inAPIVersion1: false, + shardCommandName: "splitVector", + permittedInTxn: false, + permittedOnShardedCollection: false, + command: () => ({ + splitVector: "db.collection", + keyPattern: {_id: 1}, + min: {_id: 0}, + max: {_id: MaxKey}, + maxChunkSizeBytes: 1024 + }) + } + }, + { + commandName: "startRecordingTraffic", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "startSession", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "stopRecordingTraffic", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "testDeprecation", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "testDeprecationInVersion2", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "testRemoval", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "testVersion2", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "testVersions1And2", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "update", + run: { + inAPIVersion1: true, + shardCommandName: "update", + command: () => ({ + update: "collection", + updates: [{q: {_id: 2}, u: {_id: 2}, upsert: true, multi: false}] + }), + }, + explain: { + inAPIVersion1: true, + shardCommandName: "explain", + permittedInTxn: false, + command: () => ({ + explain: { + update: "collection", + updates: [{q: {_id: 2}, u: {_id: 2}, upsert: true, multi: false}] + } + }), + } + }, + { + commandName: "updateRole", + run: { + inAPIVersion1: false, + configServerCommandName: "updateRole", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {createRole: "foo", privileges: [], roles: [], writeConcern: {w: 1}})), + command: () => ({updateRole: "foo", authenticationRestrictions: []}), + cleanUp: () => assert.commandWorked( + st.s0.getDB("db").runCommand({dropAllRolesFromDatabase: 1})) + } + }, + { + commandName: "updateUser", + run: { + inAPIVersion1: false, + configServerCommandName: "updateUser", + permittedInTxn: false, + setUp: () => assert.commandWorked(st.s0.getDB("db").runCommand( + {createUser: "foo", pwd: "bar", roles: [], writeConcern: {w: 1}})), + command: () => ({updateUser: "foo", authenticationRestrictions: []}), + cleanUp: () => assert.commandWorked( + st.s0.getDB("db").runCommand({dropAllUsersFromDatabase: 1})) + } + }, + { + commandName: "updateZoneKeyRange", + run: { + inAPIVersion1: false, + configServerCommandName: "_configsvrUpdateZoneKeyRange", + permittedInTxn: false, + runsAgainstAdminDb: true, + setUp: () => assert.commandWorked(st.s0.getDB("admin").runCommand( + {addShardToZone: st.shard0.shardName, zone: "foo"})), + command: () => ({ + updateZoneKeyRange: "db.collection", + min: {_id: 1}, + max: {_id: 5}, + zone: "foo" + }), + cleanUp: () => { + // Remove zone key range. + assert.commandWorked(st.s0.getDB("admin").runCommand({ + updateZoneKeyRange: "db.collection", + min: {_id: 1}, + max: {_id: 5}, + zone: null + })); + assert.commandWorked(st.s0.getDB("admin").runCommand( + {removeShardFromZone: st.shard0.shardName, zone: "foo"})); + } + } + }, + { + commandName: "usersInfo", + run: { + inAPIVersion1: false, + configServerCommandName: "usersInfo", + permittedInTxn: false, + command: () => ({usersInfo: 1}) + } + }, + { + commandName: "validate", + run: { + inAPIVersion1: false, + shardCommandName: "validate", + permittedInTxn: false, + command: () => ({validate: "collection"}), + } + }, + { + commandName: "waitForFailPoint", + skip: "executes locally on mongos (not sent to any remote node)" + }, + { + commandName: "whatsmyuri", + skip: "executes locally on mongos (not sent to any remote node)" + }, + ]; + + commandsRemovedFromMongosSinceLastLTS.forEach(function(cmd) { + testCases[cmd] = { + skip: "must define test coverage for latest version backwards compatibility" + }; + }); + + const st = new ShardingTest({mongos: 1, shards: 2, rs: {nodes: 1}}); + const listCommandsRes = st.s0.adminCommand({listCommands: 1}); + assert.commandWorked(listCommandsRes); + + (() => { + // Validate test cases for all commands. Ensure there is at least one test case for every + // mongos command, and that the test cases are well formed. + for (const command of Object.keys(listCommandsRes.commands)) { + const matchingCases = testCases.filter(elem => elem.commandName === command); + assert(matchingCases !== [], + "coverage failure: must define a test case for " + command); + for (const testCase of matchingCases) { + validateTestCase(testCase); + testCase.validated = true; + } + } + + // After iterating through all the existing commands, ensure there were no additional test + // cases that did not correspond to any mongos command. + for (const testCase of testCases) { + // We have defined real test cases for commands added since the last LTS version so that + // the test cases are exercised in the regular suites, but because these test cases + // can't run in the last stable suite, we skip processing them here to avoid failing the + // below assertion. We have defined "skip" test cases for commands removed since the + // last LTS version so the test case is defined in last stable suites (in which these + // commands still exist on the mongos), but these test cases won't be run in regular + // suites, so we skip processing them below as well. + if (commandsAddedToMongosSinceLastLTS.includes(testCase.commandName) || + commandsRemovedFromMongosSinceLastLTS.includes(testCase.commandName)) + continue; + assert(testCase.validated || testCase.conditional, + "you defined a test case for a command '" + testCase.commandName + + "' that does not exist on mongos: " + tojson(testCase)); + } + })(); + + function checkPrimaryLog(conn, commandName, apiVersion, apiStrict, apiDeprecationErrors) { + const logs = checkLog.getGlobalLog(conn); + let lastCommandInvocation; + + for (let logMsg of logs) { + const obj = JSON.parse(logMsg); + // Search for "About to run the command" logs. + if (obj.id !== 21965) + continue; + + const args = obj.attr.commandArgs; + if (commandName !== Object.keys(args)[0]) + continue; + + lastCommandInvocation = args; + if (args.apiVersion !== apiVersion || args.apiStrict !== apiStrict || + args.apiDeprecationErrors !== apiDeprecationErrors) + continue; + + // Found a match. + return; + } + + if (lastCommandInvocation === undefined) { + doassert(`Primary didn't log ${commandName}`); + return; + } + + doassert(`Primary didn't log ${commandName} with apiVersion ${apiVersion},` + + ` apiStrict ${apiStrict},` + + ` apiDeprecationErrors ${apiDeprecationErrors}.` + + ` Last invocation of ${commandName} was` + + ` ${tojson(lastCommandInvocation)}`); + } + + function runTests({inTransaction, shardedCollection}) { + // For each combination of config parameters and test case, create a test instance. Do this + // before executing the test instances so we can count the number of instances and log + // 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 testCase of testCases) { + if (testCase.skip) + continue; + + for (let runOrExplain of [testCase.run, testCase.explain]) { + if (runOrExplain === undefined) + continue; + + if (inTransaction && !runOrExplain.permittedInTxn) + continue; + + if (shardedCollection && !runOrExplain.permittedOnShardedCollection) + continue; + + if (!shardedCollection && runOrExplain.requiresShardedCollection) + continue; + + if (apiStrict && !runOrExplain.inAPIVersion1) + continue; + + testInstances.push({ + apiVersion: apiVersion, + apiStrict: apiStrict, + apiDeprecationErrors: apiDeprecationErrors, + commandName: testCase.commandName, + runOrExplain: runOrExplain + }); + } + } + } + + for (let i = 0; i < testInstances.length; ++i) { + const {apiVersion, apiStrict, apiDeprecationErrors, commandName, runOrExplain} = + testInstances[i]; + + if (shardedCollection) { + jsTestLog("Sharded setup"); + assert.commandWorked(st.s.getDB("db")["collection"].insert( + {_id: 0}, {writeConcern: {w: "majority"}})); + assert.commandWorked(st.s.getDB("db")["collection"].insert( + {_id: 20}, {writeConcern: {w: "majority"}})); + + assert.commandWorked(st.s.adminCommand({enableSharding: "db"})); + st.ensurePrimaryShard("db", st.shard0.shardName); + assert.commandWorked( + st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); + + // The chunk with _id 1 is on shard 0. + assert.commandWorked( + st.s.adminCommand({split: "db.collection", middle: {_id: 10}})); + assert.commandWorked(st.s.adminCommand({ + moveChunk: "db.collection", + find: {_id: 20}, + to: st.shard1.shardName, + _waitForDelete: true + })); + } else { + jsTestLog("Unsharded setup"); + assert.commandWorked(st.s.getDB("db")["collection"].insert( + {_id: 0}, {writeConcern: {w: "majority"}})); + st.ensurePrimaryShard("db", st.shard0.shardName); + } + + const configPrimary = st.configRS.getPrimary(); + const shardZeroPrimary = st.rs0.getPrimary(); + const context = {}; + + if (runOrExplain.setUp) { + jsTestLog(`setUp function for ${commandName}`); + runOrExplain.setUp(context); + jsTestLog(`setUp function for ${commandName} completed`); + } + + // Make a copy of the test's command body, and set its API parameters. + const commandDbName = runOrExplain.runsAgainstAdminDb ? "admin" : "db"; + 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; + } + + assert.commandWorked(configPrimary.adminCommand({clearLog: "global"})); + assert.commandWorked(shardZeroPrimary.adminCommand({clearLog: "global"})); + const message = + `[${i + 1} of ${testInstances.length}]: command ${tojson(commandWithAPIParams)}` + + ` ${shardedCollection ? "sharded" : "unsharded"},` + + ` ${inTransaction ? "in" : "outside"} transaction` + + ` on "${commandDbName}" database`; + + flushRoutersAndRefreshShardMetadata(st, {ns: "db.collection"}); + + jsTestLog(`Running ${message}`); + setLogVerbosity([configPrimary, shardZeroPrimary], {"command": {"verbosity": 2}}); + + if (inTransaction) { + const session = st.s0.startSession(); + const sessionDb = session.getDatabase(commandDbName); + session.startTransaction(); + assert.commandWorked(sessionDb.runCommand(commandWithAPIParams)); + assert.commandWorked(session.commitTransaction_forTesting()); + } else { + const db = st.s0.getDB(commandDbName); + assert.commandWorked(db.runCommand(commandWithAPIParams)); + } + + setLogVerbosity([configPrimary, shardZeroPrimary], {"command": {"verbosity": 0}}); + + const configServerCommandName = runOrExplain.configServerCommandName; + const shardCommandName = runOrExplain.shardCommandName; + + if (configServerCommandName) { + jsTestLog(`Check for ${configServerCommandName} in config server's log`); + checkPrimaryLog(configPrimary, + configServerCommandName, + apiVersion, + apiStrict, + apiDeprecationErrors); + } + + if (shardCommandName) { + jsTestLog(`Check for ${shardCommandName} in shard server's log`); + checkPrimaryLog(shardZeroPrimary, + shardCommandName, + apiVersion, + apiStrict, + apiDeprecationErrors); + } + + st.s0.getDB("db").runCommand({dropDatabase: 1}); + if (runOrExplain.cleanUp) { + jsTestLog(`cleanUp function for ${commandName}`); + runOrExplain.cleanUp(context); + jsTestLog(`cleanUp function for ${commandName} completed`); + } + } + + st.stop(); + } + + return {runTests: runTests}; +})(); diff --git a/jstests/sharding/mongos_forwards_api_parameters_to_shards.js b/jstests/sharding/mongos_forwards_api_parameters_to_shards.js deleted file mode 100644 index e611f716992..00000000000 --- a/jstests/sharding/mongos_forwards_api_parameters_to_shards.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * When a client calls a mongos command with API parameters, mongos must forward them to shards. - * - * @tags: [multiversion_incompatible] - */ - -(function() { -'use strict'; - -load('jstests/sharding/libs/sharded_transactions_helpers.js'); - -let st = new ShardingTest({ - mongos: 1, - shards: 2, - rs: {nodes: 1, setParameter: {logComponentVerbosity: tojson({command: {verbosity: 2}})}} -}); - -class APIParameterTest { - constructor( - command, - {dbName = "db", inAPIVersion1 = true, permittedInTxn = true, shardCommandName} = {}) { - this.command = command; - this.dbName = dbName; - this.inAPIVersion1 = inAPIVersion1; - this.permittedInTxn = permittedInTxn; - if (shardCommandName === undefined) { - this.commandName = Object.keys(command)[0]; - } else { - // mongos executes a different command on the shards, e.g. mapReduce becomes aggregate. - this.commandName = shardCommandName; - } - } -} - -const tests = [ - // Write commands. Note, these rely on _id 1 residing on shard 0. - new APIParameterTest({insert: "collection", documents: [{_id: 1}]}), - new APIParameterTest({update: "collection", updates: [{q: {_id: 1}, u: {$set: {x: 1}}}]}), - new APIParameterTest({delete: "collection", deletes: [{q: {_id: 1}, limit: 1}]}), - - // Read commands. - new APIParameterTest({aggregate: "collection", pipeline: [], cursor: {}}), - new APIParameterTest({aggregate: "collection", pipeline: [], cursor: {}, explain: true}, - {shardCommandName: "explain", permittedInTxn: false}), - new APIParameterTest({find: "collection"}), - new APIParameterTest({count: "collection"}, {permittedInTxn: false}), - new APIParameterTest({count: "collection", query: {_id: {$lt: 0}}}, - {inAPIVersion1: false, permittedInTxn: false}), - new APIParameterTest({distinct: "collection", key: "_id"}, - {inAPIVersion1: false, permittedInTxn: false}), - new APIParameterTest( - { - mapReduce: "collection", - map: function() { - emit(1, 1); - }, - reduce: function(key, values) { - return {count: values.length}; - }, - out: {inline: 1} - }, - {inAPIVersion1: false, permittedInTxn: false, shardCommandName: "aggregate"}), - - // FindAndModify. - new APIParameterTest({findAndModify: "collection", query: {_id: 1}, remove: true}), - - // DDL. Order matters: we must create, modify, then drop an index on collection2. - new APIParameterTest({createIndexes: "collection2", indexes: [{key: {x: 1}, name: "x_1"}]}), - new APIParameterTest({collMod: "collection2", index: {keyPattern: {x: 1}, hidden: true}}, - {permittedInTxn: false}), - new APIParameterTest({dropIndexes: "collection2", index: "x_1"}, {permittedInTxn: false}), - // We can create indexes on a non-existent collection in a sharded transaction. - new APIParameterTest({create: "newCollection"}), - new APIParameterTest({renameCollection: "db.newCollection", to: "db.newerCollection"}, - {inAPIVersion1: false, permittedInTxn: false, dbName: "admin"}), - new APIParameterTest({drop: "collection"}, {permittedInTxn: false}), - new APIParameterTest({dropDatabase: 1}, {permittedInTxn: false}), -]; - -function checkPrimaryLog(conn, commandName, apiVersion, apiStrict, apiDeprecationErrors, message) { - const logs = checkLog.getGlobalLog(conn); - let lastCommandInvocation; - - for (let logMsg of logs) { - const obj = JSON.parse(logMsg); - // Search for "About to run the command" logs. - if (obj.id !== 21965) { - continue; - } - - const args = obj.attr.commandArgs; - if (commandName !== Object.keys(args)[0]) { - continue; - } - - lastCommandInvocation = args; - if (args.apiVersion !== apiVersion || args.apiStrict !== apiStrict || - args.apiDeprecationErrors !== apiDeprecationErrors) { - continue; - } - - // Found a match. - return; - } - - if (lastCommandInvocation === undefined) { - doassert(`Primary didn't log ${commandName}`); - return; - } - - doassert(`Primary didn't log ${message}, last invocation of ${commandName} was` + - ` ${tojson(lastCommandInvocation)}`); -} - -for (const sharded of [false, true]) { - 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 (let inTransaction of [false, true]) { - if (sharded) { - jsTestLog("Sharded setup"); - assert.commandWorked(st.s.getDB("db")["collection"].insert( - {_id: 0}, {writeConcern: {w: "majority"}})); - assert.commandWorked(st.s.getDB("db")["collection"].insert( - {_id: 20}, {writeConcern: {w: "majority"}})); - - assert.commandWorked(st.s.adminCommand({enableSharding: "db"})); - st.ensurePrimaryShard("db", st.shard0.shardName); - assert.commandWorked( - st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); - - // The chunk with _id 1 is on shard 0. - assert.commandWorked( - st.s.adminCommand({split: "db.collection", middle: {_id: 10}})); - assert.commandWorked(st.s.adminCommand( - {moveChunk: "db.collection", find: {_id: 20}, to: st.shard1.shardName})); - } else { - jsTestLog("Unsharded setup"); - assert.commandWorked(st.s.getDB("db")["collection"].insert( - {_id: 0}, {writeConcern: {w: "majority"}})); - st.ensurePrimaryShard("db", st.shard0.shardName); - } - - // Shard 0's primary. - const primary = st.rs0.getPrimary(); - - for (const test of tests) { - if (inTransaction && !test.permittedInTxn) { - continue; - } - - if (apiStrict && !test.inAPIVersion1) { - continue; - } - - // Make a copy of the test's command body, and set its API parameters. - const commandWithAPIParams = Object.assign({}, test.command); - if (apiVersion !== undefined) { - commandWithAPIParams.apiVersion = apiVersion; - } - - if (apiStrict !== undefined) { - commandWithAPIParams.apiStrict = apiStrict; - } - - if (apiDeprecationErrors !== undefined) { - commandWithAPIParams.apiDeprecationErrors = apiDeprecationErrors; - } - - assert.commandWorked(primary.adminCommand({clearLog: "global"})); - const message = `command ${tojson(commandWithAPIParams)}` + - ` ${sharded ? "sharded" : "unsharded"},` + - ` ${inTransaction ? "in" : "outside"} transaction`; - - flushRoutersAndRefreshShardMetadata(st, {ns: "db.collection"}); - - jsTestLog(`Running ${message}`); - - if (inTransaction) { - const session = st.s0.startSession(); - const sessionDb = session.getDatabase(test.dbName); - session.startTransaction(); - assert.commandWorked(sessionDb.runCommand(commandWithAPIParams)); - assert.commandWorked(session.commitTransaction_forTesting()); - } else { - const db = st.s0.getDB(test.dbName); - assert.commandWorked(db.runCommand(commandWithAPIParams)); - } - - checkPrimaryLog(primary, - test.commandName, - apiVersion, - apiStrict, - apiDeprecationErrors, - message); - } - - jsTestLog("JS test cleanup: Drop database 'db'"); - st.s0.getDB("db").runCommand({dropDatabase: 1}); - } - } -} - -st.stop(); -})(); diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index f7ef628e7ef..ae87ab8a48e 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -1527,6 +1527,7 @@ env.Library( '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/authprivilege', '$BUILD_DIR/mongo/idl/idl_parser', + 'api_parameters', ], ) diff --git a/src/mongo/db/api_parameters.cpp b/src/mongo/db/api_parameters.cpp index 05ffe9c49cb..51e424637db 100644 --- a/src/mongo/db/api_parameters.cpp +++ b/src/mongo/db/api_parameters.cpp @@ -76,4 +76,18 @@ void APIParameters::appendInfo(BSONObjBuilder* builder) const { } } +std::size_t APIParameters::Hash::operator()(const APIParameters& params) const { + size_t seed = 0; + if (params.getAPIVersion()) { + boost::hash_combine(seed, *params.getAPIVersion()); + } + if (params.getAPIStrict()) { + boost::hash_combine(seed, *params.getAPIStrict()); + } + if (params.getAPIDeprecationErrors()) { + boost::hash_combine(seed, *params.getAPIDeprecationErrors()); + } + return seed; +} + } // namespace mongo diff --git a/src/mongo/db/api_parameters.h b/src/mongo/db/api_parameters.h index efbebfabfcc..34c129bca15 100644 --- a/src/mongo/db/api_parameters.h +++ b/src/mongo/db/api_parameters.h @@ -49,6 +49,11 @@ public: static APIParameters fromClient(const APIParametersFromClient& apiParamsFromClient); static APIParameters fromBSON(const BSONObj& cmdObj); + // For use with unordered_map. + struct Hash { + std::size_t operator()(const APIParameters& params) const; + }; + void appendInfo(BSONObjBuilder* builder) const; const boost::optional<std::string>& getAPIVersion() const { @@ -85,6 +90,14 @@ private: boost::optional<bool> _apiDeprecationErrors; }; +inline bool operator==(const APIParameters& lhs, const APIParameters& rhs) { + return lhs.getAPIVersion() == rhs.getAPIVersion() && lhs.getAPIStrict() == rhs.getAPIStrict() && + lhs.getAPIDeprecationErrors() == rhs.getAPIDeprecationErrors(); +} + +inline bool operator!=(const APIParameters& lhs, const APIParameters& rhs) { + return !(lhs == rhs); +} /** * Temporarily remove the user's API parameters from an OperationContext. diff --git a/src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp b/src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp index c7687db6782..220c585ae78 100644 --- a/src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp +++ b/src/mongo/db/commands/kill_all_sessions_by_pattern_command.cpp @@ -96,7 +96,10 @@ public: // The empty command kills all if (ksc.getKillAllSessionsByPattern().empty()) { - ksc.setKillAllSessionsByPattern({makeKillAllSessionsByPattern(opCtx)}); + auto item = makeKillAllSessionsByPattern(opCtx); + std::vector<mongo::KillAllSessionsByPattern> patterns; + patterns.push_back({std::move(item.pattern)}); + ksc.setKillAllSessionsByPattern(std::move(patterns)); } else { // If a pattern is passed, you may only pass impersonate data if you have the // impersonate privilege. @@ -114,8 +117,10 @@ public: } } - KillAllSessionsByPatternSet patterns{ksc.getKillAllSessionsByPattern().begin(), - ksc.getKillAllSessionsByPattern().end()}; + KillAllSessionsByPatternSet patterns; + for (auto& pattern : ksc.getKillAllSessionsByPattern()) { + patterns.insert({std::move(pattern), APIParameters::get(opCtx)}); + } uassertStatusOK(killSessionsCmdHelper(opCtx, result, patterns)); return true; diff --git a/src/mongo/db/commands/kill_sessions_command.cpp b/src/mongo/db/commands/kill_sessions_command.cpp index 41e3596938f..1bb9c2e3a08 100644 --- a/src/mongo/db/commands/kill_sessions_command.cpp +++ b/src/mongo/db/commands/kill_sessions_command.cpp @@ -63,9 +63,9 @@ KillAllSessionsByPatternSet patternsForLoggedInUser(OperationContext* opCtx) { User* user = authzSession->lookupUser(*iter); invariant(user); - auto pattern = makeKillAllSessionsByPattern(opCtx); - pattern.setUid(user->getDigest()); - patterns.emplace(std::move(pattern)); + auto item = makeKillAllSessionsByPattern(opCtx); + item.pattern.setUid(user->getDigest()); + patterns.emplace(std::move(item)); } } else { patterns.emplace(makeKillAllSessionsByPattern(opCtx)); diff --git a/src/mongo/db/kill_sessions.cpp b/src/mongo/db/kill_sessions.cpp index 41033d23bbb..b167e14c156 100644 --- a/src/mongo/db/kill_sessions.cpp +++ b/src/mongo/db/kill_sessions.cpp @@ -31,6 +31,7 @@ #include "mongo/db/kill_sessions.h" +#include "mongo/db/api_parameters.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/client.h" #include "mongo/db/operation_context.h" @@ -96,27 +97,25 @@ std::tuple<std::vector<UserName>, std::vector<RoleName>> getKillAllSessionsByPat return out; } -KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx) { +KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCtx) { KillAllSessionsByPattern kasbp; kasbp.setUsers(getKillAllSessionsImpersonateUsers(opCtx)); kasbp.setRoles(getKillAllSessionsImpersonateRoles(opCtx)); - - return kasbp; + return {kasbp, APIParameters::get(opCtx)}; } -KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx, - const KillAllSessionsUser& kasu) { - KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx); +KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCtx, + const KillAllSessionsUser& kasu) { + KillAllSessionsByPatternItem item = makeKillAllSessionsByPattern(opCtx); auto authMgr = AuthorizationManager::get(opCtx->getServiceContext()); UserName un(kasu.getUser(), kasu.getDb()); auto user = uassertStatusOK(authMgr->acquireUser(opCtx, un)); - kasbp.setUid(user->getDigest()); - - return kasbp; + item.pattern.setUid(user->getDigest()); + return item; } KillAllSessionsByPatternSet makeSessionFilterForAuthenticatedUsers(OperationContext* opCtx) { @@ -127,18 +126,18 @@ KillAllSessionsByPatternSet makeSessionFilterForAuthenticatedUsers(OperationCont if (auto user = authSession->lookupUser(*it)) { KillAllSessionsByPattern pattern; pattern.setUid(user->getDigest()); - patterns.emplace(std::move(pattern)); + KillAllSessionsByPatternItem item{std::move(pattern), APIParameters::get(opCtx)}; + patterns.emplace(std::move(item)); } } return patterns; } -KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx, - const LogicalSessionId& lsid) { - KillAllSessionsByPattern kasbp = makeKillAllSessionsByPattern(opCtx); - kasbp.setLsid(lsid); - - return kasbp; +KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCtx, + const LogicalSessionId& lsid) { + auto item = makeKillAllSessionsByPattern(opCtx); + item.pattern.setLsid(lsid); + return item; } } // namespace mongo diff --git a/src/mongo/db/kill_sessions.h b/src/mongo/db/kill_sessions.h index 29909fd49df..31ceeed9375 100644 --- a/src/mongo/db/kill_sessions.h +++ b/src/mongo/db/kill_sessions.h @@ -31,6 +31,7 @@ #include <tuple> +#include "mongo/db/api_parameters.h" #include "mongo/db/auth/role_name.h" #include "mongo/db/auth/user_name.h" #include "mongo/db/kill_sessions_gen.h" @@ -41,8 +42,14 @@ namespace mongo { class OperationContext; class ServiceContext; -struct KillAllSessionsByPatternHash { - std::size_t operator()(const KillAllSessionsByPattern& pattern) const { +struct KillAllSessionsByPatternItem { + KillAllSessionsByPattern pattern; + APIParameters apiParameters; +}; + +struct KillAllSessionsByPatternItemHash { + std::size_t operator()(const KillAllSessionsByPatternItem& item) const { + auto& pattern = item.pattern; if (pattern.getLsid()) { return lsidHasher(*pattern.getLsid()); } else if (pattern.getUid()) { @@ -60,20 +67,22 @@ struct KillAllSessionsByPatternHash { /** * Patterns are specifically equal if they differ only by impersonate data. */ -inline bool operator==(const KillAllSessionsByPattern& lhs, const KillAllSessionsByPattern& rhs) { - auto makeEqualityLens = [](const auto& pattern) { - return std::tie(pattern.getLsid(), pattern.getUid()); +inline bool operator==(const KillAllSessionsByPatternItem& lhs, + const KillAllSessionsByPatternItem& rhs) { + auto makeEqualityLens = [](const auto& item) { + return std::tie(item.pattern.getLsid(), item.pattern.getUid(), item.apiParameters); }; return makeEqualityLens(lhs) == makeEqualityLens(rhs); } -inline bool operator!=(const KillAllSessionsByPattern& lhs, const KillAllSessionsByPattern& rhs) { +inline bool operator!=(const KillAllSessionsByPatternItem& lhs, + const KillAllSessionsByPatternItem& rhs) { return !(lhs == rhs); } using KillAllSessionsByPatternSet = - stdx::unordered_set<KillAllSessionsByPattern, KillAllSessionsByPatternHash>; + stdx::unordered_set<KillAllSessionsByPatternItem, KillAllSessionsByPatternItemHash>; std::tuple<std::vector<UserName>, std::vector<RoleName>> getKillAllSessionsByPatternImpersonateData( const KillAllSessionsByPattern& pattern); @@ -86,13 +95,13 @@ std::tuple<std::vector<UserName>, std::vector<RoleName>> getKillAllSessionsByPat /** * Constructs a kill sessions pattern which kills all sessions */ -KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx); +KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCtx); /** * Constructs a kill sessions pattern for a particular user */ -KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx, - const KillAllSessionsUser& user); +KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCtx, + const KillAllSessionsUser& user); /** * Constructs a KillAllSessionsByPatternSet, each element of which matches the UID of a user that is @@ -103,7 +112,7 @@ KillAllSessionsByPatternSet makeSessionFilterForAuthenticatedUsers(OperationCont /** * Constructs a kill sessions pattern for a particular logical session */ -KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx, - const LogicalSessionId& lsid); +KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCtx, + const LogicalSessionId& lsid); } // namespace mongo diff --git a/src/mongo/db/s/config/sharding_catalog_manager_shard_operations.cpp b/src/mongo/db/s/config/sharding_catalog_manager_shard_operations.cpp index d10ba0288d3..b82c0b908e5 100644 --- a/src/mongo/db/s/config/sharding_catalog_manager_shard_operations.cpp +++ b/src/mongo/db/s/config/sharding_catalog_manager_shard_operations.cpp @@ -43,6 +43,7 @@ #include "mongo/client/read_preference.h" #include "mongo/client/remote_command_targeter.h" #include "mongo/client/replica_set_monitor.h" +#include "mongo/db/api_parameters.h" #include "mongo/db/audit.h" #include "mongo/db/catalog_raii.h" #include "mongo/db/client.h" @@ -145,7 +146,7 @@ StatusWith<Shard::CommandResponse> ShardingCatalogManager::_runCommandForAddShar auto host = std::move(swHost.getValue()); executor::RemoteCommandRequest request( - host, dbName.toString(), cmdObj, rpc::makeEmptyMetadata(), nullptr, Seconds(30)); + host, dbName.toString(), cmdObj, rpc::makeEmptyMetadata(), opCtx, Seconds(30)); executor::RemoteCommandResponse response = Status(ErrorCodes::InternalError, "Internal error running command"); diff --git a/src/mongo/db/session_killer.cpp b/src/mongo/db/session_killer.cpp index 20ba36ac3dd..8ac4764398a 100644 --- a/src/mongo/db/session_killer.cpp +++ b/src/mongo/db/session_killer.cpp @@ -85,18 +85,19 @@ SessionKiller::ReapResult::ReapResult() : result(std::make_shared<boost::optiona SessionKiller::Matcher::Matcher(KillAllSessionsByPatternSet&& patterns) : _patterns(std::move(patterns)) { - for (const auto& pattern : _patterns) { + for (const auto& item : _patterns) { + auto& pattern = item.pattern; if (pattern.getUid()) { _uids.emplace(pattern.getUid().get(), &pattern); } else if (pattern.getLsid()) { _lsids.emplace(pattern.getLsid().get(), &pattern); } else { // If we're killing everything, it's the only pattern we care about. - decltype(_patterns) onlyKillAll{{pattern}}; + decltype(_patterns) onlyKillAll{{item}}; using std::swap; swap(_patterns, onlyKillAll); - _killAll = &(*_patterns.begin()); + _killAll = &(_patterns.begin()->pattern); break; } } @@ -177,19 +178,38 @@ void SessionKiller::_periodicKill(OperationContext* opCtx, stdx::unique_lock<Lat // Drop the lock and run the killer. lk.unlock(); - Matcher matcher(std::move(nextToReap)); - boost::optional<Result> results; - try { - results.emplace(_killFunc(opCtx, matcher, &_urbg)); - } catch (...) { - results.emplace(exceptionToStatus()); + // Group patterns with equal API parameters into sets. + stdx::unordered_map<APIParameters, KillAllSessionsByPatternSet, APIParameters::Hash> sets; + for (auto& item : nextToReap) { + sets[item.apiParameters].insert(std::move(item)); } - lk.lock(); - invariant(results); + // Use the API parameters included in each KillAllSessionsByPattern struct. + IgnoreAPIParametersBlock ignoreApiParametersBlock(opCtx); + Result finalResults{std::vector<HostAndPort>{}}; + for (auto& [apiParameters, items] : sets) { + APIParameters::get(opCtx) = apiParameters; + Matcher matcher(std::move(items)); + boost::optional<Result> results; + try { + results.emplace(_killFunc(opCtx, matcher, &_urbg)); + } catch (...) { + results.emplace(exceptionToStatus()); + } + invariant(results); + if (!results->isOK()) { + finalResults = *results; + break; + } + + finalResults.getValue().insert(finalResults.getValue().end(), + std::make_move_iterator(results->getValue().begin()), + std::make_move_iterator(results->getValue().end())); + } // Expose the results and notify callers - *(reapResults.result) = std::move(results); + lk.lock(); + *(reapResults.result) = std::move(finalResults); _callerCV.notify_all(); }; diff --git a/src/mongo/s/commands/cluster_kill_op.cpp b/src/mongo/s/commands/cluster_kill_op.cpp index 1d4927b0f79..2ce1ecf79ef 100644 --- a/src/mongo/s/commands/cluster_kill_op.cpp +++ b/src/mongo/s/commands/cluster_kill_op.cpp @@ -38,6 +38,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/client/connpool.h" +#include "mongo/db/api_parameters.h" #include "mongo/db/audit.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" @@ -111,8 +112,10 @@ private: result.append("shardid", opId); ScopedDbConnection conn(shard->getConnString()); + BSONObjBuilder bob(BSON("killOp" << 1 << "op" << opId)); + APIParameters::get(opCtx).appendInfo(&bob); // intentionally ignore return value - that is how legacy killOp worked. - conn->runCommand(OpMsgRequest::fromDBAndBody("admin", BSON("killOp" << 1 << "op" << opId))); + conn->runCommand(OpMsgRequest::fromDBAndBody("admin", bob.obj())); conn.done(); // The original behavior of killOp on mongos is to always return success, regardless of diff --git a/src/mongo/s/commands/kill_sessions_remote.cpp b/src/mongo/s/commands/kill_sessions_remote.cpp index d9b5ce26ccf..0bda8eacf61 100644 --- a/src/mongo/s/commands/kill_sessions_remote.cpp +++ b/src/mongo/s/commands/kill_sessions_remote.cpp @@ -119,9 +119,11 @@ SessionKiller::Result killSessionsRemote(OperationContext* opCtx, // Generate the kill command. KillAllSessionsByPatternCmd cmd; - cmd.setKillAllSessionsByPattern(std::vector<KillAllSessionsByPattern>{ - matcher.getPatterns().begin(), matcher.getPatterns().end()}); - + std::vector<KillAllSessionsByPattern> patterns; + for (auto& item : matcher.getPatterns()) { + patterns.push_back(std::move(item.pattern)); + } + cmd.setKillAllSessionsByPattern(std::move(patterns)); return parallelExec(opCtx, cmd.toBSON(), urbg); } |