diff options
author | A. Jesse Jiryu Davis <jesse@mongodb.com> | 2021-05-12 14:16:14 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-05-12 18:39:03 +0000 |
commit | a696566eae01ed989738f36e396ef2ec3949728c (patch) | |
tree | c6d0696848ce85ad76308dca849ffb80a7d78603 | |
parent | 2a99e03b813f33342ffe83ccc5df9b8d2c33bf08 (diff) | |
download | mongo-a696566eae01ed989738f36e396ef2ec3949728c.tar.gz |
SERVER-49336 Set ClientMetadata before CommandInvocations are run
22 files changed, 310 insertions, 422 deletions
diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 82d68fc21d3..9187d9a2be4 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -53,7 +53,7 @@ #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/rpc/factory.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" +#include "mongo/rpc/metadata/client_metadata.h" #include "mongo/rpc/op_msg_rpc_impls.h" #include "mongo/rpc/protocol.h" #include "mongo/rpc/write_concern_error_detail.h" @@ -476,6 +476,11 @@ bool CommandHelpers::shouldActivateFailCommandFailPoint(const BSONObj& data, if (cmd->getName() == "configureFailPoint"_sd) // Banned even if in failCommands. return false; + auto appName = StringData(); + if (auto clientMetadata = ClientMetadata::get(client)) { + appName = clientMetadata->getApplicationName(); + } + if (data.hasField("threadName") && (client->desc() != data.getStringField( @@ -483,12 +488,8 @@ bool CommandHelpers::shouldActivateFailCommandFailPoint(const BSONObj& data, return false; } - if (data.hasField("appName")) { - const auto& clientMetadata = ClientMetadataIsMasterState::get(client).getClientMetadata(); - if (clientMetadata && - clientMetadata.get().getApplicationName() != data.getStringField("appName")) { - return false; // only activate failpoint on connection with a certain appName - } + if (data.hasField("appName") && (appName != data.getStringField("appName"))) { + return false; // only activate failpoint on connection with a certain appName } if (client->session() && (client->session()->getTags() & transport::Session::kInternalClient)) { @@ -577,10 +578,8 @@ void CommandHelpers::handleMarkKillOnClientDisconnect(OperationContext* opCtx, MONGO_FAIL_POINT_BLOCK_IF( waitInCommandMarkKillOnClientDisconnect, options, [&](const BSONObj& obj) { - const auto& clientMetadata = - ClientMetadataIsMasterState::get(opCtx->getClient()).getClientMetadata(); - - return clientMetadata && (clientMetadata->getApplicationName() == obj["appName"].str()); + auto md = ClientMetadata::get(opCtx->getClient()); + return md && (md->getApplicationName() == obj["appName"].str()); }) { MONGO_FAIL_POINT_PAUSE_WHILE_SET_OR_INTERRUPTED(opCtx, waitInCommandMarkKillOnClientDisconnect); diff --git a/src/mongo/db/commands/authentication_commands.cpp b/src/mongo/db/commands/authentication_commands.cpp index 3a64955228b..b56197b720a 100644 --- a/src/mongo/db/commands/authentication_commands.cpp +++ b/src/mongo/db/commands/authentication_commands.cpp @@ -54,7 +54,6 @@ #include "mongo/db/operation_context.h" #include "mongo/platform/random.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" #include "mongo/stdx/memory.h" #include "mongo/transport/session.h" #include "mongo/util/concurrency/mutex.h" diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 9d26d1c206c..44071593f1d 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -49,7 +49,6 @@ #include "mongo/db/query/getmore_request.h" #include "mongo/db/query/plan_summary_stats.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" #include "mongo/rpc/metadata/impersonated_user_metadata.h" #include "mongo/util/hex.h" #include "mongo/util/log.h" @@ -248,15 +247,13 @@ void CurOp::reportCurrentOpForClient(OperationContext* opCtx, infoBuilder->append("host", hostName); client->reportState(*infoBuilder); - const auto& clientMetadata = ClientMetadataIsMasterState::get(client).getClientMetadata(); - - if (clientMetadata) { - auto appName = clientMetadata.get().getApplicationName(); + if (auto clientMetadata = ClientMetadata::get(client)) { + auto appName = clientMetadata->getApplicationName(); if (!appName.empty()) { infoBuilder->append("appName", appName); } - auto clientMetadataDocument = clientMetadata.get().getDocument(); + auto clientMetadataDocument = clientMetadata->getDocument(); infoBuilder->append("clientMetadata", clientMetadataDocument); } @@ -649,9 +646,8 @@ string OpDebug::report(Client* client, s << curop.getNS(); - const auto& clientMetadata = ClientMetadataIsMasterState::get(client).getClientMetadata(); - if (clientMetadata) { - auto appName = clientMetadata.get().getApplicationName(); + if (auto clientMetadata = ClientMetadata::get(client)) { + auto appName = clientMetadata->getApplicationName(); if (!appName.empty()) { s << " appName: \"" << str::escape(appName) << '\"'; } diff --git a/src/mongo/db/introspect.cpp b/src/mongo/db/introspect.cpp index a75b91b8aba..a23d80ecbcc 100644 --- a/src/mongo/db/introspect.cpp +++ b/src/mongo/db/introspect.cpp @@ -43,7 +43,6 @@ #include "mongo/db/db_raii.h" #include "mongo/db/jsobj.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" #include "mongo/util/log.h" #include "mongo/util/scopeguard.h" @@ -99,10 +98,8 @@ void profile(OperationContext* opCtx, NetworkOp op) { b.appendDate("ts", jsTime()); b.append("client", opCtx->getClient()->clientAddress()); - const auto& clientMetadata = - ClientMetadataIsMasterState::get(opCtx->getClient()).getClientMetadata(); - if (clientMetadata) { - auto appName = clientMetadata.get().getApplicationName(); + if (auto clientMetadata = ClientMetadata::get(opCtx->getClient())) { + auto appName = clientMetadata->getApplicationName(); if (!appName.empty()) { b.append("appName", appName); } diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp index 75d9948ba05..17d00aca255 100644 --- a/src/mongo/db/pipeline/pipeline_d.cpp +++ b/src/mongo/db/pipeline/pipeline_d.cpp @@ -76,7 +76,7 @@ #include "mongo/db/storage/record_store.h" #include "mongo/db/storage/sorted_data_interface.h" #include "mongo/db/transaction_participant.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" +#include "mongo/rpc/metadata/client_metadata.h" #include "mongo/s/catalog_cache.h" #include "mongo/s/chunk_manager.h" #include "mongo/s/chunk_version.h" diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp index 2446336e382..780c5851168 100644 --- a/src/mongo/db/repl/replication_info.cpp +++ b/src/mongo/db/repl/replication_info.cpp @@ -59,7 +59,6 @@ #include "mongo/db/wire_version.h" #include "mongo/executor/network_interface.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" #include "mongo/util/fail_point_service.h" #include "mongo/util/log.h" #include "mongo/util/map_util.h" @@ -271,33 +270,11 @@ public: sessionTagsToSet |= transport::Session::kKeepOpen; } - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx->getClient()); - bool seenIsMaster = clientMetadataIsMasterState.hasSeenIsMaster(); - - if (!seenIsMaster) { - clientMetadataIsMasterState.setSeenIsMaster(); - } - - BSONElement element = cmdObj[kMetadataDocumentName]; - if (!element.eoo()) { - if (seenIsMaster) { - uasserted(ErrorCodes::ClientMetadataCannotBeMutated, - "The client metadata document may only be sent in the first isMaster"); - } - - auto parsedClientMetadata = uassertStatusOK(ClientMetadata::parse(element)); - - invariant(parsedClientMetadata); - - parsedClientMetadata->logClientMetadata(opCtx->getClient()); - - clientMetadataIsMasterState.setClientMetadata(opCtx->getClient(), - std::move(parsedClientMetadata)); - } - - if (!seenIsMaster) { - auto sniName = opCtx->getClient()->getSniNameForSession(); - SplitHorizon::setParameters(opCtx->getClient(), std::move(sniName)); + auto client = opCtx->getClient(); + if (ClientMetadata::tryFinalize(client)) { + // If we are the first hello, then set split horizon parameters. + auto sniName = client->getSniNameForSession(); + SplitHorizon::setParameters(client, std::move(sniName)); } // Parse the optional 'internalClient' field. This is provided by incoming connections from diff --git a/src/mongo/db/s/single_transaction_coordinator_stats.h b/src/mongo/db/s/single_transaction_coordinator_stats.h index a6d8be9b54f..9f7349cf3ed 100644 --- a/src/mongo/db/s/single_transaction_coordinator_stats.h +++ b/src/mongo/db/s/single_transaction_coordinator_stats.h @@ -30,7 +30,7 @@ #pragma once #include "mongo/db/client.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" +#include "mongo/rpc/metadata/client_metadata.h" #include "mongo/util/tick_source.h" #include "mongo/util/time_support.h" @@ -56,10 +56,9 @@ public: clientHostAndPort = client->getRemote().toString(); } connectionId = client->getConnectionId(); - if (const auto& metadata = - ClientMetadataIsMasterState::get(client).getClientMetadata()) { - clientMetadata = metadata.get().getDocument(); - appName = metadata.get().getApplicationName().toString(); + if (auto metadata = ClientMetadata::get(client)) { + clientMetadata = metadata->getDocument(); + appName = metadata->getApplicationName().toString(); } } }; diff --git a/src/mongo/db/s/transaction_coordinator_test_fixture.cpp b/src/mongo/db/s/transaction_coordinator_test_fixture.cpp index cf752cc4dae..8bb14a17760 100644 --- a/src/mongo/db/s/transaction_coordinator_test_fixture.cpp +++ b/src/mongo/db/s/transaction_coordinator_test_fixture.cpp @@ -39,7 +39,6 @@ #include "mongo/db/operation_context.h" #include "mongo/db/s/wait_for_majority_service.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" #include "mongo/s/catalog/sharding_catalog_client_mock.h" #include "mongo/s/catalog/type_shard.h" #include "mongo/unittest/unittest.h" @@ -138,7 +137,6 @@ void TransactionCoordinatorTestFixture::associateClientMetadata(Client* client, &metadataBuilder)); auto clientMetadata = metadataBuilder.obj(); auto clientMetadataParse = ClientMetadata::parse(clientMetadata["client"]); - ClientMetadataIsMasterState::setClientMetadata(client, - std::move(clientMetadataParse.getValue())); + ClientMetadata::setAndFinalize(client, std::move(clientMetadataParse.getValue())); } } // namespace mongo diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index e6997ed7877..41943251f8f 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -85,6 +85,7 @@ #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/rpc/message.h" #include "mongo/rpc/metadata.h" +#include "mongo/rpc/metadata/client_metadata.h" #include "mongo/rpc/metadata/logical_time_metadata.h" #include "mongo/rpc/metadata/oplog_query_metadata.h" #include "mongo/rpc/metadata/repl_set_metadata.h" @@ -682,6 +683,7 @@ void execCommandDatabase(OperationContext* opCtx, auto invocation = command->parse(opCtx, request); OperationSessionInfoFromClient sessionOptions; + const auto isHello = command->getName() == "hello"_sd || command->getName() == "isMaster"_sd; try { { @@ -689,6 +691,13 @@ void execCommandDatabase(OperationContext* opCtx, CurOp::get(opCtx)->setCommand_inlock(command); } + if (isHello) { + // Preload generic ClientMetadata ahead of our first hello request. After the first + // request, metaElement should always be empty. + auto metaElem = request.body[kMetadataDocumentName]; + ClientMetadata::setFromMetadata(opCtx->getClient(), metaElem); + } + MONGO_FAIL_POINT_BLOCK(sleepMillisAfterCommandExecutionBegins, arg) { const BSONObj& data = arg.getData(); auto numMillis = data["millis"].numberInt(); diff --git a/src/mongo/db/stats/single_transaction_stats.h b/src/mongo/db/stats/single_transaction_stats.h index 0afdb7f3d24..51524c8d01c 100644 --- a/src/mongo/db/stats/single_transaction_stats.h +++ b/src/mongo/db/stats/single_transaction_stats.h @@ -31,7 +31,6 @@ #include "mongo/db/curop.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" namespace mongo { @@ -56,10 +55,9 @@ public: clientHostAndPort = client->getRemote().toString(); } connectionId = client->getConnectionId(); - if (const auto& metadata = - ClientMetadataIsMasterState::get(client).getClientMetadata()) { - clientMetadata = metadata.get().getDocument(); - appName = metadata.get().getApplicationName().toString(); + if (auto metadata = ClientMetadata::get(client)) { + clientMetadata = metadata->getDocument(); + appName = metadata->getApplicationName().toString(); } } }; diff --git a/src/mongo/db/transaction_participant_test.cpp b/src/mongo/db/transaction_participant_test.cpp index 631b6bdff14..aa9959ed368 100644 --- a/src/mongo/db/transaction_participant_test.cpp +++ b/src/mongo/db/transaction_participant_test.cpp @@ -2663,7 +2663,7 @@ TEST_F(TransactionsMetricsTest, ReportStashedResources) { auto sessionCheckout = checkOutSession(); - // Create a ClientMetadata object and set it on ClientMetadataIsMasterState. + // Create a ClientMetadata object and set it. BSONObjBuilder builder; ASSERT_OK(ClientMetadata::serializePrivate("driverName", "driverVersion", @@ -2675,9 +2675,7 @@ TEST_F(TransactionsMetricsTest, ReportStashedResources) { &builder)); auto obj = builder.obj(); auto clientMetadata = ClientMetadata::parse(obj["client"]); - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx()->getClient()); - clientMetadataIsMasterState.setClientMetadata(opCtx()->getClient(), - std::move(clientMetadata.getValue())); + ClientMetadata::setAndFinalize(opCtx()->getClient(), std::move(clientMetadata.getValue())); repl::ReadConcernArgs readConcernArgs; ASSERT_OK( @@ -2867,12 +2865,10 @@ BSONObj constructClientMetadata(StringData appName) { } // namespace TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponStash) { - // Create a ClientMetadata object and set it on ClientMetadataIsMasterState. + // Create a ClientMetadata object and set it. auto obj = constructClientMetadata("appName"); auto clientMetadata = ClientMetadata::parse(obj["client"]); - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx()->getClient()); - clientMetadataIsMasterState.setClientMetadata(opCtx()->getClient(), - std::move(clientMetadata.getValue())); + ClientMetadata::setAndFinalize(opCtx()->getClient(), std::move(clientMetadata.getValue())); auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); @@ -2891,8 +2887,7 @@ TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponStash) { // Create another ClientMetadata object. auto newObj = constructClientMetadata("newAppName"); auto newClientMetadata = ClientMetadata::parse(newObj["client"]); - clientMetadataIsMasterState.setClientMetadata(opCtx()->getClient(), - std::move(newClientMetadata.getValue())); + ClientMetadata::setAndFinalize(opCtx()->getClient(), std::move(newClientMetadata.getValue())); txnParticipant.unstashTransactionResources(opCtx(), "insert"); txnParticipant.stashTransactionResources(opCtx()); @@ -2904,12 +2899,10 @@ TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponStash) { } TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponCommit) { - // Create a ClientMetadata object and set it on ClientMetadataIsMasterState. + // Create a ClientMetadata object and set it. auto obj = constructClientMetadata("appName"); auto clientMetadata = ClientMetadata::parse(obj["client"]); - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx()->getClient()); - clientMetadataIsMasterState.setClientMetadata(opCtx()->getClient(), - std::move(clientMetadata.getValue())); + ClientMetadata::setAndFinalize(opCtx()->getClient(), std::move(clientMetadata.getValue())); auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); @@ -2927,13 +2920,10 @@ TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponCommit) { } TEST_F(TransactionsMetricsTest, LastClientInfoShouldUpdateUponAbort) { - // Create a ClientMetadata object and set it on ClientMetadataIsMasterState. + // Create a ClientMetadata object and set it. auto obj = constructClientMetadata("appName"); auto clientMetadata = ClientMetadata::parse(obj["client"]); - - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx()->getClient()); - clientMetadataIsMasterState.setClientMetadata(opCtx()->getClient(), - std::move(clientMetadata.getValue())); + ClientMetadata::setAndFinalize(opCtx()->getClient(), std::move(clientMetadata.getValue())); auto sessionCheckout = checkOutSession(); auto txnParticipant = TransactionParticipant::get(opCtx()); diff --git a/src/mongo/embedded/embedded_ismaster.cpp b/src/mongo/embedded/embedded_ismaster.cpp index e42c4292dac..aa636ab7280 100644 --- a/src/mongo/embedded/embedded_ismaster.cpp +++ b/src/mongo/embedded/embedded_ismaster.cpp @@ -33,7 +33,6 @@ #include "mongo/db/commands.h" #include "mongo/db/ops/write_ops.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" namespace mongo { namespace { @@ -67,30 +66,9 @@ public: const std::string&, const BSONObj& cmdObj, BSONObjBuilder& result) { - - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx->getClient()); - bool seenIsMaster = clientMetadataIsMasterState.hasSeenIsMaster(); - if (!seenIsMaster) { - clientMetadataIsMasterState.setSeenIsMaster(); - } - - BSONElement element = cmdObj[kMetadataDocumentName]; - if (!element.eoo()) { - if (seenIsMaster) { - uasserted(ErrorCodes::ClientMetadataCannotBeMutated, - "The client metadata document may only be sent in the first isMaster"); - } - - auto swParseClientMetadata = ClientMetadata::parse(element); - uassertStatusOK(swParseClientMetadata.getStatus()); - - invariant(swParseClientMetadata.getValue()); - - swParseClientMetadata.getValue().get().logClientMetadata(opCtx->getClient()); - - clientMetadataIsMasterState.setClientMetadata( - opCtx->getClient(), std::move(swParseClientMetadata.getValue())); - } + auto metaElem = cmdObj[kMetadataDocumentName]; + ClientMetadata::setFromMetadata(opCtx->getClient(), metaElem); + ClientMetadata::tryFinalize(opCtx->getClient()); result.appendBool("ismaster", true); diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript index b195aa6534e..bb324eb3346 100644 --- a/src/mongo/rpc/SConscript +++ b/src/mongo/rpc/SConscript @@ -181,7 +181,6 @@ env.Library( target='client_metadata', source=[ 'metadata/client_metadata.cpp', - 'metadata/client_metadata_ismaster.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp index 6d9e7581698..253522a9efa 100644 --- a/src/mongo/rpc/metadata.cpp +++ b/src/mongo/rpc/metadata.cpp @@ -38,7 +38,7 @@ #include "mongo/db/logical_clock.h" #include "mongo/db/logical_time_validator.h" #include "mongo/db/repl/replication_coordinator.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" +#include "mongo/rpc/metadata/client_metadata.h" #include "mongo/rpc/metadata/config_server_metadata.h" #include "mongo/rpc/metadata/impersonated_user_metadata.h" #include "mongo/rpc/metadata/logical_time_metadata.h" @@ -85,7 +85,13 @@ void readRequestMetadata(OperationContext* opCtx, const BSONObj& metadataObj, bo readImpersonatedUserMetadata(impersonationElem, opCtx); - uassertStatusOK(ClientMetadataIsMasterState::readFromMetadata(opCtx, clientElem)); + // We check for "$client" but not "client" here, because currentOp can filter on "client" as + // a top-level field. + if (clientElem) { + // The '$client' field is populated by mongos when it sends requests to shards on behalf of + // its own requests. This may or may not be relevant for SERVER-50804. + ClientMetadata::setFromMetadataForOperation(opCtx, clientElem); + } ConfigServerMetadata::get(opCtx) = uassertStatusOK(ConfigServerMetadata::readFromMetadata(configSvrElem)); diff --git a/src/mongo/rpc/metadata/client_metadata.cpp b/src/mongo/rpc/metadata/client_metadata.cpp index 9b51a4bc750..faa62c32102 100644 --- a/src/mongo/rpc/metadata/client_metadata.cpp +++ b/src/mongo/rpc/metadata/client_metadata.cpp @@ -43,8 +43,10 @@ #include "mongo/db/operation_context.h" #include "mongo/s/is_mongos.h" #include "mongo/util/log.h" +#include "mongo/util/net/socket_utils.h" #include "mongo/util/processinfo.h" #include "mongo/util/str.h" +#include "mongo/util/version.h" namespace mongo { @@ -70,6 +72,13 @@ constexpr uint32_t kMaxMongoSMetadataDocumentByteLength = 512U; constexpr uint32_t kMaxMongoDMetadataDocumentByteLength = 1024U; constexpr uint32_t kMaxApplicationNameByteLength = 128U; +struct ClientMetadataState { + bool isFinalized = false; + boost::optional<ClientMetadata> meta; +}; +const auto getClientState = Client::declareDecoration<ClientMetadataState>(); +const auto getOperationState = OperationContext::declareDecoration<ClientMetadataState>(); + } // namespace StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElement& element) { @@ -443,4 +452,110 @@ StringData ClientMetadata::fieldName() { return kClientMetadataFieldName; } +bool ClientMetadata::tryFinalize(Client* client) { + auto lk = stdx::unique_lock(*client); + auto& state = getClientState(client); + if (std::exchange(state.isFinalized, true)) { + return false; + } + + lk.unlock(); + + if (state.meta) { + // If we reach this point, the ClientMetadata is effectively immutable because isFinalized + // is true. + state.meta->logClientMetadata(client); + } + + return true; +} + +const ClientMetadata* ClientMetadata::getForClient(Client* client) noexcept { + auto& state = getClientState(client); + if (!state.meta) { + // If we haven't finalized, it's still okay to return our existing value. + return nullptr; + } + return &state.meta.get(); +} + +const ClientMetadata* ClientMetadata::getForOperation(OperationContext* opCtx) noexcept { + auto& state = getOperationState(opCtx); + if (!state.isFinalized) { + return nullptr; + } + invariant(state.meta); + return &state.meta.get(); +} + +const ClientMetadata* ClientMetadata::get(Client* client) noexcept { + if (auto opCtx = client->getOperationContext()) { + if (auto meta = getForOperation(opCtx)) { + return meta; + } + } + + return getForClient(client); +} + +void ClientMetadata::setAndFinalize(Client* client, boost::optional<ClientMetadata> meta) { + auto lk = stdx::lock_guard(*client); + + auto& state = getClientState(client); + state.isFinalized = true; + state.meta = std::move(meta); +} + +void ClientMetadata::setFromMetadataForOperation(OperationContext* opCtx, BSONElement& elem) { + auto lk = stdx::lock_guard(*opCtx->getClient()); + + auto& state = getOperationState(opCtx); + auto wasFinalized = std::exchange(state.isFinalized, true); + uassert(ErrorCodes::ClientMetadataCannotBeMutated, + "The client metadata document may only be set once per operation", + !state.meta && !wasFinalized); + + state.meta = ClientMetadata::readFromMetadata(elem); +} + +void ClientMetadata::setFromMetadata(Client* client, BSONElement& elem) { + auto& state = getClientState(client); + + { + auto lk = stdx::lock_guard(*client); + if (state.isFinalized) { + uassert(ErrorCodes::ClientMetadataCannotBeMutated, + "The client metadata document may only be sent in the first hello", + elem.eoo()); + return; + } + } + + auto meta = ClientMetadata::readFromMetadata(elem); + if (meta && isMongos()) { + // If we had a full ClientMetadata and we're on mongos, attach some additional client data. + meta->setMongoSMetadata(getHostNameCachedAndPort(), + client->clientAddress(true), + VersionInfoInterface::instance().version()); + } + + auto lk = stdx::lock_guard(*client); + invariant(!state.meta, "ClientMetadata was previously set, it should be set precisely once"); + state.meta = std::move(meta); +} + +boost::optional<ClientMetadata> ClientMetadata::readFromMetadata(BSONElement& element) { + return uassertStatusOK(ClientMetadata::parse(element)); +} + +void ClientMetadata::writeToMetadata(BSONObjBuilder* builder) const noexcept { + auto& document = getDocument(); + if (document.isEmpty()) { + // Skip appending metadata if there is none + return; + } + + builder->append(ClientMetadata::fieldName(), document); +} + } // namespace mongo diff --git a/src/mongo/rpc/metadata/client_metadata.h b/src/mongo/rpc/metadata/client_metadata.h index eb467caab6a..294a88815ec 100644 --- a/src/mongo/rpc/metadata/client_metadata.h +++ b/src/mongo/rpc/metadata/client_metadata.h @@ -46,38 +46,35 @@ constexpr auto kMetadataDocumentName = "client"_sd; /** * The ClientMetadata class is responsible for parsing the client metadata document that is received - * in isMaster from clients. This class also provides static methods for client libraries to create - * a valid client metadata document. + * in the "client" field of the first hello from clients. The client metadata document can also be + * parsed from the "$client" field of any operation. This class also provides static methods for + * client libraries to write a valid client metadata document. * - * Example document of isMaster request with client metadata document: + * Example client metadata document: * { - * "isMaster" : 1, - * "client" : { - * "application" : { // Optional - * "name" : "string" // Optional with caveats - * }, - * "driver" : { // Required, Informational Only - * "name" : "string", // Required, Informational Only - * "version" : "string" // Required, Informational Only - * }, - * "os" : { // Required, Informational Only - * "type" : "string", // Required, Informational Only, See note - * "name" : "string", // Optional, Informational Only - * "architecture" : "string", // Optional, Informational Only - * "version" : "string" // Optional, Informational Only - * } - * "mongos" : { // Optional, Informational Only - * "host" : "string", // Optional, Informational Only - * "client" : "string", // Optional, Informational Only - * "version" : "string" // Optional, Informational Only - * } - * } + * "application" : { // Optional + * "name" : "string" // Optional with caveats + * }, + * "driver" : { // Required, Informational Only + * "name" : "string", // Required, Informational Only + * "version" : "string" // Required, Informational Only + * }, + * "os" : { // Required, Informational Only + * "type" : "string", // Required, Informational Only, See note + * "name" : "string", // Optional, Informational Only + * "architecture" : "string", // Optional, Informational Only + * "version" : "string" // Optional, Informational Only + * } + * "mongos" : { // Optional, Informational Only + * "host" : "string", // Optional, Informational Only + * "client" : "string", // Optional, Informational Only + * "version" : "string" // Optional, Informational Only + * } * } * - * For this classes' purposes, the client metadata document is the sub-document in "client". It is - * allowed to contain additional fields that are not listed in the example above. These additional - * fields are ignore by this class. The "os" document "type" field is required (defaults to - * "unknown" in Mongo Drivers). The "driver", and "os" documents while required, are for + * It is allowed to contain additional fields that are not listed in the example above. These + * additional fields are ignore by this class. The "os" document "type" field is required (defaults + * to "unknown" in Mongo Drivers). The "driver", and "os" documents while required, are for * informational purposes only. The content is logged to disk but otherwise ignored. * * See Driver Specification: "MongoDB Handshake" for more information. @@ -91,7 +88,54 @@ public: ClientMetadata& operator=(ClientMetadata&&) = default; /** - * Parse and validate a client metadata document contained in an isMaster request. + * Get the ClientMetadata for the Client. + * + * This function may return nullptr if there was no ClientMetadata provided for the + * Client. + * + * The pointer to ClientMetadata is valid to use if: + * - You hold the Client lock. + * - You are on the Client's thread. + */ + static const ClientMetadata* getForClient(Client* client) noexcept; + + /** + * Get the ClientMetadata for the OperationContext. + * + * This function may return nullptr if there was no ClientMetadata provided for the + * OperationContext. + * + * The pointer to ClientMetadata is valid to use if: + * - You hold the Client lock. + * - You are on the Client's thread. + */ + static const ClientMetadata* getForOperation(OperationContext* opCtx) noexcept; + + /** + * Get the prioritized ClientMetadata for the Client. + * + * This function returns getForOperation() if it returns a valid pointer, otherwise it returns + * getForClient(). + * + * The pointer to ClientMetadata is valid to use if: + * - You hold the Client lock. + * - You are on the Client's thread. + */ + static const ClientMetadata* get(Client* client) noexcept; + + /** + * Set the ClientMetadata for the Client directly. + * + * This should only be used in testing. It sets the ClientMetadata as finalized but does not + * check if it was previously finalized. It allows the user to replace the ClientMetadata for + * a Client, which is disallowed if done via setFromMetadata(). + * + * This function takes the Client lock. + */ + static void setAndFinalize(Client* client, boost::optional<ClientMetadata> meta); + + /** + * Parse and validate a client metadata document contained in a hello request. * * Empty or non-existent sub-documents are permitted. Non-empty documents are required to have * the fields driver.name, driver.version, and os.type which must be strings. @@ -155,6 +199,47 @@ public: BSONObjBuilder* builder); /** + * Mark the ClientMetadata as finalized. + * + * Once this function is called, no future hello can mutate the ClientMetadata. + * + * This function takes the Client lock. + */ + static bool tryFinalize(Client* client); + + /** + * Set the ClientMetadata for the Client by reading it from the given BSONElement. + * + * This function throws if the ClientMetadata has already been finalized but the BSONElement is + * an object. ClientMetadata is allowed to be set via the first hello only. + * + * This function takes the Client lock. + */ + static void setFromMetadata(Client* client, BSONElement& elem); + + /** + * Set the ClientMetadata for the OperationContext by reading it from the given BSONElement. + * + * This function throws if called more than once for the same OperationContext. + * + * This function takes the Client lock. + */ + static void setFromMetadataForOperation(OperationContext* opCtx, BSONElement& elem); + + /** + * Read from the $client field in requests. + * + * Throws an error if the $client section is not valid. It is valid for it to not exist though. + */ + static boost::optional<ClientMetadata> readFromMetadata(BSONElement& elem); + + /** + * Write the $client section to request bodies if there is a non-empty client metadata + * connection with the current client. + */ + void writeToMetadata(BSONObjBuilder* builder) const noexcept; + + /** * Modify the existing client metadata document to include a mongos section. * * hostAndPort is "host:port" of the running MongoS. diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.cpp b/src/mongo/rpc/metadata/client_metadata_ismaster.cpp deleted file mode 100644 index d095fa6ed1d..00000000000 --- a/src/mongo/rpc/metadata/client_metadata_ismaster.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (C) 2018-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. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/rpc/metadata/client_metadata_ismaster.h" - -#include <string> - -#include "mongo/base/init.h" -#include "mongo/base/status.h" -#include "mongo/db/client.h" -#include "mongo/db/operation_context.h" -#include "mongo/db/service_context.h" -#include "mongo/stdx/memory.h" - -namespace mongo { - -namespace { - -const auto getClientMetadataIsMasterState = - Client::declareDecoration<ClientMetadataIsMasterState>(); - -} // namespace - -ClientMetadataIsMasterState& ClientMetadataIsMasterState::get(Client* client) { - return getClientMetadataIsMasterState(*client); -} - -bool ClientMetadataIsMasterState::hasSeenIsMaster() const { - return _hasSeenIsMaster; -} - -void ClientMetadataIsMasterState::setSeenIsMaster() { - invariant(!_hasSeenIsMaster); - _hasSeenIsMaster = true; -} - -const boost::optional<ClientMetadata>& ClientMetadataIsMasterState::getClientMetadata() const { - return _clientMetadata; -} - -void ClientMetadataIsMasterState::setClientMetadata(Client* client, - boost::optional<ClientMetadata> clientMetadata, - bool setViaMetadata) { - auto& state = get(client); - - stdx::lock_guard<Client> lk(*client); - state._clientMetadata = std::move(clientMetadata); - state._setViaMetadata = setViaMetadata; -} - - -Status ClientMetadataIsMasterState::readFromMetadata(OperationContext* opCtx, - BSONElement& element) { - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx->getClient()); - - // If client metadata is not present in network requests, reset the in-memory metadata to be - // blank so that the wrong - // app name is not propagated. - if (element.eoo()) { - auto client = opCtx->getClient(); - - if (clientMetadataIsMasterState._setViaMetadata && !client->isInDirectClient()) { - clientMetadataIsMasterState.setClientMetadata(client, boost::none, true); - } - - return Status::OK(); - } - - auto swParseClientMetadata = ClientMetadata::parse(element); - - if (!swParseClientMetadata.getStatus().isOK()) { - return swParseClientMetadata.getStatus(); - } - - clientMetadataIsMasterState.setClientMetadata( - opCtx->getClient(), std::move(swParseClientMetadata.getValue()), true); - - return Status::OK(); -} - -void ClientMetadataIsMasterState::writeToMetadata(OperationContext* opCtx, - BSONObjBuilder* builder) { - // We may be asked to write metadata on background threads that are not associated with an - // operation context - if (!opCtx) { - return; - } - - const auto& clientMetadata = - ClientMetadataIsMasterState::get(opCtx->getClient()).getClientMetadata(); - - // Skip appending metadata if there is none - if (!clientMetadata || clientMetadata.get().getDocument().isEmpty()) { - return; - } - - builder->append(ClientMetadata::fieldName(), clientMetadata.get().getDocument()); -} - -} // namespace mongo diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.h b/src/mongo/rpc/metadata/client_metadata_ismaster.h deleted file mode 100644 index 91214a8ccab..00000000000 --- a/src/mongo/rpc/metadata/client_metadata_ismaster.h +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (C) 2018-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. - */ - -#pragma once - -#include <boost/optional.hpp> -#include <memory> -#include <string> - -#include "mongo/rpc/metadata/client_metadata.h" - -namespace mongo { - -class Client; - -/** - * ClientMetadataIsMasterState is responsible for tracking whether the client metadata document has - * been received by the specified Client object. - */ -class ClientMetadataIsMasterState { - ClientMetadataIsMasterState(const ClientMetadataIsMasterState&) = delete; - ClientMetadataIsMasterState& operator=(const ClientMetadataIsMasterState&) = delete; - -public: - ClientMetadataIsMasterState() = default; - - static ClientMetadataIsMasterState& get(Client* client); - - /** - * Get the optional client metadata object. - */ - const boost::optional<ClientMetadata>& getClientMetadata() const; - - /** - * Set the optional client metadata object. - */ - static void setClientMetadata(Client* client, - boost::optional<ClientMetadata> clientMetadata, - bool setViaMetadata = false); - - /** - * Check a flag to indicate that isMaster has been seen for this Client. - */ - bool hasSeenIsMaster() const; - - /** - * Set a flag to indicate that isMaster has been seen for this Client. - */ - void setSeenIsMaster(); - - /** - * Read from the $client field in requests. - * - * Returns an error if the $client section is not valid. It is valid for it to not exist though. - * - * Thread-Safety: - * None - must be only be read and written from the thread owning "Client". - */ - static Status readFromMetadata(OperationContext* opCtx, BSONElement& elem); - - /** - * Write the $client section to request bodies if there is a non-empty client metadata - * connection with the current client. - * - * Thread-Safety: - * None - must be only be read and written from the thread owning "Client". - */ - static void writeToMetadata(OperationContext* opCtx, BSONObjBuilder* builder); - -private: - // Optional client metadata document. - // Set if client sees isMaster cmd or the $client field. - // Thread-Safety: - // Can be read and written from the thread owning "Client". - // Can be read from other threads if they hold the "Client" lock. - boost::optional<ClientMetadata> _clientMetadata{boost::none}; - - // Indicates whether we have seen an is master for this client. - // Thread-Safety: - // None - must be only be read and written from the thread owning "Client". - bool _hasSeenIsMaster{false}; - - // Indicates whether we have set isMaster based on metadata or via isMaster - // Thread-Safety: - // None - must be only be read and written from the thread owning "Client". - bool _setViaMetadata{false}; -}; - -} // namespace mongo diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp index 260e15800f8..d5a13cd2a3c 100644 --- a/src/mongo/s/commands/cluster_is_master_cmd.cpp +++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp @@ -39,7 +39,6 @@ #include "mongo/db/ops/write_ops.h" #include "mongo/db/wire_version.h" #include "mongo/rpc/metadata/client_metadata.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" #include "mongo/transport/message_compressor_manager.h" #include "mongo/util/map_util.h" #include "mongo/util/net/socket_utils.h" @@ -84,34 +83,8 @@ public: BSONObjBuilder& result) override { CommandHelpers::handleMarkKillOnClientDisconnect(opCtx); - auto& clientMetadataIsMasterState = ClientMetadataIsMasterState::get(opCtx->getClient()); - bool seenIsMaster = clientMetadataIsMasterState.hasSeenIsMaster(); - if (!seenIsMaster) { - clientMetadataIsMasterState.setSeenIsMaster(); - } - - BSONElement element = cmdObj[kMetadataDocumentName]; - if (!element.eoo()) { - if (seenIsMaster) { - uasserted(ErrorCodes::ClientMetadataCannotBeMutated, - "The client metadata document may only be sent in the first isMaster"); - } - - auto swParseClientMetadata = ClientMetadata::parse(element); - uassertStatusOK(swParseClientMetadata.getStatus()); - - invariant(swParseClientMetadata.getValue()); - - swParseClientMetadata.getValue().get().logClientMetadata(opCtx->getClient()); - - swParseClientMetadata.getValue().get().setMongoSMetadata( - getHostNameCachedAndPort(), - opCtx->getClient()->clientAddress(true), - VersionInfoInterface::instance().version()); - - clientMetadataIsMasterState.setClientMetadata( - opCtx->getClient(), std::move(swParseClientMetadata.getValue())); - } + auto client = opCtx->getClient(); + ClientMetadata::tryFinalize(client); if (useLegacyResponseFields()) { result.appendBool("ismaster", true); diff --git a/src/mongo/s/commands/strategy.cpp b/src/mongo/s/commands/strategy.cpp index e03b3cbeb7c..b5342cb1509 100644 --- a/src/mongo/s/commands/strategy.cpp +++ b/src/mongo/s/commands/strategy.cpp @@ -62,6 +62,7 @@ #include "mongo/db/views/resolved_view.h" #include "mongo/rpc/factory.h" #include "mongo/rpc/get_status_from_command_result.h" +#include "mongo/rpc/metadata/client_metadata.h" #include "mongo/rpc/metadata/logical_time_metadata.h" #include "mongo/rpc/metadata/tracking_metadata.h" #include "mongo/rpc/op_msg.h" @@ -354,6 +355,8 @@ void runCommand(OperationContext* opCtx, return; } + const auto isHello = command->getName() == "hello"_sd || command->getName() == "isMaster"_sd; + CommandHelpers::uassertShouldAttemptParse(opCtx, command, request); // Parse the 'maxTimeMS' command option, and use it to set a deadline for the operation on @@ -418,6 +421,13 @@ void runCommand(OperationContext* opCtx, boost::optional<RouterOperationContextSession> routerSession; try { + if (isHello) { + // Preload generic ClientMetadata ahead of our first hello request. After the first + // request, metaElement should always be empty. + auto metaElem = request.body[kMetadataDocumentName]; + ClientMetadata::setFromMetadata(opCtx->getClient(), metaElem); + } + CommandHelpers::evaluateFailCommandFailPoint(opCtx, invocation.get()); if (osi.getAutocommit()) { routerSession.emplace(opCtx); diff --git a/src/mongo/s/sharding_egress_metadata_hook.cpp b/src/mongo/s/sharding_egress_metadata_hook.cpp index 10e837a2430..ec01d1be976 100644 --- a/src/mongo/s/sharding_egress_metadata_hook.cpp +++ b/src/mongo/s/sharding_egress_metadata_hook.cpp @@ -35,7 +35,7 @@ #include "mongo/base/status.h" #include "mongo/db/service_context.h" -#include "mongo/rpc/metadata/client_metadata_ismaster.h" +#include "mongo/rpc/metadata/client_metadata.h" #include "mongo/rpc/metadata/config_server_metadata.h" #include "mongo/rpc/metadata/impersonated_user_metadata.h" #include "mongo/rpc/metadata/metadata_hook.h" @@ -57,7 +57,12 @@ Status ShardingEgressMetadataHook::writeRequestMetadata(OperationContext* opCtx, BSONObjBuilder* metadataBob) { try { writeAuthDataToImpersonatedUserMetadata(opCtx, metadataBob); - ClientMetadataIsMasterState::writeToMetadata(opCtx, metadataBob); + if (opCtx) { + auto md = ClientMetadata::get(opCtx->getClient()); + if (md) { + md->writeToMetadata(metadataBob); + } + } rpc::ConfigServerMetadata(_getConfigServerOpTime()).writeToMetadata(metadataBob); return Status::OK(); } catch (...) { diff --git a/src/mongo/s/transaction_router_test.cpp b/src/mongo/s/transaction_router_test.cpp index 17b122340c6..40844906eb5 100644 --- a/src/mongo/s/transaction_router_test.cpp +++ b/src/mongo/s/transaction_router_test.cpp @@ -4617,10 +4617,8 @@ TEST_F(TransactionRouterMetricsTest, ReportResources) { auto obj = builder.obj(); auto clientMetadata = ClientMetadata::parse(obj["client"]); - auto& clientMetadataIsMasterState = - ClientMetadataIsMasterState::get(operationContext()->getClient()); - clientMetadataIsMasterState.setClientMetadata(operationContext()->getClient(), - std::move(clientMetadata.getValue())); + ClientMetadata::setAndFinalize(operationContext()->getClient(), + std::move(clientMetadata.getValue())); repl::ReadConcernArgs readConcernArgs; ASSERT_OK( |