diff --git a/test/common/integration/BUILD b/test/common/integration/BUILD index 1f5ab005c8..fddd30ff25 100644 --- a/test/common/integration/BUILD +++ b/test/common/integration/BUILD @@ -29,17 +29,45 @@ envoy_cc_test( # TODO(willengflow): Remove this once the sandboxNetwork=off works for ipv4 localhost addresses. "sandboxNetwork": "standard", }, + external_deps = [ + "abseil_strings", + ], repository = "@envoy", deps = [ - ":base_client_integration_test_lib", - "@envoy//test/common/grpc:grpc_client_integration_lib", - "@envoy//test/server:utility_lib", + ":xds_integration_test_lib", + "@envoy//test/test_common:utility_lib", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", "@envoy_api//envoy/service/runtime/v3:pkg_cc_proto", ], ) +envoy_cc_test( + name = "sds_integration_test", + srcs = [ + "sds_integration_test.cc", + ], + data = [ + "@envoy//test/config/integration/certs", + ], + exec_properties = { + # TODO(willengflow): Remove this once the sandboxNetwork=off works for ipv4 localhost addresses. + "sandboxNetwork": "standard", + }, + repository = "@envoy", + deps = [ + ":xds_integration_test_lib", + "@envoy//source/common/config:protobuf_link_hacks", + "@envoy//source/extensions/transport_sockets/tls:config", + "@envoy//source/extensions/transport_sockets/tls:context_config_lib", + "@envoy//source/extensions/transport_sockets/tls:context_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", + "@envoy_api//envoy/service/secret/v3:pkg_cc_proto", + ], +) + envoy_cc_test_library( name = "base_client_integration_test_lib", srcs = [ @@ -54,13 +82,29 @@ envoy_cc_test_library( "//library/common/http:client_lib", "//library/common/http:header_utility_lib", "//library/common/types:c_types_lib", - "@envoy//source/extensions/http/header_formatters/preserve_case:config", - "@envoy//source/extensions/http/header_formatters/preserve_case:preserve_case_formatter", "@envoy//test/common/http:common_lib", "@envoy//test/integration:http_integration_lib", ], ) +envoy_cc_test_library( + name = "xds_integration_test_lib", + srcs = [ + "xds_integration_test.cc", + ], + hdrs = [ + "xds_integration_test.h", + ], + repository = "@envoy", + deps = [ + ":base_client_integration_test_lib", + "@envoy//source/common/config:api_version_lib", + "@envoy//test/common/grpc:grpc_client_integration_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + ], +) + # interface libs for quic test server's jni implementation envoy_cc_test_library( name = "quic_test_server_interface_lib", diff --git a/test/common/integration/base_client_integration_test.cc b/test/common/integration/base_client_integration_test.cc index 191eeee99e..181f564895 100644 --- a/test/common/integration/base_client_integration_test.cc +++ b/test/common/integration/base_client_integration_test.cc @@ -1,26 +1,25 @@ #include "test/common/integration/base_client_integration_test.h" -#include "source/extensions/http/header_formatters/preserve_case/config.h" -#include "source/extensions/http/header_formatters/preserve_case/preserve_case_formatter.h" - -#include "test/common/http/common.h" - -#include "gmock/gmock.h" #include "gtest/gtest.h" #include "library/common/config/internal.h" -#include "library/common/http/header_utility.h" namespace Envoy { namespace { -void validateStreamIntel(const envoy_final_stream_intel& final_intel, bool expect_dns) { +void validateStreamIntel(const envoy_final_stream_intel& final_intel, bool expect_dns, + bool upstream_tls) { if (expect_dns) { EXPECT_NE(-1, final_intel.dns_start_ms); EXPECT_NE(-1, final_intel.dns_end_ms); } - // This test doesn't do TLS. - EXPECT_EQ(-1, final_intel.ssl_start_ms); - EXPECT_EQ(-1, final_intel.ssl_end_ms); + + if (upstream_tls) { + EXPECT_GT(final_intel.ssl_start_ms, 0); + EXPECT_GT(final_intel.ssl_end_ms, 0); + } else { + EXPECT_EQ(-1, final_intel.ssl_start_ms); + EXPECT_EQ(-1, final_intel.ssl_end_ms); + } ASSERT_NE(-1, final_intel.stream_start_ms); ASSERT_NE(-1, final_intel.connect_start_ms); @@ -72,7 +71,7 @@ void BaseClientIntegrationTest::initialize() { }); stream_prototype_->setOnComplete( [this](envoy_stream_intel, envoy_final_stream_intel final_intel) { - validateStreamIntel(final_intel, expect_dns_); + validateStreamIntel(final_intel, expect_dns_, upstream_tls_); cc_.on_complete_received_byte_count = final_intel.received_byte_count; cc_.on_complete_calls++; cc_.terminal_callback->setReady(); diff --git a/test/common/integration/rtds_integration_test.cc b/test/common/integration/rtds_integration_test.cc index 8321107374..469b96d4b1 100644 --- a/test/common/integration/rtds_integration_test.cc +++ b/test/common/integration/rtds_integration_test.cc @@ -1,50 +1,14 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/config/cluster/v3/cluster.pb.h" #include "envoy/service/runtime/v3/rtds.pb.h" -#include "source/extensions/http/header_formatters/preserve_case/preserve_case_formatter.h" - -#include "test/common/grpc/grpc_client_integration.h" -#include "test/common/http/common.h" -#include "test/common/integration/base_client_integration_test.h" -#include "test/integration/autonomous_upstream.h" -#include "test/integration/http_integration.h" -#include "test/server/utility.h" -#include "test/test_common/environment.h" +#include "test/common/integration/xds_integration_test.h" #include "test/test_common/utility.h" -#include "gmock/gmock.h" #include "gtest/gtest.h" -#include "library/common/data/utility.h" -#include "library/common/http/client.h" -#include "library/common/http/header_utility.h" -#include "library/common/types/c_types.h" namespace Envoy { namespace { -envoy::config::cluster::v3::Cluster -createSingleEndpointClusterConfig(const std::string& cluster_name, - const std::string& loopbackAddr) { - envoy::config::cluster::v3::Cluster config; - config.set_name(cluster_name); - - // Set the endpoint. - auto* load_assignment = config.mutable_load_assignment(); - load_assignment->set_cluster_name(cluster_name); - auto* endpoint = load_assignment->add_endpoints()->add_lb_endpoints()->mutable_endpoint(); - endpoint->mutable_address()->mutable_socket_address()->set_address(loopbackAddr); - endpoint->mutable_address()->mutable_socket_address()->set_port_value(0); - - // Set the protocol options. - envoy::extensions::upstreams::http::v3::HttpProtocolOptions options; - options.mutable_explicit_http_config()->mutable_http2_protocol_options(); - (*config.mutable_typed_extension_protocol_options()) - ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] - .PackFrom(options); - return config; -} - envoy::config::bootstrap::v3::LayeredRuntime layeredRuntimeConfig(const std::string& api_type) { const std::string yaml = fmt::format(R"EOF( layers: @@ -62,166 +26,41 @@ envoy::config::bootstrap::v3::LayeredRuntime layeredRuntimeConfig(const std::str transport_api_version: V3 grpc_services: envoy_grpc: - cluster_name: rtds_cluster + cluster_name: {} set_node_on_first_message_only: true - name: some_admin_layer admin_layer: {{}} )EOF", - api_type); + api_type, XDS_CLUSTER); envoy::config::bootstrap::v3::LayeredRuntime config; TestUtility::loadFromYaml(yaml, config); return config; } -envoy::config::bootstrap::v3::Admin adminConfig(const std::string& loopbackAddr) { - const std::string yaml = fmt::format(R"EOF( - access_log: - - name: envoy.access_loggers.file - typed_config: - "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog - path: "{}" - address: - socket_address: - address: {} - port_value: 0 - )EOF", - ::Platform::null_device_path, loopbackAddr); - - envoy::config::bootstrap::v3::Admin config; - TestUtility::loadFromYaml(yaml, config); - return config; -} - -class RtdsIntegrationTest : public BaseClientIntegrationTest, - public Grpc::DeltaSotwIntegrationParamTest { +class RtdsIntegrationTest : public XdsIntegrationTest { public: - RtdsIntegrationTest() : BaseClientIntegrationTest(ipVersion()) { - override_builder_config_ = true; // The builder does not yet have RTDS support. - expect_dns_ = false; // TODO(alyssawilk) debug. - create_xds_upstream_ = true; - sotw_or_delta_ = sotwOrDelta(); - scheme_ = "https"; - - if (sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedSotw || - sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedDelta) { - config_helper_.addRuntimeOverride("envoy.reloadable_features.unified_mux", "true"); - } - - // Set up the cluster config. - // - // For now, we clear the default cluster configs and add just two clusters: - // - a cluster named "base_h2" because that's what the api_listener is configured to talk to - // - an RTDS cluster, for sending and receiving RTDS config - // - // The reason we must clear the default cluster configs is because ConfigHelper::setPorts - // requires that the number of fake upstream ports equal the number of clusters in the config - // that have dynamic port configuration (i.e. port is 0). In other words, either all fake - // upstreams must be configured with a dynamic port or none of them (can't mix and match). - // - // TODO(abeyad): fix the ConfigHelper::setPorts logic to enable a subset of clusters to have - // dynamic port configuration. - - config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - const std::string loopback = loopbackAddr(); - bootstrap.mutable_static_resources()->clear_clusters(); - bootstrap.mutable_static_resources()->add_clusters()->MergeFrom( - createSingleEndpointClusterConfig("base_h2", loopback)); - bootstrap.mutable_static_resources()->add_clusters()->MergeFrom( - createSingleEndpointClusterConfig("rtds_cluster", loopback)); - }); - - // xDS upstream is created separately in the test infra, and there's only one non-xDS cluster. - setUpstreamCount(1); - - // Add the Admin config. - config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { - bootstrap.mutable_admin()->MergeFrom(adminConfig(loopbackAddr())); - }); - admin_filename_ = TestEnvironment::temporaryPath("admin_address.txt"); - setAdminAddressPathForTests(admin_filename_); - } - - void SetUp() override { - // TODO(abeyad): Add paramaterized tests for HTTP1, HTTP2, and HTTP3. - setUpstreamProtocol(Http::CodecType::HTTP2); - } - - void TearDown() override { - cleanup(); - BaseClientIntegrationTest::TearDown(); - } - - void createEnvoy() override { - BaseClientIntegrationTest::createEnvoy(); - std::string admin_str = TestEnvironment::readFileToStringForTest(admin_filename_); - auto addr = Network::Utility::parseInternetAddressAndPort(admin_str); - registerPort("admin", addr->ip()->port()); - } - - void initialize() override { - BaseClientIntegrationTest::initialize(); - // Register admin port. - // registerTestServerPorts({}); - - acceptXdsConnection(); - } - - void addRuntimeRtdsConfig() { - // Add the layered runtime config, which includes the RTDS layer. + RtdsIntegrationTest() { config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Add the layered runtime config, which includes the RTDS layer. const std::string api_type = sotw_or_delta_ == Grpc::SotwOrDelta::Sotw || sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedSotw ? "GRPC" : "DELTA_GRPC"; - bootstrap.mutable_layered_runtime()->MergeFrom(layeredRuntimeConfig(api_type)); }); } - void acceptXdsConnection() { - // Initial RTDS connection. - createXdsConnection(); - AssertionResult result = - xds_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, xds_stream_); - RELEASE_ASSERT(result, result.message()); - xds_stream_->startGrpcStream(); - } - - Network::Address::IpVersion ipVersion() const override { return std::get<0>(GetParam()); } - Grpc::ClientType clientType() const override { return std::get<1>(GetParam()); } - Grpc::SotwOrDelta sotwOrDelta() const { return std::get<2>(GetParam()); } - - std::string loopbackAddr() const { - if (ipVersion() == Network::Address::IpVersion::v6) { - return "::1"; - } - return "127.0.0.1"; - } - -protected: - std::string getRuntimeKey(const std::string& key) { - auto response = IntegrationUtil::makeSingleRequest( - lookupPort("admin"), "GET", "/runtime?format=json", "", Http::CodecType::HTTP2, version_); - EXPECT_TRUE(response->complete()); - EXPECT_EQ("200", response->headers().getStatusValue()); - Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(response->body()); - auto entries = loader->getObject("entries"); - if (entries->hasObject(key)) { - return entries->getObject(key)->getString("final_value"); - } - return ""; + void initialize() override { + BaseClientIntegrationTest::initialize(); + initializeXdsStream(); } - - uint32_t initial_load_success_{}; - std::string admin_filename_; }; INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, RtdsIntegrationTest, DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); TEST_P(RtdsIntegrationTest, RtdsReload) { - addRuntimeRtdsConfig(); initialize(); // Send a request on the data plane. diff --git a/test/common/integration/sds_integration_test.cc b/test/common/integration/sds_integration_test.cc new file mode 100644 index 0000000000..bd6bbb3b38 --- /dev/null +++ b/test/common/integration/sds_integration_test.cc @@ -0,0 +1,110 @@ +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/core/v3/config_source.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" +#include "envoy/service/secret/v3/sds.pb.h" + +#include "test/common/integration/xds_integration_test.h" +#include "test/config/integration/certs/clientcert_hash.h" +#include "test/integration/ssl_utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace { + +// Hack to force linking of the service: https://github.com/google/protobuf/issues/4221. +const envoy::service::secret::v3::SdsDummy _sds_dummy; + +class SdsIntegrationTest : public XdsIntegrationTest { +public: + SdsIntegrationTest() { + skip_tag_extraction_rule_check_ = true; + upstream_tls_ = true; + + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // Change the base_h2 cluster to use SSL and SDS. + auto* transport_socket = + bootstrap.mutable_static_resources()->mutable_clusters(0)->mutable_transport_socket(); + envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext tls_context; + tls_context.set_sni("lyft.com"); + auto* secret_config = + tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs(); + setUpSdsConfig(secret_config, "client_cert"); + + transport_socket->set_name("envoy.transport_sockets.tls"); + transport_socket->mutable_typed_config()->PackFrom(tls_context); + }); + } + + void createUpstreams() override { + // This is the fake upstream configured with SSL for the base_h2 cluster. + addFakeUpstream(Ssl::createUpstreamSslContext(context_manager_, *api_), upstreamProtocol(), + /*autonomous_upstream=*/true); + } + +protected: + void sendSdsResponse(const envoy::extensions::transport_sockets::tls::v3::Secret& secret) { + envoy::service::discovery::v3::DiscoveryResponse discovery_response; + discovery_response.set_version_info("1"); + discovery_response.set_type_url(Config::TypeUrl::get().Secret); + discovery_response.add_resources()->PackFrom(secret); + + xds_stream_->sendGrpcMessage(discovery_response); + } + + void setUpSdsConfig(envoy::extensions::transport_sockets::tls::v3::SdsSecretConfig* secret_config, + const std::string& secret_name) { + secret_config->set_name(secret_name); + auto* config_source = secret_config->mutable_sds_config(); + config_source->set_resource_api_version(envoy::config::core::v3::ApiVersion::V3); + auto* api_config_source = config_source->mutable_api_config_source(); + api_config_source->set_api_type(envoy::config::core::v3::ApiConfigSource::GRPC); + api_config_source->set_transport_api_version(envoy::config::core::v3::V3); + auto* grpc_service = api_config_source->add_grpc_services(); + setGrpcService(*grpc_service, std::string(XDS_CLUSTER), fake_upstreams_.back()->localAddress()); + } + + envoy::extensions::transport_sockets::tls::v3::Secret getClientSecret() { + envoy::extensions::transport_sockets::tls::v3::Secret secret; + secret.set_name("client_cert"); + auto* tls_certificate = secret.mutable_tls_certificate(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/clientcert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); + return secret; + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersionsClientTypeDelta, SdsIntegrationTest, + DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); + +// Note: Envoy Mobile does not have listener sockets, so we aren't including a downstream test. +TEST_P(SdsIntegrationTest, SdsForUpstreamCluster) { + on_server_init_function_ = [this]() { + initializeXdsStream(); + sendSdsResponse(getClientSecret()); + }; + initialize(); + + // Wait until the Envoy instance has obtained an updated secret from the SDS cluster. This + // verifies that the SDS API is working from the Envoy client and allows us to know we can start + // sending HTTP requests to the upstream cluster using the secret. + ASSERT_TRUE(waitForCounterGe("sds.client_cert.update_success", 1)); + ASSERT_TRUE( + waitForCounterGe("cluster.base_h2.client_ssl_socket_factory.ssl_context_update_by_sds", 1)); + + stream_->sendHeaders(default_request_headers_, true); + terminal_callback_.waitReady(); + + EXPECT_EQ(cc_.on_headers_calls, 1); + EXPECT_EQ(cc_.status, "200"); + EXPECT_EQ(cc_.on_complete_calls, 1); + EXPECT_EQ(cc_.on_cancel_calls, 0); + EXPECT_EQ(cc_.on_error_calls, 0); + EXPECT_EQ(cc_.on_header_consumed_bytes_from_response, 13); +} + +} // namespace +} // namespace Envoy diff --git a/test/common/integration/xds_integration_test.cc b/test/common/integration/xds_integration_test.cc new file mode 100644 index 0000000000..7ac78d515c --- /dev/null +++ b/test/common/integration/xds_integration_test.cc @@ -0,0 +1,176 @@ +#include "test/common/integration/xds_integration_test.h" +#include "xds_integration_test.h" + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.h" + +#include "test/common/grpc/grpc_client_integration.h" +#include "test/common/integration/base_client_integration_test.h" +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +XdsIntegrationTest::XdsIntegrationTest() : BaseClientIntegrationTest(ipVersion()) { + override_builder_config_ = true; // The builder does not yet have RTDS support. + expect_dns_ = false; // TODO(alyssawilk) debug. + create_xds_upstream_ = true; + sotw_or_delta_ = sotwOrDelta(); + scheme_ = "https"; + + if (sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedSotw || + sotw_or_delta_ == Grpc::SotwOrDelta::UnifiedDelta) { + config_helper_.addRuntimeOverride("envoy.reloadable_features.unified_mux", "true"); + } + + // Set up the basic bootstrap config for xDS. + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + // The default stats config has overenthusiastic filters. + bootstrap.clear_stats_config(); + + // Add two clusters by default: + // - base_h2: An HTTP2 cluster with one fake upstream endpoint, for accepting requests from EM. + // - xds_cluster.lyft.com: An xDS management server cluster, with one fake upstream endpoint. + bootstrap.mutable_static_resources()->clear_clusters(); + bootstrap.mutable_static_resources()->add_clusters()->MergeFrom( + createSingleEndpointClusterConfig("base_h2")); + bootstrap.mutable_static_resources()->add_clusters()->MergeFrom( + createSingleEndpointClusterConfig(std::string(XDS_CLUSTER))); + }); + + // xDS upstream is created separately in the test infra, and there's only one non-xDS cluster. + setUpstreamCount(1); + + // Add the Admin config. + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + bootstrap.mutable_admin()->MergeFrom(adminConfig()); + }); + admin_filename_ = TestEnvironment::temporaryPath("admin_address.txt"); + setAdminAddressPathForTests(admin_filename_); +} + +Network::Address::IpVersion XdsIntegrationTest::ipVersion() const { + return std::get<0>(GetParam()); +} + +Grpc::ClientType XdsIntegrationTest::clientType() const { return std::get<1>(GetParam()); } + +Grpc::SotwOrDelta XdsIntegrationTest::sotwOrDelta() const { return std::get<2>(GetParam()); } + +void XdsIntegrationTest::SetUp() { + // TODO(abeyad): Add paramaterized tests for HTTP1, HTTP2, and HTTP3. + setUpstreamProtocol(Http::CodecType::HTTP2); +} + +void XdsIntegrationTest::TearDown() { + cleanup(); + BaseClientIntegrationTest::TearDown(); +} + +void XdsIntegrationTest::createEnvoy() { + BaseClientIntegrationTest::createEnvoy(); + std::string admin_str = TestEnvironment::readFileToStringForTest(admin_filename_); + auto addr = Network::Utility::parseInternetAddressAndPort(admin_str); + registerPort("admin", addr->ip()->port()); + if (on_server_init_function_) { + on_server_init_function_(); + } +} + +void XdsIntegrationTest::initializeXdsStream() { + createXdsConnection(); + AssertionResult result = + xds_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, xds_stream_); + RELEASE_ASSERT(result, result.message()); + xds_stream_->startGrpcStream(); +} + +std::string XdsIntegrationTest::getRuntimeKey(const std::string& key) { + auto response = IntegrationUtil::makeSingleRequest( + lookupPort("admin"), "GET", "/runtime?format=json", "", Http::CodecType::HTTP2, version_); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(response->body()); + auto entries = loader->getObject("entries"); + if (entries->hasObject(key)) { + return entries->getObject(key)->getString("final_value"); + } + return ""; +} + +// TODO(abeyad): Change this implementation to use the PulseClient once implemented. See +// https://github.com/envoyproxy/envoy-mobile/issues/2356 for details. +uint64_t XdsIntegrationTest::getCounterValue(const std::string& counter) { + auto response = IntegrationUtil::makeSingleRequest(lookupPort("admin"), "GET", "/stats?usedonly", + "", Http::CodecType::HTTP2, version_); + EXPECT_TRUE(response->complete()); + EXPECT_EQ("200", response->headers().getStatusValue()); + std::stringstream ss(response->body()); + std::string line; + while (std::getline(ss, line, '\n')) { + auto pos = line.find(':'); + if (pos == std::string::npos) { + continue; + } + if (line.substr(0, pos) == counter) { + return std::stoi(line.substr(pos + 1)); + } + } + return 0; +} + +AssertionResult XdsIntegrationTest::waitForCounterGe(const std::string& name, uint64_t value) { + constexpr std::chrono::milliseconds timeout = TestUtility::DefaultTimeout; + Event::TestTimeSystem::RealTimeBound bound(timeout); + while (getCounterValue(name) < value) { + Event::GlobalTimeSystem().timeSystem().advanceTimeWait(std::chrono::milliseconds(10)); + if (timeout != std::chrono::milliseconds::zero() && !bound.withinBound()) { + return AssertionFailure() << fmt::format("timed out waiting for {} to be {}", name, value); + } + } + return AssertionSuccess(); +} + +envoy::config::cluster::v3::Cluster +XdsIntegrationTest::createSingleEndpointClusterConfig(const std::string& cluster_name) { + envoy::config::cluster::v3::Cluster config; + config.set_name(cluster_name); + + // Set the endpoint. + auto* load_assignment = config.mutable_load_assignment(); + load_assignment->set_cluster_name(cluster_name); + auto* endpoint = load_assignment->add_endpoints()->add_lb_endpoints()->mutable_endpoint(); + endpoint->mutable_address()->mutable_socket_address()->set_address( + Network::Test::getLoopbackAddressString(ipVersion())); + endpoint->mutable_address()->mutable_socket_address()->set_port_value(0); + + // Set the protocol options. + envoy::extensions::upstreams::http::v3::HttpProtocolOptions options; + options.mutable_explicit_http_config()->mutable_http2_protocol_options(); + (*config.mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(options); + return config; +} + +envoy::config::bootstrap::v3::Admin XdsIntegrationTest::adminConfig() { + const std::string yaml = fmt::format(R"EOF( + address: + socket_address: + address: {} + port_value: 0 + )EOF", + Network::Test::getLoopbackAddressString(ipVersion())); + + envoy::config::bootstrap::v3::Admin config; + TestUtility::loadFromYaml(yaml, config); + return config; +} + +} // namespace Envoy diff --git a/test/common/integration/xds_integration_test.h b/test/common/integration/xds_integration_test.h new file mode 100644 index 0000000000..3343dd9ef3 --- /dev/null +++ b/test/common/integration/xds_integration_test.h @@ -0,0 +1,62 @@ +#pragma once + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/config/cluster/v3/cluster.pb.h" + +#include "test/common/grpc/grpc_client_integration.h" +#include "test/common/integration/base_client_integration_test.h" + +#include "absl/strings/string_view.h" +#include "gtest/gtest.h" + +namespace Envoy { + +static constexpr absl::string_view XDS_CLUSTER = "xds_cluster.lyft.com"; + +// A base class for xDS integration tests. It provides common functionality for integration tests +// derived from BaseClientIntegrationTest that needs to communicate with upstream xDS servers. +class XdsIntegrationTest : public BaseClientIntegrationTest, + public Grpc::DeltaSotwIntegrationParamTest { +public: + XdsIntegrationTest(); + virtual ~XdsIntegrationTest() = default; + +protected: + void SetUp() override; + void TearDown() override; + + void createEnvoy() override; + + // Initializes the xDS connection and creates a gRPC bi-directional stream for receiving + // DiscoveryRequests and sending DiscoveryResponses. + void initializeXdsStream(); + + // Returns the IP version that the test is running with (IPv4 or IPv6). + Network::Address::IpVersion ipVersion() const override; + // Returns the gRPC client type that the test is running with (Envoy gRPC or Google gRPC). + Grpc::ClientType clientType() const override; + // Returns whether the test is using the state-of-the-world or Delta xDS protocol. + Grpc::SotwOrDelta sotwOrDelta() const; + + // Get the runtime configuration value for the given key. The runtime value is either statically + // provided in the bootstrap config or provided (or overridden) by the RTDS config. + std::string getRuntimeKey(const std::string& key); + + // Creates a cluster config with a single static endpoint, where the endpoint is intended to be of + // a fake upstream on the loopback address. + envoy::config::cluster::v3::Cluster + createSingleEndpointClusterConfig(const std::string& cluster_name); + // Creates an admin config for being able to query various configuration values. + envoy::config::bootstrap::v3::Admin adminConfig(); + + // Get the value of a Counter in the Envoy instance. + uint64_t getCounterValue(const std::string& counter); + // Wait until the Counter specified by `name` is >= `value`. + ABSL_MUST_USE_RESULT testing::AssertionResult waitForCounterGe(const std::string& name, + uint64_t value); + +private: + std::string admin_filename_; +}; + +} // namespace Envoy