summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildscripts/idl/idl/cpp_types.py2
-rw-r--r--jstests/core/views/views_all_commands.js1
-rw-r--r--jstests/noPassthrough/kill_operations.js152
-rw-r--r--jstests/sharding/database_versioning_all_commands.js1
-rw-r--r--jstests/sharding/safe_secondary_reads_drop_recreate.js1
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js1
-rw-r--r--jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js1
-rw-r--r--src/mongo/db/commands/SConscript3
-rw-r--r--src/mongo/db/commands/kill_operations.idl44
-rw-r--r--src/mongo/db/commands/kill_operations_command.cpp114
-rw-r--r--src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp1
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",