]> git.proxmox.com Git - ceph.git/blame - ceph/src/test/librbd/migration/test_mock_HttpClient.cc
import ceph quincy 17.2.4
[ceph.git] / ceph / src / test / librbd / migration / test_mock_HttpClient.cc
CommitLineData
f67539c2
TL
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3
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"
2a845540 11#include <unistd.h>
f67539c2
TL
12#include <boost/asio/ip/tcp.hpp>
13#include <boost/beast/core.hpp>
14#include <boost/beast/http.hpp>
15
16namespace librbd {
17namespace {
18
19struct MockTestImageCtx : public MockImageCtx {
20 MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) {
21 }
22};
23
24} // anonymous namespace
25
26namespace util {
27
28inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) {
29 return image_ctx->image_ctx;
30}
31
32} // namespace util
33} // namespace librbd
34
35#include "librbd/migration/HttpClient.cc"
36
37using EmptyHttpRequest = boost::beast::http::request<
38 boost::beast::http::empty_body>;
39using HttpResponse = boost::beast::http::response<
40 boost::beast::http::string_body>;
41
42namespace boost {
43namespace beast {
44namespace http {
45
46template <typename Body>
47bool 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());
51}
52
53template <typename Body>
54bool 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());
58}
59
60} // namespace http
61} // namespace beast
62} // namespace boost
63
64namespace librbd {
65namespace migration {
66
67using ::testing::Invoke;
68
69class TestMockMigrationHttpClient : public TestMockFixture {
70public:
71 typedef HttpClient<MockTestImageCtx> MockHttpClient;
72
73 void SetUp() override {
74 TestMockFixture::SetUp();
75
76 ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx));
77
78 create_acceptor(false);
79 }
80
81 void TearDown() override {
82 m_acceptor.reset();
83
84 TestMockFixture::TearDown();
85 }
86
2a845540
TL
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;
90
f67539c2 91 void create_acceptor(bool reuse) {
2a845540
TL
92 for (int retries = 0;; retries++) {
93 try {
94 m_acceptor.emplace(*m_image_ctx->asio_engine,
f67539c2
TL
95 boost::asio::ip::tcp::endpoint(
96 boost::asio::ip::tcp::v4(), m_server_port), reuse);
2a845540
TL
97 // yay!
98 break;
99 } catch (const boost::system::system_error& e) {
100 if (retries == MAX_BIND_RETRIES) {
101 throw;
102 }
103 if (e.code() != boost::system::errc::address_in_use) {
104 throw;
105 }
106 }
107 // backoff a little bit
108 sleep(1);
109 }
f67539c2
TL
110 m_server_port = m_acceptor->local_endpoint().port();
111 }
112
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";
118 break;
119 case URL_SCHEME_HTTPS:
120 sstream << "https://localhost";
121 break;
122 default:
123 ceph_assert(false);
124 break;
125 }
126
127 sstream << ":" << m_server_port << "/target";
128 return sstream.str();
129 }
130
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) {
137 if (close) {
138 in_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
139 } else {
140 ASSERT_FALSE(ec) << "Unexpected error: " << ec;
141 *socket = std::move(in_socket);
142 }
143 on_connect->complete(0);
144 });
145 }
146
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;
154
155 expected_req.target("/target");
156 ASSERT_EQ(expected_req, req);
157 }
158
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();
166
167 boost::beast::error_code ec;
168 boost::beast::http::write(socket, expected_res, ec);
169 ASSERT_FALSE(ec) << "Unexpected errror: " << ec;
170 }
171
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());
180 });
181 }
182
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());
189 });
190 }
191
192 void load_server_certificate(boost::asio::ssl::context& ctx) {
193 ctx.set_options(
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()));
199 ctx.use_private_key(
200 boost::asio::buffer(KEY.data(), KEY.size()),
201 boost::asio::ssl::context::file_format::pem);
202 ctx.use_tmp_dh(
203 boost::asio::buffer(DH.data(), DH.size()));
204 }
205
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"
227 "JIDj\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";
267
268 librbd::ImageCtx *m_image_ctx;
269
270 std::optional<boost::asio::ip::tcp::acceptor> m_acceptor;
271 boost::beast::flat_buffer m_buffer;
272 uint64_t m_server_port = 0;
273};
274
275TEST_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);
279
280 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
281 MockHttpClient http_client(&mock_test_image_ctx,
282 get_local_url(URL_SCHEME_HTTP));
283
284 C_SaferCond ctx1;
285 http_client.open(&ctx1);
286 ASSERT_EQ(0, on_connect_ctx.wait());
287 ASSERT_EQ(0, ctx1.wait());
288
289 C_SaferCond ctx2;
290 http_client.close(&ctx2);
291 ASSERT_EQ(0, ctx2.wait());
292}
293
294TEST_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);
298
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);
303
304 C_SaferCond ctx1;
305 http_client.open(&ctx1);
306 ASSERT_EQ(0, on_connect_ctx.wait());
307
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};
312
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());
316
317 ASSERT_EQ(0, ctx1.wait());
318
319 C_SaferCond ctx2;
320 http_client.close(&ctx2);
321
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());
325
326 ASSERT_EQ(0, ctx2.wait());
327}
328
329TEST_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);
333
334 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
335 MockHttpClient http_client(&mock_test_image_ctx,
336 get_local_url(URL_SCHEME_HTTPS));
337
338 C_SaferCond ctx1;
339 http_client.open(&ctx1);
340 ASSERT_EQ(0, on_connect_ctx.wait());
341
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};
346
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());
351}
352
353TEST_F(TestMockMigrationHttpClient, OpenInvalidUrl) {
354 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
355 MockHttpClient http_client(&mock_test_image_ctx, "ftp://nope/");
356
357 C_SaferCond ctx;
358 http_client.open(&ctx);
359 ASSERT_EQ(-EINVAL, ctx.wait());
360}
361
362TEST_F(TestMockMigrationHttpClient, OpenResolveFail) {
363 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
b3b6e05e 364 MockHttpClient http_client(&mock_test_image_ctx, "http://foo.example");
f67539c2
TL
365
366 C_SaferCond ctx;
367 http_client.open(&ctx);
368 ASSERT_EQ(-ENOENT, ctx.wait());
369}
370
371TEST_F(TestMockMigrationHttpClient, OpenConnectFail) {
372 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
373 MockHttpClient http_client(&mock_test_image_ctx,
374 "http://localhost:2/");
375
376 C_SaferCond ctx1;
377 http_client.open(&ctx1);
378 ASSERT_EQ(-ECONNREFUSED, ctx1.wait());
379}
380
381TEST_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);
385
386 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
387 MockHttpClient http_client(&mock_test_image_ctx,
388 get_local_url(URL_SCHEME_HTTP));
389
390 C_SaferCond ctx1;
391 http_client.open(&ctx1);
392 ASSERT_EQ(0, on_connect_ctx.wait());
393 ASSERT_EQ(0, ctx1.wait());
394
395 EmptyHttpRequest req;
396 req.method(boost::beast::http::verb::head);
397
398 C_SaferCond ctx2;
399 HttpResponse res;
400 http_client.issue(EmptyHttpRequest{req},
401 [&ctx2, &res](int r, HttpResponse&& response) mutable {
402 res = std::move(response);
403 ctx2.complete(r);
404 });
405
406 HttpResponse expected_res;
407 client_read_request(socket, req);
408 client_write_response(socket, expected_res);
409
410 ASSERT_EQ(0, ctx2.wait());
411 ASSERT_EQ(expected_res, res);
412
413 C_SaferCond ctx3;
414 http_client.close(&ctx3);
415 ASSERT_EQ(0, ctx3.wait());
416}
417
418TEST_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);
422
423 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
424 MockHttpClient http_client(&mock_test_image_ctx,
425 get_local_url(URL_SCHEME_HTTP));
426
427 C_SaferCond ctx1;
428 http_client.open(&ctx1);
429 ASSERT_EQ(0, on_connect_ctx.wait());
430 ASSERT_EQ(0, ctx1.wait());
431
432 EmptyHttpRequest req;
433 req.method(boost::beast::http::verb::get);
434
435 C_SaferCond ctx2;
436 HttpResponse res;
437 http_client.issue(EmptyHttpRequest{req},
438 [&ctx2, &res](int r, HttpResponse&& response) mutable {
439 res = std::move(response);
440 ctx2.complete(r);
441 });
442
443 HttpResponse expected_res;
444 expected_res.body() = "test";
445 client_read_request(socket, req);
446 client_write_response(socket, expected_res);
447
448 ASSERT_EQ(0, ctx2.wait());
449 ASSERT_EQ(expected_res, res);
450
451 C_SaferCond ctx3;
452 http_client.close(&ctx3);
453 ASSERT_EQ(0, ctx3.wait());
454}
455
456TEST_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);
460
461 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
462 MockHttpClient http_client(&mock_test_image_ctx,
463 get_local_url(URL_SCHEME_HTTP));
464
465 C_SaferCond ctx1;
466 http_client.open(&ctx1);
467 ASSERT_EQ(0, on_connect_ctx1.wait());
468 ASSERT_EQ(0, ctx1.wait());
469
470 // close connection to client
471 boost::system::error_code ec;
472 socket.close(ec);
473
474 C_SaferCond on_connect_ctx2;
475 client_accept(&socket, false, &on_connect_ctx2);
476
477 // send request via closed connection
478 EmptyHttpRequest req;
479 req.method(boost::beast::http::verb::get);
480
481 C_SaferCond ctx2;
482 http_client.issue(EmptyHttpRequest{req},
483 [&ctx2](int r, HttpResponse&&) mutable {
484 ctx2.complete(r);
485 });
486
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());
494
495 C_SaferCond ctx3;
496 http_client.close(&ctx3);
497 ASSERT_EQ(0, ctx3.wait());
498}
499
500TEST_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);
504
505 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
506 MockHttpClient http_client(&mock_test_image_ctx,
507 get_local_url(URL_SCHEME_HTTP));
508
509 C_SaferCond ctx1;
510 http_client.open(&ctx1);
511 ASSERT_EQ(0, on_connect_ctx1.wait());
512 ASSERT_EQ(0, ctx1.wait());
513
514 // send request via closed connection
515 EmptyHttpRequest req;
516 req.method(boost::beast::http::verb::get);
517
518 C_SaferCond ctx2;
519 http_client.issue(EmptyHttpRequest{req},
520 [&ctx2](int r, HttpResponse&&) mutable {
521 ctx2.complete(r);
522 });
523
524 // close connection to client after reading request
525 client_read_request(socket1, req);
526
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);
530
531 boost::system::error_code ec;
532 socket1.close(ec);
533 ASSERT_EQ(0, on_connect_ctx2.wait());
534
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());
541
542 C_SaferCond ctx3;
543 http_client.close(&ctx3);
544 ASSERT_EQ(0, ctx3.wait());
545}
546
547TEST_F(TestMockMigrationHttpClient, IssueResetFailed) {
548 m_server_port = 0;
549 create_acceptor(true);
550
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);
554
555 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
556 MockHttpClient http_client(&mock_test_image_ctx,
557 get_local_url(URL_SCHEME_HTTP));
558
559 C_SaferCond ctx1;
560 http_client.open(&ctx1);
561 ASSERT_EQ(0, on_connect_ctx1.wait());
562 ASSERT_EQ(0, ctx1.wait());
563
564 // send requests then close connection
565 EmptyHttpRequest req;
566 req.method(boost::beast::http::verb::get);
567
568 C_SaferCond ctx2;
569 http_client.issue(EmptyHttpRequest{req},
570 [&ctx2](int r, HttpResponse&&) mutable {
571 ctx2.complete(r);
572 });
573
574 C_SaferCond ctx3;
575 http_client.issue(EmptyHttpRequest{req},
576 [&ctx3](int r, HttpResponse&&) mutable {
577 ctx3.complete(r);
578 });
579
580 client_read_request(socket, req);
581 client_read_request(socket, req);
582
583 // close connection to client and verify requests are failed
584 m_acceptor.reset();
585 boost::system::error_code ec;
586 socket.close(ec);
587
588 ASSERT_EQ(-ECONNREFUSED, ctx2.wait());
589 ASSERT_EQ(-ECONNREFUSED, ctx3.wait());
590
591 // additional request will retry the failed connection
592 create_acceptor(true);
593
594 C_SaferCond on_connect_ctx2;
595 client_accept(&socket, false, &on_connect_ctx2);
596
597 C_SaferCond ctx4;
598 http_client.issue(EmptyHttpRequest{req},
599 [&ctx4](int r, HttpResponse&&) mutable {
600 ctx4.complete(r);
601 });
602
603 ASSERT_EQ(0, on_connect_ctx2.wait());
604 client_read_request(socket, req);
605
606 HttpResponse expected_res;
607 expected_res.body() = "test";
608 client_write_response(socket, expected_res);
609 ASSERT_EQ(0, ctx4.wait());
610
611 C_SaferCond ctx5;
612 http_client.close(&ctx5);
613 ASSERT_EQ(0, ctx5.wait());
614}
615
616TEST_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);
620
621 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
622 MockHttpClient http_client(&mock_test_image_ctx,
623 get_local_url(URL_SCHEME_HTTP));
624
625 C_SaferCond ctx1;
626 http_client.open(&ctx1);
627 ASSERT_EQ(0, on_connect_ctx.wait());
628 ASSERT_EQ(0, ctx1.wait());
629
630 // issue two pipelined (concurrent) get requests
631 EmptyHttpRequest req1;
632 req1.method(boost::beast::http::verb::get);
633
634 C_SaferCond ctx2;
635 HttpResponse res1;
636 http_client.issue(EmptyHttpRequest{req1},
637 [&ctx2, &res1](int r, HttpResponse&& response) mutable {
638 res1 = std::move(response);
639 ctx2.complete(r);
640 });
641
642 EmptyHttpRequest req2;
643 req2.method(boost::beast::http::verb::get);
644
645 C_SaferCond ctx3;
646 HttpResponse res2;
647 http_client.issue(EmptyHttpRequest{req2},
648 [&ctx3, &res2](int r, HttpResponse&& response) mutable {
649 res2 = std::move(response);
650 ctx3.complete(r);
651 });
652
653 client_read_request(socket, req1);
654 client_read_request(socket, req2);
655
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);
662
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);
668
669 C_SaferCond ctx4;
670 http_client.close(&ctx4);
671 ASSERT_EQ(0, ctx4.wait());
672}
673
674TEST_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);
678
679 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
680 MockHttpClient http_client(&mock_test_image_ctx,
681 get_local_url(URL_SCHEME_HTTP));
682
683 C_SaferCond ctx1;
684 http_client.open(&ctx1);
685 ASSERT_EQ(0, on_connect_ctx1.wait());
686 ASSERT_EQ(0, ctx1.wait());
687
688 // issue two pipelined (concurrent) get requests
689 EmptyHttpRequest req1;
690 req1.keep_alive(false);
691 req1.method(boost::beast::http::verb::get);
692
693 C_SaferCond on_connect_ctx2;
694 client_accept(&socket, false, &on_connect_ctx2);
695
696 C_SaferCond ctx2;
697 HttpResponse res1;
698 http_client.issue(EmptyHttpRequest{req1},
699 [&ctx2, &res1](int r, HttpResponse&& response) mutable {
700 res1 = std::move(response);
701 ctx2.complete(r);
702 });
703
704 EmptyHttpRequest req2;
705 req2.method(boost::beast::http::verb::get);
706
707 C_SaferCond ctx3;
708 HttpResponse res2;
709 http_client.issue(EmptyHttpRequest{req2},
710 [&ctx3, &res2](int r, HttpResponse&& response) mutable {
711 res2 = std::move(response);
712 ctx3.complete(r);
713 });
714
715 client_read_request(socket, req1);
716 client_read_request(socket, req2);
717
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);
725
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);
729
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);
735
736 C_SaferCond ctx4;
737 http_client.close(&ctx4);
738 ASSERT_EQ(0, ctx4.wait());
739}
740
741TEST_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);
745
746 MockTestImageCtx mock_test_image_ctx(*m_image_ctx);
747 MockHttpClient http_client(&mock_test_image_ctx,
748 get_local_url(URL_SCHEME_HTTP));
749
750 C_SaferCond ctx1;
751 http_client.open(&ctx1);
752 ASSERT_EQ(0, on_connect_ctx.wait());
753 ASSERT_EQ(0, ctx1.wait());
754
755 EmptyHttpRequest req;
756 req.method(boost::beast::http::verb::get);
757
758 C_SaferCond ctx2;
759 http_client.issue(EmptyHttpRequest{req},
760 [&ctx2](int r, HttpResponse&&) mutable {
761 ctx2.complete(r);
762 });
763
764 client_read_request(socket, req);
765
766 C_SaferCond ctx3;
767 http_client.close(&ctx3);
768 ASSERT_EQ(0, ctx3.wait());
769 ASSERT_EQ(-ESHUTDOWN, ctx2.wait());
770}
771
772TEST_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));
776
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);
780
781 C_SaferCond ctx1;
782 http_client.open(&ctx1);
783 ASSERT_EQ(0, on_connect_ctx.wait());
784 ASSERT_EQ(0, ctx1.wait());
785
786 uint64_t size = 0;
787 C_SaferCond ctx2;
788 http_client.get_size(&size, &ctx2);
789
790 EmptyHttpRequest expected_req;
791 expected_req.method(boost::beast::http::verb::head);
792 client_read_request(socket, expected_req);
793
794 HttpResponse expected_res;
795 expected_res.body() = std::string(123, '1');
796 client_write_response(socket, expected_res);
797
798 ASSERT_EQ(0, ctx2.wait());
799 ASSERT_EQ(123, size);
800
801 C_SaferCond ctx3;
802 http_client.close(&ctx3);
803 ASSERT_EQ(0, ctx3.wait());
804}
805
806TEST_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));
810
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);
814
815 C_SaferCond ctx1;
816 http_client.open(&ctx1);
817 ASSERT_EQ(0, on_connect_ctx.wait());
818 ASSERT_EQ(0, ctx1.wait());
819
820 uint64_t size = 0;
821 C_SaferCond ctx2;
822 http_client.get_size(&size, &ctx2);
823
824 EmptyHttpRequest expected_req;
825 expected_req.method(boost::beast::http::verb::head);
826 client_read_request(socket, expected_req);
827
828 HttpResponse expected_res;
829 expected_res.result(boost::beast::http::status::internal_server_error);
830 client_write_response(socket, expected_res);
831
832 ASSERT_EQ(-EIO, ctx2.wait());
833
834 C_SaferCond ctx3;
835 http_client.close(&ctx3);
836 ASSERT_EQ(0, ctx3.wait());
837}
838
839TEST_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));
843
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);
847
848 C_SaferCond ctx1;
849 http_client.open(&ctx1);
850 ASSERT_EQ(0, on_connect_ctx.wait());
851 ASSERT_EQ(0, ctx1.wait());
852
853 bufferlist bl;
854 C_SaferCond ctx2;
855 http_client.read({{0, 128}, {256, 64}}, &bl, &ctx2);
856
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);
861
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);
866
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);
871
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);
876
877 ASSERT_EQ(192, ctx2.wait());
878
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);
883
884 C_SaferCond ctx3;
885 http_client.close(&ctx3);
886 ASSERT_EQ(0, ctx3.wait());
887}
888
889} // namespace migration
890} // namespace librbd