diff options
11 files changed, 320 insertions, 1 deletions
diff --git a/buildscripts/idl/idl/cpp_types.py b/buildscripts/idl/idl/cpp_types.py index 4fb4f1c5029..72f8f31fa57 100644 --- a/buildscripts/idl/idl/cpp_types.py +++ b/buildscripts/idl/idl/cpp_types.py @@ -665,7 +665,7 @@ class _BinDataBsonCppTypeBase(BsonCppTypeBase): def gen_deserializer_expression(self, indented_writer, object_instance): # type: (writer.IndentedTextWriter, str) -> str if self._field.bindata_subtype == 'uuid': - return common.template_args('${object_instance}.uuid()', + return common.template_args('uassertStatusOK(UUID::parse(${object_instance}))', object_instance=object_instance) return common.template_args('${object_instance}._binDataVector()', object_instance=object_instance) diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js index 96ebd5c1dfc..815cd9f1bcd 100644 --- a/jstests/core/views/views_all_commands.js +++ b/jstests/core/views/views_all_commands.js @@ -112,6 +112,7 @@ let viewsCommandTests = { _getUserCacheGeneration: {skip: isAnInternalCommand}, _hashBSONElement: {skip: isAnInternalCommand}, _isSelf: {skip: isAnInternalCommand}, + _killOperations: {skip: isUnrelated}, _mergeAuthzCollections: {skip: isAnInternalCommand}, _migrateClone: {skip: isAnInternalCommand}, _movePrimary: {skip: wasRemovedInBinaryVersion44}, diff --git a/jstests/noPassthrough/kill_operations.js b/jstests/noPassthrough/kill_operations.js new file mode 100644 index 00000000000..65ee61fd8c4 --- /dev/null +++ b/jstests/noPassthrough/kill_operations.js @@ -0,0 +1,152 @@ +// Confirms basic killOperations execution via mongod and mongos. +// @tags: [requires_replication, requires_sharding] + +(function() { +"use strict"; + +const kDbName = "kill_operations"; +const kCollName = "test"; + +const kOpKey1 = "57710eee-37cf-4c68-a3ac-0b0b900c15d2"; +const kOpKey2 = "488f6050-e331-4483-b356-230a41ec477e"; +const kOpKey3 = "c3eb12fc-4638-4464-8f51-312724ad1710"; + +const st = new ShardingTest({shards: 1, rs: {nodes: 1}, mongos: 1}); +const shardConn = st.rs0.getPrimary(); + +function blockFinds() { + assert.commandWorked(shardConn.adminCommand({ + setParameter: 1, + internalQueryExecYieldIterations: 1, + })); + assert.commandWorked(shardConn.adminCommand({ + configureFailPoint: "setYieldAllLocksHang", + mode: "alwaysOn", + data: { + shouldCheckForInterrupt: true, + nss: kDbName + "." + kCollName, + }, + })); +} + +function unblockFinds() { + assert.commandWorked(shardConn.adminCommand({ + setParameter: 1, + internalQueryExecYieldIterations: 0, + })); + assert.commandWorked(shardConn.adminCommand({ + configureFailPoint: "setYieldAllLocksHang", + mode: "off", + })); +} + +function checkForOpKey(conn, opKey) { + const uuidOpKey = UUID(opKey); + + const ret = + conn.getDB("admin") + .aggregate([{$currentOp: {localOps: true}}, {$match: {operationKey: uuidOpKey}}]) + .toArray(); + + jsTestLog(`Checked currentOp for opKey ${uuidOpKey}: ${tojson(ret)}`); + + if (ret.length == 0) { + return false; + } + + if (ret.every(op => op.killPending)) { + // CurrentOp actually blocks kills from proceeding. + return false; + } + + return true; +} + +function killOpKey(conn, opKeys) { + const uuidOpKeys = opKeys.map((strKey) => UUID(strKey)); + assert.commandWorked(conn.getDB("admin").runCommand({ + _killOperations: 1, + operationKeys: uuidOpKeys, + })); + sleep(1000); +} + +function threadRoutine({connStr, dbName, collName, opKey}) { + var client = new Mongo(connStr); + + const uuidOpKey = UUID(opKey); + jsTestLog(`Launching find at "${connStr}" with clientOpKey: ${tojson(uuidOpKey)}`); + const ret = client.getDB(dbName).runCommand({ + find: collName, + filter: {x: 1}, + limit: 1, + clientOperationKey: uuidOpKey, + }); + assert.commandFailed(ret); +} + +function runTest(conn) { + const db = conn.getDB(kDbName); + assert.commandWorked(db.dropDatabase()); + assert.commandWorked(db.getCollection(kCollName).insert({x: 1})); + + // Kill one missing opKey + killOpKey(conn, [kOpKey1]); + + // Kill multiple missing opKeys + killOpKey(conn, [kOpKey1, kOpKey2, kOpKey3]); + + try { + blockFinds(); + + // Start three finds + let threads = []; + [kOpKey1, kOpKey2, kOpKey3].forEach(key => { + let thread = + new Thread(threadRoutine, + {connStr: conn.host, dbName: kDbName, collName: kCollName, opKey: key}); + + thread.start(); + + assert.soon(function() { + return checkForOpKey(conn, key); + }, "Timed out waiting for blocked find", 10000); + + threads.push(thread); + }); + + // Kill the first thread and check the other two + killOpKey(conn, [kOpKey1]); + + assert.soon(function() { + return !checkForOpKey(conn, kOpKey1); + }, "Timed out waiting for killed find", 10000); + + assert(checkForOpKey(conn, kOpKey2)); + assert(checkForOpKey(conn, kOpKey3)); + + // Kill all three (including the already dead one) + killOpKey(conn, [kOpKey1, kOpKey2, kOpKey3]); + + assert.soon(function() { + return !checkForOpKey(conn, kOpKey2) && !checkForOpKey(conn, kOpKey3); + }, "Timed out waiting for killed find", 10000); + + unblockFinds(); + + threads.forEach(thread => { + thread.join(); + }); + } finally { + unblockFinds(); + } +} + +// Test killOp against mongod. +runTest(shardConn); + +// Test killOp against mongos. +runTest(st.s); + +st.stop(); +})(); diff --git a/jstests/sharding/database_versioning_all_commands.js b/jstests/sharding/database_versioning_all_commands.js index bd69dc68d12..c52665ac3c1 100644 --- a/jstests/sharding/database_versioning_all_commands.js +++ b/jstests/sharding/database_versioning_all_commands.js @@ -229,6 +229,7 @@ function testCommandAfterDropRecreateDatabase(testCase, st) { let testCases = { _hashBSONElement: {skip: "executes locally on mongos (not sent to any remote node)"}, _isSelf: {skip: "executes locally on mongos (not sent to any remote node)"}, + _killOperations: {skip: "executes locally on mongos (not sent to any remote node)"}, _mergeAuthzCollections: {skip: "always targets the config server"}, abortTransaction: {skip: "unversioned and uses special targetting rules"}, addShard: {skip: "not on a user database"}, diff --git a/jstests/sharding/safe_secondary_reads_drop_recreate.js b/jstests/sharding/safe_secondary_reads_drop_recreate.js index 036d70d302b..32c3ce26dba 100644 --- a/jstests/sharding/safe_secondary_reads_drop_recreate.js +++ b/jstests/sharding/safe_secondary_reads_drop_recreate.js @@ -68,6 +68,7 @@ let testCases = { _getUserCacheGeneration: {skip: "does not return user data"}, _hashBSONElement: {skip: "does not return user data"}, _isSelf: {skip: "does not return user data"}, + _killOperations: {skip: "does not return user data"}, _mergeAuthzCollections: {skip: "primary only"}, _migrateClone: {skip: "primary only"}, _shardsvrMovePrimary: {skip: "primary only"}, diff --git a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js index 3eb37a11fe8..9cf6020b056 100644 --- a/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js +++ b/jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js @@ -79,6 +79,7 @@ let testCases = { _getUserCacheGeneration: {skip: "does not return user data"}, _hashBSONElement: {skip: "does not return user data"}, _isSelf: {skip: "does not return user data"}, + _killOperations: {skip: "does not return user data"}, _mergeAuthzCollections: {skip: "primary only"}, _migrateClone: {skip: "primary only"}, _shardsvrMovePrimary: {skip: "primary only"}, diff --git a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js index 7f7efb353e4..9258ba9c4f0 100644 --- a/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js +++ b/jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js @@ -70,6 +70,7 @@ let testCases = { _getUserCacheGeneration: {skip: "does not return user data"}, _hashBSONElement: {skip: "does not return user data"}, _isSelf: {skip: "does not return user data"}, + _killOperations: {skip: "does not return user data"}, _mergeAuthzCollections: {skip: "primary only"}, _migrateClone: {skip: "primary only"}, _shardsvrMovePrimary: {skip: "primary only"}, diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index ee4954c8654..7e6f19b0286 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -101,6 +101,7 @@ env.Library( 'kill_all_sessions_by_pattern_command.cpp', 'kill_all_sessions_command.cpp', 'kill_sessions_command.cpp', + 'kill_operations_command.cpp', 'parameters.cpp', 'refresh_logical_session_cache_now.cpp', 'refresh_sessions_command.cpp', @@ -108,6 +109,7 @@ env.Library( 'start_session_command.cpp', env.Idlc('parameters.idl')[0], env.Idlc('sessions_commands.idl')[0], + env.Idlc('kill_operations.idl')[0], ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/bson/mutable/mutable_bson', @@ -121,6 +123,7 @@ env.Library( '$BUILD_DIR/mongo/db/logical_session_id_helpers', '$BUILD_DIR/mongo/db/logical_session_id', '$BUILD_DIR/mongo/db/mongohasher', + '$BUILD_DIR/mongo/db/operation_killer', '$BUILD_DIR/mongo/db/server_options_core', '$BUILD_DIR/mongo/idl/server_parameter', '$BUILD_DIR/mongo/logger/parse_log_component_settings', diff --git a/src/mongo/db/commands/kill_operations.idl b/src/mongo/db/commands/kill_operations.idl new file mode 100644 index 00000000000..d0bba956fea --- /dev/null +++ b/src/mongo/db/commands/kill_operations.idl @@ -0,0 +1,44 @@ +# Copyright (C) 2019-present MongoDB, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Server Side Public License, version 1, +# as published by MongoDB, Inc. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# Server Side Public License for more details. +# +# You should have received a copy of the Server Side Public License +# along with this program. If not, see +# <http://www.mongodb.com/licensing/server-side-public-license>. +# +# As a special exception, the copyright holders give permission to link the +# code of portions of this program with the OpenSSL library under certain +# conditions as described in each individual source file and distribute +# linked combinations including the program with the OpenSSL library. You +# must comply with the Server Side Public License in all respects for +# all of the code used other than as permitted herein. If you modify file(s) +# with this exception, you may extend this exception to your version of the +# file(s), but you are not obligated to do so. If you do not wish to do so, +# delete this exception statement from your version. If you delete this +# exception statement from all source files in the program, then also delete +# it in the license file. +# + +global: + cpp_namespace: "mongo" + +imports: + - "mongo/idl/basic_types.idl" + +commands: + _killOperations: + cpp_name: KillOperationsRequest + description: "Interrupt a list of operations on a remote server" + namespace: ignored + strict: true + fields: + operationKeys: + description: "A list of (UUID) operation key" + type: array<uuid> diff --git a/src/mongo/db/commands/kill_operations_command.cpp b/src/mongo/db/commands/kill_operations_command.cpp new file mode 100644 index 00000000000..00fb20f0e52 --- /dev/null +++ b/src/mongo/db/commands/kill_operations_command.cpp @@ -0,0 +1,114 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand + +#include "mongo/platform/basic.h" + +#include "mongo/db/commands/kill_operations_gen.h" + +#include <fmt/format.h> + +#include "mongo/base/init.h" +#include "mongo/base/status.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/client.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/test_commands_enabled.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/operation_killer.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" + +namespace mongo { + +class KillOperationsCommand final : public TypedCommand<KillOperationsCommand> { +public: + using Request = KillOperationsRequest; + + class Invocation final : public InvocationBase { + public: + using InvocationBase::InvocationBase; + + void typedRun(OperationContext* opCtx) { + using namespace fmt::literals; + + auto opKiller = OperationKiller(opCtx->getClient()); + for (auto& opKey : request().getOperationKeys()) { + log() << "Attempting to kill operation with OperationKey '{}'"_format( + opKey.toString()); + opKiller.killOperation(OperationKey(opKey)); + } + } + + private: + NamespaceString ns() const override { + return NamespaceString(request().getDbName(), ""); + } + + bool supportsWriteConcern() const override { + return false; + } + + void doCheckAuthorization(OperationContext* opCtx) const override { + auto client = opCtx->getClient(); + auto isInternal = AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::internal); + if (!getTestCommandsEnabled() && !isInternal) { + // Either the mongod/mongos must be in testing mode or this command must come from + // an internal user + uasserted(ErrorCodes::IllegalOperation, + "_killOperations is only to be used by internal users"); + } + + auto opKiller = OperationKiller(client); + if (!opKiller.isGenerallyAuthorizedToKill()) { + // While it's feasible to have coauthorized killers, for now this is intended for + // internal use, so general kill auth is implied. + uasserted(ErrorCodes::Unauthorized, "Unauthorized"); + } + } + }; + +private: + AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + return AllowedOnSecondary::kAlways; + } + + bool adminOnly() const override { + return true; + } + + std::string help() const override { + return "Internal command -- Kill operations on the target server by OperationKey."; + } +} killOperationsCmd; + +} // namespace mongo diff --git a/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp b/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp index 82f3f735605..9bc12aeba19 100644 --- a/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp +++ b/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp @@ -542,6 +542,7 @@ TEST_F(MongodbCAPITest, RunListCommands) { auto client = createClient(); std::vector<std::string> whitelist = {"_hashBSONElement", + "_killOperations", "aggregate", "buildInfo", "collMod", |