diff options
author | Adam Midvidy <amidvidy@gmail.com> | 2015-05-15 19:22:07 -0400 |
---|---|---|
committer | Adam Midvidy <amidvidy@gmail.com> | 2015-05-18 09:52:09 -0400 |
commit | 4e0acbc7b67c5386a3a9ed60795f9cab672a3262 (patch) | |
tree | a57f1fd8e7021f088f8bfcc91f53bd47aee81e50 | |
parent | 4f0e70b66182cbb872c4e5eefda23f1c58bdaab7 (diff) | |
download | mongo-4e0acbc7b67c5386a3a9ed60795f9cab672a3262.tar.gz |
SERVER-18288 implement a common interface for OP_COMMAND and OP_QUERY
36 files changed, 2014 insertions, 155 deletions
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index 8bb00b55e39..d40b671d1df 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -124,6 +124,8 @@ error_code("DocumentValidationFailure", 121) # Only for the document validator o error_code("ReadAfterOptimeTimeout", 122) error_code("NotAReplicaSet", 123) error_code("IncompatibleElectionProtocol", 124) +error_code("CommandFailed", 125) +error_code("RPCProtocolNegotiationFailed", 126) # Non-sequential error codes (for compatibility only) error_code("NotMaster", 10107) #this comes from assert_util.h diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript index 3bff42a61e6..20279380881 100644 --- a/src/mongo/rpc/SConscript +++ b/src/mongo/rpc/SConscript @@ -26,11 +26,38 @@ env.Library( env.Library( target=[ - 'request', + 'protocol', ], source=[ - 'request.cpp', - 'request_builder.cpp', + 'protocol.cpp', + ], +) + +env.Library( + target=[ + 'rpc', + ], + source=[ + 'factory.cpp', + ], + LIBDEPS=[ + 'command_reply', + 'command_request', + 'legacy_reply', + 'legacy_request', + 'metadata', + 'protocol', + '$BUILD_DIR/mongo/db/server_parameters', + ], +) + +env.Library( + target=[ + 'command_request', + ], + source=[ + 'command_request.cpp', + 'command_request_builder.cpp', ], LIBDEPS=[ 'document_range', @@ -40,11 +67,11 @@ env.Library( env.Library( target=[ - 'reply', + 'legacy_request', ], source=[ - 'reply.cpp', - 'reply_builder.cpp' + 'legacy_request.cpp', + 'legacy_request_builder.cpp', ], LIBDEPS=[ 'document_range', @@ -52,18 +79,58 @@ env.Library( ], ) +env.Library( + target=[ + 'command_reply', + ], + source=[ + 'command_reply.cpp', + 'command_reply_builder.cpp', + 'reply_builder_interface.cpp', + ], + LIBDEPS=[ + 'document_range', + '$BUILD_DIR/mongo/util/net/network', + ], +) + +env.Library( + target=[ + 'legacy_reply', + ], + source=[ + 'legacy_reply.cpp', + 'legacy_reply_builder.cpp' + ], + LIBDEPS=[ + 'document_range', + '$BUILD_DIR/mongo/util/net/network', + ], +) + +env.Library( + target=[ + 'metadata', + ], + source=[ + 'metadata.cpp' + ], +) + env.CppUnitTest( target=[ 'rpc_test', ], source=[ - 'reply_builder_test.cpp', - 'reply_test.cpp', - 'request_builder_test.cpp', - 'request_test.cpp', + 'command_reply_builder_test.cpp', + 'command_reply_test.cpp', + 'command_request_builder_test.cpp', + 'command_request_test.cpp', + 'protocol_test.cpp', ], LIBDEPS=[ - 'request', - 'reply', + 'command_request', + 'command_reply', + 'protocol', ], ) diff --git a/src/mongo/rpc/reply.cpp b/src/mongo/rpc/command_reply.cpp index e45f72091b9..449308fbed3 100644 --- a/src/mongo/rpc/reply.cpp +++ b/src/mongo/rpc/command_reply.cpp @@ -28,7 +28,7 @@ #include "mongo/platform/basic.h" -#include "mongo/rpc/reply.h" +#include "mongo/rpc/command_reply.h" #include <tuple> @@ -38,15 +38,17 @@ namespace mongo { namespace rpc { - Reply::Reply(const Message* message) + CommandReply::CommandReply(const Message* message) : _message(message) { - char* begin = _message->singleData().data(); + const char* begin = _message->singleData().data(); std::size_t length = _message->singleData().dataLen(); - invariant(length <= MaxMessageSizeBytes); // checked by message_port.cpp + // This check failing would normally be operation fatal, but we expect it to have been + // done earlier in the network layer, so we make it an invariant. + invariant(length <= MaxMessageSizeBytes); - char* messageEnd = begin + length; + const char* messageEnd = begin + length; ConstDataRangeCursor cur(begin, messageEnd); // TODO(amidvidy): we don't currently handle BSON validation. @@ -57,24 +59,24 @@ namespace rpc { _outputDocs = DocumentRange(cur.data(), messageEnd); } - const BSONObj& Reply::getMetadata() const { + const BSONObj& CommandReply::getMetadata() const { return _metadata; } - const BSONObj& Reply::getCommandReply() const { + const BSONObj& CommandReply::getCommandReply() const { return _commandReply; } - DocumentRange Reply::getOutputDocs() const { + DocumentRange CommandReply::getOutputDocs() const { return _outputDocs; } - bool operator==(const Reply& lhs, const Reply& rhs) { + bool operator==(const CommandReply& lhs, const CommandReply& rhs) { return std::tie(lhs._metadata, lhs._commandReply, lhs._outputDocs) == std::tie(rhs._metadata, rhs._commandReply, rhs._outputDocs); } - bool operator!=(const Reply& lhs, const Reply& rhs) { + bool operator!=(const CommandReply& lhs, const CommandReply& rhs) { return !(lhs == rhs); } diff --git a/src/mongo/rpc/reply.h b/src/mongo/rpc/command_reply.h index befd6d70719..8f888e6190d 100644 --- a/src/mongo/rpc/reply.h +++ b/src/mongo/rpc/command_reply.h @@ -28,8 +28,9 @@ #pragma once -#include "mongo/bson/bsonobj.h" +#include "mongo/db/jsobj.h" #include "mongo/rpc/document_range.h" +#include "mongo/rpc/reply_interface.h" namespace mongo { class Message; @@ -45,7 +46,7 @@ namespace rpc { * explicit. * See SERVER-16730 for additional details. */ - class Reply { + class CommandReply : public ReplyInterface { public: /** * Construct a Reply from a Message. @@ -54,18 +55,18 @@ namespace rpc { * * The underlying Message also handles the wire-protocol header. */ - explicit Reply(const Message* message); + explicit CommandReply(const Message* message); /** * Accessor for the metadata object. Metadata is generally used for information * that is independent of any specific command, e.g. auditing information. */ - const BSONObj& getMetadata() const; + const BSONObj& getMetadata() const final; /** * The result of executing the command. */ - const BSONObj& getCommandReply() const; + const BSONObj& getCommandReply() const final; /** * A variable number of BSON documents returned by the command. It is valid for the @@ -77,10 +78,10 @@ namespace rpc { * ... do stuff with doc * } */ - DocumentRange getOutputDocs() const; + DocumentRange getOutputDocs() const final; - friend bool operator==(const Reply& lhs, const Reply& rhs); - friend bool operator!=(const Reply& lhs, const Reply& rhs); + friend bool operator==(const CommandReply& lhs, const CommandReply& rhs); + friend bool operator!=(const CommandReply& lhs, const CommandReply& rhs); private: const Message* _message; diff --git a/src/mongo/rpc/reply_builder.cpp b/src/mongo/rpc/command_reply_builder.cpp index cd214b33864..09d4156e09f 100644 --- a/src/mongo/rpc/reply_builder.cpp +++ b/src/mongo/rpc/command_reply_builder.cpp @@ -30,55 +30,59 @@ #include <utility> -#include "mongo/rpc/reply_builder.h" +#include "mongo/rpc/command_reply_builder.h" #include "mongo/stdx/memory.h" #include "mongo/util/assert_util.h" namespace mongo { namespace rpc { - ReplyBuilder::ReplyBuilder() + CommandReplyBuilder::CommandReplyBuilder() : _message{stdx::make_unique<Message>()} {} - ReplyBuilder::ReplyBuilder(std::unique_ptr<Message> message) + CommandReplyBuilder::CommandReplyBuilder(std::unique_ptr<Message> message) : _message{std::move(message)} {} - ReplyBuilder& ReplyBuilder::setMetadata(const BSONObj& metadata) { - invariant(_buildState == BuildState::kMetadata); + CommandReplyBuilder& CommandReplyBuilder::setMetadata(BSONObj metadata) { + invariant(_state == State::kMetadata); metadata.appendSelfToBufBuilder(_builder); - _buildState = BuildState::kCommandReply; + _state = State::kCommandReply; return *this; } - ReplyBuilder& ReplyBuilder::setCommandReply(const BSONObj& commandReply) { - invariant(_buildState == BuildState::kCommandReply); + CommandReplyBuilder& CommandReplyBuilder::setRawCommandReply(BSONObj commandReply) { + invariant(_state == State::kCommandReply); commandReply.appendSelfToBufBuilder(_builder); - _buildState = BuildState::kOutputDocs; + _state = State::kOutputDocs; return *this; } - ReplyBuilder& ReplyBuilder::addOutputDocs(DocumentRange outputDocs) { - invariant(_buildState == BuildState::kOutputDocs); + CommandReplyBuilder& CommandReplyBuilder::addOutputDocs(DocumentRange outputDocs) { + invariant(_state == State::kOutputDocs); auto rangeData = outputDocs.data(); _builder.appendBuf(rangeData.data(), rangeData.length()); // leave state as is as we can add as many outputDocs as we want. return *this; } - ReplyBuilder& ReplyBuilder::addOutputDoc(const BSONObj& outputDoc) { - invariant(_buildState == BuildState::kOutputDocs); + CommandReplyBuilder& CommandReplyBuilder::addOutputDoc(BSONObj outputDoc) { + invariant(_state == State::kOutputDocs); outputDoc.appendSelfToBufBuilder(_builder); return *this; } - std::unique_ptr<Message> ReplyBuilder::done() { - invariant(_buildState == BuildState::kOutputDocs); + ReplyBuilderInterface::State CommandReplyBuilder::getState() const { + return _state; + } + + std::unique_ptr<Message> CommandReplyBuilder::done() { + invariant(_state == State::kOutputDocs); // TODO: we can elide a large copy here by transferring the internal buffer of // the BufBuilder to the Message. _message->setData(dbCommandReply, _builder.buf(), _builder.len()); - _buildState = BuildState::kDone; + _state = State::kDone; return std::move(_message); } diff --git a/src/mongo/rpc/reply_builder.h b/src/mongo/rpc/command_reply_builder.h index 129edbc81f4..cf2cf01ee93 100644 --- a/src/mongo/rpc/reply_builder.h +++ b/src/mongo/rpc/command_reply_builder.h @@ -30,9 +30,11 @@ #include <memory> -#include "mongo/bson/bsonobj.h" +#include "mongo/base/status_with.h" +#include "mongo/db/jsobj.h" #include "mongo/rpc/document_range.h" -#include "mongo/util/net/message.h" // need Message destructor for unique_ptr +#include "mongo/rpc/reply_builder_interface.h" +#include "mongo/util/net/message.h" namespace mongo { namespace rpc { @@ -40,46 +42,41 @@ namespace rpc { /** * Constructs an OP_COMMANDREPLY message. */ - class ReplyBuilder { + class CommandReplyBuilder : public ReplyBuilderInterface { public: /** * Constructs an OP_COMMANDREPLY in a new buffer. */ - ReplyBuilder(); + CommandReplyBuilder(); /* * Constructs an OP_COMMANDREPLY in an existing buffer. Ownership of the buffer - * will be transfered to the ReplyBuilder. + * will be transfered to the CommandReplyBuilder. */ - ReplyBuilder(std::unique_ptr<Message> message); + CommandReplyBuilder(std::unique_ptr<Message> message); - ReplyBuilder& setMetadata(const BSONObj& metadata); - ReplyBuilder& setCommandReply(const BSONObj& commandReply); + CommandReplyBuilder& setMetadata(BSONObj metadata) final; + CommandReplyBuilder& setRawCommandReply(BSONObj commandReply) final; - ReplyBuilder& addOutputDocs(DocumentRange outputDocs); - ReplyBuilder& addOutputDoc(const BSONObj& outputDoc); + CommandReplyBuilder& addOutputDocs(DocumentRange outputDocs) final; + CommandReplyBuilder& addOutputDoc(BSONObj outputDoc) final; + + State getState() 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. */ - std::unique_ptr<Message> done(); + std::unique_ptr<Message> done() final; private: // Default values are all empty. BufBuilder _builder{}; std::unique_ptr<Message> _message; - enum class BuildState { - kMetadata, - kCommandReply, - kOutputDocs, - kDone - }; - - BuildState _buildState{BuildState::kMetadata}; + State _state{State::kMetadata}; }; } // namespace rpc diff --git a/src/mongo/rpc/reply_builder_test.cpp b/src/mongo/rpc/command_reply_builder_test.cpp index 7302036fe4e..8771bf3691d 100644 --- a/src/mongo/rpc/reply_builder_test.cpp +++ b/src/mongo/rpc/command_reply_builder_test.cpp @@ -28,12 +28,11 @@ #include "mongo/platform/basic.h" -#include "mongo/bson/bsonobj.h" -#include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/util/builder.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/command_reply.h" +#include "mongo/rpc/command_reply_builder.h" #include "mongo/rpc/document_range.h" -#include "mongo/rpc/reply.h" -#include "mongo/rpc/reply_builder.h" #include "mongo/unittest/unittest.h" namespace { @@ -47,7 +46,7 @@ namespace { auto metadata = metadataBob.done(); BSONObjBuilder commandReplyBob{}; - commandReplyBob.append("bar", "baz"); + commandReplyBob.append("bar", "baz").append("ok", 1.0); auto commandReply = commandReplyBob.done(); BSONObjBuilder outputDoc1Bob{}; @@ -69,14 +68,14 @@ namespace { rpc::DocumentRange outputDocRange{outputDocs.buf(), outputDocs.buf() + outputDocs.len()}; - rpc::ReplyBuilder r; + rpc::CommandReplyBuilder r; auto msg = r.setMetadata(metadata) .setCommandReply(commandReply) .addOutputDocs(outputDocRange) .done(); - rpc::Reply parsed(msg.get()); + rpc::CommandReply parsed(msg.get()); ASSERT_EQUALS(parsed.getMetadata(), metadata); ASSERT_EQUALS(parsed.getCommandReply(), commandReply); diff --git a/src/mongo/rpc/reply_test.cpp b/src/mongo/rpc/command_reply_test.cpp index 44c0ad7896e..48930bbe1b9 100644 --- a/src/mongo/rpc/reply_test.cpp +++ b/src/mongo/rpc/command_reply_test.cpp @@ -34,9 +34,9 @@ #include "mongo/base/data_type_endian.h" #include "mongo/base/data_view.h" -#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/jsobj.h" #include "mongo/platform/cstdint.h" -#include "mongo/rpc/reply.h" +#include "mongo/rpc/command_reply.h" #include "mongo/stdx/memory.h" #include "mongo/unittest/unittest.h" #include "mongo/util/net/message.h" @@ -105,7 +105,7 @@ namespace { auto outputDoc2 = outputDoc2Bob.done(); writeObj(outputDoc2); - rpc::Reply opCmdReply{buildMessage()}; + rpc::CommandReply opCmdReply{buildMessage()}; ASSERT_EQUALS(opCmdReply.getMetadata(), metadata); ASSERT_EQUALS(opCmdReply.getCommandReply(), commandReply); @@ -125,7 +125,7 @@ namespace { } TEST_F(ReplyTest, EmptyMessageThrows) { - ASSERT_THROWS(rpc::Reply{buildMessage()}, UserException); + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); } TEST_F(ReplyTest, MetadataOnlyThrows) { @@ -134,7 +134,7 @@ namespace { auto metadata = metadataBob.done(); writeObj(metadata); - ASSERT_THROWS(rpc::Reply{buildMessage()}, UserException); + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); } TEST_F(ReplyTest, MetadataInvalidLengthThrows) { @@ -151,7 +151,7 @@ namespace { auto commandReply = commandReplyBob.done(); writeObj(commandReply); - ASSERT_THROWS(rpc::Reply{buildMessage()}, UserException); + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); } TEST_F(ReplyTest, InvalidLengthThrows) { @@ -169,7 +169,7 @@ namespace { DataView(const_cast<char*>(commandReply.objdata())).write<LittleEndian<int32_t>>(100000); writeObj(commandReply, trueSize); - ASSERT_THROWS(rpc::Reply{buildMessage()}, UserException); + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); } } diff --git a/src/mongo/rpc/request.cpp b/src/mongo/rpc/command_request.cpp index b5491928729..00a9fb7b477 100644 --- a/src/mongo/rpc/request.cpp +++ b/src/mongo/rpc/command_request.cpp @@ -28,7 +28,7 @@ #include "mongo/platform/basic.h" -#include "mongo/rpc/request.h" +#include "mongo/rpc/command_request.h" #include <string> #include <utility> @@ -36,7 +36,7 @@ #include "mongo/base/data_range_cursor.h" #include "mongo/base/data_type_string_data.h" #include "mongo/base/data_type_terminated.h" -#include "mongo/bson/bsonobj.h" +#include "mongo/db/jsobj.h" #include "mongo/util/assert_util.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/message.h" @@ -54,7 +54,7 @@ namespace rpc { } // namespace - Request::Request(const Message* message) + CommandRequest::CommandRequest(const Message* message) : _message(message) { char* begin = _message->singleData().data(); std::size_t length = _message->singleData().dataLen(); @@ -87,27 +87,27 @@ namespace rpc { _inputDocs = DocumentRange{cur.data(), messageEnd}; } - StringData Request::getDatabase() const { + StringData CommandRequest::getDatabase() const { return _database; } - StringData Request::getCommandName() const { + StringData CommandRequest::getCommandName() const { return _commandName; } - const BSONObj& Request::getMetadata() const { + const BSONObj& CommandRequest::getMetadata() const { return _metadata; } - const BSONObj& Request::getCommandArgs() const { + const BSONObj& CommandRequest::getCommandArgs() const { return _commandArgs; } - DocumentRange Request::getInputDocs() const { + DocumentRange CommandRequest::getInputDocs() const { return _inputDocs; } - bool operator==(const Request& lhs, const Request& rhs) { + bool operator==(const CommandRequest& lhs, const CommandRequest& rhs) { return std::tie(lhs._database, lhs._commandName, lhs._metadata, @@ -120,7 +120,7 @@ namespace rpc { rhs._inputDocs); } - bool operator!=(const Request& lhs, const Request& rhs) { + bool operator!=(const CommandRequest& lhs, const CommandRequest& rhs) { return !(lhs == rhs); } diff --git a/src/mongo/rpc/request.h b/src/mongo/rpc/command_request.h index 12891354958..b96323477c2 100644 --- a/src/mongo/rpc/request.h +++ b/src/mongo/rpc/command_request.h @@ -29,8 +29,9 @@ #pragma once #include "mongo/base/string_data.h" -#include "mongo/bson/bsonobj.h" +#include "mongo/db/jsobj.h" #include "mongo/rpc/document_range.h" +#include "mongo/rpc/request_interface.h" namespace mongo { class Message; @@ -43,34 +44,36 @@ namespace rpc { * * TODO: BSON validation. See SERVER-18167 for details. */ - class Request { + class CommandRequest : public RequestInterface { public: /** * Construct a Request from a Message. Underlying message MUST outlive the Request. * Required fields are parsed eagerly, inputDocs are parsed lazily. */ - explicit Request(const Message* message); + explicit CommandRequest(const Message* message); + + ~CommandRequest() = default; /** * The database that the command is to be executed on. */ - StringData getDatabase() const; + StringData getDatabase() const final; /** * The name of the command to execute. */ - StringData getCommandName() const; + StringData getCommandName() const final; /** * The metadata associated with the command request. This is information that is * independent of any specific command, i.e. auditing information. */ - const BSONObj& getMetadata() const; + const BSONObj& getMetadata() const final; /** * The arguments to the command - this is passed to the command's run() method. */ - const BSONObj& getCommandArgs() const; + const BSONObj& getCommandArgs() const final; /** * A variable number of BSON documents to pass to the command. It is valid for @@ -82,10 +85,10 @@ namespace rpc { * ... do stuff with doc * } */ - DocumentRange getInputDocs() const; + DocumentRange getInputDocs() const final; - friend bool operator==(const Request& lhs, const Request& rhs); - friend bool operator!=(const Request& lhs, const Request& rhs); + friend bool operator==(const CommandRequest& lhs, const CommandRequest& rhs); + friend bool operator!=(const CommandRequest& lhs, const CommandRequest& rhs); private: const Message* _message; diff --git a/src/mongo/rpc/request_builder.cpp b/src/mongo/rpc/command_request_builder.cpp index bbbc842ab96..b65ddc3f0b3 100644 --- a/src/mongo/rpc/request_builder.cpp +++ b/src/mongo/rpc/command_request_builder.cpp @@ -28,7 +28,7 @@ #include "mongo/platform/basic.h" -#include "mongo/rpc/request_builder.h" +#include "mongo/rpc/command_request_builder.h" #include <utility> @@ -38,61 +38,68 @@ namespace mongo { namespace rpc { - RequestBuilder::RequestBuilder() + CommandRequestBuilder::CommandRequestBuilder() : _message{stdx::make_unique<Message>()} {} - RequestBuilder::RequestBuilder(std::unique_ptr<Message> message) + CommandRequestBuilder::~CommandRequestBuilder() + {} + + CommandRequestBuilder::CommandRequestBuilder(std::unique_ptr<Message> message) : _message{std::move(message)} {} - RequestBuilder& RequestBuilder::setDatabase(StringData database) { - invariant(_buildState == BuildState::kDatabase); + CommandRequestBuilder& CommandRequestBuilder::setDatabase(StringData database) { + invariant(_state == State::kDatabase); _builder.appendStr(database); - _buildState = BuildState::kCommandName; + _state = State::kCommandName; return *this; } - RequestBuilder& RequestBuilder::setCommandName(StringData commandName) { - invariant(_buildState == BuildState::kCommandName); + CommandRequestBuilder& CommandRequestBuilder::setCommandName(StringData commandName) { + invariant(_state == State::kCommandName); _builder.appendStr(commandName); - _buildState = BuildState::kMetadata; + _state = State::kMetadata; return *this; } - RequestBuilder& RequestBuilder::setMetadata(const BSONObj& metadata) { - invariant(_buildState == BuildState::kMetadata); + CommandRequestBuilder& CommandRequestBuilder::setMetadata(BSONObj metadata) { + invariant(_state == State::kMetadata); metadata.appendSelfToBufBuilder(_builder); - _buildState = BuildState::kCommandArgs; + _state = State::kCommandArgs; return *this; } - RequestBuilder& RequestBuilder::setCommandArgs(const BSONObj& commandArgs) { - invariant(_buildState == BuildState::kCommandArgs); + CommandRequestBuilder& CommandRequestBuilder::setCommandArgs(BSONObj commandArgs) { + invariant(_state == State::kCommandArgs); commandArgs.appendSelfToBufBuilder(_builder); - _buildState = BuildState::kInputDocs; + _state = State::kInputDocs; return *this; } - RequestBuilder& RequestBuilder::addInputDocs(DocumentRange inputDocs) { - invariant(_buildState == BuildState::kInputDocs); + CommandRequestBuilder& CommandRequestBuilder::addInputDocs(DocumentRange inputDocs) { + invariant(_state == State::kInputDocs); auto rangeData = inputDocs.data(); _builder.appendBuf(rangeData.data(), rangeData.length()); return *this; } - RequestBuilder& RequestBuilder::addInputDoc(const BSONObj& inputDoc) { - invariant(_buildState == BuildState::kInputDocs); + CommandRequestBuilder& CommandRequestBuilder::addInputDoc(BSONObj inputDoc) { + invariant(_state == State::kInputDocs); inputDoc.appendSelfToBufBuilder(_builder); return *this; } - std::unique_ptr<Message> RequestBuilder::done() { - invariant(_buildState == BuildState::kInputDocs); + RequestBuilderInterface::State CommandRequestBuilder::getState() const { + return _state; + } + + std::unique_ptr<Message> CommandRequestBuilder::done() { + invariant(_state == State::kInputDocs); // TODO: we can elide a large copy here by transferring the internal buffer of // the BufBuilder to the Message. _message->setData(dbCommand, _builder.buf(), _builder.len()); - _buildState = BuildState::kDone; + _state = State::kDone; return std::move(_message); } diff --git a/src/mongo/rpc/request_builder.h b/src/mongo/rpc/command_request_builder.h index 5715667ab35..12298a48f3c 100644 --- a/src/mongo/rpc/request_builder.h +++ b/src/mongo/rpc/command_request_builder.h @@ -31,8 +31,9 @@ #include <memory> #include "mongo/base/string_data.h" -#include "mongo/bson/bsonobj.h" +#include "mongo/db/jsobj.h" #include "mongo/rpc/document_range.h" +#include "mongo/rpc/request_builder_interface.h" #include "mongo/util/net/message.h" namespace mongo { @@ -41,49 +42,48 @@ namespace rpc { /** * Constructs an OP_COMMAND message. */ - class RequestBuilder { + class CommandRequestBuilder : public RequestBuilderInterface { public: /** * Constructs an OP_COMMAND in a new buffer. */ - RequestBuilder(); + CommandRequestBuilder(); + + ~CommandRequestBuilder() final; /** * Construct an OP_COMMAND in an existing buffer. Ownership of the buffer will be - * transfered to the RequestBuilder. + * transfered to the CommandRequestBuilder. */ - RequestBuilder(std::unique_ptr<Message> message); + CommandRequestBuilder(std::unique_ptr<Message> message); + + CommandRequestBuilder& setDatabase(StringData database) final; + + CommandRequestBuilder& setCommandName(StringData commandName) final; + + CommandRequestBuilder& setMetadata(BSONObj metadata) final; - RequestBuilder& setDatabase(StringData database); - RequestBuilder& setCommandName(StringData commandName); - RequestBuilder& setMetadata(const BSONObj& metadata); - RequestBuilder& setCommandArgs(const BSONObj& commandArgs); + CommandRequestBuilder& setCommandArgs(BSONObj commandArgs) final; - RequestBuilder& addInputDocs(DocumentRange inputDocs); - RequestBuilder& addInputDoc(const BSONObj& inputDoc); + CommandRequestBuilder& addInputDocs(DocumentRange inputDocs) final; + + CommandRequestBuilder& addInputDoc(BSONObj inputDoc) final; + + State getState() 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. */ - std::unique_ptr<Message> done(); + std::unique_ptr<Message> done() final; private: BufBuilder _builder{}; std::unique_ptr<Message> _message; - enum class BuildState { - kDatabase, - kCommandName, - kMetadata, - kCommandArgs, - kInputDocs, - kDone - }; - - BuildState _buildState{BuildState::kDatabase}; + State _state{State::kDatabase}; }; } // rpc diff --git a/src/mongo/rpc/request_builder_test.cpp b/src/mongo/rpc/command_request_builder_test.cpp index 1d47ad79c9b..10a4f2d8795 100644 --- a/src/mongo/rpc/request_builder_test.cpp +++ b/src/mongo/rpc/command_request_builder_test.cpp @@ -28,12 +28,10 @@ #include "mongo/platform/basic.h" -#include "mongo/bson/bsonobj.h" -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/bson/util/builder.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/command_request.h" +#include "mongo/rpc/command_request_builder.h" #include "mongo/rpc/document_range.h" -#include "mongo/rpc/request.h" -#include "mongo/rpc/request_builder.h" #include "mongo/unittest/unittest.h" namespace { @@ -72,7 +70,7 @@ namespace { rpc::DocumentRange inputDocRange{inputDocs.buf(), inputDocs.buf() + inputDocs.len()}; - rpc::RequestBuilder r; + rpc::CommandRequestBuilder r; auto msg = r.setDatabase(databaseName) .setCommandName(commandName) @@ -81,7 +79,7 @@ namespace { .addInputDocs(inputDocRange) .done(); - rpc::Request parsed(msg.get()); + rpc::CommandRequest parsed(msg.get()); ASSERT_EQUALS(parsed.getDatabase(), databaseName); ASSERT_EQUALS(parsed.getCommandName(), commandName); diff --git a/src/mongo/rpc/request_test.cpp b/src/mongo/rpc/command_request_test.cpp index da7c2d316db..7f5c5b52214 100644 --- a/src/mongo/rpc/request_test.cpp +++ b/src/mongo/rpc/command_request_test.cpp @@ -32,8 +32,8 @@ #include <string> #include <vector> -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/rpc/request.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/command_request.h" #include "mongo/unittest/unittest.h" #include "mongo/util/net/message.h" @@ -86,7 +86,7 @@ namespace { Message toSend; toSend.setData(dbCommand, opCommandData.data(), opCommandData.size()); - rpc::Request opCmd{&toSend}; + rpc::CommandRequest opCmd{&toSend}; ASSERT_EQUALS(opCmd.getCommandName(), commandName); ASSERT_EQUALS(opCmd.getDatabase(), database); @@ -107,4 +107,3 @@ namespace { ASSERT_TRUE(inputDocRangeIter == inputDocRange.end()); } } // namespace - diff --git a/src/mongo/rpc/document_range.h b/src/mongo/rpc/document_range.h index 04f00179fdd..aa8d3131b13 100644 --- a/src/mongo/rpc/document_range.h +++ b/src/mongo/rpc/document_range.h @@ -33,7 +33,7 @@ #include "mongo/base/data_range.h" #include "mongo/base/data_range_cursor.h" -#include "mongo/bson/bsonobj.h" +#include "mongo/db/jsobj.h" namespace mongo { namespace rpc { diff --git a/src/mongo/rpc/factory.cpp b/src/mongo/rpc/factory.cpp new file mode 100644 index 00000000000..1edca3950b3 --- /dev/null +++ b/src/mongo/rpc/factory.cpp @@ -0,0 +1,70 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/factory.h" + +#include "mongo/rpc/command_reply.h" +#include "mongo/rpc/command_request_builder.h" +#include "mongo/rpc/legacy_reply.h" +#include "mongo/rpc/legacy_request_builder.h" +#include "mongo/rpc/protocol.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace rpc { + + std::unique_ptr<RequestBuilderInterface> makeRequestBuilder(ProtocolSet clientProtos, + ProtocolSet serverProtos) { + switch (uassertStatusOK(negotiate(clientProtos, serverProtos))) { + case Protocol::kOpQuery: + return stdx::make_unique<LegacyRequestBuilder>(); + case Protocol::kOpCommandV1: + return stdx::make_unique<CommandRequestBuilder>(); + default: + MONGO_UNREACHABLE; + } + } + + std::unique_ptr<ReplyInterface> makeReply(const Message* unownedMessage, + ProtocolSet clientProtos, + ProtocolSet serverProtos) { + switch (uassertStatusOK(negotiate(clientProtos, serverProtos))) { + case Protocol::kOpQuery: + return stdx::make_unique<LegacyReply>(unownedMessage); + case Protocol::kOpCommandV1: + return stdx::make_unique<CommandReply>(unownedMessage); + default: + MONGO_UNREACHABLE; + } + } + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/factory.h b/src/mongo/rpc/factory.h new file mode 100644 index 00000000000..0e178a54ecb --- /dev/null +++ b/src/mongo/rpc/factory.h @@ -0,0 +1,60 @@ +/** + * 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 "mongo/rpc/protocol.h" + +#include <memory> + +/** + * Utilities to construct the correct concrete rpc class based on what the remote server + * supports, and what the client has been configured to do. + */ + +namespace mongo { + class Message; + +namespace rpc { + class ReplyInterface; + class RequestBuilderInterface; + + /** + * Returns the appropriate concrete RequestBuilder. Throws if one cannot be chosen. + */ + std::unique_ptr<RequestBuilderInterface> makeRequestBuilder(ProtocolSet clientProtos, + ProtocolSet serverProtos); + /** + * Returns the appropriate concrete Reply. Throws if one cannot be chosen. + */ + std::unique_ptr<ReplyInterface> makeReply(const Message* unownedMessage, + ProtocolSet clientProtos, + ProtocolSet serverProtos); + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_reply.cpp b/src/mongo/rpc/legacy_reply.cpp new file mode 100644 index 00000000000..1dc40c83cd6 --- /dev/null +++ b/src/mongo/rpc/legacy_reply.cpp @@ -0,0 +1,82 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/legacy_reply.h" + +#include <utility> + +#include "mongo/util/assert_util.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace rpc { + + LegacyReply::LegacyReply(const Message* message) + : _message(std::move(message)) { + invariant(message->operation() == opReply); + + QueryResult::View qr = _message->singleData().view2ptr(); + + // should be checked by caller. + invariant(qr.msgdata().getOperation() == opReply); + + uassert(ErrorCodes::BadValue, + str::stream() << "Got legacy command reply with a bad cursorId field," + << " expected a value of 0 but got " << qr.getCursorId(), + qr.getCursorId() == 0); + + uassert(ErrorCodes::BadValue, + str::stream() << "Got legacy command reply with a bad nReturned field," + << " expected a value of 1 but got " << qr.getNReturned(), + qr.getNReturned() == 1); + + uassert(ErrorCodes::BadValue, + str::stream() << "Got legacy command reply with a bad startingFrom field," + << " expected a value of 0 but got " << qr.getStartingFrom(), + qr.getStartingFrom() == 0); + // TODO bson validation + _commandReply = BSONObj(qr.data()); + } + + const BSONObj& LegacyReply::getMetadata() const { + return _metadataPlaceholder; + } + + const BSONObj& LegacyReply::getCommandReply() const { + return _commandReply; + } + + DocumentRange LegacyReply::getOutputDocs() const { + // return empty range + return DocumentRange{}; + } + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_reply.h b/src/mongo/rpc/legacy_reply.h new file mode 100644 index 00000000000..fddc050a71b --- /dev/null +++ b/src/mongo/rpc/legacy_reply.h @@ -0,0 +1,87 @@ +/** + * 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 "mongo/db/dbmessage.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/document_range.h" +#include "mongo/rpc/reply_interface.h" + +namespace mongo { + class Message; + +namespace rpc { + + /** + * Immutable view of an OP_REPLY legacy-style command reply. + * + * TODO: BSON validation (SERVER-18167) + */ + class LegacyReply : public ReplyInterface { + public: + + /** + * Construct a Reply from a Message. + * The underlying message MUST outlive the Reply. + */ + explicit LegacyReply(const Message* message); + + /** + * Accessor for the metadata object. Metadata is generally used for information + * that is independent of any specific command, e.g. auditing information. + */ + const BSONObj& getMetadata() const final; + + /** + * The result of executing the command. + */ + const BSONObj& getCommandReply() const final; + + /** + * A variable number of BSON documents returned by the command. It is valid for the + * returned range to be empty. + * + * Example usage: + * + * for (auto&& doc : reply.getOutputDocs()) { + * ... do stuff with doc + * } + */ + DocumentRange getOutputDocs() const final; + + private: + const Message* _message; + + // TODO: SERVER-18236 + BSONObj _metadataPlaceholder{}; + BSONObj _commandReply{}; // will hold unowned + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_reply_builder.cpp b/src/mongo/rpc/legacy_reply_builder.cpp new file mode 100644 index 00000000000..4ed4ff88886 --- /dev/null +++ b/src/mongo/rpc/legacy_reply_builder.cpp @@ -0,0 +1,101 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/db/dbmessage.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/legacy_reply_builder.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace rpc { + + LegacyReplyBuilder::LegacyReplyBuilder() + : LegacyReplyBuilder(stdx::make_unique<Message>()) + {} + + LegacyReplyBuilder::LegacyReplyBuilder(std::unique_ptr<Message> message) + : _message{std::move(message)} { + _builder.skip(sizeof(QueryResult::Value)); + } + + LegacyReplyBuilder::~LegacyReplyBuilder() {} + + LegacyReplyBuilder& LegacyReplyBuilder::setMetadata(BSONObj metadata) { + invariant(_state == State::kMetadata); + // no op for now: SERVER-18236 + _state = State::kCommandReply; + return *this; + } + + LegacyReplyBuilder& LegacyReplyBuilder::setRawCommandReply(BSONObj commandReply) { + invariant(_state == State::kCommandReply); + commandReply.appendSelfToBufBuilder(_builder); + _state = State::kOutputDocs; + return *this; + } + + LegacyReplyBuilder& LegacyReplyBuilder::addOutputDocs(DocumentRange outputDocs) { + invariant(_state == State::kOutputDocs); + // no op + return *this; + } + + LegacyReplyBuilder& LegacyReplyBuilder::addOutputDoc(BSONObj outputDoc) { + invariant(_state == State::kOutputDocs); + // no op + return *this; + } + + ReplyBuilderInterface::State LegacyReplyBuilder::getState() const { + return _state; + } + + std::unique_ptr<Message> LegacyReplyBuilder::done() { + invariant(_state == State::kOutputDocs); + std::unique_ptr<Message> message = stdx::make_unique<Message>(); + + QueryResult::View qr = _builder.buf(); + qr.setResultFlagsToOk(); + qr.msgdata().setLen(_builder.len()); + qr.msgdata().setOperation(opReply); + qr.setCursorId(0); + qr.setStartingFrom(0); + qr.setNReturned(1); + _builder.decouple(); + + message->setData(qr.view2ptr(), true); + + _state = State::kDone; + return std::move(message); + } + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_reply_builder.h b/src/mongo/rpc/legacy_reply_builder.h new file mode 100644 index 00000000000..35dc49a911e --- /dev/null +++ b/src/mongo/rpc/legacy_reply_builder.h @@ -0,0 +1,65 @@ +/** + * 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/status_with.h" +#include "mongo/bson/util/builder.h" +#include "mongo/rpc/document_range.h" +#include "mongo/rpc/reply_builder_interface.h" + +namespace mongo { +namespace rpc { + + class LegacyReplyBuilder : public ReplyBuilderInterface { + public: + + LegacyReplyBuilder(); + LegacyReplyBuilder(std::unique_ptr<Message>); + ~LegacyReplyBuilder() final; + + LegacyReplyBuilder& setMetadata(BSONObj metadata) final; + LegacyReplyBuilder& setRawCommandReply(BSONObj commandReply) final; + + LegacyReplyBuilder& addOutputDocs(DocumentRange outputDocs) final; + LegacyReplyBuilder& addOutputDoc(BSONObj outputDoc) final; + + State getState() const final; + + std::unique_ptr<Message> done() final; + + private: + BufBuilder _builder{}; + std::unique_ptr<Message> _message; + State _state{State::kMetadata}; + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_request.cpp b/src/mongo/rpc/legacy_request.cpp new file mode 100644 index 00000000000..bb79928abb9 --- /dev/null +++ b/src/mongo/rpc/legacy_request.cpp @@ -0,0 +1,78 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include <utility> + +#include "mongo/db/namespace_string.h" +#include "mongo/rpc/legacy_request.h" +#include "mongo/rpc/metadata.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace rpc { + + LegacyRequest::LegacyRequest(const Message *message) + : _message(std::move(message)) + , _dbMessage(*message) + , _queryMessage(_dbMessage) + , _database(NamespaceString(_queryMessage.ns).db().toString()) { + + std::tie(_upconvertedCommandArgs, _upconvertedMetadata) = uassertStatusOK( + metadata::upconvertRequest(std::move(_queryMessage.query), + std::move(_queryMessage.queryOptions)) + ); + } + + LegacyRequest::~LegacyRequest() = default; + + StringData LegacyRequest::getDatabase() const { + return _database; + } + + StringData LegacyRequest::getCommandName() const { + return _upconvertedCommandArgs.firstElement().fieldNameStringData(); + } + + const BSONObj& LegacyRequest::getMetadata() const { + // TODO SERVER-18236 + return _upconvertedMetadata; + } + + const BSONObj& LegacyRequest::getCommandArgs() const { + return _upconvertedCommandArgs; + } + + DocumentRange LegacyRequest::getInputDocs() const { + // return an empty document range. + return DocumentRange{}; + } + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_request.h b/src/mongo/rpc/legacy_request.h new file mode 100644 index 00000000000..93567a6fe6c --- /dev/null +++ b/src/mongo/rpc/legacy_request.h @@ -0,0 +1,103 @@ +/** + * 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 "mongo/base/string_data.h" +#include "mongo/db/dbmessage.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/document_range.h" +#include "mongo/rpc/request_interface.h" + +namespace mongo { + class Message; + +namespace rpc { + + /** + * An immutable view of an OP_QUERY command request. The underlying bytes are owned + * by a mongo::Message, which must outlive any LegacyRequest instances created from it. + * + */ + class LegacyRequest : public RequestInterface { + public: + /** + * Construct a Request from a Message. Underlying message MUST outlive the Request. + * Required fields are parsed eagerly, inputDocs are parsed lazily. + */ + explicit LegacyRequest(const Message* message); + + ~LegacyRequest() final; + + /** + * The database that the command is to be executed on. + */ + StringData getDatabase() const final; + + /** + * The name of the command to execute. + */ + StringData getCommandName() const final; + + /** + * The metadata associated with the command request. This is information that is + * independent of any specific command, i.e. auditing information. + */ + const BSONObj& getMetadata() const final; + + /** + * The arguments to the command - this is passed to the command's run() method. + */ + const BSONObj& getCommandArgs() const final; + + /** + * A variable number of BSON documents to pass to the command. It is valid for + * the returned range to be empty. + * + * Example usage: + * + * for (auto&& doc : req.getInputDocs()) { + * ... do stuff with doc + * } + */ + DocumentRange getInputDocs() const final; + + private: + const Message* _message; + // TODO: metadata will be handled in SERVER-18236 + // for now getMetadata() is a no op + DbMessage _dbMessage; + QueryMessage _queryMessage; + std::string _database; + + BSONObj _upconvertedMetadata; + BSONObj _upconvertedCommandArgs; + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_request_builder.cpp b/src/mongo/rpc/legacy_request_builder.cpp new file mode 100644 index 00000000000..f1978583df8 --- /dev/null +++ b/src/mongo/rpc/legacy_request_builder.cpp @@ -0,0 +1,121 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/legacy_request_builder.h" + +#include <utility> +#include <tuple> + +#include "mongo/db/namespace_string.h" +#include "mongo/rpc/metadata.h" +#include "mongo/stdx/memory.h" +#include "mongo/util/assert_util.h" + +namespace mongo { +namespace rpc { + + LegacyRequestBuilder::LegacyRequestBuilder() + : _message{stdx::make_unique<Message>()} + {} + + LegacyRequestBuilder::~LegacyRequestBuilder() + {} + + LegacyRequestBuilder::LegacyRequestBuilder(std::unique_ptr<Message> message) + : _message{std::move(message)} + {} + + LegacyRequestBuilder& LegacyRequestBuilder::setDatabase(StringData database) { + invariant(_state == State::kDatabase); + _ns = NamespaceString(database, "$cmd").toString(); + _state = State::kCommandName; + return *this; + } + + LegacyRequestBuilder& LegacyRequestBuilder::setCommandName(StringData commandName) { + invariant(_state == State::kCommandName); + // no op, as commandName is the first element of commandArgs + _state = State::kMetadata; + return *this; + } + + LegacyRequestBuilder& LegacyRequestBuilder::setMetadata(BSONObj metadata) { + invariant(_state == State::kMetadata); + _metadata = std::move(metadata); + _state = State::kCommandArgs; + return *this; + } + + LegacyRequestBuilder& LegacyRequestBuilder::setCommandArgs(BSONObj commandArgs) { + invariant(_state == State::kCommandArgs); + + BSONObj legacyCommandArgs; + int queryOptions; + + std::tie(legacyCommandArgs, queryOptions) = uassertStatusOK( + metadata::downconvertRequest(std::move(commandArgs), + std::move(_metadata)) + ); + + _builder.appendNum(queryOptions); // queryOptions + _builder.appendStr(_ns); + _builder.appendNum(0); // nToSkip + _builder.appendNum(1); // nToReturn + + legacyCommandArgs.appendSelfToBufBuilder(_builder); + _state = State::kInputDocs; + return *this; + } + + LegacyRequestBuilder& LegacyRequestBuilder::addInputDocs(DocumentRange inputDocs) { + invariant(_state == State::kInputDocs); + // no op + return *this; + } + + LegacyRequestBuilder& LegacyRequestBuilder::addInputDoc(BSONObj inputDoc) { + invariant(_state == State::kInputDocs); + // no op + return *this; + } + + RequestBuilderInterface::State LegacyRequestBuilder::getState() const { + return _state; + } + + std::unique_ptr<Message> LegacyRequestBuilder::done() { + invariant(_state == State::kInputDocs); + _message->setData(dbQuery, _builder.buf(), _builder.len()); + _state = State::kDone; + return std::move(_message); + } + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/legacy_request_builder.h b/src/mongo/rpc/legacy_request_builder.h new file mode 100644 index 00000000000..0beed3b9a71 --- /dev/null +++ b/src/mongo/rpc/legacy_request_builder.h @@ -0,0 +1,75 @@ +/** + * 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/string_data.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/document_range.h" +#include "mongo/rpc/request_builder_interface.h" +#include "mongo/util/net/message.h" + +namespace mongo { +namespace rpc { + + class LegacyRequestBuilder : public RequestBuilderInterface { + public: + LegacyRequestBuilder(); + ~LegacyRequestBuilder() final; + + LegacyRequestBuilder(std::unique_ptr<Message>); + + LegacyRequestBuilder& setDatabase(StringData database) final; + LegacyRequestBuilder& setCommandName(StringData commandName) final; + LegacyRequestBuilder& setMetadata(BSONObj metadata) final; + LegacyRequestBuilder& setCommandArgs(BSONObj commandArgs) final; + + LegacyRequestBuilder& addInputDocs(DocumentRange inputDocs) final; + LegacyRequestBuilder& addInputDoc(BSONObj inputDoc) final; + + State getState() const final; + + std::unique_ptr<Message> done() final; + + private: + std::unique_ptr<Message> _message; + BufBuilder _builder{}; + + // we need to stash this as we need commandArgs to + // upconvert. + BSONObj _metadata; + + std::string _ns{}; // copied to in setDatabase + + State _state{State::kDatabase}; + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp new file mode 100644 index 00000000000..055dfd07984 --- /dev/null +++ b/src/mongo/rpc/metadata.cpp @@ -0,0 +1,69 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/metadata.h" + +#include "mongo/client/dbclientinterface.h" +#include "mongo/db/jsobj.h" + +namespace mongo { +namespace rpc { +namespace metadata { + + BSONObj empty() { + return BSONObj(); + } + + const char kSecondaryOk[] = "$secondaryOk"; + + StatusWith<CommandAndMetadata> upconvertRequest(BSONObj legacyCmdObj, int queryFlags) { + BSONObjBuilder metadataBob; + + // note second check may be erroneous: see SERVER-18194 + if ((queryFlags & QueryOption_SlaveOk)) { + metadataBob.append(kSecondaryOk, 1); + } + + return std::make_tuple(std::move(legacyCmdObj), std::move(metadataBob.obj())); + } + + StatusWith<LegacyCommandAndFlags> downconvertRequest(BSONObj cmdObj, BSONObj metadata) { + int flags = 0; + + if (metadata.hasField(kSecondaryOk)) { + flags |= QueryOption_SlaveOk; + } + + return std::make_tuple(std::move(cmdObj), flags); + } + +} // namespace metadata +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/metadata.h b/src/mongo/rpc/metadata.h new file mode 100644 index 00000000000..183dedffc5d --- /dev/null +++ b/src/mongo/rpc/metadata.h @@ -0,0 +1,93 @@ +/** + * 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 <tuple> + +#include "mongo/base/status_with.h" + +namespace mongo { + class BSONObj; + +/** + * Utilities for converting metadata between the legacy OP_QUERY format and the new + * OP_COMMAND format. + * + * Metadata consists of information independent of any particular command such as: + * + * Request/Reply/Both | (legacy) OP_QUERY format | OP_COMMAND format + *__________________________________________________________________________________________________ + * Request | the slaveOk bit | $secondaryOk on metadata obj + * Request | $readPreference field of command | $readPreference on metadata obj + * Request | $impersonatedUsers on command obj| $impersonatedUsers on metadata obj + * Request | $impersonatedRoles on command obj| $impersonatedRoles on metadata obj + * Request | maxTimeMS on command obj | $maxTimeMS on metadata obj + * Reply | $gleStats field on command reply | $gleStats on metadata obj + * + * TODO: currently only $secondaryOk (request only) is handled. SERVER-18236 will cover the rest. + */ +namespace rpc { +namespace metadata { + + /** + * Returns an empty metadata object. + */ + BSONObj empty(); + + /** + * The field name for the secondaryOk metadata field. + */ + extern const char kSecondaryOk[]; + + /** + * A command object and a corresponding metadata object. + */ + using CommandAndMetadata = std::tuple<BSONObj, BSONObj>; + + /** + * A legacy command object and a corresponding query flags bitfield. The legacy command object + * may contain metadata fields, so it cannot safely be passed to a command's run method. + */ + using LegacyCommandAndFlags = std::tuple<BSONObj, int>; + + /** + * Given a legacy command object and a query flags bitfield, attempts to parse and remove + * the metadata from the command object and construct a corresponding metadata object. + */ + StatusWith<CommandAndMetadata> upconvertRequest(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. + */ + StatusWith<LegacyCommandAndFlags> downconvertRequest(BSONObj cmdObj, BSONObj metadata); + +} // namespace metadata +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/protocol.cpp b/src/mongo/rpc/protocol.cpp new file mode 100644 index 00000000000..dae77abce3b --- /dev/null +++ b/src/mongo/rpc/protocol.cpp @@ -0,0 +1,117 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/protocol.h" + +#include <algorithm> +#include <iterator> + +#include "mongo/base/string_data.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { +namespace rpc { + +namespace { + + /** + * Protocols supported by order of preference. + */ + const Protocol kPreferredProtos[] = { + Protocol::kOpCommandV1, + Protocol::kOpQuery + }; + + const char kNone[] = "none"; + const char kOpQueryOnly[] = "opQueryOnly"; + const char kOpCommandOnly[] = "opCommandOnly"; + const char kAll[] = "all"; + +} // namespace + + StatusWith<Protocol> negotiate(ProtocolSet fst, ProtocolSet snd) { + using std::begin; + using std::end; + + ProtocolSet common = fst & snd; + + auto it = + std::find_if(begin(kPreferredProtos), end(kPreferredProtos), [common](Protocol p) { + return common & static_cast<ProtocolSet>(p); + }); + + if (it == end(kPreferredProtos)) { + return Status(ErrorCodes::RPCProtocolNegotiationFailed, + "No common protocol found."); + } + return *it; + } + + StatusWith<StringData> toString(ProtocolSet protocols) { + switch (protocols) { + case supports::kNone: + return StringData(kNone); + case supports::kOpQueryOnly: + return StringData(kOpQueryOnly); + case supports::kOpCommandOnly: + return StringData(kOpCommandOnly); + case supports::kAll: + return StringData(kAll); + default: + return Status(ErrorCodes::BadValue, + str::stream() << "Can not convert ProtocolSet " << protocols + << " to a string, only the predefined ProtocolSet " + << "constants 'none' (0x0), 'opQueryOnly' (0x1), " + << "'opCommandOnly' (0x2), and 'all' (0x3) are supported."); + } + } + + StatusWith<ProtocolSet> parseProtocolSet(StringData repr) { + if (repr == kNone) { + return supports::kNone; + } + else if (repr == kOpQueryOnly) { + return supports::kOpQueryOnly; + } + else if (repr == kOpCommandOnly) { + return supports::kOpCommandOnly; + } + else if (repr == kAll) { + return supports::kAll; + } + return Status(ErrorCodes::BadValue, + str::stream() << "Can not parse a ProtocolSet from " << repr + << " only the predefined ProtocolSet constants " + << "'none' (0x0), 'opQueryOnly' (0x1), 'opCommandOnly' (0x2), " + << "and 'all' (0x3) are supported."); + } + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/protocol.h b/src/mongo/rpc/protocol.h new file mode 100644 index 00000000000..ba370a90e69 --- /dev/null +++ b/src/mongo/rpc/protocol.h @@ -0,0 +1,99 @@ +/** + * 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 <string> +#include <type_traits> + +#include "mongo/base/status_with.h" +#include "mongo/platform/cstdint.h" + +namespace mongo { +namespace rpc { + + /** + * Bit flags representing support for a particular RPC protocol. + * This is just an internal representation, and is never transmitted over the wire. It should + * never be used for any other feature detection in favor of max/min wire version. + * + * A new protocol must be added as the highest order bit flag so that it is prioritized in + * negotiation. + */ + enum class Protocol : std::uint64_t { + + /** + * The pre-3.2 OP_QUERY on db.$cmd protocol + */ + kOpQuery = 1 << 0, + + /** + * The post-3.2 OP_COMMAND protocol. + */ + kOpCommandV1 = 1 << 1, + }; + + /** + * Bitfield representing a set of supported RPC protocols. + */ + using ProtocolSet = std::underlying_type<Protocol>::type; + +/** + * This namespace contains predefined bitfields for common levels of protocol support. + */ +namespace supports { + + const ProtocolSet kNone = ProtocolSet{0}; + const ProtocolSet kOpQueryOnly = static_cast<ProtocolSet>(Protocol::kOpQuery); + const ProtocolSet kOpCommandOnly = static_cast<ProtocolSet>(Protocol::kOpCommandV1); + const ProtocolSet kAll = kOpQueryOnly | kOpCommandOnly; + +} // namespace supports + + /** + * Returns the newest protocol supported by two parties. + */ + StatusWith<Protocol> negotiate(ProtocolSet fst, ProtocolSet snd); + + /** + * Converts a ProtocolSet to a string. Currently only the predefined ProtocolSets in the + * 'supports' namespace are supported. + * + * This intentionally does not conform to the STL 'to_string' convention so that it will + * not conflict with the to_string overload for uint64_t. + */ + StatusWith<StringData> toString(ProtocolSet protocols); + + /** + * Parses a ProtocolSet from a string. Currently only the predefined ProtocolSets in the + * 'supports' namespace are supported + */ + StatusWith<ProtocolSet> parseProtocolSet(StringData repr); + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/protocol_test.cpp b/src/mongo/rpc/protocol_test.cpp new file mode 100644 index 00000000000..86211717eb7 --- /dev/null +++ b/src/mongo/rpc/protocol_test.cpp @@ -0,0 +1,66 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/base/status.h" +#include "mongo/rpc/protocol.h" +#include "mongo/unittest/unittest.h" + +namespace { + + using namespace mongo::rpc; + + // Checks if negotiation of the first to protocol sets results in the 'proto' + const auto assert_negotiated = [](ProtocolSet fst, ProtocolSet snd, Protocol proto) { + auto negotiated = negotiate(fst, snd); + ASSERT_TRUE(negotiated.isOK()); + ASSERT_TRUE(negotiated.getValue() == proto); + }; + + TEST(Protocol, SuccessfulNegotiation) { + assert_negotiated(supports::kAll, supports::kAll, Protocol::kOpCommandV1); + assert_negotiated(supports::kAll, supports::kOpCommandOnly, Protocol::kOpCommandV1); + assert_negotiated(supports::kAll, supports::kOpQueryOnly, Protocol::kOpQuery); + } + + // Checks that negotiation fails + const auto assert_not_negotiated = [](ProtocolSet fst, ProtocolSet snd) { + auto proto = negotiate(fst, snd); + ASSERT_TRUE(!proto.isOK()); + ASSERT_TRUE(proto.getStatus().code() == mongo::ErrorCodes::RPCProtocolNegotiationFailed); + }; + + TEST(Protocol, FailedNegotiation) { + assert_not_negotiated(supports::kOpQueryOnly, supports::kOpCommandOnly); + assert_not_negotiated(supports::kAll, supports::kNone); + assert_not_negotiated(supports::kOpQueryOnly, supports::kNone); + assert_not_negotiated(supports::kOpCommandOnly, supports::kNone); + } + +} // namespace diff --git a/src/mongo/rpc/reply_builder_interface.cpp b/src/mongo/rpc/reply_builder_interface.cpp new file mode 100644 index 00000000000..1b4b5aadbda --- /dev/null +++ b/src/mongo/rpc/reply_builder_interface.cpp @@ -0,0 +1,85 @@ +/** + * 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/reply_builder_interface.h" + +#include <utility> + +#include "mongo/base/status_with.h" +#include "mongo/db/jsobj.h" + +namespace mongo { +namespace rpc { + + namespace { + const char kOKField[] = "ok"; + const char kCodeField[] = "code"; + const char kErrorField[] = "errmsg"; + + // similar to appendCommandStatus (duplicating logic here to avoid cyclic library + // dependency) + BSONObj augmentReplyWithStatus(const Status& status, const BSONObj& reply) { + BSONObjBuilder bob; + bob.appendElements(reply); + + if (!reply.hasField(kOKField)) { + bob.append(kOKField, status.isOK() ? 1.0 : 0.0); + } + + if (status.isOK()) { + return bob.obj(); + } + + if (!reply.hasField(kErrorField)) { + bob.append(kErrorField, status.reason()); + } + + if (!reply.hasField(kCodeField)) { + bob.append(kCodeField, status.code()); + } + + return bob.obj(); + } + } + + ReplyBuilderInterface& + ReplyBuilderInterface::setCommandReply(StatusWith<BSONObj> commandReply) { + auto reply = commandReply.isOK() ? std::move(commandReply.getValue()) : BSONObj(); + return setRawCommandReply(augmentReplyWithStatus(commandReply.getStatus(), reply)); + } + + ReplyBuilderInterface& + ReplyBuilderInterface::setCommandReply(Status nonOKStatus, BSONObj extraErrorInfo) { + invariant(!nonOKStatus.isOK()); + return setRawCommandReply(augmentReplyWithStatus(nonOKStatus, extraErrorInfo)); + } + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/reply_builder_interface.h b/src/mongo/rpc/reply_builder_interface.h new file mode 100644 index 00000000000..00942fe1dac --- /dev/null +++ b/src/mongo/rpc/reply_builder_interface.h @@ -0,0 +1,125 @@ +/** + * 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/base/status_with.h" + +namespace mongo { + class BSONObj; + class Message; + +namespace rpc { + class DocumentRange; + + /** + * Constructs an RPC Reply. + */ + class ReplyBuilderInterface { + MONGO_DISALLOW_COPYING(ReplyBuilderInterface); + public: + + /** + * Reply 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 { + kMetadata, + kCommandReply, + kOutputDocs, + kDone + }; + + virtual ~ReplyBuilderInterface() = default; + + virtual ReplyBuilderInterface& setMetadata(BSONObj metadata) = 0; + + /** + * Sets the raw command reply. This should probably not be used in favor of the + * variants that accept a Status or StatusWith. + */ + virtual ReplyBuilderInterface& setRawCommandReply(BSONObj reply) = 0; + + /** + * Sets the reply for this command. If an engaged StatusWith<BSONObj> is passed, the command + * reply will be set to the contained BSONObj, augmented with the element {ok, 1.0} if it + * does not already have an "ok" field. If a disengaged StatusWith<BSONObj> is passed, the + * command reply will be set to {ok: 0.0, code: <code of status>, + * errmsg: <reason of status>} + */ + ReplyBuilderInterface& setCommandReply(StatusWith<BSONObj> commandReply); + + /** + * Sets the reply for this command. The status parameter must be non-OK. The reply for + * this command will be set to an object containing all the fields in extraErrorInfo, + * augmented with {ok: 0.0} , {code: <code of status>}, and {errmsg: <reason of status>}. + * If any of the fields "ok", "code", or "errmsg" already exist in extraErrorInfo, they + * will be left as-is in the command reply. This use of this form is intended for + * interfacing with legacy code that adds additional data to a failed command reply and + * its use is discouraged in new code. + */ + ReplyBuilderInterface& setCommandReply(Status nonOKStatus, BSONObj extraErrorInfo); + + /** + * Add a range of output documents to the reply. This method can be called multiple times + * before calling done(). + */ + virtual ReplyBuilderInterface& addOutputDocs(DocumentRange outputDocs) = 0; + + /** + * Add a single output document to the reply. This method can be called multiple times + * before calling done(). + */ + virtual ReplyBuilderInterface& addOutputDoc(BSONObj outputDoc) = 0; + + /** + * Gets the state of the builder. As the builder will simply crash the process if it is ever + * put in an invalid state, it isn't neccessary to call this method for correctness. Rather + * it may be helpful to explicitly assert that the builder is in a certain state to make + * code that manipulates the builder more readable. + */ + virtual State getState() const = 0; + + /** + * Writes data then transfers ownership of the message to the caller. The behavior of + * calling any methods on the builder is subsequently undefined. + */ + virtual std::unique_ptr<Message> done() = 0; + + protected: + ReplyBuilderInterface() = default; + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/reply_interface.h b/src/mongo/rpc/reply_interface.h new file mode 100644 index 00000000000..3fd5644f785 --- /dev/null +++ b/src/mongo/rpc/reply_interface.h @@ -0,0 +1,76 @@ +/** + * 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 "mongo/base/disallow_copying.h" + +namespace mongo { + class BSONObj; + class Message; + +namespace rpc { + class DocumentRange; + + /** + * An immutable view of an RPC Reply. + */ + class ReplyInterface { + MONGO_DISALLOW_COPYING(ReplyInterface); + public: + virtual ~ReplyInterface() = default; + + /** + * Accessor for the metadata object. Metadata is generally used for information + * that is independent of any specific command, e.g. auditing information. + */ + virtual const BSONObj& getMetadata() const = 0; + + /** + * The result of executing the command. + */ + virtual const BSONObj& getCommandReply() const = 0; + + /** + * A variable number of BSON documents returned by the command. It is valid for the + * returned range to be empty. + * + * Example usage: + * + * for (auto&& doc : reply.getOutputDocs()) { + * ... do stuff with doc + * } + */ + virtual DocumentRange getOutputDocs() const = 0; + + protected: + ReplyInterface() = default; + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/request_builder_interface.h b/src/mongo/rpc/request_builder_interface.h new file mode 100644 index 00000000000..39623e12a10 --- /dev/null +++ b/src/mongo/rpc/request_builder_interface.h @@ -0,0 +1,118 @@ +/** + * 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" + +namespace mongo { + class Message; + class BSONObj; + class StringData; + +namespace rpc { + class DocumentRange; + + /** + * 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; + + /** + * Add a range of input documents to the request. This method can be called multiple times + * before calling done(). + */ + virtual RequestBuilderInterface& addInputDocs(DocumentRange inputDocs) = 0; + + /** + * Add a single output document to the request. This method can be called multiple times + * before calling done(). + */ + virtual RequestBuilderInterface& addInputDoc(BSONObj inputDoc) = 0; + + /** + * Get the state of the builder. This method is intended to enable debug or invariant + * checks that the builder is in the correct state. + */ + virtual State getState() 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 std::unique_ptr<Message> done() = 0; + + protected: + RequestBuilderInterface() = default; + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/rpc/request_interface.h b/src/mongo/rpc/request_interface.h new file mode 100644 index 00000000000..27f407f3f5c --- /dev/null +++ b/src/mongo/rpc/request_interface.h @@ -0,0 +1,88 @@ +/** + * 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 "mongo/base/disallow_copying.h" + +namespace mongo { + class BSONObj; + class Message; + class StringData; + +namespace rpc { + class DocumentRange; + + /** + * An immutable view of an RPC message. + */ + class RequestInterface { + MONGO_DISALLOW_COPYING(RequestInterface); + public: + virtual ~RequestInterface() = default; + + /** + * Gets the database that the command is to be executed on. + */ + virtual StringData getDatabase() const = 0; + + /** + * Gets the name of the command to execute. + */ + virtual StringData getCommandName() const = 0; + + /** + * Gets the metadata associated with the command request. This is information that is + * independent of any specific command, i.e. auditing information. See metadata.h for + * further details. + */ + virtual const BSONObj& getMetadata() const = 0; + + /** + * Gets the arguments to the command - this is passed to the command's run() method. + */ + virtual const BSONObj& getCommandArgs() const = 0; + + /** + * A variable number of BSON documents to pass to the command. It is valid for + * the returned range to be empty. + * + * Example usage: + * + * for (auto&& doc : req.getInputDocs()) { + * ... do stuff with doc + * } + */ + virtual DocumentRange getInputDocs() const = 0; + + protected: + RequestInterface() = default; + }; + +} // namespace rpc +} // namespace mongo diff --git a/src/mongo/util/net/message.h b/src/mongo/util/net/message.h index c1627550724..b3573026f84 100644 --- a/src/mongo/util/net/message.h +++ b/src/mongo/util/net/message.h @@ -82,6 +82,8 @@ namespace mongo { case dbGetMore: return "getmore"; case dbDelete: return "remove"; case dbKillCursors: return "killcursors"; + case dbCommand: return "command"; + case dbCommandReply: return "commandReply"; default: massert( 16141, str::stream() << "cannot translate opcode " << op, !op ); return ""; |