1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "test/librbd/test_mock_fixture.h"
5 #include "test/librbd/test_support.h"
6 #include "include/rbd_types.h"
7 #include "common/ceph_mutex.h"
8 #include "librbd/migration/HttpClient.h"
9 #include "gtest/gtest.h"
10 #include "gmock/gmock.h"
12 #include <boost/asio/ip/tcp.hpp>
13 #include <boost/beast/core.hpp>
14 #include <boost/beast/http.hpp>
19 struct MockTestImageCtx
: public MockImageCtx
{
20 MockTestImageCtx(ImageCtx
&image_ctx
) : MockImageCtx(image_ctx
) {
24 } // anonymous namespace
28 inline ImageCtx
*get_image_ctx(MockTestImageCtx
*image_ctx
) {
29 return image_ctx
->image_ctx
;
35 #include "librbd/migration/HttpClient.cc"
37 using EmptyHttpRequest
= boost::beast::http::request
<
38 boost::beast::http::empty_body
>;
39 using HttpResponse
= boost::beast::http::response
<
40 boost::beast::http::string_body
>;
46 template <typename Body
>
47 bool operator==(const boost::beast::http::request
<Body
>& lhs
,
48 const boost::beast::http::request
<Body
>& rhs
) {
49 return (lhs
.method() == rhs
.method() &&
50 lhs
.target() == rhs
.target());
53 template <typename Body
>
54 bool operator==(const boost::beast::http::response
<Body
>& lhs
,
55 const boost::beast::http::response
<Body
>& rhs
) {
56 return (lhs
.result() == rhs
.result() &&
57 lhs
.body() == rhs
.body());
67 using ::testing::Invoke
;
69 class TestMockMigrationHttpClient
: public TestMockFixture
{
71 typedef HttpClient
<MockTestImageCtx
> MockHttpClient
;
73 void SetUp() override
{
74 TestMockFixture::SetUp();
76 ASSERT_EQ(0, open_image(m_image_name
, &m_image_ctx
));
78 create_acceptor(false);
81 void TearDown() override
{
84 TestMockFixture::TearDown();
87 // if we have a racing where another thread manages to bind and listen the
88 // port picked by this acceptor, try again.
89 static constexpr int MAX_BIND_RETRIES
= 60;
91 void create_acceptor(bool reuse
) {
92 for (int retries
= 0;; retries
++) {
94 m_acceptor
.emplace(*m_image_ctx
->asio_engine
,
95 boost::asio::ip::tcp::endpoint(
96 boost::asio::ip::tcp::v4(), m_server_port
), reuse
);
99 } catch (const boost::system::system_error
& e
) {
100 if (retries
== MAX_BIND_RETRIES
) {
103 if (e
.code() != boost::system::errc::address_in_use
) {
107 // backoff a little bit
110 m_server_port
= m_acceptor
->local_endpoint().port();
113 std::string
get_local_url(UrlScheme url_scheme
) {
114 std::stringstream sstream
;
115 switch (url_scheme
) {
116 case URL_SCHEME_HTTP
:
117 sstream
<< "http://127.0.0.1";
119 case URL_SCHEME_HTTPS
:
120 sstream
<< "https://localhost";
127 sstream
<< ":" << m_server_port
<< "/target";
128 return sstream
.str();
131 void client_accept(boost::asio::ip::tcp::socket
* socket
, bool close
,
132 Context
* on_connect
) {
133 m_acceptor
->async_accept(
134 boost::asio::make_strand(m_image_ctx
->asio_engine
->get_executor()),
135 [socket
, close
, on_connect
]
136 (auto ec
, boost::asio::ip::tcp::socket in_socket
) {
138 in_socket
.shutdown(boost::asio::ip::tcp::socket::shutdown_both
);
140 ASSERT_FALSE(ec
) << "Unexpected error: " << ec
;
141 *socket
= std::move(in_socket
);
143 on_connect
->complete(0);
147 template <typename Body
>
148 void client_read_request(boost::asio::ip::tcp::socket
& socket
,
149 boost::beast::http::request
<Body
>& expected_req
) {
150 boost::beast::http::request
<Body
> req
;
151 boost::beast::error_code ec
;
152 boost::beast::http::read(socket
, m_buffer
, req
, ec
);
153 ASSERT_FALSE(ec
) << "Unexpected errror: " << ec
;
155 expected_req
.target("/target");
156 ASSERT_EQ(expected_req
, req
);
159 void client_write_response(boost::asio::ip::tcp::socket
& socket
,
160 HttpResponse
& expected_res
) {
161 expected_res
.set(boost::beast::http::field::server
,
162 BOOST_BEAST_VERSION_STRING
);
163 expected_res
.set(boost::beast::http::field::content_type
, "text/plain");
164 expected_res
.content_length(expected_res
.body().size());
165 expected_res
.prepare_payload();
167 boost::beast::error_code ec
;
168 boost::beast::http::write(socket
, expected_res
, ec
);
169 ASSERT_FALSE(ec
) << "Unexpected errror: " << ec
;
172 template <typename Stream
>
173 void client_ssl_handshake(Stream
& stream
, bool ignore_failure
,
174 Context
* on_handshake
) {
175 stream
.async_handshake(
176 boost::asio::ssl::stream_base::server
,
177 [ignore_failure
, on_handshake
](auto ec
) {
178 ASSERT_FALSE(!ignore_failure
&& ec
) << "Unexpected error: " << ec
;
179 on_handshake
->complete(-ec
.value());
183 template <typename Stream
>
184 void client_ssl_shutdown(Stream
& stream
, Context
* on_shutdown
) {
185 stream
.async_shutdown(
186 [on_shutdown
](auto ec
) {
187 ASSERT_FALSE(ec
) << "Unexpected error: " << ec
;
188 on_shutdown
->complete(-ec
.value());
192 void load_server_certificate(boost::asio::ssl::context
& ctx
) {
194 boost::asio::ssl::context::default_workarounds
|
195 boost::asio::ssl::context::no_sslv2
|
196 boost::asio::ssl::context::single_dh_use
);
197 ctx
.use_certificate_chain(
198 boost::asio::buffer(CERTIFICATE
.data(), CERTIFICATE
.size()));
200 boost::asio::buffer(KEY
.data(), KEY
.size()),
201 boost::asio::ssl::context::file_format::pem
);
203 boost::asio::buffer(DH
.data(), DH
.size()));
206 // dummy self-signed cert for localhost
207 const std::string CERTIFICATE
=
208 "-----BEGIN CERTIFICATE-----\n"
209 "MIIDXzCCAkegAwIBAgIUYH6rAaq66LC6yJ3XK1WEMIfmY4cwDQYJKoZIhvcNAQEL\n"
210 "BQAwPzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMQ8wDQYDVQQHDAZNY0xlYW4x\n"
211 "EjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMDExMDIyMTM5NTVaFw00ODAzMjAyMTM5\n"
212 "NTVaMD8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTEPMA0GA1UEBwwGTWNMZWFu\n"
213 "MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
214 "AoIBAQCeRkyxjP0eNHxzj4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5\n"
215 "lgTLDsl8gfk2HRz4cnAiseqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhA\n"
216 "hDjM6H99ysLf0NS6t14eK+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3og\n"
217 "nHhv2hZYTdzEkQEyZHz4V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj\n"
218 "8hJdfRiRBvnA4NnkrMrxW9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtn\n"
219 "tRD1AF9sT+YjoCaHv0hXZvBEUEF3AgMBAAGjUzBRMB0GA1UdDgQWBBTQoIiX3+p/\n"
220 "P4Xz2vwERz6pbjPGhzAfBgNVHSMEGDAWgBTQoIiX3+p/P4Xz2vwERz6pbjPGhzAP\n"
221 "BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCVKoYAw+D1qqWRDSh3\n"
222 "2KlKMnT6sySo7XmReGArj8FTKiZUprByj5CfAtaiDSdPOpcg3EazWbdasZbMmSQm\n"
223 "+jpe5WoKnxL9b12lwwUYHrLl6RlrDHVkIVlXLNbJFY5TpfjvZfHpwVAygF3fnbgW\n"
224 "PPuODUNAS5NDwST+t29jBZ/wwU0pyW0CS4K5d3XMGHBc13j2V/FyvmsZ5xfA4U9H\n"
225 "oEnmZ/Qm+FFK/nR40rTAZ37cuv4ysKFtwvatNgTfHGJwaBUkKFdDbcyxt9abCi6x\n"
226 "/K+ScoJtdIeVcfx8Fnc5PNtSpy8bHI3Zy4IEyw4kOqwwI1h37iBafZ2WdQkTxlAx\n"
228 "-----END CERTIFICATE-----\n";
229 const std::string KEY
=
230 "-----BEGIN PRIVATE KEY-----\n"
231 "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCeRkyxjP0eNHxz\n"
232 "j4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5lgTLDsl8gfk2HRz4cnAi\n"
233 "seqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhAhDjM6H99ysLf0NS6t14e\n"
234 "K+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3ognHhv2hZYTdzEkQEyZHz4\n"
235 "V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj8hJdfRiRBvnA4NnkrMrx\n"
236 "W9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtntRD1AF9sT+YjoCaHv0hX\n"
237 "ZvBEUEF3AgMBAAECggEACCaYpoAbPOX5Dr5y6p47KXboIvrgNFQRPVke62rtOF6M\n"
238 "dQQ3YwKJpCzPxp8qKgbd63KKEfZX2peSHMdKzIGPcSRSRcQ7tlvUN9on1M/rgGIg\n"
239 "3swhI5H0qhdnOLNWdX73qdO6S2pmuiLdTvJ11N4IoLfNj/GnPAr1Ivs1ScL6bkQv\n"
240 "UybaNQ/g2lB0tO7vUeVe2W/AqsIb1eQlf2g+SH7xRj2bGQkr4cWTylqfiVoL/Xic\n"
241 "QVTCks3BWaZhYIhTFgvqVhXZpp52O9J+bxsWJItKQrrCBemxwp82xKbiW/KoI9L1\n"
242 "wSnKvxx7Q3RUN5EvXeOpTRR8QIpBoxP3TTeoj+EOMQKBgQDQb/VfLDlLgfYJpgRC\n"
243 "hKCLW90un9op3nA2n9Dmm9TTLYOmUyiv5ub8QDINEw/YK/NE2JsTSUk2msizqTLL\n"
244 "Z82BFbz9kPlDbJ5MgxG5zXeLvOLurAFmZk/z5JJO+65PKjf0QVLncSAJvMCeNFuC\n"
245 "2yZrEzbrItrjQsN6AedWdx6TTwKBgQDCZAsSI3lQgOh2q1GSxjuIzRAc7JnSGBvD\n"
246 "nG8+SkfKAy7BWe638772Dgx8KYO7TLI4zlm8c9Tr/nkZsGWmM5S2DMI69PWOQWNa\n"
247 "R6QzOFFwNg2JETH7ow+x8+9Q9d3WsPzROz3r5uDXgEk0glthaymVoPILFOiYpz3r\n"
248 "heUbd6mFWQKBgQCCJBVJGhyf54IOHij0u0heGrpr/QTDNY5MnNZa1hs4y2cydyOl\n"
249 "SH8aKp7ViPxQlYhriO6ySQS8YkJD4rXDSImIOmFo1Ja9oVjpHsD3iLFGf2YVbTHm\n"
250 "lKUA+8raI8x+wzZyfELeHMTLL534aWpltp0zJ6kXgQi38pyIVh3x36gogwKBgQCt\n"
251 "nba5k49VVFzLKEXqBjzD+QqMGtFjcH7TnZNJmgQ2K9OFgzIPf5atomyKNHXgQibn\n"
252 "T32cMAQaZqR4SjDvWSBX3FtZVtE+Ja57woKn8IPj6ZL7Oa1fpwpskIbM01s31cln\n"
253 "gjbSy9lC/+PiDw9YmeKBLkcfmKQJO021Xlf6yUxRuQKBgBWPODUO8oKjkuJXNI/w\n"
254 "El9hNWkd+/SZDfnt93dlVyXTtTF0M5M95tlOgqvLtWKSyB/BOnoZYWqR8luMl15d\n"
255 "bf75j5mB0lHMWtyQgvZSkFqe9Or7Zy7hfTShDlZ/w+OXK7PGesaE1F14irShXSji\n"
256 "yn5DZYAZ5pU52xreJeYvDngO\n"
257 "-----END PRIVATE KEY-----\n";
258 const std::string DH
=
259 "-----BEGIN DH PARAMETERS-----\n"
260 "MIIBCAKCAQEA4+DA1j0gDWS71okwHpnvA65NmmR4mf+B3H39g163zY5S+cnWS2LI\n"
261 "dvqnUDpw13naWtQ+Nu7I4rk1XoPaxOPSTu1MTbtYOxxU9M1ceBu4kQjDeHwasPVM\n"
262 "zyEs1XXX3tsbPUxAuayX+AgW6QQAQUEjKDnv3FzVnQTFjwI49LqjnrSjbgQcoMaH\n"
263 "EdGGUc6t1/We2vtsJZx0/dbaMkzFYO8dAbEYHL4sPKQb2mLpCPJZC3vwzpFkHFCd\n"
264 "QSnLW2qRhy+66Mf8shdr6uvpoMcnKMOAvjKdXl9PBeJM9eJPz2lC4tnTiM3DqNzK\n"
265 "Hn8+Pu3KkSIFL/5uBVu1fZSq+lFIEI23wwIBAg==\n"
266 "-----END DH PARAMETERS-----\n";
268 librbd::ImageCtx
*m_image_ctx
;
270 std::optional
<boost::asio::ip::tcp::acceptor
> m_acceptor
;
271 boost::beast::flat_buffer m_buffer
;
272 uint64_t m_server_port
= 0;
275 TEST_F(TestMockMigrationHttpClient
, OpenCloseHttp
) {
276 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
277 C_SaferCond on_connect_ctx
;
278 client_accept(&socket
, false, &on_connect_ctx
);
280 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
281 MockHttpClient
http_client(&mock_test_image_ctx
,
282 get_local_url(URL_SCHEME_HTTP
));
285 http_client
.open(&ctx1
);
286 ASSERT_EQ(0, on_connect_ctx
.wait());
287 ASSERT_EQ(0, ctx1
.wait());
290 http_client
.close(&ctx2
);
291 ASSERT_EQ(0, ctx2
.wait());
294 TEST_F(TestMockMigrationHttpClient
, OpenCloseHttps
) {
295 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
296 C_SaferCond on_connect_ctx
;
297 client_accept(&socket
, false, &on_connect_ctx
);
299 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
300 MockHttpClient
http_client(&mock_test_image_ctx
,
301 get_local_url(URL_SCHEME_HTTPS
));
302 http_client
.set_ignore_self_signed_cert(true);
305 http_client
.open(&ctx1
);
306 ASSERT_EQ(0, on_connect_ctx
.wait());
308 boost::asio::ssl::context ssl_context
{boost::asio::ssl::context::tlsv12
};
309 load_server_certificate(ssl_context
);
310 boost::beast::ssl_stream
<boost::beast::tcp_stream
> ssl_stream
{
311 std::move(socket
), ssl_context
};
313 C_SaferCond on_ssl_handshake_ctx
;
314 client_ssl_handshake(ssl_stream
, false, &on_ssl_handshake_ctx
);
315 ASSERT_EQ(0, on_ssl_handshake_ctx
.wait());
317 ASSERT_EQ(0, ctx1
.wait());
320 http_client
.close(&ctx2
);
322 C_SaferCond on_ssl_shutdown_ctx
;
323 client_ssl_shutdown(ssl_stream
, &on_ssl_shutdown_ctx
);
324 ASSERT_EQ(0, on_ssl_shutdown_ctx
.wait());
326 ASSERT_EQ(0, ctx2
.wait());
329 TEST_F(TestMockMigrationHttpClient
, OpenHttpsHandshakeFail
) {
330 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
331 C_SaferCond on_connect_ctx
;
332 client_accept(&socket
, false, &on_connect_ctx
);
334 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
335 MockHttpClient
http_client(&mock_test_image_ctx
,
336 get_local_url(URL_SCHEME_HTTPS
));
339 http_client
.open(&ctx1
);
340 ASSERT_EQ(0, on_connect_ctx
.wait());
342 boost::asio::ssl::context ssl_context
{boost::asio::ssl::context::tlsv12
};
343 load_server_certificate(ssl_context
);
344 boost::beast::ssl_stream
<boost::beast::tcp_stream
> ssl_stream
{
345 std::move(socket
), ssl_context
};
347 C_SaferCond on_ssl_handshake_ctx
;
348 client_ssl_handshake(ssl_stream
, true, &on_ssl_handshake_ctx
);
349 ASSERT_NE(0, on_ssl_handshake_ctx
.wait());
350 ASSERT_NE(0, ctx1
.wait());
353 TEST_F(TestMockMigrationHttpClient
, OpenInvalidUrl
) {
354 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
355 MockHttpClient
http_client(&mock_test_image_ctx
, "ftp://nope/");
358 http_client
.open(&ctx
);
359 ASSERT_EQ(-EINVAL
, ctx
.wait());
362 TEST_F(TestMockMigrationHttpClient
, OpenResolveFail
) {
363 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
364 MockHttpClient
http_client(&mock_test_image_ctx
, "http://foo.example");
367 http_client
.open(&ctx
);
368 ASSERT_EQ(-ENOENT
, ctx
.wait());
371 TEST_F(TestMockMigrationHttpClient
, OpenConnectFail
) {
372 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
373 MockHttpClient
http_client(&mock_test_image_ctx
,
374 "http://localhost:2/");
377 http_client
.open(&ctx1
);
378 ASSERT_EQ(-ECONNREFUSED
, ctx1
.wait());
381 TEST_F(TestMockMigrationHttpClient
, IssueHead
) {
382 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
383 C_SaferCond on_connect_ctx
;
384 client_accept(&socket
, false, &on_connect_ctx
);
386 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
387 MockHttpClient
http_client(&mock_test_image_ctx
,
388 get_local_url(URL_SCHEME_HTTP
));
391 http_client
.open(&ctx1
);
392 ASSERT_EQ(0, on_connect_ctx
.wait());
393 ASSERT_EQ(0, ctx1
.wait());
395 EmptyHttpRequest req
;
396 req
.method(boost::beast::http::verb::head
);
400 http_client
.issue(EmptyHttpRequest
{req
},
401 [&ctx2
, &res
](int r
, HttpResponse
&& response
) mutable {
402 res
= std::move(response
);
406 HttpResponse expected_res
;
407 client_read_request(socket
, req
);
408 client_write_response(socket
, expected_res
);
410 ASSERT_EQ(0, ctx2
.wait());
411 ASSERT_EQ(expected_res
, res
);
414 http_client
.close(&ctx3
);
415 ASSERT_EQ(0, ctx3
.wait());
418 TEST_F(TestMockMigrationHttpClient
, IssueGet
) {
419 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
420 C_SaferCond on_connect_ctx
;
421 client_accept(&socket
, false, &on_connect_ctx
);
423 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
424 MockHttpClient
http_client(&mock_test_image_ctx
,
425 get_local_url(URL_SCHEME_HTTP
));
428 http_client
.open(&ctx1
);
429 ASSERT_EQ(0, on_connect_ctx
.wait());
430 ASSERT_EQ(0, ctx1
.wait());
432 EmptyHttpRequest req
;
433 req
.method(boost::beast::http::verb::get
);
437 http_client
.issue(EmptyHttpRequest
{req
},
438 [&ctx2
, &res
](int r
, HttpResponse
&& response
) mutable {
439 res
= std::move(response
);
443 HttpResponse expected_res
;
444 expected_res
.body() = "test";
445 client_read_request(socket
, req
);
446 client_write_response(socket
, expected_res
);
448 ASSERT_EQ(0, ctx2
.wait());
449 ASSERT_EQ(expected_res
, res
);
452 http_client
.close(&ctx3
);
453 ASSERT_EQ(0, ctx3
.wait());
456 TEST_F(TestMockMigrationHttpClient
, IssueSendFailed
) {
457 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
458 C_SaferCond on_connect_ctx1
;
459 client_accept(&socket
, false, &on_connect_ctx1
);
461 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
462 MockHttpClient
http_client(&mock_test_image_ctx
,
463 get_local_url(URL_SCHEME_HTTP
));
466 http_client
.open(&ctx1
);
467 ASSERT_EQ(0, on_connect_ctx1
.wait());
468 ASSERT_EQ(0, ctx1
.wait());
470 // close connection to client
471 boost::system::error_code ec
;
474 C_SaferCond on_connect_ctx2
;
475 client_accept(&socket
, false, &on_connect_ctx2
);
477 // send request via closed connection
478 EmptyHttpRequest req
;
479 req
.method(boost::beast::http::verb::get
);
482 http_client
.issue(EmptyHttpRequest
{req
},
483 [&ctx2
](int r
, HttpResponse
&&) mutable {
487 // connection will be reset and request retried
488 ASSERT_EQ(0, on_connect_ctx2
.wait());
489 HttpResponse expected_res
;
490 expected_res
.body() = "test";
491 client_read_request(socket
, req
);
492 client_write_response(socket
, expected_res
);
493 ASSERT_EQ(0, ctx2
.wait());
496 http_client
.close(&ctx3
);
497 ASSERT_EQ(0, ctx3
.wait());
500 TEST_F(TestMockMigrationHttpClient
, IssueReceiveFailed
) {
501 boost::asio::ip::tcp::socket
socket1(*m_image_ctx
->asio_engine
);
502 C_SaferCond on_connect_ctx1
;
503 client_accept(&socket1
, false, &on_connect_ctx1
);
505 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
506 MockHttpClient
http_client(&mock_test_image_ctx
,
507 get_local_url(URL_SCHEME_HTTP
));
510 http_client
.open(&ctx1
);
511 ASSERT_EQ(0, on_connect_ctx1
.wait());
512 ASSERT_EQ(0, ctx1
.wait());
514 // send request via closed connection
515 EmptyHttpRequest req
;
516 req
.method(boost::beast::http::verb::get
);
519 http_client
.issue(EmptyHttpRequest
{req
},
520 [&ctx2
](int r
, HttpResponse
&&) mutable {
524 // close connection to client after reading request
525 client_read_request(socket1
, req
);
527 C_SaferCond on_connect_ctx2
;
528 boost::asio::ip::tcp::socket
socket2(*m_image_ctx
->asio_engine
);
529 client_accept(&socket2
, false, &on_connect_ctx2
);
531 boost::system::error_code ec
;
533 ASSERT_EQ(0, on_connect_ctx2
.wait());
535 // connection will be reset and request retried
536 HttpResponse expected_res
;
537 expected_res
.body() = "test";
538 client_read_request(socket2
, req
);
539 client_write_response(socket2
, expected_res
);
540 ASSERT_EQ(0, ctx2
.wait());
543 http_client
.close(&ctx3
);
544 ASSERT_EQ(0, ctx3
.wait());
547 TEST_F(TestMockMigrationHttpClient
, IssueResetFailed
) {
549 create_acceptor(true);
551 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
552 C_SaferCond on_connect_ctx1
;
553 client_accept(&socket
, false, &on_connect_ctx1
);
555 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
556 MockHttpClient
http_client(&mock_test_image_ctx
,
557 get_local_url(URL_SCHEME_HTTP
));
560 http_client
.open(&ctx1
);
561 ASSERT_EQ(0, on_connect_ctx1
.wait());
562 ASSERT_EQ(0, ctx1
.wait());
564 // send requests then close connection
565 EmptyHttpRequest req
;
566 req
.method(boost::beast::http::verb::get
);
569 http_client
.issue(EmptyHttpRequest
{req
},
570 [&ctx2
](int r
, HttpResponse
&&) mutable {
575 http_client
.issue(EmptyHttpRequest
{req
},
576 [&ctx3
](int r
, HttpResponse
&&) mutable {
580 client_read_request(socket
, req
);
581 client_read_request(socket
, req
);
583 // close connection to client and verify requests are failed
585 boost::system::error_code ec
;
588 ASSERT_EQ(-ECONNREFUSED
, ctx2
.wait());
589 ASSERT_EQ(-ECONNREFUSED
, ctx3
.wait());
591 // additional request will retry the failed connection
592 create_acceptor(true);
594 C_SaferCond on_connect_ctx2
;
595 client_accept(&socket
, false, &on_connect_ctx2
);
598 http_client
.issue(EmptyHttpRequest
{req
},
599 [&ctx4
](int r
, HttpResponse
&&) mutable {
603 ASSERT_EQ(0, on_connect_ctx2
.wait());
604 client_read_request(socket
, req
);
606 HttpResponse expected_res
;
607 expected_res
.body() = "test";
608 client_write_response(socket
, expected_res
);
609 ASSERT_EQ(0, ctx4
.wait());
612 http_client
.close(&ctx5
);
613 ASSERT_EQ(0, ctx5
.wait());
616 TEST_F(TestMockMigrationHttpClient
, IssuePipelined
) {
617 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
618 C_SaferCond on_connect_ctx
;
619 client_accept(&socket
, false, &on_connect_ctx
);
621 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
622 MockHttpClient
http_client(&mock_test_image_ctx
,
623 get_local_url(URL_SCHEME_HTTP
));
626 http_client
.open(&ctx1
);
627 ASSERT_EQ(0, on_connect_ctx
.wait());
628 ASSERT_EQ(0, ctx1
.wait());
630 // issue two pipelined (concurrent) get requests
631 EmptyHttpRequest req1
;
632 req1
.method(boost::beast::http::verb::get
);
636 http_client
.issue(EmptyHttpRequest
{req1
},
637 [&ctx2
, &res1
](int r
, HttpResponse
&& response
) mutable {
638 res1
= std::move(response
);
642 EmptyHttpRequest req2
;
643 req2
.method(boost::beast::http::verb::get
);
647 http_client
.issue(EmptyHttpRequest
{req2
},
648 [&ctx3
, &res2
](int r
, HttpResponse
&& response
) mutable {
649 res2
= std::move(response
);
653 client_read_request(socket
, req1
);
654 client_read_request(socket
, req2
);
656 // read the responses sequentially
657 HttpResponse expected_res1
;
658 expected_res1
.body() = "test";
659 client_write_response(socket
, expected_res1
);
660 ASSERT_EQ(0, ctx2
.wait());
661 ASSERT_EQ(expected_res1
, res1
);
663 HttpResponse expected_res2
;
664 expected_res2
.body() = "test";
665 client_write_response(socket
, expected_res2
);
666 ASSERT_EQ(0, ctx3
.wait());
667 ASSERT_EQ(expected_res2
, res2
);
670 http_client
.close(&ctx4
);
671 ASSERT_EQ(0, ctx4
.wait());
674 TEST_F(TestMockMigrationHttpClient
, IssuePipelinedRestart
) {
675 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
676 C_SaferCond on_connect_ctx1
;
677 client_accept(&socket
, false, &on_connect_ctx1
);
679 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
680 MockHttpClient
http_client(&mock_test_image_ctx
,
681 get_local_url(URL_SCHEME_HTTP
));
684 http_client
.open(&ctx1
);
685 ASSERT_EQ(0, on_connect_ctx1
.wait());
686 ASSERT_EQ(0, ctx1
.wait());
688 // issue two pipelined (concurrent) get requests
689 EmptyHttpRequest req1
;
690 req1
.keep_alive(false);
691 req1
.method(boost::beast::http::verb::get
);
693 C_SaferCond on_connect_ctx2
;
694 client_accept(&socket
, false, &on_connect_ctx2
);
698 http_client
.issue(EmptyHttpRequest
{req1
},
699 [&ctx2
, &res1
](int r
, HttpResponse
&& response
) mutable {
700 res1
= std::move(response
);
704 EmptyHttpRequest req2
;
705 req2
.method(boost::beast::http::verb::get
);
709 http_client
.issue(EmptyHttpRequest
{req2
},
710 [&ctx3
, &res2
](int r
, HttpResponse
&& response
) mutable {
711 res2
= std::move(response
);
715 client_read_request(socket
, req1
);
716 client_read_request(socket
, req2
);
718 // read the responses sequentially
719 HttpResponse expected_res1
;
720 expected_res1
.body() = "test";
721 expected_res1
.keep_alive(false);
722 client_write_response(socket
, expected_res1
);
723 ASSERT_EQ(0, ctx2
.wait());
724 ASSERT_EQ(expected_res1
, res1
);
726 // second request will need to be re-sent due to 'need_eof' condition
727 ASSERT_EQ(0, on_connect_ctx2
.wait());
728 client_read_request(socket
, req2
);
730 HttpResponse expected_res2
;
731 expected_res2
.body() = "test";
732 client_write_response(socket
, expected_res2
);
733 ASSERT_EQ(0, ctx3
.wait());
734 ASSERT_EQ(expected_res2
, res2
);
737 http_client
.close(&ctx4
);
738 ASSERT_EQ(0, ctx4
.wait());
741 TEST_F(TestMockMigrationHttpClient
, ShutdownInFlight
) {
742 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
743 C_SaferCond on_connect_ctx
;
744 client_accept(&socket
, false, &on_connect_ctx
);
746 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
747 MockHttpClient
http_client(&mock_test_image_ctx
,
748 get_local_url(URL_SCHEME_HTTP
));
751 http_client
.open(&ctx1
);
752 ASSERT_EQ(0, on_connect_ctx
.wait());
753 ASSERT_EQ(0, ctx1
.wait());
755 EmptyHttpRequest req
;
756 req
.method(boost::beast::http::verb::get
);
759 http_client
.issue(EmptyHttpRequest
{req
},
760 [&ctx2
](int r
, HttpResponse
&&) mutable {
764 client_read_request(socket
, req
);
767 http_client
.close(&ctx3
);
768 ASSERT_EQ(0, ctx3
.wait());
769 ASSERT_EQ(-ESHUTDOWN
, ctx2
.wait());
772 TEST_F(TestMockMigrationHttpClient
, GetSize
) {
773 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
774 MockHttpClient
http_client(&mock_test_image_ctx
,
775 get_local_url(URL_SCHEME_HTTP
));
777 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
778 C_SaferCond on_connect_ctx
;
779 client_accept(&socket
, false, &on_connect_ctx
);
782 http_client
.open(&ctx1
);
783 ASSERT_EQ(0, on_connect_ctx
.wait());
784 ASSERT_EQ(0, ctx1
.wait());
788 http_client
.get_size(&size
, &ctx2
);
790 EmptyHttpRequest expected_req
;
791 expected_req
.method(boost::beast::http::verb::head
);
792 client_read_request(socket
, expected_req
);
794 HttpResponse expected_res
;
795 expected_res
.body() = std::string(123, '1');
796 client_write_response(socket
, expected_res
);
798 ASSERT_EQ(0, ctx2
.wait());
799 ASSERT_EQ(123, size
);
802 http_client
.close(&ctx3
);
803 ASSERT_EQ(0, ctx3
.wait());
806 TEST_F(TestMockMigrationHttpClient
, GetSizeError
) {
807 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
808 MockHttpClient
http_client(&mock_test_image_ctx
,
809 get_local_url(URL_SCHEME_HTTP
));
811 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
812 C_SaferCond on_connect_ctx
;
813 client_accept(&socket
, false, &on_connect_ctx
);
816 http_client
.open(&ctx1
);
817 ASSERT_EQ(0, on_connect_ctx
.wait());
818 ASSERT_EQ(0, ctx1
.wait());
822 http_client
.get_size(&size
, &ctx2
);
824 EmptyHttpRequest expected_req
;
825 expected_req
.method(boost::beast::http::verb::head
);
826 client_read_request(socket
, expected_req
);
828 HttpResponse expected_res
;
829 expected_res
.result(boost::beast::http::status::internal_server_error
);
830 client_write_response(socket
, expected_res
);
832 ASSERT_EQ(-EIO
, ctx2
.wait());
835 http_client
.close(&ctx3
);
836 ASSERT_EQ(0, ctx3
.wait());
839 TEST_F(TestMockMigrationHttpClient
, Read
) {
840 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
841 MockHttpClient
http_client(&mock_test_image_ctx
,
842 get_local_url(URL_SCHEME_HTTP
));
844 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
845 C_SaferCond on_connect_ctx
;
846 client_accept(&socket
, false, &on_connect_ctx
);
849 http_client
.open(&ctx1
);
850 ASSERT_EQ(0, on_connect_ctx
.wait());
851 ASSERT_EQ(0, ctx1
.wait());
855 http_client
.read({{0, 128}, {256, 64}}, &bl
, &ctx2
);
857 EmptyHttpRequest expected_req1
;
858 expected_req1
.method(boost::beast::http::verb::get
);
859 expected_req1
.set(boost::beast::http::field::range
, "bytes=0-127");
860 client_read_request(socket
, expected_req1
);
862 EmptyHttpRequest expected_req2
;
863 expected_req2
.method(boost::beast::http::verb::get
);
864 expected_req2
.set(boost::beast::http::field::range
, "bytes=256-319");
865 client_read_request(socket
, expected_req2
);
867 HttpResponse expected_res1
;
868 expected_res1
.result(boost::beast::http::status::partial_content
);
869 expected_res1
.body() = std::string(128, '1');
870 client_write_response(socket
, expected_res1
);
872 HttpResponse expected_res2
;
873 expected_res2
.result(boost::beast::http::status::partial_content
);
874 expected_res2
.body() = std::string(64, '2');
875 client_write_response(socket
, expected_res2
);
877 ASSERT_EQ(192, ctx2
.wait());
879 bufferlist expect_bl
;
880 expect_bl
.append(std::string(128, '1'));
881 expect_bl
.append(std::string(64, '2'));
882 ASSERT_EQ(expect_bl
, bl
);
885 http_client
.close(&ctx3
);
886 ASSERT_EQ(0, ctx3
.wait());
889 } // namespace migration
890 } // namespace librbd