managed_lock/ReleaseRequest.cc
managed_lock/Utils.cc
migration/FileStream.cc
+ migration/HttpClient.cc
migration/ImageDispatch.cc
migration/NativeFormat.cc
migration/OpenSourceImageRequest.cc
add_dependencies(rbd_internal eventtrace_tp)
endif()
target_link_libraries(rbd_internal PRIVATE
- osdc rbd_types)
+ osdc rbd_types
+ OpenSSL::SSL)
target_include_directories(rbd_internal PRIVATE ${OPENSSL_INCLUDE_DIR})
if(WITH_RBD_RWL OR WITH_RBD_SSD_CACHE)
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "librbd/migration/HttpClient.h"
+#include "common/dout.h"
+#include "common/errno.h"
+#include "librbd/AsioEngine.h"
+#include "librbd/ImageCtx.h"
+#include "librbd/asio/Utils.h"
+#include "librbd/migration/Utils.h"
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/post.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/read.hpp>
+#include <boost/asio/ssl.hpp>
+#include <boost/beast/core.hpp>
+#include <boost/beast/ssl/ssl_stream.hpp>
+
+namespace librbd {
+namespace migration {
+
+template <typename I>
+struct HttpClient<I>::HttpSessionInterface {
+ virtual ~HttpSessionInterface() {}
+
+ virtual void init(Context* on_finish) = 0;
+ virtual void shut_down(Context* on_finish) = 0;
+};
+
+#define dout_subsys ceph_subsys_rbd
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::migration::HttpClient::" \
+ << "HttpSession " << this << " " << __func__ \
+ << ": "
+
+template <typename I>
+template <typename D>
+class HttpClient<I>::HttpSession : public HttpSessionInterface {
+public:
+ void init(Context* on_finish) override {
+ resolve_host(on_finish);
+ }
+
+ void shut_down(Context* on_finish) override {
+ disconnect(new LambdaContext([this, on_finish](int r) {
+ handle_shut_down(r, on_finish); }));
+ }
+
+protected:
+ HttpClient* m_http_client;
+
+ HttpSession(HttpClient* http_client)
+ : m_http_client(http_client), m_resolver(http_client->m_strand) {
+ }
+
+ virtual void connect(boost::asio::ip::tcp::resolver::results_type results,
+ Context* on_finish) = 0;
+ virtual void disconnect(Context* on_finish) = 0;
+
+ void close() {
+ boost::system::error_code ec;
+ boost::beast::get_lowest_layer(derived().stream()).socket().close(ec);
+ }
+
+private:
+ boost::asio::ip::tcp::resolver m_resolver;
+
+ D& derived() {
+ return static_cast<D&>(*this);
+ }
+
+ void resolve_host(Context* on_finish) {
+ auto cct = m_http_client->m_cct;
+ ldout(cct, 15) << dendl;
+
+ m_resolver.async_resolve(
+ m_http_client->m_url_spec.host, m_http_client->m_url_spec.port,
+ asio::util::get_callback_adapter(
+ [this, on_finish](int r, auto results) {
+ handle_resolve_host(r, results, on_finish); }));
+ }
+
+ void handle_resolve_host(
+ int r, boost::asio::ip::tcp::resolver::results_type results,
+ Context* on_finish) {
+ auto cct = m_http_client->m_cct;
+ ldout(cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ if (r == -boost::asio::error::host_not_found) {
+ r = -ENOENT;
+ }
+
+ lderr(cct) << "failed to resolve host '"
+ << m_http_client->m_url_spec.host << "': "
+ << cpp_strerror(r) << dendl;
+ on_finish->complete(r);
+ return;
+ }
+
+ connect(results, new LambdaContext([this, on_finish](int r) {
+ handle_connect(r, on_finish); }));
+ }
+
+ void handle_connect(int r, Context* on_finish) {
+ auto cct = m_http_client->m_cct;
+ ldout(cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(cct) << "failed to connect to host '"
+ << m_http_client->m_url_spec.host << "': "
+ << cpp_strerror(r) << dendl;
+ on_finish->complete(r);
+ return;
+ }
+
+ on_finish->complete(0);
+ }
+
+ void handle_shut_down(int r, Context* on_finish) {
+ auto cct = m_http_client->m_cct;
+ ldout(cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(cct) << "failed to close stream: '" << cpp_strerror(r) << dendl;
+ on_finish->complete(r);
+ return;
+ }
+
+ on_finish->complete(0);
+ }
+};
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::migration::HttpClient::" \
+ << "PlainHttpSession " << this << " " << __func__ \
+ << ": "
+
+template <typename I>
+class HttpClient<I>::PlainHttpSession : public HttpSession<PlainHttpSession> {
+public:
+ PlainHttpSession(HttpClient* http_client)
+ : HttpSession<PlainHttpSession>(http_client),
+ m_stream(http_client->m_strand) {
+ }
+ ~PlainHttpSession() override {
+ this->close();
+ }
+
+
+ inline boost::beast::tcp_stream&
+ stream() {
+ return m_stream;
+ }
+
+protected:
+ void connect(boost::asio::ip::tcp::resolver::results_type results,
+ Context* on_finish) override {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << dendl;
+
+ m_stream.async_connect(
+ results,
+ asio::util::get_callback_adapter(
+ [on_finish](int r, auto endpoint) { on_finish->complete(r); }));
+ }
+
+ void disconnect(Context* on_finish) override {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << dendl;
+
+ boost::system::error_code ec;
+ m_stream.socket().shutdown(
+ boost::asio::ip::tcp::socket::shutdown_both, ec);
+
+ on_finish->complete(-ec.value());
+ }
+
+private:
+ boost::beast::tcp_stream m_stream;
+
+};
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::migration::HttpClient::" \
+ << "SslHttpSession " << this << " " << __func__ \
+ << ": "
+
+template <typename I>
+class HttpClient<I>::SslHttpSession : public HttpSession<SslHttpSession> {
+public:
+ SslHttpSession(HttpClient* http_client)
+ : HttpSession<SslHttpSession>(http_client),
+ m_stream(http_client->m_strand, http_client->m_ssl_context) {
+ }
+ ~SslHttpSession() override {
+ this->close();
+ }
+
+ inline boost::beast::ssl_stream<boost::beast::tcp_stream>&
+ stream() {
+ return m_stream;
+ }
+
+protected:
+ void connect(boost::asio::ip::tcp::resolver::results_type results,
+ Context* on_finish) override {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << dendl;
+
+ boost::beast::get_lowest_layer(m_stream).async_connect(
+ results,
+ asio::util::get_callback_adapter(
+ [this, on_finish](int r, auto endpoint) {
+ handle_connect(r, on_finish); }));
+ }
+
+ void disconnect(Context* on_finish) override {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << dendl;
+
+ if (m_ssl_enabled) {
+ m_stream.async_shutdown(
+ asio::util::get_callback_adapter([this, on_finish](int r) {
+ shutdown(r, on_finish); }));
+ } else {
+ shutdown(0, on_finish);
+ }
+ }
+
+private:
+ boost::beast::ssl_stream<boost::beast::tcp_stream> m_stream;
+ bool m_ssl_enabled = false;
+
+ void handle_connect(int r, Context* on_finish) {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << dendl;
+
+ if (r < 0) {
+ lderr(cct) << "failed to connect to host '"
+ << http_client->m_url_spec.host << "': "
+ << cpp_strerror(r) << dendl;
+ on_finish->complete(r);
+ return;
+ }
+
+ handshake(on_finish);
+ }
+
+ void handshake(Context* on_finish) {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << dendl;
+
+ auto& host = http_client->m_url_spec.host;
+ m_stream.set_verify_mode(
+ boost::asio::ssl::verify_peer |
+ boost::asio::ssl::verify_fail_if_no_peer_cert);
+ m_stream.set_verify_callback(
+ [host, next=boost::asio::ssl::host_name_verification(host),
+ ignore_self_signed=http_client->m_ignore_self_signed_cert]
+ (bool preverified, boost::asio::ssl::verify_context& ctx) {
+ if (!preverified && ignore_self_signed) {
+ auto ec = X509_STORE_CTX_get_error(ctx.native_handle());
+ switch (ec) {
+ case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+ case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+ // ignore self-signed cert issues
+ preverified = true;
+ break;
+ default:
+ break;
+ }
+ }
+ return next(preverified, ctx);
+ });
+
+ // Set SNI Hostname (many hosts need this to handshake successfully)
+ if(!SSL_set_tlsext_host_name(m_stream.native_handle(),
+ http_client->m_url_spec.host.c_str())) {
+ int r = -::ERR_get_error();
+ lderr(cct) << "failed to initialize SNI hostname: " << cpp_strerror(r)
+ << dendl;
+ on_finish->complete(r);
+ return;
+ }
+
+ // Perform the SSL/TLS handshake
+ m_stream.async_handshake(
+ boost::asio::ssl::stream_base::client,
+ asio::util::get_callback_adapter(
+ [this, on_finish](int r) { handle_handshake(r, on_finish); }));
+ }
+
+ void handle_handshake(int r, Context* on_finish) {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << "r=" << r << dendl;
+
+ if (r < 0) {
+ lderr(cct) << "failed to complete handshake: " << cpp_strerror(r)
+ << dendl;
+ disconnect(new LambdaContext([r, on_finish](int) {
+ on_finish->complete(r); }));
+ return;
+ }
+
+ m_ssl_enabled = true;
+ on_finish->complete(0);
+ }
+
+ void shutdown(int r, Context* on_finish) {
+ auto http_client = this->m_http_client;
+ auto cct = http_client->m_cct;
+ ldout(cct, 15) << "r=" << r << dendl;
+
+ boost::system::error_code ec;
+ boost::beast::get_lowest_layer(m_stream).socket().shutdown(
+ boost::asio::ip::tcp::socket::shutdown_both, ec);
+
+ on_finish->complete(-ec.value());
+ }
+};
+
+#undef dout_prefix
+#define dout_prefix *_dout << "librbd::migration::HttpClient: " << this \
+ << " " << __func__ << ": "
+
+template <typename I>
+HttpClient<I>::HttpClient(I* image_ctx, const std::string& url)
+ : m_cct(image_ctx->cct), m_asio_engine(image_ctx->asio_engine), m_url(url),
+ m_strand(*m_asio_engine),
+ m_ssl_context(boost::asio::ssl::context::sslv23_client) {
+ m_ssl_context.set_default_verify_paths();
+}
+
+template <typename I>
+void HttpClient<I>::open(Context* on_finish) {
+ ldout(m_cct, 10) << "url=" << m_url << dendl;
+
+ int r = util::parse_url(m_cct, m_url, &m_url_spec);
+ if (r < 0) {
+ lderr(m_cct) << "failed to parse url '" << m_url << "': " << cpp_strerror(r)
+ << dendl;
+ on_finish->complete(-EINVAL);
+ return;
+ }
+
+ // initial bootstrap connection -- later IOs might rebuild the session
+ create_http_session(on_finish);
+}
+
+template <typename I>
+void HttpClient<I>::close(Context* on_finish) {
+ // execute within the strand to ensure all IO completes
+ boost::asio::post(
+ m_strand, [this, on_finish]() {
+ if (m_http_session == nullptr) {
+ on_finish->complete(0);
+ return;
+ }
+
+ m_http_session->shut_down(on_finish);
+ });
+}
+
+template <typename I>
+void HttpClient<I>::create_http_session(Context* on_finish) {
+ ldout(m_cct, 15) << dendl;
+
+ ceph_assert(m_http_session == nullptr);
+ switch (m_url_spec.scheme) {
+ case URL_SCHEME_HTTP:
+ m_http_session = std::make_unique<PlainHttpSession>(this);
+ break;
+ case URL_SCHEME_HTTPS:
+ m_http_session = std::make_unique<SslHttpSession>(this);
+ break;
+ default:
+ ceph_assert(false);
+ break;
+ }
+
+ m_http_session->init(on_finish);
+}
+
+} // namespace migration
+} // namespace librbd
+
+template class librbd::migration::HttpClient<librbd::ImageCtx>;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_LIBRBD_MIGRATION_HTTP_CLIENT_H
+#define CEPH_LIBRBD_MIGRATION_HTTP_CLIENT_H
+
+#include "include/common_fwd.h"
+#include "include/int_types.h"
+#include "librbd/migration/Types.h"
+#include <boost/asio/io_context_strand.hpp>
+#include <boost/asio/ip/tcp.hpp>
+#include <boost/asio/ssl/context.hpp>
+#include <memory>
+#include <string>
+#include <utility>
+
+struct Context;
+
+namespace librbd {
+
+struct AsioEngine;
+struct ImageCtx;
+
+namespace migration {
+
+template <typename ImageCtxT>
+class HttpClient {
+public:
+ static HttpClient* create(ImageCtxT* image_ctx, const std::string& url) {
+ return new HttpClient(image_ctx, url);
+ }
+
+ HttpClient(ImageCtxT* image_ctx, const std::string& url);
+ HttpClient(const HttpClient&) = delete;
+ HttpClient& operator=(const HttpClient&) = delete;
+
+ void open(Context* on_finish);
+ void close(Context* on_finish);
+
+ void set_ignore_self_signed_cert(bool ignore) {
+ m_ignore_self_signed_cert = ignore;
+ }
+
+private:
+ struct HttpSessionInterface;
+ template <typename D> struct HttpSession;
+ struct PlainHttpSession;
+ struct SslHttpSession;
+
+ CephContext* m_cct;
+ std::shared_ptr<AsioEngine> m_asio_engine;
+ std::string m_url;
+
+ UrlSpec m_url_spec;
+
+ bool m_ignore_self_signed_cert = false;
+
+ boost::asio::io_context::strand m_strand;
+
+ boost::asio::ssl::context m_ssl_context;
+ std::unique_ptr<HttpSessionInterface> m_http_session;
+
+ void create_http_session(Context* on_finish);
+};
+
+} // namespace migration
+} // namespace librbd
+
+extern template class librbd::migration::HttpClient<librbd::ImageCtx>;
+
+#endif // CEPH_LIBRBD_MIGRATION_HTTP_CLIENT_H
managed_lock/test_mock_ReacquireRequest.cc
managed_lock/test_mock_ReleaseRequest.cc
migration/test_mock_FileStream.cc
+ migration/test_mock_HttpClient.cc
migration/test_mock_RawFormat.cc
migration/test_mock_Utils.cc
mirror/snapshot/test_mock_CreateNonPrimaryRequest.cc
osdc
ceph-common
global
+ OpenSSL::SSL
${UNITTEST_LIBS})
add_executable(ceph_test_librbd
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "test/librbd/test_mock_fixture.h"
+#include "test/librbd/test_support.h"
+#include "include/rbd_types.h"
+#include "common/ceph_mutex.h"
+#include "librbd/migration/HttpClient.h"
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include <boost/asio/ip/tcp.hpp>
+
+namespace librbd {
+namespace {
+
+struct MockTestImageCtx : public MockImageCtx {
+ MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
+ }
+};
+
+} // anonymous namespace
+} // namespace librbd
+
+#include "librbd/migration/HttpClient.cc"
+
+namespace librbd {
+namespace migration {
+
+using ::testing::Invoke;
+
+class TestMockMigrationHttpClient : public TestMockFixture {
+public:
+ typedef HttpClient<MockTestImageCtx> MockHttpClient;
+
+ void SetUp() override {
+ TestMockFixture::SetUp();
+
+ ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
+
+ m_acceptor.emplace(*m_image_ctx->asio_engine,
+ boost::asio::ip::tcp::endpoint(
+ boost::asio::ip::tcp::v4(), 0));
+ m_server_port = m_acceptor->local_endpoint().port();
+ }
+
+ void TearDown() override {
+ m_acceptor.reset();
+
+ TestMockFixture::TearDown();
+ }
+
+ std::string get_local_url(UrlScheme url_scheme) {
+ std::stringstream sstream;
+ switch (url_scheme) {
+ case URL_SCHEME_HTTP:
+ sstream << "http://127.0.0.1";
+ break;
+ case URL_SCHEME_HTTPS:
+ sstream << "https://localhost";
+ break;
+ default:
+ ceph_assert(false);
+ break;
+ }
+
+ sstream << ":" << m_server_port << "/";
+ return sstream.str();
+ }
+
+ void client_accept(boost::asio::ip::tcp::socket* socket, bool close,
+ Context* on_connect) {
+ m_acceptor->async_accept(
+ boost::asio::make_strand(m_image_ctx->asio_engine->get_executor()),
+ [socket, close, on_connect]
+ (auto ec, boost::asio::ip::tcp::socket in_socket) {
+ if (close) {
+ in_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
+ } else {
+ ASSERT_EQ(0, ec.value());
+ *socket = std::move(in_socket);
+ }
+ on_connect->complete(0);
+ });
+ }
+
+ template <typename Stream>
+ void client_ssl_handshake(Stream& stream, Context* on_handshake) {
+ stream.async_handshake(
+ boost::asio::ssl::stream_base::server,
+ [on_handshake](auto ec) {
+ on_handshake->complete(-ec.value());
+ });
+ }
+
+ template <typename Stream>
+ void client_ssl_shutdown(Stream& stream, Context* on_shutdown) {
+ stream.async_shutdown(
+ [on_shutdown](auto ec) {
+ on_shutdown->complete(-ec.value());
+ });
+ }
+
+ void load_server_certificate(boost::asio::ssl::context& ctx) {
+ ctx.set_options(
+ boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::single_dh_use);
+ ctx.use_certificate_chain(
+ boost::asio::buffer(CERT.data(), CERT.size()));
+ ctx.use_private_key(
+ boost::asio::buffer(KEY.data(), KEY.size()),
+ boost::asio::ssl::context::file_format::pem);
+ ctx.use_tmp_dh(
+ boost::asio::buffer(DH.data(), DH.size()));
+ }
+
+ // dummy self-signed cert for localhost
+ const std::string CERT =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDXzCCAkegAwIBAgIUYH6rAaq66LC6yJ3XK1WEMIfmY4cwDQYJKoZIhvcNAQEL\n"
+ "BQAwPzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMQ8wDQYDVQQHDAZNY0xlYW4x\n"
+ "EjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMDExMDIyMTM5NTVaFw00ODAzMjAyMTM5\n"
+ "NTVaMD8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTEPMA0GA1UEBwwGTWNMZWFu\n"
+ "MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
+ "AoIBAQCeRkyxjP0eNHxzj4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5\n"
+ "lgTLDsl8gfk2HRz4cnAiseqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhA\n"
+ "hDjM6H99ysLf0NS6t14eK+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3og\n"
+ "nHhv2hZYTdzEkQEyZHz4V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj\n"
+ "8hJdfRiRBvnA4NnkrMrxW9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtn\n"
+ "tRD1AF9sT+YjoCaHv0hXZvBEUEF3AgMBAAGjUzBRMB0GA1UdDgQWBBTQoIiX3+p/\n"
+ "P4Xz2vwERz6pbjPGhzAfBgNVHSMEGDAWgBTQoIiX3+p/P4Xz2vwERz6pbjPGhzAP\n"
+ "BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCVKoYAw+D1qqWRDSh3\n"
+ "2KlKMnT6sySo7XmReGArj8FTKiZUprByj5CfAtaiDSdPOpcg3EazWbdasZbMmSQm\n"
+ "+jpe5WoKnxL9b12lwwUYHrLl6RlrDHVkIVlXLNbJFY5TpfjvZfHpwVAygF3fnbgW\n"
+ "PPuODUNAS5NDwST+t29jBZ/wwU0pyW0CS4K5d3XMGHBc13j2V/FyvmsZ5xfA4U9H\n"
+ "oEnmZ/Qm+FFK/nR40rTAZ37cuv4ysKFtwvatNgTfHGJwaBUkKFdDbcyxt9abCi6x\n"
+ "/K+ScoJtdIeVcfx8Fnc5PNtSpy8bHI3Zy4IEyw4kOqwwI1h37iBafZ2WdQkTxlAx\n"
+ "JIDj\n"
+ "-----END CERTIFICATE-----\n";
+ const std::string KEY =
+ "-----BEGIN PRIVATE KEY-----\n"
+ "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCeRkyxjP0eNHxz\n"
+ "j4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5lgTLDsl8gfk2HRz4cnAi\n"
+ "seqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhAhDjM6H99ysLf0NS6t14e\n"
+ "K+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3ognHhv2hZYTdzEkQEyZHz4\n"
+ "V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj8hJdfRiRBvnA4NnkrMrx\n"
+ "W9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtntRD1AF9sT+YjoCaHv0hX\n"
+ "ZvBEUEF3AgMBAAECggEACCaYpoAbPOX5Dr5y6p47KXboIvrgNFQRPVke62rtOF6M\n"
+ "dQQ3YwKJpCzPxp8qKgbd63KKEfZX2peSHMdKzIGPcSRSRcQ7tlvUN9on1M/rgGIg\n"
+ "3swhI5H0qhdnOLNWdX73qdO6S2pmuiLdTvJ11N4IoLfNj/GnPAr1Ivs1ScL6bkQv\n"
+ "UybaNQ/g2lB0tO7vUeVe2W/AqsIb1eQlf2g+SH7xRj2bGQkr4cWTylqfiVoL/Xic\n"
+ "QVTCks3BWaZhYIhTFgvqVhXZpp52O9J+bxsWJItKQrrCBemxwp82xKbiW/KoI9L1\n"
+ "wSnKvxx7Q3RUN5EvXeOpTRR8QIpBoxP3TTeoj+EOMQKBgQDQb/VfLDlLgfYJpgRC\n"
+ "hKCLW90un9op3nA2n9Dmm9TTLYOmUyiv5ub8QDINEw/YK/NE2JsTSUk2msizqTLL\n"
+ "Z82BFbz9kPlDbJ5MgxG5zXeLvOLurAFmZk/z5JJO+65PKjf0QVLncSAJvMCeNFuC\n"
+ "2yZrEzbrItrjQsN6AedWdx6TTwKBgQDCZAsSI3lQgOh2q1GSxjuIzRAc7JnSGBvD\n"
+ "nG8+SkfKAy7BWe638772Dgx8KYO7TLI4zlm8c9Tr/nkZsGWmM5S2DMI69PWOQWNa\n"
+ "R6QzOFFwNg2JETH7ow+x8+9Q9d3WsPzROz3r5uDXgEk0glthaymVoPILFOiYpz3r\n"
+ "heUbd6mFWQKBgQCCJBVJGhyf54IOHij0u0heGrpr/QTDNY5MnNZa1hs4y2cydyOl\n"
+ "SH8aKp7ViPxQlYhriO6ySQS8YkJD4rXDSImIOmFo1Ja9oVjpHsD3iLFGf2YVbTHm\n"
+ "lKUA+8raI8x+wzZyfELeHMTLL534aWpltp0zJ6kXgQi38pyIVh3x36gogwKBgQCt\n"
+ "nba5k49VVFzLKEXqBjzD+QqMGtFjcH7TnZNJmgQ2K9OFgzIPf5atomyKNHXgQibn\n"
+ "T32cMAQaZqR4SjDvWSBX3FtZVtE+Ja57woKn8IPj6ZL7Oa1fpwpskIbM01s31cln\n"
+ "gjbSy9lC/+PiDw9YmeKBLkcfmKQJO021Xlf6yUxRuQKBgBWPODUO8oKjkuJXNI/w\n"
+ "El9hNWkd+/SZDfnt93dlVyXTtTF0M5M95tlOgqvLtWKSyB/BOnoZYWqR8luMl15d\n"
+ "bf75j5mB0lHMWtyQgvZSkFqe9Or7Zy7hfTShDlZ/w+OXK7PGesaE1F14irShXSji\n"
+ "yn5DZYAZ5pU52xreJeYvDngO\n"
+ "-----END PRIVATE KEY-----\n";
+ const std::string DH =
+ "-----BEGIN DH PARAMETERS-----\n"
+ "MIIBCAKCAQEA4+DA1j0gDWS71okwHpnvA65NmmR4mf+B3H39g163zY5S+cnWS2LI\n"
+ "dvqnUDpw13naWtQ+Nu7I4rk1XoPaxOPSTu1MTbtYOxxU9M1ceBu4kQjDeHwasPVM\n"
+ "zyEs1XXX3tsbPUxAuayX+AgW6QQAQUEjKDnv3FzVnQTFjwI49LqjnrSjbgQcoMaH\n"
+ "EdGGUc6t1/We2vtsJZx0/dbaMkzFYO8dAbEYHL4sPKQb2mLpCPJZC3vwzpFkHFCd\n"
+ "QSnLW2qRhy+66Mf8shdr6uvpoMcnKMOAvjKdXl9PBeJM9eJPz2lC4tnTiM3DqNzK\n"
+ "Hn8+Pu3KkSIFL/5uBVu1fZSq+lFIEI23wwIBAg==\n"
+ "-----END DH PARAMETERS-----\n";
+
+ librbd::ImageCtx *m_image_ctx;
+
+ std::optional<boost::asio::ip::tcp::acceptor> m_acceptor;
+ uint64_t m_server_port = 0;
+};
+
+TEST_F(TestMockMigrationHttpClient, OpenCloseHttp) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ http_client.close(&ctx2);
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenCloseHttps) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTPS));
+ http_client.set_ignore_self_signed_cert(true);
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+
+ boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tlsv12};
+ load_server_certificate(ssl_context);
+ boost::beast::ssl_stream<boost::beast::tcp_stream> ssl_stream{
+ std::move(socket), ssl_context};
+
+ C_SaferCond on_ssl_handshake_ctx;
+ client_ssl_handshake(ssl_stream, &on_ssl_handshake_ctx);
+ ASSERT_EQ(0, on_ssl_handshake_ctx.wait());
+
+ ASSERT_EQ(0, ctx1.wait());
+
+ C_SaferCond ctx2;
+ http_client.close(&ctx2);
+
+ C_SaferCond on_ssl_shutdown_ctx;
+ client_ssl_shutdown(ssl_stream, &on_ssl_shutdown_ctx);
+ ASSERT_EQ(0, on_ssl_shutdown_ctx.wait());
+
+ ASSERT_EQ(0, ctx2.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenHttpsHandshakeFail) {
+ boost::asio::ip::tcp::socket socket(*m_image_ctx->asio_engine);
+ C_SaferCond on_connect_ctx;
+ client_accept(&socket, false, &on_connect_ctx);
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTPS));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(0, on_connect_ctx.wait());
+
+ boost::asio::ssl::context ssl_context{boost::asio::ssl::context::tlsv12};
+ load_server_certificate(ssl_context);
+ boost::beast::ssl_stream<boost::beast::tcp_stream> ssl_stream{
+ std::move(socket), ssl_context};
+
+ C_SaferCond on_ssl_handshake_ctx;
+ client_ssl_handshake(ssl_stream, &on_ssl_handshake_ctx);
+ ASSERT_NE(0, on_ssl_handshake_ctx.wait());
+ ASSERT_NE(0, ctx1.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenInvalidUrl) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx, "ftp://nope/");
+
+ C_SaferCond ctx;
+ http_client.open(&ctx);
+ ASSERT_EQ(-EINVAL, ctx.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenResolveFail) {
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx, "http://invalid.ceph.com");
+
+ C_SaferCond ctx;
+ http_client.open(&ctx);
+ ASSERT_EQ(-ENOENT, ctx.wait());
+}
+
+TEST_F(TestMockMigrationHttpClient, OpenConnectFail) {
+ m_acceptor.reset();
+
+ MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
+ MockHttpClient http_client(&mock_test_image_ctx,
+ get_local_url(URL_SCHEME_HTTP));
+
+ C_SaferCond ctx1;
+ http_client.open(&ctx1);
+ ASSERT_EQ(-ECONNREFUSED, ctx1.wait());
+}
+
+} // namespace migration
+} // namespace librbd
cls_journal_client
global
heap_profiler
- ${ALLOC_LIBS})
+ ${ALLOC_LIBS}
+ OpenSSL::SSL)
install(TARGETS rbd-mirror DESTINATION bin)