diff options
Diffstat (limited to 'jstests')
-rw-r--r-- | jstests/sharding/api_params_nontransaction_sharded.js | 15 | ||||
-rw-r--r-- | jstests/sharding/api_params_nontransaction_unsharded.js | 15 | ||||
-rw-r--r-- | jstests/sharding/api_params_transaction_sharded.js | 15 | ||||
-rw-r--r-- | jstests/sharding/api_params_transaction_unsharded.js | 15 | ||||
-rw-r--r-- | jstests/sharding/libs/mongos_api_params_util.js | 1496 | ||||
-rw-r--r-- | jstests/sharding/mongos_forwards_api_parameters_to_shards.js | 213 |
6 files changed, 1556 insertions, 213 deletions
diff --git a/jstests/sharding/api_params_nontransaction_sharded.js b/jstests/sharding/api_params_nontransaction_sharded.js new file mode 100644 index 00000000000..fb8aa0857c2 --- /dev/null +++ b/jstests/sharding/api_params_nontransaction_sharded.js @@ -0,0 +1,15 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [ + * multiversion_incompatible, + * requires_find_command, + * ] + */ + +(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..19ad13f34a6 --- /dev/null +++ b/jstests/sharding/api_params_nontransaction_unsharded.js @@ -0,0 +1,15 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [ + * multiversion_incompatible, + * requires_find_command, + * ] + */ + +(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..9bba21c2433 --- /dev/null +++ b/jstests/sharding/api_params_transaction_sharded.js @@ -0,0 +1,15 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [ + * multiversion_incompatible, + * requires_find_command, + * ] + */ + +(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..ae24ab778dc --- /dev/null +++ b/jstests/sharding/api_params_transaction_unsharded.js @@ -0,0 +1,15 @@ +/** + * When a client calls a mongos command with API parameters, mongos must forward them to shards. + * + * @tags: [ + * multiversion_incompatible, + * requires_find_command, + * ] + */ + +(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..8f6f925663e --- /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(`"find" reply: ${tojson(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(); -})(); |