diff options
-rw-r--r-- | jstests/noPassthrough/currentop_query.js | 830 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/current_op_common.cpp | 23 |
3 files changed, 447 insertions, 411 deletions
diff --git a/jstests/noPassthrough/currentop_query.js b/jstests/noPassthrough/currentop_query.js index b05e66d1af0..e7ea5883bb5 100644 --- a/jstests/noPassthrough/currentop_query.js +++ b/jstests/noPassthrough/currentop_query.js @@ -1,7 +1,7 @@ /** * Confirms inclusion of query, command object and planSummary in currentOp() for CRUD operations. * This test should not be run in the parallel suite as it sets fail points. - * @tags: [requires_replication, requires_sharding, SERVER-34286] + * @tags: [requires_replication, requires_sharding] */ (function() { "use strict"; @@ -59,8 +59,9 @@ * returns an array of matching current operations. This allows us to test output for both the * currentOp command and the $currentOp aggregation stage. * @params {boolean} truncatedOps - if true, we expect operations that exceed the maximum - * currentOp size to be truncated in the output 'command' field. If false, we expect the entire - * operation to be returned. + * currentOp size to be truncated in the output 'command' field, and we run only a subset of + * tests designed to exercise that scenario. If false, we expect the entire operation to be + * returned. * @params {boolean} localOps - if true, we expect currentOp to return operations running on a * mongoS itself rather than on the shards. */ @@ -76,6 +77,15 @@ const isLocalMongosCurOp = (FixtureHelpers.isMongos(testDB) && localOps); const isRemoteShardCurOp = (FixtureHelpers.isMongos(testDB) && !localOps); + // If 'truncatedOps' is true, run only the subset of tests designed to validate the + // truncation behaviour. Otherwise, run the standard set of tests which assume that + // truncation will not occur. + if (truncatedOps) { + runTruncationTests(); + } else { + runStandardTests(); + } + /** * Captures currentOp() for a given test command/operation and confirms that namespace, * operation type and planSummary are correct. @@ -131,7 +141,7 @@ // with the currentOp query argument. assert.soon( function() { - var result = currentOp(testDB, testObj.currentOpFilter, localOps); + var result = currentOp(testDB, testObj.currentOpFilter, truncatedOps, localOps); assert.commandWorked(result); if (result.inprog.length > 0) { @@ -148,9 +158,11 @@ }, function() { return "Failed to find operation from " + tojson(testObj.currentOpFilter) + - " in currentOp() output: " + tojson(currentOp(testDB, {}, localOps)) + + " in currentOp() output: " + + tojson(currentOp(testDB, {}, truncatedOps, localOps)) + (isLocalMongosCurOp - ? ", with localOps=false: " + tojson(currentOp(testDB, {}, false)) + ? ", with localOps=false: " + + tojson(currentOp(testDB, {}, truncatedOps, false)) : ""); }); @@ -166,470 +178,478 @@ delete TestData.shellReadMode; } - // - // Confirm currentOp content for commands defined in 'testList'. - // - var testList = [ - { - test: function(db) { - assert.eq(db.currentop_query - .aggregate([{$match: {a: 1, $comment: "currentop_query"}}], { - collation: {locale: "fr"}, - hint: {_id: 1}, - comment: "currentop_query_2" - }) - .itcount(), - 1); - }, - planSummary: "IXSCAN { _id: 1 }", - currentOpFilter: commandOrOriginatingCommand({ - "aggregate": {$exists: true}, - "pipeline.0.$match.$comment": "currentop_query", - "comment": "currentop_query_2", - "collation": {locale: "fr"}, - "hint": {_id: 1} - }, - isRemoteShardCurOp) - }, - { - test: function(db) { - assert.eq(db.currentop_query.find({a: 1, $comment: "currentop_query"}) - .collation({locale: "fr"}) - .count(), - 1); - }, - command: "count", - planSummary: "COLLSCAN", - currentOpFilter: { - "command.query.$comment": "currentop_query", - "command.collation": {locale: "fr"} - } - }, - { - test: function(db) { - assert.eq( - db.currentop_query.distinct( - "a", {a: 1, $comment: "currentop_query"}, {collation: {locale: "fr"}}), - [1]); - }, - command: "distinct", - planSummary: "COLLSCAN", - currentOpFilter: { - "command.query.$comment": "currentop_query", - "command.collation": {locale: "fr"} - } - }, - { - test: function(db) { - assert.eq(db.currentop_query.find({a: 1}).comment("currentop_query").itcount(), - 1); - }, - command: "find", - planSummary: "COLLSCAN", - currentOpFilter: {"command.comment": "currentop_query"} - }, - { - test: function(db) { - assert.eq(db.currentop_query.findAndModify({ - query: {_id: 1, a: 1, $comment: "currentop_query"}, - update: {$inc: {b: 1}}, - collation: {locale: "fr"} - }), - {"_id": 1, "a": 1}); - }, - command: "findandmodify", - planSummary: "IXSCAN { _id: 1 }", - currentOpFilter: { - "command.query.$comment": "currentop_query", - "command.collation": {locale: "fr"} - } - }, - { - test: function(db) { - assert.commandWorked( - db.currentop_query.mapReduce(() => {}, - (a, b) => {}, - { - query: {$comment: "currentop_query"}, - out: {inline: 1}, - })); - }, - command: "mapreduce", - planSummary: "COLLSCAN", - currentOpFilter: { - "command.query.$comment": "currentop_query", - "ns": /^currentop_query.*currentop_query/ - } - }, - { - test: function(db) { - assert.writeOK(db.currentop_query.remove({a: 2, $comment: "currentop_query"}, - {collation: {locale: "fr"}})); - }, - operation: "remove", - planSummary: "COLLSCAN", - currentOpFilter: - (isLocalMongosCurOp ? {"command.delete": coll.getName(), "command.ordered": true} - : { - "command.q.$comment": "currentop_query", - "command.collation": {locale: "fr"} - }) - }, - { - test: function(db) { - assert.writeOK( - db.currentop_query.update({a: 1, $comment: "currentop_query"}, - {$inc: {b: 1}}, - {collation: {locale: "fr"}, multi: true})); - }, - operation: "update", - planSummary: "COLLSCAN", - currentOpFilter: - (isLocalMongosCurOp ? {"command.update": coll.getName(), "command.ordered": true} - : { - "command.q.$comment": "currentop_query", - "command.collation": {locale: "fr"} - }) + /** + * Runs a set of tests to verify that the currentOp output appears as expected. These tests + * assume that the 'truncateOps' parameter is false, so no command objects in the currentOp + * output will be truncated to string. + */ + function runStandardTests() { + // + // Confirm currentOp content for commands defined in 'testList'. + // + var testList = [ + { + test: function(db) { + assert.eq(db.currentop_query + .aggregate([{$match: {a: 1, $comment: "currentop_query"}}], { + collation: {locale: "fr"}, + hint: {_id: 1}, + comment: "currentop_query_2" + }) + .itcount(), + 1); + }, + planSummary: "IXSCAN { _id: 1 }", + currentOpFilter: commandOrOriginatingCommand({ + "aggregate": {$exists: true}, + "pipeline.0.$match.$comment": "currentop_query", + "comment": "currentop_query_2", + "collation": {locale: "fr"}, + "hint": {_id: 1} + }, + isRemoteShardCurOp) + }, + { + test: function(db) { + assert.eq(db.currentop_query.find({a: 1, $comment: "currentop_query"}) + .collation({locale: "fr"}) + .count(), + 1); + }, + command: "count", + planSummary: "COLLSCAN", + currentOpFilter: { + "command.query.$comment": "currentop_query", + "command.collation": {locale: "fr"} + } + }, + { + test: function(db) { + assert.eq(db.currentop_query.distinct("a", + {a: 1, $comment: "currentop_query"}, + {collation: {locale: "fr"}}), + [1]); + }, + command: "distinct", + planSummary: "COLLSCAN", + currentOpFilter: { + "command.query.$comment": "currentop_query", + "command.collation": {locale: "fr"} + } + }, + { + test: function(db) { + assert.eq( + db.currentop_query.find({a: 1}).comment("currentop_query").itcount(), 1); + }, + command: "find", + planSummary: "COLLSCAN", + currentOpFilter: {"command.comment": "currentop_query"} + }, + { + test: function(db) { + assert.eq(db.currentop_query.findAndModify({ + query: {_id: 1, a: 1, $comment: "currentop_query"}, + update: {$inc: {b: 1}}, + collation: {locale: "fr"} + }), + {"_id": 1, "a": 1}); + }, + command: "findandmodify", + planSummary: "IXSCAN { _id: 1 }", + currentOpFilter: { + "command.query.$comment": "currentop_query", + "command.collation": {locale: "fr"} + } + }, + { + test: function(db) { + assert.commandWorked( + db.currentop_query.mapReduce(() => {}, + (a, b) => {}, + { + query: {$comment: "currentop_query"}, + out: {inline: 1}, + })); + }, + command: "mapreduce", + planSummary: "COLLSCAN", + currentOpFilter: { + "command.query.$comment": "currentop_query", + "ns": /^currentop_query.*currentop_query/ + } + }, + { + test: function(db) { + assert.writeOK(db.currentop_query.remove({a: 2, $comment: "currentop_query"}, + {collation: {locale: "fr"}})); + }, + operation: "remove", + planSummary: "COLLSCAN", + currentOpFilter: + (isLocalMongosCurOp + ? {"command.delete": coll.getName(), "command.ordered": true} + : { + "command.q.$comment": "currentop_query", + "command.collation": {locale: "fr"} + }) + }, + { + test: function(db) { + assert.writeOK( + db.currentop_query.update({a: 1, $comment: "currentop_query"}, + {$inc: {b: 1}}, + {collation: {locale: "fr"}, multi: true})); + }, + operation: "update", + planSummary: "COLLSCAN", + currentOpFilter: + (isLocalMongosCurOp + ? {"command.update": coll.getName(), "command.ordered": true} + : { + "command.q.$comment": "currentop_query", + "command.collation": {locale: "fr"} + }) + } + ]; + + // The 'group' command cannot be run on a sharded collection. + if (!FixtureHelpers.isMongos(coll.getDB())) { + testList.push({ + test: function(db) { + assert.eq(db.currentop_query.group({ + key: {a: 1}, + cond: {a: 1, $comment: "currentop_query"}, + reduce: function() {}, + initial: {}, + collation: {locale: "fr"} + }), + [{"a": 1}]); + }, + command: "group", + planSummary: "COLLSCAN", + currentOpFilter: { + "command.group.cond.$comment": "currentop_query", + "command.group.collation": {locale: "fr"} + } + }); } - ]; - // The 'group' command cannot be run on a sharded collection. - if (!FixtureHelpers.isMongos(coll.getDB())) { - testList.push({ + testList.forEach(confirmCurrentOpContents); + + // + // Confirm currentOp contains collation for find command. + // + if (readMode === "commands") { + confirmCurrentOpContents({ + test: function(db) { + assert.eq(db.currentop_query.find({a: 1}) + .comment("currentop_query") + .collation({locale: "fr"}) + .itcount(), + 1); + }, + command: "find", + planSummary: "COLLSCAN", + currentOpFilter: { + "command.comment": "currentop_query", + "command.collation": {locale: "fr"} + } + }); + } + + // + // Confirm currentOp content for geoNear. + // + dropAndRecreateTestCollection(); + for (let i = 0; i < 10; ++i) { + assert.writeOK(coll.insert({a: i, loc: {type: "Point", coordinates: [i, i]}})); + } + assert.commandWorked(coll.createIndex({loc: "2dsphere"})); + + confirmCurrentOpContents({ test: function(db) { - assert.eq(db.currentop_query.group({ - key: {a: 1}, - cond: {a: 1, $comment: "currentop_query"}, - reduce: function() {}, - initial: {}, + assert.commandWorked(db.runCommand({ + geoNear: "currentop_query", + near: {type: "Point", coordinates: [1, 1]}, + spherical: true, + query: {$comment: "currentop_query"}, collation: {locale: "fr"} - }), - [{"a": 1}]); + })); }, - command: "group", - planSummary: "COLLSCAN", + command: "geoNear", + planSummary: "GEO_NEAR_2DSPHERE { loc: \"2dsphere\" }", currentOpFilter: { - "command.group.cond.$comment": "currentop_query", - "command.group.collation": {locale: "fr"} + "command.query.$comment": "currentop_query", + "command.collation": {locale: "fr"} } }); - } - - testList.forEach(confirmCurrentOpContents); - // - // Confirm currentOp contains collation for find command. - // - if (readMode === "commands") { - confirmCurrentOpContents({ - test: function(db) { - assert.eq(db.currentop_query.find({a: 1}) - .comment("currentop_query") - .collation({locale: "fr"}) - .itcount(), - 1); - }, - command: "find", - planSummary: "COLLSCAN", - currentOpFilter: - {"command.comment": "currentop_query", "command.collation": {locale: "fr"}} - }); - } - - // - // Confirm currentOp content for geoNear. - // - dropAndRecreateTestCollection(); - for (let i = 0; i < 10; ++i) { - assert.writeOK(coll.insert({a: i, loc: {type: "Point", coordinates: [i, i]}})); - } - assert.commandWorked(coll.createIndex({loc: "2dsphere"})); - - confirmCurrentOpContents({ - test: function(db) { - assert.commandWorked(db.runCommand({ - geoNear: "currentop_query", - near: {type: "Point", coordinates: [1, 1]}, - spherical: true, - query: {$comment: "currentop_query"}, - collation: {locale: "fr"} - })); - }, - command: "geoNear", - planSummary: "GEO_NEAR_2DSPHERE { loc: \"2dsphere\" }", - currentOpFilter: { - "command.query.$comment": "currentop_query", - "command.collation": {locale: "fr"} + // + // Confirm currentOp content for getMore. This case tests command and legacy getMore + // with originating find and aggregate commands. + // + dropAndRecreateTestCollection(); + for (let i = 0; i < 10; ++i) { + assert.writeOK(coll.insert({a: i})); } - }); - // - // Confirm currentOp content for getMore. This case tests command and legacy getMore with - // originating find and aggregate commands. - // - dropAndRecreateTestCollection(); - for (let i = 0; i < 10; ++i) { - assert.writeOK(coll.insert({a: i})); - } + const originatingCommands = { + find: + {find: "currentop_query", filter: {}, comment: "currentop_query", batchSize: 0}, + aggregate: { + aggregate: "currentop_query", + pipeline: [{$match: {}}], + comment: "currentop_query", + cursor: {batchSize: 0} + } + }; - const originatingCommands = { - find: {find: "currentop_query", filter: {}, comment: "currentop_query", batchSize: 0}, - aggregate: { - aggregate: "currentop_query", - pipeline: [{$match: {}}], - comment: "currentop_query", - cursor: {batchSize: 0} + for (let cmdName in originatingCommands) { + const cmdObj = originatingCommands[cmdName]; + const cmdRes = testDB.runCommand(cmdObj); + assert.commandWorked(cmdRes); + + TestData.commandResult = cmdRes; + + // If this is a non-localOps test running via mongoS, then the cursorID we obtained + // above is the ID of the mongoS cursor, and will not match the IDs of any of the + // individual shard cursors in the currentOp output. We therefore don't perform an + // exact match on 'command.getMore', but only verify that the cursor ID is non-zero. + const filter = { + "command.getMore": + (isRemoteShardCurOp ? {$gt: 0} : TestData.commandResult.cursor.id), + [`originatingCommand.${cmdName}`]: + {$exists: true}, "originatingCommand.comment": "currentop_query" + }; + + confirmCurrentOpContents({ + test: function(db) { + const cursor = new DBCommandCursor(db, TestData.commandResult, 5); + assert.eq(cursor.itcount(), 10); + }, + command: "getMore", + planSummary: "COLLSCAN", + currentOpFilter: filter + }); + + delete TestData.commandResult; } - }; - for (let cmdName in originatingCommands) { - const cmdObj = originatingCommands[cmdName]; - const cmdRes = testDB.runCommand(cmdObj); - assert.commandWorked(cmdRes); + // + // Confirm that currentOp displays upconverted getMore and originatingCommand in the + // case of a legacy query. + // + if (readMode === "legacy") { + let filter = { + "command.getMore": {$gt: 0}, + "command.collection": "currentop_query", + "command.batchSize": 2, + "originatingCommand.find": "currentop_query", + "originatingCommand.ntoreturn": 2, + "originatingCommand.comment": "currentop_query" + }; + + confirmCurrentOpContents({ + test: function(db) { + load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. + + // Temporarily disable hanging yields so that we can iterate the first + // batch. + FixtureHelpers.runCommandOnEachPrimary({ + db: db.getSiblingDB("admin"), + cmdObj: {configureFailPoint: "setYieldAllLocksHang", mode: "off"} + }); - TestData.commandResult = cmdRes; + let cursor = + db.currentop_query.find({}).comment("currentop_query").batchSize(2); - // If this is a non-localOps test running via mongoS, then the cursorID we obtained - // above is the ID of the mongoS cursor, and will not match the IDs of any of the - // individual shard cursors in the currentOp output. We therefore don't perform an exact - // match on 'command.getMore', but simply verify that the cursor ID is non-zero. - const filter = { - "command.getMore": - (isRemoteShardCurOp ? {$gt: 0} : TestData.commandResult.cursor.id), - [`originatingCommand.${cmdName}`]: - {$exists: true}, "originatingCommand.comment": "currentop_query" - }; + // Exhaust the current batch so that the next request will force a getMore. + while (cursor.objsLeftInBatch() > 0) { + cursor.next(); + } - confirmCurrentOpContents({ - test: function(db) { - const cursor = new DBCommandCursor(db, TestData.commandResult, 5); - assert.eq(cursor.itcount(), 10); - }, - command: "getMore", - planSummary: "COLLSCAN", - currentOpFilter: filter - }); + // Set yields to hang so that we can check currentOp output. + FixtureHelpers.runCommandOnEachPrimary({ + db: db.getSiblingDB("admin"), + cmdObj: + {configureFailPoint: "setYieldAllLocksHang", mode: "alwaysOn"} + }); - delete TestData.commandResult; + assert.eq(cursor.itcount(), 8); + }, + operation: "getmore", + planSummary: "COLLSCAN", + currentOpFilter: filter + }); + } + + // + // Confirm that a legacy query whose filter contains a field named 'query' appears as + // expected in currentOp. This test ensures that upconverting a legacy query correctly + // identifies this as a user field rather than a wrapped filter spec. + // + if (readMode === "legacy") { + confirmCurrentOpContents({ + test: function(db) { + assert.eq( + db.currentop_query.find({query: "foo", $comment: "currentop_query"}) + .itcount(), + 0); + }, + command: "find", + planSummary: "COLLSCAN", + currentOpFilter: { + "command.filter.$comment": "currentop_query", + "command.filter.query": "foo" + } + }); + } } - // - // Confirm that currentOp displays upconverted getMore and originatingCommand in the case of - // a legacy query. - // - if (readMode === "legacy") { - let filter = { - "command.getMore": {$gt: 0}, - "command.collection": "currentop_query", - "command.batchSize": 2, - "originatingCommand.find": "currentop_query", - "originatingCommand.ntoreturn": 2, - "originatingCommand.comment": "currentop_query" + /** + * Runs a set of tests to verify that currentOp will serialize objects exceeding ~1000 bytes + * to string when the 'truncateOps' parameter is set. + */ + function runTruncationTests() { + dropAndRecreateTestCollection(); + assert.writeOK(coll.insert({a: 1})); + + // When the currentOp command serializes the query object as a string, individual string + // values inside it are truncated at 150 characters. To test "total length" truncation + // we need to pass multiple values, each smaller than 150 bytes. + TestData.queryFilter = { + "1": "1".repeat(149), + "2": "2".repeat(149), + "3": "3".repeat(149), + "4": "4".repeat(149), + "5": "5".repeat(149), + "6": "6".repeat(149), + "7": "7".repeat(149), }; - confirmCurrentOpContents({ - test: function(db) { - load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers. + var truncatedQueryString = "^\\{ find: \"currentop_query\", filter: \\{ " + + "1: \"1{149}\", 2: \"2{149}\", 3: \"3{149}\", 4: \"4{149}\", 5: \"5{149}\", " + + "6: \"6{149}\", 7: \"7+\\.\\.\\."; - // Temporarily disable hanging yields so that we can iterate the first batch. - FixtureHelpers.runCommandOnEachPrimary({ - db: db.getSiblingDB("admin"), - cmdObj: {configureFailPoint: "setYieldAllLocksHang", mode: "off"} - }); + let currentOpFilter; - let cursor = - db.currentop_query.find({}).comment("currentop_query").batchSize(2); - - // Exhaust the current batch so that the next request will force a getMore. - while (cursor.objsLeftInBatch() > 0) { - cursor.next(); - } - - // Set yields to hang so that we can check currentOp output. - FixtureHelpers.runCommandOnEachPrimary({ - db: db.getSiblingDB("admin"), - cmdObj: {configureFailPoint: "setYieldAllLocksHang", mode: "alwaysOn"} - }); - - assert.eq(cursor.itcount(), 8); - }, - operation: "getmore", - planSummary: "COLLSCAN", - currentOpFilter: filter - }); - } + currentOpFilter = { + "command.$truncated": {$regex: truncatedQueryString}, + "command.comment": "currentop_query" + }; - // - // Confirm that a legacy query whose filter contains a field named 'query' appears as - // expected in currentOp. This test ensures that upconverting a legacy query correctly - // identifies this as a user field rather than a wrapped filter spec. - // - if (readMode === "legacy") { confirmCurrentOpContents({ test: function(db) { - assert.eq(db.currentop_query.find({query: "foo", $comment: "currentop_query"}) + assert.eq(db.currentop_query.find(TestData.queryFilter) + .comment("currentop_query") .itcount(), 0); }, - command: "find", planSummary: "COLLSCAN", - currentOpFilter: - {"command.filter.$comment": "currentop_query", "command.filter.query": "foo"} + currentOpFilter: currentOpFilter }); - } - - // - // Confirm ~1000 byte size limit for query field in the case of the currentOp command. For - // $currentOp aggregations, this limit does not apply. - // - dropAndRecreateTestCollection(); - assert.writeOK(coll.insert({a: 1})); - - // When the currentOp command serializes the query object as a string, individual string - // values inside it are truncated at 150 characters. To test "total length" truncation we - // need to pass multiple values, each smaller than 150 bytes. - TestData.queryFilter = { - "1": "1".repeat(149), - "2": "2".repeat(149), - "3": "3".repeat(149), - "4": "4".repeat(149), - "5": "5".repeat(149), - "6": "6".repeat(149), - "7": "7".repeat(149), - }; - - var truncatedQueryString = "^\\{ find: \"currentop_query\", filter: \\{ " + - "1: \"1{149}\", 2: \"2{149}\", 3: \"3{149}\", 4: \"4{149}\", 5: \"5{149}\", " + - "6: \"6{149}\", 7: \"7+\\.\\.\\."; - let currentOpFilter; - - if (truncatedOps) { - currentOpFilter = { - "command.$truncated": {$regex: truncatedQueryString}, - "command.comment": "currentop_query" - }; - } else { - currentOpFilter = { - "command.filter": TestData.queryFilter, - "command.comment": "currentop_query" - }; - } + // Verify that an originatingCommand truncated by currentOp appears as { $truncated: + // <string>, comment: <string> }. + const cmdRes = testDB.runCommand({ + find: "currentop_query", + filter: TestData.queryFilter, + comment: "currentop_query", + batchSize: 0 + }); + assert.commandWorked(cmdRes); - confirmCurrentOpContents({ - test: function(db) { - assert.eq(db.currentop_query.find(TestData.queryFilter) - .comment("currentop_query") - .itcount(), - 0); - }, - planSummary: "COLLSCAN", - currentOpFilter: currentOpFilter - }); - - // Verify that an originatingCommand truncated by currentOp appears as { $truncated: - // <string>, comment: <string> }. - const cmdRes = testDB.runCommand({ - find: "currentop_query", - filter: TestData.queryFilter, - comment: "currentop_query", - batchSize: 0 - }); - assert.commandWorked(cmdRes); - - TestData.commandResult = cmdRes; + TestData.commandResult = cmdRes; - if (truncatedOps) { currentOpFilter = { "command.getMore": (isRemoteShardCurOp ? {$gt: 0} : TestData.commandResult.cursor.id), "originatingCommand.$truncated": {$regex: truncatedQueryString}, "originatingCommand.comment": "currentop_query" }; - } else { - currentOpFilter = { - "command.getMore": - (isRemoteShardCurOp ? {$gt: 0} : TestData.commandResult.cursor.id), - "originatingCommand.filter": TestData.queryFilter, - "originatingCommand.comment": "currentop_query" - }; - } - confirmCurrentOpContents({ - test: function(db) { - var cursor = new DBCommandCursor(db, TestData.commandResult, 5); - assert.eq(cursor.itcount(), 0); - }, - planSummary: "COLLSCAN", - currentOpFilter: currentOpFilter - }); + confirmCurrentOpContents({ + test: function(db) { + var cursor = new DBCommandCursor(db, TestData.commandResult, 5); + assert.eq(cursor.itcount(), 0); + }, + planSummary: "COLLSCAN", + currentOpFilter: currentOpFilter + }); - delete TestData.commandResult; + delete TestData.commandResult; - // Verify that an aggregation truncated by currentOp appears as { $truncated: <string>, - // comment: <string> } when a comment parameter is present. - truncatedQueryString = - "^\\{ aggregate: \"currentop_query\", pipeline: \\[ \\{ \\$match: \\{ " + - "1: \"1{149}\", 2: \"2{149}\", 3: \"3{149}\", 4: \"4{149}\", 5: \"5{149}\", " + - "6: \"6{149}\", 7: \"7+\\.\\.\\."; + // Verify that an aggregation truncated by currentOp appears as { $truncated: <string>, + // comment: <string> } when a comment parameter is present. + truncatedQueryString = + "^\\{ aggregate: \"currentop_query\", pipeline: \\[ \\{ \\$match: \\{ " + + "1: \"1{149}\", 2: \"2{149}\", 3: \"3{149}\", 4: \"4{149}\", 5: \"5{149}\", " + + "6: \"6{149}\", 7: \"7+\\.\\.\\."; - if (truncatedOps) { currentOpFilter = commandOrOriginatingCommand( {"$truncated": {$regex: truncatedQueryString}, "comment": "currentop_query"}, isRemoteShardCurOp); - } else { - currentOpFilter = commandOrOriginatingCommand( - {"pipeline.0.$match": TestData.queryFilter, "comment": "currentop_query"}, - isRemoteShardCurOp); - } - confirmCurrentOpContents({ - test: function(db) { - assert.eq( - db.currentop_query - .aggregate([{$match: TestData.queryFilter}], {comment: "currentop_query"}) - .itcount(), - 0); - }, - planSummary: "COLLSCAN", - currentOpFilter: currentOpFilter - }); - - delete TestData.queryFilter; + confirmCurrentOpContents({ + test: function(db) { + assert.eq(db.currentop_query + .aggregate([{$match: TestData.queryFilter}], + {comment: "currentop_query"}) + .itcount(), + 0); + }, + planSummary: "COLLSCAN", + currentOpFilter: currentOpFilter + }); + + delete TestData.queryFilter; + } } - function currentOpCommand(inputDB, filter, localOps) { - return inputDB.currentOp(filter); + function currentOpCommand(inputDB, filter, truncatedOps, localOps) { + return inputDB.currentOp(Object.assign(filter, {$truncateOps: truncatedOps})); } - function currentOpAgg(inputDB, filter, localOps) { + function currentOpAgg(inputDB, filter, truncatedOps, localOps) { return { - inprog: - inputDB.getSiblingDB("admin") - .aggregate([{$currentOp: {localOps: (localOps || false)}}, {$match: filter}]) - .toArray(), + inprog: inputDB.getSiblingDB("admin") + .aggregate([ + { + $currentOp: { + localOps: (localOps || false), + truncateOps: (truncatedOps || false) + } + }, + {$match: filter} + ]) + .toArray(), ok: 1 }; } for (let connType of[rsConn, mongosConn]) { for (let readMode of["commands", "legacy"]) { - for (let localOps of[false, true]) { - // Run all tests using the $currentOp aggregation stage. + for (let truncatedOps of[false, true]) { + for (let localOps of[false, true]) { + // Run all tests using the $currentOp aggregation stage. + runTests({ + conn: connType, + readMode: readMode, + currentOp: currentOpAgg, + localOps: localOps, + truncatedOps: truncatedOps + }); + } + // Run tests using the currentOp command. The 'localOps' parameter is not supported. runTests({ conn: connType, readMode: readMode, - currentOp: currentOpAgg, - localOps: localOps + currentOp: currentOpCommand, + localOps: false, + truncatedOps: truncatedOps }); } - // Run all tests using the currentOp command. The 'localOps' parameter is not supported. - runTests({ - conn: connType, - readMode: readMode, - currentOp: currentOpCommand, - truncatedOps: true - }); } } diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 0d8dac60721..a6beb415bf0 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -381,11 +381,14 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/db/commands', + ], + LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/db/namespace_string', '$BUILD_DIR/mongo/db/pipeline/aggregation_request', '$BUILD_DIR/mongo/db/query/command_request_response', '$BUILD_DIR/mongo/db/service_context', - ] + 'test_commands_enabled' + ], ) env.Library( diff --git a/src/mongo/db/commands/current_op_common.cpp b/src/mongo/db/commands/current_op_common.cpp index bd38222f0c6..cc94eeacd94 100644 --- a/src/mongo/db/commands/current_op_common.cpp +++ b/src/mongo/db/commands/current_op_common.cpp @@ -32,12 +32,20 @@ #include "mongo/db/commands/current_op_common.h" +#include <boost/container/flat_set.hpp> #include <string> #include "mongo/db/command_generic_argument.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/namespace_string.h" namespace mongo { +namespace { +static constexpr auto kAll = "$all"_sd; +static constexpr auto kOwnOps = "$ownOps"_sd; +static constexpr auto kTruncateOps = "$truncateOps"_sd; +static const boost::container::flat_set<StringData> kCurOpCmdParams = {kAll, kOwnOps, kTruncateOps}; +} // namespace bool CurrentOpCommandBase::run(OperationContext* opCtx, const std::string& dbName, @@ -53,9 +61,15 @@ bool CurrentOpCommandBase::run(OperationContext* opCtx, BSONObjBuilder currentOpBuilder; BSONObjBuilder currentOpSpecBuilder(currentOpBuilder.subobjStart("$currentOp")); - currentOpSpecBuilder.append("idleConnections", cmdObj["$all"].trueValue()); - currentOpSpecBuilder.append("allUsers", !cmdObj["$ownOps"].trueValue()); - currentOpSpecBuilder.append("truncateOps", true); + // If test commands are enabled, then we allow the currentOp commands to specify whether or not + // to truncate long operations via the '$truncateOps' parameter. Otherwise, we always truncate + // operations to match the behaviour of the legacy currentOp command. + const bool truncateOps = + !getTestCommandsEnabled() || !cmdObj[kTruncateOps] || cmdObj[kTruncateOps].trueValue(); + + currentOpSpecBuilder.append("idleConnections", cmdObj[kAll].trueValue()); + currentOpSpecBuilder.append("allUsers", !cmdObj[kOwnOps].trueValue()); + currentOpSpecBuilder.append("truncateOps", truncateOps); currentOpSpecBuilder.doneFast(); pipeline.push_back(currentOpBuilder.obj()); @@ -68,8 +82,7 @@ bool CurrentOpCommandBase::run(OperationContext* opCtx, for (const auto& elt : cmdObj) { const auto fieldName = elt.fieldNameStringData(); - if (0 == idx++ || fieldName == "$all" || fieldName == "$ownOps" || - isGenericArgument(fieldName)) { + if (0 == idx++ || kCurOpCmdParams.count(fieldName) || isGenericArgument(fieldName)) { continue; } |