diff options
author | Kaloian Manassiev <kaloian.manassiev@mongodb.com> | 2021-02-10 12:33:29 +0100 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-02-10 12:11:44 +0000 |
commit | 0dc7dd13c6b5d99d6c22d44f7ef96750f3540e50 (patch) | |
tree | 9de22c8a63b9aae80d0c32316e7249d983a35e08 | |
parent | eab7770928e86e0e70a035da87aa3fa616b9cc42 (diff) | |
download | mongo-0dc7dd13c6b5d99d6c22d44f7ef96750f3540e50.tar.gz |
Revert "SERVER-53150 Specify input/output to hello command"
This reverts commit 92dfc822d41714b47bc20e260aafb54884909acc.
-rw-r--r-- | jstests/noPassthrough/awaitable_hello.js | 7 | ||||
-rw-r--r-- | src/mongo/db/repl/SConscript | 17 | ||||
-rw-r--r-- | src/mongo/db/repl/hello.idl | 247 | ||||
-rw-r--r-- | src/mongo/db/repl/hello_auth.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/repl/hello_auth.h | 3 | ||||
-rw-r--r-- | src/mongo/db/repl/replication_info.cpp | 217 | ||||
-rw-r--r-- | src/mongo/rpc/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata.cpp | 109 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata.h | 20 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata.idl | 44 | ||||
-rw-r--r-- | src/mongo/s/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_hello_cmd.cpp | 140 | ||||
-rw-r--r-- | src/mongo/transport/message_compressor_manager.cpp | 41 | ||||
-rw-r--r-- | src/mongo/transport/message_compressor_manager.h | 7 | ||||
-rw-r--r-- | src/mongo/transport/message_compressor_manager_test.cpp | 41 |
15 files changed, 323 insertions, 600 deletions
diff --git a/jstests/noPassthrough/awaitable_hello.js b/jstests/noPassthrough/awaitable_hello.js index b65b7d63476..871396ffc21 100644 --- a/jstests/noPassthrough/awaitable_hello.js +++ b/jstests/noPassthrough/awaitable_hello.js @@ -8,9 +8,6 @@ load("jstests/libs/fail_point_util.js"); load("jstests/libs/parallel_shell_helpers.js"); -// ErrorCodes -const kIDLParserComparisonError = 51024; - // runTest takes in the hello command or its aliases, isMaster and ismaster. function runTest(db, cmd, logFailpoint) { // Check the command response contains a topologyVersion even if maxAwaitTimeMS and @@ -113,7 +110,7 @@ function runTest(db, cmd, logFailpoint) { // Check that passing a topologyVersion not of type object fails. assert.commandFailedWithCode( db.runCommand({[cmd]: 1, topologyVersion: "topology_version_string", maxAwaitTimeMS: 0}), - ErrorCodes.TypeMismatch); + 10065); // Check that a topologyVersion with an invalid processId and valid counter fails. assert.commandFailedWithCode(db.runCommand({ @@ -191,7 +188,7 @@ function runTest(db, cmd, logFailpoint) { topologyVersion: topologyVersionField, maxAwaitTimeMS: -1, }), - [31373, 51759, kIDLParserComparisonError]); + [31373, 51759]); // Check that the command fails if the awaitable flag is present but either the topologyVersion, // the maxAwaitTimeMS, or both are missing. diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index 7dcf76a2ff7..b2747bf9a24 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -1199,20 +1199,6 @@ env.Library( ) env.Library( - target="hello_command", - source=[ - "hello.idl", - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/base', - ], - LIBDEPS_PRIVATE=[ - '$BUILD_DIR/mongo/idl/idl_parser', - '$BUILD_DIR/mongo/rpc/metadata', - ], -) - -env.Library( target="replication_info", source=[ "replication_info.cpp", @@ -1222,7 +1208,6 @@ env.Library( '$BUILD_DIR/mongo/client/clientdriver_network', '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/saslauth', - '$BUILD_DIR/mongo/db/commands/test_commands_enabled', '$BUILD_DIR/mongo/db/dbhelpers', '$BUILD_DIR/mongo/db/query_exec', "$BUILD_DIR/mongo/util/fail_point", @@ -1236,7 +1221,6 @@ env.Library( '$BUILD_DIR/mongo/db/stats/counters', '$BUILD_DIR/mongo/transport/message_compressor', 'hello_auth', - 'hello_command', 'primary_only_service', 'replication_auth', 'split_horizon', @@ -1760,7 +1744,6 @@ env.Library( '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/db/auth/authentication_session', '$BUILD_DIR/mongo/db/auth/authservercommon', - 'hello_command', ], ) diff --git a/src/mongo/db/repl/hello.idl b/src/mongo/db/repl/hello.idl deleted file mode 100644 index 452b1423565..00000000000 --- a/src/mongo/db/repl/hello.idl +++ /dev/null @@ -1,247 +0,0 @@ -# Copyright (C) 2021-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# 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 -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# <http://www.mongodb.com/licensing/server-side-public-license>. -# -# 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 Server Side 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. -# - -global: - cpp_namespace: "mongo" - -imports: - - "mongo/db/auth/auth_types.idl" - - "mongo/db/repl/replication_types.idl" - - "mongo/idl/basic_types.idl" - - "mongo/rpc/metadata/client_metadata.idl" - - "mongo/rpc/topology_version.idl" - -structs: - HelloInternalClientField: - description: "Specifies min/max wire protocol versions" - strict: true - fields: - minWireVersion: - # Currently ignored - type: safeInt - default: 0 - maxWireVersion: - type: safeInt - - HelloLastWrite: - description: "Most recent op/write times for this node" - strict: true - fields: - opTime: - type: optime - optional: true - lastWriteDate: - type: date - optional: true - majorityOpTime: - type: optime - optional: true - majorityWriteDate: - type: date - optional: true - - HelloCommandReply: - description: "Reply to 'hello' command" - strict: true - fields: - helloOk: - type: bool - default: true - clientSupportsHello: - type: bool - optional: true - configsvr: - type: safeInt - optional: true - maxBsonObjectSize: - type: safeInt64 - optional: true - maxMessageSizeBytes: - type: safeInt64 - optional: true - maxWriteBatchSize: - type: safeInt64 - optional: true - localTime: - type: date - optional: true - logicalSessionTimeoutMinutes: - type: safeInt - optional: true - connectionId: - type: safeInt64 - optional: true - minWireVersion: - type: safeInt - optional: true - maxWireVersion: - type: safeInt - optional: true - readOnly: - type: bool - optional: true - compression: - type: array<string> - optional: true - automationServiceDescriptor: - type: string - optional: true - saslSupportedMechs: - type: array<string> - optional: true - speculativeAuthenticate: - type: object - optional: true - msg: - type: string - optional: true - ## - ## ReplicationInfo - ## - topologyVersion: - type: TopologyVersion - ismaster: - # Replies will contain 'ismaster' OR 'isWritablePrimary', not both - type: bool - optional: true - isWritablePrimary: - type: bool - optional: true - ## - ## Using ReplSets - ## - hosts: - type: array<string> - optional: true - passives: - type: array<string> - optional: true - arbiters: - type: array<string> - optional: true - setName: - type: string - optional: true - primary: - type: string - optional: true - secondary: - type: bool - optional: true - info: - type: string - optional: true - isreplicaset: - type: bool - optional: true - setVersion: - type: safeInt - optional: true - arbiterOnly: - type: bool - optional: true - passive: - type: bool - optional: true - hidden: - type: bool - optional: true - buildIndexes: - type: bool - optional: true - slaveDelay: - # Reply will contain either slaveDelay or secondaryDelaySecs, but not both. - type: safeInt64 - optional: true - secondaryDelaySecs: - type: safeInt64 - optional: true - tags: - type: object - optional: true - me: - type: string - optional: true - electionId: - type: objectid - optional: true - lastWrite: - type: HelloLastWrite - optional: true - -commands: - hello: - # Aliases: 'isMaster', 'ismaster' - description: "Check if this server is primary for a replica set { hello: 1 }" - command_name: hello - namespace: ignored - cpp_name: HelloCommand - api_version: "1" - reply_type: HelloCommandReply - strict: true - fields: - awaitable: - type: safeBool - optional: true - unstable: true - forShell: - type: safeBool - default: false - unstable: true - hostInfo: - type: string - default: false - hangUpOnStepDown: - type: safeBool - default: true - internalClient: - type: HelloInternalClientField - optional: true - unstable: true - client: - type: ClientMetadata - optional: true - topologyVersion: - type: TopologyVersion - optional: true - maxAwaitTimeMS: - type: safeInt64 - optional: true - validator: { gte: 0 } - helloOk: - type: safeBool - optional: true - compression: - type: array<string> - optional: true - saslSupportedMechs: - type: UserName - optional: true - speculativeAuthenticate: - type: object - optional: true diff --git a/src/mongo/db/repl/hello_auth.cpp b/src/mongo/db/repl/hello_auth.cpp index efc79fdc1dc..b88ea5b1325 100644 --- a/src/mongo/db/repl/hello_auth.cpp +++ b/src/mongo/db/repl/hello_auth.cpp @@ -39,33 +39,39 @@ namespace mongo { -void handleHelloAuth(OperationContext* opCtx, const HelloCommand& cmd, BSONObjBuilder* result) { - // saslSupportedMechs: UserName -> List[String] - if (auto userName = cmd.getSaslSupportedMechs()) { +void handleHelloAuth(OperationContext* opCtx, BSONObj cmdObj, BSONObjBuilder* result) { + auto ssme = cmdObj[auth::kSaslSupportedMechanisms]; + if (ssme.type() == BSONType::String) { AuthenticationSession::doStep( opCtx, AuthenticationSession::StepType::kSaslSupportedMechanisms, [&](auto session) { + UserName userName = uassertStatusOK(UserName::parse(ssme.String())); + auto& saslMechanismRegistry = SASLServerMechanismRegistry::get(opCtx->getServiceContext()); - saslMechanismRegistry.advertiseMechanismNamesForUser(opCtx, *userName, result); + saslMechanismRegistry.advertiseMechanismNamesForUser(opCtx, userName, result); }); } - // speculativeAuthenticate: SaslStart -> SaslReply or Authenticate -> AuthenticateReply - auto specAuth = cmd.getSpeculativeAuthenticate(); - if (!specAuth) { + auto sae = cmdObj[auth::kSpeculativeAuthenticate]; + if (sae.eoo()) { return; } uassert(ErrorCodes::BadValue, + str::stream() << "hello." << auth::kSpeculativeAuthenticate << " must be an Object", + sae.type() == Object); + auto specAuth = sae.Obj(); + + uassert(ErrorCodes::BadValue, str::stream() << "hello." << auth::kSpeculativeAuthenticate << " must be a non-empty Object", - !specAuth->isEmpty()); - auto specCmd = specAuth->firstElementFieldNameStringData(); + !specAuth.isEmpty()); + auto specCmd = specAuth.firstElementFieldNameStringData(); if (specCmd == saslStartCommandName) { - doSpeculativeSaslStart(opCtx, *specAuth, result); + doSpeculativeSaslStart(opCtx, specAuth, result); } else if (specCmd == auth::kAuthenticateCommand) { - doSpeculativeAuthenticate(opCtx, *specAuth, result); + doSpeculativeAuthenticate(opCtx, specAuth, result); } else { uasserted(51769, str::stream() << "hello." << auth::kSpeculativeAuthenticate diff --git a/src/mongo/db/repl/hello_auth.h b/src/mongo/db/repl/hello_auth.h index 8f8a73269db..1ec1b4d1658 100644 --- a/src/mongo/db/repl/hello_auth.h +++ b/src/mongo/db/repl/hello_auth.h @@ -32,7 +32,6 @@ #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/db/operation_context.h" -#include "mongo/db/repl/hello_gen.h" namespace mongo { @@ -42,6 +41,6 @@ namespace mongo { * This will attach supported mechanisms or invoke the behavior of saslStart/authenticate commands * as appropriate. */ -void handleHelloAuth(OperationContext* opCtx, const HelloCommand& cmd, BSONObjBuilder* result); +void handleHelloAuth(OperationContext* opCtx, BSONObj cmdObj, BSONObjBuilder* result); } // namespace mongo diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp index ba7c3131844..7c051a60ebd 100644 --- a/src/mongo/db/repl/replication_info.cpp +++ b/src/mongo/db/repl/replication_info.cpp @@ -39,7 +39,6 @@ #include "mongo/client/dbclient_connection.h" #include "mongo/db/client.h" #include "mongo/db/commands/server_status.h" -#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/curop.h" #include "mongo/db/db_raii.h" #include "mongo/db/dbhelpers.h" @@ -51,7 +50,6 @@ #include "mongo/db/ops/write_ops.h" #include "mongo/db/query/internal_plans.h" #include "mongo/db/repl/hello_auth.h" -#include "mongo/db/repl/hello_gen.h" #include "mongo/db/repl/hello_response.h" #include "mongo/db/repl/primary_only_service.h" #include "mongo/db/repl/replication_auth.h" @@ -103,7 +101,7 @@ TopologyVersion appendReplicationInfo(OperationContext* opCtx, bool appendReplicationProcess, bool useLegacyResponseFields, boost::optional<TopologyVersion> clientTopologyVersion, - boost::optional<std::int64_t> maxAwaitTimeMS) { + boost::optional<long long> maxAwaitTimeMS) { TopologyVersion topologyVersion; ReplicationCoordinator* replCoord = ReplicationCoordinator::get(opCtx); if (replCoord->getSettings().usingReplSets()) { @@ -239,14 +237,11 @@ public: } } oplogInfoServerStatus; -const std::string kAutomationServiceDescriptorFieldName = - HelloCommandReply::kAutomationServiceDescriptorFieldName.toString(); - class CmdHello : public BasicCommandWithReplyBuilderInterface { public: CmdHello() : CmdHello(kHelloString, {}) {} - const std::set<std::string>& apiVersions() const final { + const std::set<std::string>& apiVersions() const { return kApiVersions1; } @@ -276,15 +271,13 @@ public: const BSONObj& cmdObj, rpc::ReplyBuilderInterface* replyBuilder) final { CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); - const bool apiStrict = APIParameters::get(opCtx).getAPIStrict().value_or(false); - auto cmd = HelloCommand::parse({"hello", apiStrict}, cmdObj); waitInHello.pauseWhileSet(opCtx); /* currently request to arbiter is (somewhat arbitrarily) an ismaster request that is not authenticated. */ - if (cmd.getForShell()) { + if (cmdObj["forShell"].trueValue()) { LastError::get(opCtx->getClient()).disable(); } @@ -292,7 +285,8 @@ public: transport::Session::TagMask sessionTagsToUnset = 0; // Tag connections to avoid closing them on stepdown. - if (!cmd.getHangUpOnStepDown()) { + auto hangUpElement = cmdObj["hangUpOnStepDown"]; + if (!hangUpElement.eoo() && !hangUpElement.trueValue()) { sessionTagsToSet |= transport::Session::kKeepOpen; } @@ -305,18 +299,51 @@ public: // Parse the optional 'internalClient' field. This is provided by incoming connections from // mongod and mongos. - if (auto internalClient = cmd.getInternalClient()) { + auto internalClientElement = cmdObj["internalClient"]; + if (internalClientElement) { sessionTagsToSet |= transport::Session::kInternalClient; sessionTagsToUnset |= transport::Session::kExternalClientKeepOpen; - // All incoming connections from mongod/mongos of earlier versions should be - // closed if the featureCompatibilityVersion is bumped to 3.6. - if (internalClient->getMaxWireVersion() >= - WireSpec::instance().get()->incomingInternalClient.maxWireVersion) { - sessionTagsToSet |= transport::Session::kLatestVersionInternalClientKeepOpen; - } else { - sessionTagsToUnset |= transport::Session::kLatestVersionInternalClientKeepOpen; + uassert(ErrorCodes::TypeMismatch, + str::stream() << "'internalClient' must be of type Object, but was of type " + << typeName(internalClientElement.type()), + internalClientElement.type() == BSONType::Object); + + bool foundMaxWireVersion = false; + for (auto&& elem : internalClientElement.Obj()) { + auto fieldName = elem.fieldNameStringData(); + if (fieldName == "minWireVersion") { + // We do not currently use 'internalClient.minWireVersion'. + continue; + } else if (fieldName == "maxWireVersion") { + foundMaxWireVersion = true; + + uassert(ErrorCodes::TypeMismatch, + str::stream() << "'maxWireVersion' field of 'internalClient' must be " + "of type int, but was of type " + << typeName(elem.type()), + elem.type() == BSONType::NumberInt); + + // All incoming connections from mongod/mongos of earlier versions should be + // closed if the featureCompatibilityVersion is bumped to 3.6. + if (elem.numberInt() >= + WireSpec::instance().get()->incomingInternalClient.maxWireVersion) { + sessionTagsToSet |= + transport::Session::kLatestVersionInternalClientKeepOpen; + } else { + sessionTagsToUnset |= + transport::Session::kLatestVersionInternalClientKeepOpen; + } + } else { + uasserted(ErrorCodes::BadValue, + str::stream() << "Unrecognized field of 'internalClient': '" + << fieldName << "'"); + } } + + uassert(ErrorCodes::BadValue, + "Missing required field 'maxWireVersion' of 'internalClient'", + foundMaxWireVersion); } else { sessionTagsToUnset |= (transport::Session::kInternalClient | transport::Session::kLatestVersionInternalClientKeepOpen); @@ -342,36 +369,53 @@ public: // If a client is following the awaitable hello protocol, maxAwaitTimeMS should be // present if and only if topologyVersion is present in the request. - auto clientTopologyVersion = cmd.getTopologyVersion(); - auto maxAwaitTimeMS = cmd.getMaxAwaitTimeMS(); + auto topologyVersionElement = cmdObj["topologyVersion"]; + auto maxAwaitTimeMSField = cmdObj["maxAwaitTimeMS"]; auto curOp = CurOp::get(opCtx); + + // The awaitable field is optional, but if it exists, it requires both other fields. + // Note that the awaitable field only exists to make filtering out these operations + // via currentOp queries easier and is not otherwise explicitly used. + { + bool awaitable = true; + Status status = bsonExtractBooleanField(cmdObj, "awaitable", &awaitable); + if (status.isOK() && (!topologyVersionElement || !maxAwaitTimeMSField)) { + uassert(5135800, + "A request marked awaitable must contain both 'topologyVersion' and " + "'maxAwaitTimeMS'", + !awaitable); + } + } + + boost::optional<TopologyVersion> clientTopologyVersion; + boost::optional<long long> maxAwaitTimeMS; boost::optional<ScopeGuard<std::function<void()>>> timerGuard; - if (clientTopologyVersion && maxAwaitTimeMS) { + if (topologyVersionElement && maxAwaitTimeMSField) { + clientTopologyVersion = TopologyVersion::parse(IDLParserErrorContext("TopologyVersion"), + topologyVersionElement.Obj()); uassert(31372, "topologyVersion must have a non-negative counter", clientTopologyVersion->getCounter() >= 0); - LOGV2_DEBUG(23904, - 3, - "Using maxAwaitTimeMS for awaitable hello protocol", - "maxAwaitTimeMS"_attr = maxAwaitTimeMS.get()); + { + long long parsedMaxAwaitTimeMS; + uassertStatusOK( + bsonExtractIntegerField(cmdObj, "maxAwaitTimeMS", &parsedMaxAwaitTimeMS)); + maxAwaitTimeMS = parsedMaxAwaitTimeMS; + } + + uassert(31373, "maxAwaitTimeMS must be a non-negative integer", *maxAwaitTimeMS >= 0); + + LOGV2_DEBUG(23904, 3, "Using maxAwaitTimeMS for awaitable hello protocol."); curOp->pauseTimer(); timerGuard.emplace([curOp]() { curOp->resumeTimer(); }); } else { - // The awaitable field is optional, but if it exists, it requires both other fields. - // Note that the awaitable field only exists to make filtering out these operations - // via currentOp queries easier and is not otherwise explicitly used. - uassert(5135800, - "A request marked awaitable must contain both 'topologyVersion' and " - "'maxAwaitTimeMS'", - !cmd.getAwaitable() || !cmd.getAwaitable()); - uassert(31368, - (clientTopologyVersion + (topologyVersionElement ? "A request with a 'topologyVersion' must include 'maxAwaitTimeMS'" : "A request with 'maxAwaitTimeMS' must include a 'topologyVersion'"), - !clientTopologyVersion && !maxAwaitTimeMS); + !topologyVersionElement && !maxAwaitTimeMSField); } auto result = replyBuilder->getBodyBuilder(); @@ -380,18 +424,21 @@ public: // handshake for an incoming connection if the client supports the hello command. Clients // that specify 'helloOk' do not rely on "not master" error message parsing, which means // that we can safely return "not primary" error messages instead. - if (auto helloOk = cmd.getHelloOk()) { + bool helloOk = client->supportsHello(); + Status status = bsonExtractBooleanField(cmdObj, "helloOk", &helloOk); + if (status.isOK()) { // If the hello request contains a "helloOk" field, set _supportsHello on the Client // to the value. - client->setSupportsHello(*helloOk); + client->setSupportsHello(helloOk); // Attach helloOk: true to the response so that the client knows the server supports // the hello command. - result.append(HelloCommandReply::kHelloOkFieldName, true); + result.append("helloOk", true); + } else if (status.code() != ErrorCodes::NoSuchKey) { + uassertStatusOK(status); } if (MONGO_unlikely(appendHelloOkToHelloResponse.shouldFail())) { - result.append(HelloCommandReply::kClientSupportsHelloFieldName, - client->supportsHello()); + result.append("clientSupportsHello", client->supportsHello()); } auto currentTopologyVersion = appendReplicationInfo( @@ -400,44 +447,35 @@ public: timerGuard.reset(); // Resume curOp timer. if (serverGlobalParams.clusterRole == ClusterRole::ConfigServer) { - constexpr int kConfigServerModeNumber = 2; - result.append(HelloCommandReply::kConfigsvrFieldName, kConfigServerModeNumber); + const int configServerModeNumber = 2; + result.append("configsvr", configServerModeNumber); } - result.appendNumber(HelloCommandReply::kMaxBsonObjectSizeFieldName, BSONObjMaxUserSize); - result.appendNumber(HelloCommandReply::kMaxMessageSizeBytesFieldName, MaxMessageSizeBytes); - result.appendNumber(HelloCommandReply::kMaxWriteBatchSizeFieldName, - write_ops::kMaxWriteBatchSize); - result.appendDate(HelloCommandReply::kLocalTimeFieldName, jsTime()); - result.append(HelloCommandReply::kLogicalSessionTimeoutMinutesFieldName, - localLogicalSessionTimeoutMinutes); - result.appendNumber(HelloCommandReply::kConnectionIdFieldName, - opCtx->getClient()->getConnectionId()); - - - if (auto wireSpec = WireSpec::instance().get(); cmd.getInternalClient()) { - result.append(HelloCommandReply::kMinWireVersionFieldName, - wireSpec->incomingInternalClient.minWireVersion); - result.append(HelloCommandReply::kMaxWireVersionFieldName, - wireSpec->incomingInternalClient.maxWireVersion); + result.appendNumber("maxBsonObjectSize", BSONObjMaxUserSize); + result.appendNumber("maxMessageSizeBytes", MaxMessageSizeBytes); + result.appendNumber("maxWriteBatchSize", write_ops::kMaxWriteBatchSize); + result.appendDate("localTime", jsTime()); + result.append("logicalSessionTimeoutMinutes", localLogicalSessionTimeoutMinutes); + result.appendNumber("connectionId", opCtx->getClient()->getConnectionId()); + + if (auto wireSpec = WireSpec::instance().get(); internalClientElement) { + result.append("minWireVersion", wireSpec->incomingInternalClient.minWireVersion); + result.append("maxWireVersion", wireSpec->incomingInternalClient.maxWireVersion); } else { - result.append(HelloCommandReply::kMinWireVersionFieldName, - wireSpec->incomingExternalClient.minWireVersion); - result.append(HelloCommandReply::kMaxWireVersionFieldName, - wireSpec->incomingExternalClient.maxWireVersion); + result.append("minWireVersion", wireSpec->incomingExternalClient.minWireVersion); + result.append("maxWireVersion", wireSpec->incomingExternalClient.maxWireVersion); } - result.append(HelloCommandReply::kReadOnlyFieldName, storageGlobalParams.readOnly); + result.append("readOnly", storageGlobalParams.readOnly); const auto& params = ServerParameterSet::getGlobal()->getMap(); - if (auto iter = params.find(kAutomationServiceDescriptorFieldName); - iter != params.end() && iter->second) { - iter->second->append(opCtx, result, kAutomationServiceDescriptorFieldName); - } + if (auto iter = params.find("automationServiceDescriptor"); + iter != params.end() && iter->second) + iter->second->append(opCtx, result, "automationServiceDescriptor"); if (opCtx->getClient()->session()) { MessageCompressorManager::forSession(opCtx->getClient()->session()) - .serverNegotiate(cmd.getCompression(), &result); + .serverNegotiate(cmdObj, &result); } if (opCtx->isExhaust()) { @@ -445,7 +483,7 @@ public: uassert(51756, "An isMaster or hello request with exhaust must specify 'maxAwaitTimeMS'", - maxAwaitTimeMS); + maxAwaitTimeMSField); invariant(clientTopologyVersion); InExhaustHello::get(opCtx->getClient()->session().get()) @@ -457,45 +495,30 @@ public: // command parameters should be reused as the next BSONObj command parameters. replyBuilder->setNextInvocation(boost::none); } else { - BSONObjBuilder niBuilder; - for (const auto& elem : cmdObj) { - if (elem.fieldNameStringData() == HelloCommand::kTopologyVersionFieldName) { - BSONObjBuilder tvBuilder( - niBuilder.subobjStart(HelloCommand::kTopologyVersionFieldName)); - currentTopologyVersion.serialize(&tvBuilder); + BSONObjBuilder nextInvocationBuilder; + for (auto&& elt : cmdObj) { + if (elt.fieldNameStringData() == "topologyVersion"_sd) { + BSONObjBuilder topologyVersionBuilder( + nextInvocationBuilder.subobjStart("topologyVersion")); + currentTopologyVersion.serialize(&topologyVersionBuilder); } else { - niBuilder.append(elem); + nextInvocationBuilder.append(elt); } } - replyBuilder->setNextInvocation(niBuilder.obj()); + replyBuilder->setNextInvocation(nextInvocationBuilder.obj()); } } - handleHelloAuth(opCtx, cmd, &result); + handleHelloAuth(opCtx, cmdObj, &result); - if (getTestCommandsEnabled()) { - validateResult(&result); - } return true; } - void validateResult(BSONObjBuilder* result) { - auto ret = result->asTempObj(); - if (ret[ErrorReply::kErrmsgFieldName].eoo()) { - // Nominal success case, parse the object as-is. - HelloCommandReply::parse({"hello.reply"}, ret); - } else { - // Something went wrong, still try to parse, but accept a few ignorable fields. - StringDataSet ignorable({ErrorReply::kCodeFieldName, ErrorReply::kErrmsgFieldName}); - HelloCommandReply::parse({"hello.reply"}, ret.removeFields(ignorable)); - } - } - protected: CmdHello(const StringData cmdName, const std::initializer_list<StringData>& alias) : BasicCommandWithReplyBuilderInterface(cmdName, alias) {} - virtual bool useLegacyResponseFields() const { + virtual bool useLegacyResponseFields() { return false; } @@ -505,7 +528,7 @@ class CmdIsMaster : public CmdHello { public: CmdIsMaster() : CmdHello(kCamelCaseIsMasterString, {kLowerCaseIsMasterString}) {} - std::string help() const final { + std::string help() const override { return "Check if this server is primary for a replica set\n" "{ isMaster : 1 }"; } @@ -514,7 +537,7 @@ protected: // Parse the command name, which should be one of the following: hello, isMaster, or // ismaster. If the command is "hello", we must attach an "isWritablePrimary" response field // instead of "ismaster" and "secondaryDelaySecs" response field instead of "slaveDelay". - bool useLegacyResponseFields() const final { + bool useLegacyResponseFields() override { return true; } diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript index 257e556da03..957647e5721 100644 --- a/src/mongo/rpc/SConscript +++ b/src/mongo/rpc/SConscript @@ -135,7 +135,6 @@ env.Library( target='client_metadata', source=[ 'metadata/client_metadata.cpp', - 'metadata/client_metadata.idl', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', diff --git a/src/mongo/rpc/metadata/client_metadata.cpp b/src/mongo/rpc/metadata/client_metadata.cpp index 6ea09b44b02..a4a8767aadc 100644 --- a/src/mongo/rpc/metadata/client_metadata.cpp +++ b/src/mongo/rpc/metadata/client_metadata.cpp @@ -84,7 +84,7 @@ const auto getOperationState = OperationContext::declareDecoration<ClientMetadat } // namespace -StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElement& element) try { +StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElement& element) { if (element.eoo()) { return {boost::none}; } @@ -93,50 +93,84 @@ StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElem return Status(ErrorCodes::TypeMismatch, "The client metadata document must be a document"); } - return boost::make_optional(parseFromBSON(element.Obj())); -} catch (const DBException& ex) { - return ex.toStatus(); + ClientMetadata clientMetadata; + Status s = clientMetadata.parseClientMetadataDocument(element.Obj()); + if (!s.isOK()) { + return s; + } + + return {std::move(clientMetadata)}; } -ClientMetadata::ClientMetadata(BSONObj doc) { +Status ClientMetadata::parseClientMetadataDocument(const BSONObj& doc) { uint32_t maxLength = kMaxMongoDMetadataDocumentByteLength; if (isMongos()) { maxLength = kMaxMongoSMetadataDocumentByteLength; } - uassert(ErrorCodes::ClientMetadataDocumentTooLarge, - str::stream() << "The client metadata document must be less then or equal to " - << maxLength << "bytes", - static_cast<uint32_t>(doc.objsize()) <= maxLength); - - const auto isobj = [](StringData name, const BSONElement& e) { - uassert(ErrorCodes::TypeMismatch, - str::stream() - << "The '" << name - << "' field is required to be a BSON document in the client metadata document", - e.isABSONObj()); - }; + if (static_cast<uint32_t>(doc.objsize()) > maxLength) { + return Status(ErrorCodes::ClientMetadataDocumentTooLarge, + str::stream() << "The client metadata document must be less then or equal to " + << maxLength << "bytes"); + } // Get a copy so that we can take a stable reference to the app name inside - _document = doc.getOwned(); + BSONObj docOwned = doc.getOwned(); + StringData appName; bool foundDriver = false; bool foundOperatingSystem = false; - for (const auto& e : _document) { - auto name = e.fieldNameStringData(); + + BSONObjIterator i(docOwned); + while (i.more()) { + BSONElement e = i.next(); + StringData name = e.fieldNameStringData(); if (name == kApplication) { // Application is an optional sub-document, but we require it to be a document if // specified. - isobj(kApplication, e); - _appName = uassertStatusOK(parseApplicationDocument(e.Obj())); + if (!e.isABSONObj()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "The '" << kApplication + << "' field is required to be a BSON document in the " + "client metadata document"); + } + + auto swAppName = parseApplicationDocument(e.Obj()); + if (!swAppName.getStatus().isOK()) { + return swAppName.getStatus(); + } + + appName = swAppName.getValue(); + } else if (name == kDriver) { - isobj(kDriver, e); - uassertStatusOK(validateDriverDocument(e.Obj())); + if (!e.isABSONObj()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "The '" << kDriver + << "' field is required to be a " + "BSON document in the client " + "metadata document"); + } + + Status s = validateDriverDocument(e.Obj()); + if (!s.isOK()) { + return s; + } + foundDriver = true; } else if (name == kOperatingSystem) { - isobj(kOperatingSystem, e); - uassertStatusOK(validateOperatingSystemDocument(e.Obj())); + if (!e.isABSONObj()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "The '" << kOperatingSystem + << "' field is required to be a BSON document in the " + "client metadata document"); + } + + Status s = validateOperatingSystemDocument(e.Obj()); + if (!s.isOK()) { + return s; + } + foundOperatingSystem = true; } @@ -144,16 +178,23 @@ ClientMetadata::ClientMetadata(BSONObj doc) { } // Driver is a required sub document. - uassert(ErrorCodes::ClientMetadataMissingField, - str::stream() << "Missing required sub-document '" << kDriver - << "' in the client metadata document", - foundDriver); + if (!foundDriver) { + return Status(ErrorCodes::ClientMetadataMissingField, + str::stream() << "Missing required sub-document '" << kDriver + << "' in the client metadata document"); + } // OS is a required sub document. - uassert(ErrorCodes::ClientMetadataMissingField, - str::stream() << "Missing required sub-document '" << kOperatingSystem - << "' in the client metadata document", - foundOperatingSystem); + if (!foundOperatingSystem) { + return Status(ErrorCodes::ClientMetadataMissingField, + str::stream() << "Missing required sub-document '" << kOperatingSystem + << "' in the client metadata document"); + } + + _document = std::move(docOwned); + _appName = std::move(appName); + + return Status::OK(); } StatusWith<StringData> ClientMetadata::parseApplicationDocument(const BSONObj& doc) { diff --git a/src/mongo/rpc/metadata/client_metadata.h b/src/mongo/rpc/metadata/client_metadata.h index 309eb5668e0..cc3d870a4f0 100644 --- a/src/mongo/rpc/metadata/client_metadata.h +++ b/src/mongo/rpc/metadata/client_metadata.h @@ -80,16 +80,10 @@ constexpr auto kMetadataDocumentName = "client"_sd; * See Driver Specification: "MongoDB Handshake" for more information. */ class ClientMetadata { -public: - explicit ClientMetadata(BSONObj obj); - - ClientMetadata(const ClientMetadata& src) : ClientMetadata(src._document) {} - ClientMetadata& operator=(const ClientMetadata& src) { - ClientMetadata copy(src._document); - *this = std::move(copy); - return *this; - } + ClientMetadata(const ClientMetadata&) = delete; + ClientMetadata& operator=(const ClientMetadata&) = delete; +public: ClientMetadata(ClientMetadata&&) = default; ClientMetadata& operator=(ClientMetadata&&) = default; @@ -151,13 +145,6 @@ public: static StatusWith<boost::optional<ClientMetadata>> parse(const BSONElement& element); /** - * Wrapper for BSONObj constructor used by IDL parsers. - */ - static ClientMetadata parseFromBSON(BSONObj obj) { - return ClientMetadata(obj); - } - - /** * Create a new client metadata document with os information from the ProcessInfo class. * * This method outputs the "client" field, and client metadata sub-document in the @@ -324,6 +311,7 @@ public: private: ClientMetadata() = default; + Status parseClientMetadataDocument(const BSONObj& doc); static Status validateDriverDocument(const BSONObj& doc); static Status validateOperatingSystemDocument(const BSONObj& doc); static StatusWith<StringData> parseApplicationDocument(const BSONObj& doc); diff --git a/src/mongo/rpc/metadata/client_metadata.idl b/src/mongo/rpc/metadata/client_metadata.idl deleted file mode 100644 index d1726153d98..00000000000 --- a/src/mongo/rpc/metadata/client_metadata.idl +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2021-present MongoDB, Inc. -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the Server Side Public License, version 1, -# as published by MongoDB, Inc. -# -# 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 -# Server Side Public License for more details. -# -# You should have received a copy of the Server Side Public License -# along with this program. If not, see -# <http://www.mongodb.com/licensing/server-side-public-license>. -# -# 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 Server Side 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. -# - -global: - cpp_namespace: "mongo" - cpp_includes: - - "mongo/rpc/metadata/client_metadata.h" - -imports: - - "mongo/idl/basic_types.idl" - -types: - ClientMetadata: - description: "Client metadata document" - bson_serialization_type: object - cpp_type: ClientMetadata - serializer: "mongo::ClientMetadata::getDocument" - deserializer: "mongo::ClientMetadata::parseFromBSON" - diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index 0d20f144e62..275ae43565a 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -132,7 +132,6 @@ env.Library( '$BUILD_DIR/mongo/db/query/map_reduce_output_format', '$BUILD_DIR/mongo/db/read_write_concern_defaults', '$BUILD_DIR/mongo/db/repl/hello_auth', - '$BUILD_DIR/mongo/db/repl/hello_command', '$BUILD_DIR/mongo/db/shared_request_handling', '$BUILD_DIR/mongo/db/stats/api_version_metrics', '$BUILD_DIR/mongo/db/stats/counters', diff --git a/src/mongo/s/commands/cluster_hello_cmd.cpp b/src/mongo/s/commands/cluster_hello_cmd.cpp index 69040ef342d..630e82d041c 100644 --- a/src/mongo/s/commands/cluster_hello_cmd.cpp +++ b/src/mongo/s/commands/cluster_hello_cmd.cpp @@ -34,13 +34,11 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" -#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/curop.h" #include "mongo/db/logical_session_id.h" #include "mongo/db/operation_context.h" #include "mongo/db/ops/write_ops.h" #include "mongo/db/repl/hello_auth.h" -#include "mongo/db/repl/hello_gen.h" #include "mongo/db/wire_version.h" #include "mongo/logv2/log.h" #include "mongo/rpc/metadata/client_metadata.h" @@ -62,22 +60,21 @@ namespace { constexpr auto kHelloString = "hello"_sd; constexpr auto kCamelCaseIsMasterString = "isMaster"_sd; constexpr auto kLowerCaseIsMasterString = "ismaster"_sd; -const std::string kAutomationServiceDescriptorFieldName = - HelloCommandReply::kAutomationServiceDescriptorFieldName.toString(); + class CmdHello : public BasicCommandWithReplyBuilderInterface { public: CmdHello() : CmdHello(kHelloString, {}) {} - const std::set<std::string>& apiVersions() const final { + const std::set<std::string>& apiVersions() const { return kApiVersions1; } - bool supportsWriteConcern(const BSONObj& cmd) const final { + bool supportsWriteConcern(const BSONObj& cmd) const override { return false; } - AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { return AllowedOnSecondary::kAlways; } @@ -87,11 +84,11 @@ public: void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, - std::vector<Privilege>* out) const final { + std::vector<Privilege>* out) const override { // No auth required } - bool requiresAuth() const final { + bool requiresAuth() const override { return false; } @@ -100,8 +97,6 @@ public: const BSONObj& cmdObj, rpc::ReplyBuilderInterface* replyBuilder) final { CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); - const bool apiStrict = APIParameters::get(opCtx).getAPIStrict().value_or(false); - auto cmd = HelloCommand::parse({"hello", apiStrict}, cmdObj); waitInHello.pauseWhileSet(opCtx); @@ -110,38 +105,51 @@ public: // If a client is following the awaitable hello protocol, maxAwaitTimeMS should be // present if and only if topologyVersion is present in the request. - auto clientTopologyVersion = cmd.getTopologyVersion(); - auto maxAwaitTimeMS = cmd.getMaxAwaitTimeMS(); + auto topologyVersionElement = cmdObj["topologyVersion"]; + auto maxAwaitTimeMSField = cmdObj["maxAwaitTimeMS"]; auto curOp = CurOp::get(opCtx); + + // The awaitable field is optional, but if it exists, it requires both other fields. + // Note that the awaitable field only exists to make filtering out these operations + // via currentOp queries easier and is not otherwise explicitly used. + { + bool awaitable = true; + Status status = bsonExtractBooleanField(cmdObj, "awaitable", &awaitable); + if (status.isOK() && (!topologyVersionElement || !maxAwaitTimeMSField)) { + uassert(5135801, + "A request marked awaitable must contain both 'topologyVersion' and " + "'maxAwaitTimeMS'", + !awaitable); + } + } + + boost::optional<TopologyVersion> clientTopologyVersion; boost::optional<Date_t> deadline; boost::optional<ScopeGuard<std::function<void()>>> timerGuard; - if (clientTopologyVersion && maxAwaitTimeMS) { + if (topologyVersionElement && maxAwaitTimeMSField) { + clientTopologyVersion = TopologyVersion::parse(IDLParserErrorContext("TopologyVersion"), + topologyVersionElement.Obj()); uassert(51758, "topologyVersion must have a non-negative counter", clientTopologyVersion->getCounter() >= 0); - uassert(51759, "maxAwaitTimeMS must be a non-negative integer", *maxAwaitTimeMS >= 0); + long long maxAwaitTimeMS; + uassertStatusOK(bsonExtractIntegerField(cmdObj, "maxAwaitTimeMS", &maxAwaitTimeMS)); + + uassert(51759, "maxAwaitTimeMS must be a non-negative integer", maxAwaitTimeMS >= 0); deadline = opCtx->getServiceContext()->getPreciseClockSource()->now() + - Milliseconds(*maxAwaitTimeMS); + Milliseconds(maxAwaitTimeMS); LOGV2_DEBUG(23871, 3, "Using maxAwaitTimeMS for awaitable hello protocol."); curOp->pauseTimer(); timerGuard.emplace([curOp]() { curOp->resumeTimer(); }); } else { - // The awaitable field is optional, but if it exists, it requires both other fields. - // Note that the awaitable field only exists to make filtering out these operations - // via currentOp queries easier and is not otherwise explicitly used. - uassert(5135801, - "A request marked awaitable must contain both 'topologyVersion' and " - "'maxAwaitTimeMS'", - !cmd.getAwaitable() || !cmd.getAwaitable().get()); - uassert(51760, - (clientTopologyVersion + (topologyVersionElement ? "A request with a 'topologyVersion' must include 'maxAwaitTimeMS'" : "A request with 'maxAwaitTimeMS' must include a 'topologyVersion'"), - !clientTopologyVersion && !maxAwaitTimeMS); + !topologyVersionElement && !maxAwaitTimeMSField); } auto result = replyBuilder->getBodyBuilder(); @@ -158,54 +166,52 @@ public: // Try to parse the optional 'helloOk' field. On mongos, if we see this field, we will // respond with helloOk: true so the client knows that it can continue to send the hello // command to mongos. - if (auto helloOk = cmd.getHelloOk()) { + bool helloOk; + Status status = bsonExtractBooleanField(cmdObj, "helloOk", &helloOk); + if (status.isOK()) { // If the hello request contains a "helloOk" field, set _supportsHello on the Client // to the value. - client->setSupportsHello(*helloOk); + client->setSupportsHello(helloOk); // Attach helloOk: true to the response so that the client knows the server supports // the hello command. result.append("helloOk", true); + } else if (status.code() != ErrorCodes::NoSuchKey) { + uassertStatusOK(status); } if (MONGO_unlikely(appendHelloOkToHelloResponse.shouldFail())) { result.append("clientSupportsHello", client->supportsHello()); } - result.appendNumber(HelloCommandReply::kMaxBsonObjectSizeFieldName, BSONObjMaxUserSize); - result.appendNumber(HelloCommandReply::kMaxMessageSizeBytesFieldName, MaxMessageSizeBytes); - result.appendNumber(HelloCommandReply::kMaxWriteBatchSizeFieldName, - write_ops::kMaxWriteBatchSize); - result.appendDate(HelloCommandReply::kLocalTimeFieldName, jsTime()); - result.append(HelloCommandReply::kLogicalSessionTimeoutMinutesFieldName, - localLogicalSessionTimeoutMinutes); - result.appendNumber(HelloCommandReply::kConnectionIdFieldName, - opCtx->getClient()->getConnectionId()); + result.appendNumber("maxBsonObjectSize", BSONObjMaxUserSize); + result.appendNumber("maxMessageSizeBytes", MaxMessageSizeBytes); + result.appendNumber("maxWriteBatchSize", write_ops::kMaxWriteBatchSize); + result.appendDate("localTime", jsTime()); + result.append("logicalSessionTimeoutMinutes", localLogicalSessionTimeoutMinutes); + result.appendNumber("connectionId", opCtx->getClient()->getConnectionId()); // Mongos tries to keep exactly the same version range of the server for which // it is compiled. auto wireSpec = WireSpec::instance().get(); - result.append(HelloCommandReply::kMaxWireVersionFieldName, - wireSpec->incomingExternalClient.maxWireVersion); - result.append(HelloCommandReply::kMinWireVersionFieldName, - wireSpec->incomingExternalClient.minWireVersion); + result.append("maxWireVersion", wireSpec->incomingExternalClient.maxWireVersion); + result.append("minWireVersion", wireSpec->incomingExternalClient.minWireVersion); { const auto& serverParams = ServerParameterSet::getGlobal()->getMap(); - auto iter = serverParams.find(kAutomationServiceDescriptorFieldName); - if (iter != serverParams.end() && iter->second) { - iter->second->append(opCtx, result, kAutomationServiceDescriptorFieldName); - } + auto iter = serverParams.find("automationServiceDescriptor"); + if (iter != serverParams.end() && iter->second) + iter->second->append(opCtx, result, "automationServiceDescriptor"); } MessageCompressorManager::forSession(opCtx->getClient()->session()) - .serverNegotiate(cmd.getCompression(), &result); + .serverNegotiate(cmdObj, &result); if (opCtx->isExhaust()) { LOGV2_DEBUG(23872, 3, "Using exhaust for hello protocol"); uassert(51763, "A hello/isMaster request with exhaust must specify 'maxAwaitTimeMS'", - maxAwaitTimeMS); + maxAwaitTimeMSField); invariant(clientTopologyVersion); InExhaustHello::get(opCtx->getClient()->session().get()) @@ -218,59 +224,45 @@ public: // command parameters should be reused as the next BSONObj command parameters. replyBuilder->setNextInvocation(boost::none); } else { - BSONObjBuilder niBuilder; - for (const auto& elem : cmdObj) { - if (elem.fieldNameStringData() == HelloCommand::kTopologyVersionFieldName) { - BSONObjBuilder tvBuilder( - niBuilder.subobjStart(HelloCommand::kTopologyVersionFieldName)); - currentMongosTopologyVersion.serialize(&tvBuilder); + BSONObjBuilder nextInvocationBuilder; + for (auto&& elt : cmdObj) { + if (elt.fieldNameStringData() == "topologyVersion"_sd) { + BSONObjBuilder topologyVersionBuilder( + nextInvocationBuilder.subobjStart("topologyVersion")); + currentMongosTopologyVersion.serialize(&topologyVersionBuilder); } else { - niBuilder.append(elem); + nextInvocationBuilder.append(elt); } } - replyBuilder->setNextInvocation(niBuilder.obj()); + replyBuilder->setNextInvocation(nextInvocationBuilder.obj()); } } - handleHelloAuth(opCtx, cmd, &result); - - if (getTestCommandsEnabled()) { - validateResult(&result); - } + handleHelloAuth(opCtx, cmdObj, &result); return true; } - void validateResult(BSONObjBuilder* result) { - auto ret = result->asTempObj(); - if (ret[ErrorReply::kErrmsgFieldName].eoo()) { - // Nominal success case, parse the object as-is. - HelloCommandReply::parse({"hello.reply"}, ret); - } else { - // Something went wrong, still try to parse, but accept a few ignorable fields. - StringDataSet ignorable({ErrorReply::kCodeFieldName, ErrorReply::kErrmsgFieldName}); - HelloCommandReply::parse({"hello.reply"}, ret.removeFields(ignorable)); - } - } - protected: CmdHello(const StringData cmdName, const std::initializer_list<StringData>& alias) : BasicCommandWithReplyBuilderInterface(cmdName, alias) {} - virtual bool useLegacyResponseFields() const { + virtual bool useLegacyResponseFields() { return false; } } hello; class CmdIsMaster : public CmdHello { + public: CmdIsMaster() : CmdHello(kCamelCaseIsMasterString, {kLowerCaseIsMasterString}) {} protected: - bool useLegacyResponseFields() const final { + bool useLegacyResponseFields() override { return true; } + } isMaster; } // namespace diff --git a/src/mongo/transport/message_compressor_manager.cpp b/src/mongo/transport/message_compressor_manager.cpp index b6a60cdcebd..4b6f547b372 100644 --- a/src/mongo/transport/message_compressor_manager.cpp +++ b/src/mongo/transport/message_compressor_manager.cpp @@ -256,23 +256,24 @@ void MessageCompressorManager::clientFinish(const BSONObj& input) { } } -void MessageCompressorManager::serverNegotiate( - const boost::optional<std::vector<StringData>>& clientCompressors, BSONObjBuilder* result) { +void MessageCompressorManager::serverNegotiate(const BSONObj& input, BSONObjBuilder* output) { LOGV2_DEBUG(22934, 3, "Starting server-side compression negotiation"); - // No advertised compressions, just asking for the last negotiated result. - if (!clientCompressors) { + auto elem = input.getField("compression"); + // If the "compression" field is missing, then this isMaster request is requesting information + // rather than doing a negotiation + if (elem.eoo()) { // If we haven't negotiated any compressors yet, then don't append anything to the // output - this makes this compatible with older versions of MongoDB that don't // support compression. - std::vector<std::string> ret; - if (_negotiated.empty()) { - LOGV2_DEBUG(22935, 3, "Compression negotiation not requested by client"); - } else { - BSONArrayBuilder sub(result->subarrayStart("compression")); + if (_negotiated.size() > 0) { + BSONArrayBuilder sub(output->subarrayStart("compression")); for (const auto& algo : _negotiated) { - sub << algo->getName(); + sub.append(algo->getName()); } + sub.doneFast(); + } else { + LOGV2_DEBUG(22935, 3, "Compression negotiation not requested by client"); } return; } @@ -282,13 +283,16 @@ void MessageCompressorManager::serverNegotiate( _negotiated.clear(); // First we go through all the compressor names that the client has requested support for - if (clientCompressors->empty()) { + BSONObj theirObj = elem.Obj(); + + if (!theirObj.nFields()) { LOGV2_DEBUG(22936, 3, "No compressors provided"); return; } - for (const auto& curName : *clientCompressors) { + for (const auto& elem : theirObj) { MessageCompressorBase* cur; + auto curName = elem.checkAndGetStringData(); // If the MessageCompressorRegistry knows about a compressor with that name, then it is // valid and we add it to our list of negotiated compressors. if ((cur = _registry->getCompressor(curName))) { @@ -309,13 +313,14 @@ void MessageCompressorManager::serverNegotiate( // If the number of compressors that were eventually negotiated is greater than 0, then // we should send that back to the client. - if (_negotiated.empty()) { - LOGV2_DEBUG(22939, 3, "Could not agree on compressor to use"); - } else { - BSONArrayBuilder sub(result->subarrayStart("compression")); - for (const auto& algo : _negotiated) { - sub << algo->getName(); + if (_negotiated.size() > 0) { + BSONArrayBuilder sub(output->subarrayStart("compression")); + for (auto algo : _negotiated) { + sub.append(algo->getName()); } + sub.doneFast(); + } else { + LOGV2_DEBUG(22939, 3, "Could not agree on compressor to use"); } } diff --git a/src/mongo/transport/message_compressor_manager.h b/src/mongo/transport/message_compressor_manager.h index 9f1a1de35e3..467d942b4c4 100644 --- a/src/mongo/transport/message_compressor_manager.h +++ b/src/mongo/transport/message_compressor_manager.h @@ -81,11 +81,14 @@ public: /* * Called by a server that has received an isMaster request. * + * This looks for a BSON array called "compression" in input and appends the union of that + * array and the result of _registry->getCompressorNames(). The first name in the compression + * array in input will be used in subsequent calls to compressMessage + * * If no compressors are configured that match those requested by the client, then it will * not append anything to the BSONObjBuilder output. */ - void serverNegotiate(const boost::optional<std::vector<StringData>>& clientCompressors, - BSONObjBuilder*); + void serverNegotiate(const BSONObj& input, BSONObjBuilder* output); /* * Returns a new Message containing the compressed contentx of 'msg'. If compressorId is null, diff --git a/src/mongo/transport/message_compressor_manager_test.cpp b/src/mongo/transport/message_compressor_manager_test.cpp index 24cff651933..979e75d0842 100644 --- a/src/mongo/transport/message_compressor_manager_test.cpp +++ b/src/mongo/transport/message_compressor_manager_test.cpp @@ -85,8 +85,7 @@ void checkNegotiationResult(const BSONObj& result, const std::vector<std::string } } -void checkServerNegotiation(const boost::optional<std::vector<StringData>>& input, - const std::vector<std::string>& expected) { +void checkServerNegotiation(const BSONObj& input, const std::vector<std::string>& expected) { auto registry = buildRegistry(); MessageCompressorManager manager(®istry); @@ -106,8 +105,8 @@ void checkFidelity(const Message& msg, std::unique_ptr<MessageCompressorBase> co registry.finalizeSupportedCompressors().transitional_ignore(); MessageCompressorManager mgr(®istry); + auto negotiator = BSON("isMaster" << 1 << "compression" << BSON_ARRAY(compressorName)); BSONObjBuilder negotiatorOut; - std::vector<StringData> negotiator({compressorName}); mgr.serverNegotiate(negotiator, &negotiatorOut); checkNegotiationResult(negotiatorOut.done(), {compressorName}); @@ -183,46 +182,26 @@ Message buildMessage() { TEST(MessageCompressorManager, NoCompressionRequested) { auto input = BSON("isMaster" << 1); - checkServerNegotiation(boost::none, {}); + checkServerNegotiation(input, {}); } TEST(MessageCompressorManager, NormalCompressionRequested) { - std::vector<StringData> input{"noop"_sd}; + auto input = BSON("isMaster" << 1 << "compression" << BSON_ARRAY("noop")); checkServerNegotiation(input, {"noop"}); } TEST(MessageCompressorManager, BadCompressionRequested) { - std::vector<StringData> input{"fakecompressor"_sd}; + auto input = BSON("isMaster" << 1 << "compression" << BSON_ARRAY("fakecompressor")); checkServerNegotiation(input, {}); } TEST(MessageCompressorManager, BadAndGoodCompressionRequested) { - std::vector<StringData> input{"fakecompressor"_sd, "noop"_sd}; + auto input = BSON("isMaster" << 1 << "compression" + << BSON_ARRAY("fakecompressor" + << "noop")); checkServerNegotiation(input, {"noop"}); } -// Transitional: Parse BSON "isMaster"-like docs for compressor lists. -boost::optional<std::vector<StringData>> parseBSON(BSONObj input) { - auto elem = input["compression"]; - if (!elem) { - return boost::none; - } - - uassert(ErrorCodes::BadValue, - str::stream() << "'compression' is not an array: " << elem, - elem.type() == Array); - - std::vector<StringData> ret; - for (const auto& e : elem.Obj()) { - uassert(ErrorCodes::BadValue, - str::stream() << "'compression' element is not a string: " << e, - e.type() == String); - ret.push_back(e.valueStringData()); - } - - return ret; -} - TEST(MessageCompressorManager, FullNormalCompression) { auto registry = buildRegistry(); MessageCompressorManager clientManager(®istry); @@ -234,7 +213,7 @@ TEST(MessageCompressorManager, FullNormalCompression) { checkNegotiationResult(clientObj, {"noop"}); BSONObjBuilder serverOutput; - serverManager.serverNegotiate(parseBSON(clientObj), &serverOutput); + serverManager.serverNegotiate(clientObj, &serverOutput); auto serverObj = serverOutput.done(); checkNegotiationResult(serverObj, {"noop"}); @@ -306,7 +285,7 @@ TEST(MessageCompressorManager, SERVER_28008) { clientManager.clientBegin(&clientOutput); auto clientObj = clientOutput.done(); BSONObjBuilder serverOutput; - serverManager.serverNegotiate(parseBSON(clientObj), &serverOutput); + serverManager.serverNegotiate(clientObj, &serverOutput); auto serverObj = serverOutput.done(); clientManager.clientFinish(serverObj); |