/* * 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 . * * 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 #include #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/commands/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 kCommandName[] = "currentOp"; class ClusterCurrentOpCommand : public RunOnAllShardsCommand { public: ClusterCurrentOpCommand() : RunOnAllShardsCommand(kCommandName) {} bool adminOnly() const final { return true; } Status checkAuthForCommand(Client* client, const std::string& dbname, const BSONObj& cmdObj) final { bool isAuthorized = AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( ResourcePattern::forClusterResource(), ActionType::inprog); return isAuthorized ? Status::OK() : Status(ErrorCodes::Unauthorized, "Unauthorized"); } virtual bool supportsWriteConcern(const BSONObj& cmd) const override { return false; } void aggregateResults(const std::vector& 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: " << redact(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: " << redact(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