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"
11 #include <boost/asio/ip/tcp.hpp>
12 #include <boost/beast/core.hpp>
13 #include <boost/beast/http.hpp>
18 struct MockTestImageCtx
: public MockImageCtx
{
19 MockTestImageCtx(ImageCtx
&image_ctx
) : MockImageCtx(image_ctx
) {
23 } // anonymous namespace
27 inline ImageCtx
*get_image_ctx(MockTestImageCtx
*image_ctx
) {
28 return image_ctx
->image_ctx
;
34 #include "librbd/migration/HttpClient.cc"
36 using EmptyHttpRequest
= boost::beast::http::request
<
37 boost::beast::http::empty_body
>;
38 using HttpResponse
= boost::beast::http::response
<
39 boost::beast::http::string_body
>;
45 template <typename Body
>
46 bool operator==(const boost::beast::http::request
<Body
>& lhs
,
47 const boost::beast::http::request
<Body
>& rhs
) {
48 return (lhs
.method() == rhs
.method() &&
49 lhs
.target() == rhs
.target());
52 template <typename Body
>
53 bool operator==(const boost::beast::http::response
<Body
>& lhs
,
54 const boost::beast::http::response
<Body
>& rhs
) {
55 return (lhs
.result() == rhs
.result() &&
56 lhs
.body() == rhs
.body());
66 using ::testing::Invoke
;
68 class TestMockMigrationHttpClient
: public TestMockFixture
{
70 typedef HttpClient
<MockTestImageCtx
> MockHttpClient
;
72 void SetUp() override
{
73 TestMockFixture::SetUp();
75 ASSERT_EQ(0, open_image(m_image_name
, &m_image_ctx
));
77 create_acceptor(false);
80 void TearDown() override
{
83 TestMockFixture::TearDown();
86 void create_acceptor(bool reuse
) {
87 m_acceptor
.emplace(*m_image_ctx
->asio_engine
,
88 boost::asio::ip::tcp::endpoint(
89 boost::asio::ip::tcp::v4(), m_server_port
), reuse
);
90 m_server_port
= m_acceptor
->local_endpoint().port();
93 std::string
get_local_url(UrlScheme url_scheme
) {
94 std::stringstream sstream
;
97 sstream
<< "http://127.0.0.1";
99 case URL_SCHEME_HTTPS
:
100 sstream
<< "https://localhost";
107 sstream
<< ":" << m_server_port
<< "/target";
108 return sstream
.str();
111 void client_accept(boost::asio::ip::tcp::socket
* socket
, bool close
,
112 Context
* on_connect
) {
113 m_acceptor
->async_accept(
114 boost::asio::make_strand(m_image_ctx
->asio_engine
->get_executor()),
115 [socket
, close
, on_connect
]
116 (auto ec
, boost::asio::ip::tcp::socket in_socket
) {
118 in_socket
.shutdown(boost::asio::ip::tcp::socket::shutdown_both
);
120 ASSERT_FALSE(ec
) << "Unexpected error: " << ec
;
121 *socket
= std::move(in_socket
);
123 on_connect
->complete(0);
127 template <typename Body
>
128 void client_read_request(boost::asio::ip::tcp::socket
& socket
,
129 boost::beast::http::request
<Body
>& expected_req
) {
130 boost::beast::http::request
<Body
> req
;
131 boost::beast::error_code ec
;
132 boost::beast::http::read(socket
, m_buffer
, req
, ec
);
133 ASSERT_FALSE(ec
) << "Unexpected errror: " << ec
;
135 expected_req
.target("/target");
136 ASSERT_EQ(expected_req
, req
);
139 void client_write_response(boost::asio::ip::tcp::socket
& socket
,
140 HttpResponse
& expected_res
) {
141 expected_res
.set(boost::beast::http::field::server
,
142 BOOST_BEAST_VERSION_STRING
);
143 expected_res
.set(boost::beast::http::field::content_type
, "text/plain");
144 expected_res
.content_length(expected_res
.body().size());
145 expected_res
.prepare_payload();
147 boost::beast::error_code ec
;
148 boost::beast::http::write(socket
, expected_res
, ec
);
149 ASSERT_FALSE(ec
) << "Unexpected errror: " << ec
;
152 template <typename Stream
>
153 void client_ssl_handshake(Stream
& stream
, bool ignore_failure
,
154 Context
* on_handshake
) {
155 stream
.async_handshake(
156 boost::asio::ssl::stream_base::server
,
157 [ignore_failure
, on_handshake
](auto ec
) {
158 ASSERT_FALSE(!ignore_failure
&& ec
) << "Unexpected error: " << ec
;
159 on_handshake
->complete(-ec
.value());
163 template <typename Stream
>
164 void client_ssl_shutdown(Stream
& stream
, Context
* on_shutdown
) {
165 stream
.async_shutdown(
166 [on_shutdown
](auto ec
) {
167 ASSERT_FALSE(ec
) << "Unexpected error: " << ec
;
168 on_shutdown
->complete(-ec
.value());
172 void load_server_certificate(boost::asio::ssl::context
& ctx
) {
174 boost::asio::ssl::context::default_workarounds
|
175 boost::asio::ssl::context::no_sslv2
|
176 boost::asio::ssl::context::single_dh_use
);
177 ctx
.use_certificate_chain(
178 boost::asio::buffer(CERTIFICATE
.data(), CERTIFICATE
.size()));
180 boost::asio::buffer(KEY
.data(), KEY
.size()),
181 boost::asio::ssl::context::file_format::pem
);
183 boost::asio::buffer(DH
.data(), DH
.size()));
186 // dummy self-signed cert for localhost
187 const std::string CERTIFICATE
=
188 "-----BEGIN CERTIFICATE-----\n"
189 "MIIDXzCCAkegAwIBAgIUYH6rAaq66LC6yJ3XK1WEMIfmY4cwDQYJKoZIhvcNAQEL\n"
190 "BQAwPzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAlZBMQ8wDQYDVQQHDAZNY0xlYW4x\n"
191 "EjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMDExMDIyMTM5NTVaFw00ODAzMjAyMTM5\n"
192 "NTVaMD8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJWQTEPMA0GA1UEBwwGTWNMZWFu\n"
193 "MRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\n"
194 "AoIBAQCeRkyxjP0eNHxzj4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5\n"
195 "lgTLDsl8gfk2HRz4cnAiseqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhA\n"
196 "hDjM6H99ysLf0NS6t14eK+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3og\n"
197 "nHhv2hZYTdzEkQEyZHz4V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj\n"
198 "8hJdfRiRBvnA4NnkrMrxW9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtn\n"
199 "tRD1AF9sT+YjoCaHv0hXZvBEUEF3AgMBAAGjUzBRMB0GA1UdDgQWBBTQoIiX3+p/\n"
200 "P4Xz2vwERz6pbjPGhzAfBgNVHSMEGDAWgBTQoIiX3+p/P4Xz2vwERz6pbjPGhzAP\n"
201 "BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCVKoYAw+D1qqWRDSh3\n"
202 "2KlKMnT6sySo7XmReGArj8FTKiZUprByj5CfAtaiDSdPOpcg3EazWbdasZbMmSQm\n"
203 "+jpe5WoKnxL9b12lwwUYHrLl6RlrDHVkIVlXLNbJFY5TpfjvZfHpwVAygF3fnbgW\n"
204 "PPuODUNAS5NDwST+t29jBZ/wwU0pyW0CS4K5d3XMGHBc13j2V/FyvmsZ5xfA4U9H\n"
205 "oEnmZ/Qm+FFK/nR40rTAZ37cuv4ysKFtwvatNgTfHGJwaBUkKFdDbcyxt9abCi6x\n"
206 "/K+ScoJtdIeVcfx8Fnc5PNtSpy8bHI3Zy4IEyw4kOqwwI1h37iBafZ2WdQkTxlAx\n"
208 "-----END CERTIFICATE-----\n";
209 const std::string KEY
=
210 "-----BEGIN PRIVATE KEY-----\n"
211 "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCeRkyxjP0eNHxz\n"
212 "j4/R+Bg/31p7kEjB5d/LYtrzBIYNe+3DN8gdReixEpR5lgTLDsl8gfk2HRz4cnAi\n"
213 "seqYL6GKtw/cFadzLyXTbW4iavmTWiGYw/8RJlKunbhAhDjM6H99ysLf0NS6t14e\n"
214 "K+bEJIW1PiTYRR1U5I4kSIjpCX7+nJVuwMEZ2XBpN3ognHhv2hZYTdzEkQEyZHz4\n"
215 "V/ApfD7rlja5ecd/vJfPJeA8nudnGCh3Uo6f8I9TObAj8hJdfRiRBvnA4NnkrMrx\n"
216 "W9UtVjScnw9Xia11FM/IGJIgMpLQ5dqBw930p6FxMYtntRD1AF9sT+YjoCaHv0hX\n"
217 "ZvBEUEF3AgMBAAECggEACCaYpoAbPOX5Dr5y6p47KXboIvrgNFQRPVke62rtOF6M\n"
218 "dQQ3YwKJpCzPxp8qKgbd63KKEfZX2peSHMdKzIGPcSRSRcQ7tlvUN9on1M/rgGIg\n"
219 "3swhI5H0qhdnOLNWdX73qdO6S2pmuiLdTvJ11N4IoLfNj/GnPAr1Ivs1ScL6bkQv\n"
220 "UybaNQ/g2lB0tO7vUeVe2W/AqsIb1eQlf2g+SH7xRj2bGQkr4cWTylqfiVoL/Xic\n"
221 "QVTCks3BWaZhYIhTFgvqVhXZpp52O9J+bxsWJItKQrrCBemxwp82xKbiW/KoI9L1\n"
222 "wSnKvxx7Q3RUN5EvXeOpTRR8QIpBoxP3TTeoj+EOMQKBgQDQb/VfLDlLgfYJpgRC\n"
223 "hKCLW90un9op3nA2n9Dmm9TTLYOmUyiv5ub8QDINEw/YK/NE2JsTSUk2msizqTLL\n"
224 "Z82BFbz9kPlDbJ5MgxG5zXeLvOLurAFmZk/z5JJO+65PKjf0QVLncSAJvMCeNFuC\n"
225 "2yZrEzbrItrjQsN6AedWdx6TTwKBgQDCZAsSI3lQgOh2q1GSxjuIzRAc7JnSGBvD\n"
226 "nG8+SkfKAy7BWe638772Dgx8KYO7TLI4zlm8c9Tr/nkZsGWmM5S2DMI69PWOQWNa\n"
227 "R6QzOFFwNg2JETH7ow+x8+9Q9d3WsPzROz3r5uDXgEk0glthaymVoPILFOiYpz3r\n"
228 "heUbd6mFWQKBgQCCJBVJGhyf54IOHij0u0heGrpr/QTDNY5MnNZa1hs4y2cydyOl\n"
229 "SH8aKp7ViPxQlYhriO6ySQS8YkJD4rXDSImIOmFo1Ja9oVjpHsD3iLFGf2YVbTHm\n"
230 "lKUA+8raI8x+wzZyfELeHMTLL534aWpltp0zJ6kXgQi38pyIVh3x36gogwKBgQCt\n"
231 "nba5k49VVFzLKEXqBjzD+QqMGtFjcH7TnZNJmgQ2K9OFgzIPf5atomyKNHXgQibn\n"
232 "T32cMAQaZqR4SjDvWSBX3FtZVtE+Ja57woKn8IPj6ZL7Oa1fpwpskIbM01s31cln\n"
233 "gjbSy9lC/+PiDw9YmeKBLkcfmKQJO021Xlf6yUxRuQKBgBWPODUO8oKjkuJXNI/w\n"
234 "El9hNWkd+/SZDfnt93dlVyXTtTF0M5M95tlOgqvLtWKSyB/BOnoZYWqR8luMl15d\n"
235 "bf75j5mB0lHMWtyQgvZSkFqe9Or7Zy7hfTShDlZ/w+OXK7PGesaE1F14irShXSji\n"
236 "yn5DZYAZ5pU52xreJeYvDngO\n"
237 "-----END PRIVATE KEY-----\n";
238 const std::string DH
=
239 "-----BEGIN DH PARAMETERS-----\n"
240 "MIIBCAKCAQEA4+DA1j0gDWS71okwHpnvA65NmmR4mf+B3H39g163zY5S+cnWS2LI\n"
241 "dvqnUDpw13naWtQ+Nu7I4rk1XoPaxOPSTu1MTbtYOxxU9M1ceBu4kQjDeHwasPVM\n"
242 "zyEs1XXX3tsbPUxAuayX+AgW6QQAQUEjKDnv3FzVnQTFjwI49LqjnrSjbgQcoMaH\n"
243 "EdGGUc6t1/We2vtsJZx0/dbaMkzFYO8dAbEYHL4sPKQb2mLpCPJZC3vwzpFkHFCd\n"
244 "QSnLW2qRhy+66Mf8shdr6uvpoMcnKMOAvjKdXl9PBeJM9eJPz2lC4tnTiM3DqNzK\n"
245 "Hn8+Pu3KkSIFL/5uBVu1fZSq+lFIEI23wwIBAg==\n"
246 "-----END DH PARAMETERS-----\n";
248 librbd::ImageCtx
*m_image_ctx
;
250 std::optional
<boost::asio::ip::tcp::acceptor
> m_acceptor
;
251 boost::beast::flat_buffer m_buffer
;
252 uint64_t m_server_port
= 0;
255 TEST_F(TestMockMigrationHttpClient
, OpenCloseHttp
) {
256 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
257 C_SaferCond on_connect_ctx
;
258 client_accept(&socket
, false, &on_connect_ctx
);
260 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
261 MockHttpClient
http_client(&mock_test_image_ctx
,
262 get_local_url(URL_SCHEME_HTTP
));
265 http_client
.open(&ctx1
);
266 ASSERT_EQ(0, on_connect_ctx
.wait());
267 ASSERT_EQ(0, ctx1
.wait());
270 http_client
.close(&ctx2
);
271 ASSERT_EQ(0, ctx2
.wait());
274 TEST_F(TestMockMigrationHttpClient
, OpenCloseHttps
) {
275 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
276 C_SaferCond on_connect_ctx
;
277 client_accept(&socket
, false, &on_connect_ctx
);
279 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
280 MockHttpClient
http_client(&mock_test_image_ctx
,
281 get_local_url(URL_SCHEME_HTTPS
));
282 http_client
.set_ignore_self_signed_cert(true);
285 http_client
.open(&ctx1
);
286 ASSERT_EQ(0, on_connect_ctx
.wait());
288 boost::asio::ssl::context ssl_context
{boost::asio::ssl::context::tlsv12
};
289 load_server_certificate(ssl_context
);
290 boost::beast::ssl_stream
<boost::beast::tcp_stream
> ssl_stream
{
291 std::move(socket
), ssl_context
};
293 C_SaferCond on_ssl_handshake_ctx
;
294 client_ssl_handshake(ssl_stream
, false, &on_ssl_handshake_ctx
);
295 ASSERT_EQ(0, on_ssl_handshake_ctx
.wait());
297 ASSERT_EQ(0, ctx1
.wait());
300 http_client
.close(&ctx2
);
302 C_SaferCond on_ssl_shutdown_ctx
;
303 client_ssl_shutdown(ssl_stream
, &on_ssl_shutdown_ctx
);
304 ASSERT_EQ(0, on_ssl_shutdown_ctx
.wait());
306 ASSERT_EQ(0, ctx2
.wait());
309 TEST_F(TestMockMigrationHttpClient
, OpenHttpsHandshakeFail
) {
310 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
311 C_SaferCond on_connect_ctx
;
312 client_accept(&socket
, false, &on_connect_ctx
);
314 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
315 MockHttpClient
http_client(&mock_test_image_ctx
,
316 get_local_url(URL_SCHEME_HTTPS
));
319 http_client
.open(&ctx1
);
320 ASSERT_EQ(0, on_connect_ctx
.wait());
322 boost::asio::ssl::context ssl_context
{boost::asio::ssl::context::tlsv12
};
323 load_server_certificate(ssl_context
);
324 boost::beast::ssl_stream
<boost::beast::tcp_stream
> ssl_stream
{
325 std::move(socket
), ssl_context
};
327 C_SaferCond on_ssl_handshake_ctx
;
328 client_ssl_handshake(ssl_stream
, true, &on_ssl_handshake_ctx
);
329 ASSERT_NE(0, on_ssl_handshake_ctx
.wait());
330 ASSERT_NE(0, ctx1
.wait());
333 TEST_F(TestMockMigrationHttpClient
, OpenInvalidUrl
) {
334 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
335 MockHttpClient
http_client(&mock_test_image_ctx
, "ftp://nope/");
338 http_client
.open(&ctx
);
339 ASSERT_EQ(-EINVAL
, ctx
.wait());
342 TEST_F(TestMockMigrationHttpClient
, OpenResolveFail
) {
343 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
344 MockHttpClient
http_client(&mock_test_image_ctx
, "http://foo.example");
347 http_client
.open(&ctx
);
348 ASSERT_EQ(-ENOENT
, ctx
.wait());
351 TEST_F(TestMockMigrationHttpClient
, OpenConnectFail
) {
352 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
353 MockHttpClient
http_client(&mock_test_image_ctx
,
354 "http://localhost:2/");
357 http_client
.open(&ctx1
);
358 ASSERT_EQ(-ECONNREFUSED
, ctx1
.wait());
361 TEST_F(TestMockMigrationHttpClient
, IssueHead
) {
362 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
363 C_SaferCond on_connect_ctx
;
364 client_accept(&socket
, false, &on_connect_ctx
);
366 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
367 MockHttpClient
http_client(&mock_test_image_ctx
,
368 get_local_url(URL_SCHEME_HTTP
));
371 http_client
.open(&ctx1
);
372 ASSERT_EQ(0, on_connect_ctx
.wait());
373 ASSERT_EQ(0, ctx1
.wait());
375 EmptyHttpRequest req
;
376 req
.method(boost::beast::http::verb::head
);
380 http_client
.issue(EmptyHttpRequest
{req
},
381 [&ctx2
, &res
](int r
, HttpResponse
&& response
) mutable {
382 res
= std::move(response
);
386 HttpResponse expected_res
;
387 client_read_request(socket
, req
);
388 client_write_response(socket
, expected_res
);
390 ASSERT_EQ(0, ctx2
.wait());
391 ASSERT_EQ(expected_res
, res
);
394 http_client
.close(&ctx3
);
395 ASSERT_EQ(0, ctx3
.wait());
398 TEST_F(TestMockMigrationHttpClient
, IssueGet
) {
399 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
400 C_SaferCond on_connect_ctx
;
401 client_accept(&socket
, false, &on_connect_ctx
);
403 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
404 MockHttpClient
http_client(&mock_test_image_ctx
,
405 get_local_url(URL_SCHEME_HTTP
));
408 http_client
.open(&ctx1
);
409 ASSERT_EQ(0, on_connect_ctx
.wait());
410 ASSERT_EQ(0, ctx1
.wait());
412 EmptyHttpRequest req
;
413 req
.method(boost::beast::http::verb::get
);
417 http_client
.issue(EmptyHttpRequest
{req
},
418 [&ctx2
, &res
](int r
, HttpResponse
&& response
) mutable {
419 res
= std::move(response
);
423 HttpResponse expected_res
;
424 expected_res
.body() = "test";
425 client_read_request(socket
, req
);
426 client_write_response(socket
, expected_res
);
428 ASSERT_EQ(0, ctx2
.wait());
429 ASSERT_EQ(expected_res
, res
);
432 http_client
.close(&ctx3
);
433 ASSERT_EQ(0, ctx3
.wait());
436 TEST_F(TestMockMigrationHttpClient
, IssueSendFailed
) {
437 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
438 C_SaferCond on_connect_ctx1
;
439 client_accept(&socket
, false, &on_connect_ctx1
);
441 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
442 MockHttpClient
http_client(&mock_test_image_ctx
,
443 get_local_url(URL_SCHEME_HTTP
));
446 http_client
.open(&ctx1
);
447 ASSERT_EQ(0, on_connect_ctx1
.wait());
448 ASSERT_EQ(0, ctx1
.wait());
450 // close connection to client
451 boost::system::error_code ec
;
454 C_SaferCond on_connect_ctx2
;
455 client_accept(&socket
, false, &on_connect_ctx2
);
457 // send request via closed connection
458 EmptyHttpRequest req
;
459 req
.method(boost::beast::http::verb::get
);
462 http_client
.issue(EmptyHttpRequest
{req
},
463 [&ctx2
](int r
, HttpResponse
&&) mutable {
467 // connection will be reset and request retried
468 ASSERT_EQ(0, on_connect_ctx2
.wait());
469 HttpResponse expected_res
;
470 expected_res
.body() = "test";
471 client_read_request(socket
, req
);
472 client_write_response(socket
, expected_res
);
473 ASSERT_EQ(0, ctx2
.wait());
476 http_client
.close(&ctx3
);
477 ASSERT_EQ(0, ctx3
.wait());
480 TEST_F(TestMockMigrationHttpClient
, IssueReceiveFailed
) {
481 boost::asio::ip::tcp::socket
socket1(*m_image_ctx
->asio_engine
);
482 C_SaferCond on_connect_ctx1
;
483 client_accept(&socket1
, false, &on_connect_ctx1
);
485 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
486 MockHttpClient
http_client(&mock_test_image_ctx
,
487 get_local_url(URL_SCHEME_HTTP
));
490 http_client
.open(&ctx1
);
491 ASSERT_EQ(0, on_connect_ctx1
.wait());
492 ASSERT_EQ(0, ctx1
.wait());
494 // send request via closed connection
495 EmptyHttpRequest req
;
496 req
.method(boost::beast::http::verb::get
);
499 http_client
.issue(EmptyHttpRequest
{req
},
500 [&ctx2
](int r
, HttpResponse
&&) mutable {
504 // close connection to client after reading request
505 client_read_request(socket1
, req
);
507 C_SaferCond on_connect_ctx2
;
508 boost::asio::ip::tcp::socket
socket2(*m_image_ctx
->asio_engine
);
509 client_accept(&socket2
, false, &on_connect_ctx2
);
511 boost::system::error_code ec
;
513 ASSERT_EQ(0, on_connect_ctx2
.wait());
515 // connection will be reset and request retried
516 HttpResponse expected_res
;
517 expected_res
.body() = "test";
518 client_read_request(socket2
, req
);
519 client_write_response(socket2
, expected_res
);
520 ASSERT_EQ(0, ctx2
.wait());
523 http_client
.close(&ctx3
);
524 ASSERT_EQ(0, ctx3
.wait());
527 TEST_F(TestMockMigrationHttpClient
, IssueResetFailed
) {
529 create_acceptor(true);
531 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
532 C_SaferCond on_connect_ctx1
;
533 client_accept(&socket
, false, &on_connect_ctx1
);
535 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
536 MockHttpClient
http_client(&mock_test_image_ctx
,
537 get_local_url(URL_SCHEME_HTTP
));
540 http_client
.open(&ctx1
);
541 ASSERT_EQ(0, on_connect_ctx1
.wait());
542 ASSERT_EQ(0, ctx1
.wait());
544 // send requests then close connection
545 EmptyHttpRequest req
;
546 req
.method(boost::beast::http::verb::get
);
549 http_client
.issue(EmptyHttpRequest
{req
},
550 [&ctx2
](int r
, HttpResponse
&&) mutable {
555 http_client
.issue(EmptyHttpRequest
{req
},
556 [&ctx3
](int r
, HttpResponse
&&) mutable {
560 client_read_request(socket
, req
);
561 client_read_request(socket
, req
);
563 // close connection to client and verify requests are failed
565 boost::system::error_code ec
;
568 ASSERT_EQ(-ECONNREFUSED
, ctx2
.wait());
569 ASSERT_EQ(-ECONNREFUSED
, ctx3
.wait());
571 // additional request will retry the failed connection
572 create_acceptor(true);
574 C_SaferCond on_connect_ctx2
;
575 client_accept(&socket
, false, &on_connect_ctx2
);
578 http_client
.issue(EmptyHttpRequest
{req
},
579 [&ctx4
](int r
, HttpResponse
&&) mutable {
583 ASSERT_EQ(0, on_connect_ctx2
.wait());
584 client_read_request(socket
, req
);
586 HttpResponse expected_res
;
587 expected_res
.body() = "test";
588 client_write_response(socket
, expected_res
);
589 ASSERT_EQ(0, ctx4
.wait());
592 http_client
.close(&ctx5
);
593 ASSERT_EQ(0, ctx5
.wait());
596 TEST_F(TestMockMigrationHttpClient
, IssuePipelined
) {
597 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
598 C_SaferCond on_connect_ctx
;
599 client_accept(&socket
, false, &on_connect_ctx
);
601 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
602 MockHttpClient
http_client(&mock_test_image_ctx
,
603 get_local_url(URL_SCHEME_HTTP
));
606 http_client
.open(&ctx1
);
607 ASSERT_EQ(0, on_connect_ctx
.wait());
608 ASSERT_EQ(0, ctx1
.wait());
610 // issue two pipelined (concurrent) get requests
611 EmptyHttpRequest req1
;
612 req1
.method(boost::beast::http::verb::get
);
616 http_client
.issue(EmptyHttpRequest
{req1
},
617 [&ctx2
, &res1
](int r
, HttpResponse
&& response
) mutable {
618 res1
= std::move(response
);
622 EmptyHttpRequest req2
;
623 req2
.method(boost::beast::http::verb::get
);
627 http_client
.issue(EmptyHttpRequest
{req2
},
628 [&ctx3
, &res2
](int r
, HttpResponse
&& response
) mutable {
629 res2
= std::move(response
);
633 client_read_request(socket
, req1
);
634 client_read_request(socket
, req2
);
636 // read the responses sequentially
637 HttpResponse expected_res1
;
638 expected_res1
.body() = "test";
639 client_write_response(socket
, expected_res1
);
640 ASSERT_EQ(0, ctx2
.wait());
641 ASSERT_EQ(expected_res1
, res1
);
643 HttpResponse expected_res2
;
644 expected_res2
.body() = "test";
645 client_write_response(socket
, expected_res2
);
646 ASSERT_EQ(0, ctx3
.wait());
647 ASSERT_EQ(expected_res2
, res2
);
650 http_client
.close(&ctx4
);
651 ASSERT_EQ(0, ctx4
.wait());
654 TEST_F(TestMockMigrationHttpClient
, IssuePipelinedRestart
) {
655 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
656 C_SaferCond on_connect_ctx1
;
657 client_accept(&socket
, false, &on_connect_ctx1
);
659 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
660 MockHttpClient
http_client(&mock_test_image_ctx
,
661 get_local_url(URL_SCHEME_HTTP
));
664 http_client
.open(&ctx1
);
665 ASSERT_EQ(0, on_connect_ctx1
.wait());
666 ASSERT_EQ(0, ctx1
.wait());
668 // issue two pipelined (concurrent) get requests
669 EmptyHttpRequest req1
;
670 req1
.keep_alive(false);
671 req1
.method(boost::beast::http::verb::get
);
673 C_SaferCond on_connect_ctx2
;
674 client_accept(&socket
, false, &on_connect_ctx2
);
678 http_client
.issue(EmptyHttpRequest
{req1
},
679 [&ctx2
, &res1
](int r
, HttpResponse
&& response
) mutable {
680 res1
= std::move(response
);
684 EmptyHttpRequest req2
;
685 req2
.method(boost::beast::http::verb::get
);
689 http_client
.issue(EmptyHttpRequest
{req2
},
690 [&ctx3
, &res2
](int r
, HttpResponse
&& response
) mutable {
691 res2
= std::move(response
);
695 client_read_request(socket
, req1
);
696 client_read_request(socket
, req2
);
698 // read the responses sequentially
699 HttpResponse expected_res1
;
700 expected_res1
.body() = "test";
701 expected_res1
.keep_alive(false);
702 client_write_response(socket
, expected_res1
);
703 ASSERT_EQ(0, ctx2
.wait());
704 ASSERT_EQ(expected_res1
, res1
);
706 // second request will need to be re-sent due to 'need_eof' condition
707 ASSERT_EQ(0, on_connect_ctx2
.wait());
708 client_read_request(socket
, req2
);
710 HttpResponse expected_res2
;
711 expected_res2
.body() = "test";
712 client_write_response(socket
, expected_res2
);
713 ASSERT_EQ(0, ctx3
.wait());
714 ASSERT_EQ(expected_res2
, res2
);
717 http_client
.close(&ctx4
);
718 ASSERT_EQ(0, ctx4
.wait());
721 TEST_F(TestMockMigrationHttpClient
, ShutdownInFlight
) {
722 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
723 C_SaferCond on_connect_ctx
;
724 client_accept(&socket
, false, &on_connect_ctx
);
726 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
727 MockHttpClient
http_client(&mock_test_image_ctx
,
728 get_local_url(URL_SCHEME_HTTP
));
731 http_client
.open(&ctx1
);
732 ASSERT_EQ(0, on_connect_ctx
.wait());
733 ASSERT_EQ(0, ctx1
.wait());
735 EmptyHttpRequest req
;
736 req
.method(boost::beast::http::verb::get
);
739 http_client
.issue(EmptyHttpRequest
{req
},
740 [&ctx2
](int r
, HttpResponse
&&) mutable {
744 client_read_request(socket
, req
);
747 http_client
.close(&ctx3
);
748 ASSERT_EQ(0, ctx3
.wait());
749 ASSERT_EQ(-ESHUTDOWN
, ctx2
.wait());
752 TEST_F(TestMockMigrationHttpClient
, GetSize
) {
753 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
754 MockHttpClient
http_client(&mock_test_image_ctx
,
755 get_local_url(URL_SCHEME_HTTP
));
757 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
758 C_SaferCond on_connect_ctx
;
759 client_accept(&socket
, false, &on_connect_ctx
);
762 http_client
.open(&ctx1
);
763 ASSERT_EQ(0, on_connect_ctx
.wait());
764 ASSERT_EQ(0, ctx1
.wait());
768 http_client
.get_size(&size
, &ctx2
);
770 EmptyHttpRequest expected_req
;
771 expected_req
.method(boost::beast::http::verb::head
);
772 client_read_request(socket
, expected_req
);
774 HttpResponse expected_res
;
775 expected_res
.body() = std::string(123, '1');
776 client_write_response(socket
, expected_res
);
778 ASSERT_EQ(0, ctx2
.wait());
779 ASSERT_EQ(123, size
);
782 http_client
.close(&ctx3
);
783 ASSERT_EQ(0, ctx3
.wait());
786 TEST_F(TestMockMigrationHttpClient
, GetSizeError
) {
787 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
788 MockHttpClient
http_client(&mock_test_image_ctx
,
789 get_local_url(URL_SCHEME_HTTP
));
791 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
792 C_SaferCond on_connect_ctx
;
793 client_accept(&socket
, false, &on_connect_ctx
);
796 http_client
.open(&ctx1
);
797 ASSERT_EQ(0, on_connect_ctx
.wait());
798 ASSERT_EQ(0, ctx1
.wait());
802 http_client
.get_size(&size
, &ctx2
);
804 EmptyHttpRequest expected_req
;
805 expected_req
.method(boost::beast::http::verb::head
);
806 client_read_request(socket
, expected_req
);
808 HttpResponse expected_res
;
809 expected_res
.result(boost::beast::http::status::internal_server_error
);
810 client_write_response(socket
, expected_res
);
812 ASSERT_EQ(-EIO
, ctx2
.wait());
815 http_client
.close(&ctx3
);
816 ASSERT_EQ(0, ctx3
.wait());
819 TEST_F(TestMockMigrationHttpClient
, Read
) {
820 MockTestImageCtx
mock_test_image_ctx(*m_image_ctx
);
821 MockHttpClient
http_client(&mock_test_image_ctx
,
822 get_local_url(URL_SCHEME_HTTP
));
824 boost::asio::ip::tcp::socket
socket(*m_image_ctx
->asio_engine
);
825 C_SaferCond on_connect_ctx
;
826 client_accept(&socket
, false, &on_connect_ctx
);
829 http_client
.open(&ctx1
);
830 ASSERT_EQ(0, on_connect_ctx
.wait());
831 ASSERT_EQ(0, ctx1
.wait());
835 http_client
.read({{0, 128}, {256, 64}}, &bl
, &ctx2
);
837 EmptyHttpRequest expected_req1
;
838 expected_req1
.method(boost::beast::http::verb::get
);
839 expected_req1
.set(boost::beast::http::field::range
, "bytes=0-127");
840 client_read_request(socket
, expected_req1
);
842 EmptyHttpRequest expected_req2
;
843 expected_req2
.method(boost::beast::http::verb::get
);
844 expected_req2
.set(boost::beast::http::field::range
, "bytes=256-319");
845 client_read_request(socket
, expected_req2
);
847 HttpResponse expected_res1
;
848 expected_res1
.result(boost::beast::http::status::partial_content
);
849 expected_res1
.body() = std::string(128, '1');
850 client_write_response(socket
, expected_res1
);
852 HttpResponse expected_res2
;
853 expected_res2
.result(boost::beast::http::status::partial_content
);
854 expected_res2
.body() = std::string(64, '2');
855 client_write_response(socket
, expected_res2
);
857 ASSERT_EQ(192, ctx2
.wait());
859 bufferlist expect_bl
;
860 expect_bl
.append(std::string(128, '1'));
861 expect_bl
.append(std::string(64, '2'));
862 ASSERT_EQ(expect_bl
, bl
);
865 http_client
.close(&ctx3
);
866 ASSERT_EQ(0, ctx3
.wait());
869 } // namespace migration
870 } // namespace librbd