]>
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" | |
11 | #include <boost/asio/ip/tcp.hpp> | |
12 | #include <boost/beast/core.hpp> | |
13 | #include <boost/beast/http.hpp> | |
14 | ||
15 | namespace librbd { | |
16 | namespace { | |
17 | ||
18 | struct MockTestImageCtx : public MockImageCtx { | |
19 | MockTestImageCtx(ImageCtx &image_ctx) : MockImageCtx(image_ctx) { | |
20 | } | |
21 | }; | |
22 | ||
23 | } // anonymous namespace | |
24 | ||
25 | namespace util { | |
26 | ||
27 | inline ImageCtx *get_image_ctx(MockTestImageCtx *image_ctx) { | |
28 | return image_ctx->image_ctx; | |
29 | } | |
30 | ||
31 | } // namespace util | |
32 | } // namespace librbd | |
33 | ||
34 | #include "librbd/migration/HttpClient.cc" | |
35 | ||
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>; | |
40 | ||
41 | namespace boost { | |
42 | namespace beast { | |
43 | namespace http { | |
44 | ||
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()); | |
50 | } | |
51 | ||
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()); | |
57 | } | |
58 | ||
59 | } // namespace http | |
60 | } // namespace beast | |
61 | } // namespace boost | |
62 | ||
63 | namespace librbd { | |
64 | namespace migration { | |
65 | ||
66 | using ::testing::Invoke; | |
67 | ||
68 | class TestMockMigrationHttpClient : public TestMockFixture { | |
69 | public: | |
70 | typedef HttpClient<MockTestImageCtx> MockHttpClient; | |
71 | ||
72 | void SetUp() override { | |
73 | TestMockFixture::SetUp(); | |
74 | ||
75 | ASSERT_EQ(0, open_image(m_image_name, &m_image_ctx)); | |
76 | ||
77 | create_acceptor(false); | |
78 | } | |
79 | ||
80 | void TearDown() override { | |
81 | m_acceptor.reset(); | |
82 | ||
83 | TestMockFixture::TearDown(); | |
84 | } | |
85 | ||
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(); | |
91 | } | |
92 | ||
93 | std::string get_local_url(UrlScheme url_scheme) { | |
94 | std::stringstream sstream; | |
95 | switch (url_scheme) { | |
96 | case URL_SCHEME_HTTP: | |
97 | sstream << "http://127.0.0.1"; | |
98 | break; | |
99 | case URL_SCHEME_HTTPS: | |
100 | sstream << "https://localhost"; | |
101 | break; | |
102 | default: | |
103 | ceph_assert(false); | |
104 | break; | |
105 | } | |
106 | ||
107 | sstream << ":" << m_server_port << "/target"; | |
108 | return sstream.str(); | |
109 | } | |
110 | ||
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) { | |
117 | if (close) { | |
118 | in_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both); | |
119 | } else { | |
120 | ASSERT_FALSE(ec) << "Unexpected error: " << ec; | |
121 | *socket = std::move(in_socket); | |
122 | } | |
123 | on_connect->complete(0); | |
124 | }); | |
125 | } | |
126 | ||
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; | |
134 | ||
135 | expected_req.target("/target"); | |
136 | ASSERT_EQ(expected_req, req); | |
137 | } | |
138 | ||
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(); | |
146 | ||
147 | boost::beast::error_code ec; | |
148 | boost::beast::http::write(socket, expected_res, ec); | |
149 | ASSERT_FALSE(ec) << "Unexpected errror: " << ec; | |
150 | } | |
151 | ||
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()); | |
160 | }); | |
161 | } | |
162 | ||
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()); | |
169 | }); | |
170 | } | |
171 | ||
172 | void load_server_certificate(boost::asio::ssl::context& ctx) { | |
173 | ctx.set_options( | |
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())); | |
179 | ctx.use_private_key( | |
180 | boost::asio::buffer(KEY.data(), KEY.size()), | |
181 | boost::asio::ssl::context::file_format::pem); | |
182 | ctx.use_tmp_dh( | |
183 | boost::asio::buffer(DH.data(), DH.size())); | |
184 | } | |
185 | ||
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" | |
207 | "JIDj\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"; | |
247 | ||
248 | librbd::ImageCtx *m_image_ctx; | |
249 | ||
250 | std::optional<boost::asio::ip::tcp::acceptor> m_acceptor; | |
251 | boost::beast::flat_buffer m_buffer; | |
252 | uint64_t m_server_port = 0; | |
253 | }; | |
254 | ||
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); | |
259 | ||
260 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
261 | MockHttpClient http_client(&mock_test_image_ctx, | |
262 | get_local_url(URL_SCHEME_HTTP)); | |
263 | ||
264 | C_SaferCond ctx1; | |
265 | http_client.open(&ctx1); | |
266 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
267 | ASSERT_EQ(0, ctx1.wait()); | |
268 | ||
269 | C_SaferCond ctx2; | |
270 | http_client.close(&ctx2); | |
271 | ASSERT_EQ(0, ctx2.wait()); | |
272 | } | |
273 | ||
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); | |
278 | ||
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); | |
283 | ||
284 | C_SaferCond ctx1; | |
285 | http_client.open(&ctx1); | |
286 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
287 | ||
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}; | |
292 | ||
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()); | |
296 | ||
297 | ASSERT_EQ(0, ctx1.wait()); | |
298 | ||
299 | C_SaferCond ctx2; | |
300 | http_client.close(&ctx2); | |
301 | ||
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()); | |
305 | ||
306 | ASSERT_EQ(0, ctx2.wait()); | |
307 | } | |
308 | ||
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); | |
313 | ||
314 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
315 | MockHttpClient http_client(&mock_test_image_ctx, | |
316 | get_local_url(URL_SCHEME_HTTPS)); | |
317 | ||
318 | C_SaferCond ctx1; | |
319 | http_client.open(&ctx1); | |
320 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
321 | ||
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}; | |
326 | ||
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()); | |
331 | } | |
332 | ||
333 | TEST_F(TestMockMigrationHttpClient, OpenInvalidUrl) { | |
334 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
335 | MockHttpClient http_client(&mock_test_image_ctx, "ftp://nope/"); | |
336 | ||
337 | C_SaferCond ctx; | |
338 | http_client.open(&ctx); | |
339 | ASSERT_EQ(-EINVAL, ctx.wait()); | |
340 | } | |
341 | ||
342 | TEST_F(TestMockMigrationHttpClient, OpenResolveFail) { | |
343 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
b3b6e05e | 344 | MockHttpClient http_client(&mock_test_image_ctx, "http://foo.example"); |
f67539c2 TL |
345 | |
346 | C_SaferCond ctx; | |
347 | http_client.open(&ctx); | |
348 | ASSERT_EQ(-ENOENT, ctx.wait()); | |
349 | } | |
350 | ||
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/"); | |
355 | ||
356 | C_SaferCond ctx1; | |
357 | http_client.open(&ctx1); | |
358 | ASSERT_EQ(-ECONNREFUSED, ctx1.wait()); | |
359 | } | |
360 | ||
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); | |
365 | ||
366 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
367 | MockHttpClient http_client(&mock_test_image_ctx, | |
368 | get_local_url(URL_SCHEME_HTTP)); | |
369 | ||
370 | C_SaferCond ctx1; | |
371 | http_client.open(&ctx1); | |
372 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
373 | ASSERT_EQ(0, ctx1.wait()); | |
374 | ||
375 | EmptyHttpRequest req; | |
376 | req.method(boost::beast::http::verb::head); | |
377 | ||
378 | C_SaferCond ctx2; | |
379 | HttpResponse res; | |
380 | http_client.issue(EmptyHttpRequest{req}, | |
381 | [&ctx2, &res](int r, HttpResponse&& response) mutable { | |
382 | res = std::move(response); | |
383 | ctx2.complete(r); | |
384 | }); | |
385 | ||
386 | HttpResponse expected_res; | |
387 | client_read_request(socket, req); | |
388 | client_write_response(socket, expected_res); | |
389 | ||
390 | ASSERT_EQ(0, ctx2.wait()); | |
391 | ASSERT_EQ(expected_res, res); | |
392 | ||
393 | C_SaferCond ctx3; | |
394 | http_client.close(&ctx3); | |
395 | ASSERT_EQ(0, ctx3.wait()); | |
396 | } | |
397 | ||
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); | |
402 | ||
403 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
404 | MockHttpClient http_client(&mock_test_image_ctx, | |
405 | get_local_url(URL_SCHEME_HTTP)); | |
406 | ||
407 | C_SaferCond ctx1; | |
408 | http_client.open(&ctx1); | |
409 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
410 | ASSERT_EQ(0, ctx1.wait()); | |
411 | ||
412 | EmptyHttpRequest req; | |
413 | req.method(boost::beast::http::verb::get); | |
414 | ||
415 | C_SaferCond ctx2; | |
416 | HttpResponse res; | |
417 | http_client.issue(EmptyHttpRequest{req}, | |
418 | [&ctx2, &res](int r, HttpResponse&& response) mutable { | |
419 | res = std::move(response); | |
420 | ctx2.complete(r); | |
421 | }); | |
422 | ||
423 | HttpResponse expected_res; | |
424 | expected_res.body() = "test"; | |
425 | client_read_request(socket, req); | |
426 | client_write_response(socket, expected_res); | |
427 | ||
428 | ASSERT_EQ(0, ctx2.wait()); | |
429 | ASSERT_EQ(expected_res, res); | |
430 | ||
431 | C_SaferCond ctx3; | |
432 | http_client.close(&ctx3); | |
433 | ASSERT_EQ(0, ctx3.wait()); | |
434 | } | |
435 | ||
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); | |
440 | ||
441 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
442 | MockHttpClient http_client(&mock_test_image_ctx, | |
443 | get_local_url(URL_SCHEME_HTTP)); | |
444 | ||
445 | C_SaferCond ctx1; | |
446 | http_client.open(&ctx1); | |
447 | ASSERT_EQ(0, on_connect_ctx1.wait()); | |
448 | ASSERT_EQ(0, ctx1.wait()); | |
449 | ||
450 | // close connection to client | |
451 | boost::system::error_code ec; | |
452 | socket.close(ec); | |
453 | ||
454 | C_SaferCond on_connect_ctx2; | |
455 | client_accept(&socket, false, &on_connect_ctx2); | |
456 | ||
457 | // send request via closed connection | |
458 | EmptyHttpRequest req; | |
459 | req.method(boost::beast::http::verb::get); | |
460 | ||
461 | C_SaferCond ctx2; | |
462 | http_client.issue(EmptyHttpRequest{req}, | |
463 | [&ctx2](int r, HttpResponse&&) mutable { | |
464 | ctx2.complete(r); | |
465 | }); | |
466 | ||
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()); | |
474 | ||
475 | C_SaferCond ctx3; | |
476 | http_client.close(&ctx3); | |
477 | ASSERT_EQ(0, ctx3.wait()); | |
478 | } | |
479 | ||
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); | |
484 | ||
485 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
486 | MockHttpClient http_client(&mock_test_image_ctx, | |
487 | get_local_url(URL_SCHEME_HTTP)); | |
488 | ||
489 | C_SaferCond ctx1; | |
490 | http_client.open(&ctx1); | |
491 | ASSERT_EQ(0, on_connect_ctx1.wait()); | |
492 | ASSERT_EQ(0, ctx1.wait()); | |
493 | ||
494 | // send request via closed connection | |
495 | EmptyHttpRequest req; | |
496 | req.method(boost::beast::http::verb::get); | |
497 | ||
498 | C_SaferCond ctx2; | |
499 | http_client.issue(EmptyHttpRequest{req}, | |
500 | [&ctx2](int r, HttpResponse&&) mutable { | |
501 | ctx2.complete(r); | |
502 | }); | |
503 | ||
504 | // close connection to client after reading request | |
505 | client_read_request(socket1, req); | |
506 | ||
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); | |
510 | ||
511 | boost::system::error_code ec; | |
512 | socket1.close(ec); | |
513 | ASSERT_EQ(0, on_connect_ctx2.wait()); | |
514 | ||
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()); | |
521 | ||
522 | C_SaferCond ctx3; | |
523 | http_client.close(&ctx3); | |
524 | ASSERT_EQ(0, ctx3.wait()); | |
525 | } | |
526 | ||
527 | TEST_F(TestMockMigrationHttpClient, IssueResetFailed) { | |
528 | m_server_port = 0; | |
529 | create_acceptor(true); | |
530 | ||
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); | |
534 | ||
535 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
536 | MockHttpClient http_client(&mock_test_image_ctx, | |
537 | get_local_url(URL_SCHEME_HTTP)); | |
538 | ||
539 | C_SaferCond ctx1; | |
540 | http_client.open(&ctx1); | |
541 | ASSERT_EQ(0, on_connect_ctx1.wait()); | |
542 | ASSERT_EQ(0, ctx1.wait()); | |
543 | ||
544 | // send requests then close connection | |
545 | EmptyHttpRequest req; | |
546 | req.method(boost::beast::http::verb::get); | |
547 | ||
548 | C_SaferCond ctx2; | |
549 | http_client.issue(EmptyHttpRequest{req}, | |
550 | [&ctx2](int r, HttpResponse&&) mutable { | |
551 | ctx2.complete(r); | |
552 | }); | |
553 | ||
554 | C_SaferCond ctx3; | |
555 | http_client.issue(EmptyHttpRequest{req}, | |
556 | [&ctx3](int r, HttpResponse&&) mutable { | |
557 | ctx3.complete(r); | |
558 | }); | |
559 | ||
560 | client_read_request(socket, req); | |
561 | client_read_request(socket, req); | |
562 | ||
563 | // close connection to client and verify requests are failed | |
564 | m_acceptor.reset(); | |
565 | boost::system::error_code ec; | |
566 | socket.close(ec); | |
567 | ||
568 | ASSERT_EQ(-ECONNREFUSED, ctx2.wait()); | |
569 | ASSERT_EQ(-ECONNREFUSED, ctx3.wait()); | |
570 | ||
571 | // additional request will retry the failed connection | |
572 | create_acceptor(true); | |
573 | ||
574 | C_SaferCond on_connect_ctx2; | |
575 | client_accept(&socket, false, &on_connect_ctx2); | |
576 | ||
577 | C_SaferCond ctx4; | |
578 | http_client.issue(EmptyHttpRequest{req}, | |
579 | [&ctx4](int r, HttpResponse&&) mutable { | |
580 | ctx4.complete(r); | |
581 | }); | |
582 | ||
583 | ASSERT_EQ(0, on_connect_ctx2.wait()); | |
584 | client_read_request(socket, req); | |
585 | ||
586 | HttpResponse expected_res; | |
587 | expected_res.body() = "test"; | |
588 | client_write_response(socket, expected_res); | |
589 | ASSERT_EQ(0, ctx4.wait()); | |
590 | ||
591 | C_SaferCond ctx5; | |
592 | http_client.close(&ctx5); | |
593 | ASSERT_EQ(0, ctx5.wait()); | |
594 | } | |
595 | ||
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); | |
600 | ||
601 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
602 | MockHttpClient http_client(&mock_test_image_ctx, | |
603 | get_local_url(URL_SCHEME_HTTP)); | |
604 | ||
605 | C_SaferCond ctx1; | |
606 | http_client.open(&ctx1); | |
607 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
608 | ASSERT_EQ(0, ctx1.wait()); | |
609 | ||
610 | // issue two pipelined (concurrent) get requests | |
611 | EmptyHttpRequest req1; | |
612 | req1.method(boost::beast::http::verb::get); | |
613 | ||
614 | C_SaferCond ctx2; | |
615 | HttpResponse res1; | |
616 | http_client.issue(EmptyHttpRequest{req1}, | |
617 | [&ctx2, &res1](int r, HttpResponse&& response) mutable { | |
618 | res1 = std::move(response); | |
619 | ctx2.complete(r); | |
620 | }); | |
621 | ||
622 | EmptyHttpRequest req2; | |
623 | req2.method(boost::beast::http::verb::get); | |
624 | ||
625 | C_SaferCond ctx3; | |
626 | HttpResponse res2; | |
627 | http_client.issue(EmptyHttpRequest{req2}, | |
628 | [&ctx3, &res2](int r, HttpResponse&& response) mutable { | |
629 | res2 = std::move(response); | |
630 | ctx3.complete(r); | |
631 | }); | |
632 | ||
633 | client_read_request(socket, req1); | |
634 | client_read_request(socket, req2); | |
635 | ||
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); | |
642 | ||
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); | |
648 | ||
649 | C_SaferCond ctx4; | |
650 | http_client.close(&ctx4); | |
651 | ASSERT_EQ(0, ctx4.wait()); | |
652 | } | |
653 | ||
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); | |
658 | ||
659 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
660 | MockHttpClient http_client(&mock_test_image_ctx, | |
661 | get_local_url(URL_SCHEME_HTTP)); | |
662 | ||
663 | C_SaferCond ctx1; | |
664 | http_client.open(&ctx1); | |
665 | ASSERT_EQ(0, on_connect_ctx1.wait()); | |
666 | ASSERT_EQ(0, ctx1.wait()); | |
667 | ||
668 | // issue two pipelined (concurrent) get requests | |
669 | EmptyHttpRequest req1; | |
670 | req1.keep_alive(false); | |
671 | req1.method(boost::beast::http::verb::get); | |
672 | ||
673 | C_SaferCond on_connect_ctx2; | |
674 | client_accept(&socket, false, &on_connect_ctx2); | |
675 | ||
676 | C_SaferCond ctx2; | |
677 | HttpResponse res1; | |
678 | http_client.issue(EmptyHttpRequest{req1}, | |
679 | [&ctx2, &res1](int r, HttpResponse&& response) mutable { | |
680 | res1 = std::move(response); | |
681 | ctx2.complete(r); | |
682 | }); | |
683 | ||
684 | EmptyHttpRequest req2; | |
685 | req2.method(boost::beast::http::verb::get); | |
686 | ||
687 | C_SaferCond ctx3; | |
688 | HttpResponse res2; | |
689 | http_client.issue(EmptyHttpRequest{req2}, | |
690 | [&ctx3, &res2](int r, HttpResponse&& response) mutable { | |
691 | res2 = std::move(response); | |
692 | ctx3.complete(r); | |
693 | }); | |
694 | ||
695 | client_read_request(socket, req1); | |
696 | client_read_request(socket, req2); | |
697 | ||
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); | |
705 | ||
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); | |
709 | ||
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); | |
715 | ||
716 | C_SaferCond ctx4; | |
717 | http_client.close(&ctx4); | |
718 | ASSERT_EQ(0, ctx4.wait()); | |
719 | } | |
720 | ||
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); | |
725 | ||
726 | MockTestImageCtx mock_test_image_ctx(*m_image_ctx); | |
727 | MockHttpClient http_client(&mock_test_image_ctx, | |
728 | get_local_url(URL_SCHEME_HTTP)); | |
729 | ||
730 | C_SaferCond ctx1; | |
731 | http_client.open(&ctx1); | |
732 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
733 | ASSERT_EQ(0, ctx1.wait()); | |
734 | ||
735 | EmptyHttpRequest req; | |
736 | req.method(boost::beast::http::verb::get); | |
737 | ||
738 | C_SaferCond ctx2; | |
739 | http_client.issue(EmptyHttpRequest{req}, | |
740 | [&ctx2](int r, HttpResponse&&) mutable { | |
741 | ctx2.complete(r); | |
742 | }); | |
743 | ||
744 | client_read_request(socket, req); | |
745 | ||
746 | C_SaferCond ctx3; | |
747 | http_client.close(&ctx3); | |
748 | ASSERT_EQ(0, ctx3.wait()); | |
749 | ASSERT_EQ(-ESHUTDOWN, ctx2.wait()); | |
750 | } | |
751 | ||
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)); | |
756 | ||
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); | |
760 | ||
761 | C_SaferCond ctx1; | |
762 | http_client.open(&ctx1); | |
763 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
764 | ASSERT_EQ(0, ctx1.wait()); | |
765 | ||
766 | uint64_t size = 0; | |
767 | C_SaferCond ctx2; | |
768 | http_client.get_size(&size, &ctx2); | |
769 | ||
770 | EmptyHttpRequest expected_req; | |
771 | expected_req.method(boost::beast::http::verb::head); | |
772 | client_read_request(socket, expected_req); | |
773 | ||
774 | HttpResponse expected_res; | |
775 | expected_res.body() = std::string(123, '1'); | |
776 | client_write_response(socket, expected_res); | |
777 | ||
778 | ASSERT_EQ(0, ctx2.wait()); | |
779 | ASSERT_EQ(123, size); | |
780 | ||
781 | C_SaferCond ctx3; | |
782 | http_client.close(&ctx3); | |
783 | ASSERT_EQ(0, ctx3.wait()); | |
784 | } | |
785 | ||
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)); | |
790 | ||
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); | |
794 | ||
795 | C_SaferCond ctx1; | |
796 | http_client.open(&ctx1); | |
797 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
798 | ASSERT_EQ(0, ctx1.wait()); | |
799 | ||
800 | uint64_t size = 0; | |
801 | C_SaferCond ctx2; | |
802 | http_client.get_size(&size, &ctx2); | |
803 | ||
804 | EmptyHttpRequest expected_req; | |
805 | expected_req.method(boost::beast::http::verb::head); | |
806 | client_read_request(socket, expected_req); | |
807 | ||
808 | HttpResponse expected_res; | |
809 | expected_res.result(boost::beast::http::status::internal_server_error); | |
810 | client_write_response(socket, expected_res); | |
811 | ||
812 | ASSERT_EQ(-EIO, ctx2.wait()); | |
813 | ||
814 | C_SaferCond ctx3; | |
815 | http_client.close(&ctx3); | |
816 | ASSERT_EQ(0, ctx3.wait()); | |
817 | } | |
818 | ||
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)); | |
823 | ||
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); | |
827 | ||
828 | C_SaferCond ctx1; | |
829 | http_client.open(&ctx1); | |
830 | ASSERT_EQ(0, on_connect_ctx.wait()); | |
831 | ASSERT_EQ(0, ctx1.wait()); | |
832 | ||
833 | bufferlist bl; | |
834 | C_SaferCond ctx2; | |
835 | http_client.read({{0, 128}, {256, 64}}, &bl, &ctx2); | |
836 | ||
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); | |
841 | ||
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); | |
846 | ||
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); | |
851 | ||
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); | |
856 | ||
857 | ASSERT_EQ(192, ctx2.wait()); | |
858 | ||
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); | |
863 | ||
864 | C_SaferCond ctx3; | |
865 | http_client.close(&ctx3); | |
866 | ASSERT_EQ(0, ctx3.wait()); | |
867 | } | |
868 | ||
869 | } // namespace migration | |
870 | } // namespace librbd |