summaryrefslogtreecommitdiff
path: root/src/mongo/rpc
diff options
context:
space:
mode:
authorMathias Stearn <mathias@10gen.com>2017-04-26 09:36:41 -0400
committerMathias Stearn <mathias@10gen.com>2017-05-12 12:08:30 -0400
commitf2902d59175c0724944ca98d13f784e2de944053 (patch)
tree44788ee704540d420f649279053019e3b6a332b2 /src/mongo/rpc
parent1a955fc356627b8cc74eb15506608dd987184608 (diff)
downloadmongo-f2902d59175c0724944ca98d13f784e2de944053.tar.gz
SERVER-28814 Replace ServerSelectionMetadata with just ReadPreferenceSetting
Diffstat (limited to 'src/mongo/rpc')
-rw-r--r--src/mongo/rpc/SConscript3
-rw-r--r--src/mongo/rpc/command_request.cpp15
-rw-r--r--src/mongo/rpc/command_request_builder.cpp11
-rw-r--r--src/mongo/rpc/metadata.cpp75
-rw-r--r--src/mongo/rpc/metadata/server_selection_metadata.cpp344
-rw-r--r--src/mongo/rpc/metadata/server_selection_metadata.h126
-rw-r--r--src/mongo/rpc/metadata/server_selection_metadata_test.cpp206
-rw-r--r--src/mongo/rpc/metadata_test.cpp143
8 files changed, 222 insertions, 701 deletions
diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript
index 3d7982e6a34..47e3af75eee 100644
--- a/src/mongo/rpc/SConscript
+++ b/src/mongo/rpc/SConscript
@@ -134,7 +134,6 @@ env.Clone().InjectModule("enterprise").Library(
'metadata/config_server_metadata.cpp',
'metadata/egress_metadata_hook_list.cpp',
'metadata/logical_time_metadata.cpp',
- 'metadata/server_selection_metadata.cpp',
'metadata/sharding_metadata.cpp',
'metadata/repl_set_metadata.cpp',
'metadata/oplog_query_metadata.cpp',
@@ -158,9 +157,9 @@ env.CppUnitTest(
'rpc_metadata_test',
],
source=[
+ 'metadata_test.cpp',
'metadata/egress_metadata_hook_list_test.cpp',
'metadata/logical_time_metadata_test.cpp',
- 'metadata/server_selection_metadata_test.cpp',
'metadata/sharding_metadata_test.cpp',
'metadata/tracking_metadata_test.cpp',
],
diff --git a/src/mongo/rpc/command_request.cpp b/src/mongo/rpc/command_request.cpp
index 08bb54e91fe..54cb58a4313 100644
--- a/src/mongo/rpc/command_request.cpp
+++ b/src/mongo/rpc/command_request.cpp
@@ -93,13 +93,24 @@ CommandRequest::CommandRequest(const Message* message) : _message(message) {
_commandArgs.firstElementFieldName() == _commandName);
// OP_COMMAND is only used when communicating with 3.4 nodes and they serialize their metadata
- // fields differently. We do all up- and down-conversion here so that the rest of the code only
- // has to deal with the current format.
+ // fields differently. We do all up-conversion here so that the rest of the code only has to
+ // deal with the current format.
uassertStatusOK(cur.readAndAdvance<>(&obj));
BSONObjBuilder metadataBuilder;
for (auto elem : obj.val) {
if (elem.fieldNameStringData() == "configsvr") {
metadataBuilder.appendAs(elem, "$configServerState");
+ } else if (elem.fieldNameStringData() == "$ssm") {
+ auto ssmObj = elem.Obj();
+ if (auto readPrefElem = ssmObj["$readPreference"]) {
+ // Promote the read preference to the top level.
+ metadataBuilder.append(readPrefElem);
+ } else if (ssmObj["$secondaryOk"].trueValue()) {
+ // Convert secondaryOk to equivalent read preference if none was explicitly
+ // provided.
+ ReadPreferenceSetting(ReadPreference::SecondaryPreferred)
+ .toContainingBSON(&metadataBuilder);
+ }
} else {
metadataBuilder.append(elem);
}
diff --git a/src/mongo/rpc/command_request_builder.cpp b/src/mongo/rpc/command_request_builder.cpp
index 34f6daa0888..9b784db1841 100644
--- a/src/mongo/rpc/command_request_builder.cpp
+++ b/src/mongo/rpc/command_request_builder.cpp
@@ -32,6 +32,7 @@
#include <utility>
+#include "mongo/client/read_preference.h"
#include "mongo/stdx/memory.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/net/message.h"
@@ -71,12 +72,18 @@ CommandRequestBuilder& CommandRequestBuilder::setCommandArgs(BSONObj commandArgs
CommandRequestBuilder& CommandRequestBuilder::setMetadata(BSONObj metadata) {
invariant(_state == State::kMetadata);
// OP_COMMAND is only used when communicating with 3.4 nodes and they serialize their metadata
- // fields differently. We do all up- and down-conversion here so that the rest of the code only
- // has to deal with the current format.
+ // fields differently. We do all down-conversion here so that the rest of the code only has to
+ // deal with the current format.
BSONObjBuilder bob(_builder);
for (auto elem : metadata) {
if (elem.fieldNameStringData() == "$configServerState") {
bob.appendAs(elem, "configsvr");
+ } else if (elem.fieldNameStringData() == "$readPreference") {
+ BSONObjBuilder ssmBuilder(bob.subobjStart("$ssm"));
+ ssmBuilder.append(elem);
+ ssmBuilder.append(
+ "$secondaryOk",
+ uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(elem)).canRunOnSecondary());
} else {
bob.append(elem);
}
diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp
index 5dc5f65d169..77545303ff7 100644
--- a/src/mongo/rpc/metadata.cpp
+++ b/src/mongo/rpc/metadata.cpp
@@ -43,7 +43,6 @@
#include "mongo/rpc/metadata/client_metadata_ismaster.h"
#include "mongo/rpc/metadata/config_server_metadata.h"
#include "mongo/rpc/metadata/logical_time_metadata.h"
-#include "mongo/rpc/metadata/server_selection_metadata.h"
#include "mongo/rpc/metadata/sharding_metadata.h"
#include "mongo/rpc/metadata/tracking_metadata.h"
@@ -76,7 +75,7 @@ BSONObj makeEmptyMetadata() {
}
void readRequestMetadata(OperationContext* opCtx, const BSONObj& metadataObj) {
- BSONElement ssmElem;
+ BSONElement readPreferenceElem;
BSONElement auditElem;
BSONElement configSvrElem;
BSONElement trackingElem;
@@ -85,8 +84,8 @@ void readRequestMetadata(OperationContext* opCtx, const BSONObj& metadataObj) {
for (const auto& metadataElem : metadataObj) {
auto fieldName = metadataElem.fieldNameStringData();
- if (fieldName == ServerSelectionMetadata::fieldName()) {
- ssmElem = metadataElem;
+ if (fieldName == "$readPreference") {
+ readPreferenceElem = metadataElem;
} else if (fieldName == AuditMetadata::fieldName()) {
auditElem = metadataElem;
} else if (fieldName == ConfigServerMetadata::fieldName()) {
@@ -100,8 +99,10 @@ void readRequestMetadata(OperationContext* opCtx, const BSONObj& metadataObj) {
}
}
- ServerSelectionMetadata::get(opCtx) =
- uassertStatusOK(ServerSelectionMetadata::readFromMetadata(ssmElem));
+ if (readPreferenceElem) {
+ ReadPreferenceSetting::get(opCtx) =
+ uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(readPreferenceElem));
+ }
AuditMetadata::get(opCtx) = uassertStatusOK(AuditMetadata::readFromMetadata(auditElem));
@@ -143,20 +144,44 @@ void readRequestMetadata(OperationContext* opCtx, const BSONObj& metadataObj) {
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
+ // mutablebson here because the ReadPreference 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;
- uassertStatusOK(
- ServerSelectionMetadata::upconvert(legacyCmdObj, queryFlags, &ssmCommandBob, &metadataBob));
+ auto readPrefContainer = BSONObj();
+ const StringData firstFieldName = legacyCmdObj.firstElementFieldName();
+ if (firstFieldName == "$query" || firstFieldName == "query") {
+ // Commands sent over OP_QUERY specify read preference by putting it at the top level and
+ // putting the command in a nested field called either query or $query.
+
+ // Check if legacyCommand has an invalid $maxTimeMS option.
+ uassert(ErrorCodes::InvalidOptions,
+ "cannot use $maxTimeMS query option with commands; use maxTimeMS command option "
+ "instead",
+ !legacyCmdObj.hasField("$maxTimeMS"));
+ readPrefContainer = legacyCmdObj;
+ legacyCmdObj = legacyCmdObj.firstElement().Obj().getOwned();
+ } else if (auto queryOptions = legacyCmdObj["$queryOptions"]) {
+ // Mongos rewrites commands with $readPreference to put it in a field nested inside of
+ // $queryOptions. Its command implementations often forward commands in that format to
+ // shards. This function is responsible for rewriting it to a format that the shards
+ // understand.
+ readPrefContainer = queryOptions.Obj().getOwned();
+ legacyCmdObj = legacyCmdObj.removeField("$queryOptions");
+ }
+ BSONObjBuilder metadataBob;
+ if (auto readPref = readPrefContainer["$readPreference"]) {
+ metadataBob.append(readPref);
+ } else if (queryFlags & QueryOption_SlaveOk) {
+ ReadPreferenceSetting(ReadPreference::SecondaryPreferred).toContainingBSON(&metadataBob);
+ }
+
+ // Ordering is important here - AuditMetadata::upconvert() expects the above up-conversion to
+ // already be done.
BSONObjBuilder auditCommandBob;
uassertStatusOK(
- AuditMetadata::upconvert(ssmCommandBob.done(), queryFlags, &auditCommandBob, &metadataBob));
+ AuditMetadata::upconvert(legacyCmdObj, queryFlags, &auditCommandBob, &metadataBob));
return std::make_tuple(auditCommandBob.obj(), metadataBob.obj());
}
@@ -165,16 +190,28 @@ LegacyCommandAndFlags downconvertRequestMetadata(BSONObj cmdObj, BSONObj metadat
int legacyQueryFlags = 0;
BSONObjBuilder auditCommandBob;
// Ordering is important here - AuditingMetadata must be downconverted first,
- // then ServerSelectionMetadata.
+ // then ReadPreference.
uassertStatusOK(
AuditMetadata::downconvert(cmdObj, metadata, &auditCommandBob, &legacyQueryFlags));
- BSONObjBuilder ssmCommandBob;
- uassertStatusOK(ServerSelectionMetadata::downconvert(
- auditCommandBob.done(), metadata, &ssmCommandBob, &legacyQueryFlags));
+ auto readPref = metadata["$readPreference"];
+ if (!readPref)
+ readPref = cmdObj["$readPreference"];
+
+ if (readPref) {
+ BSONObjBuilder bob;
+ bob.append("$query", cmdObj);
+ bob.append(readPref);
+ cmdObj = bob.obj();
+
+ auto parsed = ReadPreferenceSetting::fromInnerBSON(readPref);
+ if (parsed.isOK() && parsed.getValue().canRunOnSecondary()) {
+ legacyQueryFlags |= QueryOption_SlaveOk;
+ }
+ }
- return std::make_tuple(ssmCommandBob.obj(), std::move(legacyQueryFlags));
+ return std::make_tuple(cmdObj, std::move(legacyQueryFlags));
}
CommandReplyWithMetadata upconvertReplyMetadata(const BSONObj& legacyReply) {
diff --git a/src/mongo/rpc/metadata/server_selection_metadata.cpp b/src/mongo/rpc/metadata/server_selection_metadata.cpp
deleted file mode 100644
index 533ebd650e1..00000000000
--- a/src/mongo/rpc/metadata/server_selection_metadata.cpp
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * 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 <tuple>
-#include <utility>
-
-#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 {
-
-// Symbolic constant for the "$readPreference" metadata field. The field should be of Object type
-// when present.
-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
- * returned tuple 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.
- */
-StatusWith<std::tuple<bool, BSONObj>> unwrapCommand(const BSONObj& maybeWrapped) {
- const auto firstElFieldName = maybeWrapped.firstElementFieldName();
-
- if ((firstElFieldName != StringData(kDollarQueryWrapper)) &&
- (firstElFieldName != StringData(kQueryWrapper))) {
- return std::make_tuple(false, maybeWrapped);
- }
-
- BSONElement inner;
- auto extractStatus =
- bsonExtractTypedField(maybeWrapped, firstElFieldName, mongo::Object, &inner);
-
- if (!extractStatus.isOK()) {
- return extractStatus;
- }
-
- return std::make_tuple(true, inner.Obj());
-}
-
-/**
- * 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
-
-// Symbolic constant for the "$secondaryOk" metadata field. This field should be of boolean or
-// numeric type, and is treated as a boolean.
-const char ServerSelectionMetadata::kSecondaryOkFieldName[] = "$secondaryOk";
-
-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& metadataObj) {
- return readFromMetadata(metadataObj.getField(fieldName()));
-}
-
-StatusWith<ServerSelectionMetadata> ServerSelectionMetadata::readFromMetadata(
- const BSONElement& metadataElem) {
- if (metadataElem.eoo()) {
- return ServerSelectionMetadata{};
- } else if (metadataElem.type() != mongo::Object) {
- return {ErrorCodes::TypeMismatch,
- str::stream() << "ServerSelectionMetadata element has incorrect type: expected"
- << mongo::Object
- << " but got "
- << metadataElem.type()};
- }
-
- bool secondaryOk = false;
- boost::optional<ReadPreferenceSetting> readPreference;
- BSONElement rpElem;
- for (const auto& ssmElem : metadataElem.Obj()) {
- auto ssmElemFieldName = ssmElem.fieldNameStringData();
- if (ssmElemFieldName == kSecondaryOkFieldName) {
- secondaryOk = ssmElem.trueValue();
- } else if (ssmElemFieldName == kReadPreferenceFieldName) {
- if (ssmElem.type() != mongo::Object) {
- return Status(ErrorCodes::TypeMismatch,
- str::stream() << "ReadPreference has incorrect type: expected"
- << mongo::Object
- << "but got"
- << metadataElem.type());
- }
- auto parsedRps = ReadPreferenceSetting::fromBSON(ssmElem.Obj());
- if (!parsedRps.isOK()) {
- return parsedRps.getStatus();
- }
- readPreference.emplace(std::move(parsedRps.getValue()));
- }
- }
-
- return ServerSelectionMetadata(secondaryOk, std::move(readPreference));
-}
-
-Status ServerSelectionMetadata::writeToMetadata(BSONObjBuilder* metadataBob) const {
- BSONObjBuilder ssmBob;
- if (isSecondaryOk()) {
- ssmBob.append(kSecondaryOkFieldName, 1);
- }
-
- if (getReadPreference()) {
- ssmBob.append(kReadPreferenceFieldName, getReadPreference()->toBSON());
- }
-
- auto ssm = ssmBob.done();
- if (!ssm.isEmpty()) {
- metadataBob->append(fieldName(), ssm);
- }
-
- return Status::OK();
-}
-
-BSONObj ServerSelectionMetadata::toBSON() const {
- BSONObjBuilder bob;
- writeToMetadata(&bob);
- return bob.obj();
-}
-
-Status ServerSelectionMetadata::downconvert(const BSONObj& command,
- const BSONObj& metadata,
- BSONObjBuilder* legacyCommand,
- int* legacyQueryFlags) {
- auto ssmElem = metadata.getField(fieldName());
- if (ssmElem.eoo()) {
- // slaveOk is false by default.
- *legacyQueryFlags &= ~mongo::QueryOption_SlaveOk;
- legacyCommand->appendElements(command);
- return Status::OK();
- } else if (ssmElem.type() != mongo::Object) {
- return {
- ErrorCodes::TypeMismatch,
- str::stream() << "ServerSelectionMetadata metadata element must be an object, but got "
- << typeName(ssmElem.type())};
- }
-
- auto ssmObj = ssmElem.Obj();
- BSONElement secondaryOkElem;
- BSONElement readPreferenceElem;
-
- for (auto&& el : ssmObj) {
- auto fname = el.fieldNameStringData();
- if (fname == kSecondaryOkFieldName) {
- secondaryOkElem = std::move(el);
- } else if (fname == kReadPreferenceFieldName) {
- readPreferenceElem = std::move(el);
- }
- }
-
- if (!secondaryOkElem.eoo() && secondaryOkElem.trueValue()) {
- *legacyQueryFlags |= mongo::QueryOption_SlaveOk;
- } else {
- *legacyQueryFlags &= ~mongo::QueryOption_SlaveOk;
- }
-
- if (!readPreferenceElem.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(readPreferenceElem);
- } 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.
- BSONObjBuilder ssmBob;
- if (legacyQueryFlags & QueryOption_SlaveOk) {
- ssmBob.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'.
- auto swUnwrapped = unwrapCommand(legacyCommand);
- if (!swUnwrapped.isOK()) {
- return swUnwrapped.getStatus();
- }
-
- BSONObj maybeUnwrapped;
- bool wasWrapped;
- std::tie(wasWrapped, maybeUnwrapped) = swUnwrapped.getValue();
-
- 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);
-
- auto status = extractWrappedReadPreference(legacyCommand, &ssmBob);
- if (!status.isOK()) {
- return status;
- }
- } else {
- // 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.
-
- auto status = extractUnwrappedReadPreference(maybeUnwrapped, commandBob, &ssmBob);
- if (!status.isOK()) {
- return status;
- }
- }
-
- auto ssm = ssmBob.done();
- if (!ssm.isEmpty()) {
- metadataBob->append(fieldName(), ssm);
- }
- return Status::OK();
-}
-
-bool ServerSelectionMetadata::isSecondaryOk() const {
- return _secondaryOk;
-}
-
-const boost::optional<ReadPreferenceSetting>& ServerSelectionMetadata::getReadPreference() const {
- return _readPreference;
-}
-
-bool ServerSelectionMetadata::canRunOnSecondary() const {
- return _secondaryOk ||
- (_readPreference && (_readPreference->pref != ReadPreference::PrimaryOnly));
-}
-
-} // rpc
-} // mongo
diff --git a/src/mongo/rpc/metadata/server_selection_metadata.h b/src/mongo/rpc/metadata/server_selection_metadata.h
deleted file mode 100644
index 26983bf7533..00000000000
--- a/src/mongo/rpc/metadata/server_selection_metadata.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * 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 char kSecondaryOkFieldName[];
- static const OperationContext::Decoration<ServerSelectionMetadata> get;
-
- ServerSelectionMetadata() = default;
-
- ServerSelectionMetadata(ServerSelectionMetadata&&) = default;
-
- ServerSelectionMetadata& operator=(ServerSelectionMetadata&&) = default;
-
- /**
- * Loads ServerSelectionMetadata from a metadata object.
- */
- static StatusWith<ServerSelectionMetadata> readFromMetadata(const BSONObj& metadataObj);
-
- static StatusWith<ServerSelectionMetadata> readFromMetadata(const BSONElement& metadataElem);
-
- /**
- * Writes this operation's ServerSelectionMetadata to a metadata object.
- */
- Status writeToMetadata(BSONObjBuilder* metadataBob) const;
-
- BSONObj toBSON() 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;
-
- /**
- * Returns true if this operation can run on secondary.
- */
- bool canRunOnSecondary() const;
-
- ServerSelectionMetadata(bool secondaryOk,
- boost::optional<ReadPreferenceSetting> readPreference);
-
- static StringData fieldName() {
- return "$ssm";
- }
-
-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
deleted file mode 100644
index 3775f489a06..00000000000
--- a/src/mongo/rpc/metadata/server_selection_metadata_test.cpp
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * 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("$ssm" << BSON("$secondaryOk" << 1)));
- ASSERT_TRUE(ss.isSecondaryOk());
- ASSERT_FALSE(ss.getReadPreference().is_initialized());
- }
- {
- // Set readPreference but not secondaryOk.
- auto ss = checkParse(BSON("$ssm" << 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("$ssm" << 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_BSONOBJ_EQ(upconvertedCommand, upconvertedCommandBob.done());
- ASSERT_BSONOBJ_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("$ssm" << 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("$ssm" << 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("$ssm" << 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);
-
- // invalid wrapped query
- checkUpconvertFails(BSON("$query" << 1), ErrorCodes::TypeMismatch);
- checkUpconvertFails(BSON("$query"
- << ""),
- ErrorCodes::TypeMismatch);
- checkUpconvertFails(BSON("query" << 1), ErrorCodes::TypeMismatch);
- checkUpconvertFails(BSON("query"
- << ""),
- ErrorCodes::TypeMismatch);
-}
-
-} // namespace
diff --git a/src/mongo/rpc/metadata_test.cpp b/src/mongo/rpc/metadata_test.cpp
new file mode 100644
index 00000000000..1b538d4717d
--- /dev/null
+++ b/src/mongo/rpc/metadata_test.cpp
@@ -0,0 +1,143 @@
+/*
+ * 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 <utility>
+
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/rpc/metadata.h"
+#include "mongo/unittest/unittest.h"
+
+namespace {
+using namespace mongo;
+using namespace mongo::rpc;
+using mongo::unittest::assertGet;
+
+void checkUpconvert(const BSONObj& legacyCommand,
+ const int legacyQueryFlags,
+ const BSONObj& upconvertedCommand,
+ const BSONObj& upconvertedMetadata) {
+
+ auto converted = upconvertRequestMetadata(legacyCommand, legacyQueryFlags);
+ // 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_BSONOBJ_EQ(upconvertedCommand, std::get<0>(converted));
+ ASSERT_BSONOBJ_EQ(sorted(upconvertedMetadata), sorted(std::get<1>(converted)));
+}
+
+TEST(Metadata, 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("$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"))));
+}
+
+TEST(Metadata, UpconvertInvalidMetadata) {
+ // has $maxTimeMS option
+ ASSERT_THROWS_CODE(upconvertRequestMetadata(BSON("query" << BSON("foo"
+ << "bar")
+ << "$maxTimeMS"
+ << 200),
+ 0),
+ UserException,
+ ErrorCodes::InvalidOptions);
+ ASSERT_THROWS_CODE(upconvertRequestMetadata(BSON("$query" << BSON("foo"
+ << "bar")
+ << "$maxTimeMS"
+ << 200),
+ 0),
+ UserException,
+ ErrorCodes::InvalidOptions);
+
+ // invalid wrapped query
+ ASSERT_THROWS(upconvertRequestMetadata(BSON("$query" << 1), 0), UserException);
+ ASSERT_THROWS(upconvertRequestMetadata(BSON("$query"
+ << ""),
+ 0),
+ UserException);
+ ASSERT_THROWS(upconvertRequestMetadata(BSON("query" << 1), 0), UserException);
+ ASSERT_THROWS(upconvertRequestMetadata(BSON("query"
+ << ""),
+ 0),
+ UserException);
+}
+
+} // namespace