diff --git a/CMakeLists.txt b/CMakeLists.txt index e5d5bbdabd9..9bab66f3c1a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1178,6 +1178,9 @@ if(gRPC_BUILD_TESTS) endif() add_dependencies(buildtests_cxx xds_bootstrap_test) add_dependencies(buildtests_cxx xds_certificate_provider_test) + if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) + add_dependencies(buildtests_cxx xds_core_end2end_test) + endif() add_dependencies(buildtests_cxx xds_credentials_end2end_test) add_dependencies(buildtests_cxx xds_credentials_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) @@ -17109,6 +17112,171 @@ target_link_libraries(xds_certificate_provider_test ) +endif() +if(gRPC_BUILD_TESTS) +if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) + + add_executable(xds_core_end2end_test + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/duplicate/echo_duplicate.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/echo_messages.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/simple_messages.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/ads_for_test.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/ads_for_test.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/ads_for_test.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/ads_for_test.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/eds_for_test.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/eds_for_test.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/eds_for_test.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/eds_for_test.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/lrs_for_test.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/lrs_for_test.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/lrs_for_test.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/lrs_for_test.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/ads.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/base.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/cluster.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/config_source.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/discovery.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_connection_manager.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/listener.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/load_report.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/lrs.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/protocol.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/router.grpc.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.cc + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.pb.h + ${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.h + test/cpp/end2end/test_service_impl.cc + test/cpp/end2end/xds/xds_core_end2end_test.cc + test/cpp/end2end/xds/xds_end2end_test_lib.cc + test/cpp/end2end/xds/xds_server.cc + test/cpp/util/tls_test_utils.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc + ) + + target_include_directories(xds_core_end2end_test + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR} + ${_gRPC_RE2_INCLUDE_DIR} + ${_gRPC_SSL_INCLUDE_DIR} + ${_gRPC_UPB_GENERATED_DIR} + ${_gRPC_UPB_GRPC_GENERATED_DIR} + ${_gRPC_UPB_INCLUDE_DIR} + ${_gRPC_XXHASH_INCLUDE_DIR} + ${_gRPC_ZLIB_INCLUDE_DIR} + third_party/googletest/googletest/include + third_party/googletest/googletest + third_party/googletest/googlemock/include + third_party/googletest/googlemock + ${_gRPC_PROTO_GENS_DIR} + ) + + target_link_libraries(xds_core_end2end_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc++_test_util + ) + + +endif() endif() if(gRPC_BUILD_TESTS) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index d3db2dd7929..1be22b5db9b 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -8378,6 +8378,59 @@ targets: - test/core/xds/xds_certificate_provider_test.cc deps: - grpc_test_util +- name: xds_core_end2end_test + gtest: true + build: test + language: c++ + headers: + - test/cpp/end2end/counted_service.h + - test/cpp/end2end/test_service_impl.h + - test/cpp/end2end/xds/xds_end2end_test_lib.h + - test/cpp/end2end/xds/xds_server.h + - test/cpp/util/tls_test_utils.h + src: + - src/proto/grpc/testing/duplicate/echo_duplicate.proto + - src/proto/grpc/testing/echo.proto + - src/proto/grpc/testing/echo_messages.proto + - src/proto/grpc/testing/simple_messages.proto + - src/proto/grpc/testing/xds/ads_for_test.proto + - src/proto/grpc/testing/xds/eds_for_test.proto + - src/proto/grpc/testing/xds/lrs_for_test.proto + - src/proto/grpc/testing/xds/v3/address.proto + - src/proto/grpc/testing/xds/v3/ads.proto + - src/proto/grpc/testing/xds/v3/base.proto + - src/proto/grpc/testing/xds/v3/cluster.proto + - src/proto/grpc/testing/xds/v3/config_source.proto + - src/proto/grpc/testing/xds/v3/discovery.proto + - src/proto/grpc/testing/xds/v3/endpoint.proto + - src/proto/grpc/testing/xds/v3/expr.proto + - src/proto/grpc/testing/xds/v3/extension.proto + - src/proto/grpc/testing/xds/v3/http_connection_manager.proto + - src/proto/grpc/testing/xds/v3/http_filter_rbac.proto + - src/proto/grpc/testing/xds/v3/listener.proto + - src/proto/grpc/testing/xds/v3/load_report.proto + - src/proto/grpc/testing/xds/v3/lrs.proto + - src/proto/grpc/testing/xds/v3/metadata.proto + - src/proto/grpc/testing/xds/v3/path.proto + - src/proto/grpc/testing/xds/v3/percent.proto + - src/proto/grpc/testing/xds/v3/protocol.proto + - src/proto/grpc/testing/xds/v3/range.proto + - src/proto/grpc/testing/xds/v3/rbac.proto + - src/proto/grpc/testing/xds/v3/regex.proto + - src/proto/grpc/testing/xds/v3/route.proto + - src/proto/grpc/testing/xds/v3/router.proto + - src/proto/grpc/testing/xds/v3/string.proto + - test/cpp/end2end/test_service_impl.cc + - test/cpp/end2end/xds/xds_core_end2end_test.cc + - test/cpp/end2end/xds/xds_end2end_test_lib.cc + - test/cpp/end2end/xds/xds_server.cc + - test/cpp/util/tls_test_utils.cc + deps: + - grpc++_test_util + platforms: + - linux + - posix + - mac - name: xds_credentials_end2end_test gtest: true build: test diff --git a/test/cpp/end2end/xds/BUILD b/test/cpp/end2end/xds/BUILD index 5ee2a7acfe8..cb97ef18850 100644 --- a/test/cpp/end2end/xds/BUILD +++ b/test/cpp/end2end/xds/BUILD @@ -137,6 +137,27 @@ grpc_cc_test( ], ) +grpc_cc_test( + name = "xds_core_end2end_test", + size = "large", + srcs = ["xds_core_end2end_test.cc"], + external_deps = [ + "gtest", + ], + linkstatic = True, # Fixes dyld error on MacOS + tags = [ + "no_test_ios", + "no_windows", + ], # TODO(jtattermusch): fix test on windows + deps = [ + ":xds_end2end_test_lib", + "//:gpr", + "//:grpc", + "//:grpc++", + "//test/core/util:grpc_test_util", + ], +) + grpc_cc_test( name = "xds_csds_end2end_test", size = "large", diff --git a/test/cpp/end2end/xds/xds_core_end2end_test.cc b/test/cpp/end2end/xds/xds_core_end2end_test.cc new file mode 100644 index 00000000000..9acdfac5b41 --- /dev/null +++ b/test/cpp/end2end/xds/xds_core_end2end_test.cc @@ -0,0 +1,1069 @@ +// Copyright 2017 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include +#include +#include + +#include +#include + +#include "absl/strings/str_cat.h" + +#include "src/core/ext/filters/client_channel/backup_poller.h" +#include "test/cpp/end2end/xds/xds_end2end_test_lib.h" + +namespace grpc { +namespace testing { +namespace { + +using ClientStats = LrsServiceImpl::ClientStats; + +// +// XdsClientTest - basic tests of XdsClient functionality +// + +using XdsClientTest = XdsEnd2endTest; + +INSTANTIATE_TEST_SUITE_P(XdsTest, XdsClientTest, + ::testing::Values(XdsTestType()), &XdsTestType::Name); + +// Tests that the client can handle resource wrapped in a Resource message. +TEST_P(XdsClientTest, ResourceWrappedInResourceMessage) { + CreateAndStartBackends(1); + balancer_->ads_service()->set_wrap_resources(true); + const size_t kNumRpcsPerAddress = 100; + EdsResourceArgs args({ + {"locality0", CreateEndpointsForBackends()}, + }); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Make sure that trying to connect works without a call. + channel_->GetState(true /* try_to_connect */); + // We need to wait for all backends to come online. + WaitForAllBackends(); + // Send kNumRpcsPerAddress RPCs per server. + CheckRpcSendOk(kNumRpcsPerAddress * backends_.size()); + // Each backend should have gotten 100 requests. + for (size_t i = 0; i < backends_.size(); ++i) { + EXPECT_EQ(kNumRpcsPerAddress, + backends_[i]->backend_service()->request_count()); + } + // Check LB policy name for the channel. + EXPECT_EQ("xds_cluster_manager_experimental", + channel_->GetLoadBalancingPolicyName()); +} + +TEST_P(XdsClientTest, ResourceTypeVersionPersistsAcrossStreamRestarts) { + CreateAndStartBackends(2); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Wait for backends to come online. + WaitForAllBackends(0, 1); + // Stop balancer. + balancer_->Shutdown(); + // Tell balancer to require minimum version 1 for all resource types. + balancer_->ads_service()->SetResourceMinVersion(kLdsTypeUrl, 1); + balancer_->ads_service()->SetResourceMinVersion(kRdsTypeUrl, 1); + balancer_->ads_service()->SetResourceMinVersion(kCdsTypeUrl, 1); + balancer_->ads_service()->SetResourceMinVersion(kEdsTypeUrl, 1); + // Update backend, just so we can be sure that the client has + // reconnected to the balancer. + args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Restart balancer. + balancer_->Start(); + // Make sure client has reconnected. + WaitForAllBackends(1, 2); +} + +// Tests that we restart all xDS requests when we reestablish the ADS call. +TEST_P(XdsClientTest, RestartsRequestsUponReconnection) { + CreateAndStartBackends(2); + // Manually configure use of RDS. + auto listener = default_listener_; + HttpConnectionManager http_connection_manager; + listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( + &http_connection_manager); + auto* rds = http_connection_manager.mutable_rds(); + rds->set_route_config_name(kDefaultRouteConfigurationName); + rds->mutable_config_source()->mutable_self(); + listener.mutable_api_listener()->mutable_api_listener()->PackFrom( + http_connection_manager); + balancer_->ads_service()->SetLdsResource(listener); + balancer_->ads_service()->SetRdsResource(default_route_config_); + const char* kNewClusterName = "new_cluster_name"; + const char* kNewEdsServiceName = "new_eds_service_name"; + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // We need to wait for all backends to come online. + WaitForAllBackends(0, 1); + // Now shut down and restart the balancer. When the client + // reconnects, it should automatically restart the requests for all + // resource types. + balancer_->Shutdown(); + balancer_->Start(); + // Make sure things are still working. + CheckRpcSendOk(100); + // Populate new EDS resource. + args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); + balancer_->ads_service()->SetEdsResource( + BuildEdsResource(args, kNewEdsServiceName)); + // Populate new CDS resource. + Cluster new_cluster = default_cluster_; + new_cluster.set_name(kNewClusterName); + new_cluster.mutable_eds_cluster_config()->set_service_name( + kNewEdsServiceName); + balancer_->ads_service()->SetCdsResource(new_cluster); + // Change RDS resource to point to new cluster. + RouteConfiguration new_route_config = default_route_config_; + new_route_config.mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster(kNewClusterName); + balancer_->ads_service()->SetRdsResource(new_route_config); + // Wait for all new backends to be used. + WaitForAllBackends(1, 2); +} + +// Tests that the NACK for multiple bad resources includes both errors. +TEST_P(XdsClientTest, MultipleBadCdsResources) { + constexpr char kClusterName2[] = "cluster_name_2"; + constexpr char kClusterName3[] = "cluster_name_3"; + CreateAndStartBackends(1); + // Add cluster with unsupported type. + auto cluster = default_cluster_; + cluster.set_name(kClusterName2); + cluster.set_type(Cluster::STATIC); + balancer_->ads_service()->SetCdsResource(cluster); + // Add second cluster with the same error. + cluster.set_name(kClusterName3); + balancer_->ads_service()->SetCdsResource(cluster); + // Change RouteConfig to point to all clusters. + RouteConfiguration route_config = default_route_config_; + route_config.mutable_virtual_hosts(0)->clear_routes(); + // First route: default cluster, selected based on header. + auto* route = route_config.mutable_virtual_hosts(0)->add_routes(); + route->mutable_match()->set_prefix(""); + auto* header_matcher = route->mutable_match()->add_headers(); + header_matcher->set_name("cluster"); + header_matcher->set_exact_match(kDefaultClusterName); + route->mutable_route()->set_cluster(kDefaultClusterName); + // Second route: cluster 2, selected based on header. + route = route_config.mutable_virtual_hosts(0)->add_routes(); + route->mutable_match()->set_prefix(""); + header_matcher = route->mutable_match()->add_headers(); + header_matcher->set_name("cluster"); + header_matcher->set_exact_match(kClusterName2); + route->mutable_route()->set_cluster(kClusterName2); + // Third route: cluster 3, used by default. + route = route_config.mutable_virtual_hosts(0)->add_routes(); + route->mutable_match()->set_prefix(""); + route->mutable_route()->set_cluster(kClusterName3); + SetRouteConfiguration(balancer_.get(), route_config); + // Add EDS resource. + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Send RPC. + const auto response_state = WaitForCdsNack(); + ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; + EXPECT_THAT( + response_state->error_message, + ::testing::ContainsRegex(absl::StrCat(kClusterName2, + ": validation error.*" + "DiscoveryType is not valid.*", + kClusterName3, + ": validation error.*" + "DiscoveryType is not valid"))); + // RPCs for default cluster should succeed. + std::vector> metadata_default_cluster = { + {"cluster", kDefaultClusterName}, + }; + CheckRpcSendOk( + 1, RpcOptions().set_metadata(std::move(metadata_default_cluster))); + // RPCs for cluster 2 should fail. + std::vector> metadata_cluster_2 = { + {"cluster", kClusterName2}, + }; + CheckRpcSendFailure(CheckRpcSendFailureOptions().set_rpc_options( + RpcOptions().set_metadata(std::move(metadata_cluster_2)))); +} + +TEST_P(XdsClientTest, XdsStreamErrorPropagation) { + const std::string kErrorMessage = "test forced ADS stream failure"; + balancer_->ads_service()->ForceADSFailure( + Status(StatusCode::RESOURCE_EXHAUSTED, kErrorMessage)); + auto status = SendRpc(); + gpr_log(GPR_INFO, + "XdsStreamErrorPropagation test: RPC got error: code=%d message=%s", + status.error_code(), status.error_message().c_str()); + EXPECT_THAT(status.error_code(), StatusCode::UNAVAILABLE); + EXPECT_THAT(status.error_message(), ::testing::HasSubstr(kErrorMessage)); + EXPECT_THAT(status.error_message(), + ::testing::HasSubstr("(node ID:xds_end2end_test)")); +} + +// +// GlobalXdsClientTest - tests that need to run with a global XdsClient +// (this is the default in production) +// + +using GlobalXdsClientTest = XdsEnd2endTest; + +// Get bootstrap from env var, so that there's a global XdsClient. +INSTANTIATE_TEST_SUITE_P(XdsTest, GlobalXdsClientTest, + ::testing::Values(XdsTestType().set_bootstrap_source( + XdsTestType::kBootstrapFromEnvVar)), + &XdsTestType::Name); + +TEST_P(GlobalXdsClientTest, MultipleChannelsShareXdsClient) { + CreateAndStartBackends(1); + const char* kNewServerName = "new-server.example.com"; + Listener listener = default_listener_; + listener.set_name(kNewServerName); + SetListenerAndRouteConfiguration(balancer_.get(), listener, + default_route_config_); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + WaitForAllBackends(); + // Create second channel and tell it to connect to kNewServerName. + auto channel2 = CreateChannel(/*failover_timeout_ms=*/0, kNewServerName); + channel2->GetState(/*try_to_connect=*/true); + ASSERT_TRUE( + channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); + // Make sure there's only one client connected. + EXPECT_EQ(1UL, balancer_->ads_service()->clients().size()); +} + +TEST_P( + GlobalXdsClientTest, + MultipleChannelsShareXdsClientWithResourceUpdateAfterOneChannelGoesAway) { + CreateAndStartBackends(2); + // Test for https://github.com/grpc/grpc/issues/28468. Makes sure that the + // XdsClient properly handles the case where there are multiple watchers on + // the same resource and one of them unsubscribes. + const char* kNewServerName = "new-server.example.com"; + Listener listener = default_listener_; + listener.set_name(kNewServerName); + SetListenerAndRouteConfiguration(balancer_.get(), listener, + default_route_config_); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs({ + {"locality0", CreateEndpointsForBackends(0, 1)}, + }))); + WaitForBackend(0); + // Create second channel and tell it to connect to kNewServerName. + auto channel2 = CreateChannel(/*failover_timeout_ms=*/0, kNewServerName); + channel2->GetState(/*try_to_connect=*/true); + ASSERT_TRUE( + channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); + // Now, destroy the new channel, send an EDS update to use a different backend + // and test that the channel switches to that backend. + channel2.reset(); + // This sleep is needed to be able to reproduce the bug and to give time for + // the buggy unsubscription to take place. + // TODO(yashykt): Figure out a way to do this without the sleep. + gpr_sleep_until(grpc_timeout_milliseconds_to_deadline(10)); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs({ + {"locality0", CreateEndpointsForBackends(1, 2)}, + }))); + WaitForBackend(1); +} + +// Tests that the NACK for multiple bad LDS resources includes both errors. +// This needs to be in GlobalXdsClientTest because the only way to request +// two LDS resources in the same XdsClient is for two channels to share +// the same XdsClient. +TEST_P(GlobalXdsClientTest, MultipleBadLdsResources) { + CreateAndStartBackends(1); + constexpr char kServerName2[] = "server.other.com"; + constexpr char kServerName3[] = "server.another.com"; + auto listener = default_listener_; + listener.clear_api_listener(); + balancer_->ads_service()->SetLdsResource(listener); + listener.set_name(kServerName2); + balancer_->ads_service()->SetLdsResource(listener); + listener = default_listener_; + listener.set_name(kServerName3); + SetListenerAndRouteConfiguration(balancer_.get(), listener, + default_route_config_); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + const auto response_state = WaitForLdsNack(); + ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; + EXPECT_THAT(response_state->error_message, + ::testing::ContainsRegex(absl::StrCat( + kServerName, + ": validation error.*" + "Listener has neither address nor ApiListener.*"))); + // Need to create a second channel to subscribe to a second LDS resource. + auto channel2 = CreateChannel(0, kServerName2); + auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); + { + ClientContext context; + EchoRequest request; + request.set_message(kRequestMessage); + EchoResponse response; + grpc::Status status = stub2->Echo(&context, request, &response); + EXPECT_FALSE(status.ok()); + // Wait for second NACK to be reported to xDS server. + const auto response_state = WaitForLdsNack(); + ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; + EXPECT_THAT(response_state->error_message, + ::testing::ContainsRegex(absl::StrCat( + kServerName, + ": validation error.*" + "Listener has neither address nor ApiListener.*"))); + EXPECT_THAT(response_state->error_message, + ::testing::ContainsRegex(absl::StrCat( + kServerName2, + ": validation error.*" + "Listener has neither address nor ApiListener.*"))); + } + // Now start a new channel with a third server name, this one with a + // valid resource. + auto channel3 = CreateChannel(0, kServerName3); + auto stub3 = grpc::testing::EchoTestService::NewStub(channel3); + { + ClientContext context; + EchoRequest request; + request.set_message(kRequestMessage); + EchoResponse response; + grpc::Status status = stub3->Echo(&context, request, &response); + EXPECT_TRUE(status.ok()) << "code=" << status.error_code() + << " message=" << status.error_message(); + } +} + +// Tests that we don't trigger does-not-exist callbacks for a resource +// that was previously valid but is updated to be invalid. +TEST_P(GlobalXdsClientTest, InvalidListenerStillExistsIfPreviouslyCached) { + CreateAndStartBackends(1); + // Set up valid resources and check that the channel works. + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + CheckRpcSendOk(); + // Now send an update changing the Listener to be invalid. + auto listener = default_listener_; + listener.clear_api_listener(); + balancer_->ads_service()->SetLdsResource(listener); + const auto response_state = WaitForLdsNack(StatusCode::OK); + ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; + EXPECT_THAT(response_state->error_message, + ::testing::ContainsRegex(absl::StrCat( + kServerName, + ": validation error.*" + "Listener has neither address nor ApiListener"))); + CheckRpcSendOk(); +} + +// +// TimeoutTest - tests xDS initial timeout handling +// + +class TimeoutTest : public XdsEnd2endTest { + protected: + void SetUp() override { + InitClient(BootstrapBuilder(), /*lb_expected_authority=*/"", + /*xds_resource_does_not_exist_timeout_ms=*/500); + } +}; + +// Enable RDS, so that we can test all resource types. +// Run with bootstrap from env var so that multiple channels share the same +// XdsClient (needed for testing the timeout for the 2nd LDS and RDS resource). +INSTANTIATE_TEST_SUITE_P( + XdsTest, TimeoutTest, + ::testing::Values( + XdsTestType().set_enable_rds_testing().set_bootstrap_source( + XdsTestType::kBootstrapFromEnvVar)), + &XdsTestType::Name); + +TEST_P(TimeoutTest, LdsServerIgnoresRequest) { + balancer_->ads_service()->IgnoreResourceType(kLdsTypeUrl); + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, LdsResourceNotPresentInRequest) { + balancer_->ads_service()->UnsetResource(kLdsTypeUrl, kServerName); + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, LdsSecondResourceNotPresentInRequest) { + ASSERT_NE(GetParam().bootstrap_source(), + XdsTestType::kBootstrapFromChannelArg) + << "This test cannot use bootstrap from channel args, because it " + "needs two channels to use the same XdsClient instance."; + CreateAndStartBackends(1); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + WaitForAllBackends(); + // Create second channel for a new server name. + // This should fail because there is no LDS resource for this server name. + auto channel2 = + CreateChannel(/*failover_timeout_ms=*/0, "new-server.example.com"); + auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); + ClientContext context; + EchoRequest request; + EchoResponse response; + RpcOptions rpc_options; + rpc_options.SetupRpc(&context, &request); + auto status = + SendRpcMethod(stub2.get(), rpc_options, &context, request, &response); + EXPECT_EQ(StatusCode::UNAVAILABLE, status.error_code()); +} + +TEST_P(TimeoutTest, RdsServerIgnoresRequest) { + balancer_->ads_service()->IgnoreResourceType(kRdsTypeUrl); + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, RdsResourceNotPresentInRequest) { + balancer_->ads_service()->UnsetResource(kRdsTypeUrl, + kDefaultRouteConfigurationName); + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, RdsSecondResourceNotPresentInRequest) { + ASSERT_NE(GetParam().bootstrap_source(), + XdsTestType::kBootstrapFromChannelArg) + << "This test cannot use bootstrap from channel args, because it " + "needs two channels to use the same XdsClient instance."; + CreateAndStartBackends(1); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Add listener for 2nd channel, but no RDS resource. + const char* kNewServerName = "new-server.example.com"; + Listener listener = default_listener_; + listener.set_name(kNewServerName); + HttpConnectionManager http_connection_manager = + ClientHcmAccessor().Unpack(listener); + auto* rds = http_connection_manager.mutable_rds(); + rds->set_route_config_name("rds_resource_does_not_exist"); + rds->mutable_config_source()->mutable_self(); + ClientHcmAccessor().Pack(http_connection_manager, &listener); + balancer_->ads_service()->SetLdsResource(listener); + WaitForAllBackends(); + // Create second channel for a new server name. + // This should fail because the LDS resource points to a non-existent RDS + // resource. + auto channel2 = CreateChannel(/*failover_timeout_ms=*/0, kNewServerName); + auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); + ClientContext context; + EchoRequest request; + EchoResponse response; + RpcOptions rpc_options; + rpc_options.SetupRpc(&context, &request); + auto status = + SendRpcMethod(stub2.get(), rpc_options, &context, request, &response); + EXPECT_EQ(StatusCode::UNAVAILABLE, status.error_code()); +} + +TEST_P(TimeoutTest, CdsServerIgnoresRequest) { + balancer_->ads_service()->IgnoreResourceType(kCdsTypeUrl); + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, CdsResourceNotPresentInRequest) { + balancer_->ads_service()->UnsetResource(kCdsTypeUrl, kDefaultClusterName); + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, CdsSecondResourceNotPresentInRequest) { + CreateAndStartBackends(1); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + WaitForAllBackends(); + // Change route config to point to non-existing cluster. + const char* kNewClusterName = "new_cluster_name"; + RouteConfiguration route_config = default_route_config_; + route_config.mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster(kNewClusterName); + balancer_->ads_service()->SetRdsResource(route_config); + // New cluster times out. + // May need to wait a bit for the change to propagate to the client. + gpr_timespec deadline = grpc_timeout_seconds_to_deadline(10); + bool error_seen = false; + do { + auto status = SendRpc(); + if (status.error_code() == StatusCode::UNAVAILABLE) { + error_seen = true; + break; + } + } while (gpr_time_cmp(gpr_now(GPR_CLOCK_MONOTONIC), deadline) < 0); + EXPECT_TRUE(error_seen); +} + +TEST_P(TimeoutTest, EdsServerIgnoresRequest) { + balancer_->ads_service()->IgnoreResourceType(kEdsTypeUrl); + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, EdsResourceNotPresentInRequest) { + // No need to remove EDS resource, since the test suite does not add it + // by default. + CheckRpcSendFailure(); +} + +TEST_P(TimeoutTest, EdsSecondResourceNotPresentInRequest) { + CreateAndStartBackends(1); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + WaitForAllBackends(); + // New cluster that points to a non-existant EDS resource. + const char* kNewClusterName = "new_cluster_name"; + Cluster cluster = default_cluster_; + cluster.set_name(kNewClusterName); + cluster.mutable_eds_cluster_config()->set_service_name( + "eds_service_name_does_not_exist"); + balancer_->ads_service()->SetCdsResource(cluster); + // Now add a route pointing to the new cluster. + RouteConfiguration route_config = default_route_config_; + auto* route = route_config.mutable_virtual_hosts(0)->mutable_routes(0); + *route_config.mutable_virtual_hosts(0)->add_routes() = *route; + route->mutable_match()->set_path("/grpc.testing.EchoTestService/Echo1"); + route->mutable_route()->set_cluster(kNewClusterName); + balancer_->ads_service()->SetRdsResource(route_config); + // New EDS resource times out. + // May need to wait a bit for the RDS change to propagate to the client. + gpr_timespec deadline = grpc_timeout_seconds_to_deadline(10); + bool error_seen = false; + do { + auto status = SendRpc(RpcOptions().set_rpc_method(METHOD_ECHO1)); + if (status.error_code() == StatusCode::UNAVAILABLE) { + error_seen = true; + break; + } + } while (gpr_time_cmp(gpr_now(GPR_CLOCK_MONOTONIC), deadline) < 0); + EXPECT_TRUE(error_seen); +} + +// +// BootstrapSourceTest - tests different bootstrap sources +// + +using BootstrapSourceTest = XdsEnd2endTest; + +INSTANTIATE_TEST_SUITE_P( + XdsTest, BootstrapSourceTest, + ::testing::Values( + XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), + XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromFile)), + &XdsTestType::Name); + +TEST_P(BootstrapSourceTest, Vanilla) { + CreateAndStartBackends(1); + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + WaitForAllBackends(); +} + +// +// XdsFederationTest - tests xDS federation +// + +class XdsFederationTest : public XdsEnd2endTest { + protected: + XdsFederationTest() : authority_balancer_(CreateAndStartBalancer()) {} + + void SetUp() override { + // Each test will use a slightly different bootstrap config, + // so SetUp() is intentionally empty here, and the real + // setup (calling of InitClient()) is moved into each test. + } + + void TearDown() override { + authority_balancer_->Shutdown(); + XdsEnd2endTest::TearDown(); + } + + std::unique_ptr authority_balancer_; +}; + +// Get bootstrap from env var, so that there's a global XdsClient. +// Runs with and without RDS. +INSTANTIATE_TEST_SUITE_P( + XdsTest, XdsFederationTest, + ::testing::Values( + XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), + XdsTestType() + .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) + .set_enable_rds_testing()), + &XdsTestType::Name); + +// Channel is created with URI "xds:server.example.com". +// Bootstrap config default client listener template uses new-style name with +// authority "xds.example.com". +TEST_P(XdsFederationTest, FederationTargetNoAuthorityWithResourceTemplate) { + ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); + const char* kAuthority = "xds.example.com"; + const char* kNewListenerTemplate = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/%s?psm_project_id=1234"; + const char* kNewListenerName = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/server.example.com?psm_project_id=1234"; + const char* kNewRouteConfigName = + "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" + "new_route_config_name"; + const char* kNewEdsServiceName = + "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" + "new_edsservice_name"; + const char* kNewClusterName = + "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" + "new_cluster_name"; + BootstrapBuilder builder = BootstrapBuilder(); + builder.SetClientDefaultListenerResourceNameTemplate(kNewListenerTemplate); + builder.AddAuthority( + kAuthority, absl::StrCat("localhost:", authority_balancer_->port()), + // Note we will not use the client_listener_resource_name_template field + // in the authority. + "xdstp://xds.example.com/envoy.config.listener.v3.Listener" + "client/%s?client_listener_resource_name_template_not_in_use"); + InitClient(builder); + CreateAndStartBackends(2, /*xds_enabled=*/true); + // Eds for the new authority balancer. + EdsResourceArgs args = + EdsResourceArgs({{"locality0", CreateEndpointsForBackends()}}); + authority_balancer_->ads_service()->SetEdsResource( + BuildEdsResource(args, kNewEdsServiceName)); + // New cluster + Cluster new_cluster = default_cluster_; + new_cluster.set_name(kNewClusterName); + new_cluster.mutable_eds_cluster_config()->set_service_name( + kNewEdsServiceName); + authority_balancer_->ads_service()->SetCdsResource(new_cluster); + // New Route + RouteConfiguration new_route_config = default_route_config_; + new_route_config.set_name(kNewRouteConfigName); + new_route_config.mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster(kNewClusterName); + // New Listener + Listener listener = default_listener_; + listener.set_name(kNewListenerName); + SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, + new_route_config); + WaitForAllBackends(); +} + +// Channel is created with URI "xds://xds.example.com/server.example.com". +// In bootstrap config, authority has no client listener template, so we use the +// default. +TEST_P(XdsFederationTest, FederationTargetAuthorityDefaultResourceTemplate) { + ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); + const char* kAuthority = "xds.example.com"; + const char* kNewServerName = "whee%/server.example.com"; + const char* kNewListenerName = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "whee%25/server.example.com"; + const char* kNewRouteConfigName = + "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" + "new_route_config_name"; + const char* kNewEdsServiceName = + "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" + "edsservice_name"; + const char* kNewClusterName = + "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" + "cluster_name"; + BootstrapBuilder builder = BootstrapBuilder(); + builder.AddAuthority(kAuthority, + absl::StrCat("localhost:", authority_balancer_->port())); + InitClient(builder); + CreateAndStartBackends(2, /*xds_enabled=*/true); + // Eds for 2 balancers to ensure RPCs sent using current stub go to backend 0 + // and RPCs sent using the new stub go to backend 1. + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); + authority_balancer_->ads_service()->SetEdsResource( + BuildEdsResource(args, kNewEdsServiceName)); + // New cluster + Cluster new_cluster = default_cluster_; + new_cluster.set_name(kNewClusterName); + new_cluster.mutable_eds_cluster_config()->set_service_name( + kNewEdsServiceName); + authority_balancer_->ads_service()->SetCdsResource(new_cluster); + // New Route + RouteConfiguration new_route_config = default_route_config_; + new_route_config.set_name(kNewRouteConfigName); + new_route_config.mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster(kNewClusterName); + // New Listener + Listener listener = default_listener_; + listener.set_name(kNewListenerName); + SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, + new_route_config); + // Ensure update has reached and send 10 RPCs to the current stub. + WaitForAllBackends(0, 1); + // Create second channel to new target uri and send 1 RPC . + auto channel2 = + CreateChannel(/*failover_timeout_ms=*/0, kNewServerName, kAuthority); + channel2->GetState(/*try_to_connect=*/true); + ASSERT_TRUE( + channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); + auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); + ClientContext context; + EchoRequest request; + request.set_message(kRequestMessage); + EchoResponse response; + grpc::Status status = stub2->Echo(&context, request, &response); + EXPECT_TRUE(status.ok()) << "code=" << status.error_code() + << " message=" << status.error_message(); + // We should be reaching backend 1, not 0, as balanced by the authority xds + // server. + EXPECT_EQ(0U, backends_[0]->backend_service()->request_count()); + EXPECT_EQ(1U, backends_[1]->backend_service()->request_count()); +} + +// Channel is created with URI "xds://xds.example.com/server.example.com". +// Bootstrap entry for that authority specifies a client listener name template. +TEST_P(XdsFederationTest, FederationTargetAuthorityWithResourceTemplate) { + ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); + const char* kAuthority = "xds.example.com"; + const char* kNewServerName = "whee%/server.example.com"; + const char* kNewListenerTemplate = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/%s?psm_project_id=1234"; + const char* kNewListenerName = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/whee%25/server.example.com?psm_project_id=1234"; + const char* kNewRouteConfigName = + "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" + "new_route_config_name"; + const char* kNewEdsServiceName = + "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" + "edsservice_name"; + const char* kNewClusterName = + "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" + "cluster_name"; + BootstrapBuilder builder = BootstrapBuilder(); + builder.AddAuthority(kAuthority, + absl::StrCat("localhost:", authority_balancer_->port()), + kNewListenerTemplate); + InitClient(builder); + CreateAndStartBackends(2, /*xds_enabled=*/true); + // Eds for 2 balancers to ensure RPCs sent using current stub go to backend 0 + // and RPCs sent using the new stub go to backend 1. + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); + authority_balancer_->ads_service()->SetEdsResource( + BuildEdsResource(args, kNewEdsServiceName)); + // New cluster + Cluster new_cluster = default_cluster_; + new_cluster.set_name(kNewClusterName); + new_cluster.mutable_eds_cluster_config()->set_service_name( + kNewEdsServiceName); + authority_balancer_->ads_service()->SetCdsResource(new_cluster); + // New Route + RouteConfiguration new_route_config = default_route_config_; + new_route_config.set_name(kNewRouteConfigName); + new_route_config.mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster(kNewClusterName); + // New Listener + Listener listener = default_listener_; + listener.set_name(kNewListenerName); + SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, + new_route_config); + // Ensure update has reached and send 10 RPCs to the current stub. + WaitForAllBackends(0, 1); + // Create second channel to new target uri and send 1 RPC . + auto channel2 = + CreateChannel(/*failover_timeout_ms=*/0, kNewServerName, kAuthority); + channel2->GetState(/*try_to_connect=*/true); + ASSERT_TRUE( + channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); + auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); + ClientContext context; + EchoRequest request; + request.set_message(kRequestMessage); + EchoResponse response; + grpc::Status status = stub2->Echo(&context, request, &response); + EXPECT_TRUE(status.ok()) << "code=" << status.error_code() + << " message=" << status.error_message(); + // We should be reaching backend 1, not 0, as balanced by the authority xds + // server. + EXPECT_EQ(0U, backends_[0]->backend_service()->request_count()); + EXPECT_EQ(1U, backends_[1]->backend_service()->request_count()); +} + +// Setting server_listener_resource_name_template to start with "xdstp:" and +// look up xds server under an authority map. +TEST_P(XdsFederationTest, FederationServer) { + ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); + const char* kAuthority = "xds.example.com"; + const char* kNewListenerTemplate = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/%s?psm_project_id=1234"; + const char* kNewServerListenerTemplate = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "server/%s?psm_project_id=1234"; + const char* kNewListenerName = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/server.example.com?psm_project_id=1234"; + const char* kNewRouteConfigName = + "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" + "new_route_config_name"; + const char* kNewEdsServiceName = + "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" + "new_edsservice_name"; + const char* kNewClusterName = + "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" + "new_cluster_name"; + BootstrapBuilder builder = BootstrapBuilder(); + builder.SetClientDefaultListenerResourceNameTemplate(kNewListenerTemplate); + builder.SetServerListenerResourceNameTemplate(kNewServerListenerTemplate); + builder.AddAuthority( + kAuthority, absl::StrCat("localhost:", authority_balancer_->port()), + // Note we will not use the client_listener_resource_name_template field + // in the authority. + "xdstp://xds.example.com/envoy.config.listener.v3.Listener" + "client/%s?client_listener_resource_name_template_not_in_use"); + InitClient(builder); + CreateAndStartBackends(2, /*xds_enabled=*/true); + // Eds for new authority balancer. + EdsResourceArgs args = + EdsResourceArgs({{"locality0", CreateEndpointsForBackends()}}); + authority_balancer_->ads_service()->SetEdsResource( + BuildEdsResource(args, kNewEdsServiceName)); + // New cluster + Cluster new_cluster = default_cluster_; + new_cluster.set_name(kNewClusterName); + new_cluster.mutable_eds_cluster_config()->set_service_name( + kNewEdsServiceName); + authority_balancer_->ads_service()->SetCdsResource(new_cluster); + // New Route + RouteConfiguration new_route_config = default_route_config_; + new_route_config.set_name(kNewRouteConfigName); + new_route_config.mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster(kNewClusterName); + // New Listener + Listener listener = default_listener_; + listener.set_name(kNewListenerName); + SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, + new_route_config); + // New Server Listeners + for (int port : GetBackendPorts()) { + Listener server_listener = default_server_listener_; + server_listener.set_name(absl::StrCat( + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/server/", + ipv6_only_ ? "%5B::1%5D:" : "127.0.0.1:", port, + "?psm_project_id=1234")); + server_listener.mutable_address()->mutable_socket_address()->set_port_value( + port); + authority_balancer_->ads_service()->SetLdsResource(server_listener); + } + WaitForAllBackends(); +} + +// +// XdsFederationLoadReportingTest - xDS federation and load reporting +// + +using XdsFederationLoadReportingTest = XdsFederationTest; + +// Get bootstrap from env var, so that there's a global XdsClient. +// Runs with and without RDS. +INSTANTIATE_TEST_SUITE_P( + XdsTest, XdsFederationLoadReportingTest, + ::testing::Values( + XdsTestType() + .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) + .set_enable_load_reporting(), + XdsTestType() + .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) + .set_enable_load_reporting() + .set_enable_rds_testing()), + &XdsTestType::Name); + +// Channel is created with URI "xds://xds.example.com/server.example.com". +// Bootstrap entry for that authority specifies a client listener name template. +// Sending traffic to both default balancer and authority balancer and checking +// load reporting with each one. +TEST_P(XdsFederationLoadReportingTest, FederationMultipleLoadReportingTest) { + ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); + const char* kAuthority = "xds.example.com"; + const char* kNewServerName = "whee%/server.example.com"; + const char* kNewListenerTemplate = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/%s?psm_project_id=1234"; + const char* kNewListenerName = + "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" + "client/whee%25/server.example.com?psm_project_id=1234"; + const char* kNewRouteConfigName = + "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" + "new_route_config_name"; + const char* kNewEdsServiceName = + "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" + "edsservice_name"; + const char* kNewClusterName = + "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" + "cluster_name"; + const size_t kNumRpcsToDefaultBalancer = 5; + const size_t kNumRpcsToAuthorityBalancer = 10; + BootstrapBuilder builder = BootstrapBuilder(); + builder.AddAuthority(kAuthority, + absl::StrCat("localhost:", authority_balancer_->port()), + kNewListenerTemplate); + InitClient(builder); + CreateAndStartBackends(2, /*xds_enabled=*/true); + // Eds for 2 balancers to ensure RPCs sent using current stub go to backend 0 + // and RPCs sent using the new stub go to backend 1. + EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); + authority_balancer_->ads_service()->SetEdsResource( + BuildEdsResource(args, kNewEdsServiceName)); + authority_balancer_->lrs_service()->set_cluster_names({kNewClusterName}); + // New cluster + Cluster new_cluster = default_cluster_; + new_cluster.set_name(kNewClusterName); + new_cluster.mutable_lrs_server()->mutable_self(); + new_cluster.mutable_eds_cluster_config()->set_service_name( + kNewEdsServiceName); + authority_balancer_->ads_service()->SetCdsResource(new_cluster); + // New Route + RouteConfiguration new_route_config = default_route_config_; + new_route_config.set_name(kNewRouteConfigName); + new_route_config.mutable_virtual_hosts(0) + ->mutable_routes(0) + ->mutable_route() + ->set_cluster(kNewClusterName); + // New Listener + Listener listener = default_listener_; + listener.set_name(kNewListenerName); + SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, + new_route_config); + // Ensure update has reached and send 10 RPCs to the current stub. + CheckRpcSendOk(kNumRpcsToDefaultBalancer); + // Create second channel to new target uri and send 1 RPC . + auto channel2 = + CreateChannel(/*failover_timeout_ms=*/0, kNewServerName, kAuthority); + channel2->GetState(/*try_to_connect=*/true); + ASSERT_TRUE( + channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); + auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); + for (size_t i = 0; i < kNumRpcsToAuthorityBalancer; ++i) { + ClientContext context; + EchoRequest request; + request.set_message(kRequestMessage); + EchoResponse response; + grpc::Status status = stub2->Echo(&context, request, &response); + EXPECT_TRUE(status.ok()) << "code=" << status.error_code() + << " message=" << status.error_message(); + } + // Each backend should have received the expected number of RPCs, + // and the load report also reflect the correct numbers. + EXPECT_EQ(kNumRpcsToAuthorityBalancer, + backends_[1]->backend_service()->request_count()); + EXPECT_EQ(kNumRpcsToDefaultBalancer, + backends_[0]->backend_service()->request_count()); + // Load report for authority LRS. + std::vector authority_load_report = + authority_balancer_->lrs_service()->WaitForLoadReport(); + ASSERT_EQ(authority_load_report.size(), 1UL); + ClientStats& authority_client_stats = authority_load_report.front(); + EXPECT_EQ(kNumRpcsToAuthorityBalancer, + authority_client_stats.total_successful_requests()); + EXPECT_EQ(0U, authority_client_stats.total_requests_in_progress()); + EXPECT_EQ(kNumRpcsToAuthorityBalancer, + authority_client_stats.total_issued_requests()); + EXPECT_EQ(0U, authority_client_stats.total_error_requests()); + EXPECT_EQ(0U, authority_client_stats.total_dropped_requests()); + EXPECT_EQ(1U, authority_balancer_->lrs_service()->request_count()); + EXPECT_EQ(1U, authority_balancer_->lrs_service()->response_count()); + // Load report for default LRS. + std::vector default_load_report = + balancer_->lrs_service()->WaitForLoadReport(); + ASSERT_EQ(default_load_report.size(), 1UL); + ClientStats& default_client_stats = default_load_report.front(); + EXPECT_EQ(kNumRpcsToDefaultBalancer, + default_client_stats.total_successful_requests()); + EXPECT_EQ(0U, default_client_stats.total_requests_in_progress()); + EXPECT_EQ(kNumRpcsToDefaultBalancer, + default_client_stats.total_issued_requests()); + EXPECT_EQ(0U, default_client_stats.total_error_requests()); + EXPECT_EQ(0U, default_client_stats.total_dropped_requests()); + EXPECT_EQ(1U, balancer_->lrs_service()->request_count()); + EXPECT_EQ(1U, balancer_->lrs_service()->response_count()); +} + +// +// SecureNamingTest - test that the right authority is used for the xDS server +// + +class SecureNamingTest : public XdsEnd2endTest { + public: + void SetUp() override { + // Each test calls InitClient() on its own. + } +}; + +INSTANTIATE_TEST_SUITE_P(XdsTest, SecureNamingTest, + ::testing::Values(XdsTestType()), &XdsTestType::Name); + +// Tests that secure naming check passes if target name is expected. +TEST_P(SecureNamingTest, TargetNameIsExpected) { + InitClient(BootstrapBuilder(), /*lb_expected_authority=*/"localhost:%d"); + CreateAndStartBackends(4); + EdsResourceArgs args({ + {"locality0", CreateEndpointsForBackends()}, + }); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + CheckRpcSendOk(); +} + +// Tests that secure naming check fails if target name is unexpected. +TEST_P(SecureNamingTest, TargetNameIsUnexpected) { + GTEST_FLAG_SET(death_test_style, "threadsafe"); + InitClient(BootstrapBuilder(), + /*lb_expected_authority=*/"incorrect_server_name"); + CreateAndStartBackends(4); + EdsResourceArgs args({ + {"locality0", CreateEndpointsForBackends()}, + }); + balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); + // Make sure that we blow up (via abort() from the security connector) when + // the name from the balancer doesn't match expectations. + ASSERT_DEATH_IF_SUPPORTED({ CheckRpcSendOk(); }, ""); +} + +} // namespace +} // namespace testing +} // namespace grpc + +int main(int argc, char** argv) { + grpc::testing::TestEnvironment env(&argc, argv); + ::testing::InitGoogleTest(&argc, argv); + // Make the backup poller poll very frequently in order to pick up + // updates from all the subchannels's FDs. + GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1); +#if TARGET_OS_IPHONE + // Workaround Apple CFStream bug + gpr_setenv("grpc_cfstream", "0"); +#endif + grpc_init(); + const auto result = RUN_ALL_TESTS(); + grpc_shutdown(); + return result; +} diff --git a/test/cpp/end2end/xds/xds_end2end_test.cc b/test/cpp/end2end/xds/xds_end2end_test.cc index ee9445c467a..dde195cf5c8 100644 --- a/test/cpp/end2end/xds/xds_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_end2end_test.cc @@ -330,31 +330,6 @@ TEST_P(BasicTest, Vanilla) { channel_->GetLoadBalancingPolicyName()); } -// Tests that the client can handle resource wrapped in a Resource message. -TEST_P(BasicTest, ResourceWrappedInResourceMessage) { - CreateAndStartBackends(1); - balancer_->ads_service()->set_wrap_resources(true); - const size_t kNumRpcsPerAddress = 100; - EdsResourceArgs args({ - {"locality0", CreateEndpointsForBackends()}, - }); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - // Make sure that trying to connect works without a call. - channel_->GetState(true /* try_to_connect */); - // We need to wait for all backends to come online. - WaitForAllBackends(); - // Send kNumRpcsPerAddress RPCs per server. - CheckRpcSendOk(kNumRpcsPerAddress * backends_.size()); - // Each backend should have gotten 100 requests. - for (size_t i = 0; i < backends_.size(); ++i) { - EXPECT_EQ(kNumRpcsPerAddress, - backends_[i]->backend_service()->request_count()); - } - // Check LB policy name for the channel. - EXPECT_EQ("xds_cluster_manager_experimental", - channel_->GetLoadBalancingPolicyName()); -} - TEST_P(BasicTest, IgnoresUnhealthyEndpoints) { CreateAndStartBackends(2); const size_t kNumRpcsPerAddress = 100; @@ -485,29 +460,6 @@ TEST_P(BasicTest, IgnoresDuplicateUpdates) { using XdsResolverOnlyTest = XdsEnd2endTest; -TEST_P(XdsResolverOnlyTest, ResourceTypeVersionPersistsAcrossStreamRestarts) { - CreateAndStartBackends(2); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - // Wait for backends to come online. - WaitForAllBackends(0, 1); - // Stop balancer. - balancer_->Shutdown(); - // Tell balancer to require minimum version 1 for all resource types. - balancer_->ads_service()->SetResourceMinVersion(kLdsTypeUrl, 1); - balancer_->ads_service()->SetResourceMinVersion(kRdsTypeUrl, 1); - balancer_->ads_service()->SetResourceMinVersion(kCdsTypeUrl, 1); - balancer_->ads_service()->SetResourceMinVersion(kEdsTypeUrl, 1); - // Update backend, just so we can be sure that the client has - // reconnected to the balancer. - args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - // Restart balancer. - balancer_->Start(); - // Make sure client has reconnected. - WaitForAllBackends(1, 2); -} - // Tests switching over from one cluster to another. TEST_P(XdsResolverOnlyTest, ChangeClusters) { CreateAndStartBackends(2); @@ -559,55 +511,6 @@ TEST_P(XdsResolverOnlyTest, ClusterRemoved) { EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } -// Tests that we restart all xDS requests when we reestablish the ADS call. -TEST_P(XdsResolverOnlyTest, RestartsRequestsUponReconnection) { - CreateAndStartBackends(2); - // Manually configure use of RDS. - auto listener = default_listener_; - HttpConnectionManager http_connection_manager; - listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( - &http_connection_manager); - auto* rds = http_connection_manager.mutable_rds(); - rds->set_route_config_name(kDefaultRouteConfigurationName); - rds->mutable_config_source()->mutable_self(); - listener.mutable_api_listener()->mutable_api_listener()->PackFrom( - http_connection_manager); - balancer_->ads_service()->SetLdsResource(listener); - balancer_->ads_service()->SetRdsResource(default_route_config_); - const char* kNewClusterName = "new_cluster_name"; - const char* kNewEdsServiceName = "new_eds_service_name"; - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - // We need to wait for all backends to come online. - WaitForAllBackends(0, 1); - // Now shut down and restart the balancer. When the client - // reconnects, it should automatically restart the requests for all - // resource types. - balancer_->Shutdown(); - balancer_->Start(); - // Make sure things are still working. - CheckRpcSendOk(100); - // Populate new EDS resource. - args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); - balancer_->ads_service()->SetEdsResource( - BuildEdsResource(args, kNewEdsServiceName)); - // Populate new CDS resource. - Cluster new_cluster = default_cluster_; - new_cluster.set_name(kNewClusterName); - new_cluster.mutable_eds_cluster_config()->set_service_name( - kNewEdsServiceName); - balancer_->ads_service()->SetCdsResource(new_cluster); - // Change RDS resource to point to new cluster. - RouteConfiguration new_route_config = default_route_config_; - new_route_config.mutable_virtual_hosts(0) - ->mutable_routes(0) - ->mutable_route() - ->set_cluster(kNewClusterName); - balancer_->ads_service()->SetRdsResource(new_route_config); - // Wait for all new backends to be used. - WaitForAllBackends(1, 2); -} - TEST_P(XdsResolverOnlyTest, DefaultRouteSpecifiesSlashPrefix) { CreateAndStartBackends(1); RouteConfiguration route_config = default_route_config_; @@ -765,597 +668,6 @@ TEST_P(XdsResolverOnlyTest, KeepUsingLastDataIfBalancerGoesDown) { WaitForBackend(1); } -TEST_P(XdsResolverOnlyTest, XdsStreamErrorPropagation) { - const std::string kErrorMessage = "test forced ADS stream failure"; - balancer_->ads_service()->ForceADSFailure( - Status(StatusCode::RESOURCE_EXHAUSTED, kErrorMessage)); - auto status = SendRpc(); - gpr_log(GPR_INFO, - "XdsStreamErrorPropagation test: RPC got error: code=%d message=%s", - status.error_code(), status.error_message().c_str()); - EXPECT_THAT(status.error_code(), StatusCode::UNAVAILABLE); - EXPECT_THAT(status.error_message(), ::testing::HasSubstr(kErrorMessage)); - EXPECT_THAT(status.error_message(), - ::testing::HasSubstr("(node ID:xds_end2end_test)")); -} - -using GlobalXdsClientTest = XdsEnd2endTest; - -TEST_P(GlobalXdsClientTest, MultipleChannelsShareXdsClient) { - CreateAndStartBackends(1); - const char* kNewServerName = "new-server.example.com"; - Listener listener = default_listener_; - listener.set_name(kNewServerName); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(); - // Create second channel and tell it to connect to kNewServerName. - auto channel2 = CreateChannel(/*failover_timeout_ms=*/0, kNewServerName); - channel2->GetState(/*try_to_connect=*/true); - ASSERT_TRUE( - channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); - // Make sure there's only one client connected. - EXPECT_EQ(1UL, balancer_->ads_service()->clients().size()); -} - -TEST_P( - GlobalXdsClientTest, - MultipleChannelsShareXdsClientWithResourceUpdateAfterOneChannelGoesAway) { - CreateAndStartBackends(2); - // Test for https://github.com/grpc/grpc/issues/28468. Makes sure that the - // XdsClient properly handles the case where there are multiple watchers on - // the same resource and one of them unsubscribes. - const char* kNewServerName = "new-server.example.com"; - Listener listener = default_listener_; - listener.set_name(kNewServerName); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs({ - {"locality0", CreateEndpointsForBackends(0, 1)}, - }))); - WaitForBackend(0); - // Create second channel and tell it to connect to kNewServerName. - auto channel2 = CreateChannel(/*failover_timeout_ms=*/0, kNewServerName); - channel2->GetState(/*try_to_connect=*/true); - ASSERT_TRUE( - channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); - // Now, destroy the new channel, send an EDS update to use a different backend - // and test that the channel switches to that backend. - channel2.reset(); - // This sleep is needed to be able to reproduce the bug and to give time for - // the buggy unsubscription to take place. - // TODO(yashykt): Figure out a way to do this without the sleep. - gpr_sleep_until(grpc_timeout_milliseconds_to_deadline(10)); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(EdsResourceArgs({ - {"locality0", CreateEndpointsForBackends(1, 2)}, - }))); - WaitForBackend(1); -} - -// Tests that the NACK for multiple bad LDS resources includes both errors. -TEST_P(GlobalXdsClientTest, MultipleBadResources) { - CreateAndStartBackends(1); - constexpr char kServerName2[] = "server.other.com"; - constexpr char kServerName3[] = "server.another.com"; - auto listener = default_listener_; - listener.clear_api_listener(); - balancer_->ads_service()->SetLdsResource(listener); - listener.set_name(kServerName2); - balancer_->ads_service()->SetLdsResource(listener); - listener = default_listener_; - listener.set_name(kServerName3); - SetListenerAndRouteConfiguration(balancer_.get(), listener, - default_route_config_); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - const auto response_state = WaitForLdsNack(); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::ContainsRegex(absl::StrCat( - kServerName, - ": validation error.*" - "Listener has neither address nor ApiListener.*"))); - // Need to create a second channel to subscribe to a second LDS resource. - auto channel2 = CreateChannel(0, kServerName2); - auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); - { - ClientContext context; - EchoRequest request; - request.set_message(kRequestMessage); - EchoResponse response; - grpc::Status status = stub2->Echo(&context, request, &response); - EXPECT_FALSE(status.ok()); - // Wait for second NACK to be reported to xDS server. - const auto response_state = WaitForLdsNack(); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::ContainsRegex(absl::StrCat( - kServerName, - ": validation error.*" - "Listener has neither address nor ApiListener.*"))); - EXPECT_THAT(response_state->error_message, - ::testing::ContainsRegex(absl::StrCat( - kServerName2, - ": validation error.*" - "Listener has neither address nor ApiListener.*"))); - } - // Now start a new channel with a third server name, this one with a - // valid resource. - auto channel3 = CreateChannel(0, kServerName3); - auto stub3 = grpc::testing::EchoTestService::NewStub(channel3); - { - ClientContext context; - EchoRequest request; - request.set_message(kRequestMessage); - EchoResponse response; - grpc::Status status = stub3->Echo(&context, request, &response); - EXPECT_TRUE(status.ok()) << "code=" << status.error_code() - << " message=" << status.error_message(); - } -} - -// Tests that we don't trigger does-not-exist callbacks for a resource -// that was previously valid but is updated to be invalid. -TEST_P(GlobalXdsClientTest, InvalidListenerStillExistsIfPreviouslyCached) { - CreateAndStartBackends(1); - // Set up valid resources and check that the channel works. - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - CheckRpcSendOk(); - // Now send an update changing the Listener to be invalid. - auto listener = default_listener_; - listener.clear_api_listener(); - balancer_->ads_service()->SetLdsResource(listener); - const auto response_state = WaitForLdsNack(StatusCode::OK); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::ContainsRegex(absl::StrCat( - kServerName, - ": validation error.*" - "Listener has neither address nor ApiListener"))); - CheckRpcSendOk(); -} - -class XdsFederationTest : public XdsEnd2endTest { - protected: - XdsFederationTest() : authority_balancer_(CreateAndStartBalancer()) {} - - void SetUp() override { - // Each test will use a slightly different bootstrapfile, - // so SetUp() is intentionally empty here and the real - // setup (calling of InitClient()) is moved into each test. - } - - void TearDown() override { - authority_balancer_->Shutdown(); - XdsEnd2endTest::TearDown(); - } - - std::unique_ptr authority_balancer_; -}; - -// Channel is created with URI "xds:server.example.com". -// Bootstrap config default client listener template uses new-style name with -// authority "xds.example.com". -TEST_P(XdsFederationTest, FederationTargetNoAuthorityWithResourceTemplate) { - ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); - const char* kAuthority = "xds.example.com"; - const char* kNewListenerTemplate = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/%s?psm_project_id=1234"; - const char* kNewListenerName = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/server.example.com?psm_project_id=1234"; - const char* kNewRouteConfigName = - "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" - "new_route_config_name"; - const char* kNewEdsServiceName = - "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" - "new_edsservice_name"; - const char* kNewClusterName = - "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" - "new_cluster_name"; - BootstrapBuilder builder = BootstrapBuilder(); - builder.SetClientDefaultListenerResourceNameTemplate(kNewListenerTemplate); - builder.AddAuthority( - kAuthority, absl::StrCat("localhost:", authority_balancer_->port()), - // Note we will not use the client_listener_resource_name_template field - // in the authority. - "xdstp://xds.example.com/envoy.config.listener.v3.Listener" - "client/%s?client_listener_resource_name_template_not_in_use"); - InitClient(builder); - CreateAndStartBackends(2, /*xds_enabled=*/true); - // Eds for the new authority balancer. - EdsResourceArgs args = - EdsResourceArgs({{"locality0", CreateEndpointsForBackends()}}); - authority_balancer_->ads_service()->SetEdsResource( - BuildEdsResource(args, kNewEdsServiceName)); - // New cluster - Cluster new_cluster = default_cluster_; - new_cluster.set_name(kNewClusterName); - new_cluster.mutable_eds_cluster_config()->set_service_name( - kNewEdsServiceName); - authority_balancer_->ads_service()->SetCdsResource(new_cluster); - // New Route - RouteConfiguration new_route_config = default_route_config_; - new_route_config.set_name(kNewRouteConfigName); - new_route_config.mutable_virtual_hosts(0) - ->mutable_routes(0) - ->mutable_route() - ->set_cluster(kNewClusterName); - // New Listener - Listener listener = default_listener_; - listener.set_name(kNewListenerName); - SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, - new_route_config); - WaitForAllBackends(); -} - -// Channel is created with URI "xds://xds.example.com/server.example.com". -// In bootstrap config, authority has no client listener template, so we use the -// default. -TEST_P(XdsFederationTest, FederationTargetAuthorityDefaultResourceTemplate) { - ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); - const char* kAuthority = "xds.example.com"; - const char* kNewServerName = "whee%/server.example.com"; - const char* kNewListenerName = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "whee%25/server.example.com"; - const char* kNewRouteConfigName = - "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" - "new_route_config_name"; - const char* kNewEdsServiceName = - "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" - "edsservice_name"; - const char* kNewClusterName = - "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" - "cluster_name"; - BootstrapBuilder builder = BootstrapBuilder(); - builder.AddAuthority(kAuthority, - absl::StrCat("localhost:", authority_balancer_->port())); - InitClient(builder); - CreateAndStartBackends(2, /*xds_enabled=*/true); - // Eds for 2 balancers to ensure RPCs sent using current stub go to backend 0 - // and RPCs sent using the new stub go to backend 1. - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); - authority_balancer_->ads_service()->SetEdsResource( - BuildEdsResource(args, kNewEdsServiceName)); - // New cluster - Cluster new_cluster = default_cluster_; - new_cluster.set_name(kNewClusterName); - new_cluster.mutable_eds_cluster_config()->set_service_name( - kNewEdsServiceName); - authority_balancer_->ads_service()->SetCdsResource(new_cluster); - // New Route - RouteConfiguration new_route_config = default_route_config_; - new_route_config.set_name(kNewRouteConfigName); - new_route_config.mutable_virtual_hosts(0) - ->mutable_routes(0) - ->mutable_route() - ->set_cluster(kNewClusterName); - // New Listener - Listener listener = default_listener_; - listener.set_name(kNewListenerName); - SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, - new_route_config); - // Ensure update has reached and send 10 RPCs to the current stub. - WaitForAllBackends(0, 1); - // Create second channel to new target uri and send 1 RPC . - auto channel2 = - CreateChannel(/*failover_timeout_ms=*/0, kNewServerName, kAuthority); - channel2->GetState(/*try_to_connect=*/true); - ASSERT_TRUE( - channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); - auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); - ClientContext context; - EchoRequest request; - request.set_message(kRequestMessage); - EchoResponse response; - grpc::Status status = stub2->Echo(&context, request, &response); - EXPECT_TRUE(status.ok()) << "code=" << status.error_code() - << " message=" << status.error_message(); - // We should be reaching backend 1, not 0, as balanced by the authority xds - // server. - EXPECT_EQ(0U, backends_[0]->backend_service()->request_count()); - EXPECT_EQ(1U, backends_[1]->backend_service()->request_count()); -} - -// Channel is created with URI "xds://xds.example.com/server.example.com". -// Bootstrap entry for that authority specifies a client listener name template. -TEST_P(XdsFederationTest, FederationTargetAuthorityWithResourceTemplate) { - ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); - const char* kAuthority = "xds.example.com"; - const char* kNewServerName = "whee%/server.example.com"; - const char* kNewListenerTemplate = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/%s?psm_project_id=1234"; - const char* kNewListenerName = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/whee%25/server.example.com?psm_project_id=1234"; - const char* kNewRouteConfigName = - "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" - "new_route_config_name"; - const char* kNewEdsServiceName = - "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" - "edsservice_name"; - const char* kNewClusterName = - "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" - "cluster_name"; - BootstrapBuilder builder = BootstrapBuilder(); - builder.AddAuthority(kAuthority, - absl::StrCat("localhost:", authority_balancer_->port()), - kNewListenerTemplate); - InitClient(builder); - CreateAndStartBackends(2, /*xds_enabled=*/true); - // Eds for 2 balancers to ensure RPCs sent using current stub go to backend 0 - // and RPCs sent using the new stub go to backend 1. - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); - authority_balancer_->ads_service()->SetEdsResource( - BuildEdsResource(args, kNewEdsServiceName)); - // New cluster - Cluster new_cluster = default_cluster_; - new_cluster.set_name(kNewClusterName); - new_cluster.mutable_eds_cluster_config()->set_service_name( - kNewEdsServiceName); - authority_balancer_->ads_service()->SetCdsResource(new_cluster); - // New Route - RouteConfiguration new_route_config = default_route_config_; - new_route_config.set_name(kNewRouteConfigName); - new_route_config.mutable_virtual_hosts(0) - ->mutable_routes(0) - ->mutable_route() - ->set_cluster(kNewClusterName); - // New Listener - Listener listener = default_listener_; - listener.set_name(kNewListenerName); - SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, - new_route_config); - // Ensure update has reached and send 10 RPCs to the current stub. - WaitForAllBackends(0, 1); - // Create second channel to new target uri and send 1 RPC . - auto channel2 = - CreateChannel(/*failover_timeout_ms=*/0, kNewServerName, kAuthority); - channel2->GetState(/*try_to_connect=*/true); - ASSERT_TRUE( - channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); - auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); - ClientContext context; - EchoRequest request; - request.set_message(kRequestMessage); - EchoResponse response; - grpc::Status status = stub2->Echo(&context, request, &response); - EXPECT_TRUE(status.ok()) << "code=" << status.error_code() - << " message=" << status.error_message(); - // We should be reaching backend 1, not 0, as balanced by the authority xds - // server. - EXPECT_EQ(0U, backends_[0]->backend_service()->request_count()); - EXPECT_EQ(1U, backends_[1]->backend_service()->request_count()); -} - -// Setting server_listener_resource_name_template to start with "xdstp:" and -// look up xds server under an authority map. -TEST_P(XdsFederationTest, FederationServer) { - ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); - const char* kAuthority = "xds.example.com"; - const char* kNewListenerTemplate = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/%s?psm_project_id=1234"; - const char* kNewServerListenerTemplate = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "server/%s?psm_project_id=1234"; - const char* kNewListenerName = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/server.example.com?psm_project_id=1234"; - const char* kNewRouteConfigName = - "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" - "new_route_config_name"; - const char* kNewEdsServiceName = - "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" - "new_edsservice_name"; - const char* kNewClusterName = - "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" - "new_cluster_name"; - BootstrapBuilder builder = BootstrapBuilder(); - builder.SetClientDefaultListenerResourceNameTemplate(kNewListenerTemplate); - builder.SetServerListenerResourceNameTemplate(kNewServerListenerTemplate); - builder.AddAuthority( - kAuthority, absl::StrCat("localhost:", authority_balancer_->port()), - // Note we will not use the client_listener_resource_name_template field - // in the authority. - "xdstp://xds.example.com/envoy.config.listener.v3.Listener" - "client/%s?client_listener_resource_name_template_not_in_use"); - InitClient(builder); - CreateAndStartBackends(2, /*xds_enabled=*/true); - // Eds for new authority balancer. - EdsResourceArgs args = - EdsResourceArgs({{"locality0", CreateEndpointsForBackends()}}); - authority_balancer_->ads_service()->SetEdsResource( - BuildEdsResource(args, kNewEdsServiceName)); - // New cluster - Cluster new_cluster = default_cluster_; - new_cluster.set_name(kNewClusterName); - new_cluster.mutable_eds_cluster_config()->set_service_name( - kNewEdsServiceName); - authority_balancer_->ads_service()->SetCdsResource(new_cluster); - // New Route - RouteConfiguration new_route_config = default_route_config_; - new_route_config.set_name(kNewRouteConfigName); - new_route_config.mutable_virtual_hosts(0) - ->mutable_routes(0) - ->mutable_route() - ->set_cluster(kNewClusterName); - // New Listener - Listener listener = default_listener_; - listener.set_name(kNewListenerName); - SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, - new_route_config); - // New Server Listeners - for (int port : GetBackendPorts()) { - Listener server_listener = default_server_listener_; - server_listener.set_name(absl::StrCat( - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/server/", - ipv6_only_ ? "%5B::1%5D:" : "127.0.0.1:", port, - "?psm_project_id=1234")); - server_listener.mutable_address()->mutable_socket_address()->set_port_value( - port); - authority_balancer_->ads_service()->SetLdsResource(server_listener); - } - WaitForAllBackends(); -} - -using XdsFederationLoadReportingTest = XdsFederationTest; - -// Channel is created with URI "xds://xds.example.com/server.example.com". -// Bootstrap entry for that authority specifies a client listener name template. -// Sending traffic to both default balancer and authority balancer and checking -// load reporting with each one. -TEST_P(XdsFederationLoadReportingTest, FederationMultipleLoadReportingTest) { - ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_FEDERATION"); - const char* kAuthority = "xds.example.com"; - const char* kNewServerName = "whee%/server.example.com"; - const char* kNewListenerTemplate = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/%s?psm_project_id=1234"; - const char* kNewListenerName = - "xdstp://xds.example.com/envoy.config.listener.v3.Listener/" - "client/whee%25/server.example.com?psm_project_id=1234"; - const char* kNewRouteConfigName = - "xdstp://xds.example.com/envoy.config.route.v3.RouteConfiguration/" - "new_route_config_name"; - const char* kNewEdsServiceName = - "xdstp://xds.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/" - "edsservice_name"; - const char* kNewClusterName = - "xdstp://xds.example.com/envoy.config.cluster.v3.Cluster/" - "cluster_name"; - const size_t kNumRpcsToDefaultBalancer = 5; - const size_t kNumRpcsToAuthorityBalancer = 10; - BootstrapBuilder builder = BootstrapBuilder(); - builder.AddAuthority(kAuthority, - absl::StrCat("localhost:", authority_balancer_->port()), - kNewListenerTemplate); - InitClient(builder); - CreateAndStartBackends(2, /*xds_enabled=*/true); - // Eds for 2 balancers to ensure RPCs sent using current stub go to backend 0 - // and RPCs sent using the new stub go to backend 1. - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); - authority_balancer_->ads_service()->SetEdsResource( - BuildEdsResource(args, kNewEdsServiceName)); - authority_balancer_->lrs_service()->set_cluster_names({kNewClusterName}); - // New cluster - Cluster new_cluster = default_cluster_; - new_cluster.set_name(kNewClusterName); - new_cluster.mutable_lrs_server()->mutable_self(); - new_cluster.mutable_eds_cluster_config()->set_service_name( - kNewEdsServiceName); - authority_balancer_->ads_service()->SetCdsResource(new_cluster); - // New Route - RouteConfiguration new_route_config = default_route_config_; - new_route_config.set_name(kNewRouteConfigName); - new_route_config.mutable_virtual_hosts(0) - ->mutable_routes(0) - ->mutable_route() - ->set_cluster(kNewClusterName); - // New Listener - Listener listener = default_listener_; - listener.set_name(kNewListenerName); - SetListenerAndRouteConfiguration(authority_balancer_.get(), listener, - new_route_config); - // Ensure update has reached and send 10 RPCs to the current stub. - CheckRpcSendOk(kNumRpcsToDefaultBalancer); - // Create second channel to new target uri and send 1 RPC . - auto channel2 = - CreateChannel(/*failover_timeout_ms=*/0, kNewServerName, kAuthority); - channel2->GetState(/*try_to_connect=*/true); - ASSERT_TRUE( - channel2->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100))); - auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); - for (size_t i = 0; i < kNumRpcsToAuthorityBalancer; ++i) { - ClientContext context; - EchoRequest request; - request.set_message(kRequestMessage); - EchoResponse response; - grpc::Status status = stub2->Echo(&context, request, &response); - EXPECT_TRUE(status.ok()) << "code=" << status.error_code() - << " message=" << status.error_message(); - } - // Each backend should have received the expected number of RPCs, - // and the load report also reflect the correct numbers. - EXPECT_EQ(kNumRpcsToAuthorityBalancer, - backends_[1]->backend_service()->request_count()); - EXPECT_EQ(kNumRpcsToDefaultBalancer, - backends_[0]->backend_service()->request_count()); - // Load report for authority LRS. - std::vector authority_load_report = - authority_balancer_->lrs_service()->WaitForLoadReport(); - ASSERT_EQ(authority_load_report.size(), 1UL); - ClientStats& authority_client_stats = authority_load_report.front(); - EXPECT_EQ(kNumRpcsToAuthorityBalancer, - authority_client_stats.total_successful_requests()); - EXPECT_EQ(0U, authority_client_stats.total_requests_in_progress()); - EXPECT_EQ(kNumRpcsToAuthorityBalancer, - authority_client_stats.total_issued_requests()); - EXPECT_EQ(0U, authority_client_stats.total_error_requests()); - EXPECT_EQ(0U, authority_client_stats.total_dropped_requests()); - EXPECT_EQ(1U, authority_balancer_->lrs_service()->request_count()); - EXPECT_EQ(1U, authority_balancer_->lrs_service()->response_count()); - // Load report for default LRS. - std::vector default_load_report = - balancer_->lrs_service()->WaitForLoadReport(); - ASSERT_EQ(default_load_report.size(), 1UL); - ClientStats& default_client_stats = default_load_report.front(); - EXPECT_EQ(kNumRpcsToDefaultBalancer, - default_client_stats.total_successful_requests()); - EXPECT_EQ(0U, default_client_stats.total_requests_in_progress()); - EXPECT_EQ(kNumRpcsToDefaultBalancer, - default_client_stats.total_issued_requests()); - EXPECT_EQ(0U, default_client_stats.total_error_requests()); - EXPECT_EQ(0U, default_client_stats.total_dropped_requests()); - EXPECT_EQ(1U, balancer_->lrs_service()->request_count()); - EXPECT_EQ(1U, balancer_->lrs_service()->response_count()); -} - -class SecureNamingTest : public XdsEnd2endTest { - public: - void SetUp() override { - // Each test calls InitClient() on its own. - } -}; - -// Tests that secure naming check passes if target name is expected. -TEST_P(SecureNamingTest, TargetNameIsExpected) { - InitClient(BootstrapBuilder(), /*lb_expected_authority=*/"localhost:%d"); - CreateAndStartBackends(4); - EdsResourceArgs args({ - {"locality0", CreateEndpointsForBackends()}, - }); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - CheckRpcSendOk(); -} - -// Tests that secure naming check fails if target name is unexpected. -TEST_P(SecureNamingTest, TargetNameIsUnexpected) { - GTEST_FLAG_SET(death_test_style, "threadsafe"); - InitClient(BootstrapBuilder(), - /*lb_expected_authority=*/"incorrect_server_name"); - CreateAndStartBackends(4); - EdsResourceArgs args({ - {"locality0", CreateEndpointsForBackends()}, - }); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - // Make sure that we blow up (via abort() from the security connector) when - // the name from the balancer doesn't match expectations. - ASSERT_DEATH_IF_SUPPORTED({ CheckRpcSendOk(); }, ""); -} - using LdsTest = XdsEnd2endTest; // Tests that LDS client should send a NACK if there is no API listener in the @@ -5388,69 +4700,6 @@ TEST_P(CdsTest, UnsupportedClusterType) { ::testing::HasSubstr("DiscoveryType is not valid.")); } -// Tests that the NACK for multiple bad resources includes both errors. -TEST_P(CdsTest, MultipleBadResources) { - constexpr char kClusterName2[] = "cluster_name_2"; - constexpr char kClusterName3[] = "cluster_name_3"; - CreateAndStartBackends(1); - // Add cluster with unsupported type. - auto cluster = default_cluster_; - cluster.set_name(kClusterName2); - cluster.set_type(Cluster::STATIC); - balancer_->ads_service()->SetCdsResource(cluster); - // Add second cluster with the same error. - cluster.set_name(kClusterName3); - balancer_->ads_service()->SetCdsResource(cluster); - // Change RouteConfig to point to all clusters. - RouteConfiguration route_config = default_route_config_; - route_config.mutable_virtual_hosts(0)->clear_routes(); - // First route: default cluster, selected based on header. - auto* route = route_config.mutable_virtual_hosts(0)->add_routes(); - route->mutable_match()->set_prefix(""); - auto* header_matcher = route->mutable_match()->add_headers(); - header_matcher->set_name("cluster"); - header_matcher->set_exact_match(kDefaultClusterName); - route->mutable_route()->set_cluster(kDefaultClusterName); - // Second route: cluster 2, selected based on header. - route = route_config.mutable_virtual_hosts(0)->add_routes(); - route->mutable_match()->set_prefix(""); - header_matcher = route->mutable_match()->add_headers(); - header_matcher->set_name("cluster"); - header_matcher->set_exact_match(kClusterName2); - route->mutable_route()->set_cluster(kClusterName2); - // Third route: cluster 3, used by default. - route = route_config.mutable_virtual_hosts(0)->add_routes(); - route->mutable_match()->set_prefix(""); - route->mutable_route()->set_cluster(kClusterName3); - SetRouteConfiguration(balancer_.get(), route_config); - // Add EDS resource. - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - // Send RPC. - const auto response_state = WaitForCdsNack(); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT( - response_state->error_message, - ::testing::ContainsRegex(absl::StrCat(kClusterName2, - ": validation error.*" - "DiscoveryType is not valid.*", - kClusterName3, - ": validation error.*" - "DiscoveryType is not valid"))); - // RPCs for default cluster should succeed. - std::vector> metadata_default_cluster = { - {"cluster", kDefaultClusterName}, - }; - CheckRpcSendOk( - 1, RpcOptions().set_metadata(std::move(metadata_default_cluster))); - // RPCs for cluster 2 should fail. - std::vector> metadata_cluster_2 = { - {"cluster", kClusterName2}, - }; - CheckRpcSendFailure(CheckRpcSendFailureOptions().set_rpc_options( - RpcOptions().set_metadata(std::move(metadata_cluster_2)))); -} - // Tests that we don't trigger does-not-exist callbacks for a resource // that was previously valid but is updated to be invalid. TEST_P(CdsTest, InvalidClusterStillExistsIfPreviouslyCached) { @@ -10045,175 +9294,6 @@ TEST_P(EdsTest, EdsServiceNameDefaultsToClusterName) { CheckRpcSendOk(); } -class TimeoutTest : public XdsEnd2endTest { - protected: - void SetUp() override { - InitClient(BootstrapBuilder(), /*lb_expected_authority=*/"", - /*xds_resource_does_not_exist_timeout_ms=*/500); - } -}; - -TEST_P(TimeoutTest, LdsServerIgnoresRequest) { - balancer_->ads_service()->IgnoreResourceType(kLdsTypeUrl); - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, LdsResourceNotPresentInRequest) { - balancer_->ads_service()->UnsetResource(kLdsTypeUrl, kServerName); - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, LdsSecondResourceNotPresentInRequest) { - ASSERT_NE(GetParam().bootstrap_source(), - XdsTestType::kBootstrapFromChannelArg) - << "This test cannot use bootstrap from channel args, because it " - "needs two channels to use the same XdsClient instance."; - CreateAndStartBackends(1); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(); - // Create second channel for a new server name. - // This should fail because there is no LDS resource for this server name. - auto channel2 = - CreateChannel(/*failover_timeout_ms=*/0, "new-server.example.com"); - auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); - ClientContext context; - EchoRequest request; - EchoResponse response; - RpcOptions rpc_options; - rpc_options.SetupRpc(&context, &request); - auto status = - SendRpcMethod(stub2.get(), rpc_options, &context, request, &response); - EXPECT_EQ(StatusCode::UNAVAILABLE, status.error_code()); -} - -TEST_P(TimeoutTest, RdsServerIgnoresRequest) { - balancer_->ads_service()->IgnoreResourceType(kRdsTypeUrl); - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, RdsResourceNotPresentInRequest) { - balancer_->ads_service()->UnsetResource(kRdsTypeUrl, - kDefaultRouteConfigurationName); - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, RdsSecondResourceNotPresentInRequest) { - ASSERT_NE(GetParam().bootstrap_source(), - XdsTestType::kBootstrapFromChannelArg) - << "This test cannot use bootstrap from channel args, because it " - "needs two channels to use the same XdsClient instance."; - CreateAndStartBackends(1); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - // Add listener for 2nd channel, but no RDS resource. - const char* kNewServerName = "new-server.example.com"; - Listener listener = default_listener_; - listener.set_name(kNewServerName); - HttpConnectionManager http_connection_manager = - ClientHcmAccessor().Unpack(listener); - auto* rds = http_connection_manager.mutable_rds(); - rds->set_route_config_name("rds_resource_does_not_exist"); - rds->mutable_config_source()->mutable_self(); - ClientHcmAccessor().Pack(http_connection_manager, &listener); - balancer_->ads_service()->SetLdsResource(listener); - WaitForAllBackends(); - // Create second channel for a new server name. - // This should fail because the LDS resource points to a non-existent RDS - // resource. - auto channel2 = CreateChannel(/*failover_timeout_ms=*/0, kNewServerName); - auto stub2 = grpc::testing::EchoTestService::NewStub(channel2); - ClientContext context; - EchoRequest request; - EchoResponse response; - RpcOptions rpc_options; - rpc_options.SetupRpc(&context, &request); - auto status = - SendRpcMethod(stub2.get(), rpc_options, &context, request, &response); - EXPECT_EQ(StatusCode::UNAVAILABLE, status.error_code()); -} - -TEST_P(TimeoutTest, CdsServerIgnoresRequest) { - balancer_->ads_service()->IgnoreResourceType(kCdsTypeUrl); - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, CdsResourceNotPresentInRequest) { - balancer_->ads_service()->UnsetResource(kCdsTypeUrl, kDefaultClusterName); - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, CdsSecondResourceNotPresentInRequest) { - CreateAndStartBackends(1); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(); - // Change route config to point to non-existing cluster. - const char* kNewClusterName = "new_cluster_name"; - RouteConfiguration route_config = default_route_config_; - route_config.mutable_virtual_hosts(0) - ->mutable_routes(0) - ->mutable_route() - ->set_cluster(kNewClusterName); - balancer_->ads_service()->SetRdsResource(route_config); - // New cluster times out. - // May need to wait a bit for the change to propagate to the client. - gpr_timespec deadline = grpc_timeout_seconds_to_deadline(10); - bool error_seen = false; - do { - auto status = SendRpc(); - if (status.error_code() == StatusCode::UNAVAILABLE) { - error_seen = true; - break; - } - } while (gpr_time_cmp(gpr_now(GPR_CLOCK_MONOTONIC), deadline) < 0); - EXPECT_TRUE(error_seen); -} - -TEST_P(TimeoutTest, EdsServerIgnoresRequest) { - balancer_->ads_service()->IgnoreResourceType(kEdsTypeUrl); - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, EdsResourceNotPresentInRequest) { - // No need to remove EDS resource, since the test suite does not add it - // by default. - CheckRpcSendFailure(); -} - -TEST_P(TimeoutTest, EdsSecondResourceNotPresentInRequest) { - CreateAndStartBackends(1); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(); - // New cluster that points to a non-existant EDS resource. - const char* kNewClusterName = "new_cluster_name"; - Cluster cluster = default_cluster_; - cluster.set_name(kNewClusterName); - cluster.mutable_eds_cluster_config()->set_service_name( - "eds_service_name_does_not_exist"); - balancer_->ads_service()->SetCdsResource(cluster); - // Now add a route pointing to the new cluster. - RouteConfiguration route_config = default_route_config_; - auto* route = route_config.mutable_virtual_hosts(0)->mutable_routes(0); - *route_config.mutable_virtual_hosts(0)->add_routes() = *route; - route->mutable_match()->set_path("/grpc.testing.EchoTestService/Echo1"); - route->mutable_route()->set_cluster(kNewClusterName); - balancer_->ads_service()->SetRdsResource(route_config); - // New EDS resource times out. - // May need to wait a bit for the RDS change to propagate to the client. - gpr_timespec deadline = grpc_timeout_seconds_to_deadline(10); - bool error_seen = false; - do { - auto status = SendRpc(RpcOptions().set_rpc_method(METHOD_ECHO1)); - if (status.error_code() == StatusCode::UNAVAILABLE) { - error_seen = true; - break; - } - } while (gpr_time_cmp(gpr_now(GPR_CLOCK_MONOTONIC), deadline) < 0); - EXPECT_TRUE(error_seen); -} - using LocalityMapTest = XdsEnd2endTest; // Tests that the localities in a locality map are picked according to their @@ -11185,26 +10265,12 @@ TEST_P(ClientLoadReportingTest, DropStats) { ::testing::DoubleNear(kDropRateForThrottle, kErrorTolerance)); } -using BootstrapSourceTest = XdsEnd2endTest; - -TEST_P(BootstrapSourceTest, Vanilla) { - CreateAndStartBackends(1); - EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - WaitForAllBackends(); -} - // Run both with and without load reporting. INSTANTIATE_TEST_SUITE_P( XdsTest, BasicTest, ::testing::Values(XdsTestType(), XdsTestType().set_enable_load_reporting()), &XdsTestType::Name); -// Don't run with load reporting or v2 or RDS, since they are irrelevant to -// the tests. -INSTANTIATE_TEST_SUITE_P(XdsTest, SecureNamingTest, - ::testing::Values(XdsTestType()), &XdsTestType::Name); - // LDS depends on XdsResolver. INSTANTIATE_TEST_SUITE_P(XdsTest, LdsTest, ::testing::Values(XdsTestType()), &XdsTestType::Name); @@ -11412,56 +10478,12 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Values(XdsTestType(), XdsTestType().set_enable_load_reporting()), &XdsTestType::Name); -// Test initial resource timeouts for each resource type. -// Do this only for XdsResolver with RDS enabled, so that we can test -// all resource types. -// Run with V3 only, since the functionality is no different in V2. -// Run with bootstrap from env var so that multiple channels share the same -// XdsClient (needed for testing the timeout for the 2nd LDS and RDS resource). -INSTANTIATE_TEST_SUITE_P( - XdsTest, TimeoutTest, - ::testing::Values( - XdsTestType().set_enable_rds_testing().set_bootstrap_source( - XdsTestType::kBootstrapFromEnvVar)), - &XdsTestType::Name); - // XdsResolverOnlyTest depends on XdsResolver. INSTANTIATE_TEST_SUITE_P( XdsTest, XdsResolverOnlyTest, ::testing::Values(XdsTestType(), XdsTestType().set_enable_load_reporting()), &XdsTestType::Name); -// Runs with bootstrap from env var, so that there's a global XdsClient. -INSTANTIATE_TEST_SUITE_P( - XdsTest, GlobalXdsClientTest, - ::testing::Values( - XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), - XdsTestType() - .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) - .set_enable_load_reporting()), - &XdsTestType::Name); - -INSTANTIATE_TEST_SUITE_P( - XdsTest, XdsFederationTest, - ::testing::Values( - XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), - XdsTestType() - .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) - .set_enable_rds_testing()), - &XdsTestType::Name); - -INSTANTIATE_TEST_SUITE_P( - XdsTest, XdsFederationLoadReportingTest, - ::testing::Values( - XdsTestType() - .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) - .set_enable_load_reporting(), - XdsTestType() - .set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) - .set_enable_load_reporting() - .set_enable_rds_testing()), - &XdsTestType::Name); - INSTANTIATE_TEST_SUITE_P( XdsTest, LocalityMapTest, ::testing::Values(XdsTestType(), XdsTestType().set_enable_load_reporting()), @@ -11481,13 +10503,6 @@ INSTANTIATE_TEST_SUITE_P( ::testing::Values(XdsTestType().set_enable_load_reporting()), &XdsTestType::Name); -INSTANTIATE_TEST_SUITE_P( - XdsTest, BootstrapSourceTest, - ::testing::Values( - XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), - XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromFile)), - &XdsTestType::Name); - } // namespace } // namespace testing } // namespace grpc diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index 41396ac45fc..b308d5308d9 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -7615,6 +7615,28 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "xds_core_end2end_test", + "platforms": [ + "linux", + "mac", + "posix" + ], + "uses_polling": true + }, { "args": [], "benchmark": false,