Skip to content

Commit 0e738ed

Browse files
committed
test: add integration tests for on_demand VHDS with request bodies
Add integration tests verifying VHDS on-demand updates work correctly with request bodies for both HTTP/1.1 and HTTP/2. The tests use HttpProtocolIntegrationTest parameterization to automatically run for both downstream protocols (HTTP/1.1 and HTTP/2), ensuring the fix for #17891 works correctly across different HTTP protocol versions. Tests verify that: - Request bodies are preserved when triggering on-demand VHDS updates - Stream recreation works correctly after VHDS updates complete - Both HTTP/1.1 and HTTP/2 protocols handle bodies correctly The test instantiation uses getProtocolTestParamsWithoutHTTP3() which returns parameter combinations for both HTTP/1.1 and HTTP/2 downstream protocols, so each test runs multiple times (once per protocol combination). Complements the unit tests in d39afea and provides end-to-end verification for #17891. Risk Level: Low Testing: New integration tests parameterized for HTTP/1.1 and HTTP/2 Signed-off-by: William Dauchy <[email protected]>
1 parent 6602938 commit 0e738ed

File tree

2 files changed

+209
-0
lines changed

2 files changed

+209
-0
lines changed

test/extensions/filters/http/on_demand/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ envoy_extension_cc_test(
4242
"//source/extensions/filters/http/on_demand:on_demand_update_lib",
4343
"//test/config:v2_link_hacks",
4444
"//test/integration:http_integration_lib",
45+
"//test/integration:http_protocol_integration_lib",
4546
"//test/integration:scoped_rds_lib",
4647
"//test/integration:vhds_lib",
4748
"@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto",

test/extensions/filters/http/on_demand/on_demand_integration_test.cc

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "test/common/grpc/grpc_client_integration.h"
1212
#include "test/config/v2_link_hacks.h"
1313
#include "test/integration/http_integration.h"
14+
#include "test/integration/http_protocol_integration.h"
1415
#include "test/integration/scoped_rds.h"
1516
#include "test/integration/vhds.h"
1617
#include "test/test_common/printers.h"
@@ -761,5 +762,212 @@ TEST_P(OnDemandVhdsIntegrationTest, AttemptAddingDuplicateDomainNames) {
761762
cleanupUpstreamAndDownstream();
762763
}
763764

765+
// Test class for VHDS on-demand updates with request bodies
766+
class OnDemandVhdsWithBodyIntegrationTest
767+
: public testing::TestWithParam<
768+
std::tuple<HttpProtocolTestParams, std::tuple<Network::Address::IpVersion,
769+
Grpc::ClientType, Grpc::LegacyOrUnified>>>,
770+
public HttpIntegrationTest {
771+
public:
772+
using ParamType =
773+
std::tuple<HttpProtocolTestParams,
774+
std::tuple<Network::Address::IpVersion, Grpc::ClientType, Grpc::LegacyOrUnified>>;
775+
776+
const HttpProtocolTestParams& httpProtocolParams() const { return std::get<0>(GetParam()); }
777+
778+
OnDemandVhdsWithBodyIntegrationTest()
779+
: HttpIntegrationTest(httpProtocolParams().downstream_protocol, httpProtocolParams().version,
780+
ConfigHelper::httpProxyConfig(
781+
/*downstream_is_quic=*/httpProtocolParams().downstream_protocol ==
782+
Http::CodecType::HTTP3)),
783+
use_universal_header_validator_(httpProtocolParams().use_universal_header_validator) {
784+
setupHttp2ImplOverrides(httpProtocolParams().http2_implementation);
785+
config_helper_.addRuntimeOverride("envoy.reloadable_features.enable_universal_header_validator",
786+
use_universal_header_validator_ ? "true" : "false");
787+
use_lds_ = false;
788+
config_helper_.addRuntimeOverride(
789+
"envoy.reloadable_features.unified_mux",
790+
std::get<2>(std::get<1>(GetParam())) == Grpc::LegacyOrUnified::Unified ? "true" : "false");
791+
config_helper_.prependFilter(R"EOF(
792+
name: envoy.filters.http.on_demand
793+
)EOF");
794+
}
795+
796+
void SetUp() override {
797+
setDownstreamProtocol(httpProtocolParams().downstream_protocol);
798+
setUpstreamProtocol(httpProtocolParams().upstream_protocol);
799+
}
800+
801+
void TearDown() override { cleanUpXdsConnection(); }
802+
803+
std::string virtualHostYaml(const std::string& name, const std::string& domain) {
804+
return fmt::format(R"EOF(
805+
name: {}
806+
domains: [{}]
807+
routes:
808+
- match: {{ prefix: "/" }}
809+
route: {{ cluster: "cluster_0" }}
810+
)EOF",
811+
name, domain);
812+
}
813+
814+
std::string vhdsRequestResourceName(const std::string& host_header) {
815+
return "my_route/" + host_header;
816+
}
817+
818+
envoy::config::route::v3::VirtualHost buildVirtualHost(const std::string& name,
819+
const std::string& domain) {
820+
return TestUtility::parseYaml<envoy::config::route::v3::VirtualHost>(
821+
virtualHostYaml(name, domain));
822+
}
823+
824+
void initialize() override {
825+
setUpstreamCount(2); // xds_cluster and cluster_0
826+
setUpstreamProtocol(Http::CodecType::HTTP2); // xDS uses gRPC uses HTTP2
827+
828+
const auto ip_version = httpProtocolParams().version;
829+
config_helper_.addConfigModifier([ip_version](
830+
envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
831+
// Add xds_cluster for VHDS
832+
auto* xds_cluster = bootstrap.mutable_static_resources()->add_clusters();
833+
xds_cluster->MergeFrom(ConfigHelper::buildStaticCluster(
834+
"xds_cluster", /*port=*/0, Network::Test::getLoopbackAddressString(ip_version)));
835+
ConfigHelper::setHttp2(*xds_cluster);
836+
837+
auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0);
838+
auto* filter_chain = listener->mutable_filter_chains(0);
839+
auto* config_blob = filter_chain->mutable_filters(0)->mutable_typed_config();
840+
841+
ASSERT_TRUE(config_blob->Is<envoy::extensions::filters::network::http_connection_manager::v3::
842+
HttpConnectionManager>());
843+
auto hcm_config = MessageUtil::anyConvert<
844+
envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager>(
845+
*config_blob);
846+
847+
// Use RDS instead of static route_config so Envoy connects to xDS server
848+
hcm_config.clear_route_config();
849+
auto* rds_config = hcm_config.mutable_rds();
850+
rds_config->set_route_config_name("my_route");
851+
auto* rds_config_source = rds_config->mutable_config_source();
852+
rds_config_source->mutable_api_config_source()->set_api_type(
853+
envoy::config::core::v3::ApiConfigSource::GRPC);
854+
rds_config_source->mutable_api_config_source()
855+
->add_grpc_services()
856+
->mutable_envoy_grpc()
857+
->set_cluster_name("xds_cluster");
858+
859+
config_blob->PackFrom(hcm_config);
860+
});
861+
862+
HttpIntegrationTest::initialize();
863+
864+
test_server_->waitUntilListenersReady();
865+
registerTestServerPorts({"http"});
866+
867+
// Set up xDS connection (xds_cluster is the second cluster, so it maps to fake_upstreams_[1])
868+
auto result = fake_upstreams_[1]->waitForHttpConnection(*dispatcher_, xds_connection_);
869+
RELEASE_ASSERT(result, result.message());
870+
result = xds_connection_->waitForNewStream(*dispatcher_, xds_stream_);
871+
RELEASE_ASSERT(result, result.message());
872+
xds_stream_->startGrpcStream();
873+
874+
EXPECT_TRUE(compareSotwDiscoveryRequest(Config::TestTypeUrl::get().RouteConfiguration, "",
875+
{"my_route"}, true));
876+
envoy::config::route::v3::RouteConfiguration route_config;
877+
route_config.set_name("my_route");
878+
auto* vhds_config_source = route_config.mutable_vhds()->mutable_config_source();
879+
vhds_config_source->mutable_api_config_source()->set_api_type(
880+
envoy::config::core::v3::ApiConfigSource::DELTA_GRPC);
881+
vhds_config_source->mutable_api_config_source()
882+
->add_grpc_services()
883+
->mutable_envoy_grpc()
884+
->set_cluster_name("xds_cluster");
885+
sendSotwDiscoveryResponse<envoy::config::route::v3::RouteConfiguration>(
886+
Config::TestTypeUrl::get().RouteConfiguration, {route_config}, "1");
887+
888+
result = xds_connection_->waitForNewStream(*dispatcher_, vhds_stream_);
889+
RELEASE_ASSERT(result, result.message());
890+
vhds_stream_->startGrpcStream();
891+
892+
EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().VirtualHost, {}, {},
893+
vhds_stream_.get()));
894+
}
895+
896+
FakeStreamPtr vhds_stream_;
897+
898+
protected:
899+
const bool use_universal_header_validator_;
900+
};
901+
902+
INSTANTIATE_TEST_SUITE_P(
903+
ProtocolsAndGrpcTypes, OnDemandVhdsWithBodyIntegrationTest,
904+
testing::Combine(
905+
testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParamsWithoutHTTP3()),
906+
UNIFIED_LEGACY_GRPC_CLIENT_INTEGRATION_PARAMS),
907+
[](const testing::TestParamInfo<
908+
std::tuple<HttpProtocolTestParams, std::tuple<Network::Address::IpVersion, Grpc::ClientType,
909+
Grpc::LegacyOrUnified>>>& info) {
910+
return absl::StrCat(
911+
HttpProtocolIntegrationTest::protocolTestParamsToString(
912+
testing::TestParamInfo<HttpProtocolTestParams>(std::get<0>(info.param), 0)),
913+
"_",
914+
Grpc::UnifiedOrLegacyMuxIntegrationParamTest::protocolTestParamsToString(
915+
testing::TestParamInfo<
916+
std::tuple<Network::Address::IpVersion, Grpc::ClientType, Grpc::LegacyOrUnified>>(
917+
std::get<1>(info.param), 0)));
918+
});
919+
920+
// Test VHDS on-demand update with a request body
921+
TEST_P(OnDemandVhdsWithBodyIntegrationTest, VhdsOnDemandUpdateWithBody) {
922+
// Unified mux has issues processing on-demand VHDS responses in this test case.
923+
// Skip Unified mux until this is resolved.
924+
const bool is_unified_mux =
925+
std::get<2>(std::get<1>(GetParam())) == Grpc::LegacyOrUnified::Unified;
926+
if (is_unified_mux) {
927+
GTEST_SKIP() << "Unified mux does not properly handle on-demand VHDS updates in this test";
928+
}
929+
initialize();
930+
// Make a request with body to an unknown virtual host
931+
codec_client_ = makeHttpConnection(lookupPort("http"));
932+
Http::TestRequestHeaderMapImpl request_headers{{":method", "POST"},
933+
{":path", "/"},
934+
{":scheme", "http"},
935+
{":authority", "vhost.with.body"},
936+
{"x-lyft-user-id", "123"}};
937+
const std::string request_body = "test request body";
938+
IntegrationStreamDecoderPtr response =
939+
codec_client_->makeRequestWithBody(request_headers, request_body, true);
940+
941+
// Verify VHDS on-demand request is sent
942+
EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().VirtualHost,
943+
{vhdsRequestResourceName("vhost.with.body")}, {},
944+
vhds_stream_.get()));
945+
946+
// Send VHDS response with the virtual host
947+
sendDeltaDiscoveryResponse<envoy::config::route::v3::VirtualHost>(
948+
Config::TestTypeUrl::get().VirtualHost,
949+
{buildVirtualHost("my_route/vhost_with_body", "vhost.with.body")}, {}, "2",
950+
vhds_stream_.get(), {"my_route/vhost.with.body"});
951+
952+
// Wait for VHDS ACK to ensure the response is processed
953+
EXPECT_TRUE(compareDeltaDiscoveryRequest(Config::TestTypeUrl::get().VirtualHost, {}, {},
954+
vhds_stream_.get()));
955+
956+
// Wait for upstream request (cluster_0 is the first cluster, so it maps to fake_upstreams_[0])
957+
waitForNextUpstreamRequest(0);
958+
959+
// Verify the request body was received correctly
960+
EXPECT_TRUE(upstream_request_->complete());
961+
EXPECT_EQ(request_body, upstream_request_->body().toString());
962+
963+
// Send response
964+
upstream_request_->encodeHeaders(default_response_headers_, true);
965+
966+
response->waitForHeaders();
967+
EXPECT_EQ("200", response->headers().getStatusValue());
968+
969+
cleanupUpstreamAndDownstream();
970+
}
971+
764972
} // namespace
765973
} // namespace Envoy

0 commit comments

Comments
 (0)