diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2015-06-20 00:22:50 -0400 |
---|---|---|
committer | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2015-06-20 10:56:02 -0400 |
commit | 9c2ed42daa8fbbef4a919c21ec564e2db55e8d60 (patch) | |
tree | 3814f79c10d7b490948d8cb7b112ac1dd41ceff1 /src/mongo/rpc | |
parent | 01965cf52bce6976637ecb8f4a622aeb05ab256a (diff) | |
download | mongo-9c2ed42daa8fbbef4a919c21ec564e2db55e8d60.tar.gz |
SERVER-18579: Clang-Format - reformat code, no comment reflow
Diffstat (limited to 'src/mongo/rpc')
49 files changed, 2728 insertions, 2883 deletions
diff --git a/src/mongo/rpc/command_reply.cpp b/src/mongo/rpc/command_reply.cpp index dc8e22d8b89..954d6b051d6 100644 --- a/src/mongo/rpc/command_reply.cpp +++ b/src/mongo/rpc/command_reply.cpp @@ -41,48 +41,46 @@ namespace mongo { namespace rpc { - CommandReply::CommandReply(const Message* message) - : _message(message) { - - const char* begin = _message->singleData().data(); - std::size_t length = _message->singleData().dataLen(); - - // 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); - - const char* messageEnd = begin + length; - ConstDataRangeCursor cur(begin, messageEnd); - - _metadata = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); - _commandReply = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); - _outputDocs = DocumentRange(cur.data(), messageEnd); - } - - const BSONObj& CommandReply::getMetadata() const { - return _metadata; - } - - const BSONObj& CommandReply::getCommandReply() const { - return _commandReply; - } - - DocumentRange CommandReply::getOutputDocs() const { - return _outputDocs; - } - - Protocol CommandReply::getProtocol() const { - return rpc::Protocol::kOpCommandV1; - } - - 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 CommandReply& lhs, const CommandReply& rhs) { - return !(lhs == rhs); - } +CommandReply::CommandReply(const Message* message) : _message(message) { + const char* begin = _message->singleData().data(); + std::size_t length = _message->singleData().dataLen(); + + // 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); + + const char* messageEnd = begin + length; + ConstDataRangeCursor cur(begin, messageEnd); + + _metadata = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); + _commandReply = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); + _outputDocs = DocumentRange(cur.data(), messageEnd); +} + +const BSONObj& CommandReply::getMetadata() const { + return _metadata; +} + +const BSONObj& CommandReply::getCommandReply() const { + return _commandReply; +} + +DocumentRange CommandReply::getOutputDocs() const { + return _outputDocs; +} + +Protocol CommandReply::getProtocol() const { + return rpc::Protocol::kOpCommandV1; +} + +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 CommandReply& lhs, const CommandReply& rhs) { + return !(lhs == rhs); +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/command_reply.h b/src/mongo/rpc/command_reply.h index 73a3fe60ccf..1e092d6db5d 100644 --- a/src/mongo/rpc/command_reply.h +++ b/src/mongo/rpc/command_reply.h @@ -33,65 +33,65 @@ #include "mongo/rpc/reply_interface.h" namespace mongo { - class Message; +class Message; namespace rpc { +/** + * An immutable view of an OP_COMMANDREPLY message. The underlying bytes are owned + * by a mongo::Message, which must outlive any Reply instances created from it. + * + * TODO: _metadata and _commandReply are owned by the underlying message. When + * we implement a BSONObjView or similar, we should use that here to make these semantics + * explicit. + * See SERVER-16730 for additional details. + */ +class CommandReply : public ReplyInterface { +public: /** - * An immutable view of an OP_COMMANDREPLY message. The underlying bytes are owned - * by a mongo::Message, which must outlive any Reply instances created from it. + * Construct a Reply from a Message. + * The underlying message MUST outlive the Reply. + * Required fields are parsed eagerly, outputDocs are parsed lazily. * - * TODO: _metadata and _commandReply are owned by the underlying message. When - * we implement a BSONObjView or similar, we should use that here to make these semantics - * explicit. - * See SERVER-16730 for additional details. + * The underlying Message also handles the wire-protocol header. */ - class CommandReply : public ReplyInterface { - public: - /** - * Construct a Reply from a Message. - * The underlying message MUST outlive the Reply. - * Required fields are parsed eagerly, outputDocs are parsed lazily. - * - * The underlying Message also handles the wire-protocol header. - */ - explicit CommandReply(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 final; + /** + * 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; + /** + * 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; + /** + * 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; - Protocol getProtocol() const final; + Protocol getProtocol() const final; - friend bool operator==(const CommandReply& lhs, const CommandReply& rhs); - friend bool operator!=(const CommandReply& lhs, const CommandReply& rhs); + friend bool operator==(const CommandReply& lhs, const CommandReply& rhs); + friend bool operator!=(const CommandReply& lhs, const CommandReply& rhs); - private: - const Message* _message; +private: + const Message* _message; - BSONObj _metadata; - BSONObj _commandReply; - DocumentRange _outputDocs; - }; + BSONObj _metadata; + BSONObj _commandReply; + DocumentRange _outputDocs; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/command_reply_builder.cpp b/src/mongo/rpc/command_reply_builder.cpp index 0fe84ecf424..d21358e053a 100644 --- a/src/mongo/rpc/command_reply_builder.cpp +++ b/src/mongo/rpc/command_reply_builder.cpp @@ -37,79 +37,76 @@ namespace mongo { namespace rpc { - CommandReplyBuilder::CommandReplyBuilder() - : _message{stdx::make_unique<Message>()} - {} - - CommandReplyBuilder::CommandReplyBuilder(std::unique_ptr<Message> message) - : _message{std::move(message)} - {} - - CommandReplyBuilder& CommandReplyBuilder::setMetadata(BSONObj metadata) { - invariant(_state == State::kMetadata); - metadata.appendSelfToBufBuilder(_builder); - _state = State::kCommandReply; - return *this; - } - - CommandReplyBuilder& CommandReplyBuilder::setRawCommandReply(BSONObj commandReply) { - invariant(_state == State::kCommandReply); - commandReply.appendSelfToBufBuilder(_builder); - _state = State::kOutputDocs; - return *this; - } - - 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; - } - - CommandReplyBuilder& CommandReplyBuilder::addOutputDoc(BSONObj outputDoc) { - invariant(_state == State::kOutputDocs); - outputDoc.appendSelfToBufBuilder(_builder); - return *this; - } - - ReplyBuilderInterface::State CommandReplyBuilder::getState() const { - return _state; - } - - Protocol CommandReplyBuilder::getProtocol() const { - return rpc::Protocol::kOpCommandV1; - } - - void CommandReplyBuilder::reset() { - // If we are in State::kMetadata, we are already in the 'start' state, so by - // immediately returning, we save a heap allocation. - if (_state == State::kMetadata) { - return; - } - _builder.reset(); - _message = stdx::make_unique<Message>(); - _state = State::kMetadata; - } - - 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()); - _state = State::kDone; - return std::move(_message); - } - - std::size_t CommandReplyBuilder::availableSpaceForOutputDocs() const { - invariant (State::kDone != _state); - int intLen = _builder.len(); - invariant(0 <= intLen); - std::size_t len = static_cast<std::size_t>(intLen); - std::size_t msgHeaderSz = static_cast<std::size_t>(MsgData::MsgDataHeaderSize); - invariant(len + msgHeaderSz <= mongo::MaxMessageSizeBytes); - return mongo::MaxMessageSizeBytes - len - msgHeaderSz; +CommandReplyBuilder::CommandReplyBuilder() : _message{stdx::make_unique<Message>()} {} + +CommandReplyBuilder::CommandReplyBuilder(std::unique_ptr<Message> message) + : _message{std::move(message)} {} + +CommandReplyBuilder& CommandReplyBuilder::setMetadata(BSONObj metadata) { + invariant(_state == State::kMetadata); + metadata.appendSelfToBufBuilder(_builder); + _state = State::kCommandReply; + return *this; +} + +CommandReplyBuilder& CommandReplyBuilder::setRawCommandReply(BSONObj commandReply) { + invariant(_state == State::kCommandReply); + commandReply.appendSelfToBufBuilder(_builder); + _state = State::kOutputDocs; + return *this; +} + +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; +} + +CommandReplyBuilder& CommandReplyBuilder::addOutputDoc(BSONObj outputDoc) { + invariant(_state == State::kOutputDocs); + outputDoc.appendSelfToBufBuilder(_builder); + return *this; +} + +ReplyBuilderInterface::State CommandReplyBuilder::getState() const { + return _state; +} + +Protocol CommandReplyBuilder::getProtocol() const { + return rpc::Protocol::kOpCommandV1; +} + +void CommandReplyBuilder::reset() { + // If we are in State::kMetadata, we are already in the 'start' state, so by + // immediately returning, we save a heap allocation. + if (_state == State::kMetadata) { + return; } + _builder.reset(); + _message = stdx::make_unique<Message>(); + _state = State::kMetadata; +} + +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()); + _state = State::kDone; + return std::move(_message); +} + +std::size_t CommandReplyBuilder::availableSpaceForOutputDocs() const { + invariant(State::kDone != _state); + int intLen = _builder.len(); + invariant(0 <= intLen); + std::size_t len = static_cast<std::size_t>(intLen); + std::size_t msgHeaderSz = static_cast<std::size_t>(MsgData::MsgDataHeaderSize); + invariant(len + msgHeaderSz <= mongo::MaxMessageSizeBytes); + return mongo::MaxMessageSizeBytes - len - msgHeaderSz; +} } // rpc } // mongo diff --git a/src/mongo/rpc/command_reply_builder.h b/src/mongo/rpc/command_reply_builder.h index 55292eb6da4..2433a283e04 100644 --- a/src/mongo/rpc/command_reply_builder.h +++ b/src/mongo/rpc/command_reply_builder.h @@ -40,51 +40,50 @@ namespace mongo { namespace rpc { +/** + * Constructs an OP_COMMANDREPLY message. + */ +class CommandReplyBuilder : public ReplyBuilderInterface { +public: /** - * Constructs an OP_COMMANDREPLY message. + * Constructs an OP_COMMANDREPLY in a new buffer. */ - class CommandReplyBuilder : public ReplyBuilderInterface { - public: - - /** - * Constructs an OP_COMMANDREPLY in a new buffer. - */ - CommandReplyBuilder(); + CommandReplyBuilder(); - /* - * Constructs an OP_COMMANDREPLY in an existing buffer. Ownership of the buffer - * will be transfered to the CommandReplyBuilder. - */ - CommandReplyBuilder(std::unique_ptr<Message> message); + /* + * Constructs an OP_COMMANDREPLY in an existing buffer. Ownership of the buffer + * will be transfered to the CommandReplyBuilder. + */ + CommandReplyBuilder(std::unique_ptr<Message> message); - CommandReplyBuilder& setMetadata(BSONObj metadata) final; - CommandReplyBuilder& setRawCommandReply(BSONObj commandReply) final; + CommandReplyBuilder& setMetadata(BSONObj metadata) final; + CommandReplyBuilder& setRawCommandReply(BSONObj commandReply) final; - CommandReplyBuilder& addOutputDocs(DocumentRange outputDocs) final; - CommandReplyBuilder& addOutputDoc(BSONObj outputDoc) final; + CommandReplyBuilder& addOutputDocs(DocumentRange outputDocs) final; + CommandReplyBuilder& addOutputDoc(BSONObj outputDoc) final; - State getState() const final; + State getState() const final; - Protocol getProtocol() const final; + Protocol getProtocol() const final; - void reset() final; + void reset() 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() 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() final; - std::size_t availableSpaceForOutputDocs() const final; + std::size_t availableSpaceForOutputDocs() const final; - private: - // Default values are all empty. - BufBuilder _builder{}; - std::unique_ptr<Message> _message; +private: + // Default values are all empty. + BufBuilder _builder{}; + std::unique_ptr<Message> _message; - State _state{State::kMetadata}; - }; + State _state{State::kMetadata}; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/command_reply_test.cpp b/src/mongo/rpc/command_reply_test.cpp index 48930bbe1b9..e00b8df5ec5 100644 --- a/src/mongo/rpc/command_reply_test.cpp +++ b/src/mongo/rpc/command_reply_test.cpp @@ -43,133 +43,126 @@ namespace { - using namespace mongo; - - using std::begin; - using std::end; - - class ReplyTest : public mongo::unittest::Test { - protected: - std::vector<char> _cmdData{}; - // using unique ptr so we can destroy and replace easily - // since message does not have operator= or swap defined... - std::unique_ptr<Message> _message{}; - - virtual void setUp() override { - _message = stdx::make_unique<Message>(); - } - - virtual void tearDown() override { - _cmdData.clear(); - } - - void writeObj(const BSONObj& obj) { - _cmdData.insert(end(_cmdData), obj.objdata(), - obj.objdata() + obj.objsize()); - } - - void writeObj(const BSONObj& obj, std::size_t length) { - _cmdData.insert(end(_cmdData), obj.objdata(), - obj.objdata() + length); - } - - const Message* buildMessage() { - _cmdData.shrink_to_fit(); - _message->setData(dbCommandReply, - _cmdData.data(), - _cmdData.size()); - return _message.get(); - } - - }; - - TEST_F(ReplyTest, ParseAllFields) { - - BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); - auto metadata = metadataBob.done(); - writeObj(metadata); - - BSONObjBuilder commandReplyBob{}; - commandReplyBob.append("baz", "garply"); - auto commandReply = commandReplyBob.done(); - writeObj(commandReply); - - BSONObjBuilder outputDoc1Bob{}; - outputDoc1Bob.append("meep", "boop").append("meow", "chirp"); - auto outputDoc1 = outputDoc1Bob.done(); - writeObj(outputDoc1); - - BSONObjBuilder outputDoc2Bob{}; - outputDoc1Bob.append("bleep", "bop").append("woof", "squeak"); - auto outputDoc2 = outputDoc2Bob.done(); - writeObj(outputDoc2); - - rpc::CommandReply opCmdReply{buildMessage()}; - - ASSERT_EQUALS(opCmdReply.getMetadata(), metadata); - ASSERT_EQUALS(opCmdReply.getCommandReply(), commandReply); - - auto outputDocRange = opCmdReply.getOutputDocs(); - auto outputDocRangeIter = outputDocRange.begin(); - - ASSERT_EQUALS(*outputDocRangeIter, outputDoc1); - // can't use assert equals since we don't have an op to print the iter. - ASSERT_FALSE(outputDocRangeIter == outputDocRange.end()); - ++outputDocRangeIter; - ASSERT_EQUALS(*outputDocRangeIter, outputDoc2); - ASSERT_FALSE(outputDocRangeIter == outputDocRange.end()); - ++outputDocRangeIter; - - ASSERT_TRUE(outputDocRangeIter == outputDocRange.end()); - } +using namespace mongo; + +using std::begin; +using std::end; - TEST_F(ReplyTest, EmptyMessageThrows) { - ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); +class ReplyTest : public mongo::unittest::Test { +protected: + std::vector<char> _cmdData{}; + // using unique ptr so we can destroy and replace easily + // since message does not have operator= or swap defined... + std::unique_ptr<Message> _message{}; + + virtual void setUp() override { + _message = stdx::make_unique<Message>(); } - TEST_F(ReplyTest, MetadataOnlyThrows) { - BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); - auto metadata = metadataBob.done(); - writeObj(metadata); + virtual void tearDown() override { + _cmdData.clear(); + } - ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); + void writeObj(const BSONObj& obj) { + _cmdData.insert(end(_cmdData), obj.objdata(), obj.objdata() + obj.objsize()); } - TEST_F(ReplyTest, MetadataInvalidLengthThrows) { - BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); - auto metadata = metadataBob.done(); - auto trueSize = metadata.objsize(); - // write a super long length field - DataView(const_cast<char*>(metadata.objdata())).write<LittleEndian<int32_t>>(100000); - writeObj(metadata, trueSize); - // write a valid commandReply - BSONObjBuilder commandReplyBob{}; - commandReplyBob.append("baz", "garply"); - auto commandReply = commandReplyBob.done(); - writeObj(commandReply); - - ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); + void writeObj(const BSONObj& obj, std::size_t length) { + _cmdData.insert(end(_cmdData), obj.objdata(), obj.objdata() + length); } - TEST_F(ReplyTest, InvalidLengthThrows) { - BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); - auto metadata = metadataBob.done(); - // write a valid metadata object - writeObj(metadata); - - BSONObjBuilder commandReplyBob{}; - commandReplyBob.append("baz", "garply"); - auto commandReply = commandReplyBob.done(); - auto trueSize = commandReply.objsize(); - // write a super long length field - DataView(const_cast<char*>(commandReply.objdata())).write<LittleEndian<int32_t>>(100000); - writeObj(commandReply, trueSize); - - ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); + const Message* buildMessage() { + _cmdData.shrink_to_fit(); + _message->setData(dbCommandReply, _cmdData.data(), _cmdData.size()); + return _message.get(); } +}; + +TEST_F(ReplyTest, ParseAllFields) { + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); + writeObj(metadata); + + BSONObjBuilder commandReplyBob{}; + commandReplyBob.append("baz", "garply"); + auto commandReply = commandReplyBob.done(); + writeObj(commandReply); + + BSONObjBuilder outputDoc1Bob{}; + outputDoc1Bob.append("meep", "boop").append("meow", "chirp"); + auto outputDoc1 = outputDoc1Bob.done(); + writeObj(outputDoc1); + + BSONObjBuilder outputDoc2Bob{}; + outputDoc1Bob.append("bleep", "bop").append("woof", "squeak"); + auto outputDoc2 = outputDoc2Bob.done(); + writeObj(outputDoc2); + + rpc::CommandReply opCmdReply{buildMessage()}; + + ASSERT_EQUALS(opCmdReply.getMetadata(), metadata); + ASSERT_EQUALS(opCmdReply.getCommandReply(), commandReply); + + auto outputDocRange = opCmdReply.getOutputDocs(); + auto outputDocRangeIter = outputDocRange.begin(); + + ASSERT_EQUALS(*outputDocRangeIter, outputDoc1); + // can't use assert equals since we don't have an op to print the iter. + ASSERT_FALSE(outputDocRangeIter == outputDocRange.end()); + ++outputDocRangeIter; + ASSERT_EQUALS(*outputDocRangeIter, outputDoc2); + ASSERT_FALSE(outputDocRangeIter == outputDocRange.end()); + ++outputDocRangeIter; + + ASSERT_TRUE(outputDocRangeIter == outputDocRange.end()); +} + +TEST_F(ReplyTest, EmptyMessageThrows) { + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); +} +TEST_F(ReplyTest, MetadataOnlyThrows) { + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); + writeObj(metadata); + + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); +} + +TEST_F(ReplyTest, MetadataInvalidLengthThrows) { + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); + auto trueSize = metadata.objsize(); + // write a super long length field + DataView(const_cast<char*>(metadata.objdata())).write<LittleEndian<int32_t>>(100000); + writeObj(metadata, trueSize); + // write a valid commandReply + BSONObjBuilder commandReplyBob{}; + commandReplyBob.append("baz", "garply"); + auto commandReply = commandReplyBob.done(); + writeObj(commandReply); + + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); +} + +TEST_F(ReplyTest, InvalidLengthThrows) { + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); + // write a valid metadata object + writeObj(metadata); + + BSONObjBuilder commandReplyBob{}; + commandReplyBob.append("baz", "garply"); + auto commandReply = commandReplyBob.done(); + auto trueSize = commandReply.objsize(); + // write a super long length field + DataView(const_cast<char*>(commandReply.objdata())).write<LittleEndian<int32_t>>(100000); + writeObj(commandReply, trueSize); + + ASSERT_THROWS(rpc::CommandReply{buildMessage()}, UserException); +} } diff --git a/src/mongo/rpc/command_request.cpp b/src/mongo/rpc/command_request.cpp index d5fd8fa4515..961c4ba7d93 100644 --- a/src/mongo/rpc/command_request.cpp +++ b/src/mongo/rpc/command_request.cpp @@ -47,93 +47,86 @@ namespace mongo { namespace rpc { - namespace { - // None of these include null byte - const std::size_t kMaxDatabaseLength = 63; - const std::size_t kMinDatabaseLength = 1; +namespace { +// None of these include null byte +const std::size_t kMaxDatabaseLength = 63; +const std::size_t kMinDatabaseLength = 1; - const std::size_t kMinCommandNameLength = 1; - const std::size_t kMaxCommandNameLength = 128; +const std::size_t kMinCommandNameLength = 1; +const std::size_t kMaxCommandNameLength = 128; - } // namespace +} // namespace - CommandRequest::CommandRequest(const Message* message) - : _message(message) { - char* begin = _message->singleData().data(); - std::size_t length = _message->singleData().dataLen(); +CommandRequest::CommandRequest(const Message* message) : _message(message) { + char* begin = _message->singleData().data(); + std::size_t length = _message->singleData().dataLen(); - // checked in message_port.cpp - invariant(length <= MaxMessageSizeBytes); + // checked in message_port.cpp + invariant(length <= MaxMessageSizeBytes); - const char* messageEnd = begin + length; + const char* messageEnd = begin + length; - ConstDataRangeCursor cur(begin, messageEnd); + ConstDataRangeCursor cur(begin, messageEnd); - _database = uassertStatusOK(cur.readAndAdvance<Terminated<'\0', StringData>>()); + _database = uassertStatusOK(cur.readAndAdvance<Terminated<'\0', StringData>>()); - uassert(28636, str::stream() << "Database parsed in OP_COMMAND message must be between" - << kMinDatabaseLength << " and " << kMaxDatabaseLength - << " bytes. Got: " << _database, - (_database.size() >= kMinDatabaseLength) && - (_database.size() <= kMaxDatabaseLength)); + uassert(28636, + str::stream() << "Database parsed in OP_COMMAND message must be between" + << kMinDatabaseLength << " and " << kMaxDatabaseLength + << " bytes. Got: " << _database, + (_database.size() >= kMinDatabaseLength) && (_database.size() <= kMaxDatabaseLength)); - uassert(ErrorCodes::InvalidNamespace, - str::stream() << "Invalid database name: '" << _database << "'", - NamespaceString::validDBName(_database)); + uassert(ErrorCodes::InvalidNamespace, + str::stream() << "Invalid database name: '" << _database << "'", + NamespaceString::validDBName(_database)); - _commandName = uassertStatusOK(cur.readAndAdvance<Terminated<'\0', StringData>>()); + _commandName = uassertStatusOK(cur.readAndAdvance<Terminated<'\0', StringData>>()); - uassert(28637, str::stream() << "Command name parsed in OP_COMMAND message must be between" - << kMinCommandNameLength << " and " << kMaxCommandNameLength - << " bytes. Got: " << _database, - (_commandName.size() >= kMinCommandNameLength) && + uassert(28637, + str::stream() << "Command name parsed in OP_COMMAND message must be between" + << kMinCommandNameLength << " and " << kMaxCommandNameLength + << " bytes. Got: " << _database, + (_commandName.size() >= kMinCommandNameLength) && (_commandName.size() <= kMaxCommandNameLength)); - _metadata = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); - _commandArgs = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); - _inputDocs = DocumentRange{cur.data(), messageEnd}; - } - - StringData CommandRequest::getDatabase() const { - return _database; - } - - StringData CommandRequest::getCommandName() const { - return _commandName; - } - - const BSONObj& CommandRequest::getMetadata() const { - return _metadata; - } - - const BSONObj& CommandRequest::getCommandArgs() const { - return _commandArgs; - } - - DocumentRange CommandRequest::getInputDocs() const { - return _inputDocs; - } - - bool operator==(const CommandRequest& lhs, const CommandRequest& rhs) { - return std::tie(lhs._database, - lhs._commandName, - lhs._metadata, - lhs._commandArgs, - lhs._inputDocs) == - std::tie(rhs._database, - rhs._commandName, - rhs._metadata, - rhs._commandArgs, - rhs._inputDocs); - } - - bool operator!=(const CommandRequest& lhs, const CommandRequest& rhs) { - return !(lhs == rhs); - } - - Protocol CommandRequest::getProtocol() const { - return rpc::Protocol::kOpCommandV1; - } + _metadata = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); + _commandArgs = std::move(uassertStatusOK(cur.readAndAdvance<Validated<BSONObj>>()).val); + _inputDocs = DocumentRange{cur.data(), messageEnd}; +} + +StringData CommandRequest::getDatabase() const { + return _database; +} + +StringData CommandRequest::getCommandName() const { + return _commandName; +} + +const BSONObj& CommandRequest::getMetadata() const { + return _metadata; +} + +const BSONObj& CommandRequest::getCommandArgs() const { + return _commandArgs; +} + +DocumentRange CommandRequest::getInputDocs() const { + return _inputDocs; +} + +bool operator==(const CommandRequest& lhs, const CommandRequest& rhs) { + return std::tie( + lhs._database, lhs._commandName, lhs._metadata, lhs._commandArgs, lhs._inputDocs) == + std::tie(rhs._database, rhs._commandName, rhs._metadata, rhs._commandArgs, rhs._inputDocs); +} + +bool operator!=(const CommandRequest& lhs, const CommandRequest& rhs) { + return !(lhs == rhs); +} + +Protocol CommandRequest::getProtocol() const { + return rpc::Protocol::kOpCommandV1; +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/command_request.h b/src/mongo/rpc/command_request.h index cf2b54a7d42..bd29ca30597 100644 --- a/src/mongo/rpc/command_request.h +++ b/src/mongo/rpc/command_request.h @@ -35,72 +35,72 @@ #include "mongo/rpc/request_interface.h" namespace mongo { - class Message; +class Message; namespace rpc { +/** + * An immutable view of an OP_COMMAND message. The underlying bytes are owned + * by a mongo::Message, which must outlive any Reply instances created from it. + * + * TODO: BSON validation. See SERVER-18167 for details. + */ +class CommandRequest : public RequestInterface { +public: /** - * An immutable view of an OP_COMMAND message. The underlying bytes are owned - * by a mongo::Message, which must outlive any Reply instances created from it. - * - * TODO: BSON validation. See SERVER-18167 for details. + * Construct a Request from a Message. Underlying message MUST outlive the Request. + * Required fields are parsed eagerly, inputDocs are parsed lazily. */ - 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 CommandRequest(const Message* message); + explicit CommandRequest(const Message* message); - ~CommandRequest() = default; + ~CommandRequest() = default; - /** - * The database that the command is to be executed on. - */ - StringData getDatabase() const 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 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 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; + /** + * 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; + /** + * 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; - Protocol getProtocol() const final; + Protocol getProtocol() const final; - friend bool operator==(const CommandRequest& lhs, const CommandRequest& rhs); - friend bool operator!=(const CommandRequest& lhs, const CommandRequest& rhs); + friend bool operator==(const CommandRequest& lhs, const CommandRequest& rhs); + friend bool operator!=(const CommandRequest& lhs, const CommandRequest& rhs); - private: - const Message* _message; - StringData _database; - StringData _commandName; - BSONObj _metadata; - BSONObj _commandArgs; - DocumentRange _inputDocs; - }; +private: + const Message* _message; + StringData _database; + StringData _commandName; + BSONObj _metadata; + BSONObj _commandArgs; + DocumentRange _inputDocs; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/command_request_builder.cpp b/src/mongo/rpc/command_request_builder.cpp index b06fe47075d..da280c3054c 100644 --- a/src/mongo/rpc/command_request_builder.cpp +++ b/src/mongo/rpc/command_request_builder.cpp @@ -38,74 +38,70 @@ namespace mongo { namespace rpc { - CommandRequestBuilder::CommandRequestBuilder() - : _message{stdx::make_unique<Message>()} - {} - - CommandRequestBuilder::~CommandRequestBuilder() - {} - - CommandRequestBuilder::CommandRequestBuilder(std::unique_ptr<Message> message) - : _message{std::move(message)} - {} - - CommandRequestBuilder& CommandRequestBuilder::setDatabase(StringData database) { - invariant(_state == State::kDatabase); - _builder.appendStr(database); - _state = State::kCommandName; - return *this; - } - - CommandRequestBuilder& CommandRequestBuilder::setCommandName(StringData commandName) { - invariant(_state == State::kCommandName); - _builder.appendStr(commandName); - _state = State::kMetadata; - return *this; - } - - CommandRequestBuilder& CommandRequestBuilder::setMetadata(BSONObj metadata) { - invariant(_state == State::kMetadata); - metadata.appendSelfToBufBuilder(_builder); - _state = State::kCommandArgs; - return *this; - } - - CommandRequestBuilder& CommandRequestBuilder::setCommandArgs(BSONObj commandArgs) { - invariant(_state == State::kCommandArgs); - commandArgs.appendSelfToBufBuilder(_builder); - _state = State::kInputDocs; - return *this; - } - - CommandRequestBuilder& CommandRequestBuilder::addInputDocs(DocumentRange inputDocs) { - invariant(_state == State::kInputDocs); - auto rangeData = inputDocs.data(); - _builder.appendBuf(rangeData.data(), rangeData.length()); - return *this; - } - - CommandRequestBuilder& CommandRequestBuilder::addInputDoc(BSONObj inputDoc) { - invariant(_state == State::kInputDocs); - inputDoc.appendSelfToBufBuilder(_builder); - return *this; - } - - RequestBuilderInterface::State CommandRequestBuilder::getState() const { - return _state; - } - - Protocol CommandRequestBuilder::getProtocol() const { - return rpc::Protocol::kOpCommandV1; - } - - 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()); - _state = State::kDone; - return std::move(_message); - } +CommandRequestBuilder::CommandRequestBuilder() : _message{stdx::make_unique<Message>()} {} + +CommandRequestBuilder::~CommandRequestBuilder() {} + +CommandRequestBuilder::CommandRequestBuilder(std::unique_ptr<Message> message) + : _message{std::move(message)} {} + +CommandRequestBuilder& CommandRequestBuilder::setDatabase(StringData database) { + invariant(_state == State::kDatabase); + _builder.appendStr(database); + _state = State::kCommandName; + return *this; +} + +CommandRequestBuilder& CommandRequestBuilder::setCommandName(StringData commandName) { + invariant(_state == State::kCommandName); + _builder.appendStr(commandName); + _state = State::kMetadata; + return *this; +} + +CommandRequestBuilder& CommandRequestBuilder::setMetadata(BSONObj metadata) { + invariant(_state == State::kMetadata); + metadata.appendSelfToBufBuilder(_builder); + _state = State::kCommandArgs; + return *this; +} + +CommandRequestBuilder& CommandRequestBuilder::setCommandArgs(BSONObj commandArgs) { + invariant(_state == State::kCommandArgs); + commandArgs.appendSelfToBufBuilder(_builder); + _state = State::kInputDocs; + return *this; +} + +CommandRequestBuilder& CommandRequestBuilder::addInputDocs(DocumentRange inputDocs) { + invariant(_state == State::kInputDocs); + auto rangeData = inputDocs.data(); + _builder.appendBuf(rangeData.data(), rangeData.length()); + return *this; +} + +CommandRequestBuilder& CommandRequestBuilder::addInputDoc(BSONObj inputDoc) { + invariant(_state == State::kInputDocs); + inputDoc.appendSelfToBufBuilder(_builder); + return *this; +} + +RequestBuilderInterface::State CommandRequestBuilder::getState() const { + return _state; +} + +Protocol CommandRequestBuilder::getProtocol() const { + return rpc::Protocol::kOpCommandV1; +} + +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()); + _state = State::kDone; + return std::move(_message); +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/command_request_builder.h b/src/mongo/rpc/command_request_builder.h index 453b48f9cad..cf00e3f9652 100644 --- a/src/mongo/rpc/command_request_builder.h +++ b/src/mongo/rpc/command_request_builder.h @@ -40,54 +40,53 @@ namespace mongo { namespace rpc { +/** + * Constructs an OP_COMMAND message. + */ +class CommandRequestBuilder : public RequestBuilderInterface { +public: /** - * Constructs an OP_COMMAND message. + * Constructs an OP_COMMAND in a new buffer. */ - class CommandRequestBuilder : public RequestBuilderInterface { - public: + CommandRequestBuilder(); - /** - * Constructs an OP_COMMAND in a new buffer. - */ - CommandRequestBuilder(); + ~CommandRequestBuilder() final; - ~CommandRequestBuilder() final; - - /** - * Construct an OP_COMMAND in an existing buffer. Ownership of the buffer will be - * transfered to the CommandRequestBuilder. - */ - CommandRequestBuilder(std::unique_ptr<Message> message); + /** + * Construct an OP_COMMAND in an existing buffer. Ownership of the buffer will be + * transfered to the CommandRequestBuilder. + */ + CommandRequestBuilder(std::unique_ptr<Message> message); - CommandRequestBuilder& setDatabase(StringData database) final; + CommandRequestBuilder& setDatabase(StringData database) final; - CommandRequestBuilder& setCommandName(StringData commandName) final; + CommandRequestBuilder& setCommandName(StringData commandName) final; - CommandRequestBuilder& setMetadata(BSONObj metadata) final; + CommandRequestBuilder& setMetadata(BSONObj metadata) final; - CommandRequestBuilder& setCommandArgs(BSONObj commandArgs) final; + CommandRequestBuilder& setCommandArgs(BSONObj commandArgs) final; - CommandRequestBuilder& addInputDocs(DocumentRange inputDocs) final; + CommandRequestBuilder& addInputDocs(DocumentRange inputDocs) final; - CommandRequestBuilder& addInputDoc(BSONObj inputDoc) final; + CommandRequestBuilder& addInputDoc(BSONObj inputDoc) final; - State getState() const final; + State getState() const final; - Protocol getProtocol() const final; + Protocol getProtocol() const final; - /** - * Writes data then transfers ownership of the message to the caller. - * The behavior of calling any methods on the object is subsequently - * undefined. - */ - std::unique_ptr<Message> done() 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() final; - private: - BufBuilder _builder{}; - std::unique_ptr<Message> _message; +private: + BufBuilder _builder{}; + std::unique_ptr<Message> _message; - State _state{State::kDatabase}; - }; + State _state{State::kDatabase}; +}; } // rpc } // mongo diff --git a/src/mongo/rpc/command_request_builder_test.cpp b/src/mongo/rpc/command_request_builder_test.cpp index 10a4f2d8795..cdfbc9d01a9 100644 --- a/src/mongo/rpc/command_request_builder_test.cpp +++ b/src/mongo/rpc/command_request_builder_test.cpp @@ -36,57 +36,56 @@ namespace { - using namespace mongo; +using namespace mongo; - TEST(RequestBuilder, RoundTrip) { +TEST(RequestBuilder, RoundTrip) { + auto databaseName = "barbaz"; + auto commandName = "foobar"; - auto databaseName = "barbaz"; - auto commandName = "foobar"; + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); - BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); - auto metadata = metadataBob.done(); + BSONObjBuilder commandArgsBob{}; + commandArgsBob.append("bar", "baz"); + auto commandArgs = commandArgsBob.done(); - BSONObjBuilder commandArgsBob{}; - commandArgsBob.append("bar", "baz"); - auto commandArgs = commandArgsBob.done(); + BSONObjBuilder inputDoc1Bob{}; + inputDoc1Bob.append("z", "t"); + auto inputDoc1 = inputDoc1Bob.done(); - BSONObjBuilder inputDoc1Bob{}; - inputDoc1Bob.append("z", "t"); - auto inputDoc1 = inputDoc1Bob.done(); + BSONObjBuilder inputDoc2Bob{}; + inputDoc2Bob.append("h", "j"); + auto inputDoc2 = inputDoc2Bob.done(); - BSONObjBuilder inputDoc2Bob{}; - inputDoc2Bob.append("h", "j"); - auto inputDoc2 = inputDoc2Bob.done(); + BSONObjBuilder inputDoc3Bob{}; + inputDoc3Bob.append("g", "p"); + auto inputDoc3 = inputDoc3Bob.done(); - BSONObjBuilder inputDoc3Bob{}; - inputDoc3Bob.append("g", "p"); - auto inputDoc3 = inputDoc3Bob.done(); + BufBuilder inputDocs; + inputDoc1.appendSelfToBufBuilder(inputDocs); + inputDoc2.appendSelfToBufBuilder(inputDocs); + inputDoc3.appendSelfToBufBuilder(inputDocs); - BufBuilder inputDocs; - inputDoc1.appendSelfToBufBuilder(inputDocs); - inputDoc2.appendSelfToBufBuilder(inputDocs); - inputDoc3.appendSelfToBufBuilder(inputDocs); + rpc::DocumentRange inputDocRange{inputDocs.buf(), inputDocs.buf() + inputDocs.len()}; - rpc::DocumentRange inputDocRange{inputDocs.buf(), inputDocs.buf() + inputDocs.len()}; + rpc::CommandRequestBuilder r; - rpc::CommandRequestBuilder r; + auto msg = r.setDatabase(databaseName) + .setCommandName(commandName) + .setMetadata(metadata) + .setCommandArgs(commandArgs) + .addInputDocs(inputDocRange) + .done(); - auto msg = r.setDatabase(databaseName) - .setCommandName(commandName) - .setMetadata(metadata) - .setCommandArgs(commandArgs) - .addInputDocs(inputDocRange) - .done(); + rpc::CommandRequest parsed(msg.get()); - rpc::CommandRequest parsed(msg.get()); - - ASSERT_EQUALS(parsed.getDatabase(), databaseName); - ASSERT_EQUALS(parsed.getCommandName(), commandName); - ASSERT_EQUALS(parsed.getMetadata(), metadata); - ASSERT_EQUALS(parsed.getCommandArgs(), commandArgs); - // need ostream overloads for ASSERT_EQUALS - ASSERT_TRUE(parsed.getInputDocs() == inputDocRange); - } + ASSERT_EQUALS(parsed.getDatabase(), databaseName); + ASSERT_EQUALS(parsed.getCommandName(), commandName); + ASSERT_EQUALS(parsed.getMetadata(), metadata); + ASSERT_EQUALS(parsed.getCommandArgs(), commandArgs); + // need ostream overloads for ASSERT_EQUALS + ASSERT_TRUE(parsed.getInputDocs() == inputDocRange); +} } // namespace diff --git a/src/mongo/rpc/command_request_test.cpp b/src/mongo/rpc/command_request_test.cpp index 64732735d6f..8f681655b56 100644 --- a/src/mongo/rpc/command_request_test.cpp +++ b/src/mongo/rpc/command_request_test.cpp @@ -41,82 +41,81 @@ namespace { - using namespace mongo; - - TEST(CommandRequest, ParseAllFields) { - std::vector<char> opCommandData; - - using std::begin; - using std::end; - - auto writeString = [&opCommandData](const std::string& str) { - opCommandData.insert(end(opCommandData), begin(str), end(str)); - opCommandData.push_back('\0'); - }; - - auto writeObj = [&opCommandData](const BSONObj& obj) { - opCommandData.insert(end(opCommandData), obj.objdata(), - obj.objdata() + obj.objsize()); - }; - - auto database = std::string{"ookokokokok"}; - writeString(database); - - auto commandName = std::string{"abababa"}; - writeString(commandName); - - BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); - auto metadata = metadataBob.done(); - writeObj(metadata); - - BSONObjBuilder commandArgsBob{}; - commandArgsBob.append("baz", "garply"); - auto commandArgs = commandArgsBob.done(); - writeObj(commandArgs); - - BSONObjBuilder inputDoc1Bob{}; - inputDoc1Bob.append("meep", "boop").append("meow", "chirp"); - auto inputDoc1 = inputDoc1Bob.done(); - writeObj(inputDoc1); - - BSONObjBuilder inputDoc2Bob{}; - inputDoc1Bob.append("bleep", "bop").append("woof", "squeak"); - auto inputDoc2 = inputDoc2Bob.done(); - writeObj(inputDoc2); - - Message toSend; - toSend.setData(dbCommand, opCommandData.data(), opCommandData.size()); - - rpc::CommandRequest opCmd{&toSend}; - - ASSERT_EQUALS(opCmd.getCommandName(), commandName); - ASSERT_EQUALS(opCmd.getDatabase(), database); - ASSERT_EQUALS(opCmd.getMetadata(), metadata); - ASSERT_EQUALS(opCmd.getCommandArgs(), commandArgs); - - auto inputDocRange = opCmd.getInputDocs(); - auto inputDocRangeIter = inputDocRange.begin(); - - ASSERT_EQUALS(*inputDocRangeIter, inputDoc1); - // can't use assert equals since we don't have an op to print the iter. - ASSERT_FALSE(inputDocRangeIter == inputDocRange.end()); - ++inputDocRangeIter; - ASSERT_EQUALS(*inputDocRangeIter, inputDoc2); - ASSERT_FALSE(inputDocRangeIter == inputDocRange.end()); - ++inputDocRangeIter; - - ASSERT_TRUE(inputDocRangeIter == inputDocRange.end()); - } - - TEST(CommandRequest, InvalidNSThrows) { - rpc::CommandRequestBuilder crb; - crb.setDatabase("foo////!!!!<><><>"); - crb.setCommandName("foo"); - crb.setMetadata(BSONObj()); - crb.setCommandArgs(BSON("ping" << 1)); - auto msg = crb.done(); - ASSERT_THROWS(rpc::CommandRequest{msg.get()}, AssertionException); - } +using namespace mongo; + +TEST(CommandRequest, ParseAllFields) { + std::vector<char> opCommandData; + + using std::begin; + using std::end; + + auto writeString = [&opCommandData](const std::string& str) { + opCommandData.insert(end(opCommandData), begin(str), end(str)); + opCommandData.push_back('\0'); + }; + + auto writeObj = [&opCommandData](const BSONObj& obj) { + opCommandData.insert(end(opCommandData), obj.objdata(), obj.objdata() + obj.objsize()); + }; + + auto database = std::string{"ookokokokok"}; + writeString(database); + + auto commandName = std::string{"abababa"}; + writeString(commandName); + + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); + writeObj(metadata); + + BSONObjBuilder commandArgsBob{}; + commandArgsBob.append("baz", "garply"); + auto commandArgs = commandArgsBob.done(); + writeObj(commandArgs); + + BSONObjBuilder inputDoc1Bob{}; + inputDoc1Bob.append("meep", "boop").append("meow", "chirp"); + auto inputDoc1 = inputDoc1Bob.done(); + writeObj(inputDoc1); + + BSONObjBuilder inputDoc2Bob{}; + inputDoc1Bob.append("bleep", "bop").append("woof", "squeak"); + auto inputDoc2 = inputDoc2Bob.done(); + writeObj(inputDoc2); + + Message toSend; + toSend.setData(dbCommand, opCommandData.data(), opCommandData.size()); + + rpc::CommandRequest opCmd{&toSend}; + + ASSERT_EQUALS(opCmd.getCommandName(), commandName); + ASSERT_EQUALS(opCmd.getDatabase(), database); + ASSERT_EQUALS(opCmd.getMetadata(), metadata); + ASSERT_EQUALS(opCmd.getCommandArgs(), commandArgs); + + auto inputDocRange = opCmd.getInputDocs(); + auto inputDocRangeIter = inputDocRange.begin(); + + ASSERT_EQUALS(*inputDocRangeIter, inputDoc1); + // can't use assert equals since we don't have an op to print the iter. + ASSERT_FALSE(inputDocRangeIter == inputDocRange.end()); + ++inputDocRangeIter; + ASSERT_EQUALS(*inputDocRangeIter, inputDoc2); + ASSERT_FALSE(inputDocRangeIter == inputDocRange.end()); + ++inputDocRangeIter; + + ASSERT_TRUE(inputDocRangeIter == inputDocRange.end()); +} + +TEST(CommandRequest, InvalidNSThrows) { + rpc::CommandRequestBuilder crb; + crb.setDatabase("foo////!!!!<><><>"); + crb.setCommandName("foo"); + crb.setMetadata(BSONObj()); + crb.setCommandArgs(BSON("ping" << 1)); + auto msg = crb.done(); + ASSERT_THROWS(rpc::CommandRequest{msg.get()}, AssertionException); +} } // namespace diff --git a/src/mongo/rpc/document_range.cpp b/src/mongo/rpc/document_range.cpp index 56e5d8c1add..aff2cf6dfec 100644 --- a/src/mongo/rpc/document_range.cpp +++ b/src/mongo/rpc/document_range.cpp @@ -42,74 +42,67 @@ namespace mongo { namespace rpc { - DocumentRange::DocumentRange(const char* begin, const char* end) - : _range{begin, end} - {} - - DocumentRange::const_iterator DocumentRange::begin() const { - return const_iterator{ConstDataRangeCursor{_range}}; - } - - DocumentRange::const_iterator DocumentRange::end() const { - return const_iterator{}; - } - - ConstDataRange DocumentRange::data() const { - return _range; - } - - bool operator==(const DocumentRange& lhs, const DocumentRange& rhs) { - // We might want to change this to use std::equal in the future if - // we ever allow non-contigious document ranges - return (lhs._range.length() == rhs._range.length()) && - (std::memcmp(lhs._range.data(), rhs._range.data(), lhs._range.length()) == 0); - } - - bool operator!=(const DocumentRange& lhs, const DocumentRange& rhs) { - return !(lhs == rhs); - } - - DocumentRange::const_iterator::const_iterator(ConstDataRangeCursor cursor) - : _cursor{cursor} { - operator++(); - } - - DocumentRange::const_iterator::reference - DocumentRange::const_iterator::operator*() const { - return _obj; - } - - DocumentRange::const_iterator::pointer - DocumentRange::const_iterator::operator->() const { - return &_obj; - } - - DocumentRange::const_iterator& - DocumentRange::const_iterator::operator++() { - if (_cursor.length() == 0) { - *this = const_iterator{}; - } else { - _obj = std::move(uassertStatusOK(_cursor.readAndAdvance<Validated<BSONObj>>()).val); - } - return *this; - } - - DocumentRange::const_iterator - DocumentRange::const_iterator::operator++(int) { - auto pre = const_iterator{_cursor}; - operator++(); - return pre; - } - - bool operator==(const DocumentRange::const_iterator& lhs, - const DocumentRange::const_iterator& rhs) { - return lhs._cursor == rhs._cursor; - } - - bool operator!=(const DocumentRange::const_iterator& lhs, - const DocumentRange::const_iterator& rhs) { - return !(lhs == rhs); +DocumentRange::DocumentRange(const char* begin, const char* end) : _range{begin, end} {} + +DocumentRange::const_iterator DocumentRange::begin() const { + return const_iterator{ConstDataRangeCursor{_range}}; +} + +DocumentRange::const_iterator DocumentRange::end() const { + return const_iterator{}; +} + +ConstDataRange DocumentRange::data() const { + return _range; +} + +bool operator==(const DocumentRange& lhs, const DocumentRange& rhs) { + // We might want to change this to use std::equal in the future if + // we ever allow non-contigious document ranges + return (lhs._range.length() == rhs._range.length()) && + (std::memcmp(lhs._range.data(), rhs._range.data(), lhs._range.length()) == 0); +} + +bool operator!=(const DocumentRange& lhs, const DocumentRange& rhs) { + return !(lhs == rhs); +} + +DocumentRange::const_iterator::const_iterator(ConstDataRangeCursor cursor) : _cursor{cursor} { + operator++(); +} + +DocumentRange::const_iterator::reference DocumentRange::const_iterator::operator*() const { + return _obj; +} + +DocumentRange::const_iterator::pointer DocumentRange::const_iterator::operator->() const { + return &_obj; +} + +DocumentRange::const_iterator& DocumentRange::const_iterator::operator++() { + if (_cursor.length() == 0) { + *this = const_iterator{}; + } else { + _obj = std::move(uassertStatusOK(_cursor.readAndAdvance<Validated<BSONObj>>()).val); } + return *this; +} + +DocumentRange::const_iterator DocumentRange::const_iterator::operator++(int) { + auto pre = const_iterator{_cursor}; + operator++(); + return pre; +} + +bool operator==(const DocumentRange::const_iterator& lhs, + const DocumentRange::const_iterator& rhs) { + return lhs._cursor == rhs._cursor; +} + +bool operator!=(const DocumentRange::const_iterator& lhs, + const DocumentRange::const_iterator& rhs) { + return !(lhs == rhs); +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/document_range.h b/src/mongo/rpc/document_range.h index aa8d3131b13..95d23d82b4c 100644 --- a/src/mongo/rpc/document_range.h +++ b/src/mongo/rpc/document_range.h @@ -38,62 +38,61 @@ namespace mongo { namespace rpc { - /** - * A read-only view over a sequence of BSON documents. - * - * TODO: - * - Handle document validation - * - Currently this only supports a contiguous buffer of BSON documents, - * in the future it should support non-contiguous buffers as well. - */ - class DocumentRange { - public: - class const_iterator; - - DocumentRange() = default; - - DocumentRange(const char* begin, const char* end); - - const_iterator begin() const; - const_iterator end() const; - - // Get a ConstDataRange over the underlying raw buffer. - ConstDataRange data() const; - - // Deep equality of all documents in both ranges. - friend bool operator==(const DocumentRange& lhs, const DocumentRange& rhs); - friend bool operator!=(const DocumentRange& lhs, const DocumentRange& rhs); - - private: - ConstDataRange _range{nullptr, nullptr}; - }; - - class DocumentRange::const_iterator - : public std::iterator<std::forward_iterator_tag, - BSONObj, - std::ptrdiff_t, - const BSONObj*, - const BSONObj&> { - public: - const_iterator() = default; - - reference operator*() const; - pointer operator->() const; - - const_iterator& operator++(); - const_iterator operator++(int); - - friend bool operator==(const const_iterator&, const const_iterator&); - friend bool operator!=(const const_iterator&, const const_iterator&); - - private: - // The only way to get a non-end iterator is from DocumentRange begin(). - friend class DocumentRange; - explicit const_iterator(ConstDataRangeCursor cursor); - - ConstDataRangeCursor _cursor{nullptr, nullptr}; - BSONObj _obj; - }; +/** + * A read-only view over a sequence of BSON documents. + * + * TODO: + * - Handle document validation + * - Currently this only supports a contiguous buffer of BSON documents, + * in the future it should support non-contiguous buffers as well. + */ +class DocumentRange { +public: + class const_iterator; + + DocumentRange() = default; + + DocumentRange(const char* begin, const char* end); + + const_iterator begin() const; + const_iterator end() const; + + // Get a ConstDataRange over the underlying raw buffer. + ConstDataRange data() const; + + // Deep equality of all documents in both ranges. + friend bool operator==(const DocumentRange& lhs, const DocumentRange& rhs); + friend bool operator!=(const DocumentRange& lhs, const DocumentRange& rhs); + +private: + ConstDataRange _range{nullptr, nullptr}; +}; + +class DocumentRange::const_iterator : public std::iterator<std::forward_iterator_tag, + BSONObj, + std::ptrdiff_t, + const BSONObj*, + const BSONObj&> { +public: + const_iterator() = default; + + reference operator*() const; + pointer operator->() const; + + const_iterator& operator++(); + const_iterator operator++(int); + + friend bool operator==(const const_iterator&, const const_iterator&); + friend bool operator!=(const const_iterator&, const const_iterator&); + +private: + // The only way to get a non-end iterator is from DocumentRange begin(). + friend class DocumentRange; + explicit const_iterator(ConstDataRangeCursor cursor); + + ConstDataRangeCursor _cursor{nullptr, nullptr}; + BSONObj _obj; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/factory.cpp b/src/mongo/rpc/factory.cpp index 81bcf42c969..085fa9e7a7b 100644 --- a/src/mongo/rpc/factory.cpp +++ b/src/mongo/rpc/factory.cpp @@ -43,21 +43,20 @@ namespace mongo { namespace rpc { - std::unique_ptr<RequestBuilderInterface> makeRequestBuilder(ProtocolSet clientProtos, - ProtocolSet serverProtos) { - switch (uassertStatusOK(negotiate(clientProtos, serverProtos))) { +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) { - - switch (unownedMessage->operation()) { +std::unique_ptr<ReplyInterface> makeReply(const Message* unownedMessage) { + switch (unownedMessage->operation()) { case mongo::opReply: return stdx::make_unique<LegacyReply>(unownedMessage); case mongo::dbCommandReply: @@ -66,8 +65,8 @@ namespace rpc { uasserted(ErrorCodes::UnsupportedFormat, str::stream() << "Received a reply message with unexpected opcode: " << unownedMessage->operation()); - } } +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/factory.h b/src/mongo/rpc/factory.h index 93398d86e50..7de92664b69 100644 --- a/src/mongo/rpc/factory.h +++ b/src/mongo/rpc/factory.h @@ -38,23 +38,23 @@ */ namespace mongo { - class Message; +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 according to the contents of the message. - * Throws if one cannot be chosen. - */ - std::unique_ptr<ReplyInterface> makeReply(const Message* unownedMessage); +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 according to the contents of the message. + * Throws if one cannot be chosen. + */ +std::unique_ptr<ReplyInterface> makeReply(const Message* unownedMessage); } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/get_status_from_command_result.cpp b/src/mongo/rpc/get_status_from_command_result.cpp index e7366abedcc..4aa06db1b3b 100644 --- a/src/mongo/rpc/get_status_from_command_result.cpp +++ b/src/mongo/rpc/get_status_from_command_result.cpp @@ -36,43 +36,40 @@ namespace mongo { - Status getStatusFromCommandResult(const BSONObj& result) { - BSONElement okElement = result["ok"]; - BSONElement codeElement = result["code"]; - BSONElement errmsgElement = result["errmsg"]; +Status getStatusFromCommandResult(const BSONObj& result) { + BSONElement okElement = result["ok"]; + BSONElement codeElement = result["code"]; + BSONElement errmsgElement = result["errmsg"]; - // StaleConfigException doesn't pass "ok" in legacy servers - BSONElement dollarErrElement = result["$err"]; + // StaleConfigException doesn't pass "ok" in legacy servers + BSONElement dollarErrElement = result["$err"]; - if (okElement.eoo() && dollarErrElement.eoo()) { - return Status(ErrorCodes::CommandResultSchemaViolation, - mongoutils::str::stream() << "No \"ok\" field in command result " << - result); - } - if (okElement.trueValue()) { - return Status::OK(); - } - int code = codeElement.numberInt(); - if (0 == code) { - code = ErrorCodes::UnknownError; - } - std::string errmsg; - if (errmsgElement.type() == String) { - errmsg = errmsgElement.String(); - } - else if (!errmsgElement.eoo()) { - errmsg = errmsgElement.toString(); - } - - // we can't use startsWith(errmsg, "no such") - // as we have errors such as "no such collection" - if (code == ErrorCodes::UnknownError && - (str::startsWith(errmsg, "no such cmd") || - str::startsWith(errmsg, "no such command"))) { - code = ErrorCodes::CommandNotFound; - } + if (okElement.eoo() && dollarErrElement.eoo()) { + return Status(ErrorCodes::CommandResultSchemaViolation, + mongoutils::str::stream() << "No \"ok\" field in command result " << result); + } + if (okElement.trueValue()) { + return Status::OK(); + } + int code = codeElement.numberInt(); + if (0 == code) { + code = ErrorCodes::UnknownError; + } + std::string errmsg; + if (errmsgElement.type() == String) { + errmsg = errmsgElement.String(); + } else if (!errmsgElement.eoo()) { + errmsg = errmsgElement.toString(); + } - return Status(ErrorCodes::Error(code), errmsg); + // we can't use startsWith(errmsg, "no such") + // as we have errors such as "no such collection" + if (code == ErrorCodes::UnknownError && + (str::startsWith(errmsg, "no such cmd") || str::startsWith(errmsg, "no such command"))) { + code = ErrorCodes::CommandNotFound; } + return Status(ErrorCodes::Error(code), errmsg); +} + } // namespace mongo diff --git a/src/mongo/rpc/get_status_from_command_result.h b/src/mongo/rpc/get_status_from_command_result.h index 6c930ccd017..e1477436e55 100644 --- a/src/mongo/rpc/get_status_from_command_result.h +++ b/src/mongo/rpc/get_status_from_command_result.h @@ -29,20 +29,20 @@ #pragma once namespace mongo { - class BSONObj; - class Status; +class BSONObj; +class Status; - /** - * Converts "result" into a Status object. The input is expected to be the object returned - * by running a command. Returns ErrorCodes::CommandResultSchemaViolation if "result" does - * not look like the result of a command. - * - * Command results must have a field called "ok" whose value may be interpreted as a boolean. - * If the value interpreted as a boolean is true, the resultant status is Status::OK(). - * Otherwise, it is an error status. The code comes from the "code" field, if present and - * number-ish, while the reason message will come from the errmsg field, if present and - * string-ish. - */ - Status getStatusFromCommandResult(const BSONObj& result); +/** + * Converts "result" into a Status object. The input is expected to be the object returned + * by running a command. Returns ErrorCodes::CommandResultSchemaViolation if "result" does + * not look like the result of a command. + * + * Command results must have a field called "ok" whose value may be interpreted as a boolean. + * If the value interpreted as a boolean is true, the resultant status is Status::OK(). + * Otherwise, it is an error status. The code comes from the "code" field, if present and + * number-ish, while the reason message will come from the errmsg field, if present and + * string-ish. + */ +Status getStatusFromCommandResult(const BSONObj& result); } // namespace mongo diff --git a/src/mongo/rpc/legacy_reply.cpp b/src/mongo/rpc/legacy_reply.cpp index 3c16df28426..62a45ea1286 100644 --- a/src/mongo/rpc/legacy_reply.cpp +++ b/src/mongo/rpc/legacy_reply.cpp @@ -40,52 +40,50 @@ 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 - std::tie(_commandReply, _metadata) = uassertStatusOK( - rpc::upconvertReplyMetadata(BSONObj(qr.data())) - ); - } - - const BSONObj& LegacyReply::getMetadata() const { - return _metadata; - } - - const BSONObj& LegacyReply::getCommandReply() const { - return _commandReply; - } - - DocumentRange LegacyReply::getOutputDocs() const { - // return empty range - return DocumentRange{}; - } - - Protocol LegacyReply::getProtocol() const { - return rpc::Protocol::kOpQuery; - } +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 + std::tie(_commandReply, _metadata) = + uassertStatusOK(rpc::upconvertReplyMetadata(BSONObj(qr.data()))); +} + +const BSONObj& LegacyReply::getMetadata() const { + return _metadata; +} + +const BSONObj& LegacyReply::getCommandReply() const { + return _commandReply; +} + +DocumentRange LegacyReply::getOutputDocs() const { + // return empty range + return DocumentRange{}; +} + +Protocol LegacyReply::getProtocol() const { + return rpc::Protocol::kOpQuery; +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/legacy_reply.h b/src/mongo/rpc/legacy_reply.h index e839d305b42..86258c2642b 100644 --- a/src/mongo/rpc/legacy_reply.h +++ b/src/mongo/rpc/legacy_reply.h @@ -35,56 +35,55 @@ #include "mongo/rpc/protocol.h" namespace mongo { - class Message; +class Message; namespace rpc { +/** + * Immutable view of an OP_REPLY legacy-style command reply. + * + * TODO: BSON validation (SERVER-18167) + */ +class LegacyReply : public ReplyInterface { +public: /** - * Immutable view of an OP_REPLY legacy-style command reply. - * - * TODO: BSON validation (SERVER-18167) + * Construct a Reply from a Message. + * The underlying message MUST outlive the Reply. */ - class LegacyReply : public ReplyInterface { - public: + explicit LegacyReply(const Message* message); - /** - * 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; + /** + * 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; + /** + * 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; + /** + * 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; - Protocol getProtocol() const final; + Protocol getProtocol() const final; - private: - const Message* _message; +private: + const Message* _message; - // TODO: SERVER-18236 - BSONObj _metadata{}; - BSONObj _commandReply{}; // will hold unowned - }; + // TODO: SERVER-18236 + BSONObj _metadata{}; + 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 index 0712c0d5fee..4448f1e9810 100644 --- a/src/mongo/rpc/legacy_reply_builder.cpp +++ b/src/mongo/rpc/legacy_reply_builder.cpp @@ -38,91 +38,88 @@ 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); - _metadata = std::move(metadata); - _state = State::kCommandReply; - return *this; - } - - LegacyReplyBuilder& LegacyReplyBuilder::setRawCommandReply(BSONObj commandReply) { - invariant(_state == State::kCommandReply); - BSONObj downconvertedCommandReply = uassertStatusOK( - rpc::downconvertReplyMetadata(std::move(commandReply), std::move(_metadata)) - ); - downconvertedCommandReply.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; - } - - Protocol LegacyReplyBuilder::getProtocol() const { - return rpc::Protocol::kOpQuery; - } - - void LegacyReplyBuilder::reset() { - // If we are in State::kMetadata, we are already in the 'start' state, so by - // immediately returning, we save a heap allocation. - if (_state == State::kMetadata) { - return; - } - _builder.reset(); - _metadata = BSONObj(); - _message = stdx::make_unique<Message>(); - _state = State::kMetadata; - } - - - 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); - } - - std::size_t LegacyReplyBuilder::availableSpaceForOutputDocs() const { - invariant (State::kDone != _state); - // LegacyReplyBuilder currently does not support addOutputDoc(s) - return 0u; +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); + _metadata = std::move(metadata); + _state = State::kCommandReply; + return *this; +} + +LegacyReplyBuilder& LegacyReplyBuilder::setRawCommandReply(BSONObj commandReply) { + invariant(_state == State::kCommandReply); + BSONObj downconvertedCommandReply = uassertStatusOK( + rpc::downconvertReplyMetadata(std::move(commandReply), std::move(_metadata))); + downconvertedCommandReply.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; +} + +Protocol LegacyReplyBuilder::getProtocol() const { + return rpc::Protocol::kOpQuery; +} + +void LegacyReplyBuilder::reset() { + // If we are in State::kMetadata, we are already in the 'start' state, so by + // immediately returning, we save a heap allocation. + if (_state == State::kMetadata) { + return; } + _builder.reset(); + _metadata = BSONObj(); + _message = stdx::make_unique<Message>(); + _state = State::kMetadata; +} + + +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); +} + +std::size_t LegacyReplyBuilder::availableSpaceForOutputDocs() const { + invariant(State::kDone != _state); + // LegacyReplyBuilder currently does not support addOutputDoc(s) + return 0u; +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/legacy_reply_builder.h b/src/mongo/rpc/legacy_reply_builder.h index d777c97203f..417a6db8492 100644 --- a/src/mongo/rpc/legacy_reply_builder.h +++ b/src/mongo/rpc/legacy_reply_builder.h @@ -39,36 +39,34 @@ namespace mongo { namespace rpc { - class LegacyReplyBuilder : public ReplyBuilderInterface { - public: +class LegacyReplyBuilder : public ReplyBuilderInterface { +public: + LegacyReplyBuilder(); + LegacyReplyBuilder(std::unique_ptr<Message>); + ~LegacyReplyBuilder() final; - LegacyReplyBuilder(); - LegacyReplyBuilder(std::unique_ptr<Message>); - ~LegacyReplyBuilder() final; + LegacyReplyBuilder& setMetadata(BSONObj metadata) final; + LegacyReplyBuilder& setRawCommandReply(BSONObj commandReply) final; - LegacyReplyBuilder& setMetadata(BSONObj metadata) final; - LegacyReplyBuilder& setRawCommandReply(BSONObj commandReply) final; + LegacyReplyBuilder& addOutputDocs(DocumentRange outputDocs) final; + LegacyReplyBuilder& addOutputDoc(BSONObj outputDoc) final; - LegacyReplyBuilder& addOutputDocs(DocumentRange outputDocs) final; - LegacyReplyBuilder& addOutputDoc(BSONObj outputDoc) final; + State getState() const final; - State getState() const final; + void reset() final; - void reset() final; + std::unique_ptr<Message> done() final; - std::unique_ptr<Message> done() final; + Protocol getProtocol() const final; - Protocol getProtocol() const final; + std::size_t availableSpaceForOutputDocs() const final; - std::size_t availableSpaceForOutputDocs() const final; - - private: - - BufBuilder _builder{}; - BSONObj _metadata{}; - std::unique_ptr<Message> _message; - State _state{State::kMetadata}; - }; +private: + BufBuilder _builder{}; + BSONObj _metadata{}; + 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 index 57f3f12cb58..d0340b0b831 100644 --- a/src/mongo/rpc/legacy_request.cpp +++ b/src/mongo/rpc/legacy_request.cpp @@ -38,50 +38,46 @@ namespace mongo { namespace rpc { - LegacyRequest::LegacyRequest(const Message *message) - : _message(std::move(message)) - , _dbMessage(*message) - , _queryMessage(_dbMessage) { - - _database = nsToDatabaseSubstring(_queryMessage.ns); - - uassert(ErrorCodes::InvalidNamespace, - str::stream() << "Invalid database name: '" << _database << "'", - NamespaceString::validDBName(_database)); - - std::tie(_upconvertedCommandArgs, _upconvertedMetadata) = uassertStatusOK( - rpc::upconvertRequestMetadata(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{}; - } - - Protocol LegacyRequest::getProtocol() const { - return rpc::Protocol::kOpQuery; - } +LegacyRequest::LegacyRequest(const Message* message) + : _message(std::move(message)), _dbMessage(*message), _queryMessage(_dbMessage) { + _database = nsToDatabaseSubstring(_queryMessage.ns); + + uassert(ErrorCodes::InvalidNamespace, + str::stream() << "Invalid database name: '" << _database << "'", + NamespaceString::validDBName(_database)); + + std::tie(_upconvertedCommandArgs, _upconvertedMetadata) = + uassertStatusOK(rpc::upconvertRequestMetadata(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{}; +} + +Protocol LegacyRequest::getProtocol() const { + return rpc::Protocol::kOpQuery; +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/legacy_request.h b/src/mongo/rpc/legacy_request.h index 59371600894..9312c59639d 100644 --- a/src/mongo/rpc/legacy_request.h +++ b/src/mongo/rpc/legacy_request.h @@ -36,71 +36,71 @@ #include "mongo/rpc/request_interface.h" namespace mongo { - class Message; +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: /** - * 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. - * + * Construct a Request from a Message. Underlying message MUST outlive the Request. + * Required fields are parsed eagerly, inputDocs are parsed lazily. */ - 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); + explicit LegacyRequest(const Message* message); - ~LegacyRequest() final; + ~LegacyRequest() final; - /** - * The database that the command is to be executed on. - */ - StringData getDatabase() const 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 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 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; + /** + * 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; + /** + * 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; - Protocol getProtocol() const final; + Protocol getProtocol() 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; - StringData _database; +private: + const Message* _message; + // TODO: metadata will be handled in SERVER-18236 + // for now getMetadata() is a no op + DbMessage _dbMessage; + QueryMessage _queryMessage; + StringData _database; - BSONObj _upconvertedMetadata; - BSONObj _upconvertedCommandArgs; - }; + 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 index fc96d738905..05f1fe79ce3 100644 --- a/src/mongo/rpc/legacy_request_builder.cpp +++ b/src/mongo/rpc/legacy_request_builder.cpp @@ -41,85 +41,79 @@ 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( - rpc::downconvertRequestMetadata(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; - } - - Protocol LegacyRequestBuilder::getProtocol() const { - return rpc::Protocol::kOpQuery; - } - - std::unique_ptr<Message> LegacyRequestBuilder::done() { - invariant(_state == State::kInputDocs); - _message->setData(dbQuery, _builder.buf(), _builder.len()); - _state = State::kDone; - return std::move(_message); - } +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( + rpc::downconvertRequestMetadata(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; +} + +Protocol LegacyRequestBuilder::getProtocol() const { + return rpc::Protocol::kOpQuery; +} + +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 index 83890df45e2..0d44c47a513 100644 --- a/src/mongo/rpc/legacy_request_builder.h +++ b/src/mongo/rpc/legacy_request_builder.h @@ -39,39 +39,39 @@ namespace mongo { namespace rpc { - class LegacyRequestBuilder : public RequestBuilderInterface { - public: - LegacyRequestBuilder(); - ~LegacyRequestBuilder() final; +class LegacyRequestBuilder : public RequestBuilderInterface { +public: + LegacyRequestBuilder(); + ~LegacyRequestBuilder() final; - LegacyRequestBuilder(std::unique_ptr<Message>); + 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& 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; + LegacyRequestBuilder& addInputDocs(DocumentRange inputDocs) final; + LegacyRequestBuilder& addInputDoc(BSONObj inputDoc) final; - State getState() const final; + State getState() const final; - Protocol getProtocol() const final; + Protocol getProtocol() const final; - std::unique_ptr<Message> done() final; + std::unique_ptr<Message> done() final; - private: - std::unique_ptr<Message> _message; - BufBuilder _builder{}; +private: + std::unique_ptr<Message> _message; + BufBuilder _builder{}; - // we need to stash this as we need commandArgs to - // upconvert. - BSONObj _metadata; + // we need to stash this as we need commandArgs to + // upconvert. + BSONObj _metadata; - std::string _ns{}; // copied to in setDatabase + std::string _ns{}; // copied to in setDatabase - State _state{State::kDatabase}; - }; + State _state{State::kDatabase}; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/legacy_request_test.cpp b/src/mongo/rpc/legacy_request_test.cpp index a24b8eba072..caadc74e3e8 100644 --- a/src/mongo/rpc/legacy_request_test.cpp +++ b/src/mongo/rpc/legacy_request_test.cpp @@ -37,16 +37,16 @@ namespace { - using namespace mongo; +using namespace mongo; - TEST(LegacyRequest, InvalidNSThrows) { - rpc::LegacyRequestBuilder crb; - crb.setDatabase("foo////!!!!<><><>"); - crb.setCommandName("foo"); - crb.setMetadata(BSONObj()); - crb.setCommandArgs(BSON("ping" << 1)); - auto msg = crb.done(); - ASSERT_THROWS(rpc::LegacyRequest{msg.get()}, AssertionException); - } +TEST(LegacyRequest, InvalidNSThrows) { + rpc::LegacyRequestBuilder crb; + crb.setDatabase("foo////!!!!<><><>"); + crb.setCommandName("foo"); + crb.setMetadata(BSONObj()); + crb.setCommandArgs(BSON("ping" << 1)); + auto msg = crb.done(); + ASSERT_THROWS(rpc::LegacyRequest{msg.get()}, AssertionException); +} } // namespace diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp index c65953790ec..87db6ce5389 100644 --- a/src/mongo/rpc/metadata.cpp +++ b/src/mongo/rpc/metadata.cpp @@ -39,122 +39,111 @@ namespace mongo { namespace rpc { - BSONObj makeEmptyMetadata() { - return BSONObj(); +BSONObj makeEmptyMetadata() { + return BSONObj(); +} + +Status readRequestMetadata(OperationContext* txn, const BSONObj& metadataObj) { + auto swServerSelectionMetadata = ServerSelectionMetadata::readFromMetadata(metadataObj); + if (!swServerSelectionMetadata.isOK()) { + return swServerSelectionMetadata.getStatus(); } + ServerSelectionMetadata::get(txn) = std::move(swServerSelectionMetadata.getValue()); - Status readRequestMetadata(OperationContext* txn, const BSONObj& metadataObj) { - auto swServerSelectionMetadata = ServerSelectionMetadata::readFromMetadata(metadataObj); - if (!swServerSelectionMetadata.isOK()) { - return swServerSelectionMetadata.getStatus(); - } - ServerSelectionMetadata::get(txn) = std::move(swServerSelectionMetadata.getValue()); + auto swAuditMetadata = AuditMetadata::readFromMetadata(metadataObj); + if (!swAuditMetadata.isOK()) { + return swAuditMetadata.getStatus(); + } + AuditMetadata::get(txn) = std::move(swAuditMetadata.getValue()); - auto swAuditMetadata = AuditMetadata::readFromMetadata(metadataObj); - if (!swAuditMetadata.isOK()) { - return swAuditMetadata.getStatus(); - } - AuditMetadata::get(txn) = std::move(swAuditMetadata.getValue()); + return Status::OK(); +} - return Status::OK(); +Status writeRequestMetadata(OperationContext* txn, BSONObjBuilder* metadataBob) { + auto ssStatus = ServerSelectionMetadata::get(txn).writeToMetadata(metadataBob); + if (!ssStatus.isOK()) { + return ssStatus; } - - Status writeRequestMetadata(OperationContext* txn, BSONObjBuilder* metadataBob) { - auto ssStatus = ServerSelectionMetadata::get(txn).writeToMetadata(metadataBob); - if (!ssStatus.isOK()) { - return ssStatus; - } - return Status::OK(); + return Status::OK(); +} + +StatusWith<CommandAndMetadata> upconvertRequestMetadata(BSONObj legacyCmdObj, int queryFlags) { + // We can reuse the same metadata BOB for every upconvert call, but we need to keep + // making new command BOBs as each metadata bob will need to remove fields. We can not use + // mutablebson here because the ServerSelectionMetadata upconvert routine performs + // manipulations (replacing a root with its child) that mutablebson doesn't + // support. + BSONObjBuilder metadataBob; + + // Ordering is important here - ServerSelectionMetadata must be upconverted + // first, then AuditMetadata. + BSONObjBuilder ssmCommandBob; + auto upconvertStatus = + ServerSelectionMetadata::upconvert(legacyCmdObj, queryFlags, &ssmCommandBob, &metadataBob); + if (!upconvertStatus.isOK()) { + return upconvertStatus; } - StatusWith<CommandAndMetadata> upconvertRequestMetadata(BSONObj legacyCmdObj, int queryFlags) { - // We can reuse the same metadata BOB for every upconvert call, but we need to keep - // making new command BOBs as each metadata bob will need to remove fields. We can not use - // mutablebson here because the ServerSelectionMetadata upconvert routine performs - // manipulations (replacing a root with its child) that mutablebson doesn't - // support. - BSONObjBuilder metadataBob; - - // Ordering is important here - ServerSelectionMetadata must be upconverted - // first, then AuditMetadata. - BSONObjBuilder ssmCommandBob; - auto upconvertStatus = ServerSelectionMetadata::upconvert(legacyCmdObj, - queryFlags, - &ssmCommandBob, - &metadataBob); - if (!upconvertStatus.isOK()) { - return upconvertStatus; - } - - - BSONObjBuilder auditCommandBob; - upconvertStatus = AuditMetadata::upconvert(ssmCommandBob.done(), - queryFlags, - &auditCommandBob, - &metadataBob); - - if (!upconvertStatus.isOK()) { - return upconvertStatus; - } - - - return std::make_tuple(auditCommandBob.obj(), metadataBob.obj()); + + BSONObjBuilder auditCommandBob; + upconvertStatus = + AuditMetadata::upconvert(ssmCommandBob.done(), queryFlags, &auditCommandBob, &metadataBob); + + if (!upconvertStatus.isOK()) { + return upconvertStatus; } - StatusWith<LegacyCommandAndFlags> downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata) { - int legacyQueryFlags = 0; - BSONObjBuilder auditCommandBob; - // Ordering is important here - AuditingMetadata must be downconverted first, - // then ServerSelectionMetadata. - auto downconvertStatus = AuditMetadata::downconvert(cmdObj, - metadata, - &auditCommandBob, - &legacyQueryFlags); - if (!downconvertStatus.isOK()) { - return downconvertStatus; - } + return std::make_tuple(auditCommandBob.obj(), metadataBob.obj()); +} +StatusWith<LegacyCommandAndFlags> downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata) { + int legacyQueryFlags = 0; + BSONObjBuilder auditCommandBob; + // Ordering is important here - AuditingMetadata must be downconverted first, + // then ServerSelectionMetadata. + auto downconvertStatus = + AuditMetadata::downconvert(cmdObj, metadata, &auditCommandBob, &legacyQueryFlags); - BSONObjBuilder ssmCommandBob; - downconvertStatus = ServerSelectionMetadata::downconvert(auditCommandBob.done(), - metadata, - &ssmCommandBob, - &legacyQueryFlags); - if (!downconvertStatus.isOK()) { - return downconvertStatus; - } + if (!downconvertStatus.isOK()) { + return downconvertStatus; + } - return std::make_tuple(ssmCommandBob.obj(), std::move(legacyQueryFlags)); + BSONObjBuilder ssmCommandBob; + downconvertStatus = ServerSelectionMetadata::downconvert( + auditCommandBob.done(), metadata, &ssmCommandBob, &legacyQueryFlags); + if (!downconvertStatus.isOK()) { + return downconvertStatus; } - StatusWith<CommandReplyWithMetadata> upconvertReplyMetadata(BSONObj legacyReply) { - BSONObjBuilder commandReplyBob; - BSONObjBuilder metadataBob; - auto upconvertStatus = ShardingMetadata::upconvert(legacyReply, - &commandReplyBob, - &metadataBob); - if (!upconvertStatus.isOK()) { - return upconvertStatus; - } + return std::make_tuple(ssmCommandBob.obj(), std::move(legacyQueryFlags)); +} + +StatusWith<CommandReplyWithMetadata> upconvertReplyMetadata(BSONObj legacyReply) { + BSONObjBuilder commandReplyBob; + BSONObjBuilder metadataBob; - return std::make_tuple(commandReplyBob.obj(), metadataBob.obj()); + auto upconvertStatus = ShardingMetadata::upconvert(legacyReply, &commandReplyBob, &metadataBob); + if (!upconvertStatus.isOK()) { + return upconvertStatus; } - StatusWith<BSONObj> downconvertReplyMetadata(BSONObj commandReply, BSONObj replyMetadata) { - BSONObjBuilder legacyCommandReplyBob; + return std::make_tuple(commandReplyBob.obj(), metadataBob.obj()); +} - auto downconvertStatus = ShardingMetadata::downconvert(commandReply, - replyMetadata, - &legacyCommandReplyBob); - if (!downconvertStatus.isOK()) { - return downconvertStatus; - } +StatusWith<BSONObj> downconvertReplyMetadata(BSONObj commandReply, BSONObj replyMetadata) { + BSONObjBuilder legacyCommandReplyBob; - return legacyCommandReplyBob.obj(); + auto downconvertStatus = + ShardingMetadata::downconvert(commandReply, replyMetadata, &legacyCommandReplyBob); + if (!downconvertStatus.isOK()) { + return downconvertStatus; } + return legacyCommandReplyBob.obj(); +} + } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata.h b/src/mongo/rpc/metadata.h index ea0277059a3..393c3c57234 100644 --- a/src/mongo/rpc/metadata.h +++ b/src/mongo/rpc/metadata.h @@ -34,9 +34,9 @@ #include "mongo/stdx/functional.h" namespace mongo { - class BSONObj; - class BSONObjBuilder; - class OperationContext; +class BSONObj; +class BSONObjBuilder; +class OperationContext; /** * Utilities for converting metadata between the legacy OP_QUERY format and the new @@ -57,77 +57,77 @@ namespace mongo { */ namespace rpc { - /** - * Returns an empty metadata object. - */ - BSONObj makeEmptyMetadata(); - - /** - * Reads metadata from a metadata object and sets it on this OperationContext. - */ - Status readRequestMetadata(OperationContext* txn, const BSONObj& metadataObj); - - /** - * Writes metadata from an OperationContext to a metadata object. - */ - Status writeRequestMetadata(OperationContext* txn, BSONObjBuilder* metadataBob); - - /** - * 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> upconvertRequestMetadata(BSONObj legacyCmdObj, int queryFlags); - - /** - * Given a command object and a metadata object, attempts to construct a legacy command - * object and query flags bitfield augmented with the given metadata. - */ - StatusWith<LegacyCommandAndFlags> downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata); - - /** - * A command reply and associated metadata object. - */ - using CommandReplyWithMetadata = std::tuple<BSONObj, BSONObj>; - - /** - * Given a legacy command reply, attempts to strip the metadata from the reply and construct - * a metadata object. - */ - StatusWith<CommandReplyWithMetadata> upconvertReplyMetadata(BSONObj legacyReply); - - /** - * Given a command reply object and an associated metadata object, - * attempts to construct a legacy command object. - */ - StatusWith<BSONObj> downconvertReplyMetadata(BSONObj commandReply, BSONObj replyMetadata); - - /** - * A function type for writing request metadata. The function takes a pointer to a - * BSONObjBuilder used to construct the metadata object and returns a Status indicating - * if the metadata was written successfully. - */ - using RequestMetadataWriter = stdx::function<Status(BSONObjBuilder*)>; - - /** - * A function type for reading reply metadata. The function takes a a reference to a - * metadata object received in a command reply and a string containing the server address of the - * host that executed the command and returns a Status indicating if the - * metadata was read successfully. - * - * TODO: would it be a layering violation if this hook took an OperationContext* ? - */ - using ReplyMetadataReader = stdx::function<Status(const BSONObj&, StringData)>; +/** + * Returns an empty metadata object. + */ +BSONObj makeEmptyMetadata(); + +/** + * Reads metadata from a metadata object and sets it on this OperationContext. + */ +Status readRequestMetadata(OperationContext* txn, const BSONObj& metadataObj); + +/** + * Writes metadata from an OperationContext to a metadata object. + */ +Status writeRequestMetadata(OperationContext* txn, BSONObjBuilder* metadataBob); + +/** + * 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> upconvertRequestMetadata(BSONObj legacyCmdObj, int queryFlags); + +/** + * Given a command object and a metadata object, attempts to construct a legacy command + * object and query flags bitfield augmented with the given metadata. + */ +StatusWith<LegacyCommandAndFlags> downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata); + +/** + * A command reply and associated metadata object. + */ +using CommandReplyWithMetadata = std::tuple<BSONObj, BSONObj>; + +/** + * Given a legacy command reply, attempts to strip the metadata from the reply and construct + * a metadata object. + */ +StatusWith<CommandReplyWithMetadata> upconvertReplyMetadata(BSONObj legacyReply); + +/** + * Given a command reply object and an associated metadata object, + * attempts to construct a legacy command object. + */ +StatusWith<BSONObj> downconvertReplyMetadata(BSONObj commandReply, BSONObj replyMetadata); + +/** + * A function type for writing request metadata. The function takes a pointer to a + * BSONObjBuilder used to construct the metadata object and returns a Status indicating + * if the metadata was written successfully. + */ +using RequestMetadataWriter = stdx::function<Status(BSONObjBuilder*)>; + +/** + * A function type for reading reply metadata. The function takes a a reference to a + * metadata object received in a command reply and a string containing the server address of the + * host that executed the command and returns a Status indicating if the + * metadata was read successfully. + * + * TODO: would it be a layering violation if this hook took an OperationContext* ? + */ +using ReplyMetadataReader = stdx::function<Status(const BSONObj&, StringData)>; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata/audit_metadata.cpp b/src/mongo/rpc/metadata/audit_metadata.cpp index 4b17101147f..7d064dec466 100644 --- a/src/mongo/rpc/metadata/audit_metadata.cpp +++ b/src/mongo/rpc/metadata/audit_metadata.cpp @@ -40,50 +40,47 @@ namespace mongo { namespace rpc { - const OperationContext::Decoration<AuditMetadata> AuditMetadata::get = - OperationContext::declareDecoration<AuditMetadata>(); +const OperationContext::Decoration<AuditMetadata> AuditMetadata::get = + OperationContext::declareDecoration<AuditMetadata>(); - AuditMetadata::AuditMetadata( - boost::optional<UsersAndRoles> impersonatedUsersAndRoles - ) - : _impersonatedUsersAndRoles(std::move(impersonatedUsersAndRoles)) - {} +AuditMetadata::AuditMetadata(boost::optional<UsersAndRoles> impersonatedUsersAndRoles) + : _impersonatedUsersAndRoles(std::move(impersonatedUsersAndRoles)) {} #if !defined(MONGO_ENTERPRISE_VERSION) - StatusWith<AuditMetadata> AuditMetadata::readFromMetadata(const BSONObj&) { - return AuditMetadata(boost::none); - } - - Status AuditMetadata::writeToMetadata(BSONObjBuilder*) const { - return Status::OK(); - } - - Status AuditMetadata::downconvert(const BSONObj& command, - const BSONObj&, - BSONObjBuilder* commandBob, - int*) { - commandBob->appendElements(command); - return Status::OK(); - } - - Status AuditMetadata::upconvert(const BSONObj& command, - const int, - BSONObjBuilder* commandBob, - BSONObjBuilder*) { - commandBob->appendElements(command); - return Status::OK(); - } +StatusWith<AuditMetadata> AuditMetadata::readFromMetadata(const BSONObj&) { + return AuditMetadata(boost::none); +} + +Status AuditMetadata::writeToMetadata(BSONObjBuilder*) const { + return Status::OK(); +} + +Status AuditMetadata::downconvert(const BSONObj& command, + const BSONObj&, + BSONObjBuilder* commandBob, + int*) { + commandBob->appendElements(command); + return Status::OK(); +} + +Status AuditMetadata::upconvert(const BSONObj& command, + const int, + BSONObjBuilder* commandBob, + BSONObjBuilder*) { + commandBob->appendElements(command); + return Status::OK(); +} #endif - const boost::optional<AuditMetadata::UsersAndRoles>& - AuditMetadata::getImpersonatedUsersAndRoles() const { - return _impersonatedUsersAndRoles; - } +const boost::optional<AuditMetadata::UsersAndRoles>& AuditMetadata::getImpersonatedUsersAndRoles() + const { + return _impersonatedUsersAndRoles; +} - const char kLegacyImpersonatedUsersFieldName[] = "impersonatedUsers"; - const char kLegacyImpersonatedRolesFieldName[] = "impersonatedRoles"; +const char kLegacyImpersonatedUsersFieldName[] = "impersonatedUsers"; +const char kLegacyImpersonatedRolesFieldName[] = "impersonatedRoles"; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata/audit_metadata.h b/src/mongo/rpc/metadata/audit_metadata.h index 70cc54d7482..9ad983ca30e 100644 --- a/src/mongo/rpc/metadata/audit_metadata.h +++ b/src/mongo/rpc/metadata/audit_metadata.h @@ -37,57 +37,57 @@ #include "mongo/db/auth/role_name.h" namespace mongo { - class BSONObj; - class BSONObjBuilder; - class Status; - template <typename T> class StatusWith; +class BSONObj; +class BSONObjBuilder; +class Status; +template <typename T> +class StatusWith; namespace rpc { - /** - * This class comprises the request metadata fields involving auditing. - */ - class AuditMetadata { - public: - static const OperationContext::Decoration<AuditMetadata> get; +/** + * This class comprises the request metadata fields involving auditing. + */ +class AuditMetadata { +public: + static const OperationContext::Decoration<AuditMetadata> get; - // Decorable requires a default constructor. - AuditMetadata() = default; + // Decorable requires a default constructor. + AuditMetadata() = default; - static StatusWith<AuditMetadata> readFromMetadata(const BSONObj& metadataObj); + static StatusWith<AuditMetadata> readFromMetadata(const BSONObj& metadataObj); - Status writeToMetadata(BSONObjBuilder* metadataBob) const; + Status writeToMetadata(BSONObjBuilder* metadataBob) const; - static Status downconvert(const BSONObj& command, - const BSONObj& metadata, - BSONObjBuilder* legacyCommandBob, - int* legacyQueryFlags); + static Status downconvert(const BSONObj& command, + const BSONObj& metadata, + BSONObjBuilder* legacyCommandBob, + int* legacyQueryFlags); - static Status upconvert(const BSONObj& legacyCommand, - const int legacyQueryFlags, - BSONObjBuilder* commandBob, - BSONObjBuilder* metadataBob); + static Status upconvert(const BSONObj& legacyCommand, + const int legacyQueryFlags, + BSONObjBuilder* commandBob, + BSONObjBuilder* metadataBob); - using UsersAndRoles = std::tuple<std::vector<UserName>, - std::vector<RoleName>>; + using UsersAndRoles = std::tuple<std::vector<UserName>, std::vector<RoleName>>; - const boost::optional<UsersAndRoles>& getImpersonatedUsersAndRoles() const; + const boost::optional<UsersAndRoles>& getImpersonatedUsersAndRoles() const; - AuditMetadata(boost::optional<UsersAndRoles> impersonatedUsersAndRoles); + AuditMetadata(boost::optional<UsersAndRoles> impersonatedUsersAndRoles); - private: - boost::optional<UsersAndRoles> _impersonatedUsersAndRoles; - }; +private: + boost::optional<UsersAndRoles> _impersonatedUsersAndRoles; +}; - /** - * The legacy field name used to hold impersonated users. - */ - extern const char kLegacyImpersonatedUsersFieldName[]; +/** + * The legacy field name used to hold impersonated users. + */ +extern const char kLegacyImpersonatedUsersFieldName[]; - /** - * The legacy field name used to hold impersonated roles. - */ - extern const char kLegacyImpersonatedRolesFieldName[]; +/** + * The legacy field name used to hold impersonated roles. + */ +extern const char kLegacyImpersonatedRolesFieldName[]; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata/server_selection_metadata.cpp b/src/mongo/rpc/metadata/server_selection_metadata.cpp index 4222c88e895..1441243a4e7 100644 --- a/src/mongo/rpc/metadata/server_selection_metadata.cpp +++ b/src/mongo/rpc/metadata/server_selection_metadata.cpp @@ -45,244 +45,220 @@ namespace rpc { namespace { - const char kSecondaryOkFieldName[] = "$secondaryOk"; - const char kReadPreferenceFieldName[] = "$readPreference"; - - const char kQueryOptionsFieldName[] = "$queryOptions"; - - const char kDollarQueryWrapper[] = "$query"; - const char kQueryWrapper[] = "query"; - - /** - * Utility to unwrap a '$query' or 'query' wrapped command object. The first element of the - * return value indicates whether the command was unwrapped, and the second element is either - * the unwrapped command (if it was wrapped), or the original command if it was not. - */ - std::tuple<bool, BSONObj> unwrapCommand(const BSONObj& maybeWrapped) { - const auto firstElFieldName = maybeWrapped.firstElementFieldName(); - if ((firstElFieldName == StringData(kDollarQueryWrapper)) || - (firstElFieldName == StringData(kQueryWrapper))) { - // TODO: do we need getOwned here? - return std::make_tuple(true, maybeWrapped.firstElement().embeddedObject()); - } - return std::make_tuple(false, maybeWrapped); - } +const char kSecondaryOkFieldName[] = "$secondaryOk"; +const char kReadPreferenceFieldName[] = "$readPreference"; - /** - * Reads a top-level $readPreference field from a wrapped command. - */ - Status extractWrappedReadPreference(const BSONObj& wrappedCommand, - BSONObjBuilder* metadataBob) { - BSONElement readPrefEl; - auto rpExtractStatus = bsonExtractTypedField(wrappedCommand, - kReadPreferenceFieldName, - mongo::Object, - &readPrefEl); - if (rpExtractStatus.isOK()) { - metadataBob->append(readPrefEl); - } - else if (rpExtractStatus != ErrorCodes::NoSuchKey) { - return rpExtractStatus; - } +const char kQueryOptionsFieldName[] = "$queryOptions"; - return Status::OK(); - } +const char kDollarQueryWrapper[] = "$query"; +const char kQueryWrapper[] = "query"; - /** - * Reads a $readPreference from a $queryOptions subobject, if it exists, and writes it to - * metadataBob. Writes out the original command excluding the $queryOptions subobject. - */ - Status extractUnwrappedReadPreference(const BSONObj& unwrappedCommand, - BSONObjBuilder* commandBob, - BSONObjBuilder* metadataBob) { - BSONElement queryOptionsEl; - BSONElement readPrefEl; - - auto queryOptionsExtractStatus = bsonExtractTypedField(unwrappedCommand, - kQueryOptionsFieldName, - mongo::Object, - &queryOptionsEl); - - // If there is no queryOptions subobject, we write out the command and return. - if (queryOptionsExtractStatus == ErrorCodes::NoSuchKey) { - commandBob->appendElements(unwrappedCommand); - return Status::OK(); - } - else if (!queryOptionsExtractStatus.isOK()) { - return queryOptionsExtractStatus; - } - - // Write out the command excluding the $queryOptions field. - for (const auto& elem : unwrappedCommand) { - if (elem.fieldNameStringData() != kQueryOptionsFieldName) { - commandBob->append(elem); - } - } +/** + * Utility to unwrap a '$query' or 'query' wrapped command object. The first element of the + * return value indicates whether the command was unwrapped, and the second element is either + * the unwrapped command (if it was wrapped), or the original command if it was not. + */ +std::tuple<bool, BSONObj> unwrapCommand(const BSONObj& maybeWrapped) { + const auto firstElFieldName = maybeWrapped.firstElementFieldName(); + if ((firstElFieldName == StringData(kDollarQueryWrapper)) || + (firstElFieldName == StringData(kQueryWrapper))) { + // TODO: do we need getOwned here? + return std::make_tuple(true, maybeWrapped.firstElement().embeddedObject()); + } + return std::make_tuple(false, maybeWrapped); +} - auto rpExtractStatus = bsonExtractTypedField(queryOptionsEl.embeddedObject(), - kReadPreferenceFieldName, - mongo::Object, - &readPrefEl); +/** + * Reads a top-level $readPreference field from a wrapped command. + */ +Status extractWrappedReadPreference(const BSONObj& wrappedCommand, BSONObjBuilder* metadataBob) { + BSONElement readPrefEl; + auto rpExtractStatus = + bsonExtractTypedField(wrappedCommand, kReadPreferenceFieldName, mongo::Object, &readPrefEl); + if (rpExtractStatus.isOK()) { + metadataBob->append(readPrefEl); + } else if (rpExtractStatus != ErrorCodes::NoSuchKey) { + return rpExtractStatus; + } - // If there is a $queryOptions field, we expect there to be a $readPreference. - if (!rpExtractStatus.isOK()) { - return rpExtractStatus; - } + return Status::OK(); +} - metadataBob->append(readPrefEl); +/** + * Reads a $readPreference from a $queryOptions subobject, if it exists, and writes it to + * metadataBob. Writes out the original command excluding the $queryOptions subobject. + */ +Status extractUnwrappedReadPreference(const BSONObj& unwrappedCommand, + BSONObjBuilder* commandBob, + BSONObjBuilder* metadataBob) { + BSONElement queryOptionsEl; + BSONElement readPrefEl; + + auto queryOptionsExtractStatus = bsonExtractTypedField( + unwrappedCommand, kQueryOptionsFieldName, mongo::Object, &queryOptionsEl); + + // If there is no queryOptions subobject, we write out the command and return. + if (queryOptionsExtractStatus == ErrorCodes::NoSuchKey) { + commandBob->appendElements(unwrappedCommand); return Status::OK(); + } else if (!queryOptionsExtractStatus.isOK()) { + return queryOptionsExtractStatus; } -} // namespace - - const OperationContext::Decoration<ServerSelectionMetadata> ServerSelectionMetadata::get = - OperationContext::declareDecoration<ServerSelectionMetadata>(); + // Write out the command excluding the $queryOptions field. + for (const auto& elem : unwrappedCommand) { + if (elem.fieldNameStringData() != kQueryOptionsFieldName) { + commandBob->append(elem); + } + } - ServerSelectionMetadata::ServerSelectionMetadata( - bool secondaryOk, - boost::optional<ReadPreferenceSetting> readPreference - ) - : _secondaryOk(secondaryOk) - , _readPreference(std::move(readPreference)) - {} + auto rpExtractStatus = bsonExtractTypedField( + queryOptionsEl.embeddedObject(), kReadPreferenceFieldName, mongo::Object, &readPrefEl); - StatusWith<ServerSelectionMetadata> - ServerSelectionMetadata::readFromMetadata(const BSONObj& metadata) { - auto secondaryOkField = metadata.getField(kSecondaryOkFieldName); + // If there is a $queryOptions field, we expect there to be a $readPreference. + if (!rpExtractStatus.isOK()) { + return rpExtractStatus; + } - bool secondaryOk = !secondaryOkField.eoo(); + metadataBob->append(readPrefEl); + return Status::OK(); +} - boost::optional<ReadPreferenceSetting> readPreference; - BSONElement rpElem; - auto readPrefExtractStatus = bsonExtractTypedField(metadata, - kReadPreferenceFieldName, - mongo::Object, - &rpElem); +} // namespace - if (readPrefExtractStatus == ErrorCodes::NoSuchKey) { - // Do nothing, it's valid to have no ReadPreference - } - else if (!readPrefExtractStatus.isOK()) { - return readPrefExtractStatus; - } - else { - // We have a read preference in the metadata object. - auto parsedRps = ReadPreferenceSetting::fromBSON(rpElem.Obj()); - if (!parsedRps.isOK()) { - return parsedRps.getStatus(); - } - readPreference.emplace(std::move(parsedRps.getValue())); +const OperationContext::Decoration<ServerSelectionMetadata> ServerSelectionMetadata::get = + OperationContext::declareDecoration<ServerSelectionMetadata>(); + +ServerSelectionMetadata::ServerSelectionMetadata( + bool secondaryOk, boost::optional<ReadPreferenceSetting> readPreference) + : _secondaryOk(secondaryOk), _readPreference(std::move(readPreference)) {} + +StatusWith<ServerSelectionMetadata> ServerSelectionMetadata::readFromMetadata( + const BSONObj& metadata) { + auto secondaryOkField = metadata.getField(kSecondaryOkFieldName); + + bool secondaryOk = !secondaryOkField.eoo(); + + boost::optional<ReadPreferenceSetting> readPreference; + BSONElement rpElem; + auto readPrefExtractStatus = + bsonExtractTypedField(metadata, kReadPreferenceFieldName, mongo::Object, &rpElem); + + if (readPrefExtractStatus == ErrorCodes::NoSuchKey) { + // Do nothing, it's valid to have no ReadPreference + } else if (!readPrefExtractStatus.isOK()) { + return readPrefExtractStatus; + } else { + // We have a read preference in the metadata object. + auto parsedRps = ReadPreferenceSetting::fromBSON(rpElem.Obj()); + if (!parsedRps.isOK()) { + return parsedRps.getStatus(); } - - return ServerSelectionMetadata(secondaryOk, std::move(readPreference)); + readPreference.emplace(std::move(parsedRps.getValue())); } - Status ServerSelectionMetadata::writeToMetadata(BSONObjBuilder* metadataBob) const { - if (isSecondaryOk()) { - metadataBob->append(kSecondaryOkFieldName, 1); - } - - if (getReadPreference()) { - metadataBob->append(kReadPreferenceFieldName, getReadPreference()->toBSON()); - } + return ServerSelectionMetadata(secondaryOk, std::move(readPreference)); +} - return Status::OK(); +Status ServerSelectionMetadata::writeToMetadata(BSONObjBuilder* metadataBob) const { + if (isSecondaryOk()) { + metadataBob->append(kSecondaryOkFieldName, 1); } - Status ServerSelectionMetadata::downconvert(const BSONObj& command, - const BSONObj& metadata, - BSONObjBuilder* legacyCommand, - int* legacyQueryFlags) { + if (getReadPreference()) { + metadataBob->append(kReadPreferenceFieldName, getReadPreference()->toBSON()); + } - BSONElement secondaryOkElem = metadata.getField(kSecondaryOkFieldName); - BSONElement readPrefElem = metadata.getField(kReadPreferenceFieldName); + return Status::OK(); +} - if (!secondaryOkElem.eoo()) { - *legacyQueryFlags |= mongo::QueryOption_SlaveOk; - } - else { - *legacyQueryFlags &= ~mongo::QueryOption_SlaveOk; - } +Status ServerSelectionMetadata::downconvert(const BSONObj& command, + const BSONObj& metadata, + BSONObjBuilder* legacyCommand, + int* legacyQueryFlags) { + BSONElement secondaryOkElem = metadata.getField(kSecondaryOkFieldName); + BSONElement readPrefElem = metadata.getField(kReadPreferenceFieldName); - if (!readPrefElem.eoo()) { - // Use 'query' to wrap query, then append read preference. + if (!secondaryOkElem.eoo()) { + *legacyQueryFlags |= mongo::QueryOption_SlaveOk; + } else { + *legacyQueryFlags &= ~mongo::QueryOption_SlaveOk; + } - // NOTE(amidvidy): Oddly, the _isSecondaryQuery implementation in dbclient_rs does - // not unwrap the query properly - it only checks for 'query', and not - // '$query'. We should probably standardize on one - drivers use '$query', - // and the shell uses 'query'. See SERVER-18705 for details. + if (!readPrefElem.eoo()) { + // Use 'query' to wrap query, then append read preference. - // TODO: this may need to use the $queryOptions hack on mongos. - legacyCommand->append(kQueryWrapper, command); - legacyCommand->append(readPrefElem); - } - else { - legacyCommand->appendElements(command); - } + // NOTE(amidvidy): Oddly, the _isSecondaryQuery implementation in dbclient_rs does + // not unwrap the query properly - it only checks for 'query', and not + // '$query'. We should probably standardize on one - drivers use '$query', + // and the shell uses 'query'. See SERVER-18705 for details. - return Status::OK(); + // TODO: this may need to use the $queryOptions hack on mongos. + legacyCommand->append(kQueryWrapper, command); + legacyCommand->append(readPrefElem); + } else { + legacyCommand->appendElements(command); } - Status ServerSelectionMetadata::upconvert(const BSONObj& legacyCommand, - const int legacyQueryFlags, - BSONObjBuilder* commandBob, - BSONObjBuilder* metadataBob) { + return Status::OK(); +} - // The secondaryOK option is equivalent to the slaveOk bit being set on legacy commands. - if (legacyQueryFlags & QueryOption_SlaveOk) { - metadataBob->append(kSecondaryOkFieldName, 1); - } +Status ServerSelectionMetadata::upconvert(const BSONObj& legacyCommand, + const int legacyQueryFlags, + BSONObjBuilder* commandBob, + BSONObjBuilder* metadataBob) { + // The secondaryOK option is equivalent to the slaveOk bit being set on legacy commands. + if (legacyQueryFlags & QueryOption_SlaveOk) { + metadataBob->append(kSecondaryOkFieldName, 1); + } - // First we need to check if we have a wrapped command. That is, a command of the form - // {'$query': { 'commandName': 1, ...}, '$someOption': 5, ....}. Curiously, the field name - // of the wrapped query can be either '$query', or 'query'. - BSONObj maybeUnwrapped; - bool wasWrapped; - std::tie(wasWrapped, maybeUnwrapped) = unwrapCommand(legacyCommand); - - if (wasWrapped) { - // Check if legacyCommand has an invalid $maxTimeMS option. - // TODO: Move this check elsewhere when we handle upconverting/downconverting maxTimeMS. - if (legacyCommand.hasField("$maxTimeMS")) { - return Status(ErrorCodes::InvalidOptions, - "cannot use $maxTimeMS query option with " - "commands; use maxTimeMS command option " - "instead"); - } - - // If the command was wrapped, we can write out the upconverted command now, as there - // is nothing else we need to remove from it. - commandBob->appendElements(maybeUnwrapped); - - return extractWrappedReadPreference(legacyCommand, metadataBob); + // First we need to check if we have a wrapped command. That is, a command of the form + // {'$query': { 'commandName': 1, ...}, '$someOption': 5, ....}. Curiously, the field name + // of the wrapped query can be either '$query', or 'query'. + BSONObj maybeUnwrapped; + bool wasWrapped; + std::tie(wasWrapped, maybeUnwrapped) = unwrapCommand(legacyCommand); + + if (wasWrapped) { + // Check if legacyCommand has an invalid $maxTimeMS option. + // TODO: Move this check elsewhere when we handle upconverting/downconverting maxTimeMS. + if (legacyCommand.hasField("$maxTimeMS")) { + return Status(ErrorCodes::InvalidOptions, + "cannot use $maxTimeMS query option with " + "commands; use maxTimeMS command option " + "instead"); } - // If the command was not wrapped, we need to check for a readPreference sent by mongos - // on the $queryOptions field of the command. If it is set, we remove it from the - // upconverted command, so we need to pass the command builder along. - return extractUnwrappedReadPreference(maybeUnwrapped, commandBob, metadataBob); - } + // If the command was wrapped, we can write out the upconverted command now, as there + // is nothing else we need to remove from it. + commandBob->appendElements(maybeUnwrapped); - bool ServerSelectionMetadata::isSecondaryOk() const { - return _secondaryOk; + return extractWrappedReadPreference(legacyCommand, metadataBob); } - const boost::optional<ReadPreferenceSetting>& - ServerSelectionMetadata::getReadPreference() const { - return _readPreference; - } + // If the command was not wrapped, we need to check for a readPreference sent by mongos + // on the $queryOptions field of the command. If it is set, we remove it from the + // upconverted command, so we need to pass the command builder along. + return extractUnwrappedReadPreference(maybeUnwrapped, commandBob, metadataBob); +} + +bool ServerSelectionMetadata::isSecondaryOk() const { + return _secondaryOk; +} + +const boost::optional<ReadPreferenceSetting>& ServerSelectionMetadata::getReadPreference() const { + return _readPreference; +} #if defined(_MSC_VER) && _MSC_VER < 1900 - ServerSelectionMetadata::ServerSelectionMetadata(ServerSelectionMetadata&& ssm) - : _secondaryOk(ssm._secondaryOk) - , _readPreference(std::move(ssm._readPreference)) - {} - - ServerSelectionMetadata& ServerSelectionMetadata::operator=(ServerSelectionMetadata&& ssm) { - _secondaryOk = ssm._secondaryOk; - _readPreference = std::move(ssm._readPreference); - return *this; - } +ServerSelectionMetadata::ServerSelectionMetadata(ServerSelectionMetadata&& ssm) + : _secondaryOk(ssm._secondaryOk), _readPreference(std::move(ssm._readPreference)) {} + +ServerSelectionMetadata& ServerSelectionMetadata::operator=(ServerSelectionMetadata&& ssm) { + _secondaryOk = ssm._secondaryOk; + _readPreference = std::move(ssm._readPreference); + return *this; +} #endif } // rpc diff --git a/src/mongo/rpc/metadata/server_selection_metadata.h b/src/mongo/rpc/metadata/server_selection_metadata.h index 4bdc700f20b..1f345af9a19 100644 --- a/src/mongo/rpc/metadata/server_selection_metadata.h +++ b/src/mongo/rpc/metadata/server_selection_metadata.h @@ -35,82 +35,85 @@ #include "mongo/db/operation_context.h" namespace mongo { - class BSONObj; - class BSONObjBuilder; - class Status; - template <typename T> class StatusWith; +class BSONObj; +class BSONObjBuilder; +class Status; +template <typename T> +class StatusWith; namespace rpc { - /** - * This class comprises the request metadata fields that concern server selection, that is, - * the conditions on which servers can execute this operation. - */ - class ServerSelectionMetadata { - MONGO_DISALLOW_COPYING(ServerSelectionMetadata); - public: - static const OperationContext::Decoration<ServerSelectionMetadata> get; +/** + * This class comprises the request metadata fields that concern server selection, that is, + * the conditions on which servers can execute this operation. + */ +class ServerSelectionMetadata { + MONGO_DISALLOW_COPYING(ServerSelectionMetadata); - // TODO: Remove when StatusWith supports default-constructible types (SERVER-18007). - ServerSelectionMetadata() = default; +public: + static const OperationContext::Decoration<ServerSelectionMetadata> get; + + // TODO: Remove when StatusWith supports default-constructible types (SERVER-18007). + ServerSelectionMetadata() = default; #if defined(_MSC_VER) && _MSC_VER < 1900 - ServerSelectionMetadata(ServerSelectionMetadata&&); + ServerSelectionMetadata(ServerSelectionMetadata&&); - ServerSelectionMetadata& operator=(ServerSelectionMetadata&&); + ServerSelectionMetadata& operator=(ServerSelectionMetadata&&); #else - ServerSelectionMetadata(ServerSelectionMetadata&&) = default; + ServerSelectionMetadata(ServerSelectionMetadata&&) = default; - ServerSelectionMetadata& operator=(ServerSelectionMetadata&&) = default; + ServerSelectionMetadata& operator=(ServerSelectionMetadata&&) = default; #endif - /** - * Loads ServerSelectionMetadata from a metadata object. - */ - static StatusWith<ServerSelectionMetadata> readFromMetadata(const BSONObj& metadataObj); - - /** - * Writes this operation's ServerSelectionMetadata to a metadata object. - */ - Status writeToMetadata(BSONObjBuilder* metadataBob) const; - - /** - * Rewrites the ServerSelectionMetadata from the metadata object format to the legacy OP_QUERY - * format. In particular, if secondaryOk is set, this will set QueryOption_SlaveOk - * on the legacyQueryFlags. If a readPreference is set, the legacy command will be wrapped - * in a 'query' element and a top-level $readPreference field will be set on the command. - */ - static Status downconvert(const BSONObj& command, - const BSONObj& metadata, - BSONObjBuilder* legacyCommand, - int* legacyQueryFlags); - - /** - * Rewrites the ServerSelectionMetadata from the legacy OP_QUERY format to the metadata - * object format. - */ - static Status upconvert(const BSONObj& legacyCommand, - const int legacyQueryFlags, - BSONObjBuilder* commandBob, - BSONObjBuilder* metadataBob); - /** - * Returns true if this operation has been explicitly overridden to run on a secondary. - * This replaces previous usage of QueryOption_SlaveOk. - */ - bool isSecondaryOk() const; - - /** - * Returns the ReadPreference associated with this operation. See - * mongo/client/read_preference.h for further details. - */ - const boost::optional<ReadPreferenceSetting>& getReadPreference() const; - - ServerSelectionMetadata(bool secondaryOk, - boost::optional<ReadPreferenceSetting> readPreference); - private: - bool _secondaryOk{false}; - boost::optional<ReadPreferenceSetting> _readPreference{}; - }; + /** + * Loads ServerSelectionMetadata from a metadata object. + */ + static StatusWith<ServerSelectionMetadata> readFromMetadata(const BSONObj& metadataObj); + + /** + * Writes this operation's ServerSelectionMetadata to a metadata object. + */ + Status writeToMetadata(BSONObjBuilder* metadataBob) const; + + /** + * Rewrites the ServerSelectionMetadata from the metadata object format to the legacy OP_QUERY + * format. In particular, if secondaryOk is set, this will set QueryOption_SlaveOk + * on the legacyQueryFlags. If a readPreference is set, the legacy command will be wrapped + * in a 'query' element and a top-level $readPreference field will be set on the command. + */ + static Status downconvert(const BSONObj& command, + const BSONObj& metadata, + BSONObjBuilder* legacyCommand, + int* legacyQueryFlags); + + /** + * Rewrites the ServerSelectionMetadata from the legacy OP_QUERY format to the metadata + * object format. + */ + static Status upconvert(const BSONObj& legacyCommand, + const int legacyQueryFlags, + BSONObjBuilder* commandBob, + BSONObjBuilder* metadataBob); + /** + * Returns true if this operation has been explicitly overridden to run on a secondary. + * This replaces previous usage of QueryOption_SlaveOk. + */ + bool isSecondaryOk() const; + + /** + * Returns the ReadPreference associated with this operation. See + * mongo/client/read_preference.h for further details. + */ + const boost::optional<ReadPreferenceSetting>& getReadPreference() const; + + ServerSelectionMetadata(bool secondaryOk, + boost::optional<ReadPreferenceSetting> readPreference); + +private: + bool _secondaryOk{false}; + boost::optional<ReadPreferenceSetting> _readPreference{}; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata/server_selection_metadata_test.cpp b/src/mongo/rpc/metadata/server_selection_metadata_test.cpp index fe6de217b46..ae8d4057b45 100644 --- a/src/mongo/rpc/metadata/server_selection_metadata_test.cpp +++ b/src/mongo/rpc/metadata/server_selection_metadata_test.cpp @@ -38,138 +38,145 @@ #include "mongo/unittest/unittest.h" namespace { - using namespace mongo; - using namespace mongo::rpc; - using mongo::unittest::assertGet; - - ServerSelectionMetadata checkParse(const BSONObj& metadata) { - return assertGet(ServerSelectionMetadata::readFromMetadata(metadata)); +using namespace mongo; +using namespace mongo::rpc; +using mongo::unittest::assertGet; + +ServerSelectionMetadata checkParse(const BSONObj& metadata) { + return assertGet(ServerSelectionMetadata::readFromMetadata(metadata)); +} + +TEST(ServerSelectionMetadata, ReadFromMetadata) { + { + // Empty object - should work just fine. + auto ss = checkParse(BSONObj()); + ASSERT_FALSE(ss.isSecondaryOk()); + ASSERT_FALSE(ss.getReadPreference().is_initialized()); } - - TEST(ServerSelectionMetadata, ReadFromMetadata) { - { - // Empty object - should work just fine. - auto ss = checkParse(BSONObj()); - ASSERT_FALSE(ss.isSecondaryOk()); - ASSERT_FALSE(ss.getReadPreference().is_initialized()); - } - { - // Set secondaryOk but not readPreference. - auto ss = checkParse(BSON("$secondaryOk" << 1)); - ASSERT_TRUE(ss.isSecondaryOk()); - ASSERT_FALSE(ss.getReadPreference().is_initialized()); - } - { - // Set readPreference but not secondaryOk. - auto ss = checkParse(BSON("$readPreference" << - BSON("mode" << "primary"))); - ASSERT_FALSE(ss.isSecondaryOk()); - ASSERT_TRUE(ss.getReadPreference().is_initialized()); - ASSERT_TRUE(ss.getReadPreference()->pref == ReadPreference::PrimaryOnly); - } - { - // Set both. - auto ss = checkParse(BSON("$secondaryOk" << 1 << - "$readPreference" << - BSON("mode" << "secondaryPreferred"))); - ASSERT_TRUE(ss.isSecondaryOk()); - ASSERT_TRUE(ss.getReadPreference()->pref == ReadPreference::SecondaryPreferred); - } + { + // Set secondaryOk but not readPreference. + auto ss = checkParse(BSON("$secondaryOk" << 1)); + ASSERT_TRUE(ss.isSecondaryOk()); + ASSERT_FALSE(ss.getReadPreference().is_initialized()); } - - void checkUpconvert(const BSONObj& legacyCommand, - const int legacyQueryFlags, - const BSONObj& upconvertedCommand, - const BSONObj& upconvertedMetadata) { - BSONObjBuilder upconvertedCommandBob; - BSONObjBuilder upconvertedMetadataBob; - auto convertStatus = ServerSelectionMetadata::upconvert(legacyCommand, - legacyQueryFlags, - &upconvertedCommandBob, - &upconvertedMetadataBob); - ASSERT_OK(convertStatus); - // We don't care about the order of the fields in the metadata object - const auto sorted = [](const BSONObj& obj) { - BSONObjIteratorSorted iter(obj); - BSONObjBuilder bob; - while (iter.more()) { - bob.append(iter.next()); - } - return bob.obj(); - }; - - ASSERT_EQ(upconvertedCommand, upconvertedCommandBob.done()); - ASSERT_EQ(sorted(upconvertedMetadata), sorted(upconvertedMetadataBob.done())); + { + // Set readPreference but not secondaryOk. + auto ss = checkParse(BSON("$readPreference" << BSON("mode" + << "primary"))); + ASSERT_FALSE(ss.isSecondaryOk()); + ASSERT_TRUE(ss.getReadPreference().is_initialized()); + ASSERT_TRUE(ss.getReadPreference()->pref == ReadPreference::PrimaryOnly); } - - TEST(ServerSelectionMetadata, UpconvertValidMetadata) { - // Wrapped in $query, with readPref and slaveOk bit set. - checkUpconvert(BSON("$query" << BSON("ping" << 1) << - "$readPreference" << BSON("mode" << "secondary")), - mongo::QueryOption_SlaveOk, - BSON("ping" << 1), - BSON("$secondaryOk" << 1 << - "$readPreference" << BSON("mode" << "secondary"))); - - // Wrapped in 'query', with readPref. - checkUpconvert(BSON("query" << BSON("pong" << 1 << "foo" << "bar") << - "$readPreference" << BSON("mode" << "primary" << - "tags" << BSON("dc" << "ny"))), - 0, - BSON("pong" << 1 << "foo" << "bar"), - BSON("$readPreference" << BSON("mode" << "primary" << - "tags" << BSON("dc" << "ny")))); - // Unwrapped, no readPref, no slaveOk - checkUpconvert(BSON("ping" << 1), - 0, - BSON("ping" << 1), - BSONObj()); - - // Readpref wrapped in $queryOptions - checkUpconvert(BSON("pang" << "pong" << - "$queryOptions" << - BSON("$readPreference" << BSON("mode" << "nearest" << - "tags" << BSON("rack" << "city")))), - 0, - BSON("pang" << "pong"), - BSON("$readPreference" << BSON("mode" << "nearest" << - "tags" << BSON("rack" << "city")))); - } - - void checkUpconvertFails(const BSONObj& legacyCommand, ErrorCodes::Error error) { - BSONObjBuilder upconvertedCommandBob; - BSONObjBuilder upconvertedMetadataBob; - auto upconvertStatus = ServerSelectionMetadata::upconvert(legacyCommand, - 0, - &upconvertedCommandBob, - &upconvertedMetadataBob); - ASSERT_NOT_OK(upconvertStatus); - ASSERT_EQ(upconvertStatus.code(), error); - } - - TEST(ServerSelectionMetadata, UpconvertInvalidMetadata) { - // $readPreference not an object. - checkUpconvertFails(BSON("$query" << BSON("pang" << "pong") << - "$readPreference" << 2), - ErrorCodes::TypeMismatch); - - // has $maxTimeMS option - checkUpconvertFails(BSON("query" << BSON("foo" << "bar") << - "$maxTimeMS" << 200), - ErrorCodes::InvalidOptions); - checkUpconvertFails(BSON("$query" << BSON("foo" << "bar") << - "$maxTimeMS" << 200), - ErrorCodes::InvalidOptions); - - // has $queryOptions field, but invalid $readPreference - checkUpconvertFails(BSON("ping" << "pong" << - "$queryOptions" << BSON("$readPreference" << 1.2)), - ErrorCodes::TypeMismatch); - - // has $queryOptions field, but no $readPreference - checkUpconvertFails(BSON("ping" << "pong" << - "$queryOptions" << BSONObj()), - ErrorCodes::NoSuchKey); + { + // Set both. + auto ss = checkParse( + BSON("$secondaryOk" << 1 << "$readPreference" << BSON("mode" + << "secondaryPreferred"))); + ASSERT_TRUE(ss.isSecondaryOk()); + ASSERT_TRUE(ss.getReadPreference()->pref == ReadPreference::SecondaryPreferred); } +} + +void checkUpconvert(const BSONObj& legacyCommand, + const int legacyQueryFlags, + const BSONObj& upconvertedCommand, + const BSONObj& upconvertedMetadata) { + BSONObjBuilder upconvertedCommandBob; + BSONObjBuilder upconvertedMetadataBob; + auto convertStatus = ServerSelectionMetadata::upconvert( + legacyCommand, legacyQueryFlags, &upconvertedCommandBob, &upconvertedMetadataBob); + ASSERT_OK(convertStatus); + // We don't care about the order of the fields in the metadata object + const auto sorted = [](const BSONObj& obj) { + BSONObjIteratorSorted iter(obj); + BSONObjBuilder bob; + while (iter.more()) { + bob.append(iter.next()); + } + return bob.obj(); + }; + + ASSERT_EQ(upconvertedCommand, upconvertedCommandBob.done()); + ASSERT_EQ(sorted(upconvertedMetadata), sorted(upconvertedMetadataBob.done())); +} + +TEST(ServerSelectionMetadata, UpconvertValidMetadata) { + // Wrapped in $query, with readPref and slaveOk bit set. + checkUpconvert(BSON("$query" << BSON("ping" << 1) << "$readPreference" << BSON("mode" + << "secondary")), + mongo::QueryOption_SlaveOk, + BSON("ping" << 1), + BSON("$secondaryOk" << 1 << "$readPreference" << BSON("mode" + << "secondary"))); + + // Wrapped in 'query', with readPref. + checkUpconvert(BSON("query" << BSON("pong" << 1 << "foo" + << "bar") << "$readPreference" + << BSON("mode" + << "primary" + << "tags" << BSON("dc" + << "ny"))), + 0, + BSON("pong" << 1 << "foo" + << "bar"), + BSON("$readPreference" << BSON("mode" + << "primary" + << "tags" << BSON("dc" + << "ny")))); + // Unwrapped, no readPref, no slaveOk + checkUpconvert(BSON("ping" << 1), 0, BSON("ping" << 1), BSONObj()); + + // Readpref wrapped in $queryOptions + checkUpconvert(BSON("pang" + << "pong" + << "$queryOptions" + << BSON("$readPreference" << BSON("mode" + << "nearest" + << "tags" << BSON("rack" + << "city")))), + 0, + BSON("pang" + << "pong"), + BSON("$readPreference" << BSON("mode" + << "nearest" + << "tags" << BSON("rack" + << "city")))); +} + +void checkUpconvertFails(const BSONObj& legacyCommand, ErrorCodes::Error error) { + BSONObjBuilder upconvertedCommandBob; + BSONObjBuilder upconvertedMetadataBob; + auto upconvertStatus = ServerSelectionMetadata::upconvert( + legacyCommand, 0, &upconvertedCommandBob, &upconvertedMetadataBob); + ASSERT_NOT_OK(upconvertStatus); + ASSERT_EQ(upconvertStatus.code(), error); +} + +TEST(ServerSelectionMetadata, UpconvertInvalidMetadata) { + // $readPreference not an object. + checkUpconvertFails(BSON("$query" << BSON("pang" + << "pong") << "$readPreference" << 2), + ErrorCodes::TypeMismatch); + + // has $maxTimeMS option + checkUpconvertFails(BSON("query" << BSON("foo" + << "bar") << "$maxTimeMS" << 200), + ErrorCodes::InvalidOptions); + checkUpconvertFails(BSON("$query" << BSON("foo" + << "bar") << "$maxTimeMS" << 200), + ErrorCodes::InvalidOptions); + + // has $queryOptions field, but invalid $readPreference + checkUpconvertFails(BSON("ping" + << "pong" + << "$queryOptions" << BSON("$readPreference" << 1.2)), + ErrorCodes::TypeMismatch); + + // has $queryOptions field, but no $readPreference + checkUpconvertFails(BSON("ping" + << "pong" + << "$queryOptions" << BSONObj()), + ErrorCodes::NoSuchKey); +} } // namespace diff --git a/src/mongo/rpc/metadata/sharding_metadata.cpp b/src/mongo/rpc/metadata/sharding_metadata.cpp index e33a4e075bd..4fc3a2feae2 100644 --- a/src/mongo/rpc/metadata/sharding_metadata.cpp +++ b/src/mongo/rpc/metadata/sharding_metadata.cpp @@ -41,115 +41,105 @@ namespace rpc { namespace { - const char kGLEStatsFieldName[] = "$gleStats"; - const char kGLEStatsLastOpTimeFieldName[] = "lastOpTime"; - const char kGLEStatsElectionIdFieldName[] = "electionId"; +const char kGLEStatsFieldName[] = "$gleStats"; +const char kGLEStatsLastOpTimeFieldName[] = "lastOpTime"; +const char kGLEStatsElectionIdFieldName[] = "electionId"; } // namespace - StatusWith<ShardingMetadata> ShardingMetadata::readFromMetadata(const BSONObj& metadataObj) { - BSONElement smElem; - auto smExtractStatus = bsonExtractTypedField(metadataObj, - kGLEStatsFieldName, - mongo::Object, - &smElem); - if (!smExtractStatus.isOK()) { - return smExtractStatus; - } - - if (smElem.embeddedObject().nFields() != 2) { - return Status(ErrorCodes::InvalidOptions, - str::stream() << "The $gleStats object can only have 2 fields, but got " - << smElem.embeddedObject().toString()); - } - - BSONElement lastOpTimeElem; - auto lastOpTimeExtractStatus = bsonExtractTypedField(smElem.embeddedObject(), - kGLEStatsLastOpTimeFieldName, - mongo::bsonTimestamp, - &lastOpTimeElem); - if (!lastOpTimeExtractStatus.isOK()) { - return lastOpTimeExtractStatus; - } - - BSONElement lastElectionIdElem; - auto lastElectionIdExtractStatus = bsonExtractTypedField(smElem.embeddedObject(), - kGLEStatsElectionIdFieldName, - mongo::jstOID, - &lastElectionIdElem); - if (!lastElectionIdExtractStatus.isOK()) { - return lastElectionIdExtractStatus; - } - - return ShardingMetadata(lastOpTimeElem.timestamp(), lastElectionIdElem.OID()); +StatusWith<ShardingMetadata> ShardingMetadata::readFromMetadata(const BSONObj& metadataObj) { + BSONElement smElem; + auto smExtractStatus = + bsonExtractTypedField(metadataObj, kGLEStatsFieldName, mongo::Object, &smElem); + if (!smExtractStatus.isOK()) { + return smExtractStatus; } - Status ShardingMetadata::writeToMetadata(BSONObjBuilder* metadataBob) const { - BSONObjBuilder subobj(metadataBob->subobjStart(kGLEStatsFieldName)); - subobj.append(kGLEStatsLastOpTimeFieldName, getLastOpTime()); - subobj.append(kGLEStatsElectionIdFieldName, getLastElectionId()); - return Status::OK(); + if (smElem.embeddedObject().nFields() != 2) { + return Status(ErrorCodes::InvalidOptions, + str::stream() << "The $gleStats object can only have 2 fields, but got " + << smElem.embeddedObject().toString()); } - Status ShardingMetadata::downconvert(const BSONObj& commandReply, - const BSONObj& replyMetadata, - BSONObjBuilder* legacyCommandReplyBob) { - - legacyCommandReplyBob->appendElements(commandReply); + BSONElement lastOpTimeElem; + auto lastOpTimeExtractStatus = bsonExtractTypedField(smElem.embeddedObject(), + kGLEStatsLastOpTimeFieldName, + mongo::bsonTimestamp, + &lastOpTimeElem); + if (!lastOpTimeExtractStatus.isOK()) { + return lastOpTimeExtractStatus; + } - auto swShardingMetadata = readFromMetadata(replyMetadata); - if (swShardingMetadata.isOK()) { - // We can reuse the same logic to write the sharding metadata out to the legacy - // command as the element has the same format whether it is there or on the metadata - // object. - swShardingMetadata.getValue().writeToMetadata(legacyCommandReplyBob); - } - else if (swShardingMetadata.getStatus() == ErrorCodes::NoSuchKey) { - // It is valid to not have a $gleStats field. - } - else { - return swShardingMetadata.getStatus(); - } - return Status::OK(); + BSONElement lastElectionIdElem; + auto lastElectionIdExtractStatus = bsonExtractTypedField( + smElem.embeddedObject(), kGLEStatsElectionIdFieldName, mongo::jstOID, &lastElectionIdElem); + if (!lastElectionIdExtractStatus.isOK()) { + return lastElectionIdExtractStatus; } - Status ShardingMetadata::upconvert(const BSONObj& legacyCommand, - BSONObjBuilder* commandBob, - BSONObjBuilder* metadataBob) { - // We can reuse the same logic to read the sharding metadata out from the legacy command - // as it has the same format whether it is there or on the metadata object. - auto swShardingMetadata = readFromMetadata(legacyCommand); - if (swShardingMetadata.isOK()) { - swShardingMetadata.getValue().writeToMetadata(metadataBob); - - // Write out the command excluding the $gleStats subobject. - for (const auto& elem : legacyCommand) { - if (elem.fieldNameStringData() != StringData(kGLEStatsFieldName)) { - commandBob->append(elem); - } + return ShardingMetadata(lastOpTimeElem.timestamp(), lastElectionIdElem.OID()); +} + +Status ShardingMetadata::writeToMetadata(BSONObjBuilder* metadataBob) const { + BSONObjBuilder subobj(metadataBob->subobjStart(kGLEStatsFieldName)); + subobj.append(kGLEStatsLastOpTimeFieldName, getLastOpTime()); + subobj.append(kGLEStatsElectionIdFieldName, getLastElectionId()); + return Status::OK(); +} + +Status ShardingMetadata::downconvert(const BSONObj& commandReply, + const BSONObj& replyMetadata, + BSONObjBuilder* legacyCommandReplyBob) { + legacyCommandReplyBob->appendElements(commandReply); + + auto swShardingMetadata = readFromMetadata(replyMetadata); + if (swShardingMetadata.isOK()) { + // We can reuse the same logic to write the sharding metadata out to the legacy + // command as the element has the same format whether it is there or on the metadata + // object. + swShardingMetadata.getValue().writeToMetadata(legacyCommandReplyBob); + } else if (swShardingMetadata.getStatus() == ErrorCodes::NoSuchKey) { + // It is valid to not have a $gleStats field. + } else { + return swShardingMetadata.getStatus(); + } + return Status::OK(); +} + +Status ShardingMetadata::upconvert(const BSONObj& legacyCommand, + BSONObjBuilder* commandBob, + BSONObjBuilder* metadataBob) { + // We can reuse the same logic to read the sharding metadata out from the legacy command + // as it has the same format whether it is there or on the metadata object. + auto swShardingMetadata = readFromMetadata(legacyCommand); + if (swShardingMetadata.isOK()) { + swShardingMetadata.getValue().writeToMetadata(metadataBob); + + // Write out the command excluding the $gleStats subobject. + for (const auto& elem : legacyCommand) { + if (elem.fieldNameStringData() != StringData(kGLEStatsFieldName)) { + commandBob->append(elem); } - } else if (swShardingMetadata.getStatus() == ErrorCodes::NoSuchKey) { - // it is valid to not have a $gleStats field - commandBob->appendElements(legacyCommand); - } - else { - return swShardingMetadata.getStatus(); } - return Status::OK(); + } else if (swShardingMetadata.getStatus() == ErrorCodes::NoSuchKey) { + // it is valid to not have a $gleStats field + commandBob->appendElements(legacyCommand); + } else { + return swShardingMetadata.getStatus(); } + return Status::OK(); +} - ShardingMetadata::ShardingMetadata(Timestamp lastOpTime, OID lastElectionId) - : _lastOpTime(std::move(lastOpTime)) - , _lastElectionId(std::move(lastElectionId)) - {} +ShardingMetadata::ShardingMetadata(Timestamp lastOpTime, OID lastElectionId) + : _lastOpTime(std::move(lastOpTime)), _lastElectionId(std::move(lastElectionId)) {} - const Timestamp& ShardingMetadata::getLastOpTime() const { - return _lastOpTime; - } +const Timestamp& ShardingMetadata::getLastOpTime() const { + return _lastOpTime; +} - const OID& ShardingMetadata::getLastElectionId() const { - return _lastElectionId; - } +const OID& ShardingMetadata::getLastElectionId() const { + return _lastElectionId; +} } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata/sharding_metadata.h b/src/mongo/rpc/metadata/sharding_metadata.h index 00114624996..fba0fe8f992 100644 --- a/src/mongo/rpc/metadata/sharding_metadata.h +++ b/src/mongo/rpc/metadata/sharding_metadata.h @@ -29,62 +29,62 @@ #include "mongo/db/jsobj.h" namespace mongo { - class BSONObj; - class BSONObjBuilder; - class Status; - template <typename T> class StatusWith; +class BSONObj; +class BSONObjBuilder; +class Status; +template <typename T> +class StatusWith; namespace rpc { +/** + * This class compromises the reply metadata fields that concern sharding. MongoD attaches + * this information to a command reply, which MongoS uses to process getLastError. + */ +class ShardingMetadata { +public: /** - * This class compromises the reply metadata fields that concern sharding. MongoD attaches - * this information to a command reply, which MongoS uses to process getLastError. + * Reads ShardingMetadata from a metadata object. */ - class ShardingMetadata { - public: + static StatusWith<ShardingMetadata> readFromMetadata(const BSONObj& metadataObj); - /** - * Reads ShardingMetadata from a metadata object. - */ - static StatusWith<ShardingMetadata> readFromMetadata(const BSONObj& metadataObj); - - /** - * Writes ShardingMetadata to a metadata builder. - */ - Status writeToMetadata(BSONObjBuilder* metadataBob) const; + /** + * Writes ShardingMetadata to a metadata builder. + */ + Status writeToMetadata(BSONObjBuilder* metadataBob) const; - /** - * Rewrites the ShardingMetadata from the legacy OP_QUERY format to the metadata object - * format. - */ - static Status downconvert(const BSONObj& commandReply, - const BSONObj& replyMetadata, - BSONObjBuilder* legacyCommandReply); + /** + * Rewrites the ShardingMetadata from the legacy OP_QUERY format to the metadata object + * format. + */ + static Status downconvert(const BSONObj& commandReply, + const BSONObj& replyMetadata, + BSONObjBuilder* legacyCommandReply); - /** - * Rewrites the ShardingMetadata from the legacy OP_QUERY format to the metadata object - * format. - */ - static Status upconvert(const BSONObj& legacyCommandReply, - BSONObjBuilder* commandReplyBob, - BSONObjBuilder* metadataBob); + /** + * Rewrites the ShardingMetadata from the legacy OP_QUERY format to the metadata object + * format. + */ + static Status upconvert(const BSONObj& legacyCommandReply, + BSONObjBuilder* commandReplyBob, + BSONObjBuilder* metadataBob); - /** - * Gets the OpTime of the oplog entry of the last succssful write operation executed by the - * server that produced the metadata. - */ - const Timestamp& getLastOpTime() const; + /** + * Gets the OpTime of the oplog entry of the last succssful write operation executed by the + * server that produced the metadata. + */ + const Timestamp& getLastOpTime() const; - /** - * Gets the most recent election id observed by the server that produced the metadata. - */ - const OID& getLastElectionId() const; + /** + * Gets the most recent election id observed by the server that produced the metadata. + */ + const OID& getLastElectionId() const; - ShardingMetadata(Timestamp lastOpTime, OID lastElectionId); + ShardingMetadata(Timestamp lastOpTime, OID lastElectionId); - private: - Timestamp _lastOpTime; - OID _lastElectionId; - }; +private: + Timestamp _lastOpTime; + OID _lastElectionId; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/metadata/sharding_metadata_test.cpp b/src/mongo/rpc/metadata/sharding_metadata_test.cpp index 19df5a59bcc..739e41d7860 100644 --- a/src/mongo/rpc/metadata/sharding_metadata_test.cpp +++ b/src/mongo/rpc/metadata/sharding_metadata_test.cpp @@ -36,191 +36,156 @@ namespace { - using namespace mongo; - using namespace mongo::rpc; - using mongo::unittest::assertGet; - - ShardingMetadata checkParse(const BSONObj& metadata) { - return assertGet(ShardingMetadata::readFromMetadata(metadata)); +using namespace mongo; +using namespace mongo::rpc; +using mongo::unittest::assertGet; + +ShardingMetadata checkParse(const BSONObj& metadata) { + return assertGet(ShardingMetadata::readFromMetadata(metadata)); +} + +const auto kElectionId = OID{"541b1a00e8a23afa832b218e"}; +const auto kLastOpTime = Timestamp(stdx::chrono::seconds{1337}, 800u); + +TEST(ShardingMetadata, ReadFromMetadata) { + { + auto sm = checkParse( + BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" << kElectionId))); + ASSERT_EQ(sm.getLastElectionId(), kElectionId); + ASSERT_EQ(sm.getLastOpTime(), kLastOpTime); } + { + // We don't care about order. + auto sm = checkParse( + BSON("$gleStats" << BSON("electionId" << kElectionId << "lastOpTime" << kLastOpTime))); - const auto kElectionId = OID{"541b1a00e8a23afa832b218e"}; - const auto kLastOpTime = Timestamp(stdx::chrono::seconds{1337}, 800u); - - TEST(ShardingMetadata, ReadFromMetadata) { - { - auto sm = checkParse(BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId))); - ASSERT_EQ(sm.getLastElectionId(), kElectionId); - ASSERT_EQ(sm.getLastOpTime(), kLastOpTime); - } - { - // We don't care about order. - auto sm = checkParse(BSON("$gleStats" << BSON("electionId" << kElectionId << - "lastOpTime" << kLastOpTime))); - - ASSERT_EQ(sm.getLastElectionId(), kElectionId); - ASSERT_EQ(sm.getLastOpTime(), kLastOpTime); - } + ASSERT_EQ(sm.getLastElectionId(), kElectionId); + ASSERT_EQ(sm.getLastOpTime(), kLastOpTime); } - - void checkParseFails(const BSONObj& metadata, ErrorCodes::Error error) { - auto sm = ShardingMetadata::readFromMetadata(metadata); - ASSERT_NOT_OK(sm.getStatus()); - ASSERT_EQ(sm.getStatus(), error); +} + +void checkParseFails(const BSONObj& metadata, ErrorCodes::Error error) { + auto sm = ShardingMetadata::readFromMetadata(metadata); + ASSERT_NOT_OK(sm.getStatus()); + ASSERT_EQ(sm.getStatus(), error); +} + +TEST(ShardingMetadata, ReadFromInvalidMetadata) { + { checkParseFails(BSONObj(), ErrorCodes::NoSuchKey); } + { checkParseFails(BSON("$gleStats" << 1), ErrorCodes::TypeMismatch); } + { checkParseFails(BSON("$gleStats" << BSONObj()), ErrorCodes::InvalidOptions); } + { + checkParseFails(BSON("$gleStats" << BSON("lastOpTime" << 3 << "electionId" << kElectionId)), + ErrorCodes::TypeMismatch); + } + { + checkParseFails(BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" << 3)), + ErrorCodes::TypeMismatch); + } + { + checkParseFails( + BSON("$gleStats" << BSON("lastOpTime" << kElectionId << "electionId" << kLastOpTime)), + ErrorCodes::TypeMismatch); } + { + checkParseFails(BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" + << kElectionId << "extra" + << "this should not be here")), + ErrorCodes::InvalidOptions); + } +} + +void checkUpconvert(const BSONObj& legacyCommandReply, + const BSONObj& upconvertedCommandReply, + const BSONObj& upconvertedReplyMetadata) { + { + BSONObjBuilder commandReplyBob; + BSONObjBuilder metadataBob; + ASSERT_OK(ShardingMetadata::upconvert(legacyCommandReply, &commandReplyBob, &metadataBob)); + ASSERT_EQ(commandReplyBob.done(), upconvertedCommandReply); + ASSERT_EQ(metadataBob.done(), upconvertedReplyMetadata); + } +} - TEST(ShardingMetadata, ReadFromInvalidMetadata) { - { - checkParseFails(BSONObj(), - ErrorCodes::NoSuchKey); - } - { - checkParseFails(BSON("$gleStats" << 1), - ErrorCodes::TypeMismatch); +TEST(ShardingMetadata, UpconvertValidMetadata) { + { + checkUpconvert(BSON("ok" << 1), - } - { - checkParseFails(BSON("$gleStats" << BSONObj()), - ErrorCodes::InvalidOptions); - } - { - checkParseFails(BSON("$gleStats" << BSON("lastOpTime" << 3 << - "electionId" << kElectionId)), - ErrorCodes::TypeMismatch); - } - { - checkParseFails(BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << 3)), - ErrorCodes::TypeMismatch); - } - { - checkParseFails(BSON("$gleStats" << BSON("lastOpTime" << kElectionId << - "electionId" << kLastOpTime)), - ErrorCodes::TypeMismatch); - } - { - checkParseFails(BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId << - "extra" << "this should not be here")), - ErrorCodes::InvalidOptions); - } - } + BSON("ok" << 1), - void checkUpconvert(const BSONObj& legacyCommandReply, - const BSONObj& upconvertedCommandReply, - const BSONObj& upconvertedReplyMetadata) { - { - BSONObjBuilder commandReplyBob; - BSONObjBuilder metadataBob; - ASSERT_OK(ShardingMetadata::upconvert(legacyCommandReply, - &commandReplyBob, - &metadataBob)); - ASSERT_EQ(commandReplyBob.done(), upconvertedCommandReply); - ASSERT_EQ(metadataBob.done(), upconvertedReplyMetadata); - } + BSONObj()); } + { + checkUpconvert( + BSON("ok" << 1 << "$gleStats" + << BSON("lastOpTime" << kLastOpTime << "electionId" << kElectionId)), - TEST(ShardingMetadata, UpconvertValidMetadata) { - { - checkUpconvert(BSON("ok" << 1), - - BSON("ok" << 1), - - BSONObj()); - } - { - checkUpconvert(BSON("ok" << 1 << - "$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId)), - - BSON("ok" << 1), - - BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId))); - } - { - checkUpconvert(BSON("ok" << 1 << - "somestuff" << "some other stuff" << - "$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId) << - "morestuff" << "more other stuff"), - - BSON("ok" << 1 << - "somestuff" << "some other stuff" << - "morestuff" << "more other stuff"), - - BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId))); - } - } + BSON("ok" << 1), - void checkUpconvertFails(const BSONObj& legacyCommandReply, - ErrorCodes::Error why) { - BSONObjBuilder ignoredCommand; - BSONObjBuilder ignoredMetadata; - auto status = ShardingMetadata::upconvert(legacyCommandReply, - &ignoredCommand, - &ignoredMetadata); - ASSERT_NOT_OK(status); - ASSERT_EQ(status, why); + BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" << kElectionId))); } - - TEST(ShardingMetadata, UpconvertInvalidMetadata) { - { - checkUpconvertFails(BSON("ok" << 1 << - "$gleStats" << 1), - ErrorCodes::TypeMismatch); - } - { - checkUpconvertFails(BSON("ok" << 1 << - "$gleStats" << BSON("lastOpTime" << 1)), - ErrorCodes::InvalidOptions); - } - { - checkUpconvertFails(BSON("ok" << 1 << - "$gleStats" << BSON("lastOpTime" << 2 << - "foo" << 1)), - ErrorCodes::TypeMismatch); - } - { - checkUpconvertFails(BSON("ok" << 1 << - "$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId << - "krandom" << "shouldnotbehere")), - ErrorCodes::InvalidOptions); - } + { + checkUpconvert( + BSON("ok" << 1 << "somestuff" + << "some other stuff" + << "$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" + << kElectionId) << "morestuff" + << "more other stuff"), + + BSON("ok" << 1 << "somestuff" + << "some other stuff" + << "morestuff" + << "more other stuff"), + + BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" << kElectionId))); } - - void checkDownconvert(const BSONObj& commandReply, - const BSONObj& metadata, - const BSONObj& downconvertedCommand) { - BSONObjBuilder downconvertedCommandBob; - ASSERT_OK(ShardingMetadata::downconvert(commandReply, - metadata, - &downconvertedCommandBob)); - ASSERT_EQ(downconvertedCommandBob.done(), downconvertedCommand); +} + +void checkUpconvertFails(const BSONObj& legacyCommandReply, ErrorCodes::Error why) { + BSONObjBuilder ignoredCommand; + BSONObjBuilder ignoredMetadata; + auto status = + ShardingMetadata::upconvert(legacyCommandReply, &ignoredCommand, &ignoredMetadata); + ASSERT_NOT_OK(status); + ASSERT_EQ(status, why); +} + +TEST(ShardingMetadata, UpconvertInvalidMetadata) { + { checkUpconvertFails(BSON("ok" << 1 << "$gleStats" << 1), ErrorCodes::TypeMismatch); } + { + checkUpconvertFails(BSON("ok" << 1 << "$gleStats" << BSON("lastOpTime" << 1)), + ErrorCodes::InvalidOptions); } - - TEST(ShardingMetadata, Downconvert) { - { - checkDownconvert(BSON("ok" << 1), - BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId)), - BSON("ok" << 1 << - "$gleStats" << BSON("lastOpTime" << kLastOpTime << - "electionId" << kElectionId))); - } - { - checkDownconvert(BSON("ok" << 1), - BSONObj(), - BSON("ok" << 1)); - } + { + checkUpconvertFails(BSON("ok" << 1 << "$gleStats" << BSON("lastOpTime" << 2 << "foo" << 1)), + ErrorCodes::TypeMismatch); + } + { + checkUpconvertFails( + BSON("ok" << 1 << "$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" + << kElectionId << "krandom" + << "shouldnotbehere")), + ErrorCodes::InvalidOptions); } +} + +void checkDownconvert(const BSONObj& commandReply, + const BSONObj& metadata, + const BSONObj& downconvertedCommand) { + BSONObjBuilder downconvertedCommandBob; + ASSERT_OK(ShardingMetadata::downconvert(commandReply, metadata, &downconvertedCommandBob)); + ASSERT_EQ(downconvertedCommandBob.done(), downconvertedCommand); +} + +TEST(ShardingMetadata, Downconvert) { + { + checkDownconvert( + BSON("ok" << 1), + BSON("$gleStats" << BSON("lastOpTime" << kLastOpTime << "electionId" << kElectionId)), + BSON("ok" << 1 << "$gleStats" + << BSON("lastOpTime" << kLastOpTime << "electionId" << kElectionId))); + } + { checkDownconvert(BSON("ok" << 1), BSONObj(), BSON("ok" << 1)); } +} } // namespace - - - - - diff --git a/src/mongo/rpc/object_check.cpp b/src/mongo/rpc/object_check.cpp index 988a98ed31b..3338f074cbb 100644 --- a/src/mongo/rpc/object_check.cpp +++ b/src/mongo/rpc/object_check.cpp @@ -38,12 +38,12 @@ namespace mongo { - Status Validator<BSONObj>::validateLoad(const char* ptr, size_t length) { - return serverGlobalParams.objcheck ? validateBSON(ptr, length) : Status::OK(); - } +Status Validator<BSONObj>::validateLoad(const char* ptr, size_t length) { + return serverGlobalParams.objcheck ? validateBSON(ptr, length) : Status::OK(); +} - Status Validator<BSONObj>::validateStore(const BSONObj& toStore) { - return Status::OK(); - } +Status Validator<BSONObj>::validateStore(const BSONObj& toStore) { + return Status::OK(); +} } // namespace mongo diff --git a/src/mongo/rpc/object_check.h b/src/mongo/rpc/object_check.h index 9db3e6f0015..3b3d77012a4 100644 --- a/src/mongo/rpc/object_check.h +++ b/src/mongo/rpc/object_check.h @@ -32,16 +32,17 @@ // We do not use the rpc namespace here so we can specialize Validator. namespace mongo { - class BSONObj; - class Status; +class BSONObj; +class Status; - /** - * A validator for BSON objects. The implementation will validate the input object - * if validation is enabled, or return Status::OK() otherwise. - */ - template<> struct Validator<BSONObj> { - static Status validateLoad(const char* ptr, size_t length); - static Status validateStore(const BSONObj& toStore); - }; +/** + * A validator for BSON objects. The implementation will validate the input object + * if validation is enabled, or return Status::OK() otherwise. + */ +template <> +struct Validator<BSONObj> { + static Status validateLoad(const char* ptr, size_t length); + static Status validateStore(const BSONObj& toStore); +}; } // namespace mongo diff --git a/src/mongo/rpc/object_check_test.cpp b/src/mongo/rpc/object_check_test.cpp index f0deff53f1c..03adedd0983 100644 --- a/src/mongo/rpc/object_check_test.cpp +++ b/src/mongo/rpc/object_check_test.cpp @@ -39,49 +39,51 @@ namespace { - using namespace mongo; +using namespace mongo; - TEST(DataTypeValidated, BSONValidationEnabled) { +TEST(DataTypeValidated, BSONValidationEnabled) { + using std::swap; - using std::swap; + bool wasEnabled = serverGlobalParams.objcheck; + const auto setValidation = [&](bool enabled) { serverGlobalParams.objcheck = enabled; }; + ON_BLOCK_EXIT(setValidation, wasEnabled); - bool wasEnabled = serverGlobalParams.objcheck; - const auto setValidation = [&](bool enabled){ serverGlobalParams.objcheck = enabled; }; - ON_BLOCK_EXIT(setValidation, wasEnabled); + using std::begin; + using std::end; - using std::begin; - using std::end; - - BSONObj valid = BSON("baz" << "bar" << "garply" << BSON("foo" << "bar")); - char buf[1024] = { 0 }; - std::copy(valid.objdata(), valid.objdata() + valid.objsize(), begin(buf)); - { - Validated<BSONObj> v; - ConstDataRangeCursor cdrc(begin(buf), end(buf)); - ASSERT_OK(cdrc.readAndAdvance(&v)); - } - - { - // mess up the data - DataRangeCursor drc(begin(buf), end(buf)); - // skip past size so we don't trip any sanity checks. - drc.advance(4); // skip size - while (drc.writeAndAdvance(0xFF).isOK()) ; - } + BSONObj valid = BSON("baz" + << "bar" + << "garply" << BSON("foo" + << "bar")); + char buf[1024] = {0}; + std::copy(valid.objdata(), valid.objdata() + valid.objsize(), begin(buf)); + { + Validated<BSONObj> v; + ConstDataRangeCursor cdrc(begin(buf), end(buf)); + ASSERT_OK(cdrc.readAndAdvance(&v)); + } - { - Validated<BSONObj> v; - ConstDataRangeCursor cdrc(begin(buf), end(buf)); - ASSERT_NOT_OK(cdrc.readAndAdvance(&v)); - } + { + // mess up the data + DataRangeCursor drc(begin(buf), end(buf)); + // skip past size so we don't trip any sanity checks. + drc.advance(4); // skip size + while (drc.writeAndAdvance(0xFF).isOK()) + ; + } - { - // disable validation - setValidation(false); - Validated<BSONObj> v; - ConstDataRangeCursor cdrc(begin(buf), end(buf)); - ASSERT_OK(cdrc.readAndAdvance(&v)); - } + { + Validated<BSONObj> v; + ConstDataRangeCursor cdrc(begin(buf), end(buf)); + ASSERT_NOT_OK(cdrc.readAndAdvance(&v)); } + { + // disable validation + setValidation(false); + Validated<BSONObj> v; + ConstDataRangeCursor cdrc(begin(buf), end(buf)); + ASSERT_OK(cdrc.readAndAdvance(&v)); + } +} } diff --git a/src/mongo/rpc/protocol.cpp b/src/mongo/rpc/protocol.cpp index dae77abce3b..f50860b85fd 100644 --- a/src/mongo/rpc/protocol.cpp +++ b/src/mongo/rpc/protocol.cpp @@ -41,41 +41,36 @@ 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"; +/** + * 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; +StatusWith<Protocol> negotiate(ProtocolSet fst, ProtocolSet snd) { + using std::begin; + using std::end; - ProtocolSet common = fst & snd; + ProtocolSet common = fst & snd; - auto it = - std::find_if(begin(kPreferredProtos), end(kPreferredProtos), [common](Protocol p) { - return common & static_cast<ProtocolSet>(p); - }); + 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; + if (it == end(kPreferredProtos)) { + return Status(ErrorCodes::RPCProtocolNegotiationFailed, "No common protocol found."); } + return *it; +} - StatusWith<StringData> toString(ProtocolSet protocols) { - switch (protocols) { +StatusWith<StringData> toString(ProtocolSet protocols) { + switch (protocols) { case supports::kNone: return StringData(kNone); case supports::kOpQueryOnly: @@ -90,28 +85,25 @@ namespace { << " 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."); +} + +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 index bb77caf5e85..99e6cacc92c 100644 --- a/src/mongo/rpc/protocol.h +++ b/src/mongo/rpc/protocol.h @@ -37,63 +37,63 @@ 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 { + /** - * 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. + * The pre-3.2 OP_QUERY on db.$cmd protocol */ - 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, - }; + kOpQuery = 1 << 0, /** - * Bitfield representing a set of supported RPC protocols. + * The post-3.2 OP_COMMAND protocol. */ - using ProtocolSet = std::underlying_type<Protocol>::type; + 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; +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); +/** + * 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); +/** + * 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); +/** + * 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 index 86211717eb7..71ff1559fda 100644 --- a/src/mongo/rpc/protocol_test.cpp +++ b/src/mongo/rpc/protocol_test.cpp @@ -34,33 +34,33 @@ namespace { - using namespace mongo::rpc; +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); - }; +// 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); - } +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); - }; +// 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); - } +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 index 100d63e2261..0bf5170d6ee 100644 --- a/src/mongo/rpc/reply_builder_interface.cpp +++ b/src/mongo/rpc/reply_builder_interface.cpp @@ -40,48 +40,47 @@ 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(); - } +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); } - ReplyBuilderInterface& - ReplyBuilderInterface::setCommandReply(StatusWith<BSONObj> commandReply) { - auto reply = commandReply.isOK() ? std::move(commandReply.getValue()) : BSONObj(); - return setRawCommandReply(augmentReplyWithStatus(commandReply.getStatus(), reply)); + if (status.isOK()) { + return bob.obj(); } - ReplyBuilderInterface& - ReplyBuilderInterface::setCommandReply(Status nonOKStatus, BSONObj extraErrorInfo) { - invariant(!nonOKStatus.isOK()); - return setRawCommandReply(augmentReplyWithStatus(nonOKStatus, extraErrorInfo)); + 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 index 7a36ab8b622..e97e3e7fe9b 100644 --- a/src/mongo/rpc/reply_builder_interface.h +++ b/src/mongo/rpc/reply_builder_interface.h @@ -35,110 +35,105 @@ #include "mongo/rpc/protocol.h" namespace mongo { - class BSONObj; - class Message; +class BSONObj; +class Message; namespace rpc { - class DocumentRange; +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; + + /** + * Gets the protocol used to serialize this reply. This should be used for validity checks + * only - runtime behavior changes should be implemented with polymorphism. + */ + virtual Protocol getProtocol() const = 0; + + /** + * Resets the state of the builder to kMetadata and clears any data that was previously + * written. + */ + virtual void reset() = 0; /** - * Constructs an RPC Reply. + * Returns available space in bytes, should be used to verify that the message have enough + * space for ouput documents. */ - 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; - - /** - * Gets the protocol used to serialize this reply. This should be used for validity checks - * only - runtime behavior changes should be implemented with polymorphism. - */ - virtual Protocol getProtocol() const = 0; - - /** - * Resets the state of the builder to kMetadata and clears any data that was previously - * written. - */ - virtual void reset() = 0; - - /** - * Returns available space in bytes, should be used to verify that the message have enough - * space for ouput documents. - */ - virtual std::size_t availableSpaceForOutputDocs() 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; - }; + virtual std::size_t availableSpaceForOutputDocs() 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_builder_test.cpp b/src/mongo/rpc/reply_builder_test.cpp index 906f51f4bc0..218e8b24caa 100644 --- a/src/mongo/rpc/reply_builder_test.cpp +++ b/src/mongo/rpc/reply_builder_test.cpp @@ -38,232 +38,226 @@ namespace { - using namespace mongo; +using namespace mongo; - static void _testMaxCommandReply(rpc::ReplyBuilderInterface& replyBuilder); - - TEST(CommandReplyBuilder, RoundTrip) { +static void _testMaxCommandReply(rpc::ReplyBuilderInterface& replyBuilder); - BSONObjBuilder metadataBob{}; - metadataBob.append("foo", "bar"); - auto metadata = metadataBob.done(); +TEST(CommandReplyBuilder, RoundTrip) { + BSONObjBuilder metadataBob{}; + metadataBob.append("foo", "bar"); + auto metadata = metadataBob.done(); - BSONObjBuilder commandReplyBob{}; - commandReplyBob.append("bar", "baz").append("ok", 1.0); - auto commandReply = commandReplyBob.done(); + BSONObjBuilder commandReplyBob{}; + commandReplyBob.append("bar", "baz").append("ok", 1.0); + auto commandReply = commandReplyBob.done(); - BSONObjBuilder outputDoc1Bob{}; - outputDoc1Bob.append("z", "t"); - auto outputDoc1 = outputDoc1Bob.done(); + BSONObjBuilder outputDoc1Bob{}; + outputDoc1Bob.append("z", "t"); + auto outputDoc1 = outputDoc1Bob.done(); - BSONObjBuilder outputDoc2Bob{}; - outputDoc2Bob.append("h", "j"); - auto outputDoc2 = outputDoc2Bob.done(); + BSONObjBuilder outputDoc2Bob{}; + outputDoc2Bob.append("h", "j"); + auto outputDoc2 = outputDoc2Bob.done(); - BSONObjBuilder outputDoc3Bob{}; - outputDoc3Bob.append("g", "p"); - auto outputDoc3 = outputDoc3Bob.done(); + BSONObjBuilder outputDoc3Bob{}; + outputDoc3Bob.append("g", "p"); + auto outputDoc3 = outputDoc3Bob.done(); - BufBuilder outputDocs; - outputDoc1.appendSelfToBufBuilder(outputDocs); - outputDoc2.appendSelfToBufBuilder(outputDocs); - outputDoc3.appendSelfToBufBuilder(outputDocs); + BufBuilder outputDocs; + outputDoc1.appendSelfToBufBuilder(outputDocs); + outputDoc2.appendSelfToBufBuilder(outputDocs); + outputDoc3.appendSelfToBufBuilder(outputDocs); - rpc::DocumentRange outputDocRange{outputDocs.buf(), outputDocs.buf() + outputDocs.len()}; + rpc::DocumentRange outputDocRange{outputDocs.buf(), outputDocs.buf() + outputDocs.len()}; - rpc::CommandReplyBuilder r; + rpc::CommandReplyBuilder r; - auto msg = r.setMetadata(metadata) - .setCommandReply(commandReply) - .addOutputDocs(outputDocRange) - .done(); + auto msg = + r.setMetadata(metadata).setCommandReply(commandReply).addOutputDocs(outputDocRange).done(); - rpc::CommandReply parsed(msg.get()); + rpc::CommandReply parsed(msg.get()); - ASSERT_EQUALS(parsed.getMetadata(), metadata); - ASSERT_EQUALS(parsed.getCommandReply(), commandReply); - // need ostream overloads for ASSERT_EQUALS - ASSERT_TRUE(parsed.getOutputDocs() == outputDocRange); - } + ASSERT_EQUALS(parsed.getMetadata(), metadata); + ASSERT_EQUALS(parsed.getCommandReply(), commandReply); + // need ostream overloads for ASSERT_EQUALS + ASSERT_TRUE(parsed.getOutputDocs() == outputDocRange); +} - TEST(CommandReplyBuilder, MaxCommandReply) { - rpc::CommandReplyBuilder replyBuilder; - _testMaxCommandReply(replyBuilder); - } +TEST(CommandReplyBuilder, MaxCommandReply) { + rpc::CommandReplyBuilder replyBuilder; + _testMaxCommandReply(replyBuilder); +} - TEST(LegacyReplyBuilder, MaxCommandReply) { - rpc::LegacyReplyBuilder replyBuilder; - _testMaxCommandReply(replyBuilder); - } +TEST(LegacyReplyBuilder, MaxCommandReply) { + rpc::LegacyReplyBuilder replyBuilder; + _testMaxCommandReply(replyBuilder); +} - // verify current functionality - later will need to change - TEST(LegacyReplyBuilderSpaceTest, DocSize) { - rpc::LegacyReplyBuilder replyBuilder; - replyBuilder.setMetadata(BSONObj()).setCommandReply(BSONObj()); +// verify current functionality - later will need to change +TEST(LegacyReplyBuilderSpaceTest, DocSize) { + rpc::LegacyReplyBuilder replyBuilder; + replyBuilder.setMetadata(BSONObj()).setCommandReply(BSONObj()); - std::size_t spaceBefore = replyBuilder.availableSpaceForOutputDocs(); - ASSERT_EQUALS(spaceBefore, 0u); + std::size_t spaceBefore = replyBuilder.availableSpaceForOutputDocs(); + ASSERT_EQUALS(spaceBefore, 0u); - BSONObjBuilder docBuilder{}; - docBuilder.append("foo", "bar"); - auto doc = docBuilder.done(); + BSONObjBuilder docBuilder{}; + docBuilder.append("foo", "bar"); + auto doc = docBuilder.done(); - replyBuilder.addOutputDoc(doc); //no-op - std::size_t spaceAfter = replyBuilder.availableSpaceForOutputDocs(); - ASSERT_EQUALS(spaceAfter, 0u); + replyBuilder.addOutputDoc(doc); // no-op + std::size_t spaceAfter = replyBuilder.availableSpaceForOutputDocs(); + ASSERT_EQUALS(spaceAfter, 0u); +} + +class CommandReplyBuilderSpaceTest : public mongo::unittest::Test { +protected: + // compute an empty doc size to use in follow up tests for payload size computation + virtual void setUp() override { + BSONObjBuilder docBuilder1{}; + docBuilder1.append("x", ""); + auto emptyDoc = docBuilder1.done(); + emptyDocSize = emptyDoc.objsize(); } - class CommandReplyBuilderSpaceTest : public mongo::unittest::Test { - protected: + virtual void tearDown() override {} - // compute an empty doc size to use in follow up tests for payload size computation - virtual void setUp() override { - BSONObjBuilder docBuilder1{}; - docBuilder1.append("x", ""); - auto emptyDoc = docBuilder1.done(); - emptyDocSize = emptyDoc.objsize(); - } + std::size_t emptyDocSize = 0u; +}; - virtual void tearDown() override { - } +TEST_F(CommandReplyBuilderSpaceTest, DocSizeEq) { + rpc::CommandReplyBuilder replyBuilder; + replyBuilder.setMetadata(BSONObj()).setCommandReply(BSONObj()); - std::size_t emptyDocSize = 0u; - }; + std::size_t spaceBefore = replyBuilder.availableSpaceForOutputDocs(); - TEST_F(CommandReplyBuilderSpaceTest, DocSizeEq) { - rpc::CommandReplyBuilder replyBuilder; - replyBuilder.setMetadata(BSONObj()).setCommandReply(BSONObj()); + BSONObjBuilder docBuilder{}; + docBuilder.append("foo", "bar"); + auto doc = docBuilder.done(); + std::size_t docSize = doc.objsize(); - std::size_t spaceBefore = replyBuilder.availableSpaceForOutputDocs(); + replyBuilder.addOutputDoc(doc); + std::size_t spaceAfter = replyBuilder.availableSpaceForOutputDocs(); + ASSERT_EQUALS(spaceBefore - docSize, spaceAfter); +} +// multiple calls to addOutputDoc, no metadata +TEST_F(CommandReplyBuilderSpaceTest, MaxDocSize1) { + rpc::CommandReplyBuilder replyBuilder; + + replyBuilder.setMetadata(BSONObj()).setCommandReply(BSONObj()); + + std::size_t availSpace = replyBuilder.availableSpaceForOutputDocs(); + + while (availSpace > 0u) { + std::size_t payloadSz = + std::min(availSpace, static_cast<std::size_t>(mongo::BSONObjMaxUserSize)) - + emptyDocSize; BSONObjBuilder docBuilder{}; - docBuilder.append("foo", "bar"); + std::string payload = std::string(payloadSz, 'y'); + docBuilder.append("x", payload); auto doc = docBuilder.done(); - std::size_t docSize = doc.objsize(); - replyBuilder.addOutputDoc(doc); - std::size_t spaceAfter = replyBuilder.availableSpaceForOutputDocs(); - ASSERT_EQUALS(spaceBefore - docSize, spaceAfter); + availSpace = replyBuilder.availableSpaceForOutputDocs(); } + auto msg = replyBuilder.done(); + auto sizeUInt = static_cast<std::size_t>(msg->size()); - // multiple calls to addOutputDoc, no metadata - TEST_F(CommandReplyBuilderSpaceTest, MaxDocSize1) { - rpc::CommandReplyBuilder replyBuilder; - - replyBuilder.setMetadata(BSONObj()).setCommandReply(BSONObj()); - - std::size_t availSpace = replyBuilder.availableSpaceForOutputDocs(); - - while (availSpace > 0u) { - std::size_t payloadSz = - std::min(availSpace, static_cast<std::size_t>(mongo::BSONObjMaxUserSize)) - - emptyDocSize; - BSONObjBuilder docBuilder{}; - std::string payload = std::string(payloadSz, 'y' ); - docBuilder.append("x", payload); - auto doc = docBuilder.done(); - replyBuilder.addOutputDoc(doc); - availSpace = replyBuilder.availableSpaceForOutputDocs(); - } - auto msg = replyBuilder.done(); - auto sizeUInt = static_cast<std::size_t>(msg->size()); - - ASSERT_EQUALS(sizeUInt, mongo::MaxMessageSizeBytes); - } + ASSERT_EQUALS(sizeUInt, mongo::MaxMessageSizeBytes); +} - // multiple calls to addOutputDoc, some metadata - TEST_F(CommandReplyBuilderSpaceTest, MaxDocSize2) { - rpc::CommandReplyBuilder replyBuilder; - - BSONObjBuilder metadataBuilder{}; - metadataBuilder.append("foo", "bar"); - auto metadata = metadataBuilder.done(); - - BSONObjBuilder commandReplyBuilder{}; - commandReplyBuilder.append("oof", "rab"); - auto commandReply = commandReplyBuilder.done(); - - replyBuilder.setMetadata(metadata).setCommandReply(commandReply); - - std::size_t availSpace = replyBuilder.availableSpaceForOutputDocs(); - - while (availSpace > 0u) { - std::size_t payloadSz = - std::min(availSpace, static_cast<std::size_t>(mongo::BSONObjMaxUserSize)) - - emptyDocSize; - BSONObjBuilder docBuilder{}; - std::string payload = std::string(payloadSz, 'y' ); - docBuilder.append("x", payload); - auto doc = docBuilder.done(); - replyBuilder.addOutputDoc(doc); - availSpace = replyBuilder.availableSpaceForOutputDocs(); - } - auto msg = replyBuilder.done(); - auto sizeUInt = static_cast<std::size_t>(msg->size()); - - ASSERT_EQUALS(sizeUInt, mongo::MaxMessageSizeBytes); - } +// multiple calls to addOutputDoc, some metadata +TEST_F(CommandReplyBuilderSpaceTest, MaxDocSize2) { + rpc::CommandReplyBuilder replyBuilder; + BSONObjBuilder metadataBuilder{}; + metadataBuilder.append("foo", "bar"); + auto metadata = metadataBuilder.done(); - // single call to addOutputDocs - TEST_F(CommandReplyBuilderSpaceTest, MaxDocSize3) { - rpc::CommandReplyBuilder replyBuilder; + BSONObjBuilder commandReplyBuilder{}; + commandReplyBuilder.append("oof", "rab"); + auto commandReply = commandReplyBuilder.done(); - BSONObjBuilder metadataBuilder{}; - metadataBuilder.append("foo", "bar"); - auto metadata = metadataBuilder.done(); + replyBuilder.setMetadata(metadata).setCommandReply(commandReply); - BSONObjBuilder commandReplyBuilder{}; - commandReplyBuilder.append("oof", "rab"); - auto commandReply = commandReplyBuilder.done(); + std::size_t availSpace = replyBuilder.availableSpaceForOutputDocs(); - replyBuilder.setMetadata(metadata).setCommandReply(commandReply); + while (availSpace > 0u) { + std::size_t payloadSz = + std::min(availSpace, static_cast<std::size_t>(mongo::BSONObjMaxUserSize)) - + emptyDocSize; + BSONObjBuilder docBuilder{}; + std::string payload = std::string(payloadSz, 'y'); + docBuilder.append("x", payload); + auto doc = docBuilder.done(); + replyBuilder.addOutputDoc(doc); + availSpace = replyBuilder.availableSpaceForOutputDocs(); + } + auto msg = replyBuilder.done(); + auto sizeUInt = static_cast<std::size_t>(msg->size()); - std::size_t availSpace = replyBuilder.availableSpaceForOutputDocs(); + ASSERT_EQUALS(sizeUInt, mongo::MaxMessageSizeBytes); +} - BufBuilder docs; - while (availSpace > 0u) { - std::size_t payloadSz = - std::min(availSpace, static_cast<std::size_t>(mongo::BSONObjMaxUserSize)) - - emptyDocSize; - BSONObjBuilder docBuilder{}; - std::string payload = std::string(payloadSz, 'y' ); - docBuilder.append("x", payload); - auto doc = docBuilder.done(); - availSpace -= doc.objsize(); - doc.appendSelfToBufBuilder(docs); - } - rpc::DocumentRange docRange{docs.buf(), docs.buf() + docs.len()}; - replyBuilder.addOutputDocs(docRange); - auto msg = replyBuilder.done(); - auto sizeUInt = static_cast<std::size_t>(msg->size()); +// single call to addOutputDocs +TEST_F(CommandReplyBuilderSpaceTest, MaxDocSize3) { + rpc::CommandReplyBuilder replyBuilder; - ASSERT_EQUALS(sizeUInt, mongo::MaxMessageSizeBytes); - } + BSONObjBuilder metadataBuilder{}; + metadataBuilder.append("foo", "bar"); + auto metadata = metadataBuilder.done(); - // call to addCommandReply - void _testMaxCommandReply(rpc::ReplyBuilderInterface& replyBuilder) { - BSONObjBuilder docBuilder1{}; - docBuilder1.append("x", ""); - auto emptyDoc = docBuilder1.done(); - std::size_t emptyDocSize = emptyDoc.objsize(); + BSONObjBuilder commandReplyBuilder{}; + commandReplyBuilder.append("oof", "rab"); + auto commandReply = commandReplyBuilder.done(); - BSONObjBuilder metadataBuilder{}; - metadataBuilder.append("foo", "bar"); - auto metadata = metadataBuilder.done(); - replyBuilder.setMetadata(metadata); + replyBuilder.setMetadata(metadata).setCommandReply(commandReply); - auto payloadSz = static_cast<std::size_t>(mongo::BSONObjMaxUserSize) - + std::size_t availSpace = replyBuilder.availableSpaceForOutputDocs(); + + BufBuilder docs; + while (availSpace > 0u) { + std::size_t payloadSz = + std::min(availSpace, static_cast<std::size_t>(mongo::BSONObjMaxUserSize)) - emptyDocSize; + BSONObjBuilder docBuilder{}; + std::string payload = std::string(payloadSz, 'y'); + docBuilder.append("x", payload); + auto doc = docBuilder.done(); + availSpace -= doc.objsize(); + doc.appendSelfToBufBuilder(docs); + } + rpc::DocumentRange docRange{docs.buf(), docs.buf() + docs.len()}; + replyBuilder.addOutputDocs(docRange); - BSONObjBuilder commandReplyBuilder{}; - std::string payload = std::string(payloadSz, 'y' ); - commandReplyBuilder.append("x", payload); - auto commandReply = commandReplyBuilder.done(); - ASSERT_EQUALS(commandReply.objsize(), mongo::BSONObjMaxUserSize); + auto msg = replyBuilder.done(); + auto sizeUInt = static_cast<std::size_t>(msg->size()); - replyBuilder.setCommandReply(commandReply); - } + ASSERT_EQUALS(sizeUInt, mongo::MaxMessageSizeBytes); +} + +// call to addCommandReply +void _testMaxCommandReply(rpc::ReplyBuilderInterface& replyBuilder) { + BSONObjBuilder docBuilder1{}; + docBuilder1.append("x", ""); + auto emptyDoc = docBuilder1.done(); + std::size_t emptyDocSize = emptyDoc.objsize(); + + BSONObjBuilder metadataBuilder{}; + metadataBuilder.append("foo", "bar"); + auto metadata = metadataBuilder.done(); + replyBuilder.setMetadata(metadata); + + auto payloadSz = static_cast<std::size_t>(mongo::BSONObjMaxUserSize) - emptyDocSize; + + BSONObjBuilder commandReplyBuilder{}; + std::string payload = std::string(payloadSz, 'y'); + commandReplyBuilder.append("x", payload); + auto commandReply = commandReplyBuilder.done(); + ASSERT_EQUALS(commandReply.objsize(), mongo::BSONObjMaxUserSize); + + replyBuilder.setCommandReply(commandReply); +} } // namespace diff --git a/src/mongo/rpc/reply_interface.h b/src/mongo/rpc/reply_interface.h index ac0514dc64f..7786c57cb26 100644 --- a/src/mongo/rpc/reply_interface.h +++ b/src/mongo/rpc/reply_interface.h @@ -32,52 +32,53 @@ #include "mongo/rpc/protocol.h" namespace mongo { - class BSONObj; - class Message; +class BSONObj; +class Message; namespace rpc { - class DocumentRange; +class DocumentRange; + +/** + * An immutable view of an RPC Reply. + */ +class ReplyInterface { + MONGO_DISALLOW_COPYING(ReplyInterface); + +public: + virtual ~ReplyInterface() = default; /** - * An immutable view of an RPC Reply. + * Accessor for the metadata object. Metadata is generally used for information + * that is independent of any specific command, e.g. auditing information. */ - 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; + virtual const BSONObj& getMetadata() const = 0; - /** - * The result of executing the command. - */ - virtual const BSONObj& getCommandReply() 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; + /** + * 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; - /** - * Gets the protocol used to deserialize this reply. This should be used for validity - * checks only - runtime behavior changes should be implemented with polymorphism. - */ - virtual Protocol getProtocol() const = 0; + /** + * Gets the protocol used to deserialize this reply. This should be used for validity + * checks only - runtime behavior changes should be implemented with polymorphism. + */ + virtual Protocol getProtocol() const = 0; - protected: - ReplyInterface() = default; - }; +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 index 7297bf393c5..f27c7e3e10d 100644 --- a/src/mongo/rpc/request_builder_interface.h +++ b/src/mongo/rpc/request_builder_interface.h @@ -34,92 +34,85 @@ #include "mongo/rpc/protocol.h" namespace mongo { - class Message; - class BSONObj; - class StringData; +class Message; +class BSONObj; +class StringData; namespace rpc { - class DocumentRange; +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; /** - * Constructs an RPC request. + * Sets the arguments to pass to the command. */ - 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; - - /** - * Gets the protocol used to serialize this request. This should only be used for asserts, - * and not for runtime behavior changes, which should be handled with polymorphism. - */ - virtual Protocol getProtocol() const = 0; - - /** - * Writes data then transfers ownership of the message to the caller. - * The behavior of calling any methods on the object is subsequently - * undefined. - */ - virtual std::unique_ptr<Message> done() = 0; - - protected: - RequestBuilderInterface() = default; - }; + 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; + + /** + * Gets the protocol used to serialize this request. This should only be used for asserts, + * and not for runtime behavior changes, which should be handled with polymorphism. + */ + virtual Protocol getProtocol() const = 0; + + /** + * Writes data then transfers ownership of the message to the caller. + * The behavior of calling any methods on the object is subsequently + * undefined. + */ + virtual 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 index 0933e728ab9..b5395012416 100644 --- a/src/mongo/rpc/request_interface.h +++ b/src/mongo/rpc/request_interface.h @@ -32,64 +32,65 @@ #include "mongo/rpc/protocol.h" namespace mongo { - class BSONObj; - class Message; - class StringData; +class BSONObj; +class Message; +class StringData; namespace rpc { - class DocumentRange; +class DocumentRange; + +/** + * An immutable view of an RPC message. + */ +class RequestInterface { + MONGO_DISALLOW_COPYING(RequestInterface); + +public: + virtual ~RequestInterface() = default; /** - * An immutable view of an RPC message. + * Gets the database that the command is to be executed on. */ - class RequestInterface { - MONGO_DISALLOW_COPYING(RequestInterface); - public: - virtual ~RequestInterface() = default; + virtual StringData getDatabase() const = 0; - /** - * 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 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 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; + /** + * 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; + /** + * 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; - /** - * Gets the RPC protocol used to deserialize this message. This should only be used for - * asserts, and not for runtime behavior changes, which should be handled with polymorphism. - */ - virtual Protocol getProtocol() const = 0; + /** + * Gets the RPC protocol used to deserialize this message. This should only be used for + * asserts, and not for runtime behavior changes, which should be handled with polymorphism. + */ + virtual Protocol getProtocol() const = 0; - protected: - RequestInterface() = default; - }; +protected: + RequestInterface() = default; +}; } // namespace rpc } // namespace mongo diff --git a/src/mongo/rpc/unique_message.h b/src/mongo/rpc/unique_message.h index 32799d41919..a763a82ec25 100644 --- a/src/mongo/rpc/unique_message.h +++ b/src/mongo/rpc/unique_message.h @@ -36,62 +36,59 @@ #include "mongo/rpc/request_interface.h" namespace mongo { - class Message; +class Message; namespace rpc { - /** - * A wrapper around an owned message that includes access to an associated - * ReplyInterface or RequestInterface. - */ - template <typename MessageViewType> - class UniqueMessage { - MONGO_DISALLOW_COPYING(UniqueMessage); - public: - UniqueMessage(std::unique_ptr<Message> message, std::unique_ptr<MessageViewType> view) - : _message{std::move(message)} - , _view{std::move(view)} - {} +/** + * A wrapper around an owned message that includes access to an associated + * ReplyInterface or RequestInterface. + */ +template <typename MessageViewType> +class UniqueMessage { + MONGO_DISALLOW_COPYING(UniqueMessage); + +public: + UniqueMessage(std::unique_ptr<Message> message, std::unique_ptr<MessageViewType> view) + : _message{std::move(message)}, _view{std::move(view)} {} #if defined(_MSC_VER) && _MSC_VER < 1900 - UniqueMessage(UniqueMessage&& other) - : _message{std::move(other._message)} - , _view{std::move(other._view)} - {} + UniqueMessage(UniqueMessage&& other) + : _message{std::move(other._message)}, _view{std::move(other._view)} {} - UniqueMessage& operator=(UniqueMessage&& other) { - _message = std::move(other._message); - _view = std::move(other._view); - return *this; - } + UniqueMessage& operator=(UniqueMessage&& other) { + _message = std::move(other._message); + _view = std::move(other._view); + return *this; + } #else - UniqueMessage(UniqueMessage&&) = default; - UniqueMessage& operator=(UniqueMessage&&) = default; + UniqueMessage(UniqueMessage&&) = default; + UniqueMessage& operator=(UniqueMessage&&) = default; #endif - const MessageViewType* operator->() const { - return _view.get(); - } + const MessageViewType* operator->() const { + return _view.get(); + } - const MessageViewType& operator*() const { - return *_view; - } + const MessageViewType& operator*() const { + return *_view; + } - /** - * Releases ownership of the underlying message. The result of calling any other methods - * on the object afterward is undefined. - */ - std::unique_ptr<Message> releaseMessage() { - _view.reset(); - return std::move(_message); - } + /** + * Releases ownership of the underlying message. The result of calling any other methods + * on the object afterward is undefined. + */ + std::unique_ptr<Message> releaseMessage() { + _view.reset(); + return std::move(_message); + } - private: - std::unique_ptr<Message> _message; - std::unique_ptr<MessageViewType> _view; - }; +private: + std::unique_ptr<Message> _message; + std::unique_ptr<MessageViewType> _view; +}; - using UniqueReply = UniqueMessage<ReplyInterface>; - using UniqueRequest = UniqueMessage<RequestInterface>; +using UniqueReply = UniqueMessage<ReplyInterface>; +using UniqueRequest = UniqueMessage<RequestInterface>; } // namespace rpc } // namespace mongo |