diff options
Diffstat (limited to 'src/mongo/db/commands.cpp')
-rw-r--r-- | src/mongo/db/commands.cpp | 381 |
1 files changed, 194 insertions, 187 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 25ee7429189..b7b67b5092d 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -73,58 +73,30 @@ const WriteConcernOptions kMajorityWriteConcern( } // namespace -Command::~Command() = default; - -BSONObj Command::appendPassthroughFields(const BSONObj& cmdObjWithPassthroughFields, - const BSONObj& request) { - BSONObjBuilder b; - b.appendElements(request); - for (const auto& elem : - Command::filterCommandRequestForPassthrough(cmdObjWithPassthroughFields)) { - const auto name = elem.fieldNameStringData(); - if (Command::isGenericArgument(name) && !request.hasField(name)) { - b.append(elem); - } - } - return b.obj(); -} - -BSONObj Command::appendMajorityWriteConcern(const BSONObj& cmdObj) { - - WriteConcernOptions newWC = kMajorityWriteConcern; - - if (cmdObj.hasField(kWriteConcernField)) { - auto wc = cmdObj.getField(kWriteConcernField); - // The command has a writeConcern field and it's majority, so we can - // return it as-is. - if (wc["w"].ok() && wc["w"].str() == "majority") { - return cmdObj; - } - if (wc["wtimeout"].ok()) { - // They set a timeout, but aren't using majority WC. We want to use their - // timeout along with majority WC. - newWC = WriteConcernOptions(WriteConcernOptions::kMajority, - WriteConcernOptions::SyncMode::UNSET, - wc["wtimeout"].Number()); - } - } +////////////////////////////////////////////////////////////// +// CommandHelpers - // Append all original fields except the writeConcern field to the new command. - BSONObjBuilder cmdObjWithWriteConcern; - for (const auto& elem : cmdObj) { - const auto name = elem.fieldNameStringData(); - if (name != "writeConcern" && !cmdObjWithWriteConcern.hasField(name)) { - cmdObjWithWriteConcern.append(elem); - } +BSONObj CommandHelpers::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) { + auto command = globalCommandRegistry()->findCommand(request.getCommandName()); + invariant(command); + BSONObjBuilder out; + try { + bool ok = command->publicRun(opCtx, request, out); + appendCommandStatus(out, ok); + } catch (const StaleConfigException&) { + // These exceptions are intended to be handled at a higher level and cannot losslessly + // round-trip through Status. + throw; + } catch (const DBException& ex) { + out.resetToEmpty(); + appendCommandStatus(out, ex.toStatus()); } - - // Finally, add the new write concern. - cmdObjWithWriteConcern.append(kWriteConcernField, newWC.toBSON()); - return cmdObjWithWriteConcern.obj(); + return out.obj(); } -std::string Command::parseNsFullyQualified(const std::string& dbname, const BSONObj& cmdObj) { +std::string CommandHelpers::parseNsFullyQualified(const std::string& dbname, + const BSONObj& cmdObj) { BSONElement first = cmdObj.firstElement(); uassert(ErrorCodes::BadValue, str::stream() << "collection name has invalid type " << typeName(first.type()), @@ -136,8 +108,8 @@ std::string Command::parseNsFullyQualified(const std::string& dbname, const BSON return nss.ns(); } -NamespaceString Command::parseNsCollectionRequired(const std::string& dbname, - const BSONObj& cmdObj) { +NamespaceString CommandHelpers::parseNsCollectionRequired(const std::string& dbname, + const BSONObj& cmdObj) { // Accepts both BSON String and Symbol for collection name per SERVER-16260 // TODO(kangas) remove Symbol support in MongoDB 3.0 after Ruby driver audit BSONElement first = cmdObj.firstElement(); @@ -151,9 +123,9 @@ NamespaceString Command::parseNsCollectionRequired(const std::string& dbname, return nss; } -NamespaceString Command::parseNsOrUUID(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj) { +NamespaceString CommandHelpers::parseNsOrUUID(OperationContext* opCtx, + const std::string& dbname, + const BSONObj& cmdObj) { BSONElement first = cmdObj.firstElement(); if (first.type() == BinData && first.binDataType() == BinDataType::newUUID) { UUIDCatalog& catalog = UUIDCatalog::get(opCtx); @@ -178,51 +150,11 @@ NamespaceString Command::parseNsOrUUID(OperationContext* opCtx, } } -std::string Command::parseNs(const std::string& dbname, const BSONObj& cmdObj) const { - BSONElement first = cmdObj.firstElement(); - if (first.type() != mongo::String) - return dbname; - - return str::stream() << dbname << '.' << cmdObj.firstElement().valueStringData(); -} - -ResourcePattern Command::parseResourcePattern(const std::string& dbname, - const BSONObj& cmdObj) const { - const std::string ns = parseNs(dbname, cmdObj); - if (!NamespaceString::validCollectionComponent(ns)) { - return ResourcePattern::forDatabaseName(ns); - } - return ResourcePattern::forExactNamespace(NamespaceString(ns)); -} - -Command::Command(StringData name, StringData oldName) - : _name(name.toString()), - _commandsExecutedMetric("commands." + _name + ".total", &_commandsExecuted), - _commandsFailedMetric("commands." + _name + ".failed", &_commandsFailed) { - globalCommandRegistry()->registerCommand(this, name, oldName); -} - -void Command::help(std::stringstream& help) const { - help << "no help defined"; -} - -Status Command::explain(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - ExplainOptions::Verbosity verbosity, - BSONObjBuilder* out) const { - return {ErrorCodes::IllegalOperation, str::stream() << "Cannot explain cmd: " << getName()}; -} - -BSONObj Command::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) { - return CommandHelpers::runCommandDirectly(opCtx, request); -} - -Command* Command::findCommand(StringData name) { +Command* CommandHelpers::findCommand(StringData name) { return globalCommandRegistry()->findCommand(name); } -bool Command::appendCommandStatus(BSONObjBuilder& result, const Status& status) { +bool CommandHelpers::appendCommandStatus(BSONObjBuilder& result, const Status& status) { appendCommandStatus(result, status.isOK(), status.reason()); BSONObj tmp = result.asTempObj(); if (!status.isOK() && !tmp.hasField("code")) { @@ -235,7 +167,9 @@ bool Command::appendCommandStatus(BSONObjBuilder& result, const Status& status) return status.isOK(); } -void Command::appendCommandStatus(BSONObjBuilder& result, bool ok, const std::string& errmsg) { +void CommandHelpers::appendCommandStatus(BSONObjBuilder& result, + bool ok, + const std::string& errmsg) { BSONObj tmp = result.asTempObj(); bool have_ok = tmp.hasField("ok"); bool need_errmsg = !ok && !tmp.hasField("errmsg"); @@ -248,9 +182,9 @@ void Command::appendCommandStatus(BSONObjBuilder& result, bool ok, const std::st } } -void Command::appendCommandWCStatus(BSONObjBuilder& result, - const Status& awaitReplicationStatus, - const WriteConcernResult& wcResult) { +void CommandHelpers::appendCommandWCStatus(BSONObjBuilder& result, + const Status& awaitReplicationStatus, + const WriteConcernResult& wcResult) { if (!awaitReplicationStatus.isOK() && !result.hasField("writeConcernError")) { WriteConcernErrorDetail wcError; wcError.setErrCode(awaitReplicationStatus.code()); @@ -262,6 +196,165 @@ void Command::appendCommandWCStatus(BSONObjBuilder& result, } } +BSONObj CommandHelpers::appendPassthroughFields(const BSONObj& cmdObjWithPassthroughFields, + const BSONObj& request) { + BSONObjBuilder b; + b.appendElements(request); + for (const auto& elem : filterCommandRequestForPassthrough(cmdObjWithPassthroughFields)) { + const auto name = elem.fieldNameStringData(); + if (isGenericArgument(name) && !request.hasField(name)) { + b.append(elem); + } + } + return b.obj(); +} + +BSONObj CommandHelpers::appendMajorityWriteConcern(const BSONObj& cmdObj) { + WriteConcernOptions newWC = kMajorityWriteConcern; + + if (cmdObj.hasField(kWriteConcernField)) { + auto wc = cmdObj.getField(kWriteConcernField); + // The command has a writeConcern field and it's majority, so we can + // return it as-is. + if (wc["w"].ok() && wc["w"].str() == "majority") { + return cmdObj; + } + + if (wc["wtimeout"].ok()) { + // They set a timeout, but aren't using majority WC. We want to use their + // timeout along with majority WC. + newWC = WriteConcernOptions(WriteConcernOptions::kMajority, + WriteConcernOptions::SyncMode::UNSET, + wc["wtimeout"].Number()); + } + } + + // Append all original fields except the writeConcern field to the new command. + BSONObjBuilder cmdObjWithWriteConcern; + for (const auto& elem : cmdObj) { + const auto name = elem.fieldNameStringData(); + if (name != "writeConcern" && !cmdObjWithWriteConcern.hasField(name)) { + cmdObjWithWriteConcern.append(elem); + } + } + + // Finally, add the new write concern. + cmdObjWithWriteConcern.append(kWriteConcernField, newWC.toBSON()); + return cmdObjWithWriteConcern.obj(); +} + +namespace { +const stdx::unordered_set<std::string> userManagementCommands{"createUser", + "updateUser", + "dropUser", + "dropAllUsersFromDatabase", + "grantRolesToUser", + "revokeRolesFromUser", + "createRole", + "updateRole", + "dropRole", + "dropAllRolesFromDatabase", + "grantPrivilegesToRole", + "revokePrivilegesFromRole", + "grantRolesToRole", + "revokeRolesFromRole", + "_mergeAuthzCollections"}; +} // namespace + +bool CommandHelpers::isUserManagementCommand(const std::string& name) { + return userManagementCommands.count(name); +} + +BSONObj CommandHelpers::filterCommandRequestForPassthrough(const BSONObj& cmdObj) { + BSONObjBuilder bob; + for (auto elem : cmdObj) { + const auto name = elem.fieldNameStringData(); + if (name == "$readPreference") { + BSONObjBuilder(bob.subobjStart("$queryOptions")).append(elem); + } else if (!isGenericArgument(name) || // + name == "$queryOptions" || // + name == "maxTimeMS" || // + name == "readConcern" || // + name == "writeConcern" || // + name == "lsid" || // + name == "txnNumber") { + // This is the whitelist of generic arguments that commands can be trusted to blindly + // forward to the shards. + bob.append(elem); + } + } + return bob.obj(); +} + +void CommandHelpers::filterCommandReplyForPassthrough(const BSONObj& cmdObj, + BSONObjBuilder* output) { + for (auto elem : cmdObj) { + const auto name = elem.fieldNameStringData(); + if (name == "$configServerState" || // + name == "$gleStats" || // + name == "$clusterTime" || // + name == "$oplogQueryData" || // + name == "$replData" || // + name == "operationTime") { + continue; + } + output->append(elem); + } +} + +BSONObj CommandHelpers::filterCommandReplyForPassthrough(const BSONObj& cmdObj) { + BSONObjBuilder bob; + filterCommandReplyForPassthrough(cmdObj, &bob); + return bob.obj(); +} + +bool CommandHelpers::isHelpRequest(const BSONElement& helpElem) { + return !helpElem.eoo() && helpElem.trueValue(); +} + +constexpr StringData CommandHelpers::kHelpFieldName; + +////////////////////////////////////////////////////////////// +// Command + +Command::~Command() = default; + +std::string Command::parseNs(const std::string& dbname, const BSONObj& cmdObj) const { + BSONElement first = cmdObj.firstElement(); + if (first.type() != mongo::String) + return dbname; + + return str::stream() << dbname << '.' << cmdObj.firstElement().valueStringData(); +} + +ResourcePattern Command::parseResourcePattern(const std::string& dbname, + const BSONObj& cmdObj) const { + const std::string ns = parseNs(dbname, cmdObj); + if (!NamespaceString::validCollectionComponent(ns)) { + return ResourcePattern::forDatabaseName(ns); + } + return ResourcePattern::forExactNamespace(NamespaceString(ns)); +} + +Command::Command(StringData name, StringData oldName) + : _name(name.toString()), + _commandsExecutedMetric("commands." + _name + ".total", &_commandsExecuted), + _commandsFailedMetric("commands." + _name + ".failed", &_commandsFailed) { + globalCommandRegistry()->registerCommand(this, name, oldName); +} + +void Command::help(std::stringstream& help) const { + help << "no help defined"; +} + +Status Command::explain(OperationContext* opCtx, + const std::string& dbname, + const BSONObj& cmdObj, + ExplainOptions::Verbosity verbosity, + BSONObjBuilder* out) const { + return {ErrorCodes::IllegalOperation, str::stream() << "Cannot explain cmd: " << getName()}; +} + Status BasicCommand::checkAuthForRequest(OperationContext* opCtx, const OpMsgRequest& request) { uassertNoDocumentSequences(request); return checkAuthForOperation(opCtx, request.getDatabase().toString(), request.body); @@ -372,12 +465,6 @@ bool Command::publicRun(OperationContext* opCtx, } } -bool Command::isHelpRequest(const BSONElement& helpElem) { - return !helpElem.eoo() && helpElem.trueValue(); -} - -const char Command::kHelpFieldName[] = "help"; - void Command::generateHelpResponse(OperationContext* opCtx, rpc::ReplyBuilderInterface* replyBuilder, const Command& command) { @@ -391,28 +478,6 @@ void Command::generateHelpResponse(OperationContext* opCtx, replyBuilder->setMetadata(rpc::makeEmptyMetadata()); } -namespace { -const stdx::unordered_set<std::string> userManagementCommands{"createUser", - "updateUser", - "dropUser", - "dropAllUsersFromDatabase", - "grantRolesToUser", - "revokeRolesFromUser", - "createRole", - "updateRole", - "dropRole", - "dropAllRolesFromDatabase", - "grantPrivilegesToRole", - "revokePrivilegesFromRole", - "grantRolesToRole", - "revokeRolesFromRole", - "_mergeAuthzCollections"}; -} // namespace - -bool Command::isUserManagementCommand(const std::string& name) { - return userManagementCommands.count(name); -} - void BasicCommand::uassertNoDocumentSequences(const OpMsgRequest& request) { uassert(40472, str::stream() << "The " << getName() << " command does not support document sequences.", @@ -433,52 +498,13 @@ bool ErrmsgCommandDeprecated::run(OperationContext* opCtx, std::string errmsg; auto ok = errmsgRun(opCtx, db, cmdObj, errmsg, result); if (!errmsg.empty()) { - appendCommandStatus(result, ok, errmsg); + CommandHelpers::appendCommandStatus(result, ok, errmsg); } return ok; } -BSONObj Command::filterCommandRequestForPassthrough(const BSONObj& cmdObj) { - BSONObjBuilder bob; - for (auto elem : cmdObj) { - const auto name = elem.fieldNameStringData(); - if (name == "$readPreference") { - BSONObjBuilder(bob.subobjStart("$queryOptions")).append(elem); - } else if (!Command::isGenericArgument(name) || // - name == "$queryOptions" || // - name == "maxTimeMS" || // - name == "readConcern" || // - name == "writeConcern" || // - name == "lsid" || // - name == "txnNumber") { - // This is the whitelist of generic arguments that commands can be trusted to blindly - // forward to the shards. - bob.append(elem); - } - } - return bob.obj(); -} - -void Command::filterCommandReplyForPassthrough(const BSONObj& cmdObj, BSONObjBuilder* output) { - for (auto elem : cmdObj) { - const auto name = elem.fieldNameStringData(); - if (name == "$configServerState" || // - name == "$gleStats" || // - name == "$clusterTime" || // - name == "$oplogQueryData" || // - name == "$replData" || // - name == "operationTime") { - continue; - } - output->append(elem); - } -} - -BSONObj Command::filterCommandReplyForPassthrough(const BSONObj& cmdObj) { - BSONObjBuilder bob; - filterCommandReplyForPassthrough(cmdObj, &bob); - return bob.obj(); -} +////////////////////////////////////////////////////////////// +// CommandRegistry void CommandRegistry::registerCommand(Command* command, StringData name, StringData oldName) { for (StringData key : {name, oldName}) { @@ -499,25 +525,6 @@ Command* CommandRegistry::findCommand(StringData name) const { return it->second; } -BSONObj CommandHelpers::runCommandDirectly(OperationContext* opCtx, const OpMsgRequest& request) { - auto command = globalCommandRegistry()->findCommand(request.getCommandName()); - invariant(command); - - BSONObjBuilder out; - try { - bool ok = command->publicRun(opCtx, request, out); - Command::appendCommandStatus(out, ok); - } catch (const StaleConfigException&) { - // These exceptions are intended to be handled at a higher level and cannot losslessly - // round-trip through Status. - throw; - } catch (const DBException& ex) { - out.resetToEmpty(); - Command::appendCommandStatus(out, ex.toStatus()); - } - return out.obj(); -} - CommandRegistry* globalCommandRegistry() { static auto reg = new CommandRegistry(); return reg; |