/proc/self/cwd/test/integration/http_integration.h
Line | Count | Source (jump to first uncovered line) |
1 | | #pragma once |
2 | | |
3 | | #include <cstdint> |
4 | | #include <memory> |
5 | | #include <string> |
6 | | |
7 | | #include "source/common/http/codec_client.h" |
8 | | #include "source/common/network/filter_impl.h" |
9 | | |
10 | | #include "test/common/http/http2/http2_frame.h" |
11 | | #include "test/integration/integration.h" |
12 | | #include "test/integration/utility.h" |
13 | | #include "test/test_common/printers.h" |
14 | | #include "test/test_common/utility.h" |
15 | | |
16 | | #ifdef ENVOY_ENABLE_QUIC |
17 | | #include "quiche/quic/core/deterministic_connection_id_generator.h" |
18 | | #endif |
19 | | |
20 | | namespace Envoy { |
21 | | |
22 | | using ::Envoy::Http::Http2::Http2Frame; |
23 | | |
24 | | enum class Http2Impl { |
25 | | Nghttp2, |
26 | | Oghttp2, |
27 | | }; |
28 | | |
29 | | /** |
30 | | * HTTP codec client used during integration testing. |
31 | | */ |
32 | | class IntegrationCodecClient : public Http::CodecClientProd { |
33 | | public: |
34 | | IntegrationCodecClient(Event::Dispatcher& dispatcher, Random::RandomGenerator& random, |
35 | | Network::ClientConnectionPtr&& conn, |
36 | | Upstream::HostDescriptionConstSharedPtr host_description, |
37 | | Http::CodecType type); |
38 | | IntegrationCodecClient(Event::Dispatcher& dispatcher, Random::RandomGenerator& random, |
39 | | Network::ClientConnectionPtr&& conn, |
40 | | Upstream::HostDescriptionConstSharedPtr host_description, |
41 | | Http::CodecType type, bool wait_till_connected); |
42 | | |
43 | | IntegrationStreamDecoderPtr makeHeaderOnlyRequest(const Http::RequestHeaderMap& headers); |
44 | | IntegrationStreamDecoderPtr makeRequestWithBody(const Http::RequestHeaderMap& headers, |
45 | | uint64_t body_size, bool end_stream = true); |
46 | | IntegrationStreamDecoderPtr makeRequestWithBody(const Http::RequestHeaderMap& headers, |
47 | | const std::string& body, bool end_stream = true); |
48 | 0 | bool sawGoAway() const { return saw_goaway_; } |
49 | 815 | bool connected() const { return connected_; } |
50 | 9.40k | bool streamOpen() const { return !stream_gone_; } |
51 | | void sendData(Http::RequestEncoder& encoder, absl::string_view data, bool end_stream); |
52 | | void sendData(Http::RequestEncoder& encoder, Buffer::Instance& data, bool end_stream); |
53 | | void sendData(Http::RequestEncoder& encoder, uint64_t size, bool end_stream); |
54 | | void sendTrailers(Http::RequestEncoder& encoder, const Http::RequestTrailerMap& trailers); |
55 | | void sendReset(Http::RequestEncoder& encoder); |
56 | | // Intentionally makes a copy of metadata_map. |
57 | | void sendMetadata(Http::RequestEncoder& encoder, Http::MetadataMap metadata_map); |
58 | | IntegrationStreamDecoderPtr initRequest(const Http::RequestHeaderMap& headers, |
59 | | bool header_only_request = false); |
60 | | std::pair<Http::RequestEncoder&, IntegrationStreamDecoderPtr> |
61 | | startRequest(const Http::RequestHeaderMap& headers, bool header_only_request = false); |
62 | | ABSL_MUST_USE_RESULT AssertionResult |
63 | | waitForDisconnect(std::chrono::milliseconds time_to_wait = TestUtility::DefaultTimeout); |
64 | 0 | Network::ClientConnection* connection() const { return connection_.get(); } |
65 | 0 | Network::ConnectionEvent lastConnectionEvent() const { return last_connection_event_; } |
66 | 0 | Network::Connection& rawConnection() { return *connection_; } |
67 | 0 | bool disconnected() { return disconnected_; } |
68 | | |
69 | | private: |
70 | | struct ConnectionCallbacks : public Network::ConnectionCallbacks { |
71 | | ConnectionCallbacks(IntegrationCodecClient& parent, bool block_till_connected) |
72 | 815 | : parent_(parent), block_till_connected_(block_till_connected) {} |
73 | | |
74 | | // Network::ConnectionCallbacks |
75 | | void onEvent(Network::ConnectionEvent event) override; |
76 | 0 | void onAboveWriteBufferHighWatermark() override {} |
77 | 0 | void onBelowWriteBufferLowWatermark() override {} |
78 | | |
79 | | IntegrationCodecClient& parent_; |
80 | | bool block_till_connected_; |
81 | | }; |
82 | | |
83 | | struct CodecCallbacks : public Http::ConnectionCallbacks { |
84 | 815 | CodecCallbacks(IntegrationCodecClient& parent) : parent_(parent) {} |
85 | | |
86 | | // Http::ConnectionCallbacks |
87 | 0 | void onGoAway(Http::GoAwayErrorCode) override { parent_.saw_goaway_ = true; } |
88 | | |
89 | | IntegrationCodecClient& parent_; |
90 | | }; |
91 | | |
92 | | struct CodecClientCallbacks : public Http::CodecClientCallbacks { |
93 | 815 | CodecClientCallbacks(IntegrationCodecClient& parent) : parent_(parent) {} |
94 | | |
95 | | // Http::CodecClientCallbacks |
96 | 815 | void onStreamDestroy() override { parent_.stream_gone_ = true; } |
97 | 11 | void onStreamReset(Http::StreamResetReason) override { parent_.stream_gone_ = true; } |
98 | | |
99 | | IntegrationCodecClient& parent_; |
100 | | }; |
101 | | |
102 | | void flushWrite(); |
103 | | |
104 | | Event::Dispatcher& dispatcher_; |
105 | | ConnectionCallbacks callbacks_; |
106 | | CodecCallbacks codec_callbacks_; |
107 | | CodecClientCallbacks codec_client_callbacks_; |
108 | | bool connected_{}; |
109 | | bool disconnected_{}; |
110 | | bool saw_goaway_{}; |
111 | | bool stream_gone_{}; |
112 | | Network::ConnectionEvent last_connection_event_; |
113 | | }; |
114 | | |
115 | | using IntegrationCodecClientPtr = std::unique_ptr<IntegrationCodecClient>; |
116 | | |
117 | | /** |
118 | | * Test fixture for HTTP and HTTP/2 integration tests. |
119 | | */ |
120 | | class HttpIntegrationTest : public BaseIntegrationTest { |
121 | | public: |
122 | | HttpIntegrationTest(Http::CodecType downstream_protocol, Network::Address::IpVersion version) |
123 | | : HttpIntegrationTest( |
124 | | downstream_protocol, version, |
125 | | ConfigHelper::httpProxyConfig(/*downstream_use_quic=*/downstream_protocol == |
126 | 1.96k | Http::CodecType::HTTP3)) {} |
127 | | HttpIntegrationTest(Http::CodecType downstream_protocol, Network::Address::IpVersion version, |
128 | | const std::string& config); |
129 | | |
130 | | HttpIntegrationTest(Http::CodecType downstream_protocol, |
131 | | const InstanceConstSharedPtrFn& upstream_address_fn, |
132 | | Network::Address::IpVersion version) |
133 | | : HttpIntegrationTest( |
134 | | downstream_protocol, upstream_address_fn, version, |
135 | | ConfigHelper::httpProxyConfig(/*downstream_use_quic=*/downstream_protocol == |
136 | 0 | Http::CodecType::HTTP3)) {} |
137 | | HttpIntegrationTest(Http::CodecType downstream_protocol, |
138 | | const InstanceConstSharedPtrFn& upstream_address_fn, |
139 | | Network::Address::IpVersion version, const std::string& config); |
140 | | ~HttpIntegrationTest() override; |
141 | | |
142 | | void initialize() override; |
143 | | void setupHttp1ImplOverrides(Http1ParserImpl http1_implementation); |
144 | | void setupHttp2ImplOverrides(Http2Impl http2_implementation); |
145 | | |
146 | | protected: |
147 | | void useAccessLog(absl::string_view format = "", |
148 | | std::vector<envoy::config::core::v3::TypedExtensionConfig> formatters = {}); |
149 | | std::string waitForAccessLog(const std::string& filename, uint32_t entry = 0, |
150 | | bool allow_excess_entries = false, |
151 | 0 | Network::ClientConnection* client_connection = nullptr) { |
152 | 0 | if (client_connection == nullptr && codec_client_) { |
153 | 0 | client_connection = codec_client_->connection(); |
154 | 0 | } |
155 | 0 | return BaseIntegrationTest::waitForAccessLog(filename, entry, allow_excess_entries, |
156 | 0 | client_connection); |
157 | 0 | }; |
158 | | |
159 | | IntegrationCodecClientPtr makeHttpConnection(uint32_t port); |
160 | | // Makes a http connection object without checking its connected state. |
161 | | virtual IntegrationCodecClientPtr |
162 | | makeRawHttpConnection(Network::ClientConnectionPtr&& conn, |
163 | | absl::optional<envoy::config::core::v3::Http2ProtocolOptions> http2_options, |
164 | | bool wait_till_connected = true); |
165 | | // Makes a downstream network connection object based on client codec version. |
166 | | Network::ClientConnectionPtr makeClientConnectionWithOptions( |
167 | | uint32_t port, const Network::ConnectionSocket::OptionsSharedPtr& options) override; |
168 | | // Makes a http connection object with asserting a connected state. |
169 | | IntegrationCodecClientPtr makeHttpConnection(Network::ClientConnectionPtr&& conn); |
170 | | |
171 | | // Sets downstream_protocol_ and alters the HTTP connection manager codec type in the |
172 | | // config_helper_. |
173 | | void setDownstreamProtocol(Http::CodecType type); |
174 | | |
175 | | // Enable the encoding/decoding of Http1 trailers downstream |
176 | | ConfigHelper::HttpModifierFunction setEnableDownstreamTrailersHttp1(); |
177 | | |
178 | | // Enable the encoding/decoding of Http1 trailers upstream |
179 | | ConfigHelper::ConfigModifierFunction setEnableUpstreamTrailersHttp1(); |
180 | | |
181 | | // Enable Proxy-Status response header. |
182 | | ConfigHelper::HttpModifierFunction configureProxyStatus(); |
183 | | |
184 | | // Sends |request_headers| and |request_body_size| bytes of body upstream. |
185 | | // Configured upstream to send |response_headers| and |response_body_size| |
186 | | // bytes of body downstream. |
187 | | // Waits |time| ms for both the request to be proxied upstream and the |
188 | | // response to be proxied downstream. |
189 | | // |
190 | | // Waits for the complete downstream response before returning. |
191 | | // Requires |codec_client_| to be initialized. |
192 | | IntegrationStreamDecoderPtr sendRequestAndWaitForResponse( |
193 | | const Http::TestRequestHeaderMapImpl& request_headers, uint32_t request_body_size, |
194 | | const Http::TestResponseHeaderMapImpl& response_headers, uint32_t response_body_size, |
195 | | uint64_t upstream_index = 0, std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); |
196 | | |
197 | | IntegrationStreamDecoderPtr sendRequestAndWaitForResponse( |
198 | | const Http::TestRequestHeaderMapImpl& request_headers, uint32_t request_body_size, |
199 | | const Http::TestResponseHeaderMapImpl& response_headers, uint32_t response_body_size, |
200 | | const std::vector<uint64_t>& upstream_indices, |
201 | | std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); |
202 | | |
203 | | // Wait for the end of stream on the next upstream stream on any of the provided fake upstreams. |
204 | | // Sets fake_upstream_connection_ to the connection and upstream_request_ to stream. |
205 | | // In cases where the upstream that will receive the request is not deterministic, a second |
206 | | // upstream index may be provided, in which case both upstreams will be checked for requests. |
207 | | absl::optional<uint64_t> waitForNextUpstreamRequest( |
208 | | const std::vector<uint64_t>& upstream_indices, |
209 | | std::chrono::milliseconds connection_wait_timeout = TestUtility::DefaultTimeout); |
210 | | void waitForNextUpstreamRequest( |
211 | | uint64_t upstream_index = 0, |
212 | | std::chrono::milliseconds connection_wait_timeout = TestUtility::DefaultTimeout); |
213 | | |
214 | | absl::optional<uint64_t> |
215 | | waitForNextUpstreamConnection(const std::vector<uint64_t>& upstream_indices, |
216 | | std::chrono::milliseconds connection_wait_timeout, |
217 | | FakeHttpConnectionPtr& fake_upstream_connection); |
218 | | |
219 | | // Close |codec_client_| and |fake_upstream_connection_| cleanly. |
220 | | void cleanupUpstreamAndDownstream(); |
221 | | |
222 | | // Verifies the response_headers contains the expected_headers, and response body matches given |
223 | | // body string. |
224 | | void verifyResponse(IntegrationStreamDecoderPtr response, const std::string& response_code, |
225 | | const Http::TestResponseHeaderMapImpl& expected_headers, |
226 | | const std::string& expected_body); |
227 | | |
228 | | // Helper that sends a request to Envoy, and verifies if Envoy response headers and body size is |
229 | | // the same as the expected headers map. |
230 | | // Requires the "http" port has been registered. |
231 | | void sendRequestAndVerifyResponse(const Http::TestRequestHeaderMapImpl& request_headers, |
232 | | const int request_size, |
233 | | const Http::TestResponseHeaderMapImpl& response_headers, |
234 | | const int response_size, const int backend_idx, |
235 | | absl::optional<const Http::TestResponseHeaderMapImpl> |
236 | | expected_response_headers = absl::nullopt); |
237 | | |
238 | | // Check for completion of upstream_request_, and a simple "200" response. |
239 | | void checkSimpleRequestSuccess(uint64_t expected_request_size, uint64_t expected_response_size, |
240 | | IntegrationStreamDecoder* response); |
241 | | |
242 | | using ConnectionCreationFunction = std::function<Network::ClientConnectionPtr()>; |
243 | | // Sends a simple header-only HTTP request, and waits for a response. |
244 | | IntegrationStreamDecoderPtr makeHeaderOnlyRequest(ConnectionCreationFunction* create_connection, |
245 | | int upstream_index, |
246 | | const std::string& path = "/test/long/url", |
247 | | const std::string& overwrite_authority = ""); |
248 | | void testRouterNotFound(); |
249 | | void testRouterNotFoundWithBody(); |
250 | | void testRouterVirtualClusters(); |
251 | | void testRouteStats(); |
252 | | void testRouterUpstreamProtocolError(const std::string&, const std::string&); |
253 | | |
254 | | void testRouterRequestAndResponseWithBody( |
255 | | uint64_t request_size, uint64_t response_size, bool big_header, |
256 | | bool set_content_length_header = false, ConnectionCreationFunction* creator = nullptr, |
257 | | std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); |
258 | | void testRouterHeaderOnlyRequestAndResponse(ConnectionCreationFunction* creator = nullptr, |
259 | | int upstream_index = 0, |
260 | | const std::string& path = "/test/long/url", |
261 | | const std::string& overwrite_authority = ""); |
262 | | |
263 | | // Disconnect tests |
264 | | void testRouterUpstreamDisconnectBeforeRequestComplete(); |
265 | | void |
266 | | testRouterUpstreamDisconnectBeforeResponseComplete(ConnectionCreationFunction* creator = nullptr); |
267 | | void testRouterDownstreamDisconnectBeforeRequestComplete( |
268 | | ConnectionCreationFunction* creator = nullptr); |
269 | | void testRouterDownstreamDisconnectBeforeResponseComplete( |
270 | | ConnectionCreationFunction* creator = nullptr); |
271 | | void testRouterUpstreamResponseBeforeRequestComplete(uint32_t status_code = 0); |
272 | | |
273 | | void testTwoRequests(bool force_network_backup = false); |
274 | | void testLargeHeaders(Http::TestRequestHeaderMapImpl request_headers, |
275 | | Http::TestRequestTrailerMapImpl request_trailers, uint32_t size, |
276 | | uint32_t max_size); |
277 | | void testLargeRequestUrl(uint32_t url_size, uint32_t max_headers_size); |
278 | | void testLargeRequestHeaders(uint32_t size, uint32_t count, uint32_t max_size = 60, |
279 | | uint32_t max_count = 100, |
280 | | std::chrono::milliseconds timeout = TestUtility::DefaultTimeout); |
281 | | void testLargeRequestTrailers(uint32_t size, uint32_t max_size = 60); |
282 | | void testManyRequestHeaders(std::chrono::milliseconds time = TestUtility::DefaultTimeout); |
283 | | |
284 | | void testAddEncodedTrailers(); |
285 | | void testRetry(); |
286 | | void testRouterRetryOnResetBeforeRequestBeforeHeaders(); |
287 | | void testRouterRetryOnResetBeforeRequestAfterHeaders(); |
288 | | void testRetryHittingBufferLimit(); |
289 | | void testRetryAttemptCountHeader(); |
290 | | void testGrpcRetry(); |
291 | | |
292 | | void testEnvoyHandling1xx(bool additional_continue_from_upstream = false, |
293 | | const std::string& via = "", bool disconnect_after_100 = false); |
294 | | void testEnvoyProxying1xx(bool continue_before_upstream_complete = false, |
295 | | bool with_encoder_filter = false, |
296 | | bool with_multiple_1xx_headers = false, |
297 | | absl::string_view initial_code = "100"); |
298 | | void simultaneousRequest(uint32_t request1_bytes, uint32_t request2_bytes, |
299 | | uint32_t response1_bytes, uint32_t response2_bytes); |
300 | | |
301 | | // HTTP/2 client tests. |
302 | | void testDownstreamResetBeforeResponseComplete(); |
303 | | // Test that trailers are sent. request_trailers_present and |
304 | | // response_trailers_present will check if the trailers are present, otherwise |
305 | | // makes sure they were dropped. |
306 | | void testTrailers(uint64_t request_size, uint64_t response_size, bool request_trailers_present, |
307 | | bool response_trailers_present); |
308 | | // Test /drain_listener from admin portal. |
309 | | void testAdminDrain(Http::CodecClient::Type admin_request_type); |
310 | | |
311 | | // Test sending and receiving large request and response bodies with autonomous upstream. |
312 | | void testGiantRequestAndResponse(uint64_t request_size, uint64_t response_size, |
313 | | bool set_content_length_header, |
314 | | std::chrono::milliseconds timeout = 2 * |
315 | | TestUtility::DefaultTimeout); |
316 | | |
317 | | struct BytesCountExpectation { |
318 | | BytesCountExpectation(int wire_bytes_sent, int wire_bytes_received, int header_bytes_sent, |
319 | | int header_bytes_received) |
320 | | : wire_bytes_sent_{wire_bytes_sent}, wire_bytes_received_{wire_bytes_received}, |
321 | 0 | header_bytes_sent_{header_bytes_sent}, header_bytes_received_{header_bytes_received} {} |
322 | | int wire_bytes_sent_; |
323 | | int wire_bytes_received_; |
324 | | int header_bytes_sent_; |
325 | | int header_bytes_received_; |
326 | | }; |
327 | | |
328 | | void expectUpstreamBytesSentAndReceived(BytesCountExpectation h1_expectation, |
329 | | BytesCountExpectation h2_expectation, |
330 | | BytesCountExpectation h3_expectation, const int id = 0); |
331 | | |
332 | | void expectDownstreamBytesSentAndReceived(BytesCountExpectation h1_expectation, |
333 | | BytesCountExpectation h2_expectation, |
334 | | BytesCountExpectation h3_expectation, const int id = 0); |
335 | | |
336 | 0 | Http::CodecClient::Type downstreamProtocol() const { return downstream_protocol_; } |
337 | | std::string downstreamProtocolStatsRoot() const; |
338 | | // Return the upstream protocol part of the stats root. |
339 | | std::string upstreamProtocolStatsRoot() const; |
340 | | // Prefix listener stat with IP:port, including IP version dependent loopback address. |
341 | | std::string listenerStatPrefix(const std::string& stat_name); |
342 | | |
343 | | Network::UpstreamTransportSocketFactoryPtr quic_transport_socket_factory_; |
344 | | // Must outlive |codec_client_| because it may not close connection till the end of its life |
345 | | // scope. |
346 | | std::unique_ptr<Http::PersistentQuicInfo> quic_connection_persistent_info_; |
347 | | // The client making requests to Envoy. |
348 | | IntegrationCodecClientPtr codec_client_; |
349 | | // A placeholder for the first upstream connection. |
350 | | FakeHttpConnectionPtr fake_upstream_connection_; |
351 | | // A placeholder for the first request received at upstream. |
352 | | FakeStreamPtr upstream_request_; |
353 | | // A pointer to the request encoder, if used. |
354 | | Http::RequestEncoder* request_encoder_{nullptr}; |
355 | | // The response headers sent by sendRequestAndWaitForResponse() by default. |
356 | | Http::TestResponseHeaderMapImpl default_response_headers_{{":status", "200"}}; |
357 | | Http::TestRequestHeaderMapImpl default_request_headers_{{":method", "GET"}, |
358 | | {":path", "/test/long/url"}, |
359 | | {":scheme", "http"}, |
360 | | {":authority", "sni.lyft.com"}}; |
361 | | // The codec type for the client-to-Envoy connection |
362 | | Http::CodecType downstream_protocol_{Http::CodecType::HTTP1}; |
363 | | std::string access_log_name_; |
364 | | testing::NiceMock<Random::MockRandomGenerator> random_; |
365 | | Quic::QuicStatNames quic_stat_names_; |
366 | | std::string san_to_match_{"spiffe://lyft.com/backend-team"}; |
367 | | bool enable_quic_early_data_{true}; |
368 | | std::vector<absl::string_view> custom_alpns_; |
369 | | // Set this to true when sending malformed requests to avoid test client codec rejecting it. |
370 | | // This flag is only valid when UHV build flag is enabled. |
371 | | bool disable_client_header_validation_{false}; |
372 | | #ifdef ENVOY_ENABLE_QUIC |
373 | | quic::DeterministicConnectionIdGenerator connection_id_generator_{ |
374 | | quic::kQuicDefaultConnectionIdLength}; |
375 | | #endif |
376 | | }; |
377 | | |
378 | | // Helper class for integration tests using raw HTTP/2 frames |
379 | | class Http2RawFrameIntegrationTest : public HttpIntegrationTest { |
380 | | public: |
381 | | Http2RawFrameIntegrationTest(Network::Address::IpVersion version) |
382 | 0 | : HttpIntegrationTest(Http::CodecType::HTTP2, version) {} |
383 | | |
384 | | protected: |
385 | | void startHttp2Session(); |
386 | | Http2Frame readFrame(); |
387 | | void sendFrame(const Http2Frame& frame); |
388 | | virtual void beginSession(); |
389 | | |
390 | | IntegrationTcpClientPtr tcp_client_; |
391 | | }; |
392 | | |
393 | | absl::string_view upstreamToString(Http::CodecType type); |
394 | | absl::string_view downstreamToString(Http::CodecType type); |
395 | | absl::string_view http2ImplementationToString(Http2Impl impl); |
396 | | |
397 | | } // namespace Envoy |