diff options
author | Adam Midvidy <amidvidy@gmail.com> | 2015-04-02 18:37:06 -0400 |
---|---|---|
committer | Adam Midvidy <amidvidy@gmail.com> | 2015-04-13 10:14:12 -0400 |
commit | 1991daaff1a108d98f6e9e7414a16131e244bdd4 (patch) | |
tree | 24051b5af1c645f37d4f3ecea5b5c3d64a75c9ac /src/mongo | |
parent | 5cfd95b0eb36c72f1b1b131ff1de76fe05f16cc3 (diff) | |
download | mongo-1991daaff1a108d98f6e9e7414a16131e244bdd4.tar.gz |
SERVER-7775 add currentOp command
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/client/dbclient.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/clientlistplugin.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/commands/current_op.cpp | 166 | ||||
-rw-r--r-- | src/mongo/db/currentop_command.cpp | 185 | ||||
-rw-r--r-- | src/mongo/db/instance.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/stats/fill_locker_info.cpp | 62 | ||||
-rw-r--r-- | src/mongo/db/stats/fill_locker_info.h (renamed from src/mongo/db/currentop_command.h) | 19 | ||||
-rw-r--r-- | src/mongo/s/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_current_op.cpp | 226 | ||||
-rw-r--r-- | src/mongo/s/commands/run_on_all_shards_cmd.cpp | 23 | ||||
-rw-r--r-- | src/mongo/s/commands/run_on_all_shards_cmd.h | 8 | ||||
-rw-r--r-- | src/mongo/s/commands_public.cpp | 19 | ||||
-rw-r--r-- | src/mongo/s/strategy.cpp | 69 | ||||
-rw-r--r-- | src/mongo/shell/db.js | 49 | ||||
-rw-r--r-- | src/mongo/shell/shell_utils.cpp | 9 | ||||
-rw-r--r-- | src/mongo/util/net/get_status_from_command_result.cpp | 9 |
17 files changed, 558 insertions, 311 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript index 6087caba743..4e764d4fdf4 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -753,6 +753,7 @@ serverOnlyFiles = [ "db/background.cpp", "db/commands/copydb_start_commands.cpp", "db/commands/count.cpp", "db/commands/create_indexes.cpp", + "db/commands/current_op.cpp", "db/commands/dbhash.cpp", "db/commands/distinct.cpp", "db/commands/drop_indexes.cpp", @@ -784,7 +785,6 @@ serverOnlyFiles = [ "db/background.cpp", "db/commands/write_commands/write_commands.cpp", "db/commands/writeback_compatibility_shim.cpp", "db/curop.cpp", - "db/currentop_command.cpp", "db/dbcommands.cpp", "db/dbdirectclient.cpp", "db/dbeval.cpp", @@ -836,6 +836,7 @@ serverOnlyFiles = [ "db/background.cpp", "db/repl/sync.cpp", "db/repl/sync_source_feedback.cpp", "db/repl/sync_tail.cpp", + "db/stats/fill_locker_info.cpp", "db/stats/lock_server_status_section.cpp", "db/stats/range_deleter_server_status.cpp", "db/stats/snapshots.cpp", diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp index df6b4c39fae..e03938a5e94 100644 --- a/src/mongo/client/dbclient.cpp +++ b/src/mongo/client/dbclient.cpp @@ -503,9 +503,7 @@ namespace mongo { << realCommandName << " response from server: " << info); - } else if (status == ErrorCodes::CommandNotFound || - str::startsWith(status.reason(), "no such")) { - + } else if (status == ErrorCodes::CommandNotFound) { 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); diff --git a/src/mongo/db/clientlistplugin.cpp b/src/mongo/db/clientlistplugin.cpp index 6f0a30efedc..4aa31d48bd7 100644 --- a/src/mongo/db/clientlistplugin.cpp +++ b/src/mongo/db/clientlistplugin.cpp @@ -37,11 +37,11 @@ #include "mongo/db/client.h" #include "mongo/db/commands.h" #include "mongo/db/curop.h" -#include "mongo/db/currentop_command.h" -#include "mongo/db/service_context.h" +#include "mongo/db/dbwebserver.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/operation_context.h" -#include "mongo/db/dbwebserver.h" +#include "mongo/db/service_context.h" +#include "mongo/db/stats/fill_locker_info.h" #include "mongo/util/mongoutils/html.h" #include "mongo/util/stringutils.h" diff --git a/src/mongo/db/commands/current_op.cpp b/src/mongo/db/commands/current_op.cpp new file mode 100644 index 00000000000..c4e9f49c799 --- /dev/null +++ b/src/mongo/db/commands/current_op.cpp @@ -0,0 +1,166 @@ +/** + * 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/db/auth/action_type.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/client.h" +#include "mongo/db/commands.h" +#include "mongo/db/commands/fsync.h" +#include "mongo/db/curop.h" +#include "mongo/db/dbmessage.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/matcher/matcher.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/stats/fill_locker_info.h" +#include "mongo/util/log.h" + +namespace mongo { + + class CurrentOpCommand : public Command { + public: + + CurrentOpCommand() : Command("currentOp") {} + + 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::inprog); + 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 { + + const bool includeAll = cmdObj["$all"].trueValue(); + + // Filter the output + BSONObj filter; + { + BSONObjBuilder b; + BSONObjIterator i(cmdObj); + invariant(i.more()); + i.next(); // skip {currentOp: 1} which is required to be the first element + while (i.more()) { + BSONElement e = i.next(); + if (str::equals("$all", e.fieldName())) { + continue; + } + + b.append(e); + } + filter = b.obj(); + } + + const WhereCallbackReal whereCallback(txn, db); + const Matcher matcher(filter, whereCallback); + + BSONArrayBuilder inprogBuilder(result.subarrayStart("inprog")); + + boost::lock_guard<boost::mutex> scopedLock(Client::clientsMutex); + + ClientSet::const_iterator it = Client::clients.begin(); + for ( ; it != Client::clients.end(); it++) { + Client* client = *it; + invariant(client); + + boost::unique_lock<Client> uniqueLock(*client); + const OperationContext* opCtx = client->getOperationContext(); + + if (!includeAll) { + // Skip over inactive connections. + if (!opCtx || !opCtx->getCurOp() || !opCtx->getCurOp()->active()) { + continue; + } + } + + BSONObjBuilder infoBuilder; + + // The client information + client->reportState(infoBuilder); + + // Operation context specific information + if (opCtx) { + // CurOp + if (opCtx->getCurOp()) { + opCtx->getCurOp()->reportState(&infoBuilder); + } + + // LockState + Locker::LockerInfo lockerInfo; + client->getOperationContext()->lockState()->getLockerInfo(&lockerInfo); + fillLockerInfo(lockerInfo, infoBuilder); + } + else { + // If no operation context, mark the operation as inactive + infoBuilder.append("active", false); + } + + infoBuilder.done(); + + const BSONObj info = infoBuilder.obj(); + + if (includeAll || matcher.matches(info)) { + inprogBuilder.append(info); + } + } + + inprogBuilder.done(); + + if (lockedForWriting()) { + result.append("fsyncLock", true); + result.append("info", + "use db.fsyncUnlock() to terminate the fsync write/snapshot lock"); + } + + return true; + } + + } currentOpCommand; + +} // namespace mongo diff --git a/src/mongo/db/currentop_command.cpp b/src/mongo/db/currentop_command.cpp deleted file mode 100644 index 7ec8c8d3d80..00000000000 --- a/src/mongo/db/currentop_command.cpp +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright (C) 2008-2014 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 "mongo/db/currentop_command.h" - -#include "mongo/db/audit.h" -#include "mongo/db/auth/action_type.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/bson/bsonobj.h" -#include "mongo/db/client.h" -#include "mongo/db/curop.h" -#include "mongo/db/commands/fsync.h" -#include "mongo/db/dbmessage.h" -#include "mongo/db/namespace_string.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/matcher/matcher.h" -#include "mongo/util/log.h" - -namespace mongo { - - using std::stringstream; - - void inProgCmd(OperationContext* txn, - const NamespaceString& nss, - Message &message, - DbResponse &dbresponse) { - DbMessage d(message); - QueryMessage q(d); - - const bool isAuthorized = - txn->getClient()->getAuthorizationSession()->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), - ActionType::inprog); - audit::logInProgAuthzCheck(txn->getClient(), - q.query, - isAuthorized ? ErrorCodes::OK : ErrorCodes::Unauthorized); - - BSONObjBuilder retVal; - - if (!isAuthorized) { - retVal.append("err", "unauthorized"); - replyToQuery(0, message, dbresponse, retVal.obj()); - return; - } - - const bool includeAll = q.query["$all"].trueValue(); - - // Filter the output - BSONObj filter; - { - BSONObjBuilder b; - BSONObjIterator i(q.query); - while (i.more()) { - BSONElement e = i.next(); - if (str::equals("$all", e.fieldName())) { - continue; - } - - b.append(e); - } - filter = b.obj(); - } - - const WhereCallbackReal whereCallback(txn, nss.db()); - const Matcher matcher(filter, whereCallback); - - BSONArrayBuilder inprogBuilder(retVal.subarrayStart("inprog")); - - boost::lock_guard<boost::mutex> scopedLock(Client::clientsMutex); - - ClientSet::const_iterator it = Client::clients.begin(); - for ( ; it != Client::clients.end(); it++) { - Client* client = *it; - invariant(client); - - boost::unique_lock<Client> uniqueLock(*client); - const OperationContext* opCtx = client->getOperationContext(); - - if (!includeAll) { - // Skip over inactive connections. - if (!opCtx || !opCtx->getCurOp() || !opCtx->getCurOp()->active()) { - continue; - } - } - - BSONObjBuilder infoBuilder; - - // The client information - client->reportState(infoBuilder); - - // Operation context specific information - if (opCtx) { - // CurOp - if (opCtx->getCurOp()) { - opCtx->getCurOp()->reportState(&infoBuilder); - } - - // LockState - Locker::LockerInfo lockerInfo; - client->getOperationContext()->lockState()->getLockerInfo(&lockerInfo); - fillLockerInfo(lockerInfo, infoBuilder); - } - else { - // If no operation context, mark the operation as inactive - infoBuilder.append("active", false); - } - - infoBuilder.done(); - - const BSONObj info = infoBuilder.obj(); - - if (includeAll || matcher.matches(info)) { - inprogBuilder.append(info); - } - } - - inprogBuilder.done(); - - if (lockedForWriting()) { - retVal.append("fsyncLock", true); - retVal.append("info", - "use db.fsyncUnlock() to terminate the fsync write/snapshot lock"); - } - - replyToQuery(0, message, dbresponse, retVal.obj()); - } - - - void fillLockerInfo(const Locker::LockerInfo& lockerInfo, BSONObjBuilder& infoBuilder) { - // "locks" section - BSONObjBuilder locks(infoBuilder.subobjStart("locks")); - for (size_t i = 0; i < lockerInfo.locks.size(); i++) { - const Locker::OneLock& lock = lockerInfo.locks[i]; - - if (resourceIdLocalDB == lock.resourceId) { - locks.append("local", legacyModeName(lock.mode)); - } - else { - locks.append( - resourceTypeName(lock.resourceId.getType()), legacyModeName(lock.mode)); - } - } - locks.done(); - - // "waitingForLock" section - infoBuilder.append("waitingForLock", lockerInfo.waitingResource.isValid()); - - // "lockStats" section - { - BSONObjBuilder lockStats(infoBuilder.subobjStart("lockStats")); - lockerInfo.stats.report(&lockStats); - lockStats.done(); - } - } - -} // namespace mongo diff --git a/src/mongo/db/instance.cpp b/src/mongo/db/instance.cpp index 94736779cfe..96748d818e9 100644 --- a/src/mongo/db/instance.cpp +++ b/src/mongo/db/instance.cpp @@ -50,7 +50,6 @@ #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/concurrency/lock_state.h" #include "mongo/db/concurrency/write_conflict_exception.h" -#include "mongo/db/currentop_command.h" #include "mongo/db/db.h" #include "mongo/db/db_raii.h" #include "mongo/db/dbdirectclient.h" @@ -391,11 +390,19 @@ namespace { isCommand = true; opwrite(m); } + // TODO: remove this entire code path after 3.2. Refs SERVER-7775 else if (nsString.isSpecialCommand()) { opwrite(m); if (nsString.coll() == "$cmd.sys.inprog") { - inProgCmd(txn, nsString, m, dbresponse); + // HACK: + // legacy inprog could run on any database. The currentOp command + // can only run on 'admin'. To avoid breaking old shells and a multitude + // of third-party tools, we rewrite the namespace. As auth is checked + // later in Command::_checkAuthorizationImpl, we will still properly + // reject the request if the client is not authorized. + NamespaceString adminKludge("admin", nsString.coll()); + receivedPseudoCommand(txn, adminKludge, c, dbresponse, m, "currentOp"); return; } if (nsString.coll() == "$cmd.sys.killop") { diff --git a/src/mongo/db/stats/fill_locker_info.cpp b/src/mongo/db/stats/fill_locker_info.cpp new file mode 100644 index 00000000000..1bdcb2762f1 --- /dev/null +++ b/src/mongo/db/stats/fill_locker_info.cpp @@ -0,0 +1,62 @@ +/** + * 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. + */ + +#include "mongo/db/concurrency/locker.h" +#include "mongo/db/jsobj.h" +#include "mongo/db/stats/fill_locker_info.h" + +namespace mongo { + + void fillLockerInfo(const Locker::LockerInfo& lockerInfo, BSONObjBuilder& infoBuilder) { + // "locks" section + BSONObjBuilder locks(infoBuilder.subobjStart("locks")); + for (size_t i = 0; i < lockerInfo.locks.size(); i++) { + const Locker::OneLock& lock = lockerInfo.locks[i]; + + if (resourceIdLocalDB == lock.resourceId) { + locks.append("local", legacyModeName(lock.mode)); + } + else { + locks.append(resourceTypeName(lock.resourceId.getType()), + legacyModeName(lock.mode)); + } + } + locks.done(); + + // "waitingForLock" section + infoBuilder.append("waitingForLock", lockerInfo.waitingResource.isValid()); + + // "lockStats" section + { + BSONObjBuilder lockStats(infoBuilder.subobjStart("lockStats")); + lockerInfo.stats.report(&lockStats); + lockStats.done(); + } + } + +} // namespace mongo diff --git a/src/mongo/db/currentop_command.h b/src/mongo/db/stats/fill_locker_info.h index 8aa892522c5..b4743764e25 100644 --- a/src/mongo/db/currentop_command.h +++ b/src/mongo/db/stats/fill_locker_info.h @@ -1,5 +1,5 @@ /** - * Copyright (C) 2014 MongoDB Inc. + * 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, @@ -32,24 +32,9 @@ namespace mongo { - class BSONObjBuilder; - struct DbResponse; - class Message; - class NamespaceString; - class OperationContext; - - /** - * Executes the db.currentOp() command. Currently not an actual "command" object, but should - * be converted to one at some point. - */ - void inProgCmd(OperationContext* txn, - const NamespaceString& nss, - Message &m, - DbResponse &dbresponse); - /** * Constructs a human-readable BSON from the specified LockerInfo structure. */ void fillLockerInfo(const Locker::LockerInfo& lockerInfo, BSONObjBuilder& infoBuilder); -} // namespace mongo +} // namespace mongo diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index 21e44d2fe1c..f5f1076ab54 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -8,6 +8,7 @@ env.Library( 'cluster_add_shard_cmd.cpp', 'cluster_commands_common.cpp', 'cluster_count_cmd.cpp', + 'cluster_current_op.cpp', 'cluster_drop_database_cmd.cpp', 'cluster_enable_sharding_cmd.cpp', 'cluster_explain_cmd.cpp', diff --git a/src/mongo/s/commands/cluster_current_op.cpp b/src/mongo/s/commands/cluster_current_op.cpp new file mode 100644 index 00000000000..50c5ad4390c --- /dev/null +++ b/src/mongo/s/commands/cluster_current_op.cpp @@ -0,0 +1,226 @@ +/* + * 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 <vector> +#include <tuple> + +#include "mongo/client/connpool.h" +#include "mongo/db/auth/action_type.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/commands.h" +#include "mongo/db/jsobj.h" +#include "mongo/s/commands/run_on_all_shards_cmd.h" +#include "mongo/s/strategy.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace { + + const char kInprogFieldName[] = "inprog"; + const char kOpIdFieldName[] = "opid"; + const char kClientFieldName[] = "client"; + // awkward underscores used to make this visually distinct from kClientFieldName + const char kClient_S_FieldName[] = "client_s"; + const char kLegacyInprogCollection[] = "$cmd.sys.inprog"; + + const char kCommandName[] = "currentOp"; + + class ClusterCurrentOpCommand : public RunOnAllShardsCommand { + public: + + ClusterCurrentOpCommand() : RunOnAllShardsCommand(kCommandName) { } + + 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::inprog); + + return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized"); + } + + // TODO remove after 3.2 + BSONObj specialErrorHandler(const std::string& server, + const std::string& db, + const BSONObj& cmdObj, + const BSONObj& originalResult) const final { + + // it is unfortunate that this logic needs to be duplicated from + // DBClientWithCommands::runPseudoCommand + // but I don't see a better way to do it without performing heart surgery on + // Future/CommandResponse. + + auto status = getStatusFromCommandResult(originalResult); + invariant(!status.isOK()); + + uassert(28629, + str::stream() << "Received bad " + << kCommandName + << " response from server " << server + << " got: " << originalResult, + status != ErrorCodes::CommandResultSchemaViolation); + + // getStatusFromCommandResult handles cooercing "no such command" into the right + // Status type + if (status == ErrorCodes::CommandNotFound) { + // fall back to the old inprog pseudo-command + NamespaceString pseudoCommandNss("admin", kLegacyInprogCollection); + BSONObj legacyResult; + + BSONObjBuilder legacyCommandBob; + + // need to exclude {currentOp: 1} + for (auto&& cmdElem : cmdObj) { + if (cmdElem.fieldNameStringData() != kCommandName) { + legacyCommandBob.append(cmdElem); + } + } + auto legacyCommand = legacyCommandBob.done(); + + try { + ScopedDbConnection conn(server); + legacyResult = + conn->findOne(pseudoCommandNss.ns(), legacyCommand); + + } + catch (const DBException& ex) { + // If there is a non-DBException exception the entire operation will be + // terminated, as that would be a programmer error. + + // We convert the exception to a BSONObj so that the ordinary + // failure path for RunOnAllShardsCommand will handle the failure + + // TODO: consider adding an exceptionToBSONObj utility? + BSONObjBuilder b; + b.append("errmsg", ex.toString()); + b.append("code", ex.getCode()); + return b.obj(); + } + return legacyResult; + } + // if the command failed for another reason then we don't retry it. + return originalResult; + } + + void aggregateResults(const std::vector<ShardAndReply>& results, + BSONObjBuilder& output) final { + // Each shard responds with a document containing an array of subdocuments. + // Each subdocument represents an operation running on that shard. + // We merge the responses into a single document containg an array + // of the operations from all shards. + + // There are two modifications we make. + // 1) we prepend the shardid (with a colon separator) to the opid of each operation. + // This allows users to pass the value of the opid field directly to killOp. + + // 2) we change the field name of "client" to "client_s". This is because each + // client is actually a mongos. + + // TODO: failpoint for a shard response being invalid. + + // Error handling - we maintain the same behavior as legacy currentOp/inprog + // that is, if any shard replies with an invalid response (i.e. it does not + // contain a field 'inprog' that is an array), we ignore it. + // + // If there is a lower level error (i.e. the command fails, network error, etc) + // RunOnAllShardsCommand will handle returning an error to the user. + BSONArrayBuilder aggregatedOpsBab(output.subarrayStart(kInprogFieldName)); + + for (auto&& shardResponse : results) { + + StringData shardName; + BSONObj shardResponseObj; + std::tie(shardName, shardResponseObj) = shardResponse; + + auto shardOps = shardResponseObj[kInprogFieldName]; + + // legacy behavior + if (!shardOps.isABSONObj()) { + warning() << "invalid currentOp response from shard " + << shardName + << ", got: " + << shardOps; + continue; + } + + for (auto&& shardOp : shardOps.Obj()) { + BSONObjBuilder modifiedShardOpBob; + + // maintain legacy behavior + // but log it first + if (!shardOp.isABSONObj()) { + warning() << "invalid currentOp response from shard " + << shardName + << ", got: " + << shardOp; + continue; + } + + for (auto&& shardOpElement : shardOp.Obj()) { + auto fieldName = shardOpElement.fieldNameStringData(); + if (fieldName == kOpIdFieldName) { + uassert(28630, + str::stream() << "expected numeric opid from currentOp response" + << " from shard " << shardName + << ", got: " << shardOpElement, + shardOpElement.isNumber()); + + modifiedShardOpBob.append(kOpIdFieldName, + str::stream() << shardName + << ":" + << shardOpElement.numberInt()); + } + else if (fieldName == kClientFieldName) { + modifiedShardOpBob.appendAs(shardOpElement, kClient_S_FieldName); + } + else { + modifiedShardOpBob.append(shardOpElement); + } + } + modifiedShardOpBob.done(); + // append the modified document to the output array + aggregatedOpsBab.append(modifiedShardOpBob.obj()); + } + } + aggregatedOpsBab.done(); + } + + } clusterCurrentOpCmd; + +} // namespace +} // namespace mongo diff --git a/src/mongo/s/commands/run_on_all_shards_cmd.cpp b/src/mongo/s/commands/run_on_all_shards_cmd.cpp index 699f492fe0b..4a1bc37a961 100644 --- a/src/mongo/s/commands/run_on_all_shards_cmd.cpp +++ b/src/mongo/s/commands/run_on_all_shards_cmd.cpp @@ -51,7 +51,7 @@ namespace mongo { , _useShardConn(useShardConn) {} - void RunOnAllShardsCommand::aggregateResults(const std::vector<BSONObj>& results, + void RunOnAllShardsCommand::aggregateResults(const std::vector<ShardAndReply>& results, BSONObjBuilder& output) {} @@ -83,7 +83,6 @@ namespace mongo { getShards(dbName, cmdObj, shards); // TODO: Future is deprecated, replace with commandOp() - std::list< boost::shared_ptr<Future::CommandResult> > futures; for (std::set<Shard>::const_iterator i=shards.begin(), end=shards.end() ; i != end ; i++) { futures.push_back( Future::spawnCommand( i->getConnString(), @@ -94,20 +93,25 @@ namespace mongo { _useShardConn )); } - std::vector<BSONObj> results; + std::vector<ShardAndReply> results; BSONObjBuilder subobj (output.subobjStart("raw")); BSONObjBuilder errors; int commonErrCode = -1; - for ( std::list< boost::shared_ptr<Future::CommandResult> >::iterator i=futures.begin(); - i!=futures.end(); i++ ) { + std::list< boost::shared_ptr<Future::CommandResult> >::iterator futuresit; + std::set<Shard>::const_iterator shardsit; + // We iterate over the set of shards and their corresponding futures in parallel. + // TODO: replace with zip iterator if we ever decide to use one from Boost or elsewhere + for (futuresit = futures.begin(), shardsit = shards.cbegin(); + futuresit != futures.end() && shardsit != shards.end(); + ++futuresit, ++shardsit ) { - boost::shared_ptr<Future::CommandResult> res = *i; + boost::shared_ptr<Future::CommandResult> res = *futuresit; if ( res->join() ) { // success :) BSONObj result = res->result(); - results.push_back( result ); + results.emplace_back( shardsit->getName(), result ); subobj.append( res->getServer(), result ); continue; } @@ -121,7 +125,7 @@ namespace mongo { BSONElement errmsg = result["errmsg"]; if ( errmsg.eoo() || errmsg.String().empty() ) { // it was fixed! - results.push_back( result ); + results.emplace_back( shardsit->getName(), result ); subobj.append( res->getServer(), result ); continue; } @@ -145,8 +149,7 @@ namespace mongo { else if ( commonErrCode != errCode ) { commonErrCode = 0; } - - results.push_back( result ); + results.emplace_back( shardsit->getName(), result ); subobj.append( res->getServer(), result ); } diff --git a/src/mongo/s/commands/run_on_all_shards_cmd.h b/src/mongo/s/commands/run_on_all_shards_cmd.h index f9f5bd2691f..f84643d1513 100644 --- a/src/mongo/s/commands/run_on_all_shards_cmd.h +++ b/src/mongo/s/commands/run_on_all_shards_cmd.h @@ -27,8 +27,10 @@ */ #include <string> +#include <tuple> #include <vector> +#include "mongo/base/string_data.h" #include "mongo/db/commands.h" namespace mongo { @@ -59,7 +61,11 @@ namespace mongo { bool adminOnly() const override { return false; } bool isWriteCommandForConfigServer() const override { return false; } - virtual void aggregateResults(const std::vector<BSONObj>& results, + // The StringData contains the shard ident. + // This can be used to create an instance of Shard + using ShardAndReply = std::tuple<StringData, BSONObj>; + + virtual void aggregateResults(const std::vector<ShardAndReply>& results, BSONObjBuilder& output); // The default implementation is the identity function. diff --git a/src/mongo/s/commands_public.cpp b/src/mongo/s/commands_public.cpp index 95fd9da14ec..11acb53636e 100644 --- a/src/mongo/s/commands_public.cpp +++ b/src/mongo/s/commands_public.cpp @@ -32,6 +32,8 @@ #include "mongo/platform/basic.h" +#include <tuple> + #include <boost/scoped_ptr.hpp> #include <boost/shared_ptr.hpp> @@ -422,9 +424,12 @@ namespace { actions.addAction(ActionType::validate); out->push_back(Privilege(parseResourcePattern(dbname, cmdObj), actions)); } - virtual void aggregateResults(const vector<BSONObj>& results, BSONObjBuilder& output) { - for (vector<BSONObj>::const_iterator it(results.begin()), end(results.end()); it!=end; it++){ - const BSONObj& result = *it; + virtual void aggregateResults(const vector<ShardAndReply>& results, + BSONObjBuilder& output) { + + for (vector<ShardAndReply>::const_iterator it(results.begin()), end(results.end()); + it!=end; it++) { + const BSONObj& result = std::get<1>(*it); const BSONElement valid = result["valid"]; if (!valid.eoo()){ if (!valid.trueValue()) { @@ -469,7 +474,8 @@ namespace { out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions)); } - virtual void aggregateResults(const vector<BSONObj>& results, BSONObjBuilder& output) { + virtual void aggregateResults(const vector<ShardAndReply>& results, + BSONObjBuilder& output) { long long objects = 0; long long unscaledDataSize = 0; long long dataSize = 0; @@ -482,8 +488,9 @@ namespace { long long freeListNum = 0; long long freeListSize = 0; - for (vector<BSONObj>::const_iterator it(results.begin()), end(results.end()); it != end; ++it) { - const BSONObj& b = *it; + for (vector<ShardAndReply>::const_iterator it(results.begin()), end(results.end()); + it != end; ++it) { + const BSONObj& b = std::get<1>(*it); objects += b["objects"].numberLong(); unscaledDataSize += b["avgObjSize"].numberLong() * b["objects"].numberLong(); dataSize += b["dataSize"].numberLong(); diff --git a/src/mongo/s/strategy.cpp b/src/mongo/s/strategy.cpp index 12191034629..d34c0256616 100644 --- a/src/mongo/s/strategy.cpp +++ b/src/mongo/s/strategy.cpp @@ -343,77 +343,36 @@ namespace mongo { return false; ns += 10; - BSONObjBuilder b; - vector<Shard> shards; + BSONObjBuilder reply; - ClientBasic* client = ClientBasic::getCurrent(); - AuthorizationSession* authSession = client->getAuthorizationSession(); - if ( strcmp( ns , "inprog" ) == 0 ) { - const bool isAuthorized = authSession->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::inprog); - audit::logInProgAuthzCheck( - client, q.query, isAuthorized ? ErrorCodes::OK : ErrorCodes::Unauthorized); - uassert(ErrorCodes::Unauthorized, "not authorized to run inprog", isAuthorized); - - Shard::getAllShards( shards ); - - BSONArrayBuilder arr( b.subarrayStart( "inprog" ) ); - - for ( unsigned i=0; i<shards.size(); i++ ) { - Shard shard = shards[i]; - ScopedDbConnection conn(shard.getConnString()); - BSONObj temp = conn->findOne( r.getns() , q.query ); - if ( temp["inprog"].isABSONObj() ) { - BSONObjIterator i( temp["inprog"].Obj() ); - while ( i.more() ) { - BSONObjBuilder x; - - BSONObjIterator j( i.next().Obj() ); - while( j.more() ) { - BSONElement e = j.next(); - if ( str::equals( e.fieldName() , "opid" ) ) { - stringstream ss; - ss << shard.getName() << ':' << e.numberInt(); - x.append( "opid" , ss.str() ); - } - else if ( str::equals( e.fieldName() , "client" ) ) { - x.appendAs( e , "client_s" ); - } - else { - x.append( e ); - } - } - arr.append( x.obj() ); - } - } - conn.done(); - } - - arr.done(); - } - else if ( strcmp( ns , "killop" ) == 0 ) { + const auto upgradeToRealCommand = [&r, &q, &reply](StringData commandName) { BSONObjBuilder cmdBob; - cmdBob.append("killOp", 1); - cmdBob.appendElements(q.query); // fields are validated by ClusterKillOpCommand + cmdBob.append(commandName, 1); + cmdBob.appendElements(q.query); // fields are validated by Commands auto interposedCmd = cmdBob.done(); - NamespaceString nss(r.getns()); NamespaceString interposedNss(nss.db(), "$cmd"); - Command::runAgainstRegistered(interposedNss.ns().c_str(), interposedCmd, - b, + reply, q.queryOptions); + }; + + if ( strcmp( ns , "inprog" ) == 0 ) { + upgradeToRealCommand("currentOp"); + } + else if ( strcmp( ns , "killop" ) == 0 ) { + upgradeToRealCommand("killOp"); } else if ( strcmp( ns , "unlock" ) == 0 ) { - b.append( "err" , "can't do unlock through mongos" ); + reply.append( "err" , "can't do unlock through mongos" ); } else { warning() << "unknown sys command [" << ns << "]" << endl; return false; } - BSONObj x = b.done(); + BSONObj x = reply.done(); replyToQuery(0, r.p(), r.m(), x); return true; } diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index 5bebc1b1878..d686e750485 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -683,7 +683,14 @@ DB.prototype.toString = function(){ DB.prototype.isMaster = function () { return this.runCommand("isMaster"); } -DB.prototype.currentOp = function( arg ){ +var commandUnsupported = function(res) { + return (!res.ok && + (res.errmsg.startsWith("no such cmd") || + res.errmsg.startsWith("no such command") || + res.code === 59 /* CommandNotFound */)) +}; + +DB.prototype.currentOp = function(arg) { var q = {} if ( arg ) { if ( typeof( arg ) == "object" ) @@ -692,17 +699,20 @@ DB.prototype.currentOp = function( arg ){ q["$all"] = true; } - // always send currentOp with default (null) read preference (SERVER-17951) - var _readPref = this.getMongo().getReadPrefMode(); - - try { - this.getMongo().setReadPref(null); - var results = this.$cmd.sys.inprog.findOne( q ); - } finally { - this.getMongo().setReadPref(_readPref); + var commandObj = {"currentOp": 1}; + Object.extend(commandObj, q); + var res = this.adminCommand(commandObj); + if (commandUnsupported(res)) { + // always send legacy currentOp with default (null) read preference (SERVER-17951) + var _readPref = this.getMongo().getReadPrefMode(); + try { + this.getMongo().setReadPref(null); + res = this.getSiblingDB("admin").$cmd.sys.inprog.findOne( q ); + } finally { + this.getMongo().setReadPref(_readPref); + } } - - return results + return res; } DB.prototype.currentOP = DB.prototype.currentOp; @@ -710,17 +720,12 @@ DB.prototype.killOp = function(op) { if( !op ) throw Error("no opNum to kill specified"); 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) { - + if (commandUnsupported(res)) { // fall back for old servers var _readPref = this.getMongo().getReadPrefMode(); - try { this.getMongo().setReadPref(null); - res = this.$cmd.sys.killop.findOne({'op': op}); + res = this.getSiblingDB("admin").$cmd.sys.killop.findOne({'op': op}); } finally { this.getMongo().setReadPref(_readPref); } @@ -1006,13 +1011,7 @@ DB.prototype.fsyncLock = function() { DB.prototype.fsyncUnlock = function() { var res = this.adminCommand({fsyncUnlock: 1}); - if (!res.ok && - // handle both error messages for nonexistent command... - (res.errmsg.startsWith("no such cmd") || - res.errmsg.startsWith("no such command") || - res.code === 59)) { - // fallback for old servers - + if (commandUnsupported(res)) { var _readPref = this.getMongo().getReadPrefMode(); try { this.getMongo().setReadPref(null); diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index a4895af7bdf..f6dba857250 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -316,9 +316,12 @@ namespace mongo { } const set<string>& uris = i->second; - - BSONObj inprog = conn->findOne( "admin.$cmd.sys.inprog", Query() )[ "inprog" ] - .embeddedObject().getOwned(); + + BSONObj currentOpRes; + conn->runPseudoCommand("admin", + "currentOp", + "$cmd.sys.inprog", {}, currentOpRes); + auto inprog = currentOpRes["inprog"].embeddedObject(); BSONForEach( op, inprog ) { if ( uris.count( op[ "client" ].String() ) ) { if ( !withPrompt || prompter.confirm() ) { diff --git a/src/mongo/util/net/get_status_from_command_result.cpp b/src/mongo/util/net/get_status_from_command_result.cpp index febb4a14958..48c8e9a07e9 100644 --- a/src/mongo/util/net/get_status_from_command_result.cpp +++ b/src/mongo/util/net/get_status_from_command_result.cpp @@ -59,6 +59,15 @@ namespace mongo { else if (!errmsgElement.eoo()) { errmsg = errmsgElement.toString(); } + + // we can't use startsWith(errmsg, "no such") + // as we have errors such as "no such collection" + if (code == ErrorCodes::UnknownError && + (str::startsWith(errmsg, "no such cmd") || + str::startsWith(errmsg, "no such command"))) { + code = ErrorCodes::CommandNotFound; + } + return Status(ErrorCodes::Error(code), errmsg); } |