diff options
author | Mathias Stearn <mathias@10gen.com> | 2017-05-23 18:34:32 -0400 |
---|---|---|
committer | Mathias Stearn <mathias@10gen.com> | 2017-06-19 19:02:31 -0400 |
commit | 36c13338c54d9cd7bb978d56487fc692374c8255 (patch) | |
tree | dfb18cc38414059dd043da31bb940f6192a4d994 | |
parent | 47856e523e3d3c842f95ec277f33728130ad14dd (diff) | |
download | mongo-36c13338c54d9cd7bb978d56487fc692374c8255.tar.gz |
SERVER-29319 Replace RequestBuilderInterface with OpMsgRequest
-rw-r--r-- | src/mongo/client/dbclient.cpp | 20 | ||||
-rw-r--r-- | src/mongo/client/dbclientcursor.cpp | 20 | ||||
-rw-r--r-- | src/mongo/executor/connection_pool_asio.cpp | 9 | ||||
-rw-r--r-- | src/mongo/executor/network_interface_asio_auth.cpp | 14 | ||||
-rw-r--r-- | src/mongo/executor/network_interface_asio_command.cpp | 1 | ||||
-rw-r--r-- | src/mongo/executor/network_interface_asio_operation.cpp | 25 | ||||
-rw-r--r-- | src/mongo/rpc/command_request_builder.cpp | 103 | ||||
-rw-r--r-- | src/mongo/rpc/command_request_builder.h | 49 | ||||
-rw-r--r-- | src/mongo/rpc/command_request_builder_test.cpp | 47 | ||||
-rw-r--r-- | src/mongo/rpc/command_request_test.cpp | 74 | ||||
-rw-r--r-- | src/mongo/rpc/factory.cpp | 13 | ||||
-rw-r--r-- | src/mongo/rpc/factory.h | 16 | ||||
-rw-r--r-- | src/mongo/rpc/legacy_request_builder.cpp | 98 | ||||
-rw-r--r-- | src/mongo/rpc/legacy_request_builder.h | 35 | ||||
-rw-r--r-- | src/mongo/rpc/metadata.cpp | 27 | ||||
-rw-r--r-- | src/mongo/rpc/metadata.h | 6 | ||||
-rw-r--r-- | src/mongo/rpc/op_msg_rpc_impls.h | 33 | ||||
-rw-r--r-- | src/mongo/rpc/request_builder_interface.h | 99 |
18 files changed, 233 insertions, 456 deletions
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp index 36821188887..669ca102a6a 100644 --- a/src/mongo/client/dbclient.cpp +++ b/src/mongo/client/dbclient.cpp @@ -57,7 +57,6 @@ #include "mongo/rpc/metadata.h" #include "mongo/rpc/metadata/client_metadata.h" #include "mongo/rpc/reply_interface.h" -#include "mongo/rpc/request_builder_interface.h" #include "mongo/s/stale_exception.h" // for RecvStaleConfigException #include "mongo/stdx/functional.h" #include "mongo/stdx/memory.h" @@ -172,7 +171,7 @@ const rpc::ReplyMetadataReader& DBClientWithCommands::getReplyMetadataReader() { rpc::UniqueReply DBClientWithCommands::runCommandWithMetadata(StringData database, StringData command, - const BSONObj& metadata, + const BSONObj& metadataIn, const BSONObj& commandArgs) { uassert(ErrorCodes::InvalidNamespace, str::stream() << "Database name '" << database << "' is not valid.", @@ -185,20 +184,19 @@ rpc::UniqueReply DBClientWithCommands::runCommandWithMetadata(StringData databas // call() oddly takes this by pointer, so we need to put it on the stack. auto host = getServerAddress(); - BSONObjBuilder metadataBob(std::move(metadata)); + auto metadata = metadataIn; if (_metadataWriter) { + BSONObjBuilder metadataBob(std::move(metadata)); uassertStatusOK( _metadataWriter((haveClient() ? cc().getOperationContext() : nullptr), &metadataBob)); + metadata = metadataBob.obj(); } - auto requestBuilder = rpc::makeRequestBuilder(getClientRPCProtocols(), getServerRPCProtocols()); - - requestBuilder->setDatabase(database); - requestBuilder->setCommandName(command); - requestBuilder->setCommandArgs(commandArgs); - requestBuilder->setMetadata(metadataBob.obj()); - auto requestMsg = requestBuilder->done(); + auto requestMsg = rpc::messageFromOpMsgRequest( + getClientRPCProtocols(), + getServerRPCProtocols(), + OpMsgRequest::fromDBAndBody(database, std::move(commandArgs), metadata)); Message replyMsg; @@ -224,7 +222,7 @@ rpc::UniqueReply DBClientWithCommands::runCommandWithMetadata(StringData databas << " but reply was '" << networkOpToString(replyMsg.operation()) << "' ", - requestBuilder->getProtocol() == commandReply->getProtocol()); + rpc::protocolForMessage(requestMsg) == commandReply->getProtocol()); if (_metadataReader) { uassertStatusOK(_metadataReader(commandReply->getMetadata(), host)); diff --git a/src/mongo/client/dbclientcursor.cpp b/src/mongo/client/dbclientcursor.cpp index e7675630ab6..28d9c9c88bf 100644 --- a/src/mongo/client/dbclientcursor.cpp +++ b/src/mongo/client/dbclientcursor.cpp @@ -41,7 +41,6 @@ #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/rpc/metadata.h" #include "mongo/rpc/object_check.h" -#include "mongo/rpc/request_builder_interface.h" #include "mongo/s/stale_exception.h" #include "mongo/stdx/memory.h" #include "mongo/util/debug_util.h" @@ -62,30 +61,23 @@ Message assembleCommandRequest(DBClientWithCommands* cli, StringData database, int legacyQueryOptions, BSONObj legacyQuery) { - // Can be an OP_COMMAND or OP_QUERY message. - auto requestBuilder = - rpc::makeRequestBuilder(cli->getClientRPCProtocols(), cli->getServerRPCProtocols()); - BSONObj upconvertedCommand; BSONObj upconvertedMetadata; std::tie(upconvertedCommand, upconvertedMetadata) = rpc::upconvertRequestMetadata(std::move(legacyQuery), legacyQueryOptions); - BSONObjBuilder metadataBob(std::move(upconvertedMetadata)); if (cli->getRequestMetadataWriter()) { + BSONObjBuilder metadataBob(std::move(upconvertedMetadata)); uassertStatusOK(cli->getRequestMetadataWriter()( (haveClient() ? cc().getOperationContext() : nullptr), &metadataBob)); + upconvertedMetadata = metadataBob.obj(); } - requestBuilder->setDatabase(database); - // We need to get the command name from the upconverted command as it may have originally - // been wrapped. - requestBuilder->setCommandName(upconvertedCommand.firstElementFieldName()); - requestBuilder->setCommandArgs(std::move(upconvertedCommand)); - requestBuilder->setMetadata(metadataBob.obj()); - - return requestBuilder->done(); + return rpc::messageFromOpMsgRequest( + cli->getClientRPCProtocols(), + cli->getServerRPCProtocols(), + OpMsgRequest::fromDBAndBody(database, std::move(upconvertedCommand), upconvertedMetadata)); } } // namespace diff --git a/src/mongo/executor/connection_pool_asio.cpp b/src/mongo/executor/connection_pool_asio.cpp index dab0b739ba8..00c21c0799b 100644 --- a/src/mongo/executor/connection_pool_asio.cpp +++ b/src/mongo/executor/connection_pool_asio.cpp @@ -189,13 +189,8 @@ std::unique_ptr<NetworkInterfaceASIO::AsyncOp> ASIOConnection::makeAsyncOp(ASIOC } Message ASIOConnection::makeIsMasterRequest(ASIOConnection* conn) { - rpc::LegacyRequestBuilder requestBuilder{}; - requestBuilder.setDatabase("admin"); - requestBuilder.setCommandName("isMaster"); - requestBuilder.setCommandArgs(BSON("isMaster" << 1)); - requestBuilder.setMetadata(rpc::makeEmptyMetadata()); - - return requestBuilder.done(); + return rpc::legacyRequestFromOpMsgRequest( + OpMsgRequest::fromDBAndBody("admin", BSON("isMaster" << 1))); } void ASIOConnection::setTimeout(Milliseconds timeout, TimeoutCallback cb) { diff --git a/src/mongo/executor/network_interface_asio_auth.cpp b/src/mongo/executor/network_interface_asio_auth.cpp index 1f2039dbd1e..3070de706c5 100644 --- a/src/mongo/executor/network_interface_asio_auth.cpp +++ b/src/mongo/executor/network_interface_asio_auth.cpp @@ -56,12 +56,6 @@ namespace executor { using ResponseStatus = TaskExecutor::ResponseStatus; void NetworkInterfaceASIO::_runIsMaster(AsyncOp* op) { - // We use a legacy builder to create our ismaster request because we may - // have to communicate with servers that do not support OP_COMMAND - rpc::LegacyRequestBuilder requestBuilder{}; - requestBuilder.setDatabase("admin"); - requestBuilder.setCommandName("isMaster"); - BSONObjBuilder bob; bob.append("isMaster", 1); bob.append("hangUpOnStepDown", false); @@ -84,11 +78,13 @@ void NetworkInterfaceASIO::_runIsMaster(AsyncOp* op) { WireSpec::appendInternalClientWireVersion(WireSpec::instance().outgoing, &bob); } - requestBuilder.setCommandArgs(bob.done()); - requestBuilder.setMetadata(rpc::makeEmptyMetadata()); + // We use a legacy request to create our ismaster request because we may + // have to communicate with servers that do not support other protocols. + auto isMasterRequest = + rpc::legacyRequestFromOpMsgRequest(OpMsgRequest::fromDBAndBody("admin", bob.obj())); // Set current command to ismaster request and run - auto beginStatus = op->beginCommand(requestBuilder.done(), op->request().target); + auto beginStatus = op->beginCommand(std::move(isMasterRequest), op->request().target); if (!beginStatus.isOK()) { return _completeOperation(op, beginStatus); } diff --git a/src/mongo/executor/network_interface_asio_command.cpp b/src/mongo/executor/network_interface_asio_command.cpp index 2dfac71c5f7..674539146ca 100644 --- a/src/mongo/executor/network_interface_asio_command.cpp +++ b/src/mongo/executor/network_interface_asio_command.cpp @@ -44,7 +44,6 @@ #include "mongo/rpc/metadata/metadata_hook.h" #include "mongo/rpc/protocol.h" #include "mongo/rpc/reply_interface.h" -#include "mongo/rpc/request_builder_interface.h" #include "mongo/stdx/memory.h" #include "mongo/util/assert_util.h" #include "mongo/util/fail_point_service.h" diff --git a/src/mongo/executor/network_interface_asio_operation.cpp b/src/mongo/executor/network_interface_asio_operation.cpp index b300080ce76..fa74d9996f8 100644 --- a/src/mongo/executor/network_interface_asio_operation.cpp +++ b/src/mongo/executor/network_interface_asio_operation.cpp @@ -40,7 +40,6 @@ #include "mongo/executor/network_interface_asio.h" #include "mongo/rpc/factory.h" #include "mongo/rpc/metadata/metadata_hook.h" -#include "mongo/rpc/request_builder_interface.h" #include "mongo/util/log.h" #include "mongo/util/time_support.h" @@ -62,20 +61,6 @@ namespace { // be used to run multiple distinct requests. AtomicUInt64 kAsyncOpIdCounter(0); -StatusWith<Message> messageFromRequest(const RemoteCommandRequest& request, - rpc::Protocol protocol) { - BSONObj query = request.cmdObj; - auto requestBuilder = rpc::makeRequestBuilder(protocol); - - auto toSend = rpc::makeRequestBuilder(protocol) - ->setDatabase(request.dbname) - .setCommandName(request.cmdObj.firstElementFieldName()) - .setCommandArgs(request.cmdObj) - .setMetadata(request.metadata) - .done(); - return std::move(toSend); -} - } // namespace const NetworkInterfaceASIO::TableRow NetworkInterfaceASIO::AsyncOp::kFieldLabels = { @@ -179,11 +164,11 @@ Status NetworkInterfaceASIO::AsyncOp::beginCommand(Message&& newCommand, } Status NetworkInterfaceASIO::AsyncOp::beginCommand(const RemoteCommandRequest& request) { - auto newCommand = messageFromRequest(request, operationProtocol()); - if (!newCommand.isOK()) { - return newCommand.getStatus(); - } - return beginCommand(std::move(newCommand.getValue()), request.target); + return beginCommand( + rpc::messageFromOpMsgRequest( + operationProtocol(), + OpMsgRequest::fromDBAndBody(request.dbname, request.cmdObj, request.metadata)), + request.target); } NetworkInterfaceASIO::AsyncCommand* NetworkInterfaceASIO::AsyncOp::command() { diff --git a/src/mongo/rpc/command_request_builder.cpp b/src/mongo/rpc/command_request_builder.cpp index 9b784db1841..4a10cc7656f 100644 --- a/src/mongo/rpc/command_request_builder.cpp +++ b/src/mongo/rpc/command_request_builder.cpp @@ -30,80 +30,71 @@ #include "mongo/rpc/command_request_builder.h" -#include <utility> - #include "mongo/client/read_preference.h" -#include "mongo/stdx/memory.h" +#include "mongo/db/commands.h" #include "mongo/util/assert_util.h" -#include "mongo/util/net/message.h" namespace mongo { namespace rpc { -CommandRequestBuilder::CommandRequestBuilder() : CommandRequestBuilder(Message()) {} +namespace { +// OP_COMMAND put some generic arguments in the metadata and some in the body. +bool fieldGoesInMetadata(StringData commandName, StringData field) { + if (!Command::isGenericArgument(field)) + return false; // All non-generic arguments go to the body. -CommandRequestBuilder::~CommandRequestBuilder() {} + // For some reason this goes in the body only for a single command... + if (field == "$replData") + return commandName != "replSetUpdatePosition"; -CommandRequestBuilder::CommandRequestBuilder(Message&& message) : _message{std::move(message)} { - _builder.skip(mongo::MsgData::MsgDataHeaderSize); // Leave room for message header. + // These generic arguments went in the body. + return !(field == "maxTimeMS" || field == "readConcern" || field == "writeConcern" || + field == "shardVersion"); } +} // namespace -CommandRequestBuilder& CommandRequestBuilder::setDatabase(StringData database) { - invariant(_state == State::kDatabase); - _builder.appendStr(database); - _state = State::kCommandName; - return *this; -} +Message opCommandRequestFromOpMsgRequest(const OpMsgRequest& request) { + invariant(request.sequences.empty()); // Not supported yet. -CommandRequestBuilder& CommandRequestBuilder::setCommandName(StringData commandName) { - invariant(_state == State::kCommandName); - _builder.appendStr(commandName); - _state = State::kCommandArgs; - return *this; -} + const auto commandName = request.getCommandName(); -CommandRequestBuilder& CommandRequestBuilder::setCommandArgs(BSONObj commandArgs) { - invariant(_state == State::kCommandArgs); - commandArgs.appendSelfToBufBuilder(_builder); - _state = State::kMetadata; - return *this; -} + BufBuilder builder; + builder.skip(mongo::MsgData::MsgDataHeaderSize); // Leave room for message header. + builder.appendStr(request.getDatabase()); + builder.appendStr(commandName); -CommandRequestBuilder& CommandRequestBuilder::setMetadata(BSONObj metadata) { - invariant(_state == State::kMetadata); // OP_COMMAND is only used when communicating with 3.4 nodes and they serialize their metadata - // fields differently. We do all down-conversion here so that the rest of the code only has to - // deal with the current format. - BSONObjBuilder bob(_builder); - for (auto elem : metadata) { - if (elem.fieldNameStringData() == "$configServerState") { - bob.appendAs(elem, "configsvr"); - } else if (elem.fieldNameStringData() == "$readPreference") { - BSONObjBuilder ssmBuilder(bob.subobjStart("$ssm")); - ssmBuilder.append(elem); - ssmBuilder.append( - "$secondaryOk", - uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(elem)).canRunOnSecondary()); - } else { - bob.append(elem); + // fields differently. In addition to field-level differences, some generic arguments are pulled + // out to a metadata object, separate from the body. We do all down-conversion here so that the + // rest of the code only has to deal with the current format. + BSONObjBuilder metadataBuilder; // Will be appended to the message after we finish the body. + { + BSONObjBuilder bodyBuilder(builder); + for (auto elem : request.body) { + const auto fieldName = elem.fieldNameStringData(); + if (fieldName == "$configServerState") { + metadataBuilder.appendAs(elem, "configsvr"); + } else if (fieldName == "$readPreference") { + BSONObjBuilder ssmBuilder(metadataBuilder.subobjStart("$ssm")); + ssmBuilder.append(elem); + ssmBuilder.append("$secondaryOk", + uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(elem)) + .canRunOnSecondary()); + } else if (fieldName == "$db") { + // skip + } else if (fieldGoesInMetadata(commandName, fieldName)) { + metadataBuilder.append(elem); + } else { + bodyBuilder.append(elem); + } } } - _state = State::kInputDocs; - return *this; -} - -Protocol CommandRequestBuilder::getProtocol() const { - return rpc::Protocol::kOpCommandV1; -} + metadataBuilder.obj().appendSelfToBufBuilder(builder); -Message CommandRequestBuilder::done() { - invariant(_state == State::kInputDocs); - MsgData::View msg = _builder.buf(); - msg.setLen(_builder.len()); + MsgData::View msg = builder.buf(); + msg.setLen(builder.len()); msg.setOperation(dbCommand); - _message.setData(_builder.release()); // transfer ownership to Message. - _state = State::kDone; - return std::move(_message); + return Message(builder.release()); } } // namespace rpc diff --git a/src/mongo/rpc/command_request_builder.h b/src/mongo/rpc/command_request_builder.h index 7eb3db408b5..0e3ae41fbca 100644 --- a/src/mongo/rpc/command_request_builder.h +++ b/src/mongo/rpc/command_request_builder.h @@ -28,58 +28,13 @@ #pragma once -#include <memory> - -#include "mongo/base/string_data.h" -#include "mongo/db/jsobj.h" -#include "mongo/rpc/protocol.h" -#include "mongo/rpc/request_builder_interface.h" #include "mongo/util/net/message.h" +#include "mongo/util/net/op_msg.h" namespace mongo { namespace rpc { -/** - * Constructs an OP_COMMAND message. - */ -class CommandRequestBuilder : public RequestBuilderInterface { -public: - /** - * Constructs an OP_COMMAND in a new buffer. - */ - CommandRequestBuilder(); - - ~CommandRequestBuilder() final; - - /** - * Construct an OP_COMMAND in an existing buffer. Ownership of the buffer will be - * transfered to the CommandRequestBuilder. - */ - CommandRequestBuilder(Message&& message); - - CommandRequestBuilder& setDatabase(StringData database) final; - - CommandRequestBuilder& setCommandName(StringData commandName) final; - - CommandRequestBuilder& setCommandArgs(BSONObj commandArgs) final; - - CommandRequestBuilder& setMetadata(BSONObj metadata) final; - - Protocol getProtocol() const final; - - /** - * Writes data then transfers ownership of the message to the caller. - * The behavior of calling any methods on the object is subsequently - * undefined. - */ - Message done() final; - -private: - BufBuilder _builder{}; - Message _message; - - State _state{State::kDatabase}; -}; +Message opCommandRequestFromOpMsgRequest(const OpMsgRequest& request); } // rpc } // mongo diff --git a/src/mongo/rpc/command_request_builder_test.cpp b/src/mongo/rpc/command_request_builder_test.cpp index fe9539d41d2..a8b3c9af8f1 100644 --- a/src/mongo/rpc/command_request_builder_test.cpp +++ b/src/mongo/rpc/command_request_builder_test.cpp @@ -37,12 +37,12 @@ namespace { using namespace mongo; -TEST(RequestBuilder, RoundTrip) { +TEST(CommandRequestBuilder, RoundTrip) { auto databaseName = "barbaz"; auto commandName = "foobar"; BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); + metadataBob.append("$replData", BSONObj()); auto metadata = metadataBob.done(); BSONObjBuilder commandArgsBob{}; @@ -61,18 +61,8 @@ TEST(RequestBuilder, RoundTrip) { inputDoc3Bob.append("g", "p"); auto inputDoc3 = inputDoc3Bob.done(); - BufBuilder inputDocs; - inputDoc1.appendSelfToBufBuilder(inputDocs); - inputDoc2.appendSelfToBufBuilder(inputDocs); - inputDoc3.appendSelfToBufBuilder(inputDocs); - - rpc::CommandRequestBuilder r; - - auto msg = r.setDatabase(databaseName) - .setCommandName(commandName) - .setCommandArgs(commandArgs) - .setMetadata(metadata) - .done(); + auto msg = rpc::opCommandRequestFromOpMsgRequest( + OpMsgRequest::fromDBAndBody(databaseName, commandArgs, metadata)); auto parsed = mongo::rpc::ParsedOpCommand::parse(msg); @@ -82,4 +72,33 @@ TEST(RequestBuilder, RoundTrip) { ASSERT_BSONOBJ_EQ(parsed.body, commandArgs); } +TEST(CommandRequestBuilder, DownconvertSecondaryReadPreferenceToSSM) { + auto readPref = BSON("mode" + << "secondary"); + auto msg = rpc::opCommandRequestFromOpMsgRequest( + OpMsgRequest::fromDBAndBody("admin", BSON("ping" << 1 << "$readPreference" << readPref))); + auto parsed = mongo::rpc::ParsedOpCommand::parse(msg); + + ASSERT(!parsed.body.hasField("$readPreference")); + ASSERT(!parsed.body.hasField("$ssm")); + ASSERT(!parsed.metadata.hasField("$readPreference")); + + ASSERT_BSONOBJ_EQ(parsed.metadata["$ssm"]["$readPreference"].Obj(), readPref); + ASSERT(parsed.metadata["$ssm"]["$secondaryOk"].trueValue()); +} + +TEST(CommandRequestBuilder, DownconvertPrimaryReadPreferenceToSSM) { + auto readPref = BSON("mode" + << "primary"); + auto msg = rpc::opCommandRequestFromOpMsgRequest( + OpMsgRequest::fromDBAndBody("admin", BSON("ping" << 1 << "$readPreference" << readPref))); + auto parsed = mongo::rpc::ParsedOpCommand::parse(msg); + + ASSERT(!parsed.body.hasField("$readPreference")); + ASSERT(!parsed.body.hasField("$ssm")); + ASSERT(!parsed.metadata.hasField("$readPreference")); + + ASSERT(!parsed.metadata["$ssm"]["$secondaryOk"].trueValue()); +} + } // namespace diff --git a/src/mongo/rpc/command_request_test.cpp b/src/mongo/rpc/command_request_test.cpp index 17fc056ef68..830c1f04472 100644 --- a/src/mongo/rpc/command_request_test.cpp +++ b/src/mongo/rpc/command_request_test.cpp @@ -86,22 +86,72 @@ TEST(CommandRequest, ParseAllFields) { } TEST(CommandRequest, EmptyCommandObjThrows) { - rpc::CommandRequestBuilder crb; - crb.setDatabase("someDb"); - crb.setCommandName("ping"); - crb.setCommandArgs(BSONObj()); - crb.setMetadata(BSONObj()); - auto msg = crb.done(); + std::vector<char> opCommandData; + + using std::begin; + using std::end; + + auto writeString = [&opCommandData](const std::string& str) { + opCommandData.insert(end(opCommandData), begin(str), end(str)); + opCommandData.push_back('\0'); + }; + + auto writeObj = [&opCommandData](const BSONObj& obj) { + opCommandData.insert(end(opCommandData), obj.objdata(), obj.objdata() + obj.objsize()); + }; + + auto database = std::string{"someDb"}; + writeString(database); + + auto commandName = std::string{"baz"}; + writeString(commandName); + + auto commandArgs = BSONObj(); + writeObj(commandArgs); + + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); + writeObj(metadata); + + Message msg; + msg.setData(dbCommand, opCommandData.data(), opCommandData.size()); + ASSERT_THROWS_CODE(rpc::ParsedOpCommand::parse(msg), UserException, 39950); } TEST(CommandRequest, MismatchBetweenCommandNamesThrows) { - rpc::CommandRequestBuilder crb; - crb.setDatabase("someDb"); - crb.setCommandName("ping"); - crb.setCommandArgs(BSON("launchMissiles" << 1)); - crb.setMetadata(BSONObj()); - auto msg = crb.done(); + std::vector<char> opCommandData; + + using std::begin; + using std::end; + + auto writeString = [&opCommandData](const std::string& str) { + opCommandData.insert(end(opCommandData), begin(str), end(str)); + opCommandData.push_back('\0'); + }; + + auto writeObj = [&opCommandData](const BSONObj& obj) { + opCommandData.insert(end(opCommandData), obj.objdata(), obj.objdata() + obj.objsize()); + }; + + auto database = std::string{"someDb"}; + writeString(database); + + auto commandName = std::string{"fakeName"}; + writeString(commandName); + + auto commandArgs = BSON("realName" << 1); + writeObj(commandArgs); + + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); + writeObj(metadata); + + Message msg; + msg.setData(dbCommand, opCommandData.data(), opCommandData.size()); + ASSERT_THROWS_CODE(rpc::ParsedOpCommand::parse(msg), UserException, 39950); } diff --git a/src/mongo/rpc/factory.cpp b/src/mongo/rpc/factory.cpp index 38bcbdbfeb8..6ef513c0284 100644 --- a/src/mongo/rpc/factory.cpp +++ b/src/mongo/rpc/factory.cpp @@ -48,19 +48,14 @@ namespace mongo { namespace rpc { -std::unique_ptr<RequestBuilderInterface> makeRequestBuilder(ProtocolSet clientProtos, - ProtocolSet serverProtos) { - return makeRequestBuilder(uassertStatusOK(negotiate(clientProtos, serverProtos))); -} - -std::unique_ptr<RequestBuilderInterface> makeRequestBuilder(Protocol proto) { +Message messageFromOpMsgRequest(Protocol proto, const OpMsgRequest& request) { switch (proto) { case Protocol::kOpMsg: - return stdx::make_unique<OpMsgRequestBuilder>(); + return request.serialize(); case Protocol::kOpQuery: - return stdx::make_unique<LegacyRequestBuilder>(); + return legacyRequestFromOpMsgRequest(request); case Protocol::kOpCommandV1: - return stdx::make_unique<CommandRequestBuilder>(); + return opCommandRequestFromOpMsgRequest(request); default: MONGO_UNREACHABLE; } diff --git a/src/mongo/rpc/factory.h b/src/mongo/rpc/factory.h index 08b3ef0a8d1..fb3f7c0b853 100644 --- a/src/mongo/rpc/factory.h +++ b/src/mongo/rpc/factory.h @@ -44,21 +44,21 @@ class Message; namespace rpc { class ReplyBuilderInterface; class ReplyInterface; -class RequestBuilderInterface; /** * Returns the appropriate concrete RequestBuilder. Throws if one cannot be chosen. */ -std::unique_ptr<RequestBuilderInterface> makeRequestBuilder(ProtocolSet clientProtos, - ProtocolSet serverProtos); - -std::unique_ptr<RequestBuilderInterface> makeRequestBuilder(Protocol proto); +std::unique_ptr<ReplyInterface> makeReply(const Message* unownedMessage); /** - * Returns the appropriate concrete Reply according to the contents of the message. - * Throws if one cannot be chosen. + * Serializes an OpMsgRequest for a server that speaks the requested protocol. */ -std::unique_ptr<ReplyInterface> makeReply(const Message* unownedMessage); +Message messageFromOpMsgRequest(Protocol proto, const OpMsgRequest&); +inline Message messageFromOpMsgRequest(ProtocolSet clientProtos, + ProtocolSet serverProtos, + const OpMsgRequest& request) { + return messageFromOpMsgRequest(uassertStatusOK(negotiate(clientProtos, serverProtos)), request); +} /** * Parses the message (from any protocol) into an OpMsgRequest. diff --git a/src/mongo/rpc/legacy_request_builder.cpp b/src/mongo/rpc/legacy_request_builder.cpp index 87fc766a608..f4a9252f921 100644 --- a/src/mongo/rpc/legacy_request_builder.cpp +++ b/src/mongo/rpc/legacy_request_builder.cpp @@ -33,6 +33,8 @@ #include <tuple> #include <utility> +#include "mongo/client/dbclientinterface.h" +#include "mongo/client/read_preference.h" #include "mongo/db/namespace_string.h" #include "mongo/rpc/metadata.h" #include "mongo/stdx/memory.h" @@ -42,65 +44,61 @@ namespace mongo { namespace rpc { -LegacyRequestBuilder::LegacyRequestBuilder() : LegacyRequestBuilder(Message()) {} - -LegacyRequestBuilder::~LegacyRequestBuilder() {} - -LegacyRequestBuilder::LegacyRequestBuilder(Message&& message) : _message{std::move(message)} { - _builder.skip(mongo::MsgData::MsgDataHeaderSize); -} - -LegacyRequestBuilder& LegacyRequestBuilder::setDatabase(StringData database) { - invariant(_state == State::kDatabase); - _ns = NamespaceString(database, "$cmd").toString(); - _state = State::kCommandName; - return *this; +namespace { +/** + * Given a command request, attempts to construct a legacy command + * object and query flags bitfield augmented with the given metadata. + */ +BSONObj downconvertRequestBody(const OpMsgRequest& request, int* queryOptions) { + invariant(request.sequences.empty()); // Not supported yet. + *queryOptions = 0; + + if (auto readPref = request.body["$readPreference"]) { + auto parsed = ReadPreferenceSetting::fromInnerBSON(readPref); + if (parsed.isOK() && parsed.getValue().canRunOnSecondary()) { + *queryOptions |= QueryOption_SlaveOk; + } + + BSONObjBuilder outer; + { + BSONObjBuilder inner(outer.subobjStart("$query")); + for (auto field : request.body) { + const auto name = field.fieldNameStringData(); + if (name == "$readPreference" || name == "$db") { + // skip field. + } else { + inner.append(field); + } + } + } + outer.append(readPref); + return outer.obj(); + } else { + return request.body.removeField("$db"); // No additional downconversion needed. + } } +} // namespace -LegacyRequestBuilder& LegacyRequestBuilder::setCommandName(StringData commandName) { - invariant(_state == State::kCommandName); - // no op, as commandName is the first element of commandArgs - _state = State::kCommandArgs; - return *this; -} +Message legacyRequestFromOpMsgRequest(const OpMsgRequest& request) { + BufBuilder builder; + builder.skip(mongo::MsgData::MsgDataHeaderSize); -LegacyRequestBuilder& LegacyRequestBuilder::setCommandArgs(BSONObj commandArgs) { - invariant(_state == State::kCommandArgs); - _commandArgs = std::move(commandArgs); - _state = State::kMetadata; - return *this; -} + const auto cmdNS = NamespaceString(request.getDatabase(), "").getCommandNS().toString(); -LegacyRequestBuilder& LegacyRequestBuilder::setMetadata(BSONObj metadata) { - invariant(_state == State::kMetadata); - BSONObj legacyCommandArgs; int queryOptions; + const auto downconvertedBody = downconvertRequestBody(request, &queryOptions); - std::tie(legacyCommandArgs, queryOptions) = - rpc::downconvertRequestMetadata(std::move(_commandArgs), std::move(metadata)); - - _builder.appendNum(queryOptions); // queryOptions - _builder.appendStr(_ns); - _builder.appendNum(0); // nToSkip - _builder.appendNum(1); // nToReturn + builder.appendNum(queryOptions); + builder.appendStr(cmdNS); + builder.appendNum(0); // nToSkip + builder.appendNum(1); // nToReturn - legacyCommandArgs.appendSelfToBufBuilder(_builder); - _state = State::kInputDocs; - return *this; -} - -Protocol LegacyRequestBuilder::getProtocol() const { - return rpc::Protocol::kOpQuery; -} + downconvertedBody.appendSelfToBufBuilder(builder); -Message LegacyRequestBuilder::done() { - invariant(_state == State::kInputDocs); - MsgData::View msg = _builder.buf(); - msg.setLen(_builder.len()); + MsgData::View msg = builder.buf(); + msg.setLen(builder.len()); msg.setOperation(dbQuery); - _message.setData(_builder.release()); - _state = State::kDone; - return std::move(_message); + return Message(builder.release()); } } // namespace rpc diff --git a/src/mongo/rpc/legacy_request_builder.h b/src/mongo/rpc/legacy_request_builder.h index 26bcb9903f1..31c8c9b0f77 100644 --- a/src/mongo/rpc/legacy_request_builder.h +++ b/src/mongo/rpc/legacy_request_builder.h @@ -28,44 +28,13 @@ #pragma once -#include <memory> - -#include "mongo/base/string_data.h" -#include "mongo/db/jsobj.h" -#include "mongo/rpc/request_builder_interface.h" #include "mongo/util/net/message.h" +#include "mongo/util/net/op_msg.h" namespace mongo { namespace rpc { -class LegacyRequestBuilder : public RequestBuilderInterface { -public: - LegacyRequestBuilder(); - ~LegacyRequestBuilder() final; - - LegacyRequestBuilder(Message&&); - - LegacyRequestBuilder& setDatabase(StringData database) final; - LegacyRequestBuilder& setCommandName(StringData commandName) final; - LegacyRequestBuilder& setMetadata(BSONObj metadata) final; - LegacyRequestBuilder& setCommandArgs(BSONObj commandArgs) final; - - Protocol getProtocol() const final; - - Message done() final; - -private: - Message _message; - BufBuilder _builder{}; - - // we need to stash this as we need metadata to - // upconvert. - BSONObj _commandArgs; - - std::string _ns{}; // copied to in setDatabase - - State _state{State::kDatabase}; -}; +Message legacyRequestFromOpMsgRequest(const OpMsgRequest& request); } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp index 715532f453d..69d7306ef45 100644 --- a/src/mongo/rpc/metadata.cpp +++ b/src/mongo/rpc/metadata.cpp @@ -161,33 +161,6 @@ CommandAndMetadata upconvertRequestMetadata(BSONObj legacyCmdObj, int queryFlags return std::make_tuple(logicalTimeCommandBob.obj(), metadataBob.obj()); } -LegacyCommandAndFlags downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata) { - int legacyQueryFlags = 0; - if (auto logicalTime = metadata[LogicalTimeMetadata::fieldName()]) { - BSONObjBuilder logicalTimeCommandBob(std::move(cmdObj)); - logicalTimeCommandBob.append(logicalTime); - cmdObj = logicalTimeCommandBob.obj(); - } - - auto readPref = metadata["$readPreference"]; - if (!readPref) - readPref = cmdObj["$readPreference"]; - - if (readPref) { - BSONObjBuilder bob; - bob.append("$query", cmdObj); - bob.append(readPref); - cmdObj = bob.obj(); - - auto parsed = ReadPreferenceSetting::fromInnerBSON(readPref); - if (parsed.isOK() && parsed.getValue().canRunOnSecondary()) { - legacyQueryFlags |= QueryOption_SlaveOk; - } - } - - return std::make_tuple(cmdObj, std::move(legacyQueryFlags)); -} - CommandReplyWithMetadata upconvertReplyMetadata(const BSONObj& legacyReply) { BSONObjBuilder commandReplyBob; BSONObjBuilder metadataBob; diff --git a/src/mongo/rpc/metadata.h b/src/mongo/rpc/metadata.h index 8d3482be458..3745a1d55ca 100644 --- a/src/mongo/rpc/metadata.h +++ b/src/mongo/rpc/metadata.h @@ -73,12 +73,6 @@ using LegacyCommandAndFlags = std::tuple<BSONObj, int>; CommandAndMetadata upconvertRequestMetadata(BSONObj legacyCmdObj, int queryFlags); /** - * Given a command object and a metadata object, attempts to construct a legacy command - * object and query flags bitfield augmented with the given metadata. - */ -LegacyCommandAndFlags downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata); - -/** * A command reply and associated metadata object. */ using CommandReplyWithMetadata = std::tuple<BSONObj, BSONObj>; diff --git a/src/mongo/rpc/op_msg_rpc_impls.h b/src/mongo/rpc/op_msg_rpc_impls.h index adc9a8f24f5..b6b3bab1073 100644 --- a/src/mongo/rpc/op_msg_rpc_impls.h +++ b/src/mongo/rpc/op_msg_rpc_impls.h @@ -30,7 +30,6 @@ #include "mongo/rpc/reply_builder_interface.h" #include "mongo/rpc/reply_interface.h" -#include "mongo/rpc/request_builder_interface.h" #include "mongo/util/net/op_msg.h" namespace mongo { @@ -85,37 +84,5 @@ private: OpMsgBuilder _builder; }; -class OpMsgRequestBuilder final : public rpc::RequestBuilderInterface { -public: - RequestBuilderInterface& setDatabase(StringData database) override { - _db = database.toString(); - return *this; - } - RequestBuilderInterface& setCommandName(StringData commandName) override { - // No-op because command name is first field name in command body. - return *this; - } - RequestBuilderInterface& setCommandArgs(BSONObj reply) override { - _builder.beginBody().appendElements(reply); - return *this; - } - RequestBuilderInterface& setMetadata(BSONObj metadata) override { - _builder.resumeBody().appendElements(metadata); - return *this; - } - rpc::Protocol getProtocol() const override { - return rpc::Protocol::kOpMsg; - } - Message done() override { - invariant(!_db.empty()); - _builder.resumeBody().append("$db", _db); - return _builder.finish(); - } - -private: - std::string _db; - OpMsgBuilder _builder; -}; - } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/request_builder_interface.h b/src/mongo/rpc/request_builder_interface.h deleted file mode 100644 index aef872cb0b4..00000000000 --- a/src/mongo/rpc/request_builder_interface.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * 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. - */ - -#pragma once - -#include <memory> - -#include "mongo/base/disallow_copying.h" -#include "mongo/rpc/protocol.h" - -namespace mongo { -class Message; -class BSONObj; -class StringData; - -namespace rpc { - -/** - * Constructs an RPC request. - */ -class RequestBuilderInterface { - MONGO_DISALLOW_COPYING(RequestBuilderInterface); - -public: - /** - * Request builders must have their fields set in order as they are immediately written into - * the underlying message buffer. This enum represents the next field that can be written - * into the builder. Note that when the builder is in state 'kInputDocs', multiple input - * docs can be added. After the builder's done() method is called it is in state 'kDone', - * and no further methods can be called. - */ - enum class State { kDatabase, kCommandName, kMetadata, kCommandArgs, kInputDocs, kDone }; - - virtual ~RequestBuilderInterface() = default; - - /** - * Sets the database that the command will be executed against. - */ - virtual RequestBuilderInterface& setDatabase(StringData database) = 0; - - /** - * Sets the name of the command to execute. - */ - virtual RequestBuilderInterface& setCommandName(StringData commandName) = 0; - - /** - * Sets the metadata associated with this command request - see metadata.h for details. - */ - virtual RequestBuilderInterface& setMetadata(BSONObj metadata) = 0; - - /** - * Sets the arguments to pass to the command. - */ - virtual RequestBuilderInterface& setCommandArgs(BSONObj commandArgs) = 0; - - /** - * Gets the protocol used to serialize this request. This should only be used for asserts, - * and not for runtime behavior changes, which should be handled with polymorphism. - */ - virtual Protocol getProtocol() const = 0; - - /** - * Writes data then transfers ownership of the message to the caller. - * The behavior of calling any methods on the object is subsequently - * undefined. - */ - virtual Message done() = 0; - -protected: - RequestBuilderInterface() = default; -}; - -} // namespace rpc -} // namespace mongo |