diff options
author | Adam Midvidy <amidvidy@gmail.com> | 2015-06-05 11:30:42 -0400 |
---|---|---|
committer | Adam Midvidy <amidvidy@gmail.com> | 2015-06-05 16:24:05 -0400 |
commit | aade72218f4e0dde65d5e64673b42b73baf6dec1 (patch) | |
tree | 40449bfd4242ce609291084ea732fddff420731d /src/mongo/db | |
parent | 679609b12b29dfe9d3827c089e5ef76c5db091b9 (diff) | |
download | mongo-aade72218f4e0dde65d5e64673b42b73baf6dec1.tar.gz |
SERVER-18292 move runCommands to commands.cpp
- runCommands is now linked in to mongos
- removed the CurOp dependency in generateErrorResponse
- refactored execCommand to not generate error responses manually
- refactor help response generation to a function so mongos can use it
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/commands.cpp | 125 | ||||
-rw-r--r-- | src/mongo/db/commands.h | 37 | ||||
-rw-r--r-- | src/mongo/db/dbcommands.cpp | 362 |
3 files changed, 275 insertions, 249 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index eb262282931..493537f049e 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -40,6 +40,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/mutable/document.h" +#include "mongo/client/connpool.h" #include "mongo/db/audit.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" @@ -47,10 +48,13 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/client.h" +#include "mongo/db/curop.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/server_parameters.h" #include "mongo/rpc/get_status_from_command_result.h" +#include "mongo/rpc/metadata.h" +#include "mongo/s/stale_exception.h" #include "mongo/s/write_ops/wc_error_detail.h" #include "mongo/util/log.h" @@ -387,11 +391,126 @@ namespace mongo { status.code()); return status; } -} -#include "mongo/client/connpool.h" + bool Command::isHelpRequest(const rpc::RequestInterface& request) { + return request.getCommandArgs()["help"].trueValue(); + } -namespace mongo { + void Command::generateHelpResponse(OperationContext* txn, + const rpc::RequestInterface& request, + rpc::ReplyBuilderInterface* replyBuilder, + const Command& command) { + std::stringstream ss; + BSONObjBuilder helpBuilder; + ss << "help for: " << command.name << " "; + command.help(ss); + helpBuilder.append("help", ss.str()); + helpBuilder.append("lockType", command.isWriteCommandForConfigServer() ? 1 : 0); + + replyBuilder->setMetadata(rpc::makeEmptyMetadata()); + replyBuilder->setCommandReply(helpBuilder.done()); + } + +namespace { + + void _generateErrorResponse(OperationContext* txn, + rpc::ReplyBuilderInterface* replyBuilder, + const DBException& exception) { + + Command::registerError(txn, exception); + + // No metadata is needed for an error reply. + replyBuilder->setMetadata(rpc::makeEmptyMetadata()); + + // We need to include some extra information for SendStaleConfig. + if (exception.getCode() == ErrorCodes::SendStaleConfig) { + const SendStaleConfigException& scex = + static_cast<const SendStaleConfigException&>(exception); + replyBuilder->setCommandReply(scex.toStatus(), + BSON("ns" << scex.getns() << + "vReceived" << scex.getVersionReceived().toBSON() << + "vWanted" << scex.getVersionWanted().toBSON())); + } + else { + replyBuilder->setCommandReply(exception.toStatus()); + } + } + +} // namespace + + void Command::generateErrorResponse(OperationContext* txn, + rpc::ReplyBuilderInterface* replyBuilder, + const DBException& exception, + const rpc::RequestInterface& request, + Command* command) { + + log() << "assertion while executing command '" + << request.getCommandName() << "' " + << "on database '" + << request.getDatabase() << "' " + << "with arguments '" + << command->getRedactedCopyForLogging(request.getCommandArgs()) << "' " + << "and metadata '" + << request.getMetadata() << "': " + << exception.toString(); + + _generateErrorResponse(txn, replyBuilder, exception); + } + + void Command::generateErrorResponse(OperationContext* txn, + rpc::ReplyBuilderInterface* replyBuilder, + const DBException& exception, + const rpc::RequestInterface& request) { + + log() << "assertion while executing command '" + << request.getCommandName() << "' " + << "on database '" + << request.getDatabase() << "': " + << exception.toString(); + + _generateErrorResponse(txn, replyBuilder, exception); + } + + void Command::generateErrorResponse(OperationContext* txn, + rpc::ReplyBuilderInterface* replyBuilder, + const DBException& exception) { + log() << "assertion while executing command: " << exception.toString(); + _generateErrorResponse(txn, replyBuilder, exception); + } + + void runCommands(OperationContext* txn, + const rpc::RequestInterface& request, + rpc::ReplyBuilderInterface* replyBuilder) { + + try { + + dassert(replyBuilder->getState() == rpc::ReplyBuilderInterface::State::kMetadata); + + Command* c = nullptr; + // In the absence of a Command object, no redaction is possible. Therefore + // to avoid displaying potentially sensitive information in the logs, + // we restrict the log message to the name of the unrecognized command. + // However, the complete command object will still be echoed to the client. + if (!(c = Command::findCommand(request.getCommandName()))) { + Command::unknownCommands.increment(); + std::string msg = str::stream() << "no such command: '" + << request.getCommandName() << "'"; + LOG(2) << msg; + uasserted(ErrorCodes::CommandNotFound, + str::stream() << msg << ", bad cmd: '" + << request.getCommandArgs() << "'"); + } + + LOG(2) << "run command " << request.getDatabase() << ".$cmd" << ' ' + << c->getRedactedCopyForLogging(request.getCommandArgs()); + + Command::execCommand(txn, c, request, replyBuilder); + } + + catch (const DBException& ex) { + Command::generateErrorResponse(txn, replyBuilder, ex, request); + } + } extern DBConnectionPool pool; // This is mainly used by the internal writes using write commands. diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h index 8660e842f7f..615d6c07581 100644 --- a/src/mongo/db/commands.h +++ b/src/mongo/db/commands.h @@ -331,6 +331,20 @@ namespace mutablebson { static int testCommandsEnabled; /** + * Returns true if this a request for the 'help' information associated with the command. + */ + static bool isHelpRequest(const rpc::RequestInterface& request); + + /** + * Generates a reply from the 'help' information associated with a command. The state of + * the passed ReplyBuilder will be in kOutputDocs after calling this method. + */ + static void generateHelpResponse(OperationContext* txn, + const rpc::RequestInterface& request, + rpc::ReplyBuilderInterface* replyBuilder, + const Command& command); + + /** * When an assertion is hit during command execution, this method is used to fill the fields * of the command reply with the information from the error. In addition, information about * the command is logged. This function does not return anything, because there is typically @@ -340,17 +354,36 @@ namespace mutablebson { static void generateErrorResponse(OperationContext* txn, rpc::ReplyBuilderInterface* replyBuilder, const DBException& exception, + const rpc::RequestInterface& request, + Command* command); + + /** + * Generates a command error response. This overload of generateErrorResponse is intended + * to be called if the command is successfully parsed, but there is an error before we have + * a handle to the actual Command object. This can happen, for example, when the command + * is not found. + */ + static void generateErrorResponse(OperationContext* txn, + rpc::ReplyBuilderInterface* replyBuilder, + const DBException& exception, const rpc::RequestInterface& request); /** - * Similar to other overloads of generateErrorResponse, but doesn't print any information - * about the specific command being executed. This is neccessary, for example, if there is + * Generates a command error response. Similar to other overloads of generateErrorResponse, + * but doesn't print any information about the specific command being executed. This is + * neccessary, for example, if there is * an assertion hit while parsing the command. */ static void generateErrorResponse(OperationContext* txn, rpc::ReplyBuilderInterface* replyBuilder, const DBException& exception); + /** + * Records the error on to the OperationContext. This hook is needed because mongos + * does not have CurOp linked in to it. + */ + static void registerError(OperationContext* txn, const DBException& exception); + private: /** diff --git a/src/mongo/db/dbcommands.cpp b/src/mongo/db/dbcommands.cpp index 2aa33afcd5d..8d26dc191fb 100644 --- a/src/mongo/db/dbcommands.cpp +++ b/src/mongo/db/dbcommands.cpp @@ -1178,181 +1178,150 @@ namespace { const rpc::RequestInterface& request, rpc::ReplyBuilderInterface* replyBuilder) { - dassert(replyBuilder->getState() == rpc::ReplyBuilderInterface::State::kMetadata); + try { - // Right now our metadata handling relies on mutating the command object. - // This will go away when SERVER-18236 is implemented - BSONObj interposedCmd = request.getCommandArgs(); + { + stdx::lock_guard<Client> lk(*txn->getClient()); + CurOp::get(txn)->setCommand_inlock(command); + } + // TODO: move this back to runCommands when mongos supports OperationContext + // see SERVER-18515 for details. + uassertStatusOK(rpc::readRequestMetadata(txn, request.getMetadata())); - std::string dbname = request.getDatabase().toString(); - scoped_ptr<MaintenanceModeSetter> mmSetter; + dassert(replyBuilder->getState() == rpc::ReplyBuilderInterface::State::kMetadata); - if ( request.getCommandArgs()["help"].trueValue() ) { + // Right now our metadata handling relies on mutating the command object. + // This will go away when SERVER-18236 is implemented + BSONObj interposedCmd = request.getCommandArgs(); - CurOp::get(txn)->ensureStarted(); - BSONObjBuilder helpResult; - std::stringstream ss; - ss << "help for: " << command->name << " "; - command->help(ss); - helpResult.append("help", ss.str()); - helpResult.append("lockType", command->isWriteCommandForConfigServer() ? 1 : 0); - - replyBuilder - ->setMetadata(rpc::makeEmptyMetadata()) - .setCommandReply(helpResult.done()); - - return; - } - - // Handle command option impersonatedUsers and impersonatedRoles. - // This must come before _checkAuthorization(), as there is some command parsing logic - // in that code path that must not see the impersonated user and roles array elements. - std::vector<UserName> parsedUserNames; - std::vector<RoleName> parsedRoleNames; - AuthorizationSession* authSession = AuthorizationSession::get(txn->getClient()); - bool rolesFieldIsPresent = false; - bool usersFieldIsPresent = false; - - // TODO: Remove these once the metadata refactor (SERVER-18236) is complete. - // Then we can construct the ImpersonationSessionGuard directly from the contents of the - // metadata object rather than slicing elements off of the command object. - audit::parseAndRemoveImpersonatedRolesField(interposedCmd, - authSession, - &parsedRoleNames, - &rolesFieldIsPresent); - audit::parseAndRemoveImpersonatedUsersField(interposedCmd, - authSession, - &parsedUserNames, - &usersFieldIsPresent); - if (rolesFieldIsPresent != usersFieldIsPresent) { - // If there is a version mismatch between the mongos and the mongod, - // the mongos may fail to pass the role information, causing an error. - Status s(ErrorCodes::IncompatibleAuditMetadata, - "Audit metadata does not include both user and role information."); - replyBuilder - ->setMetadata(rpc::makeEmptyMetadata()) - .setCommandReply(s); - - return; - } - ImpersonationSessionGuard impersonationSession(authSession, - usersFieldIsPresent, - parsedUserNames, - parsedRoleNames); - - Status status = _checkAuthorization(command, - txn->getClient(), - dbname, - interposedCmd); - if (!status.isOK()) { - - replyBuilder - ->setMetadata(rpc::makeEmptyMetadata()) - .setCommandReply(status); - - return; - } + std::string dbname = request.getDatabase().toString(); + scoped_ptr<MaintenanceModeSetter> mmSetter; - repl::ReplicationCoordinator* replCoord = repl::getGlobalReplicationCoordinator(); + if (isHelpRequest(request)) { + CurOp::get(txn)->ensureStarted(); + generateHelpResponse(txn, request, replyBuilder, *command); + return; + } - bool iAmPrimary = replCoord->canAcceptWritesForDatabase(dbname); - bool commandCanRunOnSecondary = command->slaveOk(); + // Handle command option impersonatedUsers and impersonatedRoles. + // This must come before _checkAuthorization(), as there is some command parsing logic + // in that code path that must not see the impersonated user and roles array elements. + std::vector<UserName> parsedUserNames; + std::vector<RoleName> parsedRoleNames; + AuthorizationSession* authSession = AuthorizationSession::get(txn->getClient()); + bool rolesFieldIsPresent = false; + bool usersFieldIsPresent = false; + + // TODO: Remove these once the metadata refactor (SERVER-18236) is complete. + // Then we can construct the ImpersonationSessionGuard directly from the contents of the + // metadata object rather than slicing elements off of the command object. + audit::parseAndRemoveImpersonatedRolesField(interposedCmd, + authSession, + &parsedRoleNames, + &rolesFieldIsPresent); + audit::parseAndRemoveImpersonatedUsersField(interposedCmd, + authSession, + &parsedUserNames, + &usersFieldIsPresent); + + uassert(ErrorCodes::IncompatibleAuditMetadata, + "Audit metadata does not include both user and role information.", + rolesFieldIsPresent == usersFieldIsPresent); + + ImpersonationSessionGuard impersonationSession(authSession, + usersFieldIsPresent, + parsedUserNames, + parsedRoleNames); + + uassertStatusOK(_checkAuthorization(command, + txn->getClient(), + dbname, + interposedCmd)); - bool commandIsOverriddenToRunOnSecondary = command->slaveOverrideOk() && + { + repl::ReplicationCoordinator* replCoord = repl::getGlobalReplicationCoordinator(); - // The $secondaryOk option is set. - (rpc::ServerSelectionMetadata::get(txn).isSecondaryOk() || + bool iAmPrimary = replCoord->canAcceptWritesForDatabase(dbname); + bool commandCanRunOnSecondary = command->slaveOk(); - // Or the command has a read preference (may be incorrect, see SERVER-18194). - (rpc::ServerSelectionMetadata::get(txn).getReadPreference() != boost::none)); + bool commandIsOverriddenToRunOnSecondary = command->slaveOverrideOk() && - bool iAmStandalone = !txn->writesAreReplicated(); - bool canRunHere = iAmPrimary || - commandCanRunOnSecondary || - commandIsOverriddenToRunOnSecondary || - iAmStandalone; + // The $secondaryOk option is set. + (rpc::ServerSelectionMetadata::get(txn).isSecondaryOk() || - auto extraErrorData = BSON("note" << "from execCommand"); + // Or the command has a read preference (may be incorrect, see SERVER-18194). + (rpc::ServerSelectionMetadata::get(txn).getReadPreference() != boost::none)); - if (!canRunHere) { + bool iAmStandalone = !txn->writesAreReplicated(); + bool canRunHere = iAmPrimary || + commandCanRunOnSecondary || + commandIsOverriddenToRunOnSecondary || + iAmStandalone; - replyBuilder->setMetadata(rpc::makeEmptyMetadata()); + // This logic is clearer if we don't have to invert it. + if (!canRunHere && command->slaveOverrideOk()) { + uasserted(ErrorCodes::NotMasterNoSlaveOkCode, + "not master and slaveOk=false"); + } - if (command->slaveOverrideOk()) { - replyBuilder - ->setCommandReply(Status(ErrorCodes::NotMasterNoSlaveOkCode, - "not master and slaveOk=false"), - extraErrorData); - } - else { - replyBuilder - ->setCommandReply(Status(ErrorCodes::NotMaster, "not master"), - extraErrorData); - } - return; - } + uassert(ErrorCodes::NotMaster, + "not master", + canRunHere); - if (!command->maintenanceOk() - && replCoord->getReplicationMode() == repl::ReplicationCoordinator::modeReplSet - && !replCoord->canAcceptWritesForDatabase(dbname) - && !replCoord->getMemberState().secondary()) { - replyBuilder - ->setMetadata(rpc::makeEmptyMetadata()) - .setCommandReply(Status(ErrorCodes::NotMasterOrSecondaryCode, "node is recovering"), - extraErrorData); + if (!command->maintenanceOk() + && replCoord->getReplicationMode() == repl::ReplicationCoordinator::modeReplSet + && !replCoord->canAcceptWritesForDatabase(dbname) + && !replCoord->getMemberState().secondary()) { - return; - } + uasserted(ErrorCodes::NotMasterOrSecondaryCode, + "node is recovering"); + } + } - if (command->adminOnly()) { - LOG(2) << "command: " << request.getCommandName(); - } + if (command->adminOnly()) { + LOG(2) << "command: " << request.getCommandName(); + } - if (command->maintenanceMode()) { - mmSetter.reset(new MaintenanceModeSetter); - } + if (command->maintenanceMode()) { + mmSetter.reset(new MaintenanceModeSetter); + } - if (command->shouldAffectCommandCounter()) { - OpCounters* opCounters = &globalOpCounters; - opCounters->gotCommand(); - } + if (command->shouldAffectCommandCounter()) { + OpCounters* opCounters = &globalOpCounters; + opCounters->gotCommand(); + } - // Handle command option maxTimeMS. - StatusWith<int> maxTimeMS = LiteParsedQuery::parseMaxTimeMSCommand(interposedCmd); - if (!maxTimeMS.isOK()) { - replyBuilder - ->setMetadata(rpc::makeEmptyMetadata()) - .setCommandReply(maxTimeMS.getStatus()); - return; - } - if (interposedCmd.hasField("$maxTimeMS")) { - replyBuilder - ->setMetadata(rpc::makeEmptyMetadata()) - .setCommandReply(Status(ErrorCodes::InvalidOptions, - "no such command option $maxTimeMS;" - " use maxTimeMS instead")); - return; - } + // Handle command option maxTimeMS. + int maxTimeMS = uassertStatusOK( + LiteParsedQuery::parseMaxTimeMSCommand(interposedCmd) + ); - CurOp::get(txn)->setMaxTimeMicros(static_cast<unsigned long long>(maxTimeMS.getValue()) - * 1000); + uassert(ErrorCodes::InvalidOptions, + "no such command option $maxTimeMs; use maxTimeMS instead", + !interposedCmd.hasField("$maxTimeMS")); - // Can throw - txn->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point. + CurOp::get(txn)->setMaxTimeMicros(static_cast<unsigned long long>(maxTimeMS) + * 1000); - std::string errmsg; - bool retval = false; + // Can throw + txn->checkForInterrupt(); // May trigger maxTimeAlwaysTimeOut fail point. - CurOp::get(txn)->ensureStarted(); + bool retval = false; + + CurOp::get(txn)->ensureStarted(); - command->_commandsExecuted.increment(); + command->_commandsExecuted.increment(); - retval = command->run(txn, interposedCmd, request, replyBuilder); + retval = command->run(txn, interposedCmd, request, replyBuilder); - dassert(replyBuilder->getState() == rpc::ReplyBuilderInterface::State::kOutputDocs); + dassert(replyBuilder->getState() == rpc::ReplyBuilderInterface::State::kOutputDocs); - if (!retval) { - command->_commandsFailed.increment(); + if (!retval) { + command->_commandsFailed.increment(); + } + } + catch (const DBException& exception) { + Command::generateErrorResponse(txn, replyBuilder, exception, request, command); } } @@ -1378,7 +1347,7 @@ namespace { int queryFlags = 0; std::tie(std::ignore, queryFlags) = uassertStatusOK( rpc::downconvertRequestMetadata(request.getCommandArgs(), - request.getMetadata()) + request.getMetadata()) ); repl::ReplicationCoordinator* replCoord = repl::getGlobalReplicationCoordinator(); @@ -1433,103 +1402,8 @@ namespace { return result; } - /* TODO make these all command objects -- legacy stuff here - - usage: - abc.$cmd.findOne( { ismaster:1 } ); - - returns true if ran a cmd - */ - void runCommands(OperationContext* txn, - const rpc::RequestInterface& request, - rpc::ReplyBuilderInterface* replyBuilder) { - - try { - uassertStatusOK(rpc::readRequestMetadata(txn, request.getMetadata())); - dassert(replyBuilder->getState() == rpc::ReplyBuilderInterface::State::kMetadata); - - Command* c = nullptr; - // In the absence of a Command object, no redaction is possible. Therefore - // to avoid displaying potentially sensitive information in the logs, - // we restrict the log message to the name of the unrecognized command. - // However, the complete command object will still be echoed to the client. - if (!(c = Command::findCommand(request.getCommandName()))) { - Command::unknownCommands.increment(); - std::string msg = str::stream() << "no such command: '" - << request.getCommandName() << "'"; - LOG(2) << msg; - uasserted(ErrorCodes::CommandNotFound, - str::stream() << msg << "', bad cmd: '" - << request.getCommandArgs() << "'"); - } - - LOG(2) << "run command " << request.getDatabase() << ".$cmd" << ' ' - << c->getRedactedCopyForLogging(request.getCommandArgs()); - - Command::execCommand(txn, c, request, replyBuilder); - } - - catch (const DBException& ex) { - Command::generateErrorResponse(txn, replyBuilder, ex, request); - } - } - -namespace { - - void _generateErrorResponse(OperationContext* txn, - rpc::ReplyBuilderInterface* replyBuilder, - const DBException& exception) { + void Command::registerError(OperationContext* txn, const DBException& exception) { CurOp::get(txn)->debug().exceptionInfo = exception.getInfo(); - - // No metadata is needed for an error reply. - replyBuilder->setMetadata(rpc::makeEmptyMetadata()); - - // We need to include some extra information for SendStaleConfig. - if (exception.getCode() == ErrorCodes::SendStaleConfig) { - const SendStaleConfigException& scex = - static_cast<const SendStaleConfigException&>(exception); - replyBuilder->setCommandReply(scex.toStatus(), - BSON("ns" << scex.getns() << - "vReceived" << scex.getVersionReceived().toBSON() << - "vWanted" << scex.getVersionWanted().toBSON())); - } - else { - replyBuilder->setCommandReply(exception.toStatus()); - } - } - -} // namespace - - void Command::generateErrorResponse(OperationContext* txn, - rpc::ReplyBuilderInterface* replyBuilder, - const DBException& exception, - const rpc::RequestInterface& request) { - - str::stream ss; - ss << "assertion while executing command '" - << request.getCommandName() << "' " - << "on database '" - << request.getDatabase() << "' "; - - Command* command = CurOp::get(txn)->getCommand(); - // If we have a pointer to the command object, we can use it to redact the arguments. - if (command) { - ss << "with arguments '" - << command->getRedactedCopyForLogging(request.getCommandArgs()) << "' " - << "and metadata '" - << request.getMetadata() << "' "; - } - - ss << ":" << exception.toString(); - log() << std::string(ss); - _generateErrorResponse(txn, replyBuilder, exception); - } - - void Command::generateErrorResponse(OperationContext* txn, - rpc::ReplyBuilderInterface* replyBuilder, - const DBException& exception) { - log() << "assertion while executing command: " << exception.toString(); - _generateErrorResponse(txn, replyBuilder, exception); } } // namespace mongo |