]>
Commit | Line | Data |
---|---|---|
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 | ||
16 | namespace librbd { | |
17 | namespace { | |
18 | ||
19 | struct MockTestImageCtx : public MockImageCtx { | |
20 | MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { | |
21 | } | |
22 | }; | |
23 | ||
24 | } // anonymous namespace | |
25 | ||
26 | namespace util { | |
27 | ||
28 | inline 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 | ||
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>; | |
41 | ||
42 | namespace boost { | |
43 | namespace beast { | |
44 | namespace http { | |
45 | ||
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()); | |
51 | } | |
52 | ||
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()); | |
58 | } | |
59 | ||
60 | } // namespace http | |
61 | } // namespace beast | |
62 | } // namespace boost | |
63 | ||
64 | namespace librbd { | |
65 | namespace migration { | |
66 | ||
67 | using ::testing::Invoke; | |
68 | ||
69 | class TestMockMigrationHttpClient : public TestMockFixture { | |
70 | public: | |
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 | ||
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); | |
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 | ||
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); | |
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 | ||
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); | |
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 | ||
353 | TEST_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 | ||
362 | TEST_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 | ||
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/"); | |
375 | ||
376 | C_SaferCond ctx1; | |
377 | http_client.open(&ctx1); | |
378 | ASSERT_EQ(-ECONNREFUSED, ctx1.wait()); | |
379 | } | |
380 | ||
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); | |
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 | ||
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); | |
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 | ||
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); | |
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 | ||
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); | |
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 | ||
547 | TEST_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 | ||
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); | |
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 | ||
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); | |
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 | ||
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); | |
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 | ||
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)); | |
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 | ||
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)); | |
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 | ||
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)); | |
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 |