/* * Utilities for checking that mongos commands forward their API version parameters to config * servers and shards. */ import {withRetryOnTransientTxnError} from "jstests/libs/auto_retry_transaction_in_sharding.js"; import {Thread} from "jstests/libs/parallelTester.js"; import {ShardTransitionUtil} from "jstests/libs/shard_transition_util.js"; import {ShardingTest} from "jstests/libs/shardingtest.js"; import {setLogVerbosity} from "jstests/replsets/rslib.js"; import { commandsAddedToMongosSinceLastLTS, commandsRemovedFromMongosSinceLastLTS, } from "jstests/sharding/libs/last_lts_mongos_commands.js"; import {removeShard} from "jstests/sharding/libs/remove_shard_util.js"; import {flushRoutersAndRefreshShardMetadata} from "jstests/sharding/libs/sharded_transactions_helpers.js"; import {isUweEnabled, mapUweShardCmdName} from "jstests/libs/query/uwe_utils.js"; // TODO SERVER-50144 Remove this and allow orphan checking. // This test calls removeShard which can leave docs in config.rangeDeletions in state "pending", // therefore preventing orphans from being cleaned up. TestData.skipCheckOrphans = true; export let MongosAPIParametersUtil = (function () { 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], ["requiresCommittedReads", false], ["requiresCatalogShardEnabled", 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.shardPrimary ? typeof testCase.shardPrimary === "function" : 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.awaitBalancerRound(); removeShard(st, shardName); assert.commandWorked(st.stopBalancer()); } function awaitTransitionToDedicatedConfigServer() { assert.commandWorked(st.startBalancer()); st.awaitBalancerRound(); ShardTransitionUtil.transitionToDedicatedConfigServer(st); 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 testCasesFirstHalf = [ {commandName: "_clusterQueryWithoutShardKey", skip: "internal API"}, {commandName: "_clusterWriteWithoutShardKey", skip: "internal API"}, {commandName: "_dropConnectionsToMongot", skip: "internal API"}, { 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: "_mongotConnPoolStats", skip: "internal API"}, {commandName: "abortMoveCollection", skip: "TODO(SERVER-108802)"}, {commandName: "abortReshardCollection", skip: "TODO(SERVER-108802)"}, {commandName: "abortRewriteCollection", skip: "TODO(SERVER-108802)"}, {commandName: "abortUnshardCollection", skip: "TODO(SERVER-108802)"}, {commandName: "analyze", skip: "TODO(SERVER-108802)"}, { commandName: "analyzeShardKey", skip: "TODO(SERVER-108802) Unclear how this is supposed to be replicated", /* run: { inAPIVersion1: false, configServerCommandName: "_configsvrCheckClusterMetadataConsistency", shardCommandName: "_shardsvrCheckMetadataConsistency", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand( {enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked( st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); for (let i = 1000; i < 10000; i++) { assert.commandWorked(st.s.getDB("db")["collection"].insertOne({_id: i})); } }, command: () => ({analyzeShardKey: "db.collection", key: {_id: 1}}), cleanUp: () => assert.commandWorked(st.s.getDB("db")["collection"].deleteMany({})) } */ }, {commandName: "appendOplogNote", skip: "TODO(SERVER-108802)"}, {commandName: "autoSplitVector", skip: "TODO(SERVER-108802)"}, { commandName: "bulkWrite", run: { inAPIVersion1: true, shardCommandName: "bulkWrite", runsAgainstAdminDb: true, command: () => ({ bulkWrite: 1, ops: [{insert: 0, document: {_id: 1}}], nsInfo: [{ns: "db.collection"}], }), }, }, {commandName: "commitShardRemoval", skip: "TODO(SERVER-108802)"}, {commandName: "commitTransitionToDedicatedConfigServer", skip: "TODO(SERVER-108802)"}, {commandName: "getAuditConfig", skip: "TODO(SERVER-108802)", conditional: true}, { commandName: "releaseMemory", run: { inAPIVersion1: false, shardCommandName: "releaseMemory", permittedInTxn: true, setUp: (context) => { assert.commandWorked( st.s0.getDB("db").runCommand({ insert: "collection", documents: [{_id: 1}, {_id: 2}, {_id: 3}, {_id: 11}, {_id: 12}, {_id: 13}], }), ); const findCmd = Object.assign({find: "collection", batchSize: 1}, context.apiParameters); const res = assert.commandWorked(context.db.runCommand(findCmd)); context.cursorId = res.cursor.id; }, command: (context) => { return {releaseMemory: [context.cursorId]}; }, }, }, {commandName: "replicateSearchIndexCommand", skip: "TODO(SERVER-108802)"}, {commandName: "shardDrainingStatus", skip: "TODO(SERVER-108802)"}, {commandName: "startShardDraining", skip: "TODO(SERVER-108802)"}, {commandName: "startTransitionToDedicatedConfigServer", skip: "TODO(SERVER-108802)"}, {commandName: "stopShardDraining", skip: "TODO(SERVER-108802)"}, {commandName: "stopTransitionToDedicatedConfigServer", skip: "TODO(SERVER-108802)"}, {commandName: "untrackUnshardedCollection", skip: "TODO(SERVER-108802)"}, { commandName: "changePrimary", skip: "TODO: Cannot run changePrimary with featureFlagBalanceUnshardedCollections disabled", }, { commandName: "checkMetadataConsistency", run: { inAPIVersion1: false, configServerCommandName: "_configsvrCheckClusterMetadataConsistency", shardCommandName: "_shardsvrCheckMetadataConsistency", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({checkMetadataConsistency: 1}), }, }, { commandName: "cleanupReshardCollection", run: { inAPIVersion1: false, configServerCommandName: "_configsvrCleanupReshardCollection", // _shardsvrCleanupReshardCollection exists in the code but is not sent to the shard // server // TODO(SERVER-108802) shardCommandName: "_shardsvrCleanupReshardCollection", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({cleanupReshardCollection: "db.collection"}), }, }, {commandName: "cleanupStructuredEncryptionData", skip: "TODO(SERVER-108802)"}, { commandName: "commitReshardCollection", skip: "TODO requires failpoints and concurrency", }, {commandName: "compactStructuredEncryptionData", skip: "TODO(SERVER-108802)"}, { commandName: "configureCollectionBalancing", run: { inAPIVersion1: false, configServerCommandName: "_configsvrConfigureCollectionBalancing", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({ configureCollectionBalancing: "db.collection", chunkSize: 256, defragmentCollection: true, enableAutoMerger: true, }), }, }, {commandName: "configureQueryAnalyzer", skip: "TODO(SERVER-108802)"}, {commandName: "coordinateCommitTransaction", skip: "TODO(SERVER-108802)"}, {commandName: "cpuload", skip: "executes locally on mongos (not sent to any remote node)"}, {commandName: "createSearchIndexes", skip: "executes locally on mongos"}, {commandName: "createUnsplittableCollection", skip: "test only and temporary"}, {commandName: "dropSearchIndex", skip: "executes locally on mongos"}, {commandName: "fsyncUnlock", skip: "TODO(SERVER-108802)"}, {commandName: "getClusterParameter", skip: "executes locally on mongos"}, { commandName: "getDatabaseVersion", skip: "executes locally on mongos (not sent to any remote node)", }, {commandName: "getTransitionToDedicatedConfigServerStatus", skip: "TODO(SERVER-108802)"}, { commandName: "getQueryableEncryptionCountInfo", run: { inAPIVersion1: false, configServerCommandName: "getQueryableEncryptionCountInfo", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, command: () => ({ getQueryableEncryptionCountInfo: "db.collection", tokens: [ { tokens: [{"s": BinData(0, "lUBO7Mov5Sb+c/D4cJ9whhhw/+PZFLCk/AQU2+BpumQ=")}], }, ], "queryType": "insert", }), }, }, {commandName: "listSearchIndexes", skip: "executes locally on mongos"}, {commandName: "lockInfo", skip: "Internal command available on mongod instances only."}, { commandName: "mergeAllChunksOnShard", run: { inAPIVersion1: false, configServerCommandName: "_configsvrCommitMergeAllChunksOnShard", shardCommandName: "_shardsvrMergeAllChunksOnShard", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({mergeAllChunksOnShard: "db.collection", shard: st.shard0.shardName}), }, }, { commandName: "moveCollection", skip: 'TODO(SERVER-108802) Primary didn\'t log _configsvrReshardCollection with API parameters { "apiVersion" : "1" }', /* run: { inAPIVersion1: false, configServerCommandName: "_configsvrReshardCollection", shardCommandName: "_shardsvrReshardCollection", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand( {enableSharding: "db", primaryShard: st.shard0.shardName})); }, command: () => ({moveCollection: "db.collection", toShard: st.shard0.shardName}) }*/ }, { commandName: "moveRange", run: { inAPIVersion1: false, configServerCommandName: "_configsvrMoveRange", shardCommandName: "_shardsvrMoveRange", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({moveRange: "db.collection", toShard: st.shard0.shardName, min: {_id: 1}}), }, }, {commandName: "oidcListKeys", skip: "TODO(SERVER-108802)", conditional: true}, {commandName: "oidcRefreshKeys", skip: "TODO(SERVER-108802)", conditional: true}, {commandName: "removeQuerySettings", skip: "TODO(SERVER-108802)"}, { commandName: "repairShardedCollectionChunksHistory", run: { inAPIVersion1: false, configServerCommandName: "_configsvrRepairShardedCollectionChunksHistory", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({repairShardedCollectionChunksHistory: "db.collection"}), }, }, { commandName: "resetPlacementHistory", // The command is expected to fail when the featureFlagChangeStreamPreciseShardTargeting is disabled. skip: "SERVER-73741 Re-enable execution once 9.0 becomes last-lts.", /* run: { inAPIVersion1: false, configServerCommandName: "_configsvrResetPlacementHistory", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, command: () => ({resetPlacementHistory: 1}), },*/ }, { commandName: "setAuditConfig", skip: "TODO(SERVER-108802) Auditing is not enabled by default", conditional: true, /* run: { inAPIVersion1: true, configServerCommandName: "setAuditConfig", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, command: () => ({setAuditConfig: 1, filter: {}, auditAuthorizationSuccess: false}) } */ }, { commandName: "setClusterParameter", skip: 'TODO(SERVER-108802): Primary didn\'t log _shardsvrSetClusterParameter with API parameters { "apiVersion" : "1" }', /* run: { inAPIVersion1: false, configServerCommandName: "_configsvrSetClusterParameter", shardCommandName: "_shardsvrSetClusterParameter", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, command: () => ({setClusterParameter: {defaultMaxTimeMS: {readOperations: 1}}}), cleanUp: () => assert.commandWorked(st.s.adminCommand( {setClusterParameter: {defaultMaxTimeMS: {readOperations: 0}}})) } */ }, { commandName: "setQuerySettings", skip: "executes locally on mongos", }, { commandName: "setUserWriteBlockMode", skip: "executes locally on mongos (not sent to any remote node)", }, {commandName: "testInternalTransactions", skip: "Internal API."}, { commandName: "unshardCollection", skip: "TODO(SERVER-108802) Unclear how this is supposed to be replicated", /*run: { inAPIVersion1: false, shardCommandName: "_shardsvrReshardCollection", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand( {enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked( st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({unshardCollection: "db.collection", toShard: st.shard0.shardName}) }*/ }, {commandName: "updateSearchIndex", skip: "executes locally on mongos"}, {commandName: "validateDBMetadata", skip: "executes locally on mongos"}, { commandName: "abortTransaction", run: { inAPIVersion1: true, runsAgainstAdminDb: true, shardCommandName: "abortTransaction", permittedInTxn: false, // We handle the transaction manually in this test. setUp: (context) => { // Start a session and transaction. const session = st.s0.startSession(); const txnOptions = {autocommit: false}; withRetryOnTransientTxnError( () => { session.startTransaction(txnOptions); const cmd = { insert: "collection", // A doc on each shard in the 2-shard configuration. documents: [{_id: 1}, {_id: 21}], }; assert.commandWorked( session.getDatabase("db").runCommand(Object.assign(cmd, context.apiParameters)), ); }, () => { session.abortTransaction(); }, ); context.session = session; }, command: (context) => ({ abortTransaction: 1, lsid: context.session.getSessionId(), txnNumber: context.session.getTxnNumber_forTesting(), autocommit: false, }), }, }, { commandName: "addShard", run: { inAPIVersion1: false, runsAgainstAdminDb: true, configServerCommandName: "_configsvrAddShard", shardCommandName: "_addShard", shardPrimary: () => { return st.rs1.getPrimary(); }, permittedInTxn: false, setUp: () => { // Remove shard0 so we can add it back. assert.commandWorked(st.s0.getDB("db").dropDatabase()); awaitRemoveShard(st.shard1.shardName); }, command: () => ({addShard: st.rs1.getURL()}), }, }, { commandName: "transitionFromDedicatedConfigServer", run: { inAPIVersion1: false, runsAgainstAdminDb: true, configServerCommandName: "_configsvrTransitionFromDedicatedConfigServer", permittedInTxn: false, requiresCatalogShardEnabled: true, setUp: () => { // Remove shard0 so we can add it back. assert.commandWorked(st.s0.getDB("db").dropDatabase()); awaitTransitionToDedicatedConfigServer(); }, command: () => ({transitionFromDedicatedConfigServer: 1}), }, }, { 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: "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: "_shardsvrCollMod", permittedInTxn: false, command: () => ({collMod: "collection"}), }, }, { commandName: "collStats", run: { inAPIVersion1: false, shardCommandName: "collStats", permittedInTxn: false, command: () => ({collStats: "collection"}), }, }, { commandName: "commitTransaction", run: { inAPIVersion1: true, runsAgainstAdminDb: true, shardCommandName: "commitTransaction", permittedInTxn: false, // We handle the transaction manually in this test. setUp: (context) => { // Start a session and transaction. const session = st.s0.startSession(); const txnOptions = {autocommit: false}; withRetryOnTransientTxnError(() => { session.startTransaction(txnOptions); const cmd = { insert: "collection", // A doc on each shard in the 2-shard configuration. documents: [{_id: 1}, {_id: 21}], }; assert.commandWorked( session.getDatabase("db").runCommand(Object.assign(cmd, context.apiParameters)), ); }); context.session = session; }, command: (context) => ({ commitTransaction: 1, lsid: context.session.getSessionId(), txnNumber: context.session.getTxnNumber_forTesting(), autocommit: false, }), }, }, {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: "_shardsvrConvertToCapped", 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: "_shardsvrCreateCollection", 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: "_shardsvrDropCollection", 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, shardCommandName: "_shardsvrDropDatabase", permittedInTxn: false, command: () => ({dropDatabase: 1}), }, }, { commandName: "dropIndexes", run: { inAPIVersion1: true, shardCommandName: "_shardsvrDropIndexes", 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: "find", run: { inAPIVersion1: true, shardCommandName: "find", setUp: function () { st.s.getDB("db")["view"].drop(); assert.commandWorked( st.s.getDB("db").runCommand({create: "view", viewOn: "collection", pipeline: []}), ); }, command: () => ({find: "view", filter: {x: 1}}), }, explain: { inAPIVersion1: true, shardCommandName: "explain", permittedInTxn: false, setUp: function () { st.s.getDB("db")["view"].drop(); assert.commandWorked( st.s.getDB("db").runCommand({create: "view", viewOn: "collection", pipeline: []}), ); }, command: () => ({explain: {find: "view", 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: "getLog", skip: "executes locally on mongos (not sent to any remote node)"}, { commandName: "getMore", run: { inAPIVersion1: true, shardCommandName: "getMore", permittedInTxn: true, setUp: (context) => { // Global setup puts one doc on each shard, we need several on each. assert.commandWorked( st.s0.getDB("db").runCommand({ insert: "collection", documents: [{_id: 1}, {_id: 2}, {_id: 3}, {_id: 11}, {_id: 12}, {_id: 13}], }), ); const findCmd = Object.assign({find: "collection", batchSize: 1}, context.apiParameters); const res = assert.commandWorked(context.db.runCommand(findCmd)); context.cursorId = res.cursor.id; }, command: (context) => { return {getMore: context.cursorId, collection: "collection"}; }, }, }, { 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: "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)"}, ]; let testCasesSecondHalf = [ {commandName: "isMaster", skip: "executes locally on mongos (not sent to any remote node)"}, { commandName: "killCursors", run: { inAPIVersion1: true, shardCommandName: "killCursors", permittedInTxn: false, // Global setup puts one doc on shard 0, we need several. setUp: () => assert.commandWorked( st.s0.getDB("db").runCommand({insert: "collection", documents: [{_id: 1}, {_id: 2}, {_id: 3}]}), ), command: () => { // Some extra logging should this test case ever fail. setLogVerbosity([st.s0, st.rs0.getPrimary(), st.rs1.getPrimary()], {"command": {"verbosity": 2}}); const res = assert.commandWorked(st.s0.getDB("db").runCommand({find: "collection", batchSize: 1})); setLogVerbosity([st.s0, st.rs0.getPrimary(), st.rs1.getPrimary()], {"command": {"verbosity": 0}}); 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, uuidStr) { const client = new Mongo(connStr); jsTestLog(`Calling find on "${connStr}" from thread,` + ` with comment ${uuidStr}`); // Target shard 0 with an _id filter. const res = client.getDB("db").runCommand({ find: "collection", filter: {_id: {$lt: 9}, $where: "sleep(99999999); return true;"}, comment: uuidStr, }); jsTestLog(`Called find command: ${tojson(res)}`); } // Some extra logging should this test case ever fail. setLogVerbosity([st.s0, st.rs0.getPrimary(), st.rs1.getPrimary()], {"command": {"verbosity": 2}}); const uuidStr = UUID().toString(); context.thread = new Thread(threadRoutine, st.s0.host, uuidStr); context.thread.start(); const adminDb = st.s0.getDB("admin"); jsTestLog( `Waiting for "find" on "${st.shard0.shardName}" ` + `with comment ${uuidStr} in currentOp`, ); assert.soon(() => { const filter = { "command.find": "collection", "command.comment": uuidStr, shard: st.shard0.shardName, }; const inprog = adminDb.currentOp(filter).inprog; if (inprog.length === 1) { jsTestLog(`Found it! findOpId ${inprog[0].opid}`); context.findOpId = inprog[0].opid; return true; } assert.lt(inprog.length, 2, `More than one command found in currentOp: ${tojson(inprog)}`); }); setLogVerbosity([st.s0, st.rs0.getPrimary(), st.rs1.getPrimary()], {"command": {"verbosity": 0}}); }, 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: 1}, }), }, 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: 1}, }, }), }, }, { commandName: "mergeChunks", run: { inAPIVersion1: false, shardCommandName: "_shardsvrMergeChunks", configServerCommandName: "_configsvrCommitChunksMerge", runsAgainstAdminDb: true, permittedInTxn: false, requiresShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({split: "db.collection", middle: {_id: -5}})); assert.commandWorked(st.s.adminCommand({split: "db.collection", middle: {_id: 10}})); // 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: "_shardsvrMoveRange", configServerCommandName: "_configsvrMoveRange", 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, 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, shardCommandName: "_shardsvrRefineCollectionShardKey", 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.shard1.shardName}), cleanUp: () => { // Wait for the shard to be removed completely before re-adding it. awaitRemoveShard(st.shard1.shardName); assert.commandWorked( st.s0.getDB("admin").runCommand({addShard: st.rs1.getURL(), name: st.shard1.shardName}), ); }, }, }, { commandName: "transitionToDedicatedConfigServer", run: { inAPIVersion1: false, runsAgainstAdminDb: true, configServerCommandName: "_configsvrTransitionToDedicatedConfigServer", permittedInTxn: false, requiresCatalogShardEnabled: true, command: () => ({transitionToDedicatedConfigServer: 1}), cleanUp: () => { // Wait for the shard to be removed completely before re-adding it. awaitTransitionToDedicatedConfigServer(st.shard0.shardName); assert.commandWorked(st.s0.getDB("admin").runCommand({transitionFromDedicatedConfigServer: 1})); }, }, }, { 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: "_shardsvrRenameCollection", permittedOnShardedCollection: false, permittedInTxn: false, runsAgainstAdminDb: true, command: () => ({renameCollection: "db.collection", to: "db.collection_renamed"}), }, }, {commandName: "replSetGetStatus", skip: "not supported in mongos"}, { commandName: "reshardCollection", run: { inAPIVersion1: false, permittedInTxn: false, shardCommandName: "_shardsvrReshardCollection", requiresShardedCollection: true, // reshardCollection internally does atClusterTime reads. requiresCommittedReads: true, runsAgainstAdminDb: true, command: () => ({reshardCollection: "db.collection", key: {_id: 1}}), }, }, { commandName: "rewriteCollection", run: { inAPIVersion1: false, permittedInTxn: false, shardCommandName: "_shardsvrReshardCollection", requiresShardedCollection: true, // rewriteCollection calls reshardCollection, which internally does atClusterTime reads. requiresCommittedReads: true, runsAgainstAdminDb: true, // Fails due to there being too few documents in the collection to meet the shard key cardinality requirement. expectedFailureCode: 4952606, command: () => ({rewriteCollection: "db.collection"}), }, }, { 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: "rotateFTDC", 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: "setAllowMigrations", run: { inAPIVersion1: false, shardCommandName: "_shardsvrSetAllowMigrations", runsAgainstAdminDb: true, permittedInTxn: false, requiresShardedCollection: true, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); }, command: () => ({setAllowMigrations: "db.collection", allowMigrations: true}), }, }, { 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, // The command should fail if there is no active index build on the collection. expectedFailureCode: ErrorCodes.IndexNotFound, command: () => ({ setIndexCommitQuorum: "collection", indexNames: ["index"], commitQuorum: "majority", }), }, }, { commandName: "setFeatureCompatibilityVersion", run: { inAPIVersion1: false, configServerCommandName: "setFeatureCompatibilityVersion", permittedInTxn: false, runsAgainstAdminDb: true, command: () => ({setFeatureCompatibilityVersion: latestFCV, confirm: true}), }, }, { commandName: "setProfilingFilterGlobally", skip: "executes locally on mongos (not sent to any remote node)", }, { commandName: "setParameter", skip: "executes locally on mongos (not sent to any remote node)", }, { commandName: "shardCollection", run: { inAPIVersion1: false, shardCommandName: "_shardsvrCreateCollection", runsAgainstAdminDb: true, permittedInTxn: false, permittedOnShardedCollection: false, setUp: () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); }, command: () => ({shardCollection: "db.collection", key: {_id: 1}}), }, }, {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 * 1024, }), }, }, {commandName: "getTrafficRecordingStatus", skip: "executes locally on targeted node"}, {commandName: "startRecordingTraffic", skip: "Renamed to startTrafficRecording"}, {commandName: "stopRecordingTraffic", skip: "Renamed to stopTrafficRecording"}, { commandName: "startTrafficRecording", 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: "stopTrafficRecording", 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) { // Since we will skip this test later, arbitrarily add it to the first half of // the test cases. testCasesFirstHalf.push({ commandName: cmd, skip: "must define test coverage for latest version backwards compatibility", }); }); const st = new ShardingTest({mongos: 1, shards: 2, config: 1, rs: {nodes: 1}}); const listCommandsRes = st.s0.adminCommand({listCommands: 1}); assert.commandWorked(listCommandsRes); const uweEnabled = isUweEnabled(st.s); const supportsCommittedReads = assert.commandWorked(st.rs0.getPrimary().adminCommand({serverStatus: 1})) .storageEngine.supportsCommittedReads; const isConfigShardEnabled = ShardTransitionUtil.isConfigServerTransitionEnabledIgnoringFCV(st); (() => { // 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 = [ ...testCasesFirstHalf.filter((elem) => elem.commandName === command), ...testCasesSecondHalf.filter((elem) => elem.commandName === command), ]; assert(matchingCases.length > 0, "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 [...testCasesFirstHalf, ...testCasesSecondHalf]) { // 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, apiParameters) { let msg; assert.soon( () => { 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 !== apiParameters.apiVersion || args.apiStrict !== apiParameters.apiStrict || args.apiDeprecationErrors !== apiParameters.apiDeprecationErrors ) continue; // Found a match. return true; } if (lastCommandInvocation === undefined) { msg = `Primary didn't log ${commandName} with API parameters ` + `${tojson(apiParameters)}.`; return false; } msg = `Primary didn't log ${commandName} with API parameters ` + `${tojson(apiParameters)}. Last invocation of ${commandName} was` + ` ${tojson(lastCommandInvocation)}`; return false; }, // assert.soon message function. () => { return msg; }, ); } // We split up the test cases into two halves in order to decrease the runtime of a single JS // test - each JS test file should either call runTestsFirstHalf or runTestsSecondHalf. function runTestsFirstHalf({inTransaction, shardedCollection}) { runTests({ inTransaction: inTransaction, shardedCollection: shardedCollection, cases: testCasesFirstHalf, }); } // We split up the test cases into two halves in order to decrease the runtime of a single JS // test - each JS test file should either call runTestsFirstHalf or runTestsSecondHalf. function runTestsSecondHalf({inTransaction, shardedCollection}) { runTests({ inTransaction: inTransaction, shardedCollection: shardedCollection, cases: testCasesSecondHalf, }); } function runTests({inTransaction, shardedCollection, cases}) { // 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 apiParameters of [ {}, {apiVersion: "1"}, {apiVersion: "1", apiDeprecationErrors: false}, {apiVersion: "1", apiDeprecationErrors: true}, {apiVersion: "1", apiStrict: false}, {apiVersion: "1", apiStrict: false, apiDeprecationErrors: false}, {apiVersion: "1", apiStrict: false, apiDeprecationErrors: true}, {apiVersion: "1", apiStrict: true}, {apiVersion: "1", apiStrict: true, apiDeprecationErrors: false}, {apiVersion: "1", apiStrict: true, apiDeprecationErrors: true}, ]) { for (const testCase of cases) { 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 (!supportsCommittedReads && runOrExplain.requiresCommittedReads) continue; if (!isConfigShardEnabled && runOrExplain.requiresCatalogShardEnabled) continue; if (apiParameters.apiStrict && !runOrExplain.inAPIVersion1) continue; testInstances.push({ apiParameters: apiParameters, commandName: testCase.commandName, runOrExplain: runOrExplain, }); } } } for (let i = 0; i < testInstances.length; ++i) { const {apiParameters, commandName, runOrExplain} = testInstances[i]; const context = {apiParameters: apiParameters}; let shardPrimary, configPrimary; withRetryOnTransientTxnError( () => { assert.commandWorked(st.s.adminCommand({enableSharding: "db", primaryShard: st.shard0.shardName})); if (shardedCollection) { assert.commandWorked(st.s.adminCommand({shardCollection: "db.collection", key: {_id: 1}})); } assert.commandWorked( st.s.getDB("db")["collection"].insert({_id: 0}, {writeConcern: {w: "majority"}}), ); configPrimary = st.configRS.getPrimary(); shardPrimary = runOrExplain.shardPrimary ? runOrExplain.shardPrimary() : st.rs0.getPrimary(); const commandDbName = runOrExplain.runsAgainstAdminDb ? "admin" : "db"; if (inTransaction) { context.session = st.s0.startSession(); context.session.startTransaction(); context.db = context.session.getDatabase(commandDbName); } else { context.db = st.s0.getDB(commandDbName); } 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 commandBody = runOrExplain.command(context); const commandWithAPIParams = Object.assign(Object.assign({}, commandBody), apiParameters); assert.commandWorked(configPrimary.adminCommand({clearLog: "global"})); assert.commandWorked(shardPrimary.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, st.rs0.getPrimary(), st.rs1.getPrimary()], { "command": {"verbosity": 2}, }); const res = context.db.runCommand(commandWithAPIParams); jsTestLog(`Command result: ${tojson(res)}`); if (runOrExplain.expectedFailureCode) { assert.commandFailedWithCode(res, runOrExplain.expectedFailureCode); } else { assert.commandWorked(res); } if (inTransaction) { const commitCmd = { commitTransaction: 1, txnNumber: context.session.getTxnNumber_forTesting(), autocommit: false, }; assert.commandWorked( context.session.getDatabase("admin").runCommand(Object.assign(commitCmd, apiParameters)), ); } }, () => { if (inTransaction) { jsTestLog(`handling transactional retry for ${commandName}`); context.session.abortTransaction(); setLogVerbosity([configPrimary, st.rs0.getPrimary(), st.rs1.getPrimary()], { "command": {"verbosity": 0}, }); st.s0.getDB("db").runCommand({dropDatabase: 1}); if (runOrExplain.cleanUp) { jsTestLog(`cleanUp function for ${commandName}`); runOrExplain.cleanUp(context); jsTestLog(`cleanUp function for ${commandName} completed`); } } }, ); const configServerCommandName = runOrExplain.configServerCommandName; if (configServerCommandName) { jsTestLog(`Check for ${configServerCommandName} in config server's log`); checkPrimaryLog(configPrimary, configServerCommandName, apiParameters); } let shardCommandName = runOrExplain.shardCommandName; if (shardCommandName) { if (uweEnabled) { shardCommandName = mapUweShardCmdName(shardCommandName); } jsTestLog(`Check for ${shardCommandName} in shard server's log`); checkPrimaryLog(shardPrimary, shardCommandName, apiParameters); } setLogVerbosity([configPrimary, st.rs0.getPrimary(), st.rs1.getPrimary()], {"command": {"verbosity": 0}}); 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 {runTestsFirstHalf: runTestsFirstHalf, runTestsSecondHalf: runTestsSecondHalf}; })();