diff options
author | Mathias Stearn <mathias@10gen.com> | 2017-06-21 16:09:31 -0400 |
---|---|---|
committer | Mathias Stearn <mathias@10gen.com> | 2017-06-28 11:57:04 -0400 |
commit | 829e5b1628ce058509ada9e0ebaf00a712751193 (patch) | |
tree | 9b0916b29f43bdde27231e273a325959ff23e51a | |
parent | b70aab6e1f4c3230da27aa04cb6fa25f2341b9a6 (diff) | |
download | mongo-829e5b1628ce058509ada9e0ebaf00a712751193.tar.gz |
SERVER-28509 Downconvert document sequences when serializing requests to old protocols
-rw-r--r-- | src/mongo/db/dbmessage.h | 10 | ||||
-rw-r--r-- | src/mongo/rpc/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/rpc/command_request_builder.cpp | 7 | ||||
-rw-r--r-- | src/mongo/rpc/command_request_builder_test.cpp | 21 | ||||
-rw-r--r-- | src/mongo/rpc/legacy_request_builder.cpp | 14 | ||||
-rw-r--r-- | src/mongo/rpc/legacy_request_test.cpp | 104 |
6 files changed, 138 insertions, 19 deletions
diff --git a/src/mongo/db/dbmessage.h b/src/mongo/db/dbmessage.h index 959dc59edd0..2b7f4517c72 100644 --- a/src/mongo/db/dbmessage.h +++ b/src/mongo/db/dbmessage.h @@ -301,7 +301,7 @@ public: * parses the message into the above fields * Warning: constructor mutates DbMessage. */ - QueryMessage(DbMessage& d) { + explicit QueryMessage(DbMessage& d) { ns = d.getns(); ntoskip = d.pullInt(); ntoreturn = d.pullInt(); @@ -311,6 +311,14 @@ public: } queryOptions = DataView(d.msg().header().data()).read<LittleEndian<int32_t>>(); } + + /** + * A non-muting constructor from the whole message. + */ + explicit QueryMessage(const Message& message) { + DbMessage dbm(message); + *this = QueryMessage(dbm); + } }; /** diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript index 3f9cbf96eae..2db964ffe9f 100644 --- a/src/mongo/rpc/SConscript +++ b/src/mongo/rpc/SConscript @@ -176,6 +176,7 @@ env.CppUnitTest( 'command_reply_test.cpp', 'command_request_builder_test.cpp', 'command_request_test.cpp', + 'legacy_request_test.cpp', 'object_check_test.cpp', 'protocol_test.cpp', 'reply_builder_test.cpp', diff --git a/src/mongo/rpc/command_request_builder.cpp b/src/mongo/rpc/command_request_builder.cpp index 4a10cc7656f..9ab4e0ce2e4 100644 --- a/src/mongo/rpc/command_request_builder.cpp +++ b/src/mongo/rpc/command_request_builder.cpp @@ -54,8 +54,6 @@ bool fieldGoesInMetadata(StringData commandName, StringData field) { } // namespace Message opCommandRequestFromOpMsgRequest(const OpMsgRequest& request) { - invariant(request.sequences.empty()); // Not supported yet. - const auto commandName = request.getCommandName(); BufBuilder builder; @@ -88,6 +86,11 @@ Message opCommandRequestFromOpMsgRequest(const OpMsgRequest& request) { bodyBuilder.append(elem); } } + for (auto&& seq : request.sequences) { + invariant(seq.name.find('.') == std::string::npos); // Only support top-level for now. + dassert(!bodyBuilder.asTempObj().hasField(seq.name)); + bodyBuilder.append(seq.name, seq.objs); + } } metadataBuilder.obj().appendSelfToBufBuilder(builder); diff --git a/src/mongo/rpc/command_request_builder_test.cpp b/src/mongo/rpc/command_request_builder_test.cpp index a8b3c9af8f1..e635cc5910e 100644 --- a/src/mongo/rpc/command_request_builder_test.cpp +++ b/src/mongo/rpc/command_request_builder_test.cpp @@ -49,27 +49,20 @@ TEST(CommandRequestBuilder, RoundTrip) { commandArgsBob.append(commandName, "baz"); auto commandArgs = commandArgsBob.done(); - BSONObjBuilder inputDoc1Bob{}; - inputDoc1Bob.append("z", "t"); - auto inputDoc1 = inputDoc1Bob.done(); + auto request = OpMsgRequest::fromDBAndBody(databaseName, commandArgs, metadata); + request.sequences.push_back({"sequence", {BSON("a" << 1), BSON("b" << 2)}}); + auto msg = rpc::opCommandRequestFromOpMsgRequest(request); - BSONObjBuilder inputDoc2Bob{}; - inputDoc2Bob.append("h", "j"); - auto inputDoc2 = inputDoc2Bob.done(); - - BSONObjBuilder inputDoc3Bob{}; - inputDoc3Bob.append("g", "p"); - auto inputDoc3 = inputDoc3Bob.done(); - - auto msg = rpc::opCommandRequestFromOpMsgRequest( - OpMsgRequest::fromDBAndBody(databaseName, commandArgs, metadata)); + auto bodyAndSequence = BSONObjBuilder(commandArgs) + .append("sequence", BSON_ARRAY(BSON("a" << 1) << BSON("b" << 2))) + .obj(); auto parsed = mongo::rpc::ParsedOpCommand::parse(msg); ASSERT_EQUALS(parsed.database, databaseName); ASSERT_EQUALS(StringData(parsed.body.firstElementFieldName()), commandName); ASSERT_BSONOBJ_EQ(parsed.metadata, metadata); - ASSERT_BSONOBJ_EQ(parsed.body, commandArgs); + ASSERT_BSONOBJ_EQ(parsed.body, bodyAndSequence); } TEST(CommandRequestBuilder, DownconvertSecondaryReadPreferenceToSSM) { diff --git a/src/mongo/rpc/legacy_request_builder.cpp b/src/mongo/rpc/legacy_request_builder.cpp index f4a9252f921..80ace2b994e 100644 --- a/src/mongo/rpc/legacy_request_builder.cpp +++ b/src/mongo/rpc/legacy_request_builder.cpp @@ -45,12 +45,19 @@ namespace mongo { namespace rpc { namespace { +void mergeInDocumentSequences(const OpMsgRequest& request, BSONObjBuilder* body) { + for (auto&& seq : request.sequences) { + invariant(seq.name.find('.') == std::string::npos); // Only support top-level for now. + dassert(!body->asTempObj().hasField(seq.name)); + body->append(seq.name, seq.objs); + } +} + /** * Given a command request, attempts to construct a legacy command * object and query flags bitfield augmented with the given metadata. */ BSONObj downconvertRequestBody(const OpMsgRequest& request, int* queryOptions) { - invariant(request.sequences.empty()); // Not supported yet. *queryOptions = 0; if (auto readPref = request.body["$readPreference"]) { @@ -70,11 +77,14 @@ BSONObj downconvertRequestBody(const OpMsgRequest& request, int* queryOptions) { inner.append(field); } } + mergeInDocumentSequences(request, &inner); } outer.append(readPref); return outer.obj(); } else { - return request.body.removeField("$db"); // No additional downconversion needed. + BSONObjBuilder body(request.body.removeField("$db")); + mergeInDocumentSequences(request, &body); + return body.obj(); } } } // namespace diff --git a/src/mongo/rpc/legacy_request_test.cpp b/src/mongo/rpc/legacy_request_test.cpp new file mode 100644 index 00000000000..2e8cacf28a3 --- /dev/null +++ b/src/mongo/rpc/legacy_request_test.cpp @@ -0,0 +1,104 @@ +/** + * Copyright (C) 2017 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/client/dbclientinterface.h" +#include "mongo/db/dbmessage.h" +#include "mongo/db/jsobj.h" +#include "mongo/rpc/legacy_request.h" +#include "mongo/rpc/legacy_request_builder.h" +#include "mongo/unittest/unittest.h" + +namespace { + +using namespace mongo; + +TEST(LegacyRequest, RoundTrip) { + auto databaseName = "barbaz"; + auto commandName = "foobar"; + + BSONObjBuilder metadataBob{}; + metadataBob.append("$replData", BSONObj()); + auto metadata = metadataBob.done(); + + BSONObjBuilder commandArgsBob{}; + commandArgsBob.append(commandName, "baz"); + auto commandArgs = commandArgsBob.done(); + + auto request = OpMsgRequest::fromDBAndBody(databaseName, commandArgs, metadata); + request.sequences.push_back({"sequence", {BSON("a" << 1), BSON("b" << 2)}}); + auto msg = rpc::legacyRequestFromOpMsgRequest(request); + + auto metadataAndSequece = BSONObjBuilder(metadata) + .append("sequence", BSON_ARRAY(BSON("a" << 1) << BSON("b" << 2))) + .obj(); + + auto parsed = rpc::opMsgRequestFromLegacyRequest(msg); + ASSERT_BSONOBJ_EQ( + parsed.body, + OpMsgRequest::fromDBAndBody(databaseName, commandArgs, metadataAndSequece).body); +} + +TEST(LegacyRequestBuilder, DownconvertSecondaryReadPreference) { + auto readPref = BSON("mode" + << "secondary"); + auto msg = rpc::legacyRequestFromOpMsgRequest( + OpMsgRequest::fromDBAndBody("admin", BSON("ping" << 1 << "$readPreference" << readPref))); + auto parsed = QueryMessage(msg); + + ASSERT_EQ(parsed.ns, "admin.$cmd"_sd); + ASSERT_EQ(parsed.queryOptions, QueryOption_SlaveOk); + ASSERT_BSONOBJ_EQ(parsed.query, + fromjson("{$query: {ping: 1}, $readPreference : {mode: 'secondary'}}")); +} + +TEST(CommandRequestBuilder, DownconvertExplicitPrimaryReadPreference) { + auto readPref = BSON("mode" + << "primary"); + auto msg = rpc::legacyRequestFromOpMsgRequest( + OpMsgRequest::fromDBAndBody("admin", BSON("ping" << 1 << "$readPreference" << readPref))); + auto parsed = QueryMessage(msg); + + ASSERT_EQ(parsed.ns, "admin.$cmd"_sd); + ASSERT_EQ(parsed.queryOptions, 0); + ASSERT_BSONOBJ_EQ(parsed.query, + fromjson("{$query: {ping: 1}, $readPreference : {mode: 'primary'}}")); +} + +TEST(CommandRequestBuilder, DownconvertImplicitPrimaryReadPreference) { + auto msg = + rpc::legacyRequestFromOpMsgRequest(OpMsgRequest::fromDBAndBody("admin", BSON("ping" << 1))); + auto parsed = QueryMessage(msg); + + ASSERT_EQ(parsed.ns, "admin.$cmd"_sd); + ASSERT_EQ(parsed.queryOptions, 0); + ASSERT_BSONOBJ_EQ(parsed.query, fromjson("{ping: 1}")); +} + +} // namespace |