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