summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2021-02-01 17:00:59 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-02-10 04:54:37 +0000
commit92dfc822d41714b47bc20e260aafb54884909acc (patch)
tree137b627c77938dd87b96b349ef090d9910485104 /src
parentd7b4c6bcaa61dc54d830007657babdd6337f9cfb (diff)
downloadmongo-92dfc822d41714b47bc20e260aafb54884909acc.tar.gz
SERVER-53150 Specify input/output to hello command
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/repl/SConscript17
-rw-r--r--src/mongo/db/repl/hello.idl247
-rw-r--r--src/mongo/db/repl/hello_auth.cpp28
-rw-r--r--src/mongo/db/repl/hello_auth.h3
-rw-r--r--src/mongo/db/repl/replication_info.cpp217
-rw-r--r--src/mongo/rpc/SConscript1
-rw-r--r--src/mongo/rpc/metadata/client_metadata.cpp109
-rw-r--r--src/mongo/rpc/metadata/client_metadata.h20
-rw-r--r--src/mongo/rpc/metadata/client_metadata.idl44
-rw-r--r--src/mongo/s/commands/SConscript1
-rw-r--r--src/mongo/s/commands/cluster_hello_cmd.cpp140
-rw-r--r--src/mongo/transport/message_compressor_manager.cpp41
-rw-r--r--src/mongo/transport/message_compressor_manager.h7
-rw-r--r--src/mongo/transport/message_compressor_manager_test.cpp41
14 files changed, 595 insertions, 321 deletions
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript
index b2747bf9a24..7dcf76a2ff7 100644
--- a/src/mongo/db/repl/SConscript
+++ b/src/mongo/db/repl/SConscript
@@ -1199,6 +1199,20 @@ 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",
@@ -1208,6 +1222,7 @@ 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",
@@ -1221,6 +1236,7 @@ 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',
@@ -1744,6 +1760,7 @@ 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
new file mode 100644
index 00000000000..452b1423565
--- /dev/null
+++ b/src/mongo/db/repl/hello.idl
@@ -0,0 +1,247 @@
+# 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 b88ea5b1325..efc79fdc1dc 100644
--- a/src/mongo/db/repl/hello_auth.cpp
+++ b/src/mongo/db/repl/hello_auth.cpp
@@ -39,39 +39,33 @@
namespace mongo {
-void handleHelloAuth(OperationContext* opCtx, BSONObj cmdObj, BSONObjBuilder* result) {
- auto ssme = cmdObj[auth::kSaslSupportedMechanisms];
- if (ssme.type() == BSONType::String) {
+void handleHelloAuth(OperationContext* opCtx, const HelloCommand& cmd, BSONObjBuilder* result) {
+ // saslSupportedMechs: UserName -> List[String]
+ if (auto userName = cmd.getSaslSupportedMechs()) {
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);
});
}
- auto sae = cmdObj[auth::kSpeculativeAuthenticate];
- if (sae.eoo()) {
+ // speculativeAuthenticate: SaslStart -> SaslReply or Authenticate -> AuthenticateReply
+ auto specAuth = cmd.getSpeculativeAuthenticate();
+ if (!specAuth) {
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 1ec1b4d1658..8f8a73269db 100644
--- a/src/mongo/db/repl/hello_auth.h
+++ b/src/mongo/db/repl/hello_auth.h
@@ -32,6 +32,7 @@
#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 {
@@ -41,6 +42,6 @@ namespace mongo {
* This will attach supported mechanisms or invoke the behavior of saslStart/authenticate commands
* as appropriate.
*/
-void handleHelloAuth(OperationContext* opCtx, BSONObj cmdObj, BSONObjBuilder* result);
+void handleHelloAuth(OperationContext* opCtx, const HelloCommand& cmd, BSONObjBuilder* result);
} // namespace mongo
diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp
index 7c051a60ebd..ba7c3131844 100644
--- a/src/mongo/db/repl/replication_info.cpp
+++ b/src/mongo/db/repl/replication_info.cpp
@@ -39,6 +39,7 @@
#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"
@@ -50,6 +51,7 @@
#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"
@@ -101,7 +103,7 @@ TopologyVersion appendReplicationInfo(OperationContext* opCtx,
bool appendReplicationProcess,
bool useLegacyResponseFields,
boost::optional<TopologyVersion> clientTopologyVersion,
- boost::optional<long long> maxAwaitTimeMS) {
+ boost::optional<std::int64_t> maxAwaitTimeMS) {
TopologyVersion topologyVersion;
ReplicationCoordinator* replCoord = ReplicationCoordinator::get(opCtx);
if (replCoord->getSettings().usingReplSets()) {
@@ -237,11 +239,14 @@ public:
}
} oplogInfoServerStatus;
+const std::string kAutomationServiceDescriptorFieldName =
+ HelloCommandReply::kAutomationServiceDescriptorFieldName.toString();
+
class CmdHello : public BasicCommandWithReplyBuilderInterface {
public:
CmdHello() : CmdHello(kHelloString, {}) {}
- const std::set<std::string>& apiVersions() const {
+ const std::set<std::string>& apiVersions() const final {
return kApiVersions1;
}
@@ -271,13 +276,15 @@ 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 (cmdObj["forShell"].trueValue()) {
+ if (cmd.getForShell()) {
LastError::get(opCtx->getClient()).disable();
}
@@ -285,8 +292,7 @@ public:
transport::Session::TagMask sessionTagsToUnset = 0;
// Tag connections to avoid closing them on stepdown.
- auto hangUpElement = cmdObj["hangUpOnStepDown"];
- if (!hangUpElement.eoo() && !hangUpElement.trueValue()) {
+ if (!cmd.getHangUpOnStepDown()) {
sessionTagsToSet |= transport::Session::kKeepOpen;
}
@@ -299,51 +305,18 @@ public:
// Parse the optional 'internalClient' field. This is provided by incoming connections from
// mongod and mongos.
- auto internalClientElement = cmdObj["internalClient"];
- if (internalClientElement) {
+ if (auto internalClient = cmd.getInternalClient()) {
sessionTagsToSet |= transport::Session::kInternalClient;
sessionTagsToUnset |= transport::Session::kExternalClientKeepOpen;
- 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 << "'");
- }
+ // 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::BadValue,
- "Missing required field 'maxWireVersion' of 'internalClient'",
- foundMaxWireVersion);
} else {
sessionTagsToUnset |= (transport::Session::kInternalClient |
transport::Session::kLatestVersionInternalClientKeepOpen);
@@ -369,53 +342,36 @@ 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 topologyVersionElement = cmdObj["topologyVersion"];
- auto maxAwaitTimeMSField = cmdObj["maxAwaitTimeMS"];
+ auto clientTopologyVersion = cmd.getTopologyVersion();
+ auto maxAwaitTimeMS = cmd.getMaxAwaitTimeMS();
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 (topologyVersionElement && maxAwaitTimeMSField) {
- clientTopologyVersion = TopologyVersion::parse(IDLParserErrorContext("TopologyVersion"),
- topologyVersionElement.Obj());
+ if (clientTopologyVersion && maxAwaitTimeMS) {
uassert(31372,
"topologyVersion must have a non-negative counter",
clientTopologyVersion->getCounter() >= 0);
- {
- 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.");
+ LOGV2_DEBUG(23904,
+ 3,
+ "Using maxAwaitTimeMS for awaitable hello protocol",
+ "maxAwaitTimeMS"_attr = maxAwaitTimeMS.get());
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,
- (topologyVersionElement
+ (clientTopologyVersion
? "A request with a 'topologyVersion' must include 'maxAwaitTimeMS'"
: "A request with 'maxAwaitTimeMS' must include a 'topologyVersion'"),
- !topologyVersionElement && !maxAwaitTimeMSField);
+ !clientTopologyVersion && !maxAwaitTimeMS);
}
auto result = replyBuilder->getBodyBuilder();
@@ -424,21 +380,18 @@ 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.
- bool helloOk = client->supportsHello();
- Status status = bsonExtractBooleanField(cmdObj, "helloOk", &helloOk);
- if (status.isOK()) {
+ if (auto helloOk = cmd.getHelloOk()) {
// 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);
+ result.append(HelloCommandReply::kHelloOkFieldName, true);
}
if (MONGO_unlikely(appendHelloOkToHelloResponse.shouldFail())) {
- result.append("clientSupportsHello", client->supportsHello());
+ result.append(HelloCommandReply::kClientSupportsHelloFieldName,
+ client->supportsHello());
}
auto currentTopologyVersion = appendReplicationInfo(
@@ -447,35 +400,44 @@ public:
timerGuard.reset(); // Resume curOp timer.
if (serverGlobalParams.clusterRole == ClusterRole::ConfigServer) {
- const int configServerModeNumber = 2;
- result.append("configsvr", configServerModeNumber);
+ constexpr int kConfigServerModeNumber = 2;
+ result.append(HelloCommandReply::kConfigsvrFieldName, kConfigServerModeNumber);
}
- 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);
+ 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);
} else {
- result.append("minWireVersion", wireSpec->incomingExternalClient.minWireVersion);
- result.append("maxWireVersion", wireSpec->incomingExternalClient.maxWireVersion);
+ result.append(HelloCommandReply::kMinWireVersionFieldName,
+ wireSpec->incomingExternalClient.minWireVersion);
+ result.append(HelloCommandReply::kMaxWireVersionFieldName,
+ wireSpec->incomingExternalClient.maxWireVersion);
}
- result.append("readOnly", storageGlobalParams.readOnly);
+ result.append(HelloCommandReply::kReadOnlyFieldName, storageGlobalParams.readOnly);
const auto& params = ServerParameterSet::getGlobal()->getMap();
- if (auto iter = params.find("automationServiceDescriptor");
- iter != params.end() && iter->second)
- iter->second->append(opCtx, result, "automationServiceDescriptor");
+ if (auto iter = params.find(kAutomationServiceDescriptorFieldName);
+ iter != params.end() && iter->second) {
+ iter->second->append(opCtx, result, kAutomationServiceDescriptorFieldName);
+ }
if (opCtx->getClient()->session()) {
MessageCompressorManager::forSession(opCtx->getClient()->session())
- .serverNegotiate(cmdObj, &result);
+ .serverNegotiate(cmd.getCompression(), &result);
}
if (opCtx->isExhaust()) {
@@ -483,7 +445,7 @@ public:
uassert(51756,
"An isMaster or hello request with exhaust must specify 'maxAwaitTimeMS'",
- maxAwaitTimeMSField);
+ maxAwaitTimeMS);
invariant(clientTopologyVersion);
InExhaustHello::get(opCtx->getClient()->session().get())
@@ -495,30 +457,45 @@ public:
// command parameters should be reused as the next BSONObj command parameters.
replyBuilder->setNextInvocation(boost::none);
} else {
- BSONObjBuilder nextInvocationBuilder;
- for (auto&& elt : cmdObj) {
- if (elt.fieldNameStringData() == "topologyVersion"_sd) {
- BSONObjBuilder topologyVersionBuilder(
- nextInvocationBuilder.subobjStart("topologyVersion"));
- currentTopologyVersion.serialize(&topologyVersionBuilder);
+ BSONObjBuilder niBuilder;
+ for (const auto& elem : cmdObj) {
+ if (elem.fieldNameStringData() == HelloCommand::kTopologyVersionFieldName) {
+ BSONObjBuilder tvBuilder(
+ niBuilder.subobjStart(HelloCommand::kTopologyVersionFieldName));
+ currentTopologyVersion.serialize(&tvBuilder);
} else {
- nextInvocationBuilder.append(elt);
+ niBuilder.append(elem);
}
}
- replyBuilder->setNextInvocation(nextInvocationBuilder.obj());
+ replyBuilder->setNextInvocation(niBuilder.obj());
}
}
- handleHelloAuth(opCtx, cmdObj, &result);
+ handleHelloAuth(opCtx, cmd, &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() {
+ virtual bool useLegacyResponseFields() const {
return false;
}
@@ -528,7 +505,7 @@ class CmdIsMaster : public CmdHello {
public:
CmdIsMaster() : CmdHello(kCamelCaseIsMasterString, {kLowerCaseIsMasterString}) {}
- std::string help() const override {
+ std::string help() const final {
return "Check if this server is primary for a replica set\n"
"{ isMaster : 1 }";
}
@@ -537,7 +514,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() override {
+ bool useLegacyResponseFields() const final {
return true;
}
diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript
index 957647e5721..257e556da03 100644
--- a/src/mongo/rpc/SConscript
+++ b/src/mongo/rpc/SConscript
@@ -135,6 +135,7 @@ 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 a4a8767aadc..6ea09b44b02 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) {
+StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElement& element) try {
if (element.eoo()) {
return {boost::none};
}
@@ -93,84 +93,50 @@ StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElem
return Status(ErrorCodes::TypeMismatch, "The client metadata document must be a document");
}
- ClientMetadata clientMetadata;
- Status s = clientMetadata.parseClientMetadataDocument(element.Obj());
- if (!s.isOK()) {
- return s;
- }
-
- return {std::move(clientMetadata)};
+ return boost::make_optional(parseFromBSON(element.Obj()));
+} catch (const DBException& ex) {
+ return ex.toStatus();
}
-Status ClientMetadata::parseClientMetadataDocument(const BSONObj& doc) {
+ClientMetadata::ClientMetadata(BSONObj doc) {
uint32_t maxLength = kMaxMongoDMetadataDocumentByteLength;
if (isMongos()) {
maxLength = kMaxMongoSMetadataDocumentByteLength;
}
- 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");
- }
+ 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());
+ };
// Get a copy so that we can take a stable reference to the app name inside
- BSONObj docOwned = doc.getOwned();
+ _document = doc.getOwned();
- StringData appName;
bool foundDriver = false;
bool foundOperatingSystem = false;
-
- BSONObjIterator i(docOwned);
- while (i.more()) {
- BSONElement e = i.next();
- StringData name = e.fieldNameStringData();
+ for (const auto& e : _document) {
+ auto name = e.fieldNameStringData();
if (name == kApplication) {
// Application is an optional sub-document, but we require it to be a document if
// specified.
- 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();
-
+ isobj(kApplication, e);
+ _appName = uassertStatusOK(parseApplicationDocument(e.Obj()));
} else if (name == kDriver) {
- 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;
- }
-
+ isobj(kDriver, e);
+ uassertStatusOK(validateDriverDocument(e.Obj()));
foundDriver = true;
} else if (name == kOperatingSystem) {
- 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;
- }
-
+ isobj(kOperatingSystem, e);
+ uassertStatusOK(validateOperatingSystemDocument(e.Obj()));
foundOperatingSystem = true;
}
@@ -178,23 +144,16 @@ Status ClientMetadata::parseClientMetadataDocument(const BSONObj& doc) {
}
// Driver is a required sub document.
- if (!foundDriver) {
- return Status(ErrorCodes::ClientMetadataMissingField,
- str::stream() << "Missing required sub-document '" << kDriver
- << "' in the client metadata document");
- }
+ uassert(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required sub-document '" << kDriver
+ << "' in the client metadata document",
+ foundDriver);
// OS is a required sub document.
- 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();
+ uassert(ErrorCodes::ClientMetadataMissingField,
+ str::stream() << "Missing required sub-document '" << kOperatingSystem
+ << "' in the client metadata document",
+ foundOperatingSystem);
}
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 cc3d870a4f0..309eb5668e0 100644
--- a/src/mongo/rpc/metadata/client_metadata.h
+++ b/src/mongo/rpc/metadata/client_metadata.h
@@ -80,10 +80,16 @@ constexpr auto kMetadataDocumentName = "client"_sd;
* See Driver Specification: "MongoDB Handshake" for more information.
*/
class ClientMetadata {
- ClientMetadata(const ClientMetadata&) = delete;
- ClientMetadata& operator=(const ClientMetadata&) = delete;
-
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(ClientMetadata&&) = default;
ClientMetadata& operator=(ClientMetadata&&) = default;
@@ -145,6 +151,13 @@ 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
@@ -311,7 +324,6 @@ 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
new file mode 100644
index 00000000000..d1726153d98
--- /dev/null
+++ b/src/mongo/rpc/metadata/client_metadata.idl
@@ -0,0 +1,44 @@
+# 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 275ae43565a..0d20f144e62 100644
--- a/src/mongo/s/commands/SConscript
+++ b/src/mongo/s/commands/SConscript
@@ -132,6 +132,7 @@ 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 630e82d041c..69040ef342d 100644
--- a/src/mongo/s/commands/cluster_hello_cmd.cpp
+++ b/src/mongo/s/commands/cluster_hello_cmd.cpp
@@ -34,11 +34,13 @@
#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"
@@ -60,21 +62,22 @@ 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 {
+ const std::set<std::string>& apiVersions() const final {
return kApiVersions1;
}
- bool supportsWriteConcern(const BSONObj& cmd) const override {
+ bool supportsWriteConcern(const BSONObj& cmd) const final {
return false;
}
- AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
+ AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
return AllowedOnSecondary::kAlways;
}
@@ -84,11 +87,11 @@ public:
void addRequiredPrivileges(const std::string& dbname,
const BSONObj& cmdObj,
- std::vector<Privilege>* out) const override {
+ std::vector<Privilege>* out) const final {
// No auth required
}
- bool requiresAuth() const override {
+ bool requiresAuth() const final {
return false;
}
@@ -97,6 +100,8 @@ 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);
@@ -105,51 +110,38 @@ 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 topologyVersionElement = cmdObj["topologyVersion"];
- auto maxAwaitTimeMSField = cmdObj["maxAwaitTimeMS"];
+ auto clientTopologyVersion = cmd.getTopologyVersion();
+ auto maxAwaitTimeMS = cmd.getMaxAwaitTimeMS();
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 (topologyVersionElement && maxAwaitTimeMSField) {
- clientTopologyVersion = TopologyVersion::parse(IDLParserErrorContext("TopologyVersion"),
- topologyVersionElement.Obj());
+ if (clientTopologyVersion && maxAwaitTimeMS) {
uassert(51758,
"topologyVersion must have a non-negative counter",
clientTopologyVersion->getCounter() >= 0);
- long long maxAwaitTimeMS;
- uassertStatusOK(bsonExtractIntegerField(cmdObj, "maxAwaitTimeMS", &maxAwaitTimeMS));
-
- uassert(51759, "maxAwaitTimeMS must be a non-negative integer", maxAwaitTimeMS >= 0);
+ 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,
- (topologyVersionElement
+ (clientTopologyVersion
? "A request with a 'topologyVersion' must include 'maxAwaitTimeMS'"
: "A request with 'maxAwaitTimeMS' must include a 'topologyVersion'"),
- !topologyVersionElement && !maxAwaitTimeMSField);
+ !clientTopologyVersion && !maxAwaitTimeMS);
}
auto result = replyBuilder->getBodyBuilder();
@@ -166,52 +158,54 @@ 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.
- bool helloOk;
- Status status = bsonExtractBooleanField(cmdObj, "helloOk", &helloOk);
- if (status.isOK()) {
+ if (auto helloOk = cmd.getHelloOk()) {
// 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("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());
+ 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());
// Mongos tries to keep exactly the same version range of the server for which
// it is compiled.
auto wireSpec = WireSpec::instance().get();
- result.append("maxWireVersion", wireSpec->incomingExternalClient.maxWireVersion);
- result.append("minWireVersion", wireSpec->incomingExternalClient.minWireVersion);
+ result.append(HelloCommandReply::kMaxWireVersionFieldName,
+ wireSpec->incomingExternalClient.maxWireVersion);
+ result.append(HelloCommandReply::kMinWireVersionFieldName,
+ wireSpec->incomingExternalClient.minWireVersion);
{
const auto& serverParams = ServerParameterSet::getGlobal()->getMap();
- auto iter = serverParams.find("automationServiceDescriptor");
- if (iter != serverParams.end() && iter->second)
- iter->second->append(opCtx, result, "automationServiceDescriptor");
+ auto iter = serverParams.find(kAutomationServiceDescriptorFieldName);
+ if (iter != serverParams.end() && iter->second) {
+ iter->second->append(opCtx, result, kAutomationServiceDescriptorFieldName);
+ }
}
MessageCompressorManager::forSession(opCtx->getClient()->session())
- .serverNegotiate(cmdObj, &result);
+ .serverNegotiate(cmd.getCompression(), &result);
if (opCtx->isExhaust()) {
LOGV2_DEBUG(23872, 3, "Using exhaust for hello protocol");
uassert(51763,
"A hello/isMaster request with exhaust must specify 'maxAwaitTimeMS'",
- maxAwaitTimeMSField);
+ maxAwaitTimeMS);
invariant(clientTopologyVersion);
InExhaustHello::get(opCtx->getClient()->session().get())
@@ -224,45 +218,59 @@ public:
// command parameters should be reused as the next BSONObj command parameters.
replyBuilder->setNextInvocation(boost::none);
} else {
- BSONObjBuilder nextInvocationBuilder;
- for (auto&& elt : cmdObj) {
- if (elt.fieldNameStringData() == "topologyVersion"_sd) {
- BSONObjBuilder topologyVersionBuilder(
- nextInvocationBuilder.subobjStart("topologyVersion"));
- currentMongosTopologyVersion.serialize(&topologyVersionBuilder);
+ BSONObjBuilder niBuilder;
+ for (const auto& elem : cmdObj) {
+ if (elem.fieldNameStringData() == HelloCommand::kTopologyVersionFieldName) {
+ BSONObjBuilder tvBuilder(
+ niBuilder.subobjStart(HelloCommand::kTopologyVersionFieldName));
+ currentMongosTopologyVersion.serialize(&tvBuilder);
} else {
- nextInvocationBuilder.append(elt);
+ niBuilder.append(elem);
}
}
- replyBuilder->setNextInvocation(nextInvocationBuilder.obj());
+ replyBuilder->setNextInvocation(niBuilder.obj());
}
}
- handleHelloAuth(opCtx, cmdObj, &result);
+ handleHelloAuth(opCtx, cmd, &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() {
+ virtual bool useLegacyResponseFields() const {
return false;
}
} hello;
class CmdIsMaster : public CmdHello {
-
public:
CmdIsMaster() : CmdHello(kCamelCaseIsMasterString, {kLowerCaseIsMasterString}) {}
protected:
- bool useLegacyResponseFields() override {
+ bool useLegacyResponseFields() const final {
return true;
}
-
} isMaster;
} // namespace
diff --git a/src/mongo/transport/message_compressor_manager.cpp b/src/mongo/transport/message_compressor_manager.cpp
index 4b6f547b372..b6a60cdcebd 100644
--- a/src/mongo/transport/message_compressor_manager.cpp
+++ b/src/mongo/transport/message_compressor_manager.cpp
@@ -256,24 +256,23 @@ void MessageCompressorManager::clientFinish(const BSONObj& input) {
}
}
-void MessageCompressorManager::serverNegotiate(const BSONObj& input, BSONObjBuilder* output) {
+void MessageCompressorManager::serverNegotiate(
+ const boost::optional<std::vector<StringData>>& clientCompressors, BSONObjBuilder* result) {
LOGV2_DEBUG(22934, 3, "Starting server-side compression negotiation");
- 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()) {
+ // No advertised compressions, just asking for the last negotiated result.
+ if (!clientCompressors) {
// 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.
- if (_negotiated.size() > 0) {
- BSONArrayBuilder sub(output->subarrayStart("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"));
for (const auto& algo : _negotiated) {
- sub.append(algo->getName());
+ sub << algo->getName();
}
- sub.doneFast();
- } else {
- LOGV2_DEBUG(22935, 3, "Compression negotiation not requested by client");
}
return;
}
@@ -283,16 +282,13 @@ void MessageCompressorManager::serverNegotiate(const BSONObj& input, BSONObjBuil
_negotiated.clear();
// First we go through all the compressor names that the client has requested support for
- BSONObj theirObj = elem.Obj();
-
- if (!theirObj.nFields()) {
+ if (clientCompressors->empty()) {
LOGV2_DEBUG(22936, 3, "No compressors provided");
return;
}
- for (const auto& elem : theirObj) {
+ for (const auto& curName : *clientCompressors) {
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))) {
@@ -313,14 +309,13 @@ void MessageCompressorManager::serverNegotiate(const BSONObj& input, BSONObjBuil
// If the number of compressors that were eventually negotiated is greater than 0, then
// we should send that back to the client.
- if (_negotiated.size() > 0) {
- BSONArrayBuilder sub(output->subarrayStart("compression"));
- for (auto algo : _negotiated) {
- sub.append(algo->getName());
- }
- sub.doneFast();
- } else {
+ 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();
+ }
}
}
diff --git a/src/mongo/transport/message_compressor_manager.h b/src/mongo/transport/message_compressor_manager.h
index 467d942b4c4..9f1a1de35e3 100644
--- a/src/mongo/transport/message_compressor_manager.h
+++ b/src/mongo/transport/message_compressor_manager.h
@@ -81,14 +81,11 @@ 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 BSONObj& input, BSONObjBuilder* output);
+ void serverNegotiate(const boost::optional<std::vector<StringData>>& clientCompressors,
+ BSONObjBuilder*);
/*
* 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 979e75d0842..24cff651933 100644
--- a/src/mongo/transport/message_compressor_manager_test.cpp
+++ b/src/mongo/transport/message_compressor_manager_test.cpp
@@ -85,7 +85,8 @@ void checkNegotiationResult(const BSONObj& result, const std::vector<std::string
}
}
-void checkServerNegotiation(const BSONObj& input, const std::vector<std::string>& expected) {
+void checkServerNegotiation(const boost::optional<std::vector<StringData>>& input,
+ const std::vector<std::string>& expected) {
auto registry = buildRegistry();
MessageCompressorManager manager(&registry);
@@ -105,8 +106,8 @@ void checkFidelity(const Message& msg, std::unique_ptr<MessageCompressorBase> co
registry.finalizeSupportedCompressors().transitional_ignore();
MessageCompressorManager mgr(&registry);
- auto negotiator = BSON("isMaster" << 1 << "compression" << BSON_ARRAY(compressorName));
BSONObjBuilder negotiatorOut;
+ std::vector<StringData> negotiator({compressorName});
mgr.serverNegotiate(negotiator, &negotiatorOut);
checkNegotiationResult(negotiatorOut.done(), {compressorName});
@@ -182,26 +183,46 @@ Message buildMessage() {
TEST(MessageCompressorManager, NoCompressionRequested) {
auto input = BSON("isMaster" << 1);
- checkServerNegotiation(input, {});
+ checkServerNegotiation(boost::none, {});
}
TEST(MessageCompressorManager, NormalCompressionRequested) {
- auto input = BSON("isMaster" << 1 << "compression" << BSON_ARRAY("noop"));
+ std::vector<StringData> input{"noop"_sd};
checkServerNegotiation(input, {"noop"});
}
TEST(MessageCompressorManager, BadCompressionRequested) {
- auto input = BSON("isMaster" << 1 << "compression" << BSON_ARRAY("fakecompressor"));
+ std::vector<StringData> input{"fakecompressor"_sd};
checkServerNegotiation(input, {});
}
TEST(MessageCompressorManager, BadAndGoodCompressionRequested) {
- auto input = BSON("isMaster" << 1 << "compression"
- << BSON_ARRAY("fakecompressor"
- << "noop"));
+ std::vector<StringData> input{"fakecompressor"_sd, "noop"_sd};
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(&registry);
@@ -213,7 +234,7 @@ TEST(MessageCompressorManager, FullNormalCompression) {
checkNegotiationResult(clientObj, {"noop"});
BSONObjBuilder serverOutput;
- serverManager.serverNegotiate(clientObj, &serverOutput);
+ serverManager.serverNegotiate(parseBSON(clientObj), &serverOutput);
auto serverObj = serverOutput.done();
checkNegotiationResult(serverObj, {"noop"});
@@ -285,7 +306,7 @@ TEST(MessageCompressorManager, SERVER_28008) {
clientManager.clientBegin(&clientOutput);
auto clientObj = clientOutput.done();
BSONObjBuilder serverOutput;
- serverManager.serverNegotiate(clientObj, &serverOutput);
+ serverManager.serverNegotiate(parseBSON(clientObj), &serverOutput);
auto serverObj = serverOutput.done();
clientManager.clientFinish(serverObj);