From c91c0a1a2b7bb3f79eb1cec5894f2daab9faf1cf Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Wed, 30 Jan 2019 14:24:32 -0500 Subject: [PATCH 1/4] test: pulling protocol specific test code into protocol-specific test files Signed-off-by: Alyssa Wilk --- test/integration/http2_integration_test.cc | 347 ++++++++++- test/integration/http_integration.cc | 664 --------------------- test/integration/http_integration.h | 27 +- test/integration/integration_test.cc | 311 +++++++++- test/integration/integration_test.h | 1 + 5 files changed, 637 insertions(+), 713 deletions(-) diff --git a/test/integration/http2_integration_test.cc b/test/integration/http2_integration_test.cc index 3665698d89da6..cf011c258b9cc 100644 --- a/test/integration/http2_integration_test.cc +++ b/test/integration/http2_integration_test.cc @@ -95,18 +95,355 @@ TEST_P(Http2IntegrationTest, RetryAttemptCount) { testRetryAttemptCountHeader(); TEST_P(Http2IntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } +static std::string response_metadata_filter = R"EOF( +name: response-metadata-filter +config: {} +)EOF"; + +// Verifies metadata can be sent at different locations of the responses. TEST_P(Http2MetadataIntegrationTest, ProxyMetadataInResponse) { - testEnvoyProxyMetadataInResponse(); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Sends the first request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata before response header. + const std::string key = "key"; + std::string value = std::string(80 * 1024, '1'); + Http::MetadataMap metadata_map = {{key, value}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(12, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the second request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata after response header followed by an empty data frame with end_stream true. + value = std::string(10, '2'); + upstream_request_->encodeHeaders(default_response_headers_, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(0, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the third request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata after response header and before data. + value = std::string(10, '3'); + upstream_request_->encodeHeaders(default_response_headers_, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(10, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the fourth request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata between data frames. + value = std::string(10, '4'); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(10, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the fifth request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata after the last non-empty data frames. + value = std::string(10, '5'); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(0, true); + + // Verifies metadata is received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().find(key)->second, value); + + // Sends the sixth request. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends metadata before reset. + value = std::string(10, '6'); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + metadata_map = {{key, value}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.erase(metadata_map_vector.begin()); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeResetStream(); + + // Verifies stream is reset. + response->waitForReset(); + ASSERT_FALSE(response->complete()); } -TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) { testEnvoyProxyMultipleMetadata(); } +TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadata) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Sends a request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + const int size = 4; + std::vector multiple_vecs(size); + for (int i = 0; i < size; i++) { + Runtime::RandomGeneratorImpl random; + int value_size = random.random() % Http::METADATA_MAX_PAYLOAD_SIZE + 1; + Http::MetadataMap metadata_map = {{std::string(i, 'a'), std::string(value_size, 'b')}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + multiple_vecs[i].push_back(std::move(metadata_map_ptr)); + } + upstream_request_->encodeMetadata(multiple_vecs[0]); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeMetadata(multiple_vecs[1]); + upstream_request_->encodeData(12, false); + upstream_request_->encodeMetadata(multiple_vecs[2]); + upstream_request_->encodeData(12, false); + upstream_request_->encodeMetadata(multiple_vecs[3]); + upstream_request_->encodeData(12, true); + + // Verifies multiple metadata are received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + for (int i = 0; i < size; i++) { + for (const auto& metadata : *multiple_vecs[i][0]) { + EXPECT_EQ(response->metadata_map().find(metadata.first)->second, metadata.second); + } + } + EXPECT_EQ(response->metadata_map().size(), multiple_vecs.size()); +} -TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) { testEnvoyProxyInvalidMetadata(); } +TEST_P(Http2MetadataIntegrationTest, ProxyInvalidMetadata) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); -TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { testResponseMetadata(); } + // Sends a request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends over-sized metadata before response header. + const std::string key = "key"; + std::string value = std::string(1024 * 1024, 'a'); + Http::MetadataMap metadata_map = {{key, value}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(12, false); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeData(12, true); + + // Verifies metadata is not received by the client. + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_EQ(response->metadata_map().size(), 0); +} + +void verifyExpectedMetadata(Http::MetadataMap metadata_map, std::set keys) { + for (const auto key : keys) { + // keys are the same as their corresponding values. + EXPECT_EQ(metadata_map.find(key)->second, key); + } + EXPECT_EQ(metadata_map.size(), keys.size()); +} + +TEST_P(Http2MetadataIntegrationTest, TestResponseMetadata) { + + addFilters({response_metadata_filter}); + config_helper_.addConfigModifier( + [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) + -> void { hcm.set_proxy_100_continue(true); }); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Upstream responds with headers. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers", "duplicate" and "keep". + std::set expected_metadata_keys = {"headers", "duplicate", "keep"}; + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + + // Upstream responds with headers and data. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(100, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate" and metadata added in + // encodeData(): "data" and "duplicate" are received by the client. Note that "remove" is + // consumed. + expected_metadata_keys.insert("data"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(response->keyCount("keep"), 2); + + // Upstream responds with headers, data and trailers. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(10, false); + Http::TestHeaderMapImpl response_trailers{{"response", "trailer"}}; + upstream_request_->encodeTrailers(response_trailers); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in + // encodeData(): "data" and "duplicate", and metadata added in encodeTrailer(): "trailers" and + // "duplicate" are received by the client. Note that "remove" is consumed. + expected_metadata_keys.insert("trailers"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 3); + EXPECT_EQ(response->keyCount("keep"), 4); + + // Upstream responds with headers, 100-continue and data. + response = codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/dynamo/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"expect", "100-continue"}}, + 10); + + waitForNextUpstreamRequest(); + upstream_request_->encode100ContinueHeaders(Http::TestHeaderMapImpl{{":status", "100"}}); + response->waitForContinueHeaders(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(100, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders: "headers" and "duplicate", and metadata added in + // encodeData(): "data" and "duplicate", and metadata added in encode100Continue(): "100-continue" + // and "duplicate" are received by the client. Note that "remove" is consumed. + expected_metadata_keys.erase("trailers"); + expected_metadata_keys.insert("100-continue"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 4); + EXPECT_EQ(response->keyCount("keep"), 4); + + // Upstream responds with headers and metadata that will not be consumed. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + Http::MetadataMap metadata_map = {{"aaa", "aaa"}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + Http::MetadataMapVector metadata_map_vector; + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in + // encodeMetadata(): "aaa", "keep" and "duplicate" are received by the client. Note that "remove" + // is consumed. + expected_metadata_keys.erase("data"); + expected_metadata_keys.erase("100-continue"); + expected_metadata_keys.insert("aaa"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("keep"), 2); + + // Upstream responds with headers, data and metadata that will be consumed. + response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + metadata_map = {{"consume", "consume"}, {"remove", "remove"}}; + metadata_map_ptr = std::make_unique(metadata_map); + metadata_map_vector.clear(); + metadata_map_vector.push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(metadata_map_vector); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(100, true); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in + // encodeData(): "data", "duplicate", and metadata added in encodeMetadata(): "keep", "duplicate", + // "replace" are received by the client. Note that key "remove" and "consume" are consumed. + expected_metadata_keys.erase("aaa"); + expected_metadata_keys.insert("data"); + expected_metadata_keys.insert("replace"); + verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); + EXPECT_EQ(response->keyCount("duplicate"), 2); + EXPECT_EQ(response->keyCount("keep"), 3); +} TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) { - testEnvoyMultipleMetadataReachSizeLimit(); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Sends a request. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); + waitForNextUpstreamRequest(); + + // Sends multiple metadata after response header until max size limit is reached. + upstream_request_->encodeHeaders(default_response_headers_, false); + const int size = 200; + std::vector multiple_vecs(size); + for (int i = 0; i < size; i++) { + Http::MetadataMap metadata_map = {{"key", std::string(10000, 'a')}}; + Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); + multiple_vecs[i].push_back(std::move(metadata_map_ptr)); + upstream_request_->encodeMetadata(multiple_vecs[i]); + } + upstream_request_->encodeData(12, true); + + // Verifies reset is received. + response->waitForReset(); + ASSERT_FALSE(response->complete()); } TEST_P(Http2IntegrationTest, EnvoyHandlingDuplicate100Continue) { diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 0cee15a594947..be7c1cc488cec 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -42,23 +42,6 @@ using testing::Not; namespace Envoy { namespace { -std::string normalizeDate(const std::string& s) { - const std::regex date_regex("date:[^\r]+"); - return std::regex_replace(s, date_regex, "date: Mon, 01 Jan 2017 00:00:00 GMT"); -} - -void setAllowAbsoluteUrl( - envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { - envoy::api::v2::core::Http1ProtocolOptions options; - options.mutable_allow_absolute_url()->set_value(true); - hcm.mutable_http_protocol_options()->CopyFrom(options); -}; - -void setAllowHttp10WithDefaultHost( - envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { - hcm.mutable_http_protocol_options()->set_accept_http_10(true); - hcm.mutable_http_protocol_options()->set_default_host_for_http_10("default.com"); -} envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::CodecType typeToCodecType(Http::CodecClient::Type type) { @@ -442,47 +425,6 @@ void HttpIntegrationTest::testRouterRedirect() { response->headers().get(Http::Headers::get().Location)->value().c_str()); } -void HttpIntegrationTest::testRouterDirectResponse() { - const std::string body = "Response body"; - const std::string file_path = TestEnvironment::writeStringToFileForTest("test_envoy", body); - static const std::string domain("direct.example.com"); - static const std::string prefix("/"); - static const Http::Code status(Http::Code::OK); - config_helper_.addConfigModifier( - [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) - -> void { - auto* route_config = hcm.mutable_route_config(); - auto* header_value_option = route_config->mutable_response_headers_to_add()->Add(); - header_value_option->mutable_header()->set_key("x-additional-header"); - header_value_option->mutable_header()->set_value("example-value"); - header_value_option->mutable_append()->set_value(false); - header_value_option = route_config->mutable_response_headers_to_add()->Add(); - header_value_option->mutable_header()->set_key("content-type"); - header_value_option->mutable_header()->set_value("text/html"); - header_value_option->mutable_append()->set_value(false); - auto* virtual_host = route_config->add_virtual_hosts(); - virtual_host->set_name(domain); - virtual_host->add_domains(domain); - virtual_host->add_routes()->mutable_match()->set_prefix(prefix); - virtual_host->mutable_routes(0)->mutable_direct_response()->set_status( - static_cast(status)); - virtual_host->mutable_routes(0)->mutable_direct_response()->mutable_body()->set_filename( - file_path); - }); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "GET", "/", "", downstream_protocol_, version_, "direct.example.com"); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); - EXPECT_STREQ("example-value", response->headers() - .get(Envoy::Http::LowerCaseString("x-additional-header")) - ->value() - .c_str()); - EXPECT_STREQ("text/html", response->headers().ContentType()->value().c_str()); - EXPECT_EQ(body, response->body()); -} - // Add a health check filter and verify correct computation of health based on upstream status. void HttpIntegrationTest::testComputedHealthCheck() { config_helper_.addFilter(R"EOF( @@ -1060,219 +1002,6 @@ void HttpIntegrationTest::testHittingEncoderFilterLimit() { EXPECT_STREQ("500", response->headers().Status()->value().c_str()); } -// Verifies metadata can be sent at different locations of the responses. -void HttpIntegrationTest::testEnvoyProxyMetadataInResponse() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends the first request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata before response header. - const std::string key = "key"; - std::string value = std::string(80 * 1024, '1'); - Http::MetadataMap metadata_map = {{key, value}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - Http::MetadataMapVector metadata_map_vector; - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(12, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the second request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata after response header followed by an empty data frame with end_stream true. - value = std::string(10, '2'); - upstream_request_->encodeHeaders(default_response_headers_, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(0, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the third request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata after response header and before data. - value = std::string(10, '3'); - upstream_request_->encodeHeaders(default_response_headers_, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(10, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the fourth request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata between data frames. - value = std::string(10, '4'); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(10, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the fifth request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata after the last non-empty data frames. - value = std::string(10, '5'); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(0, true); - - // Verifies metadata is received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().find(key)->second, value); - - // Sends the sixth request. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends metadata before reset. - value = std::string(10, '6'); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - metadata_map = {{key, value}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.erase(metadata_map_vector.begin()); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeResetStream(); - - // Verifies stream is reset. - response->waitForReset(); - ASSERT_FALSE(response->complete()); -} - -void HttpIntegrationTest::testEnvoyProxyMultipleMetadata() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends a request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - const int size = 4; - std::vector multiple_vecs(size); - for (int i = 0; i < size; i++) { - Runtime::RandomGeneratorImpl random; - int value_size = random.random() % Http::METADATA_MAX_PAYLOAD_SIZE + 1; - Http::MetadataMap metadata_map = {{std::string(i, 'a'), std::string(value_size, 'b')}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - multiple_vecs[i].push_back(std::move(metadata_map_ptr)); - } - upstream_request_->encodeMetadata(multiple_vecs[0]); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeMetadata(multiple_vecs[1]); - upstream_request_->encodeData(12, false); - upstream_request_->encodeMetadata(multiple_vecs[2]); - upstream_request_->encodeData(12, false); - upstream_request_->encodeMetadata(multiple_vecs[3]); - upstream_request_->encodeData(12, true); - - // Verifies multiple metadata are received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - for (int i = 0; i < size; i++) { - for (const auto& metadata : *multiple_vecs[i][0]) { - EXPECT_EQ(response->metadata_map().find(metadata.first)->second, metadata.second); - } - } - EXPECT_EQ(response->metadata_map().size(), multiple_vecs.size()); -} - -void HttpIntegrationTest::testEnvoyProxyInvalidMetadata() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends a request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends over-sized metadata before response header. - const std::string key = "key"; - std::string value = std::string(1024 * 1024, 'a'); - Http::MetadataMap metadata_map = {{key, value}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - Http::MetadataMapVector metadata_map_vector; - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(12, false); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeData(12, true); - - // Verifies metadata is not received by the client. - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - EXPECT_EQ(response->metadata_map().size(), 0); -} - -void HttpIntegrationTest::testEnvoyMultipleMetadataReachSizeLimit() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Sends a request. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - - // Sends multiple metadata after response header until max size limit is reached. - upstream_request_->encodeHeaders(default_response_headers_, false); - const int size = 200; - std::vector multiple_vecs(size); - for (int i = 0; i < size; i++) { - Http::MetadataMap metadata_map = {{"key", std::string(10000, 'a')}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - multiple_vecs[i].push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(multiple_vecs[i]); - } - upstream_request_->encodeData(12, true); - - // Verifies reset is received. - response->waitForReset(); - ASSERT_FALSE(response->complete()); -} - void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_from_upstream, const std::string& via) { initialize(); @@ -1563,240 +1292,6 @@ void HttpIntegrationTest::testTwoRequests(bool network_backup) { EXPECT_EQ(1024U, response->body().size()); } -void HttpIntegrationTest::testBadFirstline() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "hello", &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testMissingDelimiter() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET / HTTP/1.1\r\nHost: host\r\nfoo bar\r\n\r\n", &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testInvalidCharacterInFirstline() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GE(T / HTTP/1.1\r\nHost: host\r\n\r\n", - &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testInvalidVersion() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.01\r\nHost: host\r\n\r\n", - &response); - EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); -} - -void HttpIntegrationTest::testHttp10DisabledWithUpgrade() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\nUpgrade: h2c\r\n\r\n", - &response, true); - EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); -} - -void HttpIntegrationTest::testHttp10Disabled() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, true); - EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); -} - -// Turn HTTP/1.0 support on and verify the request is proxied and the default host is sent upstream. -void HttpIntegrationTest::testHttp10Enabled() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, false); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, HasSubstr("connection: close")); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); - - sendRawHttpAndWaitForResponse(lookupPort("http"), "HEAD / HTTP/1.0\r\n\r\n", &response, false); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, HasSubstr("connection: close")); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); -} - -// Verify for HTTP/1.0 a keep-alive header results in no connection: close. -// Also verify existing host headers are passed through for the HTTP/1.0 case. -void HttpIntegrationTest::testHttp10WithHostAndKeepAlive() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET / HTTP/1.0\r\nHost: foo.com\r\nConnection:Keep-alive\r\n\r\n", - &response, true); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, Not(HasSubstr("connection: close"))); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); -} - -// Turn HTTP/1.0 support on and verify 09 style requests work. -void HttpIntegrationTest::testHttp09Enabled() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), "GET /\r\n\r\n", &response, false); - EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); - EXPECT_THAT(response, HasSubstr("connection: close")); - EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); -} - -void HttpIntegrationTest::testAbsolutePath() { - // Configure www.redirect.com to send a redirect, and ensure the redirect is - // encountered via absolute URL. - config_helper_.addRoute("www.redirect.com", "/", "cluster_0", true, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::ALL); - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET http://www.redirect.com HTTP/1.1\r\nHost: host\r\n\r\n", - &response, true); - EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); -} - -void HttpIntegrationTest::testAbsolutePathWithPort() { - // Configure www.namewithport.com:1234 to send a redirect, and ensure the redirect is - // encountered via absolute URL with a port. - config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::ALL); - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse( - lookupPort("http"), "GET http://www.namewithport.com:1234 HTTP/1.1\r\nHost: host\r\n\r\n", - &response, true); - EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); -} - -void HttpIntegrationTest::testAbsolutePathWithoutPort() { - // Add a restrictive default match, to avoid the request hitting the * / catchall. - config_helper_.setDefaultHostAndRoute("foo.com", "/found"); - // Set a matcher for www.namewithport.com:1234 and verify http://www.namewithport.com does not - // match - config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::ALL); - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET http://www.namewithport.com HTTP/1.1\r\nHost: host\r\n\r\n", - &response, true); - EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0) << response; -} - -void HttpIntegrationTest::testAllowAbsoluteSameRelative() { - // TODO(mattwoodyard) run this test. - // Ensure that relative urls behave the same with allow_absolute_url enabled and without - testEquivalent("GET /foo/bar HTTP/1.1\r\nHost: host\r\n\r\n"); -} - -void HttpIntegrationTest::testConnect() { - // Ensure that connect behaves the same with allow_absolute_url enabled and without - testEquivalent("CONNECT www.somewhere.com:80 HTTP/1.1\r\nHost: host\r\n\r\n"); -} - -void HttpIntegrationTest::testInlineHeaders() { - autonomous_upstream_ = true; - config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET / HTTP/1.1\r\n" - "Host: foo.com\r\n" - "Foo: bar\r\n" - "Cache-control: public\r\n" - "Cache-control: 123\r\n" - "Eep: baz\r\n\r\n", - &response, true); - EXPECT_THAT(response, HasSubstr("HTTP/1.1 200 OK\r\n")); - - std::unique_ptr upstream_headers = - reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); - ASSERT_TRUE(upstream_headers != nullptr); - EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); - EXPECT_EQ(upstream_headers->CacheControl()->value(), "public,123"); - ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("foo")) != nullptr); - EXPECT_STREQ("bar", upstream_headers->get(Envoy::Http::LowerCaseString("foo"))->value().c_str()); - ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("eep")) != nullptr); - EXPECT_STREQ("baz", upstream_headers->get(Envoy::Http::LowerCaseString("eep"))->value().c_str()); -} - -void HttpIntegrationTest::testEquivalent(const std::string& request) { - config_helper_.addConfigModifier([&](envoy::config::bootstrap::v2::Bootstrap& bootstrap) -> void { - // Clone the whole listener. - auto static_resources = bootstrap.mutable_static_resources(); - auto* old_listener = static_resources->mutable_listeners(0); - auto* cloned_listener = static_resources->add_listeners(); - cloned_listener->CopyFrom(*old_listener); - old_listener->set_name("http_forward"); - }); - // Set the first listener to allow absolute URLs. - config_helper_.addConfigModifier(&setAllowAbsoluteUrl); - initialize(); - - std::string response1; - sendRawHttpAndWaitForResponse(lookupPort("http"), request.c_str(), &response1, true); - - std::string response2; - sendRawHttpAndWaitForResponse(lookupPort("http_forward"), request.c_str(), &response2, true); - - EXPECT_EQ(normalizeDate(response1), normalizeDate(response2)); -} - -void HttpIntegrationTest::testBadPath() { - initialize(); - std::string response; - sendRawHttpAndWaitForResponse(lookupPort("http"), - "GET http://api.lyft.com HTTP/1.1\r\nHost: host\r\n\r\n", &response, - true); - EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); -} - -void HttpIntegrationTest::testNoHost() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - Http::TestHeaderMapImpl request_headers{ - {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}}; - auto response = codec_client_->makeHeaderOnlyRequest(request_headers); - response->waitForEndStream(); - - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("400", response->headers().Status()->value().c_str()); -} - void HttpIntegrationTest::testValidZeroLengthContent() { initialize(); @@ -1903,28 +1398,6 @@ void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t max_si } } -void HttpIntegrationTest::testUpstreamProtocolError() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - auto encoder_decoder = codec_client_->startRequest(Http::TestHeaderMapImpl{ - {":method", "GET"}, {":path", "/test/long/url"}, {":authority", "host"}}); - auto response = std::move(encoder_decoder.second); - - FakeRawConnectionPtr fake_upstream_connection; - ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); - // TODO(mattklein123): Waiting for exact amount of data is a hack. This needs to - // be fixed. - std::string data; - ASSERT_TRUE(fake_upstream_connection->waitForData(187, &data)); - ASSERT_TRUE(fake_upstream_connection->write("bad protocol data!")); - ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); - codec_client_->waitForDisconnect(); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); -} - void HttpIntegrationTest::testHeadersOnlyFilterEncoding() { config_helper_.addFilter(R"EOF( name: encode-headers-only @@ -2156,143 +1629,6 @@ void HttpIntegrationTest::testTrailers(uint64_t request_size, uint64_t response_ } } -static std::string response_metadata_filter = R"EOF( -name: response-metadata-filter -config: {} -)EOF"; - -void verifyExpectedMetadata(Http::MetadataMap metadata_map, std::set keys) { - for (const auto key : keys) { - // keys are the same as their corresponding values. - EXPECT_EQ(metadata_map.find(key)->second, key); - } - EXPECT_EQ(metadata_map.size(), keys.size()); -} - -void HttpIntegrationTest::testResponseMetadata() { - addFilters({response_metadata_filter}); - config_helper_.addConfigModifier( - [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) - -> void { hcm.set_proxy_100_continue(true); }); - - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Upstream responds with headers. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers", "duplicate" and "keep". - std::set expected_metadata_keys = {"headers", "duplicate", "keep"}; - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - - // Upstream responds with headers and data. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(100, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate" and metadata added in - // encodeData(): "data" and "duplicate" are received by the client. Note that "remove" is - // consumed. - expected_metadata_keys.insert("data"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 2); - EXPECT_EQ(response->keyCount("keep"), 2); - - // Upstream responds with headers, data and trailers. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(10, false); - Http::TestHeaderMapImpl response_trailers{{"response", "trailer"}}; - upstream_request_->encodeTrailers(response_trailers); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in - // encodeData(): "data" and "duplicate", and metadata added in encodeTrailer(): "trailers" and - // "duplicate" are received by the client. Note that "remove" is consumed. - expected_metadata_keys.insert("trailers"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 3); - EXPECT_EQ(response->keyCount("keep"), 4); - - // Upstream responds with headers, 100-continue and data. - response = codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, - {":path", "/dynamo/url"}, - {":scheme", "http"}, - {":authority", "host"}, - {"expect", "100-continue"}}, - 10); - - waitForNextUpstreamRequest(); - upstream_request_->encode100ContinueHeaders(Http::TestHeaderMapImpl{{":status", "100"}}); - response->waitForContinueHeaders(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(100, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders: "headers" and "duplicate", and metadata added in - // encodeData(): "data" and "duplicate", and metadata added in encode100Continue(): "100-continue" - // and "duplicate" are received by the client. Note that "remove" is consumed. - expected_metadata_keys.erase("trailers"); - expected_metadata_keys.insert("100-continue"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 4); - EXPECT_EQ(response->keyCount("keep"), 4); - - // Upstream responds with headers and metadata that will not be consumed. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - Http::MetadataMap metadata_map = {{"aaa", "aaa"}}; - Http::MetadataMapPtr metadata_map_ptr = std::make_unique(metadata_map); - Http::MetadataMapVector metadata_map_vector; - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in - // encodeMetadata(): "aaa", "keep" and "duplicate" are received by the client. Note that "remove" - // is consumed. - expected_metadata_keys.erase("data"); - expected_metadata_keys.erase("100-continue"); - expected_metadata_keys.insert("aaa"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("keep"), 2); - - // Upstream responds with headers, data and metadata that will be consumed. - response = codec_client_->makeRequestWithBody(default_request_headers_, 10); - waitForNextUpstreamRequest(); - metadata_map = {{"consume", "consume"}, {"remove", "remove"}}; - metadata_map_ptr = std::make_unique(metadata_map); - metadata_map_vector.clear(); - metadata_map_vector.push_back(std::move(metadata_map_ptr)); - upstream_request_->encodeMetadata(metadata_map_vector); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(100, true); - - response->waitForEndStream(); - ASSERT_TRUE(response->complete()); - // Verify metadata added in encodeHeaders(): "headers" and "duplicate", and metadata added in - // encodeData(): "data", "duplicate", and metadata added in encodeMetadata(): "keep", "duplicate", - // "replace" are received by the client. Note that key "remove" and "consume" are consumed. - expected_metadata_keys.erase("aaa"); - expected_metadata_keys.insert("data"); - expected_metadata_keys.insert("replace"); - verifyExpectedMetadata(response->metadata_map(), expected_metadata_keys); - EXPECT_EQ(response->keyCount("duplicate"), 2); - EXPECT_EQ(response->keyCount("keep"), 3); -} - std::string HttpIntegrationTest::listenerStatPrefix(const std::string& stat_name) { if (version_ == Network::Address::IpVersion::v4) { return "listener.127.0.0.1_0." + stat_name; diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index fa7ddfbdd7119..a0d2b0d2bc8a9 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -130,7 +130,6 @@ class HttpIntegrationTest : public BaseIntegrationTest { int upstream_index); void testRouterRedirect(); - void testRouterDirectResponse(); void testRouterNotFound(); void testRouterNotFoundWithBody(); void testRouterClusterNotFound404(); @@ -160,28 +159,9 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testHeadersOnlyFilterEncodingIntermediateFilters(); void testHeadersOnlyFilterDecodingIntermediateFilters(); void testHeadersOnlyFilterInterleaved(); - // HTTP/1 tests - void testBadFirstline(); - void testMissingDelimiter(); - void testInvalidCharacterInFirstline(); - void testInvalidVersion(); - void testHttp10Disabled(); - void testHttp10DisabledWithUpgrade(); - void testHttp09Enabled(); - void testHttp10Enabled(); - void testHttp10WithHostAndKeepAlive(); - void testUpstreamProtocolError(); - void testBadPath(); - void testAbsolutePath(); - void testAbsolutePathWithPort(); - void testAbsolutePathWithoutPort(); - void testConnect(); - void testInlineHeaders(); - void testAllowAbsoluteSameRelative(); + // Test that a request returns the same content with both allow_absolute_urls enabled and // allow_absolute_urls disabled - void testEquivalent(const std::string& request); - void testNoHost(); void testDefaultHost(); void testValidZeroLengthContent(); void testInvalidContentLength(); @@ -198,11 +178,6 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testRetryHostPredicateFilter(); void testHittingDecoderFilterLimit(); void testHittingEncoderFilterLimit(); - void testEnvoyProxyMetadataInResponse(); - void testEnvoyProxyMultipleMetadata(); - void testEnvoyProxyInvalidMetadata(); - void testResponseMetadata(); - void testEnvoyMultipleMetadataReachSizeLimit(); void testEnvoyHandling100Continue(bool additional_continue_from_upstream = false, const std::string& via = ""); void testEnvoyProxying100Continue(bool continue_before_upstream_complete = false, diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index b1c2c984fc47a..5e250d981e753 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -8,6 +8,7 @@ #include "common/http/headers.h" #include "common/protobuf/utility.h" +#include "test/integration/autonomous_upstream.h" #include "test/integration/utility.h" #include "test/mocks/http/mocks.h" #include "test/test_common/network_utility.h" @@ -26,6 +27,28 @@ using testing::Not; namespace Envoy { +namespace { + +std::string normalizeDate(const std::string& s) { + const std::regex date_regex("date:[^\r]+"); + return std::regex_replace(s, date_regex, "date: Mon, 01 Jan 2017 00:00:00 GMT"); +} + +void setAllowAbsoluteUrl( + envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { + envoy::api::v2::core::Http1ProtocolOptions options; + options.mutable_allow_absolute_url()->set_value(true); + hcm.mutable_http_protocol_options()->CopyFrom(options); +}; + +void setAllowHttp10WithDefaultHost( + envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) { + hcm.mutable_http_protocol_options()->set_accept_http_10(true); + hcm.mutable_http_protocol_options()->set_default_host_for_http_10("default.com"); +} + +} // namespace + INSTANTIATE_TEST_SUITE_P(IpVersions, IntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); @@ -40,7 +63,46 @@ TEST_P(IntegrationTest, RouterClusterNotFound503) { testRouterClusterNotFound503 TEST_P(IntegrationTest, RouterRedirect) { testRouterRedirect(); } -TEST_P(IntegrationTest, RouterDirectResponse) { testRouterDirectResponse(); } +TEST_P(IntegrationTest, RouterDirectResponse) { + const std::string body = "Response body"; + const std::string file_path = TestEnvironment::writeStringToFileForTest("test_envoy", body); + static const std::string domain("direct.example.com"); + static const std::string prefix("/"); + static const Http::Code status(Http::Code::OK); + config_helper_.addConfigModifier( + [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) + -> void { + auto* route_config = hcm.mutable_route_config(); + auto* header_value_option = route_config->mutable_response_headers_to_add()->Add(); + header_value_option->mutable_header()->set_key("x-additional-header"); + header_value_option->mutable_header()->set_value("example-value"); + header_value_option->mutable_append()->set_value(false); + header_value_option = route_config->mutable_response_headers_to_add()->Add(); + header_value_option->mutable_header()->set_key("content-type"); + header_value_option->mutable_header()->set_value("text/html"); + header_value_option->mutable_append()->set_value(false); + auto* virtual_host = route_config->add_virtual_hosts(); + virtual_host->set_name(domain); + virtual_host->add_domains(domain); + virtual_host->add_routes()->mutable_match()->set_prefix(prefix); + virtual_host->mutable_routes(0)->mutable_direct_response()->set_status( + static_cast(status)); + virtual_host->mutable_routes(0)->mutable_direct_response()->mutable_body()->set_filename( + file_path); + }); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/", "", downstream_protocol_, version_, "direct.example.com"); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + EXPECT_STREQ("example-value", response->headers() + .get(Envoy::Http::LowerCaseString("x-additional-header")) + ->value() + .c_str()); + EXPECT_STREQ("text/html", response->headers().ContentType()->value().c_str()); + EXPECT_EQ(body, response->body()); +} TEST_P(IntegrationTest, ComputedHealthCheck) { testComputedHealthCheck(); } @@ -198,37 +260,230 @@ TEST_P(IntegrationTest, HittingGrpcFilterLimitBufferingHeaders) { TEST_P(IntegrationTest, HittingEncoderFilterLimit) { testHittingEncoderFilterLimit(); } -TEST_P(IntegrationTest, BadFirstline) { testBadFirstline(); } +TEST_P(IntegrationTest, BadFirstline) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "hello", &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, MissingDelimiter) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET / HTTP/1.1\r\nHost: host\r\nfoo bar\r\n\r\n", &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, InvalidCharacterInFirstline) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GE(T / HTTP/1.1\r\nHost: host\r\n\r\n", + &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, InvalidVersion) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.01\r\nHost: host\r\n\r\n", + &response); + EXPECT_EQ("HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n", response); +} + +TEST_P(IntegrationTest, Http10Disabled) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, true); + EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); +} + +TEST_P(IntegrationTest, Http10DisabledWithUpgrade) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\nUpgrade: h2c\r\n\r\n", + &response, true); + EXPECT_TRUE(response.find("HTTP/1.1 426 Upgrade Required\r\n") == 0); +} + +// Turn HTTP/1.0 support on and verify 09 style requests work. +TEST_P(IntegrationTest, Http09Enabled) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET /\r\n\r\n", &response, false); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("connection: close")); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); + + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); +} + +// Turn HTTP/1.0 support on and verify the request is proxied and the default host is sent upstream. +TEST_P(IntegrationTest, Http10Enabled) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), "GET / HTTP/1.0\r\n\r\n", &response, false); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("connection: close")); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); + + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "default.com"); + + sendRawHttpAndWaitForResponse(lookupPort("http"), "HEAD / HTTP/1.0\r\n\r\n", &response, false); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, HasSubstr("connection: close")); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); +} -TEST_P(IntegrationTest, MissingDelimiter) { testMissingDelimiter(); } +TEST_P(IntegrationTest, TestInlineHeaders) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET / HTTP/1.1\r\n" + "Host: foo.com\r\n" + "Foo: bar\r\n" + "Cache-control: public\r\n" + "Cache-control: 123\r\n" + "Eep: baz\r\n\r\n", + &response, true); + EXPECT_THAT(response, HasSubstr("HTTP/1.1 200 OK\r\n")); -TEST_P(IntegrationTest, InvalidCharacterInFirstline) { testInvalidCharacterInFirstline(); } + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); + EXPECT_EQ(upstream_headers->CacheControl()->value(), "public,123"); + ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("foo")) != nullptr); + EXPECT_STREQ("bar", upstream_headers->get(Envoy::Http::LowerCaseString("foo"))->value().c_str()); + ASSERT_TRUE(upstream_headers->get(Envoy::Http::LowerCaseString("eep")) != nullptr); + EXPECT_STREQ("baz", upstream_headers->get(Envoy::Http::LowerCaseString("eep"))->value().c_str()); +} -TEST_P(IntegrationTest, InvalidVersion) { testInvalidVersion(); } +// Verify for HTTP/1.0 a keep-alive header results in no connection: close. +// Also verify existing host headers are passed through for the HTTP/1.0 case. +TEST_P(IntegrationTest, Http10WithHostandKeepAlive) { + autonomous_upstream_ = true; + config_helper_.addConfigModifier(&setAllowHttp10WithDefaultHost); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET / HTTP/1.0\r\nHost: foo.com\r\nConnection:Keep-alive\r\n\r\n", + &response, true); + EXPECT_THAT(response, HasSubstr("HTTP/1.0 200 OK\r\n")); + EXPECT_THAT(response, Not(HasSubstr("connection: close"))); + EXPECT_THAT(response, Not(HasSubstr("transfer-encoding: chunked\r\n"))); + + std::unique_ptr upstream_headers = + reinterpret_cast(fake_upstreams_.front().get())->lastRequestHeaders(); + ASSERT_TRUE(upstream_headers != nullptr); + EXPECT_EQ(upstream_headers->Host()->value(), "foo.com"); +} -TEST_P(IntegrationTest, Http10Disabled) { testHttp10Disabled(); } +TEST_P(IntegrationTest, NoHost) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); -TEST_P(IntegrationTest, Http10DisabledWithUpgrade) { testHttp10DisabledWithUpgrade(); } + Http::TestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}}; + auto response = codec_client_->makeHeaderOnlyRequest(request_headers); + response->waitForEndStream(); -TEST_P(IntegrationTest, Http09Enabled) { testHttp09Enabled(); } + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("400", response->headers().Status()->value().c_str()); +} -TEST_P(IntegrationTest, Http10Enabled) { testHttp10Enabled(); } +TEST_P(IntegrationTest, BadPath) { + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET http://api.lyft.com HTTP/1.1\r\nHost: host\r\n\r\n", &response, + true); + EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); +} -TEST_P(IntegrationTest, TestInlineHeaders) { testInlineHeaders(); } +TEST_P(IntegrationTest, AbsolutePath) { + // Configure www.redirect.com to send a redirect, and ensure the redirect is + // encountered via absolute URL. + config_helper_.addRoute("www.redirect.com", "/", "cluster_0", true, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::ALL); + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); -TEST_P(IntegrationTest, Http10WithHostandKeepAlive) { testHttp10WithHostAndKeepAlive(); } + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET http://www.redirect.com HTTP/1.1\r\nHost: host\r\n\r\n", + &response, true); + EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); +} -TEST_P(IntegrationTest, NoHost) { testNoHost(); } +TEST_P(IntegrationTest, AbsolutePathWithPort) { + // Configure www.namewithport.com:1234 to send a redirect, and ensure the redirect is + // encountered via absolute URL with a port. + config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::ALL); + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse( + lookupPort("http"), "GET http://www.namewithport.com:1234 HTTP/1.1\r\nHost: host\r\n\r\n", + &response, true); + EXPECT_FALSE(response.find("HTTP/1.1 404 Not Found\r\n") == 0); +} -TEST_P(IntegrationTest, BadPath) { testBadPath(); } +TEST_P(IntegrationTest, AbsolutePathWithoutPort) { + // Add a restrictive default match, to avoid the request hitting the * / catchall. + config_helper_.setDefaultHostAndRoute("foo.com", "/found"); + // Set a matcher for www.namewithport.com:1234 and verify http://www.namewithport.com does not + // match + config_helper_.addRoute("www.namewithport.com:1234", "/", "cluster_0", true, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::ALL); + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); + initialize(); + std::string response; + sendRawHttpAndWaitForResponse(lookupPort("http"), + "GET http://www.namewithport.com HTTP/1.1\r\nHost: host\r\n\r\n", + &response, true); + EXPECT_TRUE(response.find("HTTP/1.1 404 Not Found\r\n") == 0) << response; +} -TEST_P(IntegrationTest, AbsolutePath) { testAbsolutePath(); } +// Ensure that connect behaves the same with allow_absolute_url enabled and without +TEST_P(IntegrationTest, Connect) { + const std::string& request = "CONNECT www.somewhere.com:80 HTTP/1.1\r\nHost: host\r\n\r\n"; + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v2::Bootstrap& bootstrap) -> void { + // Clone the whole listener. + auto static_resources = bootstrap.mutable_static_resources(); + auto* old_listener = static_resources->mutable_listeners(0); + auto* cloned_listener = static_resources->add_listeners(); + cloned_listener->CopyFrom(*old_listener); + old_listener->set_name("http_forward"); + }); + // Set the first listener to allow absolute URLs. + config_helper_.addConfigModifier(&setAllowAbsoluteUrl); + initialize(); -TEST_P(IntegrationTest, AbsolutePathWithPort) { testAbsolutePathWithPort(); } + std::string response1; + sendRawHttpAndWaitForResponse(lookupPort("http"), request.c_str(), &response1, true); -TEST_P(IntegrationTest, AbsolutePathWithoutPort) { testAbsolutePathWithoutPort(); } + std::string response2; + sendRawHttpAndWaitForResponse(lookupPort("http_forward"), request.c_str(), &response2, true); -TEST_P(IntegrationTest, Connect) { testConnect(); } + EXPECT_EQ(normalizeDate(response1), normalizeDate(response2)); +} TEST_P(IntegrationTest, ValidZeroLengthContent) { testValidZeroLengthContent(); } @@ -240,7 +495,27 @@ TEST_P(IntegrationTest, LargeHeadersRejected) { testLargeRequestHeaders(62, 60); TEST_P(IntegrationTest, LargeHeadersAccepted) { testLargeRequestHeaders(62, 63); } -TEST_P(IntegrationTest, UpstreamProtocolError) { testUpstreamProtocolError(); } +TEST_P(IntegrationTest, UpstreamProtocolError) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto encoder_decoder = codec_client_->startRequest(Http::TestHeaderMapImpl{ + {":method", "GET"}, {":path", "/test/long/url"}, {":authority", "host"}}); + auto response = std::move(encoder_decoder.second); + + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + // TODO(mattklein123): Waiting for exact amount of data is a hack. This needs to + // be fixed. + std::string data; + ASSERT_TRUE(fake_upstream_connection->waitForData(187, &data)); + ASSERT_TRUE(fake_upstream_connection->write("bad protocol data!")); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + codec_client_->waitForDisconnect(); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); +} TEST_P(IntegrationTest, TestHead) { initialize(); diff --git a/test/integration/integration_test.h b/test/integration/integration_test.h index 96f17288e1712..40d32f782a582 100644 --- a/test/integration/integration_test.h +++ b/test/integration/integration_test.h @@ -4,6 +4,7 @@ #include "gtest/gtest.h" +// A test class for testing HTTP/1.1 upstream and downstreams namespace Envoy { class IntegrationTest : public HttpIntegrationTest, public testing::TestWithParam { From 9a4c7787050eb031d9a59528144eb19770070663 Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Wed, 30 Jan 2019 16:23:23 -0500 Subject: [PATCH 2/4] test: moving common H1/H2 tests to their own file. Signed-off-by: Alyssa Wilk --- test/integration/BUILD | 18 + test/integration/http2_integration_test.cc | 80 +- .../http2_upstream_integration_test.cc | 32 - test/integration/http_integration.cc | 720 ----------------- test/integration/http_integration.h | 31 +- .../idle_timeout_integration_test.cc | 79 ++ test/integration/integration_test.cc | 114 ++- test/integration/protocol_integration_test.cc | 748 ++++++++++++++++++ 8 files changed, 907 insertions(+), 915 deletions(-) create mode 100644 test/integration/protocol_integration_test.cc diff --git a/test/integration/BUILD b/test/integration/BUILD index 41ea56a0626c2..1a152464f9707 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -158,6 +158,23 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "protocol_integration_test", + srcs = [ + "protocol_integration_test.cc", + ], + shard_count = 3, + deps = [ + ":http_protocol_integration_lib", + "//source/common/http:header_map_lib", + "//source/extensions/filters/http/buffer:config", + "//source/extensions/filters/http/dynamo:config", + "//source/extensions/filters/http/health_check:config", + "//test/integration/filters:random_pause_filter_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "http2_upstream_integration_test", srcs = [ @@ -244,6 +261,7 @@ envoy_cc_test_library( envoy_cc_test( name = "idle_timeout_integration_test", srcs = ["idle_timeout_integration_test.cc"], + shard_count = 2, deps = [ ":http_protocol_integration_lib", "//test/test_common:test_time_lib", diff --git a/test/integration/http2_integration_test.cc b/test/integration/http2_integration_test.cc index cf011c258b9cc..2804776e5d09f 100644 --- a/test/integration/http2_integration_test.cc +++ b/test/integration/http2_integration_test.cc @@ -22,28 +22,6 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, Http2IntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -TEST_P(Http2IntegrationTest, RouterNotFound) { testRouterNotFound(); } - -TEST_P(Http2IntegrationTest, RouterNotFoundBodyNoBuffer) { testRouterNotFoundWithBody(); } - -TEST_P(Http2IntegrationTest, RouterClusterNotFound404) { testRouterClusterNotFound404(); } - -TEST_P(Http2IntegrationTest, RouterClusterNotFound503) { testRouterClusterNotFound503(); } - -TEST_P(Http2IntegrationTest, RouterRedirect) { testRouterRedirect(); } - -TEST_P(Http2IntegrationTest, ValidZeroLengthContent) { testValidZeroLengthContent(); } - -TEST_P(Http2IntegrationTest, InvalidContentLength) { testInvalidContentLength(); } - -TEST_P(Http2IntegrationTest, MultipleContentLengths) { testMultipleContentLengths(); } - -TEST_P(Http2IntegrationTest, ComputedHealthCheck) { testComputedHealthCheck(); } - -TEST_P(Http2IntegrationTest, AddEncodedTrailers) { testAddEncodedTrailers(); } - -TEST_P(Http2IntegrationTest, DrainClose) { testDrainClose(); } - TEST_P(Http2IntegrationTest, RouterRequestAndResponseWithBodyNoBuffer) { testRouterRequestAndResponseWithBody(1024, 512, false); } @@ -61,10 +39,6 @@ TEST_P(Http2IntegrationTest, RouterRequestAndResponseLargeHeaderNoBuffer) { testRouterRequestAndResponseWithBody(1024, 512, true); } -TEST_P(Http2IntegrationTest, ShutdownWithActiveConnPoolConnections) { - testRequestAndResponseShutdownWithActiveConnection(); -} - TEST_P(Http2IntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { testRouterUpstreamDisconnectBeforeRequestComplete(); } @@ -85,16 +59,10 @@ TEST_P(Http2IntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { testRouterUpstreamResponseBeforeRequestComplete(); } -TEST_P(Http2IntegrationTest, TwoRequests) { testTwoRequests(); } - -TEST_P(Http2IntegrationTest, TwoRequestsWithForcedBackup) { testTwoRequests(true); } - TEST_P(Http2IntegrationTest, Retry) { testRetry(); } TEST_P(Http2IntegrationTest, RetryAttemptCount) { testRetryAttemptCountHeader(); } -TEST_P(Http2IntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } - static std::string response_metadata_filter = R"EOF( name: response-metadata-filter config: {} @@ -446,25 +414,19 @@ TEST_P(Http2MetadataIntegrationTest, ProxyMultipleMetadataReachSizeLimit) { ASSERT_FALSE(response->complete()); } -TEST_P(Http2IntegrationTest, EnvoyHandlingDuplicate100Continue) { - testEnvoyHandling100Continue(true); -} - -TEST_P(Http2IntegrationTest, EnvoyProxyingEarly100Continue) { testEnvoyProxying100Continue(true); } - -TEST_P(Http2IntegrationTest, EnvoyProxyingLate100Continue) { testEnvoyProxying100Continue(false); } - -TEST_P(Http2IntegrationTest, RetryHittingBufferLimit) { testRetryHittingBufferLimit(); } - -TEST_P(Http2IntegrationTest, HittingDecoderFilterLimit) { testHittingDecoderFilterLimit(); } - -TEST_P(Http2IntegrationTest, HittingEncoderFilterLimit) { testHittingEncoderFilterLimit(); } - -TEST_P(Http2IntegrationTest, GrpcRouterNotFound) { testGrpcRouterNotFound(); } - -TEST_P(Http2IntegrationTest, RetryHostPredicateFilter) { testRetryHostPredicateFilter(); } +TEST_P(Http2IntegrationTest, GrpcRouterNotFound) { + config_helper_.setDefaultHostAndRoute("foo.com", "/found"); + initialize(); -TEST_P(Http2IntegrationTest, RetryPriority) { testRetryPriority(); } + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "POST", "/service/notfound", "", downstream_protocol_, version_, "host", + Http::Headers::get().ContentTypeValues.Grpc); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + EXPECT_EQ(Http::Headers::get().ContentTypeValues.Grpc, + response->headers().ContentType()->value().c_str()); + EXPECT_STREQ("12", response->headers().GrpcStatus()->value().c_str()); +} TEST_P(Http2IntegrationTest, GrpcRetry) { testGrpcRetry(); } @@ -472,16 +434,6 @@ TEST_P(Http2IntegrationTest, LargeHeadersInvokeResetStream) { testLargeRequestHe TEST_P(Http2IntegrationTest, LargeHeadersAcceptedIfConfigured) { testLargeRequestHeaders(62, 63); } -TEST_P(Http2IntegrationTest, EncodingHeaderOnlyResponse) { testHeadersOnlyFilterEncoding(); } - -TEST_P(Http2IntegrationTest, DecodingHeaderOnlyResponse) { testHeadersOnlyFilterDecoding(); } - -TEST_P(Http2IntegrationTest, DecodingHeaderOnlyInterleaved) { testHeadersOnlyFilterInterleaved(); } - -TEST_P(Http2IntegrationTest, DownstreamResetBeforeResponseComplete) { - testDownstreamResetBeforeResponseComplete(); -} - TEST_P(Http2IntegrationTest, BadMagic) { initialize(); Buffer::OwnedImpl buffer("hello"); @@ -569,14 +521,6 @@ TEST_P(Http2IntegrationTest, GrpcRequestTimeout) { EXPECT_LT(0, test_server_->counter("cluster.cluster_0.upstream_rq_timeout")->value()); } -// Tests idle timeout behaviour with single request and validates that idle timer kicks in -// after given timeout. -TEST_P(Http2IntegrationTest, IdleTimoutBasic) { testIdleTimeoutBasic(); } - -// Tests idle timeout behaviour with multiple requests and validates that idle timer kicks in -// after both the requests are done. -TEST_P(Http2IntegrationTest, IdleTimeoutWithTwoRequests) { testIdleTimeoutWithTwoRequests(); } - // Interleave two requests and responses and make sure that idle timeout is handled correctly. TEST_P(Http2IntegrationTest, IdleTimeoutWithSimultaneousRequests) { FakeHttpConnectionPtr fake_upstream_connection1; diff --git a/test/integration/http2_upstream_integration_test.cc b/test/integration/http2_upstream_integration_test.cc index 0e0ba792cb485..a872a3fc3f23b 100644 --- a/test/integration/http2_upstream_integration_test.cc +++ b/test/integration/http2_upstream_integration_test.cc @@ -16,16 +16,6 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, Http2UpstreamIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -TEST_P(Http2UpstreamIntegrationTest, RouterNotFound) { testRouterNotFound(); } - -TEST_P(Http2UpstreamIntegrationTest, RouterRedirect) { testRouterRedirect(); } - -TEST_P(Http2UpstreamIntegrationTest, ComputedHealthCheck) { testComputedHealthCheck(); } - -TEST_P(Http2UpstreamIntegrationTest, AddEncodedTrailers) { testAddEncodedTrailers(); } - -TEST_P(Http2UpstreamIntegrationTest, DrainClose) { testDrainClose(); } - TEST_P(Http2UpstreamIntegrationTest, RouterRequestAndResponseWithBodyNoBuffer) { testRouterRequestAndResponseWithBody(1024, 512, false); } @@ -58,32 +48,10 @@ TEST_P(Http2UpstreamIntegrationTest, RouterUpstreamResponseBeforeRequestComplete testRouterUpstreamResponseBeforeRequestComplete(); } -TEST_P(Http2UpstreamIntegrationTest, TwoRequests) { testTwoRequests(); } - TEST_P(Http2UpstreamIntegrationTest, Retry) { testRetry(); } -TEST_P(Http2UpstreamIntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } - -TEST_P(Http2UpstreamIntegrationTest, EnvoyHandlingDuplicate100Continue) { - testEnvoyHandling100Continue(true); -} - -TEST_P(Http2UpstreamIntegrationTest, EnvoyProxyingEarly100Continue) { - testEnvoyProxying100Continue(true); -} - -TEST_P(Http2UpstreamIntegrationTest, EnvoyProxyingLate100Continue) { - testEnvoyProxying100Continue(false); -} - -TEST_P(Http2UpstreamIntegrationTest, RetryHittingBufferLimit) { testRetryHittingBufferLimit(); } - TEST_P(Http2UpstreamIntegrationTest, GrpcRetry) { testGrpcRetry(); } -TEST_P(Http2UpstreamIntegrationTest, DownstreamResetBeforeResponseComplete) { - testDownstreamResetBeforeResponseComplete(); -} - TEST_P(Http2UpstreamIntegrationTest, Trailers) { testTrailers(1024, 2048); } // Ensure Envoy handles streaming requests and responses simultaneously. diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index be7c1cc488cec..b924002b8bcee 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -354,141 +354,6 @@ void HttpIntegrationTest::testRouterHeaderOnlyRequestAndResponse( checkSimpleRequestSuccess(0U, 0U, response.get()); } -void HttpIntegrationTest::testRequestAndResponseShutdownWithActiveConnection() { - auto response = makeHeaderOnlyRequest(nullptr, 0); - // Shut down the server with active connection pool connections. - fake_upstreams_[0]->set_allow_unexpected_disconnects(true); - test_server_.reset(); - checkSimpleRequestSuccess(0U, 0U, response.get()); -} - -// Change the default route to be restrictive, and send a request to an alternate route. -void HttpIntegrationTest::testRouterNotFound() { - config_helper_.setDefaultHostAndRoute("foo.com", "/found"); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "GET", "/notfound", "", downstream_protocol_, version_); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("404", response->headers().Status()->value().c_str()); -} - -// Change the default route to be restrictive, and send a POST to an alternate route. -void HttpIntegrationTest::testRouterNotFoundWithBody() { - config_helper_.setDefaultHostAndRoute("foo.com", "/found"); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "POST", "/notfound", "foo", downstream_protocol_, version_); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("404", response->headers().Status()->value().c_str()); -} - -// Add a route that uses unknown cluster (expect 404 Not Found). -void HttpIntegrationTest::testRouterClusterNotFound404() { - config_helper_.addRoute("foo.com", "/unknown", "unknown_cluster", false, - envoy::api::v2::route::RouteAction::NOT_FOUND, - envoy::api::v2::route::VirtualHost::NONE); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "GET", "/unknown", "", downstream_protocol_, version_, "foo.com"); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("404", response->headers().Status()->value().c_str()); -} - -// Add a route that uses unknown cluster (expect 503 Service Unavailable). -void HttpIntegrationTest::testRouterClusterNotFound503() { - config_helper_.addRoute("foo.com", "/unknown", "unknown_cluster", false, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::NONE); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "GET", "/unknown", "", downstream_protocol_, version_, "foo.com"); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); -} - -// Add a route which redirects HTTP to HTTPS, and verify Envoy sends a 301 -void HttpIntegrationTest::testRouterRedirect() { - config_helper_.addRoute("www.redirect.com", "/", "cluster_0", true, - envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, - envoy::api::v2::route::VirtualHost::ALL); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "GET", "/foo", "", downstream_protocol_, version_, "www.redirect.com"); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("301", response->headers().Status()->value().c_str()); - EXPECT_STREQ("https://www.redirect.com/foo", - response->headers().get(Http::Headers::get().Location)->value().c_str()); -} - -// Add a health check filter and verify correct computation of health based on upstream status. -void HttpIntegrationTest::testComputedHealthCheck() { - config_helper_.addFilter(R"EOF( -name: envoy.health_check -config: - pass_through_mode: false - cluster_min_healthy_percentages: - example_cluster_name: { value: 75 } -)EOF"); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = codec_client_->makeHeaderOnlyRequest(Http::TestHeaderMapImpl{ - {":method", "GET"}, {":path", "/healthcheck"}, {":scheme", "http"}, {":authority", "host"}}); - response->waitForEndStream(); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); -} - -void HttpIntegrationTest::testAddEncodedTrailers() { - config_helper_.addFilter(R"EOF( -name: add-trailers-filter -config: {} -)EOF"); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 128); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - upstream_request_->encodeData(128, true); - response->waitForEndStream(); - - if (upstreamProtocol() == FakeHttpConnection::Type::HTTP2) { - EXPECT_STREQ("decode", upstream_request_->trailers()->GrpcMessage()->value().c_str()); - } - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); - if (downstream_protocol_ == Http::CodecClient::Type::HTTP2) { - EXPECT_STREQ("encode", response->trailers()->GrpcMessage()->value().c_str()); - } -} - -// Add a health check filter and verify correct behavior when draining. -void HttpIntegrationTest::testDrainClose() { - config_helper_.addFilter(ConfigHelper::DEFAULT_HEALTH_CHECK_FILTER); - initialize(); - - test_server_->drainManager().draining_ = true; - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); - response->waitForEndStream(); - codec_client_->waitForDisconnect(); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); - if (downstream_protocol_ == Http::CodecClient::Type::HTTP2) { - EXPECT_TRUE(codec_client_->sawGoAway()); - } - - test_server_->drainManager().draining_ = false; -} - void HttpIntegrationTest::testRouterUpstreamDisconnectBeforeRequestComplete() { initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -773,235 +638,6 @@ void HttpIntegrationTest::testGrpcRetry() { } } -// Verifies that a retry priority can be configured and affect the host selected during retries. -// The retry priority will always target P1, which would otherwise never be hit due to P0 being -// healthy. -void HttpIntegrationTest::testRetryPriority() { - const Upstream::HealthyLoad healthy_priority_load({0u, 100u}); - const Upstream::DegradedLoad degraded_priority_load({0u, 100u}); - NiceMock retry_priority(healthy_priority_load, - degraded_priority_load); - Upstream::MockRetryPriorityFactory factory(retry_priority); - - Registry::InjectFactory inject_factory(factory); - - envoy::api::v2::route::RetryPolicy retry_policy; - retry_policy.mutable_retry_priority()->set_name(factory.name()); - - // Add route with custom retry policy - config_helper_.addRoute("host", "/test_retry", "cluster_0", false, - envoy::api::v2::route::RouteAction::NOT_FOUND, - envoy::api::v2::route::VirtualHost::NONE, retry_policy); - - // Use load assignments instead of static hosts. Necessary in order to use priorities. - config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { - auto cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); - auto load_assignment = cluster->mutable_load_assignment(); - load_assignment->set_cluster_name(cluster->name()); - const auto& host_address = cluster->hosts(0).socket_address().address(); - - for (int i = 0; i < 2; ++i) { - auto locality = load_assignment->add_endpoints(); - locality->set_priority(i); - locality->mutable_locality()->set_region("region"); - locality->mutable_locality()->set_zone("zone"); - locality->mutable_locality()->set_sub_zone("sub_zone" + std::to_string(i)); - auto lb_endpoint = locality->add_lb_endpoints(); - lb_endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address( - host_address); - lb_endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value( - 0); - } - - cluster->clear_hosts(); - }); - - fake_upstreams_count_ = 2; - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/test_retry"}, - {":scheme", "http"}, - {":authority", "host"}, - {"x-forwarded-for", "10.0.0.1"}, - {"x-envoy-retry-on", "5xx"}}, - 1024); - - // Note how we're expecting each upstream request to hit the same upstream. - waitForNextUpstreamRequest(0); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - - if (fake_upstreams_[0]->httpType() == FakeHttpConnection::Type::HTTP1) { - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - ASSERT_TRUE(fake_upstreams_[1]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); - } else { - ASSERT_TRUE(upstream_request_->waitForReset()); - } - - waitForNextUpstreamRequest(1); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); - - response->waitForEndStream(); - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_EQ(1024U, upstream_request_->bodyLength()); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); - EXPECT_EQ(512U, response->body().size()); -} - -// -// Verifies that a retry host filter can be configured and affect the host selected during retries. -// The predicate will keep track of the first host attempted, and attempt to route all requests to -// the same host. With a total of two upstream hosts, this should result in us continuously sending -// requests to the same host. -void HttpIntegrationTest::testRetryHostPredicateFilter() { - TestHostPredicateFactory predicate_factory; - Registry::InjectFactory inject_factory(predicate_factory); - - envoy::api::v2::route::RetryPolicy retry_policy; - retry_policy.add_retry_host_predicate()->set_name(predicate_factory.name()); - - // Add route with custom retry policy - config_helper_.addRoute("host", "/test_retry", "cluster_0", false, - envoy::api::v2::route::RouteAction::NOT_FOUND, - envoy::api::v2::route::VirtualHost::NONE, retry_policy); - - // We want to work with a cluster with two hosts. - config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { - auto* new_host = bootstrap.mutable_static_resources()->mutable_clusters(0)->add_hosts(); - new_host->MergeFrom(bootstrap.static_resources().clusters(0).hosts(0)); - }); - fake_upstreams_count_ = 2; - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/test_retry"}, - {":scheme", "http"}, - {":authority", "host"}, - {"x-forwarded-for", "10.0.0.1"}, - {"x-envoy-retry-on", "5xx"}}, - 1024); - - // Note how we're expecting each upstream request to hit the same upstream. - auto upstream_idx = waitForNextUpstreamRequest({0, 1}); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - - if (fake_upstreams_[upstream_idx]->httpType() == FakeHttpConnection::Type::HTTP1) { - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - ASSERT_TRUE(fake_upstreams_[upstream_idx]->waitForHttpConnection(*dispatcher_, - fake_upstream_connection_)); - } else { - ASSERT_TRUE(upstream_request_->waitForReset()); - } - - waitForNextUpstreamRequest(upstream_idx); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); - - response->waitForEndStream(); - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_EQ(1024U, upstream_request_->bodyLength()); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); - EXPECT_EQ(512U, response->body().size()); -} - -// Very similar set-up to testRetry but with a 16k request the request will not -// be buffered and the 503 will be returned to the user. -void HttpIntegrationTest::testRetryHittingBufferLimit() { - config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}, - {"x-forwarded-for", "10.0.0.1"}, - {"x-envoy-retry-on", "5xx"}}, - 1024 * 65); - waitForNextUpstreamRequest(); - - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, true); - - response->waitForEndStream(); - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_EQ(66560U, upstream_request_->bodyLength()); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); -} - -// Test hitting the dynamo filter with too many request bytes to buffer. Ensure the connection -// manager sends a 413. -void HttpIntegrationTest::testHittingDecoderFilterLimit() { - config_helper_.addFilter("{ name: envoy.http_dynamo_filter, config: {} }"); - config_helper_.setBufferLimits(1024, 1024); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Envoy will likely connect and proxy some unspecified amount of data before - // hitting the buffer limit and disconnecting. Ignore this if it happens. - fake_upstreams_[0]->set_allow_unexpected_disconnects(true); - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/dynamo/url"}, - {":scheme", "http"}, - {":authority", "host"}, - {"x-forwarded-for", "10.0.0.1"}, - {"x-envoy-retry-on", "5xx"}}, - 1024 * 65); - - response->waitForEndStream(); - // With HTTP/1 there's a possible race where if the connection backs up early, - // the 413-and-connection-close may be sent while the body is still being - // sent, resulting in a write error and the connection being closed before the - // response is read. - if (downstream_protocol_ == Http::CodecClient::Type::HTTP2) { - ASSERT_TRUE(response->complete()); - } - if (response->complete()) { - EXPECT_STREQ("413", response->headers().Status()->value().c_str()); - } -} - -// Test hitting the dynamo filter with too many response bytes to buffer. Given the request headers -// are sent on early, the stream/connection will be reset. -void HttpIntegrationTest::testHittingEncoderFilterLimit() { - config_helper_.addFilter("{ name: envoy.http_dynamo_filter, config: {} }"); - config_helper_.setBufferLimits(1024, 1024); - initialize(); - - // Send the request. - codec_client_ = makeHttpConnection(lookupPort("http")); - auto encoder_decoder = codec_client_->startRequest(default_request_headers_); - auto downstream_request = &encoder_decoder.first; - auto response = std::move(encoder_decoder.second); - Buffer::OwnedImpl data("{\"TableName\":\"locations\"}"); - codec_client_->sendData(*downstream_request, data, true); - waitForNextUpstreamRequest(); - - // Send the response headers. - upstream_request_->encodeHeaders(default_response_headers_, false); - - // Now send an overly large response body. At some point, too much data will - // be buffered, the stream will be reset, and the connection will disconnect. - fake_upstreams_[0]->set_allow_unexpected_disconnects(true); - upstream_request_->encodeData(1024 * 65, false); - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - - response->waitForEndStream(); - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("500", response->headers().Status()->value().c_str()); -} - void HttpIntegrationTest::testEnvoyHandling100Continue(bool additional_continue_from_upstream, const std::string& via) { initialize(); @@ -1115,137 +751,6 @@ void HttpIntegrationTest::testEnvoyProxying100Continue(bool continue_before_upst EXPECT_STREQ("200", response->headers().Status()->value().c_str()); } -void HttpIntegrationTest::testIdleTimeoutBasic() { - config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { - auto* static_resources = bootstrap.mutable_static_resources(); - auto* cluster = static_resources->mutable_clusters(0); - auto* http_protocol_options = cluster->mutable_common_http_protocol_options(); - auto* idle_time_out = http_protocol_options->mutable_idle_timeout(); - std::chrono::milliseconds timeout(1000); - auto seconds = std::chrono::duration_cast(timeout); - idle_time_out->set_seconds(seconds.count()); - }); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); - waitForNextUpstreamRequest(); - - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); - response->waitForEndStream(); - - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); - - // Do not send any requests and validate if idle time out kicks in. - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 1); -} - -void HttpIntegrationTest::testIdleTimeoutWithTwoRequests() { - config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { - auto* static_resources = bootstrap.mutable_static_resources(); - auto* cluster = static_resources->mutable_clusters(0); - auto* http_protocol_options = cluster->mutable_common_http_protocol_options(); - auto* idle_time_out = http_protocol_options->mutable_idle_timeout(); - std::chrono::milliseconds timeout(1000); - auto seconds = std::chrono::duration_cast(timeout); - idle_time_out->set_seconds(seconds.count()); - }); - - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Request 1. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); - waitForNextUpstreamRequest(); - - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); - response->waitForEndStream(); - - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); - - // Request 2. - response = codec_client_->makeRequestWithBody(default_request_headers_, 512); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(1024, true); - response->waitForEndStream(); - - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_TRUE(response->complete()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 2); - - // Do not send any requests and validate if idle time out kicks in. - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 1); -} - -// This is a regression for https://github.com/envoyproxy/envoy/issues/2715 and validates that a -// pending request is not sent on a connection that has been half-closed. -void HttpIntegrationTest::testUpstreamDisconnectWithTwoRequests() { - config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { - auto* static_resources = bootstrap.mutable_static_resources(); - auto* cluster = static_resources->mutable_clusters(0); - // Ensure we only have one connection upstream, one request active at a time. - cluster->mutable_max_requests_per_connection()->set_value(1); - auto* circuit_breakers = cluster->mutable_circuit_breakers(); - circuit_breakers->add_thresholds()->mutable_max_connections()->set_value(1); - }); - initialize(); - fake_upstreams_[0]->set_allow_unexpected_disconnects(true); - - codec_client_ = makeHttpConnection(lookupPort("http")); - - // Request 1. - auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); - waitForNextUpstreamRequest(); - - // Request 2. - IntegrationCodecClientPtr codec_client2 = makeHttpConnection(lookupPort("http")); - auto response2 = codec_client2->makeRequestWithBody(default_request_headers_, 512); - - // Validate one request active, the other pending. - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); - test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_pending_active", 1); - - // Response 1. - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(512, true); - ASSERT_TRUE(fake_upstream_connection_->close()); - response->waitForEndStream(); - - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); - - // Response 2. - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - fake_upstream_connection_.reset(); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(default_response_headers_, false); - upstream_request_->encodeData(1024, true); - response2->waitForEndStream(); - codec_client2->close(); - - EXPECT_TRUE(upstream_request_->complete()); - EXPECT_TRUE(response2->complete()); - EXPECT_STREQ("200", response2->headers().Status()->value().c_str()); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 2); - test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 2); -} - void HttpIntegrationTest::testTwoRequests(bool network_backup) { // if network_backup is false, this simply tests that Envoy can handle multiple // requests on a connection. @@ -1292,76 +797,6 @@ void HttpIntegrationTest::testTwoRequests(bool network_backup) { EXPECT_EQ(1024U, response->body().size()); } -void HttpIntegrationTest::testValidZeroLengthContent() { - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - - Http::TestHeaderMapImpl request_headers{{":method", "POST"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}, - {"content-length", "0"}}; - auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); - - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); -} - -void HttpIntegrationTest::testInvalidContentLength() { - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - - auto encoder_decoder = - codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/test/long/url"}, - {":authority", "host"}, - {"content-length", "-1"}}); - auto response = std::move(encoder_decoder.second); - - if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { - codec_client_->waitForDisconnect(); - } else { - response->waitForReset(); - codec_client_->close(); - } - - if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("400", response->headers().Status()->value().c_str()); - } else { - ASSERT_TRUE(response->reset()); - EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->reset_reason()); - } -} - -void HttpIntegrationTest::testMultipleContentLengths() { - initialize(); - codec_client_ = makeHttpConnection(lookupPort("http")); - auto encoder_decoder = - codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/test/long/url"}, - {":authority", "host"}, - {"content-length", "3,2"}}); - auto response = std::move(encoder_decoder.second); - - if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { - codec_client_->waitForDisconnect(); - } else { - response->waitForReset(); - codec_client_->close(); - } - - if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("400", response->headers().Status()->value().c_str()); - } else { - ASSERT_TRUE(response->reset()); - EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->reset_reason()); - } -} - void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t max_size) { // `size` parameter is the size of the header that will be added to the // request. The actual request byte size will exceed `size` due to keys @@ -1398,160 +833,6 @@ void HttpIntegrationTest::testLargeRequestHeaders(uint32_t size, uint32_t max_si } } -void HttpIntegrationTest::testHeadersOnlyFilterEncoding() { - config_helper_.addFilter(R"EOF( -name: encode-headers-only -)EOF"); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}}, - 128); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - response->waitForEndStream(); - EXPECT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); - if (upstreamProtocol() == FakeHttpConnection::Type::HTTP1) { - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - } else { - ASSERT_TRUE(upstream_request_->waitForReset()); - ASSERT_TRUE(fake_upstream_connection_->close()); - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - } - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); - EXPECT_EQ(0, response->body().size()); -} - -void HttpIntegrationTest::testHeadersOnlyFilterDecoding() { - config_helper_.addFilter(R"EOF( -name: decode-headers-only -)EOF"); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}}, - 128); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - upstream_request_->encodeData(128, true); - response->waitForEndStream(); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); - EXPECT_EQ(128, response->body().size()); -} - -void HttpIntegrationTest::testHeadersOnlyFilterEncodingIntermediateFilters() { - config_helper_.addFilter(R"EOF( -name: passthrough-filter -)EOF"); - config_helper_.addFilter(R"EOF( -name: encode-headers-only -)EOF"); - config_helper_.addFilter(R"EOF( -name: passthrough-filter -)EOF"); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}}, - 128); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - response->waitForEndStream(); - if (upstreamProtocol() == FakeHttpConnection::Type::HTTP1) { - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - } else { - ASSERT_TRUE(upstream_request_->waitForReset()); - ASSERT_TRUE(fake_upstream_connection_->close()); - ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); - } - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); - EXPECT_EQ(0, response->body().size()); -} - -void HttpIntegrationTest::testHeadersOnlyFilterDecodingIntermediateFilters() { - config_helper_.addFilter(R"EOF( -name: passthrough-filter -)EOF"); - config_helper_.addFilter(R"EOF( -name: decode-headers-only -)EOF"); - config_helper_.addFilter(R"EOF( -name: passthrough-filter -)EOF"); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - auto response = - codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}}, - 128); - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - upstream_request_->encodeData(128, true); - response->waitForEndStream(); - - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); - EXPECT_EQ(128, response->body().size()); -} - -// Verifies behavior when request data is encoded after the request has been -// turned into a headers-only request and the response has already begun. -void HttpIntegrationTest::testHeadersOnlyFilterInterleaved() { - config_helper_.addFilter(R"EOF( -name: decode-headers-only -)EOF"); - initialize(); - - codec_client_ = makeHttpConnection(lookupPort("http")); - - // First send the request headers. The filter should turn this into a header-only - // request. - auto encoder_decoder = - codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "GET"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}}); - request_encoder_ = &encoder_decoder.first; - auto response = std::move(encoder_decoder.second); - - // Wait for the upstream request and begin sending a response with end_stream = false. - waitForNextUpstreamRequest(); - upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); - - // Simulate additional data after the request has been turned into a headers only request. - Buffer::OwnedImpl data(std::string(128, 'a')); - request_encoder_->encodeData(data, false); - - // End the response. - upstream_request_->encodeData(128, true); - - response->waitForEndStream(); - EXPECT_TRUE(response->complete()); - EXPECT_STREQ("503", response->headers().Status()->value().c_str()); - EXPECT_EQ(0, upstream_request_->body().length()); -} - void HttpIntegrationTest::testDownstreamResetBeforeResponseComplete() { initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -1635,5 +916,4 @@ std::string HttpIntegrationTest::listenerStatPrefix(const std::string& stat_name } return "listener.[__1]_0." + stat_name; } - } // namespace Envoy diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index a0d2b0d2bc8a9..92da330e16b94 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -129,17 +129,14 @@ class HttpIntegrationTest : public BaseIntegrationTest { IntegrationStreamDecoderPtr makeHeaderOnlyRequest(ConnectionCreationFunction* create_connection, int upstream_index); - void testRouterRedirect(); - void testRouterNotFound(); - void testRouterNotFoundWithBody(); - void testRouterClusterNotFound404(); - void testRouterClusterNotFound503(); void testRouterRequestAndResponseWithBody(uint64_t request_size, uint64_t response_size, bool big_header, ConnectionCreationFunction* creator = nullptr); void testRouterHeaderOnlyRequestAndResponse(ConnectionCreationFunction* creator = nullptr, int upstream_index = 0); void testRequestAndResponseShutdownWithActiveConnection(); + + // Disconnect tests void testRouterUpstreamDisconnectBeforeRequestComplete(); void testRouterUpstreamDisconnectBeforeResponseComplete(ConnectionCreationFunction* creator = nullptr); @@ -148,36 +145,16 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testRouterDownstreamDisconnectBeforeResponseComplete( ConnectionCreationFunction* creator = nullptr); void testRouterUpstreamResponseBeforeRequestComplete(); + void testTwoRequests(bool force_network_backup = false); void testLargeRequestHeaders(uint32_t size, uint32_t max_size = 60); - void testIdleTimeoutBasic(); - void testIdleTimeoutWithTwoRequests(); - void testIdleTimerDisabled(); - void testUpstreamDisconnectWithTwoRequests(); - void testHeadersOnlyFilterEncoding(); - void testHeadersOnlyFilterDecoding(); - void testHeadersOnlyFilterEncodingIntermediateFilters(); - void testHeadersOnlyFilterDecodingIntermediateFilters(); - void testHeadersOnlyFilterInterleaved(); - - // Test that a request returns the same content with both allow_absolute_urls enabled and - // allow_absolute_urls disabled - void testDefaultHost(); - void testValidZeroLengthContent(); - void testInvalidContentLength(); - void testMultipleContentLengths(); - void testComputedHealthCheck(); + void testAddEncodedTrailers(); - void testDrainClose(); void testRetry(); void testRetryHittingBufferLimit(); void testRetryAttemptCountHeader(); void testGrpcRouterNotFound(); void testGrpcRetry(); - void testRetryPriority(); - void testRetryHostPredicateFilter(); - void testHittingDecoderFilterLimit(); - void testHittingEncoderFilterLimit(); void testEnvoyHandling100Continue(bool additional_continue_from_upstream = false, const std::string& via = ""); void testEnvoyProxying100Continue(bool continue_before_upstream_complete = false, diff --git a/test/integration/idle_timeout_integration_test.cc b/test/integration/idle_timeout_integration_test.cc index 299c6582f28a2..edaa2e363cc83 100644 --- a/test/integration/idle_timeout_integration_test.cc +++ b/test/integration/idle_timeout_integration_test.cc @@ -82,6 +82,85 @@ INSTANTIATE_TEST_SUITE_P(Protocols, IdleTimeoutIntegrationTest, testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()), HttpProtocolIntegrationTest::protocolTestParamsToString); +// Tests idle timeout behaviour with single request and validates that idle timer kicks in +// after given timeout. +TEST_P(IdleTimeoutIntegrationTest, TimeoutBasic) { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* static_resources = bootstrap.mutable_static_resources(); + auto* cluster = static_resources->mutable_clusters(0); + auto* http_protocol_options = cluster->mutable_common_http_protocol_options(); + auto* idle_time_out = http_protocol_options->mutable_idle_timeout(); + std::chrono::milliseconds timeout(1000); + auto seconds = std::chrono::duration_cast(timeout); + idle_time_out->set_seconds(seconds.count()); + }); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + response->waitForEndStream(); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + + // Do not send any requests and validate if idle time out kicks in. + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 1); +} + +// Tests idle timeout behaviour with multiple requests and validates that idle timer kicks in +// after both the requests are done. +TEST_P(IdleTimeoutIntegrationTest, IdleTimeoutWithTwoRequests) { + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* static_resources = bootstrap.mutable_static_resources(); + auto* cluster = static_resources->mutable_clusters(0); + auto* http_protocol_options = cluster->mutable_common_http_protocol_options(); + auto* idle_time_out = http_protocol_options->mutable_idle_timeout(); + std::chrono::milliseconds timeout(1000); + auto seconds = std::chrono::duration_cast(timeout); + idle_time_out->set_seconds(seconds.count()); + }); + + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Request 1. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + response->waitForEndStream(); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); + + // Request 2. + response = codec_client_->makeRequestWithBody(default_request_headers_, 512); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(1024, true); + response->waitForEndStream(); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 2); + + // Do not send any requests and validate if idle time out kicks in. + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_idle_timeout", 1); +} + // Per-stream idle timeout after having sent downstream headers. TEST_P(IdleTimeoutIntegrationTest, PerStreamIdleTimeoutAfterDownstreamHeaders) { enable_per_stream_idle_timeout_ = true; diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index 5e250d981e753..080fe0b968e16 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -53,16 +53,6 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, IntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -TEST_P(IntegrationTest, RouterNotFound) { testRouterNotFound(); } - -TEST_P(IntegrationTest, RouterNotFoundBodyNoBuffer) { testRouterNotFoundWithBody(); } - -TEST_P(IntegrationTest, RouterClusterNotFound404) { testRouterClusterNotFound404(); } - -TEST_P(IntegrationTest, RouterClusterNotFound503) { testRouterClusterNotFound503(); } - -TEST_P(IntegrationTest, RouterRedirect) { testRouterRedirect(); } - TEST_P(IntegrationTest, RouterDirectResponse) { const std::string body = "Response body"; const std::string file_path = TestEnvironment::writeStringToFileForTest("test_envoy", body); @@ -104,12 +94,6 @@ TEST_P(IntegrationTest, RouterDirectResponse) { EXPECT_EQ(body, response->body()); } -TEST_P(IntegrationTest, ComputedHealthCheck) { testComputedHealthCheck(); } - -TEST_P(IntegrationTest, AddEncodedTrailers) { testAddEncodedTrailers(); } - -TEST_P(IntegrationTest, DrainClose) { testDrainClose(); } - TEST_P(IntegrationTest, ConnectionClose) { config_helper_.addFilter(ConfigHelper::DEFAULT_HEALTH_CHECK_FILTER); initialize(); @@ -144,10 +128,6 @@ TEST_P(IntegrationTest, RouterHeaderOnlyRequestAndResponseNoBuffer) { testRouterHeaderOnlyRequestAndResponse(); } -TEST_P(IntegrationTest, ShutdownWithActiveConnPoolConnections) { - testRequestAndResponseShutdownWithActiveConnection(); -} - TEST_P(IntegrationTest, RouterUpstreamDisconnectBeforeRequestcomplete) { testRouterUpstreamDisconnectBeforeRequestComplete(); } @@ -168,22 +148,6 @@ TEST_P(IntegrationTest, RouterUpstreamResponseBeforeRequestComplete) { testRouterUpstreamResponseBeforeRequestComplete(); } -TEST_P(IntegrationTest, Retry) { testRetry(); } - -TEST_P(IntegrationTest, RetryAttemptCount) { testRetryAttemptCountHeader(); } - -TEST_P(IntegrationTest, RetryHostPredicateFilter) { testRetryHostPredicateFilter(); } - -TEST_P(IntegrationTest, RetryPriority) { testRetryPriority(); } - -TEST_P(IntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } - -TEST_P(IntegrationTest, EnvoyHandlingDuplicate100Continues) { testEnvoyHandling100Continue(true); } - -TEST_P(IntegrationTest, EnvoyProxyingEarly100Continue) { testEnvoyProxying100Continue(true); } - -TEST_P(IntegrationTest, EnvoyProxyingLate100Continue) { testEnvoyProxying100Continue(false); } - TEST_P(IntegrationTest, EnvoyProxyingEarly100ContinueWithEncoderFilter) { testEnvoyProxying100Continue(true, true); } @@ -192,39 +156,61 @@ TEST_P(IntegrationTest, EnvoyProxyingLate100ContinueWithEncoderFilter) { testEnvoyProxying100Continue(false, true); } -TEST_P(IntegrationTest, TwoRequests) { testTwoRequests(); } - -TEST_P(IntegrationTest, TwoRequestsWithForcedBackup) { testTwoRequests(true); } - +// This is a regression for https://github.com/envoyproxy/envoy/issues/2715 and validates that a +// pending request is not sent on a connection that has been half-closed. TEST_P(IntegrationTest, UpstreamDisconnectWithTwoRequests) { - testUpstreamDisconnectWithTwoRequests(); -} - -TEST_P(IntegrationTest, EncodingHeaderOnlyResponse) { testHeadersOnlyFilterEncoding(); } + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* static_resources = bootstrap.mutable_static_resources(); + auto* cluster = static_resources->mutable_clusters(0); + // Ensure we only have one connection upstream, one request active at a time. + cluster->mutable_max_requests_per_connection()->set_value(1); + auto* circuit_breakers = cluster->mutable_circuit_breakers(); + circuit_breakers->add_thresholds()->mutable_max_connections()->set_value(1); + }); + initialize(); + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); -TEST_P(IntegrationTest, DecodingHeaderOnlyResponse) { testHeadersOnlyFilterDecoding(); } + codec_client_ = makeHttpConnection(lookupPort("http")); -TEST_P(IntegrationTest, EncodingHeaderOnlyResponseIntermediateFilters) { - testHeadersOnlyFilterEncodingIntermediateFilters(); -} + // Request 1. + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 1024); + waitForNextUpstreamRequest(); -TEST_P(IntegrationTest, DecodingHeaderOnlyResponseIntermediateFilters) { - testHeadersOnlyFilterDecodingIntermediateFilters(); -} + // Request 2. + IntegrationCodecClientPtr codec_client2 = makeHttpConnection(lookupPort("http")); + auto response2 = codec_client2->makeRequestWithBody(default_request_headers_, 512); -TEST_P(IntegrationTest, DecodingHeaderOnlyInterleaved) { testHeadersOnlyFilterInterleaved(); } + // Validate one request active, the other pending. + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_pending_active", 1); -TEST_P(IntegrationTest, RetryHittingBufferLimit) { testRetryHittingBufferLimit(); } + // Response 1. + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + ASSERT_TRUE(fake_upstream_connection_->close()); + response->waitForEndStream(); -TEST_P(IntegrationTest, HittingDecoderFilterLimit) { testHittingDecoderFilterLimit(); } + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 1); -// Tests idle timeout behaviour with single request and validates that idle timer kicks in -// after given timeout. -TEST_P(IntegrationTest, IdleTimoutBasic) { testIdleTimeoutBasic(); } + // Response 2. + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + fake_upstream_connection_.reset(); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(1024, true); + response2->waitForEndStream(); + codec_client2->close(); -// Tests idle timeout behaviour with multiple requests and validates that idle timer kicks in -// after both the requests are done. -TEST_P(IntegrationTest, IdleTimeoutWithTwoRequests) { testIdleTimeoutWithTwoRequests(); } + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_TRUE(response2->complete()); + EXPECT_STREQ("200", response2->headers().Status()->value().c_str()); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_cx_total", 2); + test_server_->waitForCounterGe("cluster.cluster_0.upstream_rq_200", 2); +} // Test hitting the bridge filter with too many response bytes to buffer. Given // the headers are not proxied, the connection manager will send a local error reply. @@ -258,8 +244,6 @@ TEST_P(IntegrationTest, HittingGrpcFilterLimitBufferingHeaders) { HeaderValueOf(Headers::get().GrpcStatus, "2")); // Unknown gRPC error } -TEST_P(IntegrationTest, HittingEncoderFilterLimit) { testHittingEncoderFilterLimit(); } - TEST_P(IntegrationTest, BadFirstline) { initialize(); std::string response; @@ -485,12 +469,6 @@ TEST_P(IntegrationTest, Connect) { EXPECT_EQ(normalizeDate(response1), normalizeDate(response2)); } -TEST_P(IntegrationTest, ValidZeroLengthContent) { testValidZeroLengthContent(); } - -TEST_P(IntegrationTest, InvalidContentLength) { testInvalidContentLength(); } - -TEST_P(IntegrationTest, MultipleContentLengths) { testMultipleContentLengths(); } - TEST_P(IntegrationTest, LargeHeadersRejected) { testLargeRequestHeaders(62, 60); } TEST_P(IntegrationTest, LargeHeadersAccepted) { testLargeRequestHeaders(62, 63); } diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc new file mode 100644 index 0000000000000..afa7f18108f14 --- /dev/null +++ b/test/integration/protocol_integration_test.cc @@ -0,0 +1,748 @@ +#include +#include +#include +#include +#include +#include + +#include "envoy/buffer/buffer.h" +#include "envoy/event/dispatcher.h" +#include "envoy/http/header_map.h" +#include "envoy/registry/registry.h" + +#include "common/api/api_impl.h" +#include "common/buffer/buffer_impl.h" +#include "common/common/fmt.h" +#include "common/common/thread_annotations.h" +#include "common/http/headers.h" +#include "common/network/utility.h" +#include "common/protobuf/utility.h" +#include "common/runtime/runtime_impl.h" +#include "common/upstream/upstream_impl.h" + +#include "test/common/upstream/utility.h" +#include "test/integration/autonomous_upstream.h" +#include "test/integration/http_integration.h" +#include "test/integration/http_protocol_integration.h" +#include "test/integration/test_host_predicate_config.h" +#include "test/integration/utility.h" +#include "test/mocks/upstream/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/registry.h" + +#include "gtest/gtest.h" + +using testing::_; +using testing::AnyNumber; +using testing::HasSubstr; +using testing::Invoke; +using testing::Not; + +namespace Envoy { + +// Tests for DownstreamProtocolIntegrationTest will be run with all protocols +// (H1/H2 downstream) but only H1 upstreams. +// +// This is useful for things which will likely not differ based on upstream +// behavior, for example "how does Envoy handle duplicate content lengths from +// downstream"? +typedef HttpProtocolIntegrationTest DownstreamProtocolIntegrationTest; + +// Tests for ProtocolIntegrationTest will be run with the full mesh of H1/H2 +// downstream and H1/H2 upstreams. +typedef HttpProtocolIntegrationTest ProtocolIntegrationTest; + +TEST_P(ProtocolIntegrationTest, ShutdownWithActiveConnPoolConnections) { + auto response = makeHeaderOnlyRequest(nullptr, 0); + // Shut down the server with active connection pool connections. + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); + test_server_.reset(); + checkSimpleRequestSuccess(0U, 0U, response.get()); +} + +// Change the default route to be restrictive, and send a request to an alternate route. +TEST_P(ProtocolIntegrationTest, RouterNotFound) { + config_helper_.setDefaultHostAndRoute("foo.com", "/found"); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/notfound", "", downstream_protocol_, version_); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("404", response->headers().Status()->value().c_str()); +} + +// Change the default route to be restrictive, and send a POST to an alternate route. +TEST_P(DownstreamProtocolIntegrationTest, RouterNotFoundBodyNoBuffer) { + config_helper_.setDefaultHostAndRoute("foo.com", "/found"); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "POST", "/notfound", "foo", downstream_protocol_, version_); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("404", response->headers().Status()->value().c_str()); +} + +// Add a route that uses unknown cluster (expect 404 Not Found). +TEST_P(DownstreamProtocolIntegrationTest, RouterClusterNotFound404) { + config_helper_.addRoute("foo.com", "/unknown", "unknown_cluster", false, + envoy::api::v2::route::RouteAction::NOT_FOUND, + envoy::api::v2::route::VirtualHost::NONE); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/unknown", "", downstream_protocol_, version_, "foo.com"); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("404", response->headers().Status()->value().c_str()); +} + +// Add a route that uses unknown cluster (expect 503 Service Unavailable). +TEST_P(DownstreamProtocolIntegrationTest, RouterClusterNotFound503) { + config_helper_.addRoute("foo.com", "/unknown", "unknown_cluster", false, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::NONE); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/unknown", "", downstream_protocol_, version_, "foo.com"); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); +} + +// Add a route which redirects HTTP to HTTPS, and verify Envoy sends a 301 +TEST_P(ProtocolIntegrationTest, RouterRedirect) { + config_helper_.addRoute("www.redirect.com", "/", "cluster_0", true, + envoy::api::v2::route::RouteAction::SERVICE_UNAVAILABLE, + envoy::api::v2::route::VirtualHost::ALL); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/foo", "", downstream_protocol_, version_, "www.redirect.com"); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("301", response->headers().Status()->value().c_str()); + EXPECT_STREQ("https://www.redirect.com/foo", + response->headers().get(Http::Headers::get().Location)->value().c_str()); +} + +// Add a health check filter and verify correct computation of health based on upstream status. +TEST_P(ProtocolIntegrationTest, ComputedHealthCheck) { + config_helper_.addFilter(R"EOF( +name: envoy.health_check +config: + pass_through_mode: false + cluster_min_healthy_percentages: + example_cluster_name: { value: 75 } +)EOF"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestHeaderMapImpl{ + {":method", "GET"}, {":path", "/healthcheck"}, {":scheme", "http"}, {":authority", "host"}}); + response->waitForEndStream(); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); +} + +TEST_P(ProtocolIntegrationTest, AddEncodedTrailers) { + config_helper_.addFilter(R"EOF( +name: add-trailers-filter +config: {} +)EOF"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeRequestWithBody(default_request_headers_, 128); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + upstream_request_->encodeData(128, true); + response->waitForEndStream(); + + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP2) { + EXPECT_STREQ("decode", upstream_request_->trailers()->GrpcMessage()->value().c_str()); + } + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); + if (downstream_protocol_ == Http::CodecClient::Type::HTTP2) { + EXPECT_STREQ("encode", response->trailers()->GrpcMessage()->value().c_str()); + } +} + +// Add a health check filter and verify correct behavior when draining. +TEST_P(ProtocolIntegrationTest, DrainClose) { + config_helper_.addFilter(ConfigHelper::DEFAULT_HEALTH_CHECK_FILTER); + initialize(); + + test_server_->drainManager().draining_ = true; + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + response->waitForEndStream(); + codec_client_->waitForDisconnect(); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + if (downstream_protocol_ == Http::CodecClient::Type::HTTP2) { + EXPECT_TRUE(codec_client_->sawGoAway()); + } + + test_server_->drainManager().draining_ = false; +} + +TEST_P(ProtocolIntegrationTest, Retry) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + {"x-envoy-retry-on", "5xx"}}, + 1024); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + + if (fake_upstreams_[0]->httpType() == FakeHttpConnection::Type::HTTP1) { + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + } else { + ASSERT_TRUE(upstream_request_->waitForReset()); + } + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + + response->waitForEndStream(); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(1024U, upstream_request_->bodyLength()); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + EXPECT_EQ(512U, response->body().size()); +} + +// Tests that the x-envoy-attempt-count header is properly set on the upstream request +// and updated after the request is retried. +TEST_P(DownstreamProtocolIntegrationTest, RetryAttemptCountHeader) { + config_helper_.addRoute("host", "/test_retry", "cluster_0", false, + envoy::api::v2::route::RouteAction::NOT_FOUND, + envoy::api::v2::route::VirtualHost::NONE, {}, true); + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test_retry"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + {"x-envoy-retry-on", "5xx"}}, + 1024); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + + EXPECT_EQ(atoi(upstream_request_->headers().EnvoyAttemptCount()->value().c_str()), 1); + + if (fake_upstreams_[0]->httpType() == FakeHttpConnection::Type::HTTP1) { + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + } else { + ASSERT_TRUE(upstream_request_->waitForReset()); + } + waitForNextUpstreamRequest(); + EXPECT_EQ(atoi(upstream_request_->headers().EnvoyAttemptCount()->value().c_str()), 2); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + + response->waitForEndStream(); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(1024U, upstream_request_->bodyLength()); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + EXPECT_EQ(512U, response->body().size()); +} + +// Verifies that a retry priority can be configured and affect the host selected during retries. +// The retry priority will always target P1, which would otherwise never be hit due to P0 being +// healthy. +TEST_P(DownstreamProtocolIntegrationTest, RetryPriority) { + const Upstream::HealthyLoad healthy_priority_load({0u, 100u}); + const Upstream::DegradedLoad degraded_priority_load({0u, 100u}); + NiceMock retry_priority(healthy_priority_load, + degraded_priority_load); + Upstream::MockRetryPriorityFactory factory(retry_priority); + + Registry::InjectFactory inject_factory(factory); + + envoy::api::v2::route::RetryPolicy retry_policy; + retry_policy.mutable_retry_priority()->set_name(factory.name()); + + // Add route with custom retry policy + config_helper_.addRoute("host", "/test_retry", "cluster_0", false, + envoy::api::v2::route::RouteAction::NOT_FOUND, + envoy::api::v2::route::VirtualHost::NONE, retry_policy); + + // Use load assignments instead of static hosts. Necessary in order to use priorities. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + auto load_assignment = cluster->mutable_load_assignment(); + load_assignment->set_cluster_name(cluster->name()); + const auto& host_address = cluster->hosts(0).socket_address().address(); + + for (int i = 0; i < 2; ++i) { + auto locality = load_assignment->add_endpoints(); + locality->set_priority(i); + locality->mutable_locality()->set_region("region"); + locality->mutable_locality()->set_zone("zone"); + locality->mutable_locality()->set_sub_zone("sub_zone" + std::to_string(i)); + auto lb_endpoint = locality->add_lb_endpoints(); + lb_endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_address( + host_address); + lb_endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address()->set_port_value( + 0); + } + + cluster->clear_hosts(); + }); + + fake_upstreams_count_ = 2; + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test_retry"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + {"x-envoy-retry-on", "5xx"}}, + 1024); + + // Note how we're expecting each upstream request to hit the same upstream. + waitForNextUpstreamRequest(0); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + + if (fake_upstreams_[0]->httpType() == FakeHttpConnection::Type::HTTP1) { + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + ASSERT_TRUE(fake_upstreams_[1]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + } else { + ASSERT_TRUE(upstream_request_->waitForReset()); + } + + waitForNextUpstreamRequest(1); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + + response->waitForEndStream(); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(1024U, upstream_request_->bodyLength()); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + EXPECT_EQ(512U, response->body().size()); +} + +// +// Verifies that a retry host filter can be configured and affect the host selected during retries. +// The predicate will keep track of the first host attempted, and attempt to route all requests to +// the same host. With a total of two upstream hosts, this should result in us continuously sending +// requests to the same host. +TEST_P(DownstreamProtocolIntegrationTest, RetryHostPredicateFilter) { + TestHostPredicateFactory predicate_factory; + Registry::InjectFactory inject_factory(predicate_factory); + + envoy::api::v2::route::RetryPolicy retry_policy; + retry_policy.add_retry_host_predicate()->set_name(predicate_factory.name()); + + // Add route with custom retry policy + config_helper_.addRoute("host", "/test_retry", "cluster_0", false, + envoy::api::v2::route::RouteAction::NOT_FOUND, + envoy::api::v2::route::VirtualHost::NONE, retry_policy); + + // We want to work with a cluster with two hosts. + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* new_host = bootstrap.mutable_static_resources()->mutable_clusters(0)->add_hosts(); + new_host->MergeFrom(bootstrap.static_resources().clusters(0).hosts(0)); + }); + fake_upstreams_count_ = 2; + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test_retry"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + {"x-envoy-retry-on", "5xx"}}, + 1024); + + // Note how we're expecting each upstream request to hit the same upstream. + auto upstream_idx = waitForNextUpstreamRequest({0, 1}); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + + if (fake_upstreams_[upstream_idx]->httpType() == FakeHttpConnection::Type::HTTP1) { + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + ASSERT_TRUE(fake_upstreams_[upstream_idx]->waitForHttpConnection(*dispatcher_, + fake_upstream_connection_)); + } else { + ASSERT_TRUE(upstream_request_->waitForReset()); + } + + waitForNextUpstreamRequest(upstream_idx); + upstream_request_->encodeHeaders(default_response_headers_, false); + upstream_request_->encodeData(512, true); + + response->waitForEndStream(); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(1024U, upstream_request_->bodyLength()); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); + EXPECT_EQ(512U, response->body().size()); +} + +// Very similar set-up to testRetry but with a 16k request the request will not +// be buffered and the 503 will be returned to the user. +TEST_P(ProtocolIntegrationTest, RetryHittingBufferLimit) { + config_helper_.setBufferLimits(1024, 1024); // Set buffer limits upstream and downstream. + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + {"x-envoy-retry-on", "5xx"}}, + 1024 * 65); + waitForNextUpstreamRequest(); + + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, true); + + response->waitForEndStream(); + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(66560U, upstream_request_->bodyLength()); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); +} + +// Test hitting the dynamo filter with too many request bytes to buffer. Ensure the connection +// manager sends a 413. +TEST_P(DownstreamProtocolIntegrationTest, HittingDecoderFilterLimit) { + config_helper_.addFilter("{ name: envoy.http_dynamo_filter, config: {} }"); + config_helper_.setBufferLimits(1024, 1024); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // Envoy will likely connect and proxy some unspecified amount of data before + // hitting the buffer limit and disconnecting. Ignore this if it happens. + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/dynamo/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + {"x-envoy-retry-on", "5xx"}}, + 1024 * 65); + + response->waitForEndStream(); + // With HTTP/1 there's a possible race where if the connection backs up early, + // the 413-and-connection-close may be sent while the body is still being + // sent, resulting in a write error and the connection being closed before the + // response is read. + if (downstream_protocol_ == Http::CodecClient::Type::HTTP2) { + ASSERT_TRUE(response->complete()); + } + if (response->complete()) { + EXPECT_STREQ("413", response->headers().Status()->value().c_str()); + } +} + +// Test hitting the dynamo filter with too many response bytes to buffer. Given the request headers +// are sent on early, the stream/connection will be reset. +TEST_P(DownstreamProtocolIntegrationTest, HittingEncoderFilterLimit) { + config_helper_.addFilter("{ name: envoy.http_dynamo_filter, config: {} }"); + config_helper_.setBufferLimits(1024, 1024); + initialize(); + + // Send the request. + codec_client_ = makeHttpConnection(lookupPort("http")); + auto encoder_decoder = codec_client_->startRequest(default_request_headers_); + auto downstream_request = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + Buffer::OwnedImpl data("{\"TableName\":\"locations\"}"); + codec_client_->sendData(*downstream_request, data, true); + waitForNextUpstreamRequest(); + + // Send the response headers. + upstream_request_->encodeHeaders(default_response_headers_, false); + + // Now send an overly large response body. At some point, too much data will + // be buffered, the stream will be reset, and the connection will disconnect. + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); + upstream_request_->encodeData(1024 * 65, false); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("500", response->headers().Status()->value().c_str()); +} + +TEST_P(ProtocolIntegrationTest, EnvoyHandling100Continue) { testEnvoyHandling100Continue(); } + +TEST_P(ProtocolIntegrationTest, EnvoyHandlingDuplicate100Continue) { + testEnvoyHandling100Continue(true); +} + +TEST_P(ProtocolIntegrationTest, EnvoyProxyingEarly100Continue) { + testEnvoyProxying100Continue(true); +} + +TEST_P(ProtocolIntegrationTest, EnvoyProxyingLate100Continue) { + testEnvoyProxying100Continue(false); +} + +TEST_P(ProtocolIntegrationTest, TwoRequests) { testTwoRequests(); } + +TEST_P(ProtocolIntegrationTest, TwoRequestsWithForcedBackup) { testTwoRequests(true); } + +TEST_P(DownstreamProtocolIntegrationTest, ValidZeroLengthContent) { + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + Http::TestHeaderMapImpl request_headers{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"content-length", "0"}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("200", response->headers().Status()->value().c_str()); +} + +TEST_P(DownstreamProtocolIntegrationTest, InvalidContentLength) { + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto encoder_decoder = + codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":authority", "host"}, + {"content-length", "-1"}}); + auto response = std::move(encoder_decoder.second); + + if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { + codec_client_->waitForDisconnect(); + } else { + response->waitForReset(); + codec_client_->close(); + } + + if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("400", response->headers().Status()->value().c_str()); + } else { + ASSERT_TRUE(response->reset()); + EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->reset_reason()); + } +} + +TEST_P(DownstreamProtocolIntegrationTest, MultipleContentLengths) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto encoder_decoder = + codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":authority", "host"}, + {"content-length", "3,2"}}); + auto response = std::move(encoder_decoder.second); + + if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { + codec_client_->waitForDisconnect(); + } else { + response->waitForReset(); + codec_client_->close(); + } + + if (downstream_protocol_ == Http::CodecClient::Type::HTTP1) { + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("400", response->headers().Status()->value().c_str()); + } else { + ASSERT_TRUE(response->reset()); + EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->reset_reason()); + } +} + +TEST_P(DownstreamProtocolIntegrationTest, HeadersOnlyFilterEncoding) { + config_helper_.addFilter(R"EOF( +name: encode-headers-only +)EOF"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}, + 128); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + response->waitForEndStream(); + EXPECT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP1) { + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + } else { + ASSERT_TRUE(upstream_request_->waitForReset()); + ASSERT_TRUE(fake_upstream_connection_->close()); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + } + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); + EXPECT_EQ(0, response->body().size()); +} + +TEST_P(DownstreamProtocolIntegrationTest, HeadersOnlyFilterDecoding) { + config_helper_.addFilter(R"EOF( +name: decode-headers-only +)EOF"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}, + 128); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + upstream_request_->encodeData(128, true); + response->waitForEndStream(); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); + EXPECT_EQ(128, response->body().size()); +} + +TEST_P(DownstreamProtocolIntegrationTest, HeadersOnlyFilterEncodingIntermediateFilters) { + config_helper_.addFilter(R"EOF( +name: passthrough-filter +)EOF"); + config_helper_.addFilter(R"EOF( +name: encode-headers-only +)EOF"); + config_helper_.addFilter(R"EOF( +name: passthrough-filter +)EOF"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}, + 128); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + response->waitForEndStream(); + if (upstreamProtocol() == FakeHttpConnection::Type::HTTP1) { + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + } else { + ASSERT_TRUE(upstream_request_->waitForReset()); + ASSERT_TRUE(fake_upstream_connection_->close()); + ASSERT_TRUE(fake_upstream_connection_->waitForDisconnect()); + } + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); + EXPECT_EQ(0, response->body().size()); +} + +TEST_P(DownstreamProtocolIntegrationTest, HeadersOnlyFilterDecodingIntermediateFilters) { + config_helper_.addFilter(R"EOF( +name: passthrough-filter +)EOF"); + config_helper_.addFilter(R"EOF( +name: decode-headers-only +)EOF"); + config_helper_.addFilter(R"EOF( +name: passthrough-filter +)EOF"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeRequestWithBody(Http::TestHeaderMapImpl{{":method", "POST"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}, + 128); + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + upstream_request_->encodeData(128, true); + response->waitForEndStream(); + + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); + EXPECT_EQ(128, response->body().size()); +} + +// Verifies behavior when request data is encoded after the request has been +// turned into a headers-only request and the response has already begun. +TEST_P(DownstreamProtocolIntegrationTest, HeadersOnlyFilterInterleaved) { + config_helper_.addFilter(R"EOF( +name: decode-headers-only +)EOF"); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + // First send the request headers. The filter should turn this into a header-only + // request. + auto encoder_decoder = + codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}}); + request_encoder_ = &encoder_decoder.first; + auto response = std::move(encoder_decoder.second); + + // Wait for the upstream request and begin sending a response with end_stream = false. + waitForNextUpstreamRequest(); + upstream_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, false); + + // Simulate additional data after the request has been turned into a headers only request. + Buffer::OwnedImpl data(std::string(128, 'a')); + request_encoder_->encodeData(data, false); + + // End the response. + upstream_request_->encodeData(128, true); + + response->waitForEndStream(); + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); + EXPECT_EQ(0, upstream_request_->body().length()); +} + +// For tests which focus on downstream-to-Envoy behavior, and don't need to be +// run with both HTTP/1 and HTTP/2 upstreams. +INSTANTIATE_TEST_SUITE_P(Protocols, DownstreamProtocolIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams( + {Http::CodecClient::Type::HTTP1, Http::CodecClient::Type::HTTP2}, + {FakeHttpConnection::Type::HTTP1})), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +INSTANTIATE_TEST_SUITE_P(Protocols, ProtocolIntegrationTest, + testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParams()), + HttpProtocolIntegrationTest::protocolTestParamsToString); + +} // namespace Envoy From 7f7a3e6793c5d6a00180d1f2038fd532962793ec Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Wed, 13 Feb 2019 13:45:00 -0500 Subject: [PATCH 3/4] Riiight, not all tests are under integration Signed-off-by: Alyssa Wilk --- test/integration/http_integration.cc | 37 +++++++++++-------- test/integration/http_integration.h | 3 +- test/integration/protocol_integration_test.cc | 18 +-------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 7a58c7bb04498..4da6db6e8196a 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -352,6 +352,28 @@ void HttpIntegrationTest::testRouterHeaderOnlyRequestAndResponse( checkSimpleRequestSuccess(0U, 0U, response.get()); } +// Change the default route to be restrictive, and send a request to an alternate route. +void HttpIntegrationTest::testRouterNotFound() { + config_helper_.setDefaultHostAndRoute("foo.com", "/found"); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/notfound", "", downstream_protocol_, version_); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("404", response->headers().Status()->value().c_str()); +} + +// Change the default route to be restrictive, and send a POST to an alternate route. +void HttpIntegrationTest::testRouterNotFoundWithBody() { + config_helper_.setDefaultHostAndRoute("foo.com", "/found"); + initialize(); + + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "POST", "/notfound", "foo", downstream_protocol_, version_); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("404", response->headers().Status()->value().c_str()); +} + void HttpIntegrationTest::testRouterUpstreamDisconnectBeforeRequestComplete() { initialize(); codec_client_ = makeHttpConnection(lookupPort("http")); @@ -579,21 +601,6 @@ void HttpIntegrationTest::testRetryAttemptCountHeader() { EXPECT_EQ(512U, response->body().size()); } -// Change the default route to be restrictive, and send a request to an alternate route. -void HttpIntegrationTest::testGrpcRouterNotFound() { - config_helper_.setDefaultHostAndRoute("foo.com", "/found"); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "POST", "/service/notfound", "", downstream_protocol_, version_, "host", - Http::Headers::get().ContentTypeValues.Grpc); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("200", response->headers().Status()->value().c_str()); - EXPECT_EQ(Http::Headers::get().ContentTypeValues.Grpc, - response->headers().ContentType()->value().c_str()); - EXPECT_STREQ("12", response->headers().GrpcStatus()->value().c_str()); -} - void HttpIntegrationTest::testGrpcRetry() { Http::TestHeaderMapImpl response_trailers{{"response1", "trailer1"}, {"grpc-status", "0"}}; initialize(); diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index ce7910367572e..a97c3465f70dd 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -132,6 +132,8 @@ class HttpIntegrationTest : public BaseIntegrationTest { // Sends a simple header-only HTTP request, and waits for a response. IntegrationStreamDecoderPtr makeHeaderOnlyRequest(ConnectionCreationFunction* create_connection, int upstream_index); + void testRouterNotFound(); + void testRouterNotFoundWithBody(); void testRouterRequestAndResponseWithBody(uint64_t request_size, uint64_t response_size, bool big_header, @@ -157,7 +159,6 @@ class HttpIntegrationTest : public BaseIntegrationTest { void testRetry(); void testRetryHittingBufferLimit(); void testRetryAttemptCountHeader(); - void testGrpcRouterNotFound(); void testGrpcRetry(); void testEnvoyHandling100Continue(bool additional_continue_from_upstream = false, diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index afa7f18108f14..56273d07a2d0c 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -62,25 +62,11 @@ TEST_P(ProtocolIntegrationTest, ShutdownWithActiveConnPoolConnections) { } // Change the default route to be restrictive, and send a request to an alternate route. -TEST_P(ProtocolIntegrationTest, RouterNotFound) { - config_helper_.setDefaultHostAndRoute("foo.com", "/found"); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "GET", "/notfound", "", downstream_protocol_, version_); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("404", response->headers().Status()->value().c_str()); -} +TEST_P(ProtocolIntegrationTest, RouterNotFound) { testRouterNotFound(); } // Change the default route to be restrictive, and send a POST to an alternate route. TEST_P(DownstreamProtocolIntegrationTest, RouterNotFoundBodyNoBuffer) { - config_helper_.setDefaultHostAndRoute("foo.com", "/found"); - initialize(); - - BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( - lookupPort("http"), "POST", "/notfound", "foo", downstream_protocol_, version_); - ASSERT_TRUE(response->complete()); - EXPECT_STREQ("404", response->headers().Status()->value().c_str()); + testRouterNotFoundWithBody(); } // Add a route that uses unknown cluster (expect 404 Not Found). From abbd6f00ee811e0232b7c116fcd0cbb5ed3325a7 Mon Sep 17 00:00:00 2001 From: Alyssa Wilk Date: Thu, 14 Feb 2019 08:49:18 -0500 Subject: [PATCH 4/4] comments Signed-off-by: Alyssa Wilk --- test/integration/BUILD | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/integration/BUILD b/test/integration/BUILD index 690b5f2cc7c25..86959cfc83b43 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -163,6 +163,8 @@ envoy_cc_test( srcs = [ "protocol_integration_test.cc", ], + # As this test has many H1/H2/v4/v6 tests it takes a while to run. + # Shard it enough to bring the run time in line with other integration tests. shard_count = 3, deps = [ ":http_protocol_integration_lib", @@ -261,6 +263,8 @@ envoy_cc_test_library( envoy_cc_test( name = "idle_timeout_integration_test", srcs = ["idle_timeout_integration_test.cc"], + # As this test has many pauses for idle timeouts, it takes a while to run. + # Shard it enough to bring the run time in line with other integration tests. shard_count = 2, deps = [ ":http_protocol_integration_lib",