summaryrefslogtreecommitdiff
path: root/src/mongo/rpc
diff options
context:
space:
mode:
authorAdam Midvidy <amidvidy@gmail.com>2015-06-01 11:19:53 -0400
committerAdam Midvidy <amidvidy@gmail.com>2015-06-03 11:13:31 -0400
commitb9ed79089841c5c05086a490c1323514f342bb42 (patch)
treeedaf31999ac6c6266254e9a41e54674c4d9120a4 /src/mongo/rpc
parentf9685f7f8ed8240e763da406e3e97d94e7a919e6 (diff)
downloadmongo-b9ed79089841c5c05086a490c1323514f342bb42.tar.gz
SERVER-18236 hang slaveOk/secondaryOk and readPreference off OpCtx
- secondaryOk is upconverted/downconverted from QueryOption_SlaveOk - readPreference is upconverted/downconverted from $query wrapped commands or $queryOptions - both are now accessed via OperationContext instead of being read from a mutable command object - removed logic for parsing secondaryOk and readPreference from the command execution pipeline in dbcommands.cpp
Diffstat (limited to 'src/mongo/rpc')
-rw-r--r--src/mongo/rpc/SConscript19
-rw-r--r--src/mongo/rpc/legacy_request.cpp4
-rw-r--r--src/mongo/rpc/legacy_request_builder.cpp4
-rw-r--r--src/mongo/rpc/metadata.cpp55
-rw-r--r--src/mongo/rpc/metadata.h19
-rw-r--r--src/mongo/rpc/metadata/server_selection_metadata.cpp291
-rw-r--r--src/mongo/rpc/metadata/server_selection_metadata.h119
-rw-r--r--src/mongo/rpc/metadata/server_selection_metadata_test.cpp175
8 files changed, 660 insertions, 26 deletions
diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript
index 20279380881..e59e959f6ea 100644
--- a/src/mongo/rpc/SConscript
+++ b/src/mongo/rpc/SConscript
@@ -113,7 +113,24 @@ env.Library(
'metadata',
],
source=[
- 'metadata.cpp'
+ 'metadata.cpp',
+ 'metadata/server_selection_metadata.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/client/read_preference',
+ '$BUILD_DIR/mongo/util/decorable',
+ ],
+)
+
+env.CppUnitTest(
+ target=[
+ 'metadata_test',
+ ],
+ source=[
+ 'metadata/server_selection_metadata_test.cpp',
+ ],
+ LIBDEPS=[
+ 'metadata',
],
)
diff --git a/src/mongo/rpc/legacy_request.cpp b/src/mongo/rpc/legacy_request.cpp
index bb79928abb9..459bc5f3d83 100644
--- a/src/mongo/rpc/legacy_request.cpp
+++ b/src/mongo/rpc/legacy_request.cpp
@@ -45,8 +45,8 @@ namespace rpc {
, _database(NamespaceString(_queryMessage.ns).db().toString()) {
std::tie(_upconvertedCommandArgs, _upconvertedMetadata) = uassertStatusOK(
- metadata::upconvertRequest(std::move(_queryMessage.query),
- std::move(_queryMessage.queryOptions))
+ rpc::upconvertRequestMetadata(std::move(_queryMessage.query),
+ std::move(_queryMessage.queryOptions))
);
}
diff --git a/src/mongo/rpc/legacy_request_builder.cpp b/src/mongo/rpc/legacy_request_builder.cpp
index f1978583df8..6997bf739ea 100644
--- a/src/mongo/rpc/legacy_request_builder.cpp
+++ b/src/mongo/rpc/legacy_request_builder.cpp
@@ -80,8 +80,8 @@ namespace rpc {
int queryOptions;
std::tie(legacyCommandArgs, queryOptions) = uassertStatusOK(
- metadata::downconvertRequest(std::move(commandArgs),
- std::move(_metadata))
+ rpc::downconvertRequestMetadata(std::move(commandArgs),
+ std::move(_metadata))
);
_builder.appendNum(queryOptions); // queryOptions
diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp
index 055dfd07984..595ca571f16 100644
--- a/src/mongo/rpc/metadata.cpp
+++ b/src/mongo/rpc/metadata.cpp
@@ -32,38 +32,65 @@
#include "mongo/client/dbclientinterface.h"
#include "mongo/db/jsobj.h"
+#include "mongo/rpc/metadata/server_selection_metadata.h"
namespace mongo {
namespace rpc {
-namespace metadata {
- BSONObj empty() {
+ BSONObj makeEmptyMetadata() {
return BSONObj();
}
- const char kSecondaryOk[] = "$secondaryOk";
+ 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());
+
+ return Status::OK();
+ }
+
+ Status writeRequestMetadata(OperationContext* txn, BSONObjBuilder* metadataBob) {
+ auto ssStatus = ServerSelectionMetadata::writeToMetadata(
+ ServerSelectionMetadata::get(txn),
+ metadataBob
+ );
+ if (!ssStatus.isOK()) {
+ return ssStatus;
+ }
+ return Status::OK();
+ }
- StatusWith<CommandAndMetadata> upconvertRequest(BSONObj legacyCmdObj, int queryFlags) {
+ StatusWith<CommandAndMetadata> upconvertRequestMetadata(BSONObj legacyCmdObj, int queryFlags) {
+ BSONObjBuilder commandBob;
BSONObjBuilder metadataBob;
- // note second check may be erroneous: see SERVER-18194
- if ((queryFlags & QueryOption_SlaveOk)) {
- metadataBob.append(kSecondaryOk, 1);
+ auto upconvertStatus = ServerSelectionMetadata::upconvert(legacyCmdObj,
+ queryFlags,
+ &commandBob,
+ &metadataBob);
+ if (!upconvertStatus.isOK()) {
+ return upconvertStatus;
}
- return std::make_tuple(std::move(legacyCmdObj), std::move(metadataBob.obj()));
+ return std::make_tuple(commandBob.obj(), metadataBob.obj());
}
- StatusWith<LegacyCommandAndFlags> downconvertRequest(BSONObj cmdObj, BSONObj metadata) {
- int flags = 0;
+ StatusWith<LegacyCommandAndFlags> downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata) {
+ int legacyQueryFlags = 0;
+ BSONObjBuilder legacyCommandBob;
- if (metadata.hasField(kSecondaryOk)) {
- flags |= QueryOption_SlaveOk;
+ auto downconvertStatus = ServerSelectionMetadata::downconvert(cmdObj,
+ metadata,
+ &legacyCommandBob,
+ &legacyQueryFlags);
+ if (!downconvertStatus.isOK()) {
+ return downconvertStatus;
}
- return std::make_tuple(std::move(cmdObj), flags);
+ return std::make_tuple(legacyCommandBob.obj(), std::move(legacyQueryFlags));
}
-} // namespace metadata
} // namespace rpc
} // namespace mongo
diff --git a/src/mongo/rpc/metadata.h b/src/mongo/rpc/metadata.h
index 183dedffc5d..ca281066cb9 100644
--- a/src/mongo/rpc/metadata.h
+++ b/src/mongo/rpc/metadata.h
@@ -34,6 +34,8 @@
namespace mongo {
class BSONObj;
+ class BSONObjBuilder;
+ class OperationContext;
/**
* Utilities for converting metadata between the legacy OP_QUERY format and the new
@@ -53,17 +55,21 @@ namespace mongo {
* TODO: currently only $secondaryOk (request only) is handled. SERVER-18236 will cover the rest.
*/
namespace rpc {
-namespace metadata {
/**
* Returns an empty metadata object.
*/
- BSONObj empty();
+ BSONObj makeEmptyMetadata();
/**
- * The field name for the secondaryOk metadata field.
+ * Reads metadata from a metadata object and sets it on this OperationContext.
*/
- extern const char kSecondaryOk[];
+ 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.
@@ -80,14 +86,13 @@ namespace metadata {
* Given a legacy command object and a query flags bitfield, attempts to parse and remove
* the metadata from the command object and construct a corresponding metadata object.
*/
- StatusWith<CommandAndMetadata> upconvertRequest(BSONObj legacyCmdObj, int queryFlags);
+ 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> downconvertRequest(BSONObj cmdObj, BSONObj metadata);
+ StatusWith<LegacyCommandAndFlags> downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadata);
-} // namespace metadata
} // namespace rpc
} // namespace mongo
diff --git a/src/mongo/rpc/metadata/server_selection_metadata.cpp b/src/mongo/rpc/metadata/server_selection_metadata.cpp
new file mode 100644
index 00000000000..290fd83fb8d
--- /dev/null
+++ b/src/mongo/rpc/metadata/server_selection_metadata.cpp
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/rpc/metadata/server_selection_metadata.h"
+
+#include <utility>
+#include <tuple>
+
+#include "mongo/base/status_with.h"
+#include "mongo/bson/util/bson_extract.h"
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+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);
+ }
+
+ /**
+ * 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;
+ }
+
+ return Status::OK();
+ }
+
+ /**
+ * 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);
+ }
+ }
+
+ auto rpExtractStatus = bsonExtractTypedField(queryOptionsEl.embeddedObject(),
+ kReadPreferenceFieldName,
+ mongo::Object,
+ &readPrefEl);
+
+ // If there is a $queryOptions field, we expect there to be a $readPreference.
+ if (!rpExtractStatus.isOK()) {
+ return rpExtractStatus;
+ }
+
+ metadataBob->append(readPrefEl);
+ return Status::OK();
+ }
+
+} // namespace
+
+ 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);
+ auto readPrefField = metadata.getField(kReadPreferenceFieldName);
+
+ 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();
+ }
+ readPreference.emplace(std::move(parsedRps.getValue()));
+ }
+
+ return ServerSelectionMetadata(secondaryOk, std::move(readPreference));
+ }
+
+ Status ServerSelectionMetadata::writeToMetadata(const ServerSelectionMetadata& ss,
+ BSONObjBuilder* metadataBob) {
+ if (ss.isSecondaryOk()) {
+ metadataBob->append(kSecondaryOkFieldName, 1);
+ }
+
+ if (ss.getReadPreference()) {
+ metadataBob->append(kReadPreferenceFieldName, ss.getReadPreference()->toBSON());
+ }
+
+ return Status::OK();
+ }
+
+ Status ServerSelectionMetadata::downconvert(const BSONObj& command,
+ const BSONObj& metadata,
+ BSONObjBuilder* legacyCommand,
+ int* legacyQueryFlags) {
+
+ BSONElement secondaryOkElem = metadata.getField(kSecondaryOkFieldName);
+ BSONElement readPrefElem = metadata.getField(kReadPreferenceFieldName);
+
+ if (!secondaryOkElem.eoo()) {
+ *legacyQueryFlags |= mongo::QueryOption_SlaveOk;
+ }
+ else {
+ *legacyQueryFlags &= ~mongo::QueryOption_SlaveOk;
+ }
+
+ if (!readPrefElem.eoo()) {
+ // Use 'query' to wrap query, then append read preference.
+
+ // 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.
+
+ // TODO: this may need to use the $queryOptions hack on mongos.
+ legacyCommand->append(kQueryWrapper, command);
+ legacyCommand->append(readPrefElem);
+ }
+ else {
+ legacyCommand->appendElements(command);
+ }
+
+ return Status::OK();
+ }
+
+ 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);
+ }
+
+ // 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;
+ }
+#endif
+
+} // rpc
+} // mongo
diff --git a/src/mongo/rpc/metadata/server_selection_metadata.h b/src/mongo/rpc/metadata/server_selection_metadata.h
new file mode 100644
index 00000000000..93ba352f317
--- /dev/null
+++ b/src/mongo/rpc/metadata/server_selection_metadata.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+
+#include "mongo/base/disallow_copying.h"
+#include "mongo/client/read_preference.h"
+#include "mongo/db/operation_context.h"
+
+namespace mongo {
+ 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;
+
+ // TODO: Remove when StatusWith supports default-constructible types (SERVER-18007).
+ ServerSelectionMetadata() = default;
+
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ ServerSelectionMetadata(ServerSelectionMetadata&&);
+
+ ServerSelectionMetadata& operator=(ServerSelectionMetadata&&);
+#else
+ ServerSelectionMetadata(ServerSelectionMetadata&&) = default;
+
+ ServerSelectionMetadata& operator=(ServerSelectionMetadata&&) = default;
+#endif
+
+ /**
+ * Loads ServerSelectionMetadata from a metadata object and stores it on this
+ * OperationContext.
+ */
+ static StatusWith<ServerSelectionMetadata> readFromMetadata(const BSONObj& metadataObj);
+
+ /**
+ * Writes this operation's ServerSelectionMetadata to a metadata object.
+ */
+ static Status writeToMetadata(const ServerSelectionMetadata& ss,
+ BSONObjBuilder* metadataBob);
+
+ /**
+ * Rewrites the ServerSelectionMetadata from the legacy OP_QUERY format to the metadata
+ * object 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;
+
+ private:
+ ServerSelectionMetadata(bool secondaryOk,
+ boost::optional<ReadPreferenceSetting> readPreference);
+
+ 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
new file mode 100644
index 00000000000..a87b246359a
--- /dev/null
+++ b/src/mongo/rpc/metadata/server_selection_metadata_test.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 MongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include <utility>
+
+#include "mongo/base/status.h"
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/client/read_preference.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/rpc/metadata/server_selection_metadata.h"
+#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));
+ }
+
+ 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);
+ }
+ }
+
+ 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);
+ }
+
+}