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
pull/31127/head
Mark D. Roth 2 years ago committed by GitHub
parent 1aac7caba9
commit cdb7d2c93c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      BUILD
  2. 52
      CMakeLists.txt
  3. 14
      build_autogenerated.yaml
  4. 203
      src/core/ext/xds/xds_endpoint.cc
  5. 16
      test/core/xds/BUILD
  6. 797
      test/core/xds/xds_endpoint_resource_type_test.cc
  7. 45
      test/cpp/end2end/xds/xds_cluster_end2end_test.cc
  8. 5
      test/cpp/end2end/xds/xds_csds_end2end_test.cc
  9. 24
      tools/run_tests/generated/tests.json

@ -4757,6 +4757,7 @@ grpc_cc_library(
"upb_utils",
"uri_parser",
"useful",
"validation_errors",
"work_serializer",
"xds_client",
"xds_type_upb",

52
CMakeLists.txt generated

@ -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)

@ -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

@ -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<absl::optional<ServerAddress>> ServerAddressParse(
const envoy_config_endpoint_v3_LbEndpoint* lb_endpoint) {
// If health_status is not HEALTHY or UNKNOWN, skip this endpoint.
absl::optional<ServerAddress> 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.
// load_balancing_weight
uint32_t weight = 1;
{
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");
}
}
}
// 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 = envoy_config_core_v3_SocketAddress_port_value(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) {
return absl::InvalidArgumentError("Invalid port.");
}
// Find load_balancing_weight for the endpoint.
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.");
errors->AddError("invalid port");
return absl::nullopt;
}
}
// Populate grpc_resolved_address.
auto addr = StringToSockaddr(address_str, port);
if (!addr.ok()) return addr.status();
// Append the address to the list.
if (!addr.ok()) {
errors->AddError(addr.status().message());
} else {
grpc_address = *addr;
}
}
// Convert to ServerAddress.
std::map<const char*, std::unique_ptr<ServerAddress::AttributeInterface>>
attributes;
attributes[ServerAddressWeightAttribute::kServerAddressWeightAttributeKey] =
absl::make_unique<ServerAddressWeightAttribute>(weight);
return ServerAddress(*addr, ChannelArgs(), std::move(attributes));
return ServerAddress(grpc_address, ChannelArgs(), std::move(attributes));
}
struct ParsedLocality {
@ -188,71 +221,90 @@ struct ParsedLocality {
XdsEndpointResource::Priority::Locality locality;
};
absl::StatusOr<ParsedLocality> LocalityParse(
const envoy_config_endpoint_v3_LocalityLbEndpoints* locality_lb_endpoints) {
absl::optional<ParsedLocality> 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<XdsLocalityName>(
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).
// 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);
uint32_t numerator =
envoy_type_v3_FractionalPercent_numerator(drop_percentage);
const auto denominator =
static_cast<envoy_type_v3_FractionalPercent_DenominatorType>(
envoy_type_v3_FractionalPercent_denominator(drop_percentage));
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:
@ -264,35 +316,34 @@ absl::Status DropParseAndAppend(
case envoy_type_v3_FractionalPercent_MILLION:
break;
default:
return absl::InvalidArgumentError(
"drop config: unknown denominator type");
errors->AddError("unknown denominator type");
}
}
// Cap numerator to 1000000.
numerator = std::min(numerator, 1000000u);
}
// Add category.
drop_config->AddCategory(std::move(category), numerator);
return absl::OkStatus();
}
absl::StatusOr<XdsEndpointResource> 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<std::string> errors;
// Get the endpoints.
// 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 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;
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) {
@ -302,8 +353,8 @@ absl::StatusOr<XdsEndpointResource> EdsResourceParse(
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 ",
errors.AddError(absl::StrCat(
"duplicate locality ",
parsed_locality->locality.name->AsHumanReadableString(),
" found in priority ", parsed_locality->priority));
} else {
@ -311,33 +362,33 @@ absl::StatusOr<XdsEndpointResource> EdsResourceParse(
std::move(parsed_locality->locality));
}
}
for (const auto& priority : eds_resource.priorities) {
}
for (size_t i = 0; i < eds_resource.priorities.size(); ++i) {
const auto& priority = eds_resource.priorities[i];
if (priority.localities.empty()) {
errors.emplace_back("sparse priority list");
errors.AddError(absl::StrCat("priority ", i, " empty"));
}
}
// Get the drop config.
}
// policy
eds_resource.drop_config = MakeRefCounted<XdsEndpointResource::DropConfig>();
const envoy_config_endpoint_v3_ClusterLoadAssignment_Policy* policy =
envoy_config_endpoint_v3_ClusterLoadAssignment_policy(
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 =
const auto* 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());
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<XdsEndpointResource> 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",

@ -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",
],
)

@ -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 <gmock/gmock.h>
#include <gtest/gtest.h>
#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<XdsClient> 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<XdsClient>(std::move(*bootstrap),
/*transport_factory=*/nullptr);
}
RefCountedPtr<XdsClient> 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<XdsEndpointResourceType::ResourceDataSubclass*>(
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<const ServerAddressWeightAttribute*>(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<XdsEndpointResourceType::ResourceDataSubclass*>(
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<const ServerAddressWeightAttribute*>(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<XdsEndpointResourceType::ResourceDataSubclass*>(
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<const ServerAddressWeightAttribute*>(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<XdsEndpointResourceType::ResourceDataSubclass*>(
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<const ServerAddressWeightAttribute*>(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<XdsEndpointResourceType::ResourceDataSubclass*>(
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<XdsEndpointResourceType::ResourceDataSubclass*>(
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<XdsEndpointResourceType::ResourceDataSubclass*>(
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<envoy::type::v3::FractionalPercent_DenominatorType>(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;
}

@ -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

@ -548,7 +548,10 @@ TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpEndpointError) {
kDefaultEdsServiceName, backends_[0]->port(),
kDefaultLocalityWeight)),
ClientResourceStatus::NACKED,
EqUpdateFailureState(::testing::HasSubstr("Empty locality"),
EqUpdateFailureState(
::testing::HasSubstr(
"errors parsing EDS resource: ["
"field:endpoints[0].locality error:field not present]"),
"2"))));
if (ok) return; // TEST PASSED!
gpr_sleep_until(

@ -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,

Loading…
Cancel
Save