diff options
author | Mathias Stearn <mathias@10gen.com> | 2017-04-26 09:36:41 -0400 |
---|---|---|
committer | Mathias Stearn <mathias@10gen.com> | 2017-05-12 12:08:30 -0400 |
commit | f2902d59175c0724944ca98d13f784e2de944053 (patch) | |
tree | 44788ee704540d420f649279053019e3b6a332b2 /src/mongo/rpc | |
parent | 1a955fc356627b8cc74eb15506608dd987184608 (diff) | |
download | mongo-f2902d59175c0724944ca98d13f784e2de944053.tar.gz |
SERVER-28814 Replace ServerSelectionMetadata with just ReadPreferenceSetting
Diffstat (limited to 'src/mongo/rpc')
-rw-r--r-- | src/mongo/rpc/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/rpc/command_request.cpp | 15 | ||||
-rw-r--r-- | src/mongo/rpc/command_request_builder.cpp | 11 | ||||
-rw-r--r-- | src/mongo/rpc/metadata.cpp | 75 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/server_selection_metadata.cpp | 344 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/server_selection_metadata.h | 126 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/server_selection_metadata_test.cpp | 206 | ||||
-rw-r--r-- | src/mongo/rpc/metadata_test.cpp | 143 |
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 |