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/dbcommands.cpp | |
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/dbcommands.cpp')
-rw-r--r-- | src/mongo/db/dbcommands.cpp | 362 |
1 files changed, 118 insertions, 244 deletions
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 |