From cdb7d2c93c2a1d82c048ec8402702bee95381180 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Mon, 26 Sep 2022 11:45:56 -0700 Subject: [PATCH] XdsEndpointResourceType: use ValidationErrors and add unit tests (#31076) * simplify XdsResourceType::Decode() API * fix xds_client_test * xDS endpoint: use ValidationErrors to improve error messages * fix sanity * add xDS endpoint resource type test * clang-format * remove a couple of now-unnecessary end2end tests * generate_projects * fix xds_csds_e2e_test * iwyu * attempt to avoid ubsan failure --- BUILD | 1 + CMakeLists.txt | 52 ++ build_autogenerated.yaml | 14 + src/core/ext/xds/xds_endpoint.cc | 299 ++++--- test/core/xds/BUILD | 16 + .../xds/xds_endpoint_resource_type_test.cc | 797 ++++++++++++++++++ .../end2end/xds/xds_cluster_end2end_test.cc | 45 +- test/cpp/end2end/xds/xds_csds_end2end_test.cc | 7 +- tools/run_tests/generated/tests.json | 24 + 9 files changed, 1093 insertions(+), 162 deletions(-) create mode 100644 test/core/xds/xds_endpoint_resource_type_test.cc diff --git a/BUILD b/BUILD index a949a027570..ee73d392136 100644 --- a/BUILD +++ b/BUILD @@ -4757,6 +4757,7 @@ grpc_cc_library( "upb_utils", "uri_parser", "useful", + "validation_errors", "work_serializer", "xds_client", "xds_type_upb", diff --git a/CMakeLists.txt b/CMakeLists.txt index 5500fe0566f..30b4d211fdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1269,6 +1269,7 @@ if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx xds_end2end_test) endif() + add_dependencies(buildtests_cxx xds_endpoint_resource_type_test) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) add_dependencies(buildtests_cxx xds_fault_injection_end2end_test) endif() @@ -20998,6 +20999,57 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) endif() +endif() +if(gRPC_BUILD_TESTS) + +add_executable(xds_endpoint_resource_type_test + ${_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/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/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/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 + test/core/xds/xds_endpoint_resource_type_test.cc + third_party/googletest/googletest/src/gtest-all.cc + third_party/googletest/googlemock/src/gmock-all.cc +) + +target_include_directories(xds_endpoint_resource_type_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_endpoint_resource_type_test + ${_gRPC_PROTOBUF_LIBRARIES} + ${_gRPC_ALLTARGETS_LIBRARIES} + grpc_test_util +) + + endif() if(gRPC_BUILD_TESTS) if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX) diff --git a/build_autogenerated.yaml b/build_autogenerated.yaml index cf6575e9cc0..869c9eba8e1 100644 --- a/build_autogenerated.yaml +++ b/build_autogenerated.yaml @@ -11201,6 +11201,20 @@ targets: - linux - posix - mac +- name: xds_endpoint_resource_type_test + gtest: true + build: test + language: c++ + headers: [] + src: + - src/proto/grpc/testing/xds/v3/address.proto + - src/proto/grpc/testing/xds/v3/base.proto + - src/proto/grpc/testing/xds/v3/endpoint.proto + - src/proto/grpc/testing/xds/v3/percent.proto + - test/core/xds/xds_endpoint_resource_type_test.cc + deps: + - grpc_test_util + uses_polling: false - name: xds_fault_injection_end2end_test gtest: true build: test diff --git a/src/core/ext/xds/xds_endpoint.cc b/src/core/ext/xds/xds_endpoint.cc index 2800fd47e67..53f4946f440 100644 --- a/src/core/ext/xds/xds_endpoint.cc +++ b/src/core/ext/xds/xds_endpoint.cc @@ -46,6 +46,8 @@ #include "src/core/lib/address_utils/parse_address.h" #include "src/core/lib/channel/channel_args.h" #include "src/core/lib/debug/trace.h" +#include "src/core/lib/gprpp/validation_errors.h" +#include "src/core/lib/iomgr/resolved_address.h" namespace grpc_core { @@ -140,47 +142,78 @@ void MaybeLogClusterLoadAssignment( } } -absl::StatusOr> ServerAddressParse( - const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint) { - // If health_status is not HEALTHY or UNKNOWN, skip this endpoint. +absl::optional ServerAddressParse( + const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint, + ValidationErrors* errors) { + // health_status + // If not HEALTHY or UNKNOWN, skip this endpoint. const int32_t health_status = envoy_config_endpoint_v3_LbEndpoint_health_status(lb_endpoint); if (health_status != envoy_config_core_v3_UNKNOWN && health_status != envoy_config_core_v3_HEALTHY) { return absl::nullopt; } - // Find the ip:port. - const envoy_config_endpoint_v3_Endpoint* endpoint = - envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint); - const envoy_config_core_v3_Address* address = - envoy_config_endpoint_v3_Endpoint_address(endpoint); - const envoy_config_core_v3_SocketAddress* socket_address = - envoy_config_core_v3_Address_socket_address(address); - std::string address_str = UpbStringToStdString( - envoy_config_core_v3_SocketAddress_address(socket_address)); - uint32_t port = envoy_config_core_v3_SocketAddress_port_value(socket_address); - if (GPR_UNLIKELY(port >> 16) != 0) { - return absl::InvalidArgumentError("Invalid port."); - } - // Find load_balancing_weight for the endpoint. + // load_balancing_weight uint32_t weight = 1; - const google_protobuf_UInt32Value* load_balancing_weight = - envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint); - if (load_balancing_weight != nullptr) { - weight = google_protobuf_UInt32Value_value(load_balancing_weight); - if (weight == 0) { - return absl::InvalidArgumentError("Invalid endpoint weight of 0."); + { + ValidationErrors::ScopedField field(errors, ".load_balancing_weight"); + const google_protobuf_UInt32Value* load_balancing_weight = + envoy_config_endpoint_v3_LbEndpoint_load_balancing_weight(lb_endpoint); + if (load_balancing_weight != nullptr) { + weight = google_protobuf_UInt32Value_value(load_balancing_weight); + if (weight == 0) { + errors->AddError("must be greater than 0"); + } } } - // Populate grpc_resolved_address. - auto addr = StringToSockaddr(address_str, port); - if (!addr.ok()) return addr.status(); - // Append the address to the list. + // endpoint + grpc_resolved_address grpc_address; + { + ValidationErrors::ScopedField field(errors, ".endpoint"); + const envoy_config_endpoint_v3_Endpoint* endpoint = + envoy_config_endpoint_v3_LbEndpoint_endpoint(lb_endpoint); + if (endpoint == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + ValidationErrors::ScopedField field2(errors, ".address"); + const envoy_config_core_v3_Address* address = + envoy_config_endpoint_v3_Endpoint_address(endpoint); + if (address == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + ValidationErrors::ScopedField field3(errors, ".socket_address"); + const envoy_config_core_v3_SocketAddress* socket_address = + envoy_config_core_v3_Address_socket_address(address); + if (socket_address == nullptr) { + errors->AddError("field not present"); + return absl::nullopt; + } + std::string address_str = UpbStringToStdString( + envoy_config_core_v3_SocketAddress_address(socket_address)); + uint32_t port; + { + ValidationErrors::ScopedField field(errors, ".port_value"); + port = envoy_config_core_v3_SocketAddress_port_value(socket_address); + if (GPR_UNLIKELY(port >> 16) != 0) { + errors->AddError("invalid port"); + return absl::nullopt; + } + } + auto addr = StringToSockaddr(address_str, port); + if (!addr.ok()) { + errors->AddError(addr.status().message()); + } else { + grpc_address = *addr; + } + } + // Convert to ServerAddress. std::map> attributes; attributes[ServerAddressWeightAttribute::kServerAddressWeightAttributeKey] = absl::make_unique(weight); - return ServerAddress(*addr, ChannelArgs(), std::move(attributes)); + return ServerAddress(grpc_address, ChannelArgs(), std::move(attributes)); } struct ParsedLocality { @@ -188,156 +221,174 @@ struct ParsedLocality { XdsEndpointResource::Priority::Locality locality; }; -absl::StatusOr LocalityParse( - const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints) { +absl::optional LocalityParse( + const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints, + ValidationErrors* errors) { + const size_t original_error_size = errors->size(); ParsedLocality parsed_locality; - // Parse LB weight. + // load_balancing_weight + // If LB weight is not specified or 0, it means this locality is assigned + // no load. const google_protobuf_UInt32Value* lb_weight = envoy_config_endpoint_v3_LocalityLbEndpoints_load_balancing_weight( locality_lb_endpoints); - // If LB weight is not specified, it means this locality is assigned no load. parsed_locality.locality.lb_weight = lb_weight != nullptr ? google_protobuf_UInt32Value_value(lb_weight) : 0; - if (parsed_locality.locality.lb_weight == 0) return parsed_locality; - // Parse locality name. + if (parsed_locality.locality.lb_weight == 0) return absl::nullopt; + // locality const envoy_config_core_v3_Locality* locality = envoy_config_endpoint_v3_LocalityLbEndpoints_locality( locality_lb_endpoints); if (locality == nullptr) { - return absl::InvalidArgumentError("Empty locality."); + ValidationErrors::ScopedField field(errors, ".locality"); + errors->AddError("field not present"); + return absl::nullopt; } + // region std::string region = UpbStringToStdString(envoy_config_core_v3_Locality_region(locality)); + // zone std::string zone = UpbStringToStdString(envoy_config_core_v3_Locality_zone(locality)); + // sub_zone std::string sub_zone = UpbStringToStdString(envoy_config_core_v3_Locality_sub_zone(locality)); parsed_locality.locality.name = MakeRefCounted( std::move(region), std::move(zone), std::move(sub_zone)); - // Parse the addresses. + // lb_endpoints size_t size; const envoy_config_endpoint_v3_LbEndpoint* const* lb_endpoints = envoy_config_endpoint_v3_LocalityLbEndpoints_lb_endpoints( locality_lb_endpoints, &size); for (size_t i = 0; i < size; ++i) { - auto address = ServerAddressParse(lb_endpoints[i]); - if (!address.ok()) return address.status(); - if (address->has_value()) { - parsed_locality.locality.endpoints.push_back(std::move(**address)); + ValidationErrors::ScopedField field(errors, + absl::StrCat(".lb_endpoints[", i, "]")); + auto address = ServerAddressParse(lb_endpoints[i], errors); + if (address.has_value()) { + parsed_locality.locality.endpoints.push_back(std::move(*address)); } } - // Parse the priority. + // priority parsed_locality.priority = envoy_config_endpoint_v3_LocalityLbEndpoints_priority( locality_lb_endpoints); + // Return result. + if (original_error_size != errors->size()) return absl::nullopt; return parsed_locality; } -absl::Status DropParseAndAppend( +void DropParseAndAppend( const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* drop_overload, - XdsEndpointResource::DropConfig* drop_config) { - // Get the category. + XdsEndpointResource::DropConfig* drop_config, ValidationErrors* errors) { + // category std::string category = UpbStringToStdString( envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_category( drop_overload)); if (category.empty()) { - return absl::InvalidArgumentError("Empty drop category name"); + ValidationErrors::ScopedField field(errors, ".category"); + errors->AddError("empty drop category name"); } - // Get the drop rate (per million). - const envoy_type_v3_FractionalPercent* drop_percentage = - envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage( - drop_overload); - uint32_t numerator = - envoy_type_v3_FractionalPercent_numerator(drop_percentage); - const auto denominator = - static_cast( - envoy_type_v3_FractionalPercent_denominator(drop_percentage)); - // Normalize to million. - switch (denominator) { - case envoy_type_v3_FractionalPercent_HUNDRED: - numerator *= 10000; - break; - case envoy_type_v3_FractionalPercent_TEN_THOUSAND: - numerator *= 100; - break; - case envoy_type_v3_FractionalPercent_MILLION: - break; - default: - return absl::InvalidArgumentError( - "drop config: unknown denominator type"); + // drop_percentage + uint32_t numerator; + { + ValidationErrors::ScopedField field(errors, ".drop_percentage"); + const envoy_type_v3_FractionalPercent* drop_percentage = + envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload_drop_percentage( + drop_overload); + if (drop_percentage == nullptr) { + errors->AddError("field not present"); + return; + } + numerator = envoy_type_v3_FractionalPercent_numerator(drop_percentage); + { + ValidationErrors::ScopedField field(errors, ".denominator"); + const int denominator = + envoy_type_v3_FractionalPercent_denominator(drop_percentage); + // Normalize to million. + switch (denominator) { + case envoy_type_v3_FractionalPercent_HUNDRED: + numerator *= 10000; + break; + case envoy_type_v3_FractionalPercent_TEN_THOUSAND: + numerator *= 100; + break; + case envoy_type_v3_FractionalPercent_MILLION: + break; + default: + errors->AddError("unknown denominator type"); + } + } + // Cap numerator to 1000000. + numerator = std::min(numerator, 1000000u); } - // Cap numerator to 1000000. - numerator = std::min(numerator, 1000000u); + // Add category. drop_config->AddCategory(std::move(category), numerator); - return absl::OkStatus(); } absl::StatusOr EdsResourceParse( const XdsResourceType::DecodeContext& /*context*/, const envoy_config_endpoint_v3_ClusterLoadAssignment* - cluster_load_assignment, - bool /*is_v2*/) { + cluster_load_assignment) { + ValidationErrors errors; XdsEndpointResource eds_resource; - std::vector errors; - // Get the endpoints. - size_t locality_size; - const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints = - envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints( - cluster_load_assignment, &locality_size); - for (size_t j = 0; j < locality_size; ++j) { - auto parsed_locality = LocalityParse(endpoints[j]); - if (!parsed_locality.ok()) { - errors.emplace_back(parsed_locality.status().message()); - continue; - } - // Filter out locality with weight 0. - if (parsed_locality->locality.lb_weight == 0) continue; - // Make sure prorities is big enough. Note that they might not - // arrive in priority order. - if (eds_resource.priorities.size() < parsed_locality->priority + 1) { - eds_resource.priorities.resize(parsed_locality->priority + 1); + // endpoints + { + ValidationErrors::ScopedField field(&errors, "endpoints"); + size_t locality_size; + const envoy_config_endpoint_v3_LocalityLbEndpoints* const* endpoints = + envoy_config_endpoint_v3_ClusterLoadAssignment_endpoints( + cluster_load_assignment, &locality_size); + for (size_t i = 0; i < locality_size; ++i) { + ValidationErrors::ScopedField field(&errors, absl::StrCat("[", i, "]")); + auto parsed_locality = LocalityParse(endpoints[i], &errors); + if (parsed_locality.has_value()) { + GPR_ASSERT(parsed_locality->locality.lb_weight != 0); + // Make sure prorities is big enough. Note that they might not + // arrive in priority order. + if (eds_resource.priorities.size() < parsed_locality->priority + 1) { + eds_resource.priorities.resize(parsed_locality->priority + 1); + } + auto& locality_map = + eds_resource.priorities[parsed_locality->priority].localities; + auto it = locality_map.find(parsed_locality->locality.name.get()); + if (it != locality_map.end()) { + errors.AddError(absl::StrCat( + "duplicate locality ", + parsed_locality->locality.name->AsHumanReadableString(), + " found in priority ", parsed_locality->priority)); + } else { + locality_map.emplace(parsed_locality->locality.name.get(), + std::move(parsed_locality->locality)); + } + } } - auto& locality_map = - eds_resource.priorities[parsed_locality->priority].localities; - auto it = locality_map.find(parsed_locality->locality.name.get()); - if (it != locality_map.end()) { - errors.emplace_back( - absl::StrCat("duplicate locality ", - parsed_locality->locality.name->AsHumanReadableString(), - " found in priority ", parsed_locality->priority)); - } else { - locality_map.emplace(parsed_locality->locality.name.get(), - std::move(parsed_locality->locality)); - } - } - for (const auto& priority : eds_resource.priorities) { - if (priority.localities.empty()) { - errors.emplace_back("sparse priority list"); + for (size_t i = 0; i < eds_resource.priorities.size(); ++i) { + const auto& priority = eds_resource.priorities[i]; + if (priority.localities.empty()) { + errors.AddError(absl::StrCat("priority ", i, " empty")); + } } } - // Get the drop config. + // policy eds_resource.drop_config = MakeRefCounted(); - const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy* policy = - envoy_config_endpoint_v3_ClusterLoadAssignment_policy( - cluster_load_assignment); + const auto* policy = envoy_config_endpoint_v3_ClusterLoadAssignment_policy( + cluster_load_assignment); if (policy != nullptr) { + ValidationErrors::ScopedField field(&errors, "policy"); size_t drop_size; - const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_DropOverload* const* - drop_overload = - envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads( - policy, &drop_size); - for (size_t j = 0; j < drop_size; ++j) { - absl::Status status = - DropParseAndAppend(drop_overload[j], eds_resource.drop_config.get()); - if (!status.ok()) errors.emplace_back(status.message()); + const auto* const* drop_overload = + envoy_config_endpoint_v3_ClusterLoadAssignment_Policy_drop_overloads( + policy, &drop_size); + for (size_t i = 0; i < drop_size; ++i) { + ValidationErrors::ScopedField field( + &errors, absl::StrCat(".drop_overloads[", i, "]")); + DropParseAndAppend(drop_overload[i], eds_resource.drop_config.get(), + &errors); } } // Return result. - if (!errors.empty()) { - return absl::InvalidArgumentError(absl::StrCat( - "errors parsing EDS resource: [", absl::StrJoin(errors, "; "), "]")); - } + if (!errors.ok()) return errors.status("errors parsing EDS resource"); return eds_resource; } @@ -345,7 +396,7 @@ absl::StatusOr EdsResourceParse( XdsResourceType::DecodeResult XdsEndpointResourceType::Decode( const XdsResourceType::DecodeContext& context, - absl::string_view serialized_resource, bool is_v2) const { + absl::string_view serialized_resource, bool /*is_v2*/) const { DecodeResult result; // Parse serialized proto. auto* resource = envoy_config_endpoint_v3_ClusterLoadAssignment_parse( @@ -359,7 +410,7 @@ XdsResourceType::DecodeResult XdsEndpointResourceType::Decode( // Validate resource. result.name = UpbStringToStdString( envoy_config_endpoint_v3_ClusterLoadAssignment_cluster_name(resource)); - auto eds_resource = EdsResourceParse(context, resource, is_v2); + auto eds_resource = EdsResourceParse(context, resource); if (!eds_resource.ok()) { if (GRPC_TRACE_FLAG_ENABLED(*context.tracer)) { gpr_log(GPR_ERROR, "[xds_client %p] invalid ClusterLoadAssignment %s: %s", diff --git a/test/core/xds/BUILD b/test/core/xds/BUILD index e77e713d6a2..302daea1e81 100644 --- a/test/core/xds/BUILD +++ b/test/core/xds/BUILD @@ -154,3 +154,19 @@ grpc_cc_test( "//test/core/util:grpc_test_util", ], ) + +grpc_cc_test( + name = "xds_endpoint_resource_type_test", + srcs = ["xds_endpoint_resource_type_test.cc"], + external_deps = ["gtest"], + language = "C++", + uses_event_engine = False, + uses_polling = False, + deps = [ + "//:gpr", + "//:grpc", + "//:grpc_xds_client", + "//src/proto/grpc/testing/xds/v3:endpoint_proto", + "//test/core/util:grpc_test_util", + ], +) diff --git a/test/core/xds/xds_endpoint_resource_type_test.cc b/test/core/xds/xds_endpoint_resource_type_test.cc new file mode 100644 index 00000000000..86412012a58 --- /dev/null +++ b/test/core/xds/xds_endpoint_resource_type_test.cc @@ -0,0 +1,797 @@ +// +// Copyright 2022 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 "src/core/ext/xds/xds_bootstrap_grpc.h" +#include "src/core/ext/xds/xds_client.h" +#include "src/core/ext/xds/xds_endpoint.h" +#include "src/core/lib/address_utils/sockaddr_utils.h" +#include "src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.h" +#include "test/core/util/test_config.h" + +using envoy::config::endpoint::v3::ClusterLoadAssignment; + +namespace grpc_core { +namespace testing { +namespace { + +TraceFlag xds_endpoint_resource_type_test_trace( + true, "xds_endpoint_resource_type_test"); + +class XdsEndpointTest : public ::testing::Test { + protected: + XdsEndpointTest() + : xds_client_(MakeXdsClient()), + decode_context_{xds_client_.get(), xds_client_->bootstrap().server(), + &xds_endpoint_resource_type_test_trace, + upb_def_pool_.ptr(), upb_arena_.ptr()} {} + + static RefCountedPtr MakeXdsClient() { + grpc_error_handle error = GRPC_ERROR_NONE; + auto bootstrap = GrpcXdsBootstrap::Create( + "{\n" + " \"xds_servers\": [\n" + " {\n" + " \"server_uri\": \"xds.example.com\",\n" + " \"channel_creds\": [\n" + " {\"type\": \"google_default\"}\n" + " ]\n" + " }\n" + " ]\n" + "}"); + if (!bootstrap.ok()) { + gpr_log(GPR_ERROR, "Error parsing bootstrap: %s", + bootstrap.status().ToString().c_str()); + GPR_ASSERT(false); + } + return MakeRefCounted(std::move(*bootstrap), + /*transport_factory=*/nullptr); + } + + RefCountedPtr xds_client_; + upb::DefPool upb_def_pool_; + upb::Arena upb_arena_; + XdsResourceType::DecodeContext decode_context_; +}; + +TEST_F(XdsEndpointTest, Definition) { + auto* resource_type = XdsEndpointResourceType::Get(); + ASSERT_NE(resource_type, nullptr); + EXPECT_EQ(resource_type->type_url(), + "envoy.config.endpoint.v3.ClusterLoadAssignment"); + EXPECT_EQ(resource_type->v2_type_url(), "envoy.api.v2.ClusterLoadAssignment"); + EXPECT_FALSE(resource_type->AllResourcesRequiredInSotW()); +} + +TEST_F(XdsEndpointTest, UnparseableProto) { + std::string serialized_resource("\0", 1); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "Can't parse ClusterLoadAssignment resource.") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, MinimumValidConfig) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast( + decode_result.resource->get()) + ->resource; + ASSERT_EQ(resource.priorities.size(), 1); + const auto& priority = resource.priorities[0]; + ASSERT_EQ(priority.localities.size(), 1); + const auto& p = *priority.localities.begin(); + ASSERT_EQ(p.first, p.second.name.get()); + EXPECT_EQ(p.first->region(), "myregion"); + EXPECT_EQ(p.first->zone(), "myzone"); + EXPECT_EQ(p.first->sub_zone(), "mysubzone"); + EXPECT_EQ(p.second.lb_weight, 1); + ASSERT_EQ(p.second.endpoints.size(), 1); + const auto& address = p.second.endpoints.front(); + auto addr = grpc_sockaddr_to_string(&address.address(), /*normalize=*/false); + ASSERT_TRUE(addr.ok()) << addr.status(); + EXPECT_EQ(*addr, "127.0.0.1:443"); + EXPECT_EQ(address.args(), ChannelArgs()); + const auto* attribute = + static_cast(address.GetAttribute( + ServerAddressWeightAttribute::kServerAddressWeightAttributeKey)); + ASSERT_NE(attribute, nullptr); + EXPECT_EQ(attribute->weight(), 1); + ASSERT_NE(resource.drop_config, nullptr); + EXPECT_TRUE(resource.drop_config->drop_category_list().empty()); +} + +TEST_F(XdsEndpointTest, EndpointWeight) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* endpoint = locality->add_lb_endpoints(); + endpoint->mutable_load_balancing_weight()->set_value(3); + auto* socket_address = + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast( + decode_result.resource->get()) + ->resource; + ASSERT_EQ(resource.priorities.size(), 1); + const auto& priority = resource.priorities[0]; + ASSERT_EQ(priority.localities.size(), 1); + const auto& p = *priority.localities.begin(); + ASSERT_EQ(p.first, p.second.name.get()); + EXPECT_EQ(p.first->region(), "myregion"); + EXPECT_EQ(p.first->zone(), "myzone"); + EXPECT_EQ(p.first->sub_zone(), "mysubzone"); + EXPECT_EQ(p.second.lb_weight, 1); + ASSERT_EQ(p.second.endpoints.size(), 1); + const auto& address = p.second.endpoints.front(); + auto addr = grpc_sockaddr_to_string(&address.address(), /*normalize=*/false); + ASSERT_TRUE(addr.ok()) << addr.status(); + EXPECT_EQ(*addr, "127.0.0.1:443"); + EXPECT_EQ(address.args(), ChannelArgs()); + const auto* attribute = + static_cast(address.GetAttribute( + ServerAddressWeightAttribute::kServerAddressWeightAttributeKey)); + ASSERT_NE(attribute, nullptr); + EXPECT_EQ(attribute->weight(), 3); + ASSERT_NE(resource.drop_config, nullptr); + EXPECT_TRUE(resource.drop_config->drop_category_list().empty()); +} + +TEST_F(XdsEndpointTest, IgnoresLocalityWithNoWeight) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + locality = cla.add_endpoints(); + *locality = cla.endpoints(0); + locality->mutable_load_balancing_weight()->set_value(1); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast( + decode_result.resource->get()) + ->resource; + ASSERT_EQ(resource.priorities.size(), 1); + const auto& priority = resource.priorities[0]; + ASSERT_EQ(priority.localities.size(), 1); + const auto& p = *priority.localities.begin(); + ASSERT_EQ(p.first, p.second.name.get()); + EXPECT_EQ(p.first->region(), "myregion"); + EXPECT_EQ(p.first->zone(), "myzone"); + EXPECT_EQ(p.first->sub_zone(), "mysubzone"); + EXPECT_EQ(p.second.lb_weight, 1); + ASSERT_EQ(p.second.endpoints.size(), 1); + const auto& address = p.second.endpoints.front(); + auto addr = grpc_sockaddr_to_string(&address.address(), /*normalize=*/false); + ASSERT_TRUE(addr.ok()) << addr.status(); + EXPECT_EQ(*addr, "127.0.0.1:443"); + EXPECT_EQ(address.args(), ChannelArgs()); + const auto* attribute = + static_cast(address.GetAttribute( + ServerAddressWeightAttribute::kServerAddressWeightAttributeKey)); + ASSERT_NE(attribute, nullptr); + EXPECT_EQ(attribute->weight(), 1); + ASSERT_NE(resource.drop_config, nullptr); + EXPECT_TRUE(resource.drop_config->drop_category_list().empty()); +} + +TEST_F(XdsEndpointTest, IgnoresLocalityWithZeroWeight) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(0); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + locality = cla.add_endpoints(); + *locality = cla.endpoints(0); + locality->mutable_load_balancing_weight()->set_value(1); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast( + decode_result.resource->get()) + ->resource; + ASSERT_EQ(resource.priorities.size(), 1); + const auto& priority = resource.priorities[0]; + ASSERT_EQ(priority.localities.size(), 1); + const auto& p = *priority.localities.begin(); + ASSERT_EQ(p.first, p.second.name.get()); + EXPECT_EQ(p.first->region(), "myregion"); + EXPECT_EQ(p.first->zone(), "myzone"); + EXPECT_EQ(p.first->sub_zone(), "mysubzone"); + EXPECT_EQ(p.second.lb_weight, 1); + ASSERT_EQ(p.second.endpoints.size(), 1); + const auto& address = p.second.endpoints.front(); + auto addr = grpc_sockaddr_to_string(&address.address(), /*normalize=*/false); + ASSERT_TRUE(addr.ok()) << addr.status(); + EXPECT_EQ(*addr, "127.0.0.1:443"); + EXPECT_EQ(address.args(), ChannelArgs()); + const auto* attribute = + static_cast(address.GetAttribute( + ServerAddressWeightAttribute::kServerAddressWeightAttributeKey)); + ASSERT_NE(attribute, nullptr); + EXPECT_EQ(attribute->weight(), 1); + ASSERT_NE(resource.drop_config, nullptr); + EXPECT_TRUE(resource.drop_config->drop_category_list().empty()); +} + +TEST_F(XdsEndpointTest, LocalityWithNoEndpoints) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast( + decode_result.resource->get()) + ->resource; + ASSERT_EQ(resource.priorities.size(), 1); + const auto& priority = resource.priorities[0]; + ASSERT_EQ(priority.localities.size(), 1); + const auto& p = *priority.localities.begin(); + ASSERT_EQ(p.first, p.second.name.get()); + EXPECT_EQ(p.first->region(), "myregion"); + EXPECT_EQ(p.first->zone(), "myzone"); + EXPECT_EQ(p.first->sub_zone(), "mysubzone"); + EXPECT_EQ(p.second.lb_weight, 1); + EXPECT_EQ(p.second.endpoints.size(), 0); + ASSERT_NE(resource.drop_config, nullptr); + EXPECT_TRUE(resource.drop_config->drop_category_list().empty()); +} + +TEST_F(XdsEndpointTest, NoLocality) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[0].locality error:field not present]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, InvalidPort) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(65537); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[0].lb_endpoints[0].endpoint.address" + ".socket_address.port_value error:invalid port]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, InvalidAddress) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("not_an_ip_address"); + socket_address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[0].lb_endpoints[0].endpoint.address" + ".socket_address error:" + "Failed to parse address:not_an_ip_address:443]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, MissingSocketAddress) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + locality->add_lb_endpoints()->mutable_endpoint()->mutable_address(); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[0].lb_endpoints[0].endpoint.address" + ".socket_address error:field not present]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, MissingAddress) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + locality->add_lb_endpoints()->mutable_endpoint(); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[0].lb_endpoints[0].endpoint.address " + "error:field not present]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, MissingEndpoint) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + locality->add_lb_endpoints(); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[0].lb_endpoints[0].endpoint " + "error:field not present]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, EndpointWeightZero) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* endpoint = locality->add_lb_endpoints(); + endpoint->mutable_load_balancing_weight()->set_value(0); + auto* socket_address = + endpoint->mutable_endpoint()->mutable_address()->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[0].lb_endpoints[0].load_balancing_weight " + "error:must be greater than 0]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, DuplicateLocalityName) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + locality = cla.add_endpoints(); + *locality = cla.endpoints(0); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints[1] error:duplicate locality {region=\"myregion\", " + "zone=\"myzone\", sub_zone=\"mysubzone\"} found in priority 0]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, SparsePriorityList) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + locality->set_priority(1); + locality = cla.add_endpoints(); + *locality = cla.endpoints(0); + locality->set_priority(3); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:endpoints errors:[priority 0 empty; priority 2 empty]]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, DropConfig) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + auto* drop_overload = cla.mutable_policy()->add_drop_overloads(); + drop_overload->set_category("lb_drop"); + drop_overload->mutable_drop_percentage()->set_numerator(50); + drop_overload = cla.mutable_policy()->add_drop_overloads(); + drop_overload->set_category("lb_overload"); + drop_overload->mutable_drop_percentage()->set_numerator(2500); + drop_overload->mutable_drop_percentage()->set_denominator( + envoy::type::v3::FractionalPercent::TEN_THOUSAND); + drop_overload = cla.mutable_policy()->add_drop_overloads(); + drop_overload->set_category("why_not"); + drop_overload->mutable_drop_percentage()->set_numerator(750000); + drop_overload->mutable_drop_percentage()->set_denominator( + envoy::type::v3::FractionalPercent::MILLION); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast( + decode_result.resource->get()) + ->resource; + ASSERT_NE(resource.drop_config, nullptr); + const auto& drop_list = resource.drop_config->drop_category_list(); + ASSERT_EQ(drop_list.size(), 3); + EXPECT_EQ(drop_list[0].name, "lb_drop"); + EXPECT_EQ(drop_list[0].parts_per_million, 500000); + EXPECT_EQ(drop_list[1].name, "lb_overload"); + EXPECT_EQ(drop_list[1].parts_per_million, 250000); + EXPECT_EQ(drop_list[2].name, "why_not"); + EXPECT_EQ(drop_list[2].parts_per_million, 750000); +} + +TEST_F(XdsEndpointTest, CapsDropPercentageAt100) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + auto* drop_overload = cla.mutable_policy()->add_drop_overloads(); + drop_overload->set_category("lb_drop"); + drop_overload->mutable_drop_percentage()->set_numerator(10000001); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.resource.ok()) << decode_result.resource.status(); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + auto& resource = static_cast( + decode_result.resource->get()) + ->resource; + ASSERT_NE(resource.drop_config, nullptr); + const auto& drop_list = resource.drop_config->drop_category_list(); + ASSERT_EQ(drop_list.size(), 1); + EXPECT_EQ(drop_list[0].name, "lb_drop"); + EXPECT_EQ(drop_list[0].parts_per_million, 1000000); + EXPECT_TRUE(resource.drop_config->drop_all()); +} + +TEST_F(XdsEndpointTest, MissingDropCategoryName) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + auto* drop_overload = cla.mutable_policy()->add_drop_overloads(); + drop_overload->mutable_drop_percentage()->set_numerator(50); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:policy.drop_overloads[0].category " + "error:empty drop category name]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, MissingDropPercentage) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + auto* drop_overload = cla.mutable_policy()->add_drop_overloads(); + drop_overload->set_category("lb_drop"); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:policy.drop_overloads[0].drop_percentage " + "error:field not present]") + << decode_result.resource.status(); +} + +TEST_F(XdsEndpointTest, DropPercentageInvalidDenominator) { + ClusterLoadAssignment cla; + cla.set_cluster_name("foo"); + auto* locality = cla.add_endpoints(); + locality->mutable_load_balancing_weight()->set_value(1); + auto* locality_name = locality->mutable_locality(); + locality_name->set_region("myregion"); + locality_name->set_zone("myzone"); + locality_name->set_sub_zone("mysubzone"); + auto* socket_address = locality->add_lb_endpoints() + ->mutable_endpoint() + ->mutable_address() + ->mutable_socket_address(); + socket_address->set_address("127.0.0.1"); + socket_address->set_port_value(443); + auto* drop_overload = cla.mutable_policy()->add_drop_overloads(); + drop_overload->set_category("lb_drop"); + drop_overload->mutable_drop_percentage()->set_numerator(750000); + drop_overload->mutable_drop_percentage()->set_denominator( + static_cast(100)); + std::string serialized_resource; + ASSERT_TRUE(cla.SerializeToString(&serialized_resource)); + auto* resource_type = XdsEndpointResourceType::Get(); + auto decode_result = resource_type->Decode( + decode_context_, serialized_resource, /*is_v2=*/false); + ASSERT_TRUE(decode_result.name.has_value()); + EXPECT_EQ(*decode_result.name, "foo"); + EXPECT_EQ(decode_result.resource.status().code(), + absl::StatusCode::kInvalidArgument); + EXPECT_EQ(decode_result.resource.status().message(), + "errors parsing EDS resource: [" + "field:policy.drop_overloads[0].drop_percentage.denominator " + "error:unknown denominator type]") + << decode_result.resource.status(); +} + +} // namespace +} // namespace testing +} // namespace grpc_core + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + grpc::testing::TestEnvironment env(&argc, argv); + grpc_init(); + int ret = RUN_ALL_TESTS(); + grpc_shutdown(); + return ret; +} diff --git a/test/cpp/end2end/xds/xds_cluster_end2end_test.cc b/test/cpp/end2end/xds/xds_cluster_end2end_test.cc index cef45769fb0..7458f430c3c 100644 --- a/test/cpp/end2end/xds/xds_cluster_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_cluster_end2end_test.cc @@ -618,48 +618,21 @@ TEST_P(EdsTest, IgnoresDuplicateUpdates) { } } -// Tests that EDS client should send a NACK if the EDS update contains -// sparse priorities. -TEST_P(EdsTest, NacksSparsePriorityList) { +// Testing just one example of an invalid resource here. +// Unit tests for XdsEndpointResourceType have exhaustive tests for all +// of the invalid cases. +TEST_P(EdsTest, NacksInvalidResource) { EdsResourceArgs args({ {"locality0", {MakeNonExistantEndpoint()}, kDefaultLocalityWeight, 1}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); const auto response_state = WaitForEdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("sparse priority list")); -} - -// Tests that EDS client should send a NACK if the EDS update contains -// multiple instances of the same locality in the same priority. -TEST_P(EdsTest, NacksDuplicateLocalityInSamePriority) { - EdsResourceArgs args({ - {"locality0", {MakeNonExistantEndpoint()}, kDefaultLocalityWeight, 0}, - {"locality0", {MakeNonExistantEndpoint()}, kDefaultLocalityWeight, 0}, - }); - balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); - const auto response_state = WaitForEdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr( - "duplicate locality {region=\"xds_default_locality_region\", " - "zone=\"xds_default_locality_zone\", sub_zone=\"locality0\"} " - "found in priority 0")); -} - -TEST_P(EdsTest, NacksEndpointWeightZero) { - EdsResourceArgs args({{"locality0", {MakeNonExistantEndpoint()}}}); - auto eds_resource = BuildEdsResource(args); - eds_resource.mutable_endpoints(0) - ->mutable_lb_endpoints(0) - ->mutable_load_balancing_weight() - ->set_value(0); - balancer_->ads_service()->SetEdsResource(eds_resource); - const auto response_state = WaitForEdsNack(DEBUG_LOCATION); - ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; - EXPECT_THAT(response_state->error_message, - ::testing::HasSubstr("Invalid endpoint weight of 0.")); + EXPECT_EQ(response_state->error_message, + "xDS response validation errors: [" + "resource index 0: eds_service_name: " + "INVALID_ARGUMENT: errors parsing EDS resource: [" + "field:endpoints error:priority 0 empty]]"); } // Tests that if the balancer is down, the RPCs will still be sent to the diff --git a/test/cpp/end2end/xds/xds_csds_end2end_test.cc b/test/cpp/end2end/xds/xds_csds_end2end_test.cc index 63aeed36e0a..066ba970cba 100644 --- a/test/cpp/end2end/xds/xds_csds_end2end_test.cc +++ b/test/cpp/end2end/xds/xds_csds_end2end_test.cc @@ -548,8 +548,11 @@ TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpEndpointError) { kDefaultEdsServiceName, backends_[0]->port(), kDefaultLocalityWeight)), ClientResourceStatus::NACKED, - EqUpdateFailureState(::testing::HasSubstr("Empty locality"), - "2")))); + EqUpdateFailureState( + ::testing::HasSubstr( + "errors parsing EDS resource: [" + "field:endpoints[0].locality error:field not present]"), + "2")))); if (ok) return; // TEST PASSED! gpr_sleep_until( grpc_timeout_milliseconds_to_deadline(kFetchIntervalMilliseconds)); diff --git a/tools/run_tests/generated/tests.json b/tools/run_tests/generated/tests.json index b7e38ea539e..84bad94acd2 100644 --- a/tools/run_tests/generated/tests.json +++ b/tools/run_tests/generated/tests.json @@ -8081,6 +8081,30 @@ ], "uses_polling": true }, + { + "args": [], + "benchmark": false, + "ci_platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "cpu_cost": 1.0, + "exclude_configs": [], + "exclude_iomgrs": [], + "flaky": false, + "gtest": true, + "language": "c++", + "name": "xds_endpoint_resource_type_test", + "platforms": [ + "linux", + "mac", + "posix", + "windows" + ], + "uses_polling": false + }, { "args": [], "benchmark": false,