summaryrefslogtreecommitdiff
path: root/jstests
diff options
context:
space:
mode:
Diffstat (limited to 'jstests')
-rw-r--r--jstests/sharding/api_params_nontransaction_sharded.js15
-rw-r--r--jstests/sharding/api_params_nontransaction_unsharded.js15
-rw-r--r--jstests/sharding/api_params_transaction_sharded.js15
-rw-r--r--jstests/sharding/api_params_transaction_unsharded.js15
-rw-r--r--jstests/sharding/libs/mongos_api_params_util.js1496
-rw-r--r--jstests/sharding/mongos_forwards_api_parameters_to_shards.js213
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();
-})();