diff options
26 files changed, 443 insertions, 133 deletions
diff --git a/jstests/auth/auth3.js b/jstests/auth/auth3.js index a148b936153..b22c86b87b4 100644 --- a/jstests/auth/auth3.js +++ b/jstests/auth/auth3.js @@ -13,7 +13,7 @@ assert.eq(x.err, "unauthorized", tojson(x)); x = admin.killOp(123); assert(!("info" in x), tojson(x)); -assert.eq(x.err, "unauthorized", tojson(x)); +assert.eq(x.code, errorCodeUnauthorized, tojson(x)); x = admin.fsyncUnlock(); assert(x.errmsg != "not locked", tojson(x)); diff --git a/jstests/auth/basic_role_auth.js b/jstests/auth/basic_role_auth.js index 95b18050116..1122951fd25 100644 --- a/jstests/auth/basic_role_auth.js +++ b/jstests/auth/basic_role_auth.js @@ -149,9 +149,10 @@ var testOps = function(db, allowedActions) { }); checkErr(allowedActions.hasOwnProperty('killOp'), function() { + var errorCodeUnauthorized = 13; var res = db.killOp(1); - if (res.err == 'unauthorized') { + if (res.code == errorCodeUnauthorized) { throw Error("unauthorized killOp"); } }); diff --git a/jstests/auth/lib/commands_lib.js b/jstests/auth/lib/commands_lib.js index 8e8f22b2363..25827201154 100644 --- a/jstests/auth/lib/commands_lib.js +++ b/jstests/auth/lib/commands_lib.js @@ -1463,6 +1463,39 @@ var authCommandsLib = { ] }, { + testname: "killOp", // standalone version + command: {killOp: 1, op: 123}, + skipSharded: true, + testcases : [ + { + runOnDb: adminDbName, + roles: roles_hostManager, + privileges: [ + { resource: {cluster: true}, actions: ["killop"] } + ] + }, + { runOnDb: firstDbName, roles: {} }, + { runOnDb: secondDbName, roles: {} } + ] + }, + { + testname: "killOp", // sharded version + command: {killOp: 1, op: "shard1:123"}, + skipStandalone: true, + testcases : [ + { + runOnDb: adminDbName, + roles: roles_hostManager, + privileges: [ + { resource: {cluster: true}, actions: ["killop"] } + ], + expectFail: true // we won't be able to find the shardId + }, + { runOnDb: firstDbName, roles: {} }, + { runOnDb: secondDbName, roles: {} } + ] + }, + { testname: "listCommands", command: {listCommands: 1}, testcases: [ diff --git a/jstests/auth/pseudo_commands.js b/jstests/auth/pseudo_commands.js index 17cbf25a8e1..006d84e147d 100644 --- a/jstests/auth/pseudo_commands.js +++ b/jstests/auth/pseudo_commands.js @@ -119,59 +119,16 @@ function runTest(conn) { } var res = db.killOp(opid); printjson(res); - passed = !res.err && !res['$err']; + passed = res.ok && !res.errmsg && !res.err && !res['$err']; } catch (e) { passed = false; } - assert.eq(shouldPass, passed); } testProperAuthorization(testFunc, roles, privilege); })(); - (function testKillop() { - jsTestLog("Testing killOp"); - - var roles = {read: false, - readAnyDatabase: false, - readWrite: false, - readWriteAnyDatabase: false, - dbAdmin: false, - dbAdminAnyDatabase: false, - dbOwner: false, - clusterMonitor: false, - clusterManager: false, - hostManager: true, - clusterAdmin: true, - root: true, - __system: true - }; - - var privilege = { resource: {cluster: true}, actions: ['killop'] }; - - var testFunc = function(shouldPass) { - var passed = true; - try { - var opid; - if (isMongos(db)) { // opid format different between mongos and mongod - opid = "shard0000:1234"; - } else { - opid = 1234; - } - var res = db.killOp(opid); - printjson(res); - passed = !res.err && !res['$err']; - } catch (e) { - passed = false; - } - - assert.eq(shouldPass, passed); - } - - testProperAuthorization(testFunc, roles, privilege); - })(); - (function testUnlock() { if (isMongos(db)) { return; // unlock doesn't work on mongos diff --git a/jstests/multiVersion/kill_op_pseudocommand.js b/jstests/multiVersion/kill_op_pseudocommand.js new file mode 100644 index 00000000000..b5acd4ad774 --- /dev/null +++ b/jstests/multiVersion/kill_op_pseudocommand.js @@ -0,0 +1,68 @@ +(function() { + "use strict"; + // Test that a 3.2 Mongos will correctly translate a killOp command + // to a killop pseudocommand when talking to an old shard + + // TODO: Remove after mongodb 3.2 is released + + // Sharded cluster + // -- latest mongos + // -- latest config + // -- one 3.0 shard + var options = { + mongosOptions: {binVersion: "3.1"}, + configOptions: {binVersion: "3.1"}, + shardOptions: {binVersion: "3.0"} + }; + + var st = new ShardingTest({name: "killOp-multiver", shards: 1, other: options}); + + var db = st.s.getDB("killOp-multiver"); + var db1 = db; + db.dropDatabase(); + assert.commandWorked(db.adminCommand({enableSharding: db.getName()})); + + var testCol = db.tc; + + // start long running op + testCol.insert({"foo": "bar"}); + jsTestLog("Starting long-running $where operation"); + + var start = new Date(); + + var parShell = startParallelShell( + 'db.getSiblingDB("killOp-multiver").tc.count( { $where: function() { while( 1 ) { ; } }})', + st.s.port); + var findOpId = function () { + var curOps = db.currentOp(); + var inProg = curOps.inprog; + var opId = null; + inProg.forEach(function(op) { + if ((op.active === true) && + (op.ns === "killOp-multiver.tc") && + (op.query.count === "tc")) { + + opId = op.opid; + } + }); + return opId; + }; + + var opToKill = null; + + do { + opToKill = findOpId() + sleep(25); + } while (opToKill === null); + + db.killOp(opToKill); + try { + parShell(); // wait for query to end + } catch (ex) {} // ignore + + var end = new Date(); + + // make sure the query didn't end due to js op timeout + assert.lt(diff, 30000, "Query was killed due to timeout - not killOp"); + st.stop(); +})(); diff --git a/jstests/sharding/auth.js b/jstests/sharding/auth.js index 41783fcce1a..3de61fafa0d 100644 --- a/jstests/sharding/auth.js +++ b/jstests/sharding/auth.js @@ -294,11 +294,8 @@ function runTest(s) { assert.throws(function() { printjson(readOnlyDB.currentOp()); }); - assert.throws(function() { - printjson(readOnlyDB.killOp(123)); - }); + assert.commandFailed(readOnlyDB.killOp(123)); // fsyncUnlock doesn't work in mongos anyway, so no need check authorization for it - /* broken because of SERVER-4156 print( " testing write command (should fail)" ); @@ -316,9 +313,7 @@ function runTest(s) { assert.throws(function() { printjson(readOnlyDB.currentOp()); }); - assert.throws(function() { - printjson(readOnlyDB.killOp(123)); - }); + assert.commandFailed(readOnlyDB.killOp(123)); // fsyncUnlock doesn't work in mongos anyway, so no need check authorization for it } diff --git a/src/mongo/SConscript b/src/mongo/SConscript index f281d1cca66..7303b035560 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -57,7 +57,11 @@ env.SConscript(['base/SConscript', 'util/concurrency/SConscript', 'util/options_parser/SConscript', 'util/cmdline_utils/SConscript', - 'util/mongoutils/SConscript']) + 'util/mongoutils/SConscript', + 'util/net/SConscript', + ] +) + def add_exe( v ): return "${PROGPREFIX}%s${PROGSUFFIX}" % v @@ -342,6 +346,7 @@ env.Library('clientdriver', [ ], LIBDEPS=['$BUILD_DIR/mongo/db/auth/authcommon', '$BUILD_DIR/mongo/crypto/scramauth', + '$BUILD_DIR/mongo/util/net/command_status', 'network' ]) @@ -753,6 +758,7 @@ serverOnlyFiles = [ "db/background.cpp", "db/commands/getmore_cmd.cpp", "db/commands/group.cpp", "db/commands/index_filter_commands.cpp", + "db/commands/kill_op.cpp", "db/commands/list_collections.cpp", "db/commands/list_databases.cpp", "db/commands/list_indexes.cpp", diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp index be4f1653811..df6b4c39fae 100644 --- a/src/mongo/client/dbclient.cpp +++ b/src/mongo/client/dbclient.cpp @@ -47,6 +47,7 @@ #include "mongo/util/assert_util.h" #include "mongo/util/debug_util.h" #include "mongo/util/log.h" +#include "mongo/util/net/get_status_from_command_result.h" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/net/ssl_options.h" #include "mongo/util/password_digest.h" @@ -478,6 +479,43 @@ namespace mongo { return runCommand(dbname, b.done(), *info); } + bool DBClientWithCommands::runPseudoCommand(StringData db, + StringData realCommandName, + StringData pseudoCommandCol, + const BSONObj& cmdArgs, + BSONObj& info, + int options) { + + BSONObjBuilder bob; + bob.append(realCommandName, 1); + bob.appendElements(cmdArgs); + auto cmdObj = bob.done(); + + bool success = false; + + if (!(success = runCommand(db.toString(), cmdObj, info, options))) { + + auto status = getStatusFromCommandResult(info); + verify(!status.isOK()); + + if (status == ErrorCodes::CommandResultSchemaViolation) { + msgasserted(28624, str::stream() << "Received bad " + << realCommandName + << " response from server: " + << info); + } else if (status == ErrorCodes::CommandNotFound || + str::startsWith(status.reason(), "no such")) { + + NamespaceString pseudoCommandNss(db, pseudoCommandCol); + // if this throws we just let it escape as that's how runCommand works. + info = findOne(pseudoCommandNss.ns(), cmdArgs, nullptr, options); + return true; + } + } + + return success; + } + unsigned long long DBClientWithCommands::count(const string &myns, const BSONObj& query, int options, int limit, int skip ) { BSONObj cmd = _countCmd( myns , query , options , limit , skip ); BSONObj res; diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h index 87c7f69f37d..e2d6bafe0d8 100644 --- a/src/mongo/client/dbclientinterface.h +++ b/src/mongo/client/dbclientinterface.h @@ -41,6 +41,7 @@ #include "mongo/db/jsobj.h" #include "mongo/logger/log_severity.h" #include "mongo/platform/atomic_word.h" +#include "mongo/platform/cstdint.h" #include "mongo/stdx/functional.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/message.h" @@ -847,7 +848,6 @@ namespace mongo { bool setDbProfilingLevel(const std::string &dbname, ProfilingLevel level, BSONObj *info = 0); bool getDbProfilingLevel(const std::string &dbname, ProfilingLevel& level, BSONObj *info = 0); - /** This implicitly converts from char*, string, and BSONObj to be an argument to mapreduce You shouldn't need to explicitly construct this */ @@ -1035,6 +1035,24 @@ namespace mongo { return _postRunCommandHook; } + /** + * Run a pseudo-command such as sys.inprog/currentOp, sys.killop/killOp + * or sys.unlock/fsyncUnlock + * + * The real command will be tried first, and if the remote server does not + * implement the command, it will fall back to the pseudoCommand. + * + * The cmdArgs parameter should NOT include {<commandName>: 1}. + * + * TODO: remove after MongoDB 3.2 is released and replace all callers with + * a call to plain runCommand + */ + virtual bool runPseudoCommand(StringData db, + StringData realCommandName, + StringData pseudoCommandCol, + const BSONObj& cmdArgs, + BSONObj& info, + int options=0); protected: /** if the result of a command is ok*/ diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 299c671fa51..c0bc6f93a08 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -14,7 +14,6 @@ env.Library( 'field_ref.cpp', 'field_ref_set.cpp', 'field_parser.cpp', - 'get_status_from_command_result.cpp', 'keypattern.cpp', 'write_concern_options.cpp' ], diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 8278d91fcad..45a84e3452e 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -38,8 +38,8 @@ #include <string> #include <vector> -#include "mongo/bson/mutable/document.h" #include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/mutable/document.h" #include "mongo/db/audit.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" @@ -47,12 +47,12 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/client.h" -#include "mongo/db/get_status_from_command_result.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/server_parameters.h" #include "mongo/s/write_ops/wc_error_detail.h" #include "mongo/util/log.h" +#include "mongo/util/net/get_status_from_command_result.h" namespace mongo { diff --git a/src/mongo/db/commands/kill_op.cpp b/src/mongo/db/commands/kill_op.cpp new file mode 100644 index 00000000000..e8f80928558 --- /dev/null +++ b/src/mongo/db/commands/kill_op.cpp @@ -0,0 +1,91 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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 <limits> + +#include "mongo/base/init.h" +#include "mongo/base/status.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/db/audit.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/client.h" +#include "mongo/db/commands.h" +#include "mongo/db/service_context.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + + class KillOpCommand : public Command { + public: + + KillOpCommand() : Command("killOp") {} + + bool isWriteCommandForConfigServer() const final { return false; } + + bool slaveOk() const final { return true; } + + bool adminOnly() const final { return true; } + + Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) final { + + bool isAuthorized = client->getAuthorizationSession()->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), + ActionType::killop); + return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + + bool run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result, + bool fromRepl) final { + + long long op; + uassertStatusOK(bsonExtractIntegerField(cmdObj, "op", &op)); + + log() << "going to kill op: " << op; + result.append("info", "attempting to kill op"); + + uassert(26823, str::stream() << "invalid op : " << op, + (op >= 0) && (op <= std::numeric_limits<unsigned int>::max())); + + getGlobalServiceContext()->killOperation(static_cast<unsigned int>(op)); + return true; + } + } killOpCmd; + +} // namespace mongo diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 2ab4f1e58a3..f5efc2938d4 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -150,36 +150,6 @@ namespace mongo { MONGO_FP_DECLARE(rsStopGetMore); - void killOp( OperationContext* txn, Message &m, DbResponse &dbresponse ) { - DbMessage d(m); - QueryMessage q(d); - BSONObj obj; - - const bool isAuthorized = txn->getClient()->getAuthorizationSession()->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::killop); - audit::logKillOpAuthzCheck(txn->getClient(), - q.query, - isAuthorized ? ErrorCodes::OK : ErrorCodes::Unauthorized); - if (!isAuthorized) { - obj = fromjson("{\"err\":\"unauthorized\"}"); - } - /*else if( !dbMutexInfo.isLocked() ) - obj = fromjson("{\"info\":\"no op in progress/not locked\"}"); - */ - else { - BSONElement e = q.query.getField("op"); - if( !e.isNumber() ) { - obj = fromjson("{\"err\":\"no op number field specified?\"}"); - } - else { - log() << "going to kill op: " << e << endl; - obj = fromjson("{\"info\":\"attempting to kill op\"}"); - getGlobalServiceContext()->killOperation( (unsigned) e.number() ); - } - } - replyToQuery(0, m, dbresponse, obj); - } - namespace { void generateErrorResponse(const AssertionException* exception, @@ -310,12 +280,18 @@ namespace { DbResponse& dbResponse, Message& message, StringData realCommandName) { - Message interposed; + DbMessage originalDbm(message); + originalDbm.pullInt(); // ntoskip + originalDbm.pullInt(); // ntoreturn + auto cmdParams = originalDbm.nextJsObj(); + + Message interposed; NamespaceString interposedNss(nss.db(), "$cmd"); BSONObjBuilder cmdBob; cmdBob.append(realCommandName, 1); + cmdBob.appendElements(cmdParams); auto cmd = cmdBob.done(); // TODO: use OP_COMMAND here instead of constructing @@ -416,7 +392,7 @@ namespace { return; } if (nsString.coll() == "$cmd.sys.killop") { - killOp(txn, m, dbresponse); + receivedPseudoCommand(txn, nsString, c, dbresponse, m, "killOp"); return; } if (nsString.coll() == "$cmd.sys.unlock") { diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index f6fa162790b..e8d797785de 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -90,10 +90,10 @@ env.Library('repl_coordinator_impl', '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/db/index/index_descriptor', '$BUILD_DIR/mongo/fail_point', - '$BUILD_DIR/mongo/foundation', '$BUILD_DIR/mongo/global_optime', '$BUILD_DIR/mongo/server_options_core', '$BUILD_DIR/mongo/service_context', + '$BUILD_DIR/mongo/util/net/command_status', 'repl_coordinator_interface', 'replica_set_messages', 'replication_executor', @@ -230,9 +230,9 @@ env.Library( ], LIBDEPS=[ 'replication_executor', - '$BUILD_DIR/mongo/db/common', '$BUILD_DIR/mongo/logger/logger', '$BUILD_DIR/mongo/namespace_string', + '$BUILD_DIR/mongo/util/net/command_status', ], ) diff --git a/src/mongo/db/repl/fetcher.cpp b/src/mongo/db/repl/fetcher.cpp index ab0198a9363..d691fe4bca6 100644 --- a/src/mongo/db/repl/fetcher.cpp +++ b/src/mongo/db/repl/fetcher.cpp @@ -30,12 +30,12 @@ #include "mongo/db/repl/fetcher.h" -#include "mongo/db/get_status_from_command_result.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/repl/replication_executor.h" #include "mongo/util/assert_util.h" #include "mongo/util/mongoutils/str.h" +#include "mongo/util/net/get_status_from_command_result.h" namespace mongo { namespace repl { diff --git a/src/mongo/db/repl/freshness_checker.cpp b/src/mongo/db/repl/freshness_checker.cpp index 62e514c6793..3f533e28f6e 100644 --- a/src/mongo/db/repl/freshness_checker.cpp +++ b/src/mongo/db/repl/freshness_checker.cpp @@ -34,12 +34,12 @@ #include "mongo/base/status.h" #include "mongo/bson/optime.h" -#include "mongo/db/get_status_from_command_result.h" #include "mongo/db/repl/member_heartbeat_data.h" #include "mongo/db/repl/replica_set_config.h" #include "mongo/db/repl/replication_executor.h" #include "mongo/db/repl/scatter_gather_runner.h" #include "mongo/util/log.h" +#include "mongo/util/net/get_status_from_command_result.h" #include "mongo/util/scopeguard.h" #include "mongo/util/time_support.h" diff --git a/src/mongo/db/repl/network_interface_impl.cpp b/src/mongo/db/repl/network_interface_impl.cpp index 998fe7016ee..8a7f50e6bbe 100644 --- a/src/mongo/db/repl/network_interface_impl.cpp +++ b/src/mongo/db/repl/network_interface_impl.cpp @@ -42,7 +42,6 @@ #include "mongo/db/auth/internal_user_auth.h" #include "mongo/db/client.h" #include "mongo/db/concurrency/d_concurrency.h" -#include "mongo/db/get_status_from_command_result.h" #include "mongo/db/operation_context_impl.h" #include "mongo/db/repl/network_interface_impl_downconvert_find_getmore.h" #include "mongo/platform/unordered_map.h" @@ -50,6 +49,7 @@ #include "mongo/stdx/list.h" #include "mongo/util/assert_util.h" #include "mongo/util/log.h" +#include "mongo/util/net/get_status_from_command_result.h" #include "mongo/util/net/hostandport.h" #include "mongo/util/time_support.h" diff --git a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp index 2479d831247..9ec647d8dee 100644 --- a/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp +++ b/src/mongo/db/repl/replication_coordinator_impl_heartbeat.cpp @@ -33,7 +33,6 @@ #include <algorithm> #include "mongo/base/status.h" -#include "mongo/db/get_status_from_command_result.h" #include "mongo/db/operation_context.h" #include "mongo/db/repl/elect_cmd_runner.h" #include "mongo/db/repl/freshness_checker.h" @@ -49,6 +48,7 @@ #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" +#include "mongo/util/net/get_status_from_command_result.h" #include "mongo/util/time_support.h" namespace mongo { diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index 0a2f7b19a3c..3d359c9e357 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -20,6 +20,7 @@ env.Library( 'cluster_index_filter_cmd.cpp', 'cluster_is_db_grid_cmd.cpp', 'cluster_is_master_cmd.cpp', + 'cluster_kill_op.cpp', 'cluster_list_databases_cmd.cpp', 'cluster_list_shards_cmd.cpp', 'cluster_merge_chunks_cmd.cpp', diff --git a/src/mongo/s/commands/cluster_kill_op.cpp b/src/mongo/s/commands/cluster_kill_op.cpp new file mode 100644 index 00000000000..88f43fd88d9 --- /dev/null +++ b/src/mongo/s/commands/cluster_kill_op.cpp @@ -0,0 +1,121 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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 <string> + +#include "mongo/bson/bsonelement.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/client/connpool.h" +#include "mongo/client/dbclientinterface.h" +#include "mongo/db/audit.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/s/shard.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace { + + class ClusterKillOpCommand : public Command { + public: + ClusterKillOpCommand() : Command("killOp") {} + + bool isWriteCommandForConfigServer() const final { return false; } + + bool slaveOk() const final { return true; } + + bool adminOnly() const final { return true; } + + Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) final { + + bool isAuthorized = client->getAuthorizationSession()->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), + ActionType::killop); + return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + + bool run(OperationContext* txn, + const std::string& db, + BSONObj& cmdObj, + int options, + std::string& errmsg, + BSONObjBuilder& result, + bool fromRepl) final { + + // The format of op is shardid:opid + // This is different than the format passed to the mongod killOp command. + std::string opToKill; + uassertStatusOK(bsonExtractStringField(cmdObj, "op", &opToKill)); + + auto opSepPos = opToKill.find(':'); + + uassert(28625, + str::stream() << "The op argument to killOp must be of the format shardid:opid" + << " but found \"" << opToKill << '"', + (opToKill.size() >= 3) &&// must have at least N:N + (opSepPos != std::string::npos) && // must have ':' as separator + (opSepPos != 0) && // can't be :NN + (opSepPos != (opToKill.size() - 1))); // can't be NN: + + auto shardIdent = opToKill.substr(0, opSepPos); + auto opId = std::stoi(opToKill.substr(opSepPos + 1)); + + // shardid is actually the opid - keeping for backwards compatibility. + result.append("shard" , shardIdent); + result.append("shardid", opId); + + log() << "want to kill op: " << opToKill; + + // Will throw if shard id is not found + Shard s(shardIdent); + ScopedDbConnection conn(s.getConnString()); + BSONObj cmdRes; + BSONObjBuilder argsBob; + argsBob.append("op", opId); + auto args = argsBob.done(); + // intentionally ignore return value - that is how legacy killOp worked. + conn->runPseudoCommand("admin", "killOp", "$cmd.sys.killop", args, cmdRes); + conn.done(); + // The original behavior of killOp on mongos is to always return success, regardless of + // whether the shard reported success or not. + return true; + } + + } clusterKillOpCommand; + +} // namespace +} // namespace mongo diff --git a/src/mongo/s/strategy.cpp b/src/mongo/s/strategy.cpp index 553d4dc1a2b..3901d11ed9d 100644 --- a/src/mongo/s/strategy.cpp +++ b/src/mongo/s/strategy.cpp @@ -330,6 +330,7 @@ namespace mongo { } } + // TODO: remove after MongoDB 3.2 bool Strategy::handleSpecialNamespaces( Request& r , QueryMessage& q ) { const char * ns = strstr( r.getns() , ".$cmd.sys." ); if ( ! ns ) @@ -385,40 +386,18 @@ namespace mongo { arr.done(); } else if ( strcmp( ns , "killop" ) == 0 ) { - const bool isAuthorized = authSession->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::killop); - audit::logKillOpAuthzCheck( - client, - q.query, - isAuthorized ? ErrorCodes::OK : ErrorCodes::Unauthorized); - uassert(ErrorCodes::Unauthorized, "not authorized to run killop", isAuthorized); - - BSONElement e = q.query["op"]; - if ( e.type() != String ) { - b.append( "err" , "bad op" ); - b.append( e ); - } - else { - b.append( e ); - string s = e.String(); - string::size_type i = s.find( ':' ); - if ( i == string::npos ) { - b.append( "err" , "bad opid" ); - } - else { - string shard = s.substr( 0 , i ); - int opid = atoi( s.substr( i + 1 ).c_str() ); - b.append( "shard" , shard ); - b.append( "shardid" , opid ); - - log() << "want to kill op: " << e << endl; - Shard s(shard); - - ScopedDbConnection conn(s.getConnString()); - conn->findOne( r.getns() , BSON( "op" << opid ) ); - conn.done(); - } - } + BSONObjBuilder cmdBob; + cmdBob.append("killOp", 1); + cmdBob.appendElements(q.query); // fields are validated by ClusterKillOpCommand + auto interposedCmd = cmdBob.done(); + + NamespaceString nss(r.getns()); + NamespaceString interposedNss(nss.db(), "$cmd"); + + Command::runAgainstRegistered(interposedNss.ns().c_str(), + interposedCmd, + b, + q.queryOptions); } else if ( strcmp( ns , "unlock" ) == 0 ) { b.append( "err" , "can't do unlock through mongos" ); diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index 382b6b33fab..b73e0e9f2e2 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -698,7 +698,15 @@ DB.prototype.currentOP = DB.prototype.currentOp; DB.prototype.killOp = function(op) { if( !op ) throw Error("no opNum to kill specified"); - return this.$cmd.sys.killop.findOne({'op':op}); + var res = this.adminCommand({'killOp': 1, 'op': op}); + if (!res.ok && + (res.errmsg.startsWith("no such cmd") || + res.errmsg.startsWith("no such command")) || + res.code === 59) { + // fall back for old servers + res = this.$cmd.sys.killop.findOne({'op': op}); + } + return res; } DB.prototype.killOP = DB.prototype.killOp; diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index ebc903e6488..a4895af7bdf 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -322,7 +322,12 @@ namespace mongo { BSONForEach( op, inprog ) { if ( uris.count( op[ "client" ].String() ) ) { if ( !withPrompt || prompter.confirm() ) { - conn->findOne( "admin.$cmd.sys.killop", QUERY( "op"<< op[ "opid" ] ) ); + BSONObjBuilder cmdBob; + BSONObj info; + cmdBob.append("op", op["opid"]); + auto cmdArgs = cmdBob.done(); + conn->runPseudoCommand("admin", "killOp", "$cmd.sys.killop", + cmdArgs, info); } else { return; diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript new file mode 100644 index 00000000000..4bd51bed099 --- /dev/null +++ b/src/mongo/util/net/SConscript @@ -0,0 +1,14 @@ +# -*- mode: python -*- + +Import("env") + + +# TODO(amidvidy) move to OP_COMMAND subdirectory +env.Library( + target='command_status', + source='get_status_from_command_result.cpp', + LIBDEPS=[ + '$BUILD_DIR/mongo/bson', + '$BUILD_DIR/mongo/foundation' + ] +) diff --git a/src/mongo/db/get_status_from_command_result.cpp b/src/mongo/util/net/get_status_from_command_result.cpp index 14c3ffbd25e..febb4a14958 100644 --- a/src/mongo/db/get_status_from_command_result.cpp +++ b/src/mongo/util/net/get_status_from_command_result.cpp @@ -28,7 +28,7 @@ #include "mongo/platform/basic.h" -#include "mongo/db/get_status_from_command_result.h" +#include "mongo/util/net/get_status_from_command_result.h" #include "mongo/base/status.h" #include "mongo/db/jsobj.h" diff --git a/src/mongo/db/get_status_from_command_result.h b/src/mongo/util/net/get_status_from_command_result.h index 6c930ccd017..6c930ccd017 100644 --- a/src/mongo/db/get_status_from_command_result.h +++ b/src/mongo/util/net/get_status_from_command_result.h |