summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias Stearn <mathias@10gen.com>2017-06-21 16:09:31 -0400
committerMathias Stearn <mathias@10gen.com>2017-06-28 11:57:04 -0400
commit829e5b1628ce058509ada9e0ebaf00a712751193 (patch)
tree9b0916b29f43bdde27231e273a325959ff23e51a
parentb70aab6e1f4c3230da27aa04cb6fa25f2341b9a6 (diff)
downloadmongo-829e5b1628ce058509ada9e0ebaf00a712751193.tar.gz
SERVER-28509 Downconvert document sequences when serializing requests to old protocols
-rw-r--r--src/mongo/db/dbmessage.h10
-rw-r--r--src/mongo/rpc/SConscript1
-rw-r--r--src/mongo/rpc/command_request_builder.cpp7
-rw-r--r--src/mongo/rpc/command_request_builder_test.cpp21
-rw-r--r--src/mongo/rpc/legacy_request_builder.cpp14
-rw-r--r--src/mongo/rpc/legacy_request_test.cpp104
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