summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorAdam Midvidy <amidvidy@gmail.com>2015-04-02 18:37:06 -0400
committerAdam Midvidy <amidvidy@gmail.com>2015-04-13 10:14:12 -0400
commit1991daaff1a108d98f6e9e7414a16131e244bdd4 (patch)
tree24051b5af1c645f37d4f3ecea5b5c3d64a75c9ac /src/mongo
parent5cfd95b0eb36c72f1b1b131ff1de76fe05f16cc3 (diff)
downloadmongo-1991daaff1a108d98f6e9e7414a16131e244bdd4.tar.gz
SERVER-7775 add currentOp command
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/SConscript3
-rw-r--r--src/mongo/client/dbclient.cpp4
-rw-r--r--src/mongo/db/clientlistplugin.cpp6
-rw-r--r--src/mongo/db/commands/current_op.cpp166
-rw-r--r--src/mongo/db/currentop_command.cpp185
-rw-r--r--src/mongo/db/instance.cpp11
-rw-r--r--src/mongo/db/stats/fill_locker_info.cpp62
-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/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_current_op.cpp226
-rw-r--r--src/mongo/s/commands/run_on_all_shards_cmd.cpp23
-rw-r--r--src/mongo/s/commands/run_on_all_shards_cmd.h8
-rw-r--r--src/mongo/s/commands_public.cpp19
-rw-r--r--src/mongo/s/strategy.cpp69
-rw-r--r--src/mongo/shell/db.js49
-rw-r--r--src/mongo/shell/shell_utils.cpp9
-rw-r--r--src/mongo/util/net/get_status_from_command_result.cpp9
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);
}