diff options
Diffstat (limited to 'src/mongo/rpc')
-rw-r--r-- | src/mongo/rpc/SConscript | 26 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata.cpp | 400 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata.h | 230 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata_ismaster.cpp | 77 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata_ismaster.h | 89 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/client_metadata_test.cpp | 296 |
6 files changed, 1118 insertions, 0 deletions
diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript index 7822e216189..c3b2b73ddaf 100644 --- a/src/mongo/rpc/SConscript +++ b/src/mongo/rpc/SConscript @@ -139,6 +139,7 @@ env.Library( 'metadata/repl_set_metadata.cpp', ], LIBDEPS=[ + 'client_metadata', '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/bson/util/bson_extract', '$BUILD_DIR/mongo/client/read_preference', @@ -194,3 +195,28 @@ env.CppUnitTest( ], LIBDEPS=['metadata'] ) + +env.Library( + target='client_metadata', + source=[ + 'metadata/client_metadata.cpp', + 'metadata/client_metadata_ismaster.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + "$BUILD_DIR/mongo/util/concurrency/spin_lock", + '$BUILD_DIR/mongo/util/decorable', + '$BUILD_DIR/mongo/util/net/hostandport', + "$BUILD_DIR/mongo/util/processinfo", + ], +) + +env.CppUnitTest( + target='client_metadata_test', + source=[ + 'metadata/client_metadata_test.cpp', + ], + LIBDEPS=[ + 'client_metadata', + ] +) diff --git a/src/mongo/rpc/metadata/client_metadata.cpp b/src/mongo/rpc/metadata/client_metadata.cpp new file mode 100644 index 00000000000..ea03d5e0f84 --- /dev/null +++ b/src/mongo/rpc/metadata/client_metadata.cpp @@ -0,0 +1,400 @@ +/** + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kNetwork + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/metadata/client_metadata.h" + +#include <string> + +#include "mongo/base/status.h" +#include "mongo/base/status_with.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/operation_context.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/processinfo.h" + +namespace mongo { + +namespace { + +constexpr auto kApplication = "application"_sd; +constexpr auto kDriver = "driver"_sd; +constexpr auto kOperatingSystem = "os"_sd; + +constexpr auto kArchitecture = "architecture"_sd; +constexpr auto kName = "name"_sd; +constexpr auto kType = "type"_sd; +constexpr auto kVersion = "version"_sd; + +constexpr uint32_t kMaxMetadataDocumentByteLength = 512U; +constexpr uint32_t kMaxApplicationNameByteLength = 128U; + +} // namespace + +StatusWith<boost::optional<ClientMetadata>> ClientMetadata::parse(const BSONElement& element) { + if (element.eoo()) { + return {boost::none}; + } + + if (!element.isABSONObj()) { + 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)}; +} + +Status ClientMetadata::parseClientMetadataDocument(const BSONObj& doc) { + if (static_cast<uint32_t>(doc.objsize()) > kMaxMetadataDocumentByteLength) { + return Status(ErrorCodes::ClientMetadataDocumentTooLarge, + str::stream() << "The client metadata document must be less then or equal to " + << kMaxMetadataDocumentByteLength + << "bytes"); + } + + // Get a copy so that we can take a stable reference to the app name inside + BSONObj docOwned = doc.getOwned(); + + StringData appName; + bool foundDriver = false; + bool foundOperatingSystem = false; + + BSONObjIterator i(docOwned); + while (i.more()) { + BSONElement e = i.next(); + StringData name = e.fieldNameStringData(); + + if (name == kApplication) { + // Application is an optional sub-document, but we require it to be a document if + // specified. + if (!e.isABSONObj()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "The '" << kApplication + << "' field is required to be a BSON document in the " + "client metadata document"); + } + + auto swAppName = parseApplicationDocument(e.Obj()); + if (!swAppName.getStatus().isOK()) { + return swAppName.getStatus(); + } + + appName = swAppName.getValue(); + + } else if (name == kDriver) { + if (!e.isABSONObj()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "The '" << kDriver << "' field is required to be a " + "BSON document in the client " + "metadata document"); + } + + Status s = validateDriverDocument(e.Obj()); + if (!s.isOK()) { + return s; + } + + foundDriver = true; + } else if (name == kOperatingSystem) { + if (!e.isABSONObj()) { + return Status(ErrorCodes::TypeMismatch, + str::stream() << "The '" << kOperatingSystem + << "' field is required to be a BSON document in the " + "client metadata document"); + } + + Status s = validateOperatingSystemDocument(e.Obj()); + if (!s.isOK()) { + return s; + } + + foundOperatingSystem = true; + } + + // Ignore other fields as extra fields are allowed. + } + + // Driver is a required sub document. + if (!foundDriver) { + return Status(ErrorCodes::ClientMetadataMissingField, + str::stream() << "Missing required sub-document '" << kDriver + << "' in the client metadata document"); + } + + // 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(); +} + +StatusWith<StringData> ClientMetadata::parseApplicationDocument(const BSONObj& doc) { + BSONObjIterator i(doc); + + while (i.more()) { + BSONElement e = i.next(); + StringData name = e.fieldNameStringData(); + + // Name is the only required field, and any other fields are simply ignored. + if (name == kName) { + + if (e.type() != String) { + return { + ErrorCodes::TypeMismatch, + str::stream() << "The '" << kApplication << "." << kName + << "' field must be a string in the client metadata document"}; + } + + StringData value = e.checkAndGetStringData(); + + if (value.size() > kMaxApplicationNameByteLength) { + return {ErrorCodes::ClientMetadataAppNameTooLarge, + str::stream() << "The '" << kApplication << "." << kName + << "' field must be less then or equal to " + << kMaxApplicationNameByteLength + << " bytes in the client metadata document"}; + } + + return {std::move(value)}; + } + } + + return {StringData()}; +} + +Status ClientMetadata::validateDriverDocument(const BSONObj& doc) { + bool foundName = false; + bool foundVersion = false; + + BSONObjIterator i(doc); + while (i.more()) { + BSONElement e = i.next(); + StringData name = e.fieldNameStringData(); + + if (name == kName) { + if (e.type() != String) { + return Status( + ErrorCodes::TypeMismatch, + str::stream() << "The '" << kDriver << "." << kName + << "' field must be a string in the client metadata document"); + } + + foundName = true; + } else if (name == kVersion) { + if (e.type() != String) { + return Status( + ErrorCodes::TypeMismatch, + str::stream() << "The '" << kDriver << "." << kVersion + << "' field must be a string in the client metadata document"); + } + + foundVersion = true; + } + } + + if (foundName == false) { + return Status(ErrorCodes::ClientMetadataMissingField, + str::stream() << "Missing required field '" << kDriver << "." << kName + << "' in the client metadata document"); + } + + if (foundVersion == false) { + return Status(ErrorCodes::ClientMetadataMissingField, + str::stream() << "Missing required field '" << kDriver << "." << kVersion + << "' in the client metadata document"); + } + + return Status::OK(); +} + +Status ClientMetadata::validateOperatingSystemDocument(const BSONObj& doc) { + bool foundType = false; + + BSONObjIterator i(doc); + while (i.more()) { + BSONElement e = i.next(); + StringData name = e.fieldNameStringData(); + + if (name == kType) { + if (e.type() != String) { + return Status( + ErrorCodes::TypeMismatch, + str::stream() << "The '" << kOperatingSystem << "." << kType + << "' field must be a string in the client metadata document"); + } + + foundType = true; + } + } + + if (foundType == false) { + return Status(ErrorCodes::ClientMetadataMissingField, + str::stream() << "Missing required field '" << kOperatingSystem << "." + << kType + << "' in the client metadata document"); + } + + return Status::OK(); +} + +void ClientMetadata::serialize(StringData driverName, + StringData driverVersion, + BSONObjBuilder* builder) { + + ProcessInfo processInfo; + + serializePrivate(driverName, + driverVersion, + processInfo.getOsType(), + processInfo.getOsName(), + processInfo.getArch(), + processInfo.getOsVersion(), + builder); +} + +void ClientMetadata::serializePrivate(StringData driverName, + StringData driverVersion, + StringData osType, + StringData osName, + StringData osArchitecture, + StringData osVersion, + BSONObjBuilder* builder) { + invariant(!driverName.empty() && !driverVersion.empty() && !osType.empty() && !osName.empty() && + !osArchitecture.empty() && !osVersion.empty()); + + BSONObjBuilder metaObjBuilder(builder->subobjStart(kMetadataDocumentName)); + + { + BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kDriver)); + subObjBuilder.append(kName, driverName); + subObjBuilder.append(kVersion, driverVersion); + } + + { + BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kOperatingSystem)); + subObjBuilder.append(kType, osType); + subObjBuilder.append(kName, osName); + subObjBuilder.append(kArchitecture, osArchitecture); + subObjBuilder.append(kVersion, osVersion); + } +} + +Status ClientMetadata::serialize(StringData driverName, + StringData driverVersion, + StringData appName, + BSONObjBuilder* builder) { + + ProcessInfo processInfo; + + return serializePrivate(driverName, + driverVersion, + processInfo.getOsType(), + processInfo.getOsName(), + processInfo.getArch(), + processInfo.getOsVersion(), + appName, + builder); +} + +Status ClientMetadata::serializePrivate(StringData driverName, + StringData driverVersion, + StringData osType, + StringData osName, + StringData osArchitecture, + StringData osVersion, + StringData appName, + BSONObjBuilder* builder) { + invariant(!driverName.empty() && !driverVersion.empty() && !osType.empty() && !osName.empty() && + !osArchitecture.empty() && !osVersion.empty()); + + if (appName.size() > kMaxApplicationNameByteLength) { + return Status(ErrorCodes::ClientMetadataAppNameTooLarge, + str::stream() << "The '" << kApplication << "." << kName + << "' field must be less then or equal to " + << kMaxApplicationNameByteLength + << " bytes in the client metadata document"); + } + + { + BSONObjBuilder metaObjBuilder(builder->subobjStart(kMetadataDocumentName)); + + if (!appName.empty()) { + BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kApplication)); + subObjBuilder.append(kName, appName); + } + + { + BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kDriver)); + subObjBuilder.append(kName, driverName); + subObjBuilder.append(kVersion, driverVersion); + } + + { + BSONObjBuilder subObjBuilder(metaObjBuilder.subobjStart(kOperatingSystem)); + subObjBuilder.append(kType, osType); + subObjBuilder.append(kName, osName); + subObjBuilder.append(kArchitecture, osArchitecture); + subObjBuilder.append(kVersion, osVersion); + } + } + + return Status::OK(); +} + +StringData ClientMetadata::getApplicationName() const { + return _appName; +} + +const BSONObj& ClientMetadata::getDocument() const { + return _document; +} + +void ClientMetadata::logClientMetadata(Client* client) const { + invariant(!getDocument().isEmpty()); + log() << "received client metadata from " << client->getRemote().toString() << " " + << client->desc() << ": " << getDocument(); +} + +} // namespace mongo diff --git a/src/mongo/rpc/metadata/client_metadata.h b/src/mongo/rpc/metadata/client_metadata.h new file mode 100644 index 00000000000..92646e3a88d --- /dev/null +++ b/src/mongo/rpc/metadata/client_metadata.h @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#pragma once + +#include <string> + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/base/status_with.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" + +namespace mongo { + +class Client; +class ClientBasic; +class OperationContext; + +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. + * + * Example document of isMaster request with 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 + * } + * } + * } + * + * 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 + * informational purposes only. The content is logged to disk but otherwise ignored. + * + * See Driver Specification: "MongoDB Handshake" for more information. + */ +class ClientMetadata { + MONGO_DISALLOW_COPYING(ClientMetadata); + +public: + ClientMetadata(ClientMetadata&&) = default; + ClientMetadata& operator=(ClientMetadata&&) = default; + + /** + * Parse and validate a client metadata document contained in an isMaster 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. + * + * Returns an empty optional if element is empty. + */ + static StatusWith<boost::optional<ClientMetadata>> parse(const BSONElement& element); + + /** + * 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 + * BSONObjBuilder: + * + * "client" : { + * "driver" : { + * "name" : "string", + * "version" : "string" + * }, + * "os" : { + * "type" : "string", + * "name" : "string", + * "architecture" : "string", + * "version" : "string" + * } + * } + */ + static void serialize(StringData driverName, StringData driverVersion, BSONObjBuilder* builder); + + /** + * Create a new client metadata document with os information from the ProcessInfo class. + * + * driverName - name of the driver, must not be empty + * driverVersion - a string for the driver version, must not be empty + * + * Notes: appName must be <= 128 bytes otherwise an error is returned. It may be empty in which + * case it is omitted from the output document. + * + * This method outputs the "client" field, and client metadata sub-document in the + * BSONObjBuilder: + * + * "client" : { + * "application" : { + * "name" : "string" + * }, + * "driver" : { + * "name" : "string", + * "version" : "string" + * }, + * "os" : { + * "type" : "string", + * "name" : "string", + * "architecture" : "string", + * "version" : "string" + * } + * } + */ + static Status serialize(StringData driverName, + StringData driverVersion, + StringData appName, + BSONObjBuilder* builder); + + /** + * Get the Application Name for the client metadata document. + * + * Used to log Application Name in slow operation reports, and into system.profile. + * Return: May be empty. + */ + StringData getApplicationName() const; + + /** + * Get the BSON Document of the client metadata document. In the example above in the class + * comment, this is the document in the "client" field. + * + * Return: May be empty. + */ + const BSONObj& getDocument() const; + + /** + * Log client and client metadata information to disk. + */ + void logClientMetadata(Client* client) const; + +public: + /** + * Create a new client metadata document. + * + * Exposed for Unit Test purposes + */ + static void serializePrivate(StringData driverName, + StringData driverVersion, + StringData osType, + StringData osName, + StringData osArchitecture, + StringData osVersion, + BSONObjBuilder* builder); + + /** + * Create a new client metadata document. + * + * driverName - name of the driver + * driverVersion - a string for the driver version + * osType - name of host operating system of client, i.e. uname -s + * osName - name of operating system distro, i.e. "Ubuntu..." or "Microsoft Windows 8" + * osArchitecture - architecture of host operating system, i.e. uname -p + * osVersion - operating system version, i.e. uname -v + * + * Notes: appName must be <= 128 bytes otherwise an error is returned. It may be empty in which + * case it is omitted from the output document. All other fields must not be empty. + * + * Exposed for Unit Test purposes + */ + static Status serializePrivate(StringData driverName, + StringData driverVersion, + StringData osType, + StringData osName, + StringData osArchitecture, + StringData osVersion, + StringData appName, + BSONObjBuilder* builder); + +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); + +private: + // Parsed Client Metadata document + // May be empty + // Owned + BSONObj _document; + + // Application Name extracted from the client metadata document. + // May be empty + StringData _appName; +}; + +} // namespace mongo diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.cpp b/src/mongo/rpc/metadata/client_metadata_ismaster.cpp new file mode 100644 index 00000000000..53164ed33d2 --- /dev/null +++ b/src/mongo/rpc/metadata/client_metadata_ismaster.cpp @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/metadata/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 = + ClientBasic::declareDecoration<ClientMetadataIsMasterState>(); + +} // namespace + +ClientMetadataIsMasterState& ClientMetadataIsMasterState::get(ClientBasic* 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) { + auto& state = get(client); + + stdx::lock_guard<Client> lk(*client); + state._clientMetadata = std::move(clientMetadata); +} + + +} // namespace mongo diff --git a/src/mongo/rpc/metadata/client_metadata_ismaster.h b/src/mongo/rpc/metadata/client_metadata_ismaster.h new file mode 100644 index 00000000000..9bd274ae4f6 --- /dev/null +++ b/src/mongo/rpc/metadata/client_metadata_ismaster.h @@ -0,0 +1,89 @@ +/** +* Copyright (C) 2016 MongoDB Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* As a special exception, the copyright holders give permission to link the +* code of portions of this program with the OpenSSL library under certain +* conditions as described in each individual source file and distribute +* linked combinations including the program with the OpenSSL library. You +* must comply with the GNU Affero General Public License in all respects +* for all of the code used other than as permitted herein. If you modify +* file(s) with this exception, you may extend this exception to your +* version of the file(s), but you are not obligated to do so. If you do not +* wish to do so, delete this exception statement from your version. If you +* delete this exception statement from all source files in the program, +* then also delete it in the license file. +*/ + +#pragma once + +#include <boost/optional.hpp> +#include <memory> +#include <string> + +#include "mongo/base/disallow_copying.h" +#include "mongo/rpc/metadata/client_metadata.h" + +namespace mongo { + +class Client; +class ClientBasic; + +/** + * ClientMetadataIsMasterState is responsible for tracking whether the client metadata document has + * been received by the specified Client object. + */ +class ClientMetadataIsMasterState { + MONGO_DISALLOW_COPYING(ClientMetadataIsMasterState); + +public: + ClientMetadataIsMasterState() = default; + + static ClientMetadataIsMasterState& get(ClientBasic* 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); + + /** + * 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(); + +private: + // Optional client metadata document. + // Set if client sees isMaster cmd or as part of OP_Command processing. + // 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}; +}; + +} // namespace mongo diff --git a/src/mongo/rpc/metadata/client_metadata_test.cpp b/src/mongo/rpc/metadata/client_metadata_test.cpp new file mode 100644 index 00000000000..8d64e5e7697 --- /dev/null +++ b/src/mongo/rpc/metadata/client_metadata_test.cpp @@ -0,0 +1,296 @@ +/** + * Copyright (C) 2016 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects + * for all of the code used other than as permitted herein. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you do not + * wish to do so, delete this exception statement from your version. If you + * delete this exception statement from all source files in the program, + * then also delete it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kDefault + +#include "mongo/platform/basic.h" + +#include "mongo/rpc/metadata/client_metadata.h" + +#include <boost/filesystem.hpp> +#include <map> + +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/log.h" + +namespace mongo { + +constexpr auto kMetadataDoc = "client"_sd; +constexpr auto kApplication = "application"_sd; +constexpr auto kDriver = "driver"_sd; +constexpr auto kName = "name"_sd; +constexpr auto kType = "type"_sd; +constexpr auto kVersion = "version"_sd; +constexpr auto kOperatingSystem = "os"_sd; +constexpr auto kArchitecture = "architecture"_sd; + +constexpr auto kUnknown = "unkown"_sd; + +#define ASSERT_DOC_OK(...) \ + do { \ + auto _swParseStatus = \ + ClientMetadata::parse(BSON(kMetadataDoc << BSON(__VA_ARGS__))[kMetadataDoc]); \ + ASSERT_OK(_swParseStatus.getStatus()); \ + } while (0) + +#define ASSERT_DOC_NOT_OK(...) \ + do { \ + auto _swParseStatus = \ + ClientMetadata::parse(BSON(kMetadataDoc << BSON(__VA_ARGS__))[kMetadataDoc]); \ + ASSERT_NOT_OK(_swParseStatus.getStatus()); \ + } while (0) + + +TEST(ClientMetadatTest, TestLoopbackTest) { + // Serialize without application name + { + BSONObjBuilder builder; + ASSERT_OK(ClientMetadata::serializePrivate("a", "b", "c", "d", "e", "f", "g", &builder)); + + auto obj = builder.obj(); + auto swParseStatus = ClientMetadata::parse(obj[kMetadataDoc]); + ASSERT_OK(swParseStatus.getStatus()); + ASSERT_EQUALS("g", swParseStatus.getValue().get().getApplicationName()); + + BSONObj outDoc = + BSON(kMetadataDoc << BSON( + kApplication << BSON(kName << "g") << kDriver + << BSON(kName << "a" << kVersion << "b") + << kOperatingSystem + << BSON(kType << "c" << kName << "d" << kArchitecture << "e" + << kVersion + << "f"))); + ASSERT_EQUALS(obj, outDoc); + } + + // Serialize without application name + { + BSONObjBuilder builder; + ClientMetadata::serializePrivate("a", "b", "c", "d", "e", "f", &builder); + + auto obj = builder.obj(); + auto swParseStatus = ClientMetadata::parse(obj[kMetadataDoc]); + ASSERT_OK(swParseStatus.getStatus()); + + BSONObj outDoc = BSON( + kMetadataDoc << BSON( + kDriver << BSON(kName << "a" << kVersion << "b") << kOperatingSystem + << BSON(kType << "c" << kName << "d" << kArchitecture << "e" << kVersion + << "f"))); + ASSERT_EQUALS(obj, outDoc); + } + + // Serialize with the os information automatically computed + { + BSONObjBuilder builder; + ASSERT_OK(ClientMetadata::serialize("a", "b", "f", &builder)); + + auto obj = builder.obj(); + + auto swParse = ClientMetadata::parse(obj[kMetadataDoc]); + ASSERT_OK(swParse.getStatus()); + ASSERT_EQUALS("f", swParse.getValue().get().getApplicationName()); + } +} + +// Mixed: no client metadata document +TEST(ClientMetadatTest, TestEmptyDoc) { + { + auto parseStatus = ClientMetadata::parse(BSONElement()); + + ASSERT_OK(parseStatus.getStatus()); + } + + { + auto obj = BSON("client" << BSONObj()); + auto parseStatus = ClientMetadata::parse(obj[kMetadataDoc]); + + ASSERT_NOT_OK(parseStatus.getStatus()); + } +} + +// Positive: test with only required fields +TEST(ClientMetadatTest, TestRequiredOnlyFields) { + // Without app name + ASSERT_DOC_OK(kDriver << BSON(kName << "n1" << kVersion << "v1") << kOperatingSystem + << BSON(kType << kUnknown)); + + // With AppName + ASSERT_DOC_OK(kApplication << BSON(kName << "1") << kDriver + << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); +} + + +// Positive: test with app_name spelled wrong fields +TEST(ClientMetadatTest, TestWithAppNameSpelledWrong) { + ASSERT_DOC_OK(kApplication << BSON("extra" + << "1") + << kDriver + << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); +} + +// Positive: test with empty application document +TEST(ClientMetadatTest, TestWithEmptyApplication) { + ASSERT_DOC_OK(kApplication << BSONObj() << kDriver << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); +} + +// Negative: test with appplication wrong type +TEST(ClientMetadatTest, TestNegativeWithAppNameWrongType) { + ASSERT_DOC_NOT_OK(kApplication << "1" << kDriver << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); +} + +// Positive: test with extra fields +TEST(ClientMetadatTest, TestExtraFields) { + ASSERT_DOC_OK(kApplication << BSON(kName << "1" + << "extra" + << "v1") + << kDriver + << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); + ASSERT_DOC_OK(kApplication << BSON(kName << "1" + << "extra" + << "v1") + << kDriver + << BSON(kName << "n1" << kVersion << "v1" + << "extra" + << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); + ASSERT_DOC_OK(kApplication << BSON(kName << "1" + << "extra" + << "v1") + << kDriver + << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown << "extra" + << "v1")); + ASSERT_DOC_OK(kApplication << BSON(kName << "1" + << "extra" + << "v1") + << kDriver + << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown) + << "extra" + << "v1"); +} + +// Negative: only application specified +TEST(ClientMetadatTest, TestNegativeOnlyApplication) { + ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1" + << "extra" + << "v1")); +} + +// Negative: all combinations of only missing 1 required field +TEST(ClientMetadatTest, TestNegativeMissingRequiredOneField) { + ASSERT_DOC_NOT_OK(kDriver << BSON(kVersion << "v1") << kOperatingSystem + << BSON(kType << kUnknown)); + ASSERT_DOC_NOT_OK(kDriver << BSON(kName << "n1") << kOperatingSystem + << BSON(kType << kUnknown)); + ASSERT_DOC_NOT_OK(kDriver << BSON(kName << "n1" << kVersion << "v1")); +} + +// Negative: document with wrong types for required fields +TEST(ClientMetadatTest, TestNegativeWrongTypes) { + ASSERT_DOC_NOT_OK(kApplication << BSON(kName << 1) << kDriver + << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); + ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver + << BSON(kName << 1 << kVersion << "v1") + << kOperatingSystem + << BSON(kType << kUnknown)); + ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver + << BSON(kName << "n1" << kVersion << 1) + << kOperatingSystem + << BSON(kType << kUnknown)); + ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver + << BSON(kName << "n1" << kVersion << "v1") + << kOperatingSystem + << BSON(kType << 1)); +} + +// Negative: document larger than 512 bytes +TEST(ClientMetadatTest, TestNegativeLargeDocument) { + { + std::string str(350, 'x'); + ASSERT_DOC_OK(kApplication << BSON(kName << "1") << kDriver + << BSON(kName << "n1" << kVersion << "1") + << kOperatingSystem + << BSON(kType << kUnknown) + << "extra" + << str); + } + { + std::string str(512, 'x'); + ASSERT_DOC_NOT_OK(kApplication << BSON(kName << "1") << kDriver + << BSON(kName << "n1" << kVersion << "1") + << kOperatingSystem + << BSON(kType << kUnknown) + << "extra" + << str); + } +} + +// Negative: document with app_name larger than 128 bytes +TEST(ClientMetadatTest, TestNegativeLargeAppName) { + { + std::string str(128, 'x'); + ASSERT_DOC_OK(kApplication << BSON(kName << str) << kDriver + << BSON(kName << "n1" << kVersion << "1") + << kOperatingSystem + << BSON(kType << kUnknown)); + + BSONObjBuilder builder; + ASSERT_OK(ClientMetadata::serialize("n1", "1", str, &builder)); + } + { + std::string str(129, 'x'); + ASSERT_DOC_NOT_OK(kApplication << BSON(kName << str) << kDriver + << BSON(kName << "n1" << kVersion << "1") + << kOperatingSystem + << BSON(kType << kUnknown)); + + BSONObjBuilder builder; + ASSERT_NOT_OK(ClientMetadata::serialize("n1", "1", str, &builder)); + } +} + +} // namespace mongo |