diff options
29 files changed, 381 insertions, 881 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript index 89fb9b75516..fa9f3dcff45 100644 --- a/src/mongo/client/SConscript +++ b/src/mongo/client/SConscript @@ -37,6 +37,7 @@ env.Library( ], LIBDEPS=[ '$BUILD_DIR/mongo/bson/util/bson_extract', + '$BUILD_DIR/mongo/db/service_context' ], ) diff --git a/src/mongo/client/dbclient_rs.cpp b/src/mongo/client/dbclient_rs.cpp index 219069bb9dc..cd80a61f79d 100644 --- a/src/mongo/client/dbclient_rs.cpp +++ b/src/mongo/client/dbclient_rs.cpp @@ -43,7 +43,7 @@ #include "mongo/client/sasl_client_authenticate.h" #include "mongo/db/dbmessage.h" #include "mongo/db/jsobj.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" +#include "mongo/stdx/memory.h" #include "mongo/util/log.h" namespace mongo { @@ -104,30 +104,19 @@ const size_t MAX_RETRY = 3; * * @throws AssertionException if the read preference object is malformed */ -ReadPreferenceSetting* _extractReadPref(const BSONObj& query, int queryOptions) { - if (Query::hasReadPreference(query)) { - BSONElement readPrefElement; - - if (query.hasField(Query::ReadPrefField.name())) { - readPrefElement = query[Query::ReadPrefField.name()]; - } else { - readPrefElement = query["$queryOptions"][Query::ReadPrefField.name()]; - } - - uassert(16381, "$readPreference should be an object", readPrefElement.isABSONObj()); - - const BSONObj& prefDoc = readPrefElement.Obj(); - - auto readPrefSetting = uassertStatusOK(ReadPreferenceSetting::fromBSON(prefDoc)); - - return new ReadPreferenceSetting(std::move(readPrefSetting)); - } - +std::unique_ptr<ReadPreferenceSetting> _extractReadPref(const BSONObj& query, int queryOptions) { // Default read pref is primary only or secondary preferred with slaveOK - ReadPreference pref = queryOptions & QueryOption_SlaveOk - ? mongo::ReadPreference::SecondaryPreferred - : mongo::ReadPreference::PrimaryOnly; - return new ReadPreferenceSetting(pref, TagSet()); + const auto defaultReadPref = queryOptions & QueryOption_SlaveOk + ? ReadPreference::SecondaryPreferred + : ReadPreference::PrimaryOnly; + + auto readPrefContainingObj = query; + if (auto elem = query["$queryOptions"]) { + // The readPreference is embedded in the $queryOptions field. + readPrefContainingObj = elem.Obj(); + } + return stdx::make_unique<ReadPreferenceSetting>(uassertStatusOK( + ReadPreferenceSetting::fromContainingBSON(readPrefContainingObj, defaultReadPref))); } } // namespace @@ -273,7 +262,9 @@ bool _isSecondaryQuery(const string& ns, // Only certain commands are supported for secondary operation. BSONObj actualQueryObj; - if (strcmp(queryObj.firstElement().fieldName(), "query") == 0) { + if (strcmp(queryObj.firstElement().fieldName(), "$query") == 0) { + actualQueryObj = queryObj["$query"].embeddedObject(); + } else if (strcmp(queryObj.firstElement().fieldName(), "query") == 0) { actualQueryObj = queryObj["query"].embeddedObject(); } else { actualQueryObj = queryObj; @@ -532,7 +523,7 @@ unique_ptr<DBClientCursor> DBClientReplicaSet::query(const string& ns, shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(query.obj, queryOptions)); if (_isSecondaryQuery(ns, query.obj, *readPref)) { LOG(3) << "dbclient_rs query using secondary or tagged node selection in " - << _getMonitor()->getName() << ", read pref is " << readPref->toBSON() + << _getMonitor()->getName() << ", read pref is " << readPref->toString() << " (primary : " << (_master.get() != NULL ? _master->getServerAddress() : "[not cached]") << ", lastTagged : " << (_lastSlaveOkConn.get() != NULL @@ -584,7 +575,7 @@ BSONObj DBClientReplicaSet::findOne(const string& ns, shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(query.obj, queryOptions)); if (_isSecondaryQuery(ns, query.obj, *readPref)) { LOG(3) << "dbclient_rs findOne using secondary or tagged node selection in " - << _getMonitor()->getName() << ", read pref is " << readPref->toBSON() + << _getMonitor()->getName() << ", read pref is " << readPref->toString() << " (primary : " << (_master.get() != NULL ? _master->getServerAddress() : "[not cached]") << ", lastTagged : " << (_lastSlaveOkConn.get() != NULL @@ -761,7 +752,7 @@ void DBClientReplicaSet::say(Message& toSend, bool isRetry, string* actualServer shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(qm.query, qm.queryOptions)); if (_isSecondaryQuery(qm.ns, qm.query, *readPref)) { LOG(3) << "dbclient_rs say using secondary or tagged node selection in " - << _getMonitor()->getName() << ", read pref is " << readPref->toBSON() + << _getMonitor()->getName() << ", read pref is " << readPref->toString() << " (primary : " << (_master.get() != NULL ? _master->getServerAddress() : "[not cached]") << ", lastTagged : " << (_lastSlaveOkConn.get() != NULL @@ -932,16 +923,7 @@ DBClientReplicaSet::runCommandWithMetadataAndTarget(StringData database, // so we don't have to re-parse it, however, that will come with its own set of // complications (e.g. some kind of base class or concept for MetadataSerializable // objects). For now we do it the stupid way. - auto ssm = uassertStatusOK(rpc::ServerSelectionMetadata::readFromMetadata( - metadata.getField(rpc::ServerSelectionMetadata::fieldName()))); - - // If we didn't get a readPref with this query, we assume SecondaryPreferred if secondaryOk - // is true, and PrimaryOnly otherwise. This logic is replicated from _extractReadPref. - auto defaultReadPref = ssm.isSecondaryOk() - ? ReadPreferenceSetting(ReadPreference::SecondaryPreferred, TagSet()) - : ReadPreferenceSetting(ReadPreference::PrimaryOnly, TagSet::primaryOnly()); - - auto readPref = ssm.getReadPreference().get_value_or(defaultReadPref); + auto readPref = uassertStatusOK(ReadPreferenceSetting::fromContainingBSON(metadata)); if (readPref.pref == ReadPreference::PrimaryOnly || // If the command is not runnable on a secondary, we run it on the primary @@ -970,8 +952,8 @@ DBClientReplicaSet::runCommandWithMetadataAndTarget(StringData database, } uasserted(ErrorCodes::NodeNotFound, - str::stream() << "Could not satisfy $readPreference of '" << readPref.toBSON() << "' " - << "while attempting to run command " + str::stream() << "Could not satisfy $readPreference of '" << readPref.toString() + << "' while attempting to run command " << command); } @@ -990,7 +972,7 @@ bool DBClientReplicaSet::call(Message& toSend, shared_ptr<ReadPreferenceSetting> readPref(_extractReadPref(qm.query, qm.queryOptions)); if (_isSecondaryQuery(ns, qm.query, *readPref)) { LOG(3) << "dbclient_rs call using secondary or tagged node selection in " - << _getMonitor()->getName() << ", read pref is " << readPref->toBSON() + << _getMonitor()->getName() << ", read pref is " << readPref->toString() << " (primary : " << (_master.get() != NULL ? _master->getServerAddress() : "[not cached]") << ", lastTagged : " << (_lastSlaveOkConn.get() != NULL diff --git a/src/mongo/client/dbclient_rs_test.cpp b/src/mongo/client/dbclient_rs_test.cpp index 15967c07677..15bc9f24a58 100644 --- a/src/mongo/client/dbclient_rs_test.cpp +++ b/src/mongo/client/dbclient_rs_test.cpp @@ -44,7 +44,6 @@ #include "mongo/db/jsobj.h" #include "mongo/dbtests/mock/mock_conn_registry.h" #include "mongo/dbtests/mock/mock_replica_set.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/stdx/unordered_set.h" #include "mongo/unittest/unittest.h" #include "mongo/util/assert_util.h" @@ -62,11 +61,8 @@ using std::vector; /** * Constructs a metadata object containing the passed server selection metadata. */ -BSONObj makeMetadata(ReadPreference rp, TagSet tagSet, bool secondaryOk) { - BSONObjBuilder metadataBob; - rpc::ServerSelectionMetadata ssm(secondaryOk, ReadPreferenceSetting(rp, tagSet)); - uassertStatusOK(ssm.writeToMetadata(&metadataBob)); - return metadataBob.obj(); +BSONObj makeMetadata(ReadPreference rp, TagSet tagSet) { + return ReadPreferenceSetting(rp, std::move(tagSet)).toContainingBSON(); } /** @@ -105,7 +101,7 @@ void assertOneOfNodesSelected(MockReplicaSet* replSet, auto tagSet = secondaryOk ? TagSet() : TagSet::primaryOnly(); // We need the command to be a "SecOk command" auto res = replConn.runCommandWithMetadata( - "foo", "dbStats", makeMetadata(rp, tagSet, secondaryOk), BSON("dbStats" << 1)); + "foo", "dbStats", makeMetadata(rp, tagSet), BSON("dbStats" << 1)); stdx::unordered_set<HostAndPort> hostSet; for (const auto& hostName : hostNames) { hostSet.emplace(hostName); @@ -228,13 +224,11 @@ private: void assertRunCommandWithReadPrefThrows(MockReplicaSet* replSet, ReadPreference rp) { bool isPrimaryOnly = (rp == ReadPreference::PrimaryOnly); - - bool secondaryOk = !isPrimaryOnly; TagSet ts = isPrimaryOnly ? TagSet::primaryOnly() : TagSet(); DBClientReplicaSet replConn(replSet->getSetName(), replSet->getHosts(), StringData()); ASSERT_THROWS(replConn.runCommandWithMetadata( - "foo", "whoami", makeMetadata(rp, ts, secondaryOk), BSON("dbStats" << 1)), + "foo", "whoami", makeMetadata(rp, ts), BSON("dbStats" << 1)), AssertionException); } diff --git a/src/mongo/client/query.cpp b/src/mongo/client/query.cpp index 419f5b485e0..349ed5ff96d 100644 --- a/src/mongo/client/query.cpp +++ b/src/mongo/client/query.cpp @@ -115,7 +115,8 @@ bool Query::isComplex(const BSONObj& obj, bool* hasDollar) { } Query& Query::readPref(ReadPreference pref, const BSONArray& tags) { - appendComplex(ReadPrefField.name().c_str(), ReadPreferenceSetting(pref, TagSet(tags)).toBSON()); + appendComplex(ReadPrefField.name().c_str(), + ReadPreferenceSetting(pref, TagSet(tags)).toInnerBSON()); return *this; } diff --git a/src/mongo/client/read_preference.cpp b/src/mongo/client/read_preference.cpp index acdd83c1771..a8790ae9f3c 100644 --- a/src/mongo/client/read_preference.cpp +++ b/src/mongo/client/read_preference.cpp @@ -119,10 +119,14 @@ TagSet defaultTagSetForMode(ReadPreference mode) { */ const Seconds ReadPreferenceSetting::kMinimalMaxStalenessValue(90); +const OperationContext::Decoration<ReadPreferenceSetting> ReadPreferenceSetting::get = + OperationContext::declareDecoration<ReadPreferenceSetting>(); + const BSONObj& ReadPreferenceSetting::secondaryPreferredMetadata() { // This is a static method rather than a static member only because it is used by another TU // during dynamic init. - static const auto bson = BSON("$ssm" << BSON("$secondaryOk" << true)); + static const auto bson = + ReadPreferenceSetting(ReadPreference::SecondaryPreferred).toContainingBSON(); return bson; } @@ -148,7 +152,7 @@ ReadPreferenceSetting::ReadPreferenceSetting(ReadPreference pref, TagSet tags) ReadPreferenceSetting::ReadPreferenceSetting(ReadPreference pref) : ReadPreferenceSetting(pref, defaultTagSetForMode(pref)) {} -StatusWith<ReadPreferenceSetting> ReadPreferenceSetting::fromBSON(const BSONObj& readPrefObj) { +StatusWith<ReadPreferenceSetting> ReadPreferenceSetting::fromInnerBSON(const BSONObj& readPrefObj) { std::string modeStr; auto modeExtractStatus = bsonExtractStringField(readPrefObj, kModeFieldName, &modeStr); if (!modeExtractStatus.isOK()) { @@ -227,20 +231,37 @@ StatusWith<ReadPreferenceSetting> ReadPreferenceSetting::fromBSON(const BSONObj& return ReadPreferenceSetting(mode, tags, Seconds(maxStalenessSecondsValue)); } -BSONObj ReadPreferenceSetting::toBSON() const { - BSONObjBuilder bob; - bob.append(kModeFieldName, readPreferenceName(pref)); +StatusWith<ReadPreferenceSetting> ReadPreferenceSetting::fromInnerBSON(const BSONElement& elem) { + if (elem.type() != mongo::Object) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "$readPreference has incorrect type: expected " + << mongo::Object + << " but got " + << elem.type()); + } + return fromInnerBSON(elem.Obj()); +} + +StatusWith<ReadPreferenceSetting> ReadPreferenceSetting::fromContainingBSON( + const BSONObj& obj, ReadPreference defaultReadPref) { + if (auto elem = obj["$readPreference"]) { + return fromInnerBSON(elem); + } + return ReadPreferenceSetting(defaultReadPref); +} + +void ReadPreferenceSetting::toInnerBSON(BSONObjBuilder* bob) const { + bob->append(kModeFieldName, readPreferenceName(pref)); if (tags != defaultTagSetForMode(pref)) { - bob.append(kTagsFieldName, tags.getTagBSON()); + bob->append(kTagsFieldName, tags.getTagBSON()); } if (maxStalenessSeconds.count() > 0) { - bob.append(kMaxStalenessSecondsFieldName, maxStalenessSeconds.count()); + bob->append(kMaxStalenessSecondsFieldName, maxStalenessSeconds.count()); } - return bob.obj(); } std::string ReadPreferenceSetting::toString() const { - return toBSON().toString(); + return toInnerBSON().toString(); } } // namespace mongo diff --git a/src/mongo/client/read_preference.h b/src/mongo/client/read_preference.h index 35ef1c9067b..c1f09765be5 100644 --- a/src/mongo/client/read_preference.h +++ b/src/mongo/client/read_preference.h @@ -30,6 +30,7 @@ #include "mongo/bson/simple_bsonobj_comparator.h" #include "mongo/db/jsobj.h" +#include "mongo/db/operation_context.h" #include "mongo/db/repl/optime.h" #include "mongo/util/duration.h" @@ -115,6 +116,8 @@ private: }; struct ReadPreferenceSetting { + static const OperationContext::Decoration<ReadPreferenceSetting> get; + /** * The minimal value maxStalenessSeconds can have. */ @@ -122,7 +125,7 @@ struct ReadPreferenceSetting { /** * An object representing the metadata generated for a SecondaryPreferred read preference: - * {$ssm: {$secondaryOk: true}} + * {$readPreference: {mode: "secondaryPreferred"}} */ static const BSONObj& secondaryPreferredMetadata(); @@ -137,6 +140,7 @@ struct ReadPreferenceSetting { ReadPreferenceSetting(ReadPreference pref, Seconds maxStalenessSeconds); ReadPreferenceSetting(ReadPreference pref, TagSet tags); explicit ReadPreferenceSetting(ReadPreference pref); + ReadPreferenceSetting() : ReadPreferenceSetting(ReadPreference::PrimaryOnly) {} inline bool equals(const ReadPreferenceSetting& other) const { return (pref == other.pref) && (tags == other.tags) && @@ -144,14 +148,33 @@ struct ReadPreferenceSetting { } /** - * Serializes this ReadPreferenceSetting as a BSON document. + * Serializes this ReadPreferenceSetting as an inner BSON document. (The document inside a + * $readPreference element) */ - BSONObj toBSON() const; + void toInnerBSON(BSONObjBuilder* builder) const; + BSONObj toInnerBSON() const { + BSONObjBuilder bob; + toInnerBSON(&bob); + return bob.obj(); + } /** - * Describes this ReadPreferenceSetting as a string. + * Serializes this ReadPreferenceSetting as a containing BSON document. (The document containing + * a $readPreference element) + * + * Will not add the $readPreference element if the read preference is PrimaryOnly. */ - std::string toString() const; + void toContainingBSON(BSONObjBuilder* builder) const { + if (!canRunOnSecondary()) + return; // Write nothing since default is fine. + BSONObjBuilder inner(builder->subobjStart("$readPreference")); + toInnerBSON(&inner); + } + BSONObj toContainingBSON() const { + BSONObjBuilder bob; + toContainingBSON(&bob); + return bob.obj(); + } /** * Parses a ReadPreferenceSetting from a BSON document of the form: @@ -161,7 +184,25 @@ struct ReadPreferenceSetting { * further validation is performed on it other than checking that it is an array, and that it is * empty if 'mode' is 'primary'. */ - static StatusWith<ReadPreferenceSetting> fromBSON(const BSONObj& readPrefSettingObj); + static StatusWith<ReadPreferenceSetting> fromInnerBSON(const BSONObj& readPrefSettingObj); + static StatusWith<ReadPreferenceSetting> fromInnerBSON(const BSONElement& readPrefSettingObj); + + /** + * Parses a ReadPreference setting from an object that may contain a $readPreference object + * field with the contents described in fromInnerObject(). If the field is missing, returns the + * default read preference. + */ + static StatusWith<ReadPreferenceSetting> fromContainingBSON( + const BSONObj& obj, ReadPreference defaultReadPref = ReadPreference::PrimaryOnly); + + /** + * Describes this ReadPreferenceSetting as a string. + */ + std::string toString() const; + + bool canRunOnSecondary() const { + return pref != ReadPreference::PrimaryOnly; + } ReadPreference pref; TagSet tags; diff --git a/src/mongo/client/read_preference_test.cpp b/src/mongo/client/read_preference_test.cpp index dcf20107433..fda208ef45f 100644 --- a/src/mongo/client/read_preference_test.cpp +++ b/src/mongo/client/read_preference_test.cpp @@ -39,7 +39,7 @@ using namespace mongo; static const Seconds kMinMaxStaleness = ReadPreferenceSetting::kMinimalMaxStalenessValue; void checkParse(const BSONObj& rpsObj, const ReadPreferenceSetting& expected) { - const auto swRps = ReadPreferenceSetting::fromBSON(rpsObj); + const auto swRps = ReadPreferenceSetting::fromInnerBSON(rpsObj); ASSERT_OK(swRps.getStatus()); const auto rps = swRps.getValue(); ASSERT_TRUE(rps.equals(expected)); @@ -92,12 +92,18 @@ TEST(ReadPreferenceSetting, ParseValid) { } void checkParseFails(const BSONObj& rpsObj) { - auto swRps = ReadPreferenceSetting::fromBSON(rpsObj); + auto swRps = ReadPreferenceSetting::fromInnerBSON(rpsObj); ASSERT_NOT_OK(swRps.getStatus()); } void checkParseFailsWithError(const BSONObj& rpsObj, ErrorCodes::Error error) { - auto swRps = ReadPreferenceSetting::fromBSON(rpsObj); + auto swRps = ReadPreferenceSetting::fromInnerBSON(rpsObj); + ASSERT_NOT_OK(swRps.getStatus()); + ASSERT_EQUALS(swRps.getStatus().code(), error); +} + +void checkParseContainerFailsWithError(const BSONObj& rpsObj, ErrorCodes::Error error) { + auto swRps = ReadPreferenceSetting::fromContainingBSON(rpsObj); ASSERT_NOT_OK(swRps.getStatus()); ASSERT_EQUALS(swRps.getStatus().code(), error); } @@ -169,10 +175,16 @@ TEST(ReadPreferenceSetting, ParseInvalid) { << "secondary" << "maxStalenessSeconds" << Seconds::max().count())); + + checkParseContainerFailsWithError(BSON("$query" << BSON("pang" + << "pong") + << "$readPreference" + << 2), + ErrorCodes::TypeMismatch); } void checkRoundtrip(const ReadPreferenceSetting& rps) { - auto parsed = ReadPreferenceSetting::fromBSON(rps.toBSON()); + auto parsed = ReadPreferenceSetting::fromInnerBSON(rps.toInnerBSON()); ASSERT_OK(parsed.getStatus()); ASSERT_TRUE(parsed.getValue().equals(rps)); } diff --git a/src/mongo/db/commands.h b/src/mongo/db/commands.h index a6a53116bac..780af5827cc 100644 --- a/src/mongo/db/commands.h +++ b/src/mongo/db/commands.h @@ -63,10 +63,6 @@ namespace mutablebson { class Document; } // namespace mutablebson -namespace rpc { -class ServerSelectionMetadata; -} // namespace rpc - /** * Serves as a base for server commands. See the constructor for more details. */ @@ -483,8 +479,8 @@ public: arg == "$gleStats" || // arg == "$oplogQueryData" || // arg == "$queryOptions" || // + arg == "$readPreference" || // arg == "$replData" || // - arg == "$ssm" || // arg == "logicalTime" || // arg == "maxTimeMS" || // arg == "readConcern" || // diff --git a/src/mongo/db/commands/dbcommands.cpp b/src/mongo/db/commands/dbcommands.cpp index 05600734aa7..8751e871a82 100644 --- a/src/mongo/db/commands/dbcommands.cpp +++ b/src/mongo/db/commands/dbcommands.cpp @@ -102,7 +102,6 @@ #include "mongo/rpc/metadata/logical_time_metadata.h" #include "mongo/rpc/metadata/oplog_query_metadata.h" #include "mongo/rpc/metadata/repl_set_metadata.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/rpc/metadata/sharding_metadata.h" #include "mongo/rpc/metadata/tracking_metadata.h" #include "mongo/rpc/reply_builder_interface.h" @@ -1602,8 +1601,8 @@ void mongo::execCommandDatabase(OperationContext* opCtx, { bool commandCanRunOnSecondary = command->slaveOk(); - bool commandIsOverriddenToRunOnSecondary = command->slaveOverrideOk() && - rpc::ServerSelectionMetadata::get(opCtx).canRunOnSecondary(); + bool commandIsOverriddenToRunOnSecondary = + command->slaveOverrideOk() && ReadPreferenceSetting::get(opCtx).canRunOnSecondary(); bool iAmStandalone = !opCtx->writesAreReplicated(); bool canRunHere = iAmPrimary || commandCanRunOnSecondary || diff --git a/src/mongo/db/commands/explain_cmd.cpp b/src/mongo/db/commands/explain_cmd.cpp index 59004e87e12..b62fe416ac7 100644 --- a/src/mongo/db/commands/explain_cmd.cpp +++ b/src/mongo/db/commands/explain_cmd.cpp @@ -31,7 +31,6 @@ #include "mongo/db/commands.h" #include "mongo/db/query/explain.h" #include "mongo/db/repl/replication_coordinator_global.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/util/mongoutils/str.h" namespace mongo { @@ -150,7 +149,7 @@ public: bool commandCanRunOnSecondary = commToExplain->slaveOk(); bool commandIsOverriddenToRunOnSecondary = commToExplain->slaveOverrideOk() && - rpc::ServerSelectionMetadata::get(opCtx).canRunOnSecondary(); + ReadPreferenceSetting::get(opCtx).canRunOnSecondary(); bool iAmStandalone = !opCtx->writesAreReplicated(); const bool canRunHere = iAmPrimary || commandCanRunOnSecondary || 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 diff --git a/src/mongo/s/async_requests_sender.cpp b/src/mongo/s/async_requests_sender.cpp index eb512b1a281..55f63010a13 100644 --- a/src/mongo/s/async_requests_sender.cpp +++ b/src/mongo/s/async_requests_sender.cpp @@ -35,7 +35,6 @@ #include "mongo/client/remote_command_targeter.h" #include "mongo/executor/remote_command_request.h" #include "mongo/rpc/get_status_from_command_result.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/grid.h" #include "mongo/stdx/memory.h" @@ -62,11 +61,7 @@ AsyncRequestsSender::AsyncRequestsSender(OperationContext* opCtx, } // Initialize command metadata to handle the read preference. - BSONObjBuilder metadataBuilder; - rpc::ServerSelectionMetadata metadata(_readPreference.pref != ReadPreference::PrimaryOnly, - boost::none); - uassertStatusOK(metadata.writeToMetadata(&metadataBuilder)); - _metadataObj = metadataBuilder.obj(); + _metadataObj = readPreference.toContainingBSON(); // Schedule the requests immediately. diff --git a/src/mongo/s/commands/cluster_commands_common.cpp b/src/mongo/s/commands/cluster_commands_common.cpp index 24cfe617e98..a5e961eda2b 100644 --- a/src/mongo/s/commands/cluster_commands_common.cpp +++ b/src/mongo/s/commands/cluster_commands_common.cpp @@ -37,7 +37,6 @@ #include "mongo/db/query/cursor_response.h" #include "mongo/executor/task_executor_pool.h" #include "mongo/rpc/get_status_from_command_result.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/catalog/type_collection.h" #include "mongo/s/catalog_cache.h" #include "mongo/s/client/parallel.h" @@ -208,16 +207,7 @@ BSONObj appendShardVersion(const BSONObj& cmdObj, ChunkVersion version) { ReadPreferenceSetting getReadPref(const BSONObj& cmdObj) { const auto queryOptionsObj = cmdObj.getObjectField(QueryRequest::kUnwrappedReadPrefField); - const auto readPrefObj = queryOptionsObj.getObjectField(QueryRequest::kWrappedReadPrefField); - return readPrefObj.isEmpty() ? ReadPreferenceSetting(ReadPreference::PrimaryOnly, TagSet()) - : uassertStatusOK(ReadPreferenceSetting::fromBSON(readPrefObj)); -} - -ReadPreferenceSetting getReadPref(const rpc::ServerSelectionMetadata& ssm) { - return ssm.getReadPreference() - ? *ssm.getReadPreference() - : (ssm.isSecondaryOk() ? ReadPreferenceSetting(ReadPreference::SecondaryPreferred, TagSet()) - : ReadPreferenceSetting(ReadPreference::PrimaryOnly, TagSet())); + return uassertStatusOK(ReadPreferenceSetting::fromContainingBSON(queryOptionsObj)); } StatusWith<std::vector<AsyncRequestsSender::Response>> scatterGather( diff --git a/src/mongo/s/commands/cluster_commands_common.h b/src/mongo/s/commands/cluster_commands_common.h index 5b999272807..56bac9b8e6a 100644 --- a/src/mongo/s/commands/cluster_commands_common.h +++ b/src/mongo/s/commands/cluster_commands_common.h @@ -57,19 +57,6 @@ BSONObj appendShardVersion(const BSONObj& cmdObj, ChunkVersion version); ReadPreferenceSetting getReadPref(const BSONObj& cmdObj); /** - * Returns the read preference from the ServerSelectionMetadata if set or defaults to PrimaryOnly - * and an empty TagSet. - * - * This is used by explain commands, where the read preference is extracted and placed into - * ServerSelectionMetadata before the explain command is run. - * - * This is because only the value of the 'explain' field in the cmdObj is passed to the Command's - * explain() method, and the readPreference is at the same level as the 'explain' field: - * { explain: { find: "foo" }, $queryOptions: { $readPreference: { ... } } } - */ -ReadPreferenceSetting getReadPref(const rpc::ServerSelectionMetadata& ssm); - -/** * Broadcasts 'cmdObj' to all shards and returns the responses as a vector. * * Returns a non-OK status if a failure occurs on *this* node during execution. diff --git a/src/mongo/s/commands/cluster_find_cmd.cpp b/src/mongo/s/commands/cluster_find_cmd.cpp index 83598d92401..80e9508ae55 100644 --- a/src/mongo/s/commands/cluster_find_cmd.cpp +++ b/src/mongo/s/commands/cluster_find_cmd.cpp @@ -38,7 +38,6 @@ #include "mongo/db/stats/counters.h" #include "mongo/db/views/resolved_view.h" #include "mongo/rpc/get_status_from_command_result.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/commands/cluster_aggregate.h" #include "mongo/s/commands/strategy.h" #include "mongo/s/query/cluster_find.h" @@ -115,12 +114,8 @@ public: return qr.getStatus(); } - auto result = Strategy::explainFind(opCtx, - cmdObj, - *qr.getValue(), - verbosity, - rpc::ServerSelectionMetadata::get(opCtx), - out); + auto result = Strategy::explainFind( + opCtx, cmdObj, *qr.getValue(), verbosity, ReadPreferenceSetting::get(opCtx), out); if (result == ErrorCodes::CommandOnShardedViewNotSupportedOnMongod) { auto resolvedView = ResolvedView::fromBSON(out->asTempObj()); diff --git a/src/mongo/s/commands/cluster_pipeline_cmd.cpp b/src/mongo/s/commands/cluster_pipeline_cmd.cpp index 9841f87d6a4..010bed8f14b 100644 --- a/src/mongo/s/commands/cluster_pipeline_cmd.cpp +++ b/src/mongo/s/commands/cluster_pipeline_cmd.cpp @@ -33,7 +33,6 @@ #include "mongo/base/status.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/commands.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/commands/cluster_aggregate.h" namespace mongo { @@ -81,13 +80,15 @@ public: const BSONObj& cmdObj, ExplainOptions::Verbosity verbosity, BSONObjBuilder* out) const override { - // Add the server selection metadata to the aggregate command in the "unwrapped" format that + // Add the read preference to the aggregate command in the "unwrapped" format that // runAggregate() expects: {aggregate: ..., $queryOptions: {$readPreference: ...}}. + const auto& readPref = ReadPreferenceSetting::get(opCtx); BSONObjBuilder aggCmdBuilder; aggCmdBuilder.appendElements(cmdObj); - if (auto readPref = rpc::ServerSelectionMetadata::get(opCtx).getReadPreference()) { - aggCmdBuilder.append(QueryRequest::kUnwrappedReadPrefField, - BSON("$readPreference" << readPref->toBSON())); + if (readPref.canRunOnSecondary()) { + auto queryOptionsBuilder = + BSONObjBuilder(aggCmdBuilder.subobjStart(QueryRequest::kUnwrappedReadPrefField)); + readPref.toContainingBSON(&queryOptionsBuilder); } BSONObj aggCmd = aggCmdBuilder.obj(); diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp index 7500509f366..11cd2ef1381 100644 --- a/src/mongo/s/commands/strategy.cpp +++ b/src/mongo/s/commands/strategy.cpp @@ -55,7 +55,6 @@ #include "mongo/db/views/resolved_view.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/rpc/metadata/logical_time_metadata.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/rpc/metadata/tracking_metadata.h" #include "mongo/s/catalog_cache.h" #include "mongo/s/client/parallel.h" @@ -224,20 +223,11 @@ DbResponse Strategy::queryOp(OperationContext* opCtx, const NamespaceString& nss } // Determine the default read preference mode based on the value of the slaveOk flag. - const ReadPreferenceSetting readPreference = [&]() { - BSONElement rpElem; - auto readPrefExtractStatus = bsonExtractTypedField( - q.query, QueryRequest::kWrappedReadPrefField, mongo::Object, &rpElem); - if (readPrefExtractStatus == ErrorCodes::NoSuchKey) { - return ReadPreferenceSetting(q.queryOptions & QueryOption_SlaveOk - ? ReadPreference::SecondaryPreferred - : ReadPreference::PrimaryOnly); - } - - uassertStatusOK(readPrefExtractStatus); - - return uassertStatusOK(ReadPreferenceSetting::fromBSON(rpElem.Obj())); - }(); + const auto defaultReadPref = q.queryOptions & QueryOption_SlaveOk + ? ReadPreference::SecondaryPreferred + : ReadPreference::PrimaryOnly; + const auto readPreference = + uassertStatusOK(ReadPreferenceSetting::fromContainingBSON(q.query, defaultReadPref)); auto canonicalQuery = uassertStatusOK(CanonicalQuery::canonicalize(opCtx, q, ExtensionsCallbackNoop())); @@ -251,12 +241,9 @@ DbResponse Strategy::queryOp(OperationContext* opCtx, const NamespaceString& nss // We default to allPlansExecution verbosity. const auto verbosity = ExplainOptions::Verbosity::kExecAllPlans; - const bool secondaryOk = (readPreference.pref != ReadPreference::PrimaryOnly); - const rpc::ServerSelectionMetadata metadata(secondaryOk, readPreference); - BSONObjBuilder explainBuilder; uassertStatusOK(Strategy::explainFind( - opCtx, findCommand, queryRequest, verbosity, metadata, &explainBuilder)); + opCtx, findCommand, queryRequest, verbosity, readPreference, &explainBuilder)); BSONObj explainObj = explainBuilder.done(); return replyToQuery(explainObj); @@ -357,10 +344,9 @@ DbResponse Strategy::clientCommandOp(OperationContext* opCtx, // The command has a read preference setting. We don't want to lose this information // so we put it on the OperationContext and copy it to a new field called // $queryOptions.$readPreference - auto readPref = - uassertStatusOK(ReadPreferenceSetting::fromBSON(readPrefElem.Obj())); - rpc::ServerSelectionMetadata::get(opCtx) = rpc::ServerSelectionMetadata( - readPref.pref != ReadPreference::PrimaryOnly, readPref); + ReadPreferenceSetting::get(opCtx) = + uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(readPrefElem)); + haveReadPref = true; BSONObjBuilder finalCmdObjBuilder; finalCmdObjBuilder.appendElements(e.embeddedObject()); @@ -378,16 +364,15 @@ DbResponse Strategy::clientCommandOp(OperationContext* opCtx, if (!haveReadPref && q.queryOptions & QueryOption_SlaveOk) { // If the slaveOK bit is set, behave as-if read preference secondary-preferred was // specified. - rpc::ServerSelectionMetadata::get(opCtx) = rpc::ServerSelectionMetadata( - true, ReadPreferenceSetting(ReadPreference::SecondaryPreferred)); + const auto readPref = ReadPreferenceSetting(ReadPreference::SecondaryPreferred); + ReadPreferenceSetting::get(opCtx) = readPref; + BSONObjBuilder finalCmdObjBuilder; finalCmdObjBuilder.appendElements(cmdObj); BSONObjBuilder queryOptionsBuilder(finalCmdObjBuilder.subobjStart("$queryOptions")); - queryOptionsBuilder.append( - Query::ReadPrefField.name(), - ReadPreferenceSetting(ReadPreference::SecondaryPreferred).toBSON()); - queryOptionsBuilder.done(); + readPref.toContainingBSON(&queryOptionsBuilder); + queryOptionsBuilder.doneFast(); cmdObj = finalCmdObjBuilder.obj(); } @@ -624,7 +609,7 @@ Status Strategy::explainFind(OperationContext* opCtx, const BSONObj& findCommand, const QueryRequest& qr, ExplainOptions::Verbosity verbosity, - const rpc::ServerSelectionMetadata& ssm, + const ReadPreferenceSetting& readPref, BSONObjBuilder* out) { const auto explainCmd = ClusterExplain::wrapAsExplain(findCommand, verbosity); @@ -635,7 +620,7 @@ Status Strategy::explainFind(OperationContext* opCtx, auto swShardResponses = scatterGatherForNamespace(opCtx, qr.nss(), explainCmd, - getReadPref(ssm), + readPref, qr.getFilter(), qr.getCollation(), true, // do shard versioning diff --git a/src/mongo/s/commands/strategy.h b/src/mongo/s/commands/strategy.h index f91e3d61793..9da42bae611 100644 --- a/src/mongo/s/commands/strategy.h +++ b/src/mongo/s/commands/strategy.h @@ -42,10 +42,6 @@ class NamespaceString; class OperationContext; class QueryRequest; -namespace rpc { -class ServerSelectionMetadata; -} // namespace rpc - /** * Legacy interface for processing client read/write/cmd requests. */ @@ -101,7 +97,7 @@ public: const BSONObj& findCommand, const QueryRequest& qr, ExplainOptions::Verbosity verbosity, - const rpc::ServerSelectionMetadata& serverSelectionMetadata, + const ReadPreferenceSetting& readPref, BSONObjBuilder* out); struct CommandResult { diff --git a/src/mongo/s/query/async_results_merger.cpp b/src/mongo/s/query/async_results_merger.cpp index bd3c67bf44a..ced16197e98 100644 --- a/src/mongo/s/query/async_results_merger.cpp +++ b/src/mongo/s/query/async_results_merger.cpp @@ -38,7 +38,6 @@ #include "mongo/db/query/killcursors_request.h" #include "mongo/executor/remote_command_request.h" #include "mongo/executor/remote_command_response.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/grid.h" #include "mongo/util/assert_util.h" @@ -72,11 +71,7 @@ AsyncResultsMerger::AsyncResultsMerger(executor::TaskExecutor* executor, // is primaryOnly, in which case if the remote host for one of the cursors changes roles, the // remote will return an error. if (_params->readPreference) { - BSONObjBuilder metadataBuilder; - rpc::ServerSelectionMetadata metadata( - _params->readPreference->pref != ReadPreference::PrimaryOnly, boost::none); - uassertStatusOK(metadata.writeToMetadata(&metadataBuilder)); - _metadataObj = metadataBuilder.obj(); + _metadataObj = _params->readPreference->toContainingBSON(); } } diff --git a/src/mongo/s/query/async_results_merger_test.cpp b/src/mongo/s/query/async_results_merger_test.cpp index 3396649256c..bca910ce347 100644 --- a/src/mongo/s/query/async_results_merger_test.cpp +++ b/src/mongo/s/query/async_results_merger_test.cpp @@ -39,7 +39,6 @@ #include "mongo/executor/network_interface_mock.h" #include "mongo/executor/task_executor.h" #include "mongo/executor/thread_pool_task_executor_test_fixture.h" -#include "mongo/rpc/metadata/server_selection_metadata.h" #include "mongo/s/catalog/type_shard.h" #include "mongo/s/client/shard_registry.h" #include "mongo/s/sharding_test_fixture.h" @@ -1226,7 +1225,9 @@ TEST_F(AsyncResultsMergerTest, SendsSecondaryOkAsMetadata) { ASSERT_FALSE(arm->ready()); BSONObj cmdRequestMetadata = getFirstPendingRequest().metadata; - ASSERT_BSONOBJ_EQ(cmdRequestMetadata, rpc::ServerSelectionMetadata(true, boost::none).toBSON()); + ASSERT(uassertStatusOK(ReadPreferenceSetting::fromContainingBSON(cmdRequestMetadata)) + .canRunOnSecondary()) + << "full metadata: " << cmdRequestMetadata; std::vector<CursorResponse> responses; std::vector<BSONObj> batch1 = {fromjson("{_id: 1}")}; diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp index 0dd13e834d5..c282fee7800 100644 --- a/src/mongo/s/query/cluster_find.cpp +++ b/src/mongo/s/query/cluster_find.cpp @@ -470,15 +470,7 @@ StatusWith<ReadPreferenceSetting> ClusterFind::extractUnwrappedReadPref(const BS if (status.isOK()) { // There must be a nested object containing the read preference if there is a queryOptions // field. - BSONObj queryOptionsObj = queryOptionsElt.Obj(); - invariant(queryOptionsObj[QueryRequest::kWrappedReadPrefField].type() == BSONType::Object); - BSONObj readPrefObj = queryOptionsObj[QueryRequest::kWrappedReadPrefField].Obj(); - - auto readPref = ReadPreferenceSetting::fromBSON(readPrefObj); - if (!readPref.isOK()) { - return readPref.getStatus(); - } - return readPref.getValue(); + return ReadPreferenceSetting::fromContainingBSON(queryOptionsElt.Obj()); } else if (status != ErrorCodes::NoSuchKey) { return status; } diff --git a/src/mongo/s/query/cluster_find.h b/src/mongo/s/query/cluster_find.h index 8cf65aac017..492c0dc81e2 100644 --- a/src/mongo/s/query/cluster_find.h +++ b/src/mongo/s/query/cluster_find.h @@ -44,10 +44,6 @@ class OperationContext; struct GetMoreRequest; struct ReadPreferenceSetting; -namespace rpc { -class ServerSelectionMetadata; -} // namespace rpc - /** * Methods for running find and getMore operations across a sharded cluster. */ |