diff options
Diffstat (limited to 'jstests/sharding/aggregation_currentop.js')
-rw-r--r-- | jstests/sharding/aggregation_currentop.js | 1573 |
1 files changed, 775 insertions, 798 deletions
diff --git a/jstests/sharding/aggregation_currentop.js b/jstests/sharding/aggregation_currentop.js index 5e7ed32f09a..4973b4f2d3f 100644 --- a/jstests/sharding/aggregation_currentop.js +++ b/jstests/sharding/aggregation_currentop.js @@ -20,906 +20,883 @@ TestData.skipAwaitingReplicationOnShardsBeforeCheckingUUIDs = true; (function() { - "use strict"; - - load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. - load("jstests/libs/namespace_utils.js"); // For getCollectionNameFromFullNamespace. - - // Replica set nodes started with --shardsvr do not enable key generation until they are added - // to a sharded cluster and reject commands with gossiped clusterTime from users without the - // advanceClusterTime privilege. This causes ShardingTest setup to fail because the shell - // briefly authenticates as __system and recieves clusterTime metadata then will fail trying to - // gossip that time later in setup. - // - // TODO SERVER-32672: remove this flag. - TestData.skipGossipingClusterTime = true; +"use strict"; + +load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. +load("jstests/libs/namespace_utils.js"); // For getCollectionNameFromFullNamespace. + +// Replica set nodes started with --shardsvr do not enable key generation until they are added +// to a sharded cluster and reject commands with gossiped clusterTime from users without the +// advanceClusterTime privilege. This causes ShardingTest setup to fail because the shell +// briefly authenticates as __system and recieves clusterTime metadata then will fail trying to +// gossip that time later in setup. +// +// TODO SERVER-32672: remove this flag. +TestData.skipGossipingClusterTime = true; + +const key = "jstests/libs/key1"; + +// Parameters used to establish the sharded cluster. +const stParams = { + name: jsTestName(), + keyFile: key, + shards: 3, + rs: {nodes: 1, setParameter: {internalQueryExecYieldIterations: 1}} +}; + +// Create a new sharded cluster for testing. We set the internalQueryExecYieldIterations +// parameter so that plan execution yields on every iteration. For some tests, we will +// temporarily set yields to hang the mongod so we can capture particular operations in the +// currentOp output. +const st = new ShardingTest(stParams); + +// Assign various elements of the cluster. We will use shard rs0 to test replica-set level +// $currentOp behaviour. +let shardConn = st.rs0.getPrimary(); +let mongosConn = st.s; +let shardRS = st.rs0; + +let clusterTestDB = mongosConn.getDB(jsTestName()); +let clusterAdminDB = mongosConn.getDB("admin"); +shardConn.waitForClusterTime(60); +let shardTestDB = shardConn.getDB(jsTestName()); +let shardAdminDB = shardConn.getDB("admin"); + +function createUsers(conn) { + let adminDB = conn.getDB("admin"); + + // Create an admin user, one user with the inprog privilege, and one without. + assert.commandWorked(adminDB.runCommand({createUser: "admin", pwd: "pwd", roles: ["root"]})); + assert(adminDB.auth("admin", "pwd")); + + assert.commandWorked(adminDB.runCommand({ + createRole: "role_inprog", + roles: [], + privileges: [{resource: {cluster: true}, actions: ["inprog"]}] + })); - const key = "jstests/libs/key1"; + assert.commandWorked(adminDB.runCommand( + {createUser: "user_inprog", pwd: "pwd", roles: ["readWriteAnyDatabase", "role_inprog"]})); - // Parameters used to establish the sharded cluster. - const stParams = { - name: jsTestName(), - keyFile: key, - shards: 3, - rs: {nodes: 1, setParameter: {internalQueryExecYieldIterations: 1}} - }; + assert.commandWorked(adminDB.runCommand( + {createUser: "user_no_inprog", pwd: "pwd", roles: ["readWriteAnyDatabase"]})); +} - // Create a new sharded cluster for testing. We set the internalQueryExecYieldIterations - // parameter so that plan execution yields on every iteration. For some tests, we will - // temporarily set yields to hang the mongod so we can capture particular operations in the - // currentOp output. - const st = new ShardingTest(stParams); +// Create necessary users at both cluster and shard-local level. +createUsers(shardConn); +createUsers(mongosConn); - // Assign various elements of the cluster. We will use shard rs0 to test replica-set level - // $currentOp behaviour. - let shardConn = st.rs0.getPrimary(); - let mongosConn = st.s; - let shardRS = st.rs0; +// Create a test database and some dummy data on rs0. +assert(clusterAdminDB.auth("admin", "pwd")); - let clusterTestDB = mongosConn.getDB(jsTestName()); - let clusterAdminDB = mongosConn.getDB("admin"); - shardConn.waitForClusterTime(60); - let shardTestDB = shardConn.getDB(jsTestName()); - let shardAdminDB = shardConn.getDB("admin"); +for (let i = 0; i < 5; i++) { + assert.writeOK(clusterTestDB.test.insert({_id: i, a: i})); +} - function createUsers(conn) { - let adminDB = conn.getDB("admin"); +st.ensurePrimaryShard(clusterTestDB.getName(), shardRS.name); - // Create an admin user, one user with the inprog privilege, and one without. - assert.commandWorked( - adminDB.runCommand({createUser: "admin", pwd: "pwd", roles: ["root"]})); - assert(adminDB.auth("admin", "pwd")); - - assert.commandWorked(adminDB.runCommand({ - createRole: "role_inprog", - roles: [], - privileges: [{resource: {cluster: true}, actions: ["inprog"]}] - })); - - assert.commandWorked(adminDB.runCommand({ - createUser: "user_inprog", - pwd: "pwd", - roles: ["readWriteAnyDatabase", "role_inprog"] - })); - - assert.commandWorked(adminDB.runCommand( - {createUser: "user_no_inprog", pwd: "pwd", roles: ["readWriteAnyDatabase"]})); +// Restarts a replset with a different set of parameters. Explicitly set the keyFile to null, +// since if ReplSetTest#stopSet sees a keyFile property, it attempts to auth before dbhash +// checks. +function restartReplSet(replSet, newOpts) { + const numNodes = replSet.nodeList().length; + for (let n = 0; n < numNodes; n++) { + replSet.restart(n, newOpts); } + replSet.keyFile = newOpts.keyFile; + return replSet.getPrimary(); +} +// Restarts a cluster with a different set of parameters. +function restartCluster(st, newOpts) { + restartReplSet(st.configRS, newOpts); + for (let i = 0; i < stParams.shards; i++) { + restartReplSet(st[`rs${i}`], newOpts); + } + st.restartMongos(0, Object.assign(newOpts, {restart: true})); + st.keyFile = newOpts.keyFile; + // Re-link the cluster components. + shardConn = st.rs0.getPrimary(); + mongosConn = st.s; + shardRS = st.rs0; + clusterTestDB = mongosConn.getDB(jsTestName()); + clusterAdminDB = mongosConn.getDB("admin"); + shardTestDB = shardConn.getDB(jsTestName()); + shardAdminDB = shardConn.getDB("admin"); +} - // Create necessary users at both cluster and shard-local level. - createUsers(shardConn); - createUsers(mongosConn); +function runCommandOnAllPrimaries({dbName, cmdObj, username, password}) { + for (let i = 0; i < stParams.shards; i++) { + const rsAdminDB = st[`rs${i}`].getPrimary().getDB("admin"); + rsAdminDB.auth(username, password); + assert.commandWorked(rsAdminDB.getSiblingDB(dbName).runCommand(cmdObj)); + } +} + +// Functions to support running an operation in a parallel shell for testing allUsers behaviour. +function runInParallelShell({conn, testfunc, username, password}) { + TestData.aggCurOpTest = testfunc; + TestData.aggCurOpUser = username; + TestData.aggCurOpPwd = password; + + runCommandOnAllPrimaries({ + dbName: "admin", + username: username, + password: password, + cmdObj: {configureFailPoint: "setYieldAllLocksHang", mode: "alwaysOn"} + }); + + testfunc = function() { + db.getSiblingDB("admin").auth(TestData.aggCurOpUser, TestData.aggCurOpPwd); + TestData.aggCurOpTest(); + db.getSiblingDB("admin").logout(); + }; - // Create a test database and some dummy data on rs0. - assert(clusterAdminDB.auth("admin", "pwd")); + return startParallelShell(testfunc, conn.port); +} - for (let i = 0; i < 5; i++) { - assert.writeOK(clusterTestDB.test.insert({_id: i, a: i})); - } +function assertCurrentOpHasSingleMatchingEntry({conn, currentOpAggFilter, curOpSpec}) { + curOpSpec = (curOpSpec || {allUsers: true}); - st.ensurePrimaryShard(clusterTestDB.getName(), shardRS.name); + const connAdminDB = conn.getDB("admin"); - // Restarts a replset with a different set of parameters. Explicitly set the keyFile to null, - // since if ReplSetTest#stopSet sees a keyFile property, it attempts to auth before dbhash - // checks. - function restartReplSet(replSet, newOpts) { - const numNodes = replSet.nodeList().length; - for (let n = 0; n < numNodes; n++) { - replSet.restart(n, newOpts); - } - replSet.keyFile = newOpts.keyFile; - return replSet.getPrimary(); - } - // Restarts a cluster with a different set of parameters. - function restartCluster(st, newOpts) { - restartReplSet(st.configRS, newOpts); - for (let i = 0; i < stParams.shards; i++) { - restartReplSet(st[`rs${i}`], newOpts); - } - st.restartMongos(0, Object.assign(newOpts, {restart: true})); - st.keyFile = newOpts.keyFile; - // Re-link the cluster components. - shardConn = st.rs0.getPrimary(); - mongosConn = st.s; - shardRS = st.rs0; - clusterTestDB = mongosConn.getDB(jsTestName()); - clusterAdminDB = mongosConn.getDB("admin"); - shardTestDB = shardConn.getDB(jsTestName()); - shardAdminDB = shardConn.getDB("admin"); - } + let curOpResult; - function runCommandOnAllPrimaries({dbName, cmdObj, username, password}) { - for (let i = 0; i < stParams.shards; i++) { - const rsAdminDB = st[`rs${i}`].getPrimary().getDB("admin"); - rsAdminDB.auth(username, password); - assert.commandWorked(rsAdminDB.getSiblingDB(dbName).runCommand(cmdObj)); - } - } + assert.soon( + function() { + curOpResult = + connAdminDB.aggregate([{$currentOp: curOpSpec}, {$match: currentOpAggFilter}]) + .toArray(); - // Functions to support running an operation in a parallel shell for testing allUsers behaviour. - function runInParallelShell({conn, testfunc, username, password}) { - TestData.aggCurOpTest = testfunc; - TestData.aggCurOpUser = username; - TestData.aggCurOpPwd = password; - - runCommandOnAllPrimaries({ - dbName: "admin", - username: username, - password: password, - cmdObj: {configureFailPoint: "setYieldAllLocksHang", mode: "alwaysOn"} + return curOpResult.length === 1; + }, + function() { + return "Failed to find operation " + tojson(currentOpAggFilter) + + " in $currentOp output: " + tojson(curOpResult); }); - testfunc = function() { - db.getSiblingDB("admin").auth(TestData.aggCurOpUser, TestData.aggCurOpPwd); - TestData.aggCurOpTest(); - db.getSiblingDB("admin").logout(); - }; - - return startParallelShell(testfunc, conn.port); - } + return curOpResult[0]; +} - function assertCurrentOpHasSingleMatchingEntry({conn, currentOpAggFilter, curOpSpec}) { - curOpSpec = (curOpSpec || {allUsers: true}); +function waitForParallelShell({conn, username, password, awaitShell}) { + runCommandOnAllPrimaries({ + dbName: "admin", + username: username, + password: password, + cmdObj: {configureFailPoint: "setYieldAllLocksHang", mode: "off"} + }); - const connAdminDB = conn.getDB("admin"); + awaitShell(); +} + +// Generic function for running getMore on a $currentOp aggregation cursor and returning the +// command response. +function getMoreTest({conn, curOpSpec, getMoreBatchSize}) { + // Ensure that there are some other connections present so that the result set is larger + // than 1 $currentOp entry. + const otherConns = [new Mongo(conn.host), new Mongo(conn.host)]; + curOpSpec = Object.assign({idleConnections: true}, (curOpSpec || {})); + + // Log the other connections in as user_no_inprog so that they will show up for user_inprog + // with {allUsers: true} and user_no_inprog with {allUsers: false}. + for (let otherConn of otherConns) { + assert(otherConn.getDB("admin").auth("user_no_inprog", "pwd")); + } - let curOpResult; + const connAdminDB = conn.getDB("admin"); - assert.soon( - function() { - curOpResult = - connAdminDB.aggregate([{$currentOp: curOpSpec}, {$match: currentOpAggFilter}]) - .toArray(); + const aggCmdRes = assert.commandWorked(connAdminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: curOpSpec}], cursor: {batchSize: 0}})); + assert.neq(aggCmdRes.cursor.id, 0); - return curOpResult.length === 1; - }, - function() { - return "Failed to find operation " + tojson(currentOpAggFilter) + - " in $currentOp output: " + tojson(curOpResult); - }); + return connAdminDB.runCommand({ + getMore: aggCmdRes.cursor.id, + collection: getCollectionNameFromFullNamespace(aggCmdRes.cursor.ns), + batchSize: (getMoreBatchSize || 100) + }); +} - return curOpResult[0]; - } +// +// Common tests. +// - function waitForParallelShell({conn, username, password, awaitShell}) { - runCommandOnAllPrimaries({ - dbName: "admin", - username: username, - password: password, - cmdObj: {configureFailPoint: "setYieldAllLocksHang", mode: "off"} - }); +// Runs a suite of tests for behaviour common to both the replica set and cluster levels. +function runCommonTests(conn, curOpSpec) { + const testDB = conn.getDB(jsTestName()); + const adminDB = conn.getDB("admin"); + curOpSpec = (curOpSpec || {}); - awaitShell(); + function addToSpec(spec) { + return Object.assign({}, curOpSpec, spec); } - // Generic function for running getMore on a $currentOp aggregation cursor and returning the - // command response. - function getMoreTest({conn, curOpSpec, getMoreBatchSize}) { - // Ensure that there are some other connections present so that the result set is larger - // than 1 $currentOp entry. - const otherConns = [new Mongo(conn.host), new Mongo(conn.host)]; - curOpSpec = Object.assign({idleConnections: true}, (curOpSpec || {})); - - // Log the other connections in as user_no_inprog so that they will show up for user_inprog - // with {allUsers: true} and user_no_inprog with {allUsers: false}. - for (let otherConn of otherConns) { - assert(otherConn.getDB("admin").auth("user_no_inprog", "pwd")); - } + const isLocalMongosCurOp = (conn == mongosConn && curOpSpec.localOps); + const isRemoteShardCurOp = (conn == mongosConn && !curOpSpec.localOps); - const connAdminDB = conn.getDB("admin"); + // Test that an unauthenticated connection cannot run $currentOp even with {allUsers: + // false}. + assert(adminDB.logout()); - const aggCmdRes = assert.commandWorked(connAdminDB.runCommand( - {aggregate: 1, pipeline: [{$currentOp: curOpSpec}], cursor: {batchSize: 0}})); - assert.neq(aggCmdRes.cursor.id, 0); + assert.commandFailedWithCode( + adminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: addToSpec({allUsers: false})}], cursor: {}}), + ErrorCodes.Unauthorized); - return connAdminDB.runCommand({ - getMore: aggCmdRes.cursor.id, - collection: getCollectionNameFromFullNamespace(aggCmdRes.cursor.ns), - batchSize: (getMoreBatchSize || 100) - }); - } + // Test that an unauthenticated connection cannot run the currentOp command even with + // {$ownOps: true}. + assert.commandFailedWithCode(adminDB.currentOp({$ownOps: true}), ErrorCodes.Unauthorized); // - // Common tests. + // Authenticate as user_no_inprog. // + assert(adminDB.logout()); + assert(adminDB.auth("user_no_inprog", "pwd")); - // Runs a suite of tests for behaviour common to both the replica set and cluster levels. - function runCommonTests(conn, curOpSpec) { - const testDB = conn.getDB(jsTestName()); - const adminDB = conn.getDB("admin"); - curOpSpec = (curOpSpec || {}); + // Test that $currentOp fails with {allUsers: true} for a user without the "inprog" + // privilege. + assert.commandFailedWithCode( + adminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: addToSpec({allUsers: true})}], cursor: {}}), + ErrorCodes.Unauthorized); - function addToSpec(spec) { - return Object.assign({}, curOpSpec, spec); - } + // Test that the currentOp command fails with {ownOps: false} for a user without the + // "inprog" privilege. + assert.commandFailedWithCode(adminDB.currentOp({$ownOps: false}), ErrorCodes.Unauthorized); - const isLocalMongosCurOp = (conn == mongosConn && curOpSpec.localOps); - const isRemoteShardCurOp = (conn == mongosConn && !curOpSpec.localOps); - - // Test that an unauthenticated connection cannot run $currentOp even with {allUsers: - // false}. - assert(adminDB.logout()); - - assert.commandFailedWithCode( - adminDB.runCommand( - {aggregate: 1, pipeline: [{$currentOp: addToSpec({allUsers: false})}], cursor: {}}), - ErrorCodes.Unauthorized); - - // Test that an unauthenticated connection cannot run the currentOp command even with - // {$ownOps: true}. - assert.commandFailedWithCode(adminDB.currentOp({$ownOps: true}), ErrorCodes.Unauthorized); - - // - // Authenticate as user_no_inprog. - // - assert(adminDB.logout()); - assert(adminDB.auth("user_no_inprog", "pwd")); - - // Test that $currentOp fails with {allUsers: true} for a user without the "inprog" - // privilege. - assert.commandFailedWithCode( - adminDB.runCommand( - {aggregate: 1, pipeline: [{$currentOp: addToSpec({allUsers: true})}], cursor: {}}), - ErrorCodes.Unauthorized); - - // Test that the currentOp command fails with {ownOps: false} for a user without the - // "inprog" privilege. - assert.commandFailedWithCode(adminDB.currentOp({$ownOps: false}), ErrorCodes.Unauthorized); - - // Test that {aggregate: 1} fails when the first stage in the pipeline is not $currentOp. - assert.commandFailedWithCode( - adminDB.runCommand({aggregate: 1, pipeline: [{$match: {}}], cursor: {}}), - ErrorCodes.InvalidNamespace); - - // - // Authenticate as user_inprog. - // - assert(adminDB.logout()); - assert(adminDB.auth("user_inprog", "pwd")); - - // Test that $currentOp fails when it is not the first stage in the pipeline. We use two - // $currentOp stages since any other stage in the initial position will trip the {aggregate: - // 1} namespace check. - assert.commandFailedWithCode( - adminDB.runCommand( - {aggregate: 1, pipeline: [{$currentOp: {}}, {$currentOp: curOpSpec}], cursor: {}}), - 40602); - - // Test that $currentOp fails when run on admin without {aggregate: 1}. - assert.commandFailedWithCode( - adminDB.runCommand( - {aggregate: "collname", pipeline: [{$currentOp: curOpSpec}], cursor: {}}), - ErrorCodes.InvalidNamespace); - - // Test that $currentOp fails when run as {aggregate: 1} on a database other than admin. - assert.commandFailedWithCode( - testDB.runCommand({aggregate: 1, pipeline: [{$currentOp: curOpSpec}], cursor: {}}), - ErrorCodes.InvalidNamespace); - - // Test that the currentOp command fails when run directly on a database other than admin. - assert.commandFailedWithCode(testDB.runCommand({currentOp: 1}), ErrorCodes.Unauthorized); - - // Test that the currentOp command helper succeeds when run on a database other than admin. - // This is because the currentOp shell helper redirects the command to the admin database. - assert.commandWorked(testDB.currentOp()); - - // Test that $currentOp and the currentOp command accept all numeric types. - const ones = [1, 1.0, NumberInt(1), NumberLong(1), NumberDecimal(1)]; - - for (let one of ones) { - assert.commandWorked(adminDB.runCommand( - {aggregate: one, pipeline: [{$currentOp: curOpSpec}], cursor: {}})); - - assert.commandWorked(adminDB.runCommand({currentOp: one, $ownOps: true})); - } + // Test that {aggregate: 1} fails when the first stage in the pipeline is not $currentOp. + assert.commandFailedWithCode( + adminDB.runCommand({aggregate: 1, pipeline: [{$match: {}}], cursor: {}}), + ErrorCodes.InvalidNamespace); - // Test that $currentOp with {allUsers: true} succeeds for a user with the "inprog" - // privilege. - assert.commandWorked(adminDB.runCommand( - {aggregate: 1, pipeline: [{$currentOp: addToSpec({allUsers: true})}], cursor: {}})); - - // Test that the currentOp command with {$ownOps: false} succeeds for a user with the - // "inprog" privilege. - assert.commandWorked(adminDB.currentOp({$ownOps: false})); - - // Test that $currentOp succeeds if local readConcern is specified. - assert.commandWorked(adminDB.runCommand({ - aggregate: 1, - pipeline: [{$currentOp: curOpSpec}], - readConcern: {level: "local"}, - cursor: {} - })); - - // Test that $currentOp fails if a non-local readConcern is specified for any data-bearing - // target. - const linearizableAggCmd = { - aggregate: 1, - pipeline: [{$currentOp: curOpSpec}], - readConcern: {level: "linearizable"}, - cursor: {} - }; - assert.commandFailedWithCode(adminDB.runCommand(linearizableAggCmd), - ErrorCodes.InvalidOptions); - - // Test that {idleConnections: false} returns only active connections. - const idleConn = new Mongo(conn.host); - - assert.eq(adminDB - .aggregate([ - {$currentOp: addToSpec({allUsers: true, idleConnections: false})}, - {$match: {active: false}} - ]) - .itcount(), - 0); + // + // Authenticate as user_inprog. + // + assert(adminDB.logout()); + assert(adminDB.auth("user_inprog", "pwd")); - // Test that the currentOp command with {$all: false} returns only active connections. - assert.eq(adminDB.currentOp({$ownOps: false, $all: false, active: false}).inprog.length, 0); + // Test that $currentOp fails when it is not the first stage in the pipeline. We use two + // $currentOp stages since any other stage in the initial position will trip the {aggregate: + // 1} namespace check. + assert.commandFailedWithCode( + adminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: {}}, {$currentOp: curOpSpec}], cursor: {}}), + 40602); - // Test that {idleConnections: true} returns inactive connections. - assert.gte(adminDB - .aggregate([ - {$currentOp: addToSpec({allUsers: true, idleConnections: true})}, - {$match: {active: false}} - ]) - .itcount(), - 1); + // Test that $currentOp fails when run on admin without {aggregate: 1}. + assert.commandFailedWithCode( + adminDB.runCommand( + {aggregate: "collname", pipeline: [{$currentOp: curOpSpec}], cursor: {}}), + ErrorCodes.InvalidNamespace); - // Test that the currentOp command with {$all: true} returns inactive connections. - assert.gte(adminDB.currentOp({$ownOps: false, $all: true, active: false}).inprog.length, 1); + // Test that $currentOp fails when run as {aggregate: 1} on a database other than admin. + assert.commandFailedWithCode( + testDB.runCommand({aggregate: 1, pipeline: [{$currentOp: curOpSpec}], cursor: {}}), + ErrorCodes.InvalidNamespace); - // Test that collation rules apply to matches on $currentOp output. - const matchField = - (isRemoteShardCurOp ? "cursor.originatingCommand.comment" : "command.comment"); - const numExpectedMatches = (isRemoteShardCurOp ? stParams.shards : 1); + // Test that the currentOp command fails when run directly on a database other than admin. + assert.commandFailedWithCode(testDB.runCommand({currentOp: 1}), ErrorCodes.Unauthorized); - assert.eq( - adminDB - .aggregate( - [{$currentOp: curOpSpec}, {$match: {[matchField]: "AGG_currént_op_COLLATION"}}], - { - collation: {locale: "en_US", strength: 1}, // Case and diacritic insensitive. - comment: "agg_current_op_collation" - }) - .itcount(), - numExpectedMatches); - - // Test that $currentOp output can be processed by $facet subpipelines. - assert.eq(adminDB - .aggregate( - [ - {$currentOp: curOpSpec}, - { - $facet: { - testFacet: [ - {$match: {[matchField]: "agg_current_op_facets"}}, - {$count: "count"} - ] - } - }, - {$unwind: "$testFacet"}, - {$replaceRoot: {newRoot: "$testFacet"}} - ], - {comment: "agg_current_op_facets"}) - .next() - .count, - numExpectedMatches); - - // Test that $currentOp is explainable. - const explainPlan = assert.commandWorked(adminDB.runCommand({ - aggregate: 1, - pipeline: [ - {$currentOp: addToSpec({idleConnections: true, allUsers: false})}, - {$match: {desc: "test"}} - ], - explain: true - })); - - let expectedStages = - [{$currentOp: {idleConnections: true}}, {$match: {desc: {$eq: "test"}}}]; - - if (isRemoteShardCurOp) { - assert.docEq(explainPlan.splitPipeline.shardsPart, expectedStages); - for (let i = 0; i < stParams.shards; i++) { - let shardName = st["rs" + i].name; - assert.docEq(explainPlan.shards[shardName].stages, expectedStages); - } - } else if (isLocalMongosCurOp) { - expectedStages[0].$currentOp.localOps = true; - assert.docEq(explainPlan.mongos.stages, expectedStages); - } else { - assert.docEq(explainPlan.stages, expectedStages); - } + // Test that the currentOp command helper succeeds when run on a database other than admin. + // This is because the currentOp shell helper redirects the command to the admin database. + assert.commandWorked(testDB.currentOp()); - // Test that a user with the inprog privilege can run getMore on a $currentOp aggregation - // cursor which they created with {allUsers: true}. - let getMoreCmdRes = assert.commandWorked( - getMoreTest({conn: conn, curOpSpec: {allUsers: true}, getMoreBatchSize: 1})); - - // Test that a user without the inprog privilege cannot run getMore on a $currentOp - // aggregation cursor created by a user with {allUsers: true}. - assert(adminDB.logout()); - assert(adminDB.auth("user_no_inprog", "pwd")); - - assert.neq(getMoreCmdRes.cursor.id, 0); - assert.commandFailedWithCode(adminDB.runCommand({ - getMore: getMoreCmdRes.cursor.id, - collection: getCollectionNameFromFullNamespace(getMoreCmdRes.cursor.ns), - batchSize: 100 - }), - ErrorCodes.Unauthorized); - } + // Test that $currentOp and the currentOp command accept all numeric types. + const ones = [1, 1.0, NumberInt(1), NumberLong(1), NumberDecimal(1)]; - // Run the common tests on a shard, through mongoS, and on mongoS with 'localOps' enabled. - runCommonTests(shardConn); - runCommonTests(mongosConn); - runCommonTests(mongosConn, {localOps: true}); + for (let one of ones) { + assert.commandWorked( + adminDB.runCommand({aggregate: one, pipeline: [{$currentOp: curOpSpec}], cursor: {}})); - // - // mongoS specific tests. - // + assert.commandWorked(adminDB.runCommand({currentOp: one, $ownOps: true})); + } - // Test that a user without the inprog privilege cannot run non-local $currentOp via mongoS even - // if allUsers is false. - assert(clusterAdminDB.logout()); - assert(clusterAdminDB.auth("user_no_inprog", "pwd")); + // Test that $currentOp with {allUsers: true} succeeds for a user with the "inprog" + // privilege. + assert.commandWorked(adminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: addToSpec({allUsers: true})}], cursor: {}})); - assert.commandFailedWithCode( - clusterAdminDB.runCommand( - {aggregate: 1, pipeline: [{$currentOp: {allUsers: false}}], cursor: {}}), - ErrorCodes.Unauthorized); + // Test that the currentOp command with {$ownOps: false} succeeds for a user with the + // "inprog" privilege. + assert.commandWorked(adminDB.currentOp({$ownOps: false})); - // Test that a user without the inprog privilege cannot run non-local currentOp command via - // mongoS even if $ownOps is true. - assert.commandFailedWithCode(clusterAdminDB.currentOp({$ownOps: true}), - ErrorCodes.Unauthorized); + // Test that $currentOp succeeds if local readConcern is specified. + assert.commandWorked(adminDB.runCommand({ + aggregate: 1, + pipeline: [{$currentOp: curOpSpec}], + readConcern: {level: "local"}, + cursor: {} + })); - // Test that a non-local $currentOp pipeline via mongoS returns results from all shards, and - // includes both the shard and host names. - assert(clusterAdminDB.logout()); - assert(clusterAdminDB.auth("user_inprog", "pwd")); + // Test that $currentOp fails if a non-local readConcern is specified for any data-bearing + // target. + const linearizableAggCmd = { + aggregate: 1, + pipeline: [{$currentOp: curOpSpec}], + readConcern: {level: "linearizable"}, + cursor: {} + }; + assert.commandFailedWithCode(adminDB.runCommand(linearizableAggCmd), ErrorCodes.InvalidOptions); - assert.eq(clusterAdminDB + // Test that {idleConnections: false} returns only active connections. + const idleConn = new Mongo(conn.host); + + assert.eq(adminDB .aggregate([ - {$currentOp: {allUsers: true, idleConnections: true}}, - {$group: {_id: {shard: "$shard", host: "$host"}}}, - {$sort: {_id: 1}} + {$currentOp: addToSpec({allUsers: true, idleConnections: false})}, + {$match: {active: false}} ]) - .toArray(), - [ - {_id: {shard: "aggregation_currentop-rs0", host: st.rs0.getPrimary().host}}, - {_id: {shard: "aggregation_currentop-rs1", host: st.rs1.getPrimary().host}}, - {_id: {shard: "aggregation_currentop-rs2", host: st.rs2.getPrimary().host}} - ]); - - // Test that a $currentOp pipeline with {localOps:true} returns operations from the mongoS - // itself rather than the shards. - assert.eq(clusterAdminDB - .aggregate( - [ - {$currentOp: {localOps: true}}, - { - $match: { - $expr: {$eq: ["$host", "$clientMetadata.mongos.host"]}, - "command.comment": "mongos_currentop_localOps" - } - } - ], - {comment: "mongos_currentop_localOps"}) .itcount(), - 1); + 0); - // - // localOps tests. - // + // Test that the currentOp command with {$all: false} returns only active connections. + assert.eq(adminDB.currentOp({$ownOps: false, $all: false, active: false}).inprog.length, 0); - // Runs a suite of tests for behaviour common to both replica sets and mongoS with - // {localOps:true}. - function runLocalOpsTests(conn) { - // The 'localOps' parameter is not supported by the currentOp command, so we limit its - // testing to the replica set in certain cases. - const connAdminDB = conn.getDB("admin"); - const isMongos = FixtureHelpers.isMongos(connAdminDB); - - // Test that a user with the inprog privilege can see another user's ops with - // {allUsers:true}. - assert(connAdminDB.logout()); - assert(connAdminDB.auth("user_inprog", "pwd")); - - let awaitShell = runInParallelShell({ - testfunc: function() { - assert.eq(db.getSiblingDB(jsTestName()) - .test.find({}) - .comment("agg_current_op_allusers_test") - .itcount(), - 5); - }, - conn: conn, - username: "admin", - password: "pwd" - }); + // Test that {idleConnections: true} returns inactive connections. + assert.gte(adminDB + .aggregate([ + {$currentOp: addToSpec({allUsers: true, idleConnections: true})}, + {$match: {active: false}} + ]) + .itcount(), + 1); - assertCurrentOpHasSingleMatchingEntry({ - conn: conn, - currentOpAggFilter: {"command.comment": "agg_current_op_allusers_test"}, - curOpSpec: {allUsers: true, localOps: true} - }); + // Test that the currentOp command with {$all: true} returns inactive connections. + assert.gte(adminDB.currentOp({$ownOps: false, $all: true, active: false}).inprog.length, 1); - // Test that the currentOp command can see another user's operations with {$ownOps: false}. - // Only test on a replica set since 'localOps' isn't supported by the currentOp command. - if (!isMongos) { - assert.eq( - connAdminDB - .currentOp({$ownOps: false, "command.comment": "agg_current_op_allusers_test"}) - .inprog.length, - 1); - } + // Test that collation rules apply to matches on $currentOp output. + const matchField = + (isRemoteShardCurOp ? "cursor.originatingCommand.comment" : "command.comment"); + const numExpectedMatches = (isRemoteShardCurOp ? stParams.shards : 1); - // Test that $currentOp succeeds with {allUsers: false} for a user without the "inprog" - // privilege. - assert(connAdminDB.logout()); - assert(connAdminDB.auth("user_no_inprog", "pwd")); - - assert.commandWorked(connAdminDB.runCommand({ - aggregate: 1, - pipeline: [{$currentOp: {allUsers: false, localOps: true}}], - cursor: {} - })); - - // Test that the currentOp command succeeds with {$ownOps: true} for a user without the - // "inprog" privilege. Because currentOp does not support the 'localOps' parameter, we only - // perform this test in the replica set case. - if (!isMongos) { - assert.commandWorked(connAdminDB.currentOp({$ownOps: true})); - } - - // Test that a user without the inprog privilege cannot see another user's operations. - assert.eq(connAdminDB - .aggregate([ - {$currentOp: {allUsers: false, localOps: true}}, - {$match: {"command.comment": "agg_current_op_allusers_test"}} - ]) - .itcount(), - 0); - - // Test that a user without the inprog privilege cannot see another user's operations via - // the currentOp command. Limit this test to the replica set case due to the absence of a - // 'localOps' parameter for the currentOp command. - if (!isMongos) { - assert.eq( - connAdminDB - .currentOp({$ownOps: true, "command.comment": "agg_current_op_allusers_test"}) - .inprog.length, - 0); - } - - // Release the failpoint and wait for the parallel shell to complete. - waitForParallelShell( - {conn: conn, username: "admin", password: "pwd", awaitShell: awaitShell}); - - // Test that a user without the inprog privilege can run getMore on a $currentOp cursor - // which they created with {allUsers: false}. - assert.commandWorked( - getMoreTest({conn: conn, curOpSpec: {allUsers: false, localOps: true}})); - } + assert.eq( + adminDB + .aggregate( + [{$currentOp: curOpSpec}, {$match: {[matchField]: "AGG_currént_op_COLLATION"}}], { + collation: {locale: "en_US", strength: 1}, // Case and diacritic insensitive. + comment: "agg_current_op_collation" + }) + .itcount(), + numExpectedMatches); - // Run the localOps tests for both replset and mongoS. - runLocalOpsTests(mongosConn); - runLocalOpsTests(shardConn); + // Test that $currentOp output can be processed by $facet subpipelines. + assert.eq(adminDB + .aggregate( + [ + {$currentOp: curOpSpec}, + { + $facet: { + testFacet: [ + {$match: {[matchField]: "agg_current_op_facets"}}, + {$count: "count"} + ] + } + }, + {$unwind: "$testFacet"}, + {$replaceRoot: {newRoot: "$testFacet"}} + ], + {comment: "agg_current_op_facets"}) + .next() + .count, + numExpectedMatches); - // - // Stashed transactions tests. - // + // Test that $currentOp is explainable. + const explainPlan = assert.commandWorked(adminDB.runCommand({ + aggregate: 1, + pipeline: [ + {$currentOp: addToSpec({idleConnections: true, allUsers: false})}, + {$match: {desc: "test"}} + ], + explain: true + })); - // Test that $currentOp will display stashed transaction locks if 'idleSessions' is true, and - // will only permit a user to view other users' sessions if the caller possesses the 'inprog' - // privilege and 'allUsers' is true. - const userNames = ["user_inprog", "admin", "user_no_inprog"]; - let sessionDBs = []; - let sessions = []; - - // Returns a set of predicates that filter $currentOp for all stashed transactions. - function sessionFilter() { - return { - type: "idleSession", - active: false, - opid: {$exists: false}, - desc: "inactive transaction", - "lsid.id": {$in: sessions.map((session) => session.getSessionId().id)}, - "transaction.parameters.txnNumber": {$gte: 0, $lt: sessions.length}, - }; - } + let expectedStages = [{$currentOp: {idleConnections: true}}, {$match: {desc: {$eq: "test"}}}]; - for (let i in userNames) { - shardAdminDB.logout(); - assert(shardAdminDB.auth(userNames[i], "pwd")); - - // Create a session for this user. - const session = shardAdminDB.getMongo().startSession(); - - // For each session, start but do not complete a transaction. - const sessionDB = session.getDatabase(shardTestDB.getName()); - assert.commandWorked(sessionDB.runCommand({ - insert: "test", - documents: [{_id: `txn-insert-${userNames[i]}-${i}`}], - readConcern: {level: "snapshot"}, - txnNumber: NumberLong(i), - startTransaction: true, - autocommit: false - })); - sessionDBs.push(sessionDB); - sessions.push(session); - - // Use $currentOp to confirm that the incomplete transactions have stashed their locks while - // inactive, and that each user can only view their own sessions with 'allUsers:false'. - assert.eq(shardAdminDB - .aggregate([ - {$currentOp: {allUsers: false, idleSessions: true}}, - {$match: sessionFilter()} - ]) - .itcount(), - 1); + if (isRemoteShardCurOp) { + assert.docEq(explainPlan.splitPipeline.shardsPart, expectedStages); + for (let i = 0; i < stParams.shards; i++) { + let shardName = st["rs" + i].name; + assert.docEq(explainPlan.shards[shardName].stages, expectedStages); + } + } else if (isLocalMongosCurOp) { + expectedStages[0].$currentOp.localOps = true; + assert.docEq(explainPlan.mongos.stages, expectedStages); + } else { + assert.docEq(explainPlan.stages, expectedStages); } - // Log in as 'user_no_inprog' to verify that the user cannot view other users' sessions via - // 'allUsers:true'. - shardAdminDB.logout(); - assert(shardAdminDB.auth("user_no_inprog", "pwd")); - - assert.commandFailedWithCode(shardAdminDB.runCommand({ - aggregate: 1, - cursor: {}, - pipeline: [{$currentOp: {allUsers: true, idleSessions: true}}, {$match: sessionFilter()}] + // Test that a user with the inprog privilege can run getMore on a $currentOp aggregation + // cursor which they created with {allUsers: true}. + let getMoreCmdRes = assert.commandWorked( + getMoreTest({conn: conn, curOpSpec: {allUsers: true}, getMoreBatchSize: 1})); + + // Test that a user without the inprog privilege cannot run getMore on a $currentOp + // aggregation cursor created by a user with {allUsers: true}. + assert(adminDB.logout()); + assert(adminDB.auth("user_no_inprog", "pwd")); + + assert.neq(getMoreCmdRes.cursor.id, 0); + assert.commandFailedWithCode(adminDB.runCommand({ + getMore: getMoreCmdRes.cursor.id, + collection: getCollectionNameFromFullNamespace(getMoreCmdRes.cursor.ns), + batchSize: 100 }), ErrorCodes.Unauthorized); +} + +// Run the common tests on a shard, through mongoS, and on mongoS with 'localOps' enabled. +runCommonTests(shardConn); +runCommonTests(mongosConn); +runCommonTests(mongosConn, {localOps: true}); + +// +// mongoS specific tests. +// + +// Test that a user without the inprog privilege cannot run non-local $currentOp via mongoS even +// if allUsers is false. +assert(clusterAdminDB.logout()); +assert(clusterAdminDB.auth("user_no_inprog", "pwd")); + +assert.commandFailedWithCode( + clusterAdminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: {allUsers: false}}], cursor: {}}), + ErrorCodes.Unauthorized); + +// Test that a user without the inprog privilege cannot run non-local currentOp command via +// mongoS even if $ownOps is true. +assert.commandFailedWithCode(clusterAdminDB.currentOp({$ownOps: true}), ErrorCodes.Unauthorized); + +// Test that a non-local $currentOp pipeline via mongoS returns results from all shards, and +// includes both the shard and host names. +assert(clusterAdminDB.logout()); +assert(clusterAdminDB.auth("user_inprog", "pwd")); + +assert.eq(clusterAdminDB + .aggregate([ + {$currentOp: {allUsers: true, idleConnections: true}}, + {$group: {_id: {shard: "$shard", host: "$host"}}}, + {$sort: {_id: 1}} + ]) + .toArray(), + [ + {_id: {shard: "aggregation_currentop-rs0", host: st.rs0.getPrimary().host}}, + {_id: {shard: "aggregation_currentop-rs1", host: st.rs1.getPrimary().host}}, + {_id: {shard: "aggregation_currentop-rs2", host: st.rs2.getPrimary().host}} + ]); + +// Test that a $currentOp pipeline with {localOps:true} returns operations from the mongoS +// itself rather than the shards. +assert.eq(clusterAdminDB + .aggregate( + [ + {$currentOp: {localOps: true}}, + { + $match: { + $expr: {$eq: ["$host", "$clientMetadata.mongos.host"]}, + "command.comment": "mongos_currentop_localOps" + } + } + ], + {comment: "mongos_currentop_localOps"}) + .itcount(), + 1); + +// +// localOps tests. +// + +// Runs a suite of tests for behaviour common to both replica sets and mongoS with +// {localOps:true}. +function runLocalOpsTests(conn) { + // The 'localOps' parameter is not supported by the currentOp command, so we limit its + // testing to the replica set in certain cases. + const connAdminDB = conn.getDB("admin"); + const isMongos = FixtureHelpers.isMongos(connAdminDB); + + // Test that a user with the inprog privilege can see another user's ops with + // {allUsers:true}. + assert(connAdminDB.logout()); + assert(connAdminDB.auth("user_inprog", "pwd")); + + let awaitShell = runInParallelShell({ + testfunc: function() { + assert.eq(db.getSiblingDB(jsTestName()) + .test.find({}) + .comment("agg_current_op_allusers_test") + .itcount(), + 5); + }, + conn: conn, + username: "admin", + password: "pwd" + }); + + assertCurrentOpHasSingleMatchingEntry({ + conn: conn, + currentOpAggFilter: {"command.comment": "agg_current_op_allusers_test"}, + curOpSpec: {allUsers: true, localOps: true} + }); + + // Test that the currentOp command can see another user's operations with {$ownOps: false}. + // Only test on a replica set since 'localOps' isn't supported by the currentOp command. + if (!isMongos) { + assert.eq( + connAdminDB + .currentOp({$ownOps: false, "command.comment": "agg_current_op_allusers_test"}) + .inprog.length, + 1); + } - // Log in as 'user_inprog' to confirm that a user with the 'inprog' privilege can see all three - // stashed transactions with 'allUsers:true'. - shardAdminDB.logout(); - assert(shardAdminDB.auth("user_inprog", "pwd")); + // Test that $currentOp succeeds with {allUsers: false} for a user without the "inprog" + // privilege. + assert(connAdminDB.logout()); + assert(connAdminDB.auth("user_no_inprog", "pwd")); - assert.eq( - shardAdminDB - .aggregate( - [{$currentOp: {allUsers: true, idleSessions: true}}, {$match: sessionFilter()}]) - .itcount(), - 3); + assert.commandWorked(connAdminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: {allUsers: false, localOps: true}}], cursor: {}})); + + // Test that the currentOp command succeeds with {$ownOps: true} for a user without the + // "inprog" privilege. Because currentOp does not support the 'localOps' parameter, we only + // perform this test in the replica set case. + if (!isMongos) { + assert.commandWorked(connAdminDB.currentOp({$ownOps: true})); + } - // Confirm that the 'idleSessions' parameter defaults to true. - assert.eq(shardAdminDB.aggregate([{$currentOp: {allUsers: true}}, {$match: sessionFilter()}]) + // Test that a user without the inprog privilege cannot see another user's operations. + assert.eq(connAdminDB + .aggregate([ + {$currentOp: {allUsers: false, localOps: true}}, + {$match: {"command.comment": "agg_current_op_allusers_test"}} + ]) .itcount(), - 3); + 0); - // Confirm that idleSessions:false omits the stashed locks from the report. - assert.eq( - shardAdminDB - .aggregate( - [{$currentOp: {allUsers: true, idleSessions: false}}, {$match: sessionFilter()}]) - .itcount(), - 0); - - // Allow all transactions to complete and close the associated sessions. - for (let i in userNames) { - assert(shardAdminDB.auth(userNames[i], "pwd")); - assert.commandWorked(sessionDBs[i].adminCommand({ - commitTransaction: 1, - txnNumber: NumberLong(i), - autocommit: false, - writeConcern: {w: 'majority'} - })); - sessions[i].endSession(); + // Test that a user without the inprog privilege cannot see another user's operations via + // the currentOp command. Limit this test to the replica set case due to the absence of a + // 'localOps' parameter for the currentOp command. + if (!isMongos) { + assert.eq(connAdminDB + .currentOp({$ownOps: true, "command.comment": "agg_current_op_allusers_test"}) + .inprog.length, + 0); } - // - // No-auth tests. - // + // Release the failpoint and wait for the parallel shell to complete. + waitForParallelShell({conn: conn, username: "admin", password: "pwd", awaitShell: awaitShell}); + + // Test that a user without the inprog privilege can run getMore on a $currentOp cursor + // which they created with {allUsers: false}. + assert.commandWorked(getMoreTest({conn: conn, curOpSpec: {allUsers: false, localOps: true}})); +} + +// Run the localOps tests for both replset and mongoS. +runLocalOpsTests(mongosConn); +runLocalOpsTests(shardConn); + +// +// Stashed transactions tests. +// + +// Test that $currentOp will display stashed transaction locks if 'idleSessions' is true, and +// will only permit a user to view other users' sessions if the caller possesses the 'inprog' +// privilege and 'allUsers' is true. +const userNames = ["user_inprog", "admin", "user_no_inprog"]; +let sessionDBs = []; +let sessions = []; + +// Returns a set of predicates that filter $currentOp for all stashed transactions. +function sessionFilter() { + return { + type: "idleSession", + active: false, + opid: {$exists: false}, + desc: "inactive transaction", + "lsid.id": {$in: sessions.map((session) => session.getSessionId().id)}, + "transaction.parameters.txnNumber": {$gte: 0, $lt: sessions.length}, + }; +} - // Restart the cluster with auth disabled. - restartCluster(st, {keyFile: null}); +for (let i in userNames) { + shardAdminDB.logout(); + assert(shardAdminDB.auth(userNames[i], "pwd")); - // Test that $currentOp will display all stashed transaction locks by default if auth is - // disabled, even with 'allUsers:false'. + // Create a session for this user. const session = shardAdminDB.getMongo().startSession(); - // Run an operation prior to starting the transaction and save its operation time. + // For each session, start but do not complete a transaction. const sessionDB = session.getDatabase(shardTestDB.getName()); - const res = assert.commandWorked(sessionDB.runCommand({insert: "test", documents: [{x: 1}]})); - const operationTime = res.operationTime; - - // Set and save the transaction's lifetime. We will use this later to assert that our - // transaction's expiry time is equal to its start time + lifetime. - const transactionLifeTime = 10; - assert.commandWorked(sessionDB.adminCommand( - {setParameter: 1, transactionLifetimeLimitSeconds: transactionLifeTime})); - - // Start but do not complete a transaction. assert.commandWorked(sessionDB.runCommand({ insert: "test", - documents: [{_id: `txn-insert-no-auth`}], + documents: [{_id: `txn-insert-${userNames[i]}-${i}`}], readConcern: {level: "snapshot"}, - txnNumber: NumberLong(0), + txnNumber: NumberLong(i), startTransaction: true, autocommit: false })); - sessionDBs = [sessionDB]; - sessions = [session]; + sessionDBs.push(sessionDB); + sessions.push(session); - const timeAfterTransactionStarts = new ISODate(); - - // Use $currentOp to confirm that the incomplete transaction has stashed its locks. - assert.eq(shardAdminDB.aggregate([{$currentOp: {allUsers: false}}, {$match: sessionFilter()}]) - .itcount(), - 1); - - // Confirm that idleSessions:false omits the stashed locks from the report. + // Use $currentOp to confirm that the incomplete transactions have stashed their locks while + // inactive, and that each user can only view their own sessions with 'allUsers:false'. assert.eq( shardAdminDB .aggregate( - [{$currentOp: {allUsers: false, idleSessions: false}}, {$match: sessionFilter()}]) + [{$currentOp: {allUsers: false, idleSessions: true}}, {$match: sessionFilter()}]) .itcount(), - 0); - - // Prepare the transaction and ensure the prepareTimestamp is valid. - const prepareRes = assert.commandWorked(sessionDB.adminCommand({ - prepareTransaction: 1, - txnNumber: NumberLong(0), - autocommit: false, - writeConcern: {w: "majority"} - })); - assert(prepareRes.prepareTimestamp, - "prepareTransaction did not return a 'prepareTimestamp': " + tojson(prepareRes)); - assert(prepareRes.prepareTimestamp instanceof Timestamp, - 'prepareTimestamp was not a Timestamp: ' + tojson(prepareRes)); - assert.neq(prepareRes.prepareTimestamp, - Timestamp(0, 0), - "prepareTimestamp cannot be null: " + tojson(prepareRes)); - - const timeBeforeCurrentOp = new ISODate(); - - // Check that the currentOp's transaction subdocument's fields align with our expectations. - let currentOp = - shardAdminDB.aggregate([{$currentOp: {allUsers: false}}, {$match: sessionFilter()}]) - .toArray(); - let transactionDocument = currentOp[0].transaction; - assert.eq(transactionDocument.parameters.autocommit, false); - assert.eq(transactionDocument.parameters.readConcern, {level: "snapshot"}); - assert.gte(transactionDocument.readTimestamp, operationTime); - // We round timeOpenMicros up to the nearest multiple of 1000 to avoid occasional assertion - // failures caused by timeOpenMicros having microsecond precision while - // timeBeforeCurrentOp/timeAfterTransactionStarts only have millisecond precision. - assert.gte(Math.ceil(transactionDocument.timeOpenMicros / 1000) * 1000, - (timeBeforeCurrentOp - timeAfterTransactionStarts) * 1000); - assert.gte(transactionDocument.timeActiveMicros, 0); - assert.gte(transactionDocument.timeInactiveMicros, 0); - assert.gte(transactionDocument.timePreparedMicros, 0); - // Not worried about its specific value, validate that in general we return some non-zero & - // valid time greater than epoch time. - assert.gt(ISODate(transactionDocument.startWallClockTime), ISODate("1970-01-01T00:00:00.000Z")); - assert.eq( - ISODate(transactionDocument.expiryTime).getTime(), - ISODate(transactionDocument.startWallClockTime).getTime() + transactionLifeTime * 1000); - - // Allow the transactions to complete and close the session. We must commit prepared - // transactions at a timestamp greater than the prepare timestamp. - const commitTimestamp = - Timestamp(prepareRes.prepareTimestamp.getTime(), prepareRes.prepareTimestamp.getInc() + 1); - assert.commandWorked(sessionDB.adminCommand({ + 1); +} + +// Log in as 'user_no_inprog' to verify that the user cannot view other users' sessions via +// 'allUsers:true'. +shardAdminDB.logout(); +assert(shardAdminDB.auth("user_no_inprog", "pwd")); + +assert.commandFailedWithCode(shardAdminDB.runCommand({ + aggregate: 1, + cursor: {}, + pipeline: [{$currentOp: {allUsers: true, idleSessions: true}}, {$match: sessionFilter()}] +}), + ErrorCodes.Unauthorized); + +// Log in as 'user_inprog' to confirm that a user with the 'inprog' privilege can see all three +// stashed transactions with 'allUsers:true'. +shardAdminDB.logout(); +assert(shardAdminDB.auth("user_inprog", "pwd")); + +assert.eq( + shardAdminDB + .aggregate([{$currentOp: {allUsers: true, idleSessions: true}}, {$match: sessionFilter()}]) + .itcount(), + 3); + +// Confirm that the 'idleSessions' parameter defaults to true. +assert.eq( + shardAdminDB.aggregate([{$currentOp: {allUsers: true}}, {$match: sessionFilter()}]).itcount(), + 3); + +// Confirm that idleSessions:false omits the stashed locks from the report. +assert.eq( + shardAdminDB + .aggregate([{$currentOp: {allUsers: true, idleSessions: false}}, {$match: sessionFilter()}]) + .itcount(), + 0); + +// Allow all transactions to complete and close the associated sessions. +for (let i in userNames) { + assert(shardAdminDB.auth(userNames[i], "pwd")); + assert.commandWorked(sessionDBs[i].adminCommand({ commitTransaction: 1, - txnNumber: NumberLong(0), + txnNumber: NumberLong(i), autocommit: false, - writeConcern: {w: 'majority'}, - commitTimestamp: commitTimestamp + writeConcern: {w: 'majority'} })); - session.endSession(); - - // Run a set of tests of behaviour common to replset and mongoS when auth is disabled. - function runNoAuthTests(conn, curOpSpec) { - // Test that the allUsers parameter is ignored when authentication is disabled. - // Ensure that there is at least one other connection present. - const connAdminDB = conn.getDB("admin"); - const otherConn = new Mongo(conn.host); - curOpSpec = Object.assign({localOps: false}, (curOpSpec || {})); - - // Verify that $currentOp displays all operations when auth is disabled regardless of the - // allUsers parameter, by confirming that we can see non-client system operations when - // {allUsers: false} is specified. - assert.gte( - connAdminDB - .aggregate([ - { - $currentOp: - {allUsers: false, idleConnections: true, localOps: curOpSpec.localOps} - }, - {$match: {connectionId: {$exists: false}}} - ]) - .itcount(), - 1); - - // Verify that the currentOp command displays all operations when auth is disabled - // regardless of - // the $ownOps parameter, by confirming that we can see non-client system operations when - // {$ownOps: true} is specified. - assert.gte( - connAdminDB.currentOp({$ownOps: true, $all: true, connectionId: {$exists: false}}) - .inprog.length, - 1); - - // Test that a user can run getMore on a $currentOp cursor when authentication is disabled. - assert.commandWorked( - getMoreTest({conn: conn, curOpSpec: {allUsers: true, localOps: curOpSpec.localOps}})); - } - - runNoAuthTests(shardConn); - runNoAuthTests(mongosConn); - runNoAuthTests(mongosConn, {localOps: true}); - - // - // Replset specific tests. - // - - // Take the replica set out of the cluster. - shardConn = restartReplSet(st.rs0, {shardsvr: null}); - shardTestDB = shardConn.getDB(jsTestName()); - shardAdminDB = shardConn.getDB("admin"); - - // Test that the host field is present and the shard field is absent when run on mongoD. - assert.eq(shardAdminDB - .aggregate([ - {$currentOp: {allUsers: true, idleConnections: true}}, - {$group: {_id: {shard: "$shard", host: "$host"}}} - ]) - .toArray(), - [ - {_id: {host: shardConn.host}}, - ]); + sessions[i].endSession(); +} + +// +// No-auth tests. +// + +// Restart the cluster with auth disabled. +restartCluster(st, {keyFile: null}); + +// Test that $currentOp will display all stashed transaction locks by default if auth is +// disabled, even with 'allUsers:false'. +const session = shardAdminDB.getMongo().startSession(); + +// Run an operation prior to starting the transaction and save its operation time. +const sessionDB = session.getDatabase(shardTestDB.getName()); +const res = assert.commandWorked(sessionDB.runCommand({insert: "test", documents: [{x: 1}]})); +const operationTime = res.operationTime; + +// Set and save the transaction's lifetime. We will use this later to assert that our +// transaction's expiry time is equal to its start time + lifetime. +const transactionLifeTime = 10; +assert.commandWorked(sessionDB.adminCommand( + {setParameter: 1, transactionLifetimeLimitSeconds: transactionLifeTime})); + +// Start but do not complete a transaction. +assert.commandWorked(sessionDB.runCommand({ + insert: "test", + documents: [{_id: `txn-insert-no-auth`}], + readConcern: {level: "snapshot"}, + txnNumber: NumberLong(0), + startTransaction: true, + autocommit: false +})); +sessionDBs = [sessionDB]; +sessions = [session]; + +const timeAfterTransactionStarts = new ISODate(); + +// Use $currentOp to confirm that the incomplete transaction has stashed its locks. +assert.eq( + shardAdminDB.aggregate([{$currentOp: {allUsers: false}}, {$match: sessionFilter()}]).itcount(), + 1); + +// Confirm that idleSessions:false omits the stashed locks from the report. +assert.eq(shardAdminDB + .aggregate( + [{$currentOp: {allUsers: false, idleSessions: false}}, {$match: sessionFilter()}]) + .itcount(), + 0); + +// Prepare the transaction and ensure the prepareTimestamp is valid. +const prepareRes = assert.commandWorked(sessionDB.adminCommand({ + prepareTransaction: 1, + txnNumber: NumberLong(0), + autocommit: false, + writeConcern: {w: "majority"} +})); +assert(prepareRes.prepareTimestamp, + "prepareTransaction did not return a 'prepareTimestamp': " + tojson(prepareRes)); +assert(prepareRes.prepareTimestamp instanceof Timestamp, + 'prepareTimestamp was not a Timestamp: ' + tojson(prepareRes)); +assert.neq(prepareRes.prepareTimestamp, + Timestamp(0, 0), + "prepareTimestamp cannot be null: " + tojson(prepareRes)); + +const timeBeforeCurrentOp = new ISODate(); + +// Check that the currentOp's transaction subdocument's fields align with our expectations. +let currentOp = + shardAdminDB.aggregate([{$currentOp: {allUsers: false}}, {$match: sessionFilter()}]).toArray(); +let transactionDocument = currentOp[0].transaction; +assert.eq(transactionDocument.parameters.autocommit, false); +assert.eq(transactionDocument.parameters.readConcern, {level: "snapshot"}); +assert.gte(transactionDocument.readTimestamp, operationTime); +// We round timeOpenMicros up to the nearest multiple of 1000 to avoid occasional assertion +// failures caused by timeOpenMicros having microsecond precision while +// timeBeforeCurrentOp/timeAfterTransactionStarts only have millisecond precision. +assert.gte(Math.ceil(transactionDocument.timeOpenMicros / 1000) * 1000, + (timeBeforeCurrentOp - timeAfterTransactionStarts) * 1000); +assert.gte(transactionDocument.timeActiveMicros, 0); +assert.gte(transactionDocument.timeInactiveMicros, 0); +assert.gte(transactionDocument.timePreparedMicros, 0); +// Not worried about its specific value, validate that in general we return some non-zero & +// valid time greater than epoch time. +assert.gt(ISODate(transactionDocument.startWallClockTime), ISODate("1970-01-01T00:00:00.000Z")); +assert.eq(ISODate(transactionDocument.expiryTime).getTime(), + ISODate(transactionDocument.startWallClockTime).getTime() + transactionLifeTime * 1000); + +// Allow the transactions to complete and close the session. We must commit prepared +// transactions at a timestamp greater than the prepare timestamp. +const commitTimestamp = + Timestamp(prepareRes.prepareTimestamp.getTime(), prepareRes.prepareTimestamp.getInc() + 1); +assert.commandWorked(sessionDB.adminCommand({ + commitTransaction: 1, + txnNumber: NumberLong(0), + autocommit: false, + writeConcern: {w: 'majority'}, + commitTimestamp: commitTimestamp +})); +session.endSession(); + +// Run a set of tests of behaviour common to replset and mongoS when auth is disabled. +function runNoAuthTests(conn, curOpSpec) { + // Test that the allUsers parameter is ignored when authentication is disabled. + // Ensure that there is at least one other connection present. + const connAdminDB = conn.getDB("admin"); + const otherConn = new Mongo(conn.host); + curOpSpec = Object.assign({localOps: false}, (curOpSpec || {})); + + // Verify that $currentOp displays all operations when auth is disabled regardless of the + // allUsers parameter, by confirming that we can see non-client system operations when + // {allUsers: false} is specified. + assert.gte( + connAdminDB + .aggregate([ + { + $currentOp: + {allUsers: false, idleConnections: true, localOps: curOpSpec.localOps} + }, + {$match: {connectionId: {$exists: false}}} + ]) + .itcount(), + 1); - // Test that attempting to 'spoof' a sharded request on non-shardsvr mongoD fails. - assert.commandFailedWithCode( - shardAdminDB.runCommand( - {aggregate: 1, pipeline: [{$currentOp: {}}], fromMongos: true, cursor: {}}), - 40465); - - // Test that an operation which is at the BSON user size limit does not throw an error when the - // currentOp metadata is added to the output document. - const bsonUserSizeLimit = assert.commandWorked(shardAdminDB.isMaster()).maxBsonObjectSize; - - let aggPipeline = [ - {$currentOp: {}}, - { - $match: { - $or: [ - { + // Verify that the currentOp command displays all operations when auth is disabled + // regardless of + // the $ownOps parameter, by confirming that we can see non-client system operations when + // {$ownOps: true} is specified. + assert.gte(connAdminDB.currentOp({$ownOps: true, $all: true, connectionId: {$exists: false}}) + .inprog.length, + 1); + + // Test that a user can run getMore on a $currentOp cursor when authentication is disabled. + assert.commandWorked( + getMoreTest({conn: conn, curOpSpec: {allUsers: true, localOps: curOpSpec.localOps}})); +} + +runNoAuthTests(shardConn); +runNoAuthTests(mongosConn); +runNoAuthTests(mongosConn, {localOps: true}); + +// +// Replset specific tests. +// + +// Take the replica set out of the cluster. +shardConn = restartReplSet(st.rs0, {shardsvr: null}); +shardTestDB = shardConn.getDB(jsTestName()); +shardAdminDB = shardConn.getDB("admin"); + +// Test that the host field is present and the shard field is absent when run on mongoD. +assert.eq(shardAdminDB + .aggregate([ + {$currentOp: {allUsers: true, idleConnections: true}}, + {$group: {_id: {shard: "$shard", host: "$host"}}} + ]) + .toArray(), + [ + {_id: {host: shardConn.host}}, + ]); + +// Test that attempting to 'spoof' a sharded request on non-shardsvr mongoD fails. +assert.commandFailedWithCode( + shardAdminDB.runCommand( + {aggregate: 1, pipeline: [{$currentOp: {}}], fromMongos: true, cursor: {}}), + 40465); + +// Test that an operation which is at the BSON user size limit does not throw an error when the +// currentOp metadata is added to the output document. +const bsonUserSizeLimit = assert.commandWorked(shardAdminDB.isMaster()).maxBsonObjectSize; + +let aggPipeline = [ + {$currentOp: {}}, + { + $match: { + $or: [ + { "command.comment": "agg_current_op_bson_limit_test", "command.$truncated": {$exists: false} - }, - {padding: ""} - ] - } + }, + {padding: ""} + ] } - ]; + } +]; - aggPipeline[1].$match.$or[1].padding = - "a".repeat(bsonUserSizeLimit - Object.bsonsize(aggPipeline)); +aggPipeline[1].$match.$or[1].padding = "a".repeat(bsonUserSizeLimit - Object.bsonsize(aggPipeline)); - assert.eq(Object.bsonsize(aggPipeline), bsonUserSizeLimit); +assert.eq(Object.bsonsize(aggPipeline), bsonUserSizeLimit); - assert.eq( - shardAdminDB.aggregate(aggPipeline, {comment: "agg_current_op_bson_limit_test"}).itcount(), - 1); +assert.eq( + shardAdminDB.aggregate(aggPipeline, {comment: "agg_current_op_bson_limit_test"}).itcount(), 1); - // Test that $currentOp can run while the mongoD is write-locked. - let awaitShell = startParallelShell(function() { - assert.commandFailedWithCode(db.adminCommand({sleep: 1, lock: "w", secs: 300}), - ErrorCodes.Interrupted); - }, shardConn.port); +// Test that $currentOp can run while the mongoD is write-locked. +let awaitShell = startParallelShell(function() { + assert.commandFailedWithCode(db.adminCommand({sleep: 1, lock: "w", secs: 300}), + ErrorCodes.Interrupted); +}, shardConn.port); - const op = assertCurrentOpHasSingleMatchingEntry( - {conn: shardConn, currentOpAggFilter: {"command.sleep": 1, active: true}}); +const op = assertCurrentOpHasSingleMatchingEntry( + {conn: shardConn, currentOpAggFilter: {"command.sleep": 1, active: true}}); - assert.commandWorked(shardAdminDB.killOp(op.opid)); +assert.commandWorked(shardAdminDB.killOp(op.opid)); - awaitShell(); +awaitShell(); - // Add the shard back into the replset so that it can be validated by st.stop(). - shardConn = restartReplSet(st.rs0, {shardsvr: ""}); - st.stop(); +// Add the shard back into the replset so that it can be validated by st.stop(). +shardConn = restartReplSet(st.rs0, {shardsvr: ""}); +st.stop(); })(); |