diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2018-04-10 13:29:16 -0400 |
---|---|---|
committer | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2018-04-10 13:29:16 -0400 |
commit | e2327f7b9acd5016b419db6a9b9a6c150bfdd79b (patch) | |
tree | 9f247efc1ab649a1e4fe886306ef125a9138bd7d | |
parent | b9cbfd8fd3cc8438a3e8e3986505f348416f5da4 (diff) | |
download | mongo-e2327f7b9acd5016b419db6a9b9a6c150bfdd79b.tar.gz |
SERVER-34220 Write Curl Networking Impl
-rw-r--r-- | SConstruct | 7 | ||||
-rw-r--r-- | src/mongo/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/base/error_codes.err | 3 | ||||
-rw-r--r-- | src/mongo/db/db.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/free_mon/SConscript | 26 | ||||
-rw-r--r-- | src/mongo/db/free_mon/free_mon_http.h | 60 | ||||
-rw-r--r-- | src/mongo/db/free_mon/free_mon_mongod.cpp | 186 | ||||
-rw-r--r-- | src/mongo/db/free_mon/free_mon_mongod.h | 46 | ||||
-rw-r--r-- | src/mongo/db/free_mon/free_mon_network.h | 58 | ||||
-rw-r--r-- | src/mongo/db/free_mon/free_mon_protocol.idl | 123 | ||||
-rw-r--r-- | src/mongo/db/free_mon/free_mon_stub.cpp | 39 | ||||
-rw-r--r-- | src/mongo/db/free_mon/http_client_curl.cpp | 230 |
12 files changed, 783 insertions, 0 deletions
diff --git a/SConstruct b/SConstruct index 7e095394db6..f012e1a27d9 100644 --- a/SConstruct +++ b/SConstruct @@ -275,6 +275,13 @@ add_option('gcov', nargs=0, ) +add_option('enable-free-mon', + choices=["on", "off"], + default="off", + help='Disable support for Free Monitoring to avoid HTTP client library dependencies', + type='choice', +) + add_option('use-sasl-client', help='Support SASL authentication in the client library', nargs=0, diff --git a/src/mongo/SConscript b/src/mongo/SConscript index c7d8a7ac98d..45135329615 100644 --- a/src/mongo/SConscript +++ b/src/mongo/SConscript @@ -295,6 +295,7 @@ env.Library( 'db/commands/mongod_fcv', 'db/dbdirectclient', 'db/ftdc/ftdc_mongod', + 'db/free_mon/free_mon_mongod', 'db/index_d', 'db/initialize_snmp', 'db/keys_collection_client_direct', diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index 64b78b5a948..e1c5002a512 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -250,6 +250,9 @@ error_code("StaleDbVersion", 249, extra="StaleDbRoutingVersion"); error_code("StaleChunkHistory", 250); error_code("NoSuchTransaction", 251) error_code("ReentrancyNotAllowed", 252) +error_code("FreeMonHttpInFlight", 253) +error_code("FreeMonHttpTemporaryFailure", 254) +error_code("FreeMonHttpPermanentFailure", 255) # Error codes 4000-8999 are reserved. diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp index beb87a5d086..52e368abb7a 100644 --- a/src/mongo/db/db.cpp +++ b/src/mongo/db/db.cpp @@ -70,6 +70,7 @@ #include "mongo/db/dbhelpers.h" #include "mongo/db/dbmessage.h" #include "mongo/db/exec/working_set_common.h" +#include "mongo/db/free_mon/free_mon_mongod.h" #include "mongo/db/ftdc/ftdc_mongod.h" #include "mongo/db/global_settings.h" #include "mongo/db/index_names.h" @@ -505,6 +506,8 @@ ExitCode _initAndListen(int listenPort) { startMongoDFTDC(); + startFreeMonitoring(serviceContext); + restartInProgressIndexesFromLastShutdown(startupOpCtx.get()); if (serverGlobalParams.clusterRole == ClusterRole::ShardServer) { @@ -910,6 +913,7 @@ void shutdownTask() { } } #endif + stopFreeMonitoring(); // Shutdown Full-Time Data Capture stopMongoDFTDC(); diff --git a/src/mongo/db/free_mon/SConscript b/src/mongo/db/free_mon/SConscript index 6a2b57d4a05..0bfce9a48a8 100644 --- a/src/mongo/db/free_mon/SConscript +++ b/src/mongo/db/free_mon/SConscript @@ -12,6 +12,7 @@ fmEnv.Library( source=[ 'free_mon_queue.cpp', 'free_mon_storage.cpp', + env.Idlc('free_mon_protocol.idl')[0], env.Idlc('free_mon_storage.idl')[0], ], LIBDEPS_PRIVATE=[ @@ -24,6 +25,31 @@ fmEnv.Library( ], ) +if get_option("enable-free-mon") == "on": + fmEnv.Library( + target='free_mon_mongod', + source=[ + 'free_mon_mongod.cpp', + 'http_client_curl.cpp', + #'http_client_curl.cpp' if not env.TargetOSIs('windows') else 'http_client_winhttp.cpp', + ], + LIBDEPS=[ + 'free_mon', + ], + SYSLIBDEPS=[ + 'libcurld' if env.TargetOSIs('windows') else 'curl', + # 'winhttp' if env.TargetOSIs('windows') else 'curl', + ], + ) +else: + fmEnv.Library( + target='free_mon_mongod', + source=[ + 'free_mon_stub.cpp', + ], + ) + + env.CppUnitTest( target='free_mon_test', source=[ diff --git a/src/mongo/db/free_mon/free_mon_http.h b/src/mongo/db/free_mon/free_mon_http.h new file mode 100644 index 00000000000..cd162f910ea --- /dev/null +++ b/src/mongo/db/free_mon/free_mon_http.h @@ -0,0 +1,60 @@ +/** + * Copyright (C) 2018 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 <cstdint> +#include <vector> + +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/executor/thread_pool_task_executor.h" +#include "mongo/util/concurrency/thread_pool.h" +#include "mongo/util/future.h" + +namespace mongo { + +constexpr uint64_t kConnectionTimeoutSeconds = 60L; +constexpr uint64_t kTotalRequestTimeoutSeconds = 120L; + +/** + * Interface used to upload and receive binary payloads to HTTP servers. + */ +class FreeMonHttpClientInterface { +public: + virtual ~FreeMonHttpClientInterface(); + + /** + * Url is a full URL with hostname and protocol specification. + */ + virtual Future<std::vector<uint8_t>> postAsync(StringData url, BSONObj data) = 0; +}; + +std::unique_ptr<FreeMonHttpClientInterface> createFreeMonHttpClient( + std::unique_ptr<executor::ThreadPoolTaskExecutor> executor); +} // namespace mongo diff --git a/src/mongo/db/free_mon/free_mon_mongod.cpp b/src/mongo/db/free_mon/free_mon_mongod.cpp new file mode 100644 index 00000000000..aa4008973d5 --- /dev/null +++ b/src/mongo/db/free_mon/free_mon_mongod.cpp @@ -0,0 +1,186 @@ +/** + * Copyright (C) 2018 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/db/free_mon/free_mon_mongod.h" + +#include <mutex> +#include <string> + +#include "mongo/base/data_type_validated.h" +#include "mongo/base/error_codes.h" +#include "mongo/base/status.h" +#include "mongo/bson/bsonelement.h" +#include "mongo/bson/bsonmisc.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/bsontypes.h" +#include "mongo/db/commands/test_commands_enabled.h" +#include "mongo/db/free_mon/free_mon_http.h" +#include "mongo/db/free_mon/free_mon_message.h" +#include "mongo/db/free_mon/free_mon_network.h" +#include "mongo/db/free_mon/free_mon_protocol_gen.h" +#include "mongo/db/free_mon/free_mon_storage.h" +#include "mongo/db/ftdc/ftdc_server.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/repl/replication_coordinator.h" +#include "mongo/db/server_parameters.h" +#include "mongo/db/service_context.h" +#include "mongo/executor/network_interface_factory.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/concurrency/thread_pool.h" +#include "mongo/util/future.h" + +namespace mongo { + +namespace { + +/** + * Expose cloudFreeMonitoringEndpointURL set parameter to URL for free monitoring. + */ +class ExportedFreeMonEndpointURL : public LockedServerParameter<std::string> { +public: + ExportedFreeMonEndpointURL() + : LockedServerParameter<std::string>("cloudFreeMonitoringEndpointURL", + "https://localhost:8080", + ServerParameterType::kStartupOnly) {} + + + Status setFromString(const std::string& str) final { + // Check for http, not https here because testEnabled may not be set yet + if (!str.compare(0, 4, "http")) { + return Status(ErrorCodes::BadValue, + "ExportedFreeMonEndpointURL only supports https:// URLs"); + } + + return setLocked(str); + } +} exportedExportedFreeMonEndpointURL; + + +class FreeMonNetworkHttp : public FreeMonNetworkInterface { +public: + explicit FreeMonNetworkHttp(std::unique_ptr<FreeMonHttpClientInterface> client) + : _client(std::move(client)) {} + ~FreeMonNetworkHttp() final = default; + + Future<FreeMonRegistrationResponse> sendRegistrationAsync( + const FreeMonRegistrationRequest& req) override { + BSONObj reqObj = req.toBSON(); + + return _client->postAsync(exportedExportedFreeMonEndpointURL.getURL() + "/register", reqObj) + .then([](std::vector<uint8_t> blob) { + + if (blob.empty()) { + uassertStatusOK( + Status(ErrorCodes::FreeMonHttpTemporaryFailure, "Empty response received")); + } + + ConstDataRange cdr(reinterpret_cast<char*>(blob.data()), blob.size()); + + auto swDoc = cdr.read<Validated<BSONObj>>(); + uassertStatusOK(swDoc.getStatus()); + + BSONObj respObj(swDoc.getValue()); + + auto resp = + FreeMonRegistrationResponse::parse(IDLParserErrorContext("response"), respObj); + + return resp; + }); + } + + Future<FreeMonMetricsResponse> sendMetricsAsync(const FreeMonMetricsRequest& req) override { + BSONObj reqObj = req.toBSON(); + + return _client->postAsync(exportedExportedFreeMonEndpointURL.getURL() + "/metrics", reqObj) + .then([](std::vector<uint8_t> blob) { + + if (blob.empty()) { + uassertStatusOK( + Status(ErrorCodes::FreeMonHttpTemporaryFailure, "Empty response received")); + } + + ConstDataRange cdr(reinterpret_cast<char*>(blob.data()), blob.size()); + + auto swDoc = cdr.read<Validated<BSONObj>>(); + uassertStatusOK(swDoc.getStatus()); + + BSONObj respObj(swDoc.getValue()); + + auto resp = + FreeMonMetricsResponse::parse(IDLParserErrorContext("response"), respObj); + + return resp; + }); + } + +private: + std::unique_ptr<FreeMonHttpClientInterface> _client; +}; + + +auto makeTaskExecutor(ServiceContext* /*serviceContext*/) { + ThreadPool::Options tpOptions; + tpOptions.poolName = "freemon"; + tpOptions.maxThreads = 2; + tpOptions.onCreateThread = [](const std::string& threadName) { + Client::initThread(threadName.c_str()); + }; + return stdx::make_unique<executor::ThreadPoolTaskExecutor>( + std::make_unique<ThreadPool>(tpOptions), + executor::makeNetworkInterface("NetworkInterfaceASIO-FreeMon")); +} + +} // namespace + + +void startFreeMonitoring(ServiceContext* serviceContext) { + + auto executor = makeTaskExecutor(serviceContext); + + executor->startup(); + + auto http = createFreeMonHttpClient(std::move(executor)); + if (http == nullptr) { + // HTTP init failed + return; + } + + auto network = + std::unique_ptr<FreeMonNetworkInterface>(new FreeMonNetworkHttp(std::move(http))); +} + +void stopFreeMonitoring() {} + +FreeMonHttpClientInterface::~FreeMonHttpClientInterface() = default; + +FreeMonNetworkInterface::~FreeMonNetworkInterface() = default; + +} // namespace mongo diff --git a/src/mongo/db/free_mon/free_mon_mongod.h b/src/mongo/db/free_mon/free_mon_mongod.h new file mode 100644 index 00000000000..b4638930cf6 --- /dev/null +++ b/src/mongo/db/free_mon/free_mon_mongod.h @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2018 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 "mongo/db/service_context.h" + +namespace mongo { + +/** + * Start Free Monitoring + * Starts 1 thread. + */ +void startFreeMonitoring(ServiceContext* serviceContext); + +/** + * Stop Free Monitoring + */ +void stopFreeMonitoring(); + +} // namespace mongo diff --git a/src/mongo/db/free_mon/free_mon_network.h b/src/mongo/db/free_mon/free_mon_network.h new file mode 100644 index 00000000000..f3a84901353 --- /dev/null +++ b/src/mongo/db/free_mon/free_mon_network.h @@ -0,0 +1,58 @@ +/** + * Copyright (C) 2018 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 "mongo/db/free_mon/free_mon_protocol_gen.h" +#include "mongo/util/future.h" + +namespace mongo { + +/** + * Makes HTTPS calls to cloud endpoint. + */ +class FreeMonNetworkInterface { +public: + virtual ~FreeMonNetworkInterface(); + + /** + * POSTs FreeMonRegistrationRequest to endpoint. + * + * Returns a FreeMonRegistrationResponse or throws an error on non-HTTP 200. + */ + virtual Future<FreeMonRegistrationResponse> sendRegistrationAsync( + const FreeMonRegistrationRequest& req) = 0; + + /** + * POSTs FreeMonMetricsRequest to endpoint. + * + * Returns a FreeMonMetricsResponse or throws an error on non-HTTP 200. + */ + virtual Future<FreeMonMetricsResponse> sendMetricsAsync(const FreeMonMetricsRequest& req) = 0; +}; +} // namespace mongo diff --git a/src/mongo/db/free_mon/free_mon_protocol.idl b/src/mongo/db/free_mon/free_mon_protocol.idl new file mode 100644 index 00000000000..0ebac53c4b8 --- /dev/null +++ b/src/mongo/db/free_mon/free_mon_protocol.idl @@ -0,0 +1,123 @@ +# Copyright (C) 2017 MongoDB Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3, +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +global: + cpp_namespace: "mongo" + +imports: + - "mongo/idl/basic_types.idl" + + +enums: + MetricsEncoding: + description: "Metrics Encoding Methods" + type: string + values: + snappy: "snappy" + + +structs: + FreeMonRegistrationRequest: + description: "Registration Request to Cloud Server" + fields: + version: + description: "Protocol version, initial version is 1" + type: long + payload: + description: "Payload of registration information" + type: object + id: + description: "Existing Registration Id" + type: string + optional: true + tag: + description: "Tag" + type: array<string> + optional: true + + FreeMonRegistrationResponse: + description: "Registration Response from Cloud Server" + fields: + version: + description: "Protocol version, initial version is 1" + type: long + haltMetricsUploading: + description: "True indicates it should not proceed to metrics uploading" + type: bool + id: + description: "Existing Registration Id" + type: string + informationalURL: + description: "Informational HTTP web page for metrics" + type: string + message: + description: "Informational message for shell to display to user" + type: string + reportingInterval: + description: "Metrics Reporting interval in seconds" + type: long + userReminder: + description: "Informational message to display to user to remind them about the service" + type: string + optional: true + + + FreeMonMetricsRequest: + description: "Metrics Request to Cloud Server" + fields: + version: + description: "Protocol version, initial version is 1" + type: long + id: + description: "Registration Id" + type: string + encoding: + description: "Compression Encoding" + type: MetricsEncoding + metrics: + description: "Metrics Blob" + type: bindata_generic + + + FreeMonMetricsResponse: + description: "Metrics Response from Cloud Server" + fields: + version: + description: "Protocol version, initial version is 1" + type: long + haltMetricsUploading: + description: "True indicates it should not proceed to metrics uploading" + type: bool + permanentlyDelete: + description: "True indicates it permanently delete the local state" + type: bool + reportingInterval: + description: "Metrics Reporting interval in seconds" + type: long + id: + description: "Existing Registration Id" + type: string + optional: true + message: + description: "Informational message for shell to display to user" + type: string + optional: true + informationalURL: + description: "Informational HTTP web page for metrics" + type: string + optional: true + userReminder: + description: "Message to display to user to remind them about service" + type: string + optional: true diff --git a/src/mongo/db/free_mon/free_mon_stub.cpp b/src/mongo/db/free_mon/free_mon_stub.cpp new file mode 100644 index 00000000000..bc44e1dd9e4 --- /dev/null +++ b/src/mongo/db/free_mon/free_mon_stub.cpp @@ -0,0 +1,39 @@ +/** + * Copyright (C) 2018 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/db/free_mon/free_mon_mongod.h" + +#include "mongo/db/service_context.h" + +namespace mongo { + +void startFreeMonitoring(ServiceContext* serviceContext) {} + +void stopFreeMonitoring() {} + +} // namespace mongo diff --git a/src/mongo/db/free_mon/http_client_curl.cpp b/src/mongo/db/free_mon/http_client_curl.cpp new file mode 100644 index 00000000000..ad78618b9bd --- /dev/null +++ b/src/mongo/db/free_mon/http_client_curl.cpp @@ -0,0 +1,230 @@ +/** + * Copyright (C) 2018 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 <cstddef> +#include <curl/curl.h> +#include <curl/easy.h> +#include <string> + +#include "mongo/base/data_builder.h" +#include "mongo/base/data_range.h" +#include "mongo/base/data_range_cursor.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/db/commands/test_commands_enabled.h" +#include "mongo/db/free_mon/free_mon_http.h" +#include "mongo/util/assert_util.h" +#include "mongo/util/log.h" + +namespace mongo { + +namespace { + +class CurlLibraryManager { +public: + ~CurlLibraryManager() { + curl_global_cleanup(); + } + + bool initialize() { + CURLcode ret = curl_global_init(CURL_GLOBAL_ALL); + if (ret != CURLE_OK) { + error() << "Failed to initialize CURL: " << static_cast<int64_t>(ret); + return false; + } + + curl_version_info_data* version_data = curl_version_info(CURLVERSION_NOW); + if (!(version_data->features & CURL_VERSION_SSL)) { + error() << "Curl lacks SSL support, cannot continue"; + return false; + } + + return true; + } +}; + +CurlLibraryManager curlLibraryManager; + +/** + * Receives data from the remote side. + */ +size_t WriteMemoryCallback(void* ptr, size_t size, size_t nmemb, void* data) { + const size_t realsize = size * nmemb; + + auto* mem = reinterpret_cast<DataBuilder*>(data); + if (!mem->writeAndAdvance(ConstDataRange(reinterpret_cast<const char*>(ptr), + reinterpret_cast<const char*>(ptr) + realsize)) + .isOK()) { + // Cause curl to generate a CURLE_WRITE_ERROR by returning a different number than how much + // data there was to write. + return 0; + } + + return realsize; +} + +/** + * Sends data to the remote side + */ +size_t ReadMemoryCallback(char* buffer, size_t size, size_t nitems, void* instream) { + + auto* cdrc = reinterpret_cast<ConstDataRangeCursor*>(instream); + + size_t ret = 0; + + if (cdrc->length() > 0) { + size_t readSize = std::min(size * nitems, cdrc->length()); + memcpy(buffer, cdrc->data(), readSize); + invariant(cdrc->advance(readSize).isOK()); + ret = readSize; + } + + return ret; +} + +class FreeMonCurlHttpClient : public FreeMonHttpClientInterface { +public: + explicit FreeMonCurlHttpClient(std::unique_ptr<executor::ThreadPoolTaskExecutor> executor) + : _executor(std::move(executor)) {} + + ~FreeMonCurlHttpClient() final = default; + + Future<std::vector<uint8_t>> postAsync(StringData url, const BSONObj obj) final { + + Promise<std::vector<uint8_t>> promise; + auto future = promise.getFuture(); + auto shared_promise = promise.share(); + + std::string urlString(url.toString()); + + auto status = _executor->scheduleWork([shared_promise, urlString, obj]( + const executor::TaskExecutor::CallbackArgs& cbArgs) mutable { + doPost(shared_promise, urlString, obj); + }); + + uassertStatusOK(status); + return future; + } + +private: + static void doPost(SharedPromise<std::vector<uint8_t>> shared_promise, + const std::string& urlString, + const BSONObj& obj) { + ConstDataRange data(obj.objdata(), obj.objdata() + obj.objsize()); + + ConstDataRangeCursor cdrc(data); + + std::unique_ptr<CURL, void (*)(CURL*)> myHandle(curl_easy_init(), curl_easy_cleanup); + + if (!myHandle) { + shared_promise.setError({ErrorCodes::InternalError, "Curl initialization failed"}); + return; + } + + curl_easy_setopt(myHandle.get(), CURLOPT_URL, urlString.c_str()); + curl_easy_setopt(myHandle.get(), CURLOPT_POST, 1); + + // Allow http only if test commands are enabled + if (getTestCommandsEnabled()) { + curl_easy_setopt(myHandle.get(), CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP); + } else { + curl_easy_setopt(myHandle.get(), CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); + } + + curl_easy_setopt(myHandle.get(), CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP); + curl_easy_setopt(myHandle.get(), CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + + DataBuilder dataBuilder(4096); + + curl_easy_setopt(myHandle.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(myHandle.get(), CURLOPT_WRITEDATA, &dataBuilder); + + curl_easy_setopt(myHandle.get(), CURLOPT_READFUNCTION, ReadMemoryCallback); + curl_easy_setopt(myHandle.get(), CURLOPT_READDATA, &cdrc); + curl_easy_setopt(myHandle.get(), CURLOPT_POSTFIELDSIZE, (long)cdrc.length()); + + // CURLOPT_EXPECT_100_TIMEOUT_MS?? + curl_easy_setopt(myHandle.get(), CURLOPT_CONNECTTIMEOUT, kConnectionTimeoutSeconds); + curl_easy_setopt(myHandle.get(), CURLOPT_TIMEOUT, kTotalRequestTimeoutSeconds); + +#if LIBCURL_VERSION_NUM > 0x072200 + // Requires >= 7.34.0 + curl_easy_setopt(myHandle.get(), CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); +#endif + curl_easy_setopt(myHandle.get(), CURLOPT_FOLLOWLOCATION, 0); + + curl_easy_setopt(myHandle.get(), CURLOPT_NOSIGNAL, 1); + // TODO: consider making this configurable If server log level > 3 + // curl_easy_setopt(myHandle.get(), CURLOPT_VERBOSE, 1); + // curl_easy_setopt(myHandle.get(), CURLOPT_DEBUGFUNCTION , ???); + + curl_slist* chunk = nullptr; + chunk = curl_slist_append(chunk, "Content-Type: application/octet-stream"); + chunk = curl_slist_append(chunk, "Accept: application/octet-stream"); + + // Send the empty expect because we do not need the server to respond with 100-Contine + chunk = curl_slist_append(chunk, "Expect:"); + + std::unique_ptr<curl_slist, void (*)(curl_slist*)> chunkHolder(chunk, curl_slist_free_all); + + curl_easy_setopt(myHandle.get(), CURLOPT_HTTPHEADER, chunk); + + CURLcode result = curl_easy_perform(myHandle.get()); + if (result != CURLE_OK) { + shared_promise.setError({ErrorCodes::OperationFailed, + str::stream() << "Bad HTTP response from API server: " + << curl_easy_strerror(result)}); + return; + } + + auto d = dataBuilder.getCursor(); + shared_promise.emplaceValue(std::vector<uint8_t>(d.data(), d.data() + d.length())); + } + +private: + std::unique_ptr<executor::ThreadPoolTaskExecutor> _executor{}; +}; + +} // namespace + +std::unique_ptr<FreeMonHttpClientInterface> createFreeMonHttpClient( + std::unique_ptr<executor::ThreadPoolTaskExecutor> executor) { + + if (!curlLibraryManager.initialize()) { + return nullptr; + } + + return std::make_unique<FreeMonCurlHttpClient>(std::move(executor)); +} + +} // namespace mongo |