Second attempt: xDS RouteConfig: use ValidationErrors and add unit test (#31658)

* Revert "Revert "xDS RouteConfig: use ValidationErrors and add unit test (#31418)" (#31653)"

This reverts commit 335b74fea7.

* use auto instead of explicitly naming the protobuf map type
pull/31668/head
Mark D. Roth 2 years ago committed by GitHub
parent c45e4f003d
commit df80a31199
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 117
      CMakeLists.txt
  2. 42
      build_autogenerated.yaml
  3. 37
      src/core/ext/filters/client_channel/resolver/xds/xds_resolver.cc
  4. 78
      src/core/ext/xds/xds_cluster_specifier_plugin.cc
  5. 23
      src/core/ext/xds/xds_cluster_specifier_plugin.h
  6. 7
      src/core/ext/xds/xds_common_types.cc
  7. 23
      src/core/ext/xds/xds_listener.cc
  8. 971
      src/core/ext/xds/xds_route_config.cc
  9. 41
      src/core/ext/xds/xds_route_config.h
  10. 18
      src/core/lib/channel/status_util.cc
  11. 9
      src/core/lib/channel/status_util.h
  12. 4
      src/core/lib/matchers/matchers.cc
  13. 2
      src/proto/grpc/testing/xds/v3/route.proto
  14. 8
      test/core/security/matchers_test.cc
  15. 22
      test/core/xds/BUILD
  16. 31
      test/core/xds/xds_listener_resource_type_test.cc
  17. 2166
      test/core/xds/xds_route_config_resource_type_test.cc
  18. 11
      test/cpp/end2end/xds/xds_csds_end2end_test.cc
  19. 56
      test/cpp/end2end/xds/xds_end2end_test.cc
  20. 218
      test/cpp/end2end/xds/xds_rls_end2end_test.cc
  21. 736
      test/cpp/end2end/xds/xds_routing_end2end_test.cc
  22. 24
      tools/run_tests/generated/tests.json

117
CMakeLists.txt generated

@ -1292,6 +1292,7 @@ if(gRPC_BUILD_TESTS)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx xds_rls_end2end_test)
endif()
add_dependencies(buildtests_cxx xds_route_config_resource_type_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_cxx xds_routing_end2end_test)
endif()
@ -24373,6 +24374,122 @@ if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
endif()
endif()
if(gRPC_BUILD_TESTS)
add_executable(xds_route_config_resource_type_test
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/lookup/v1/rls_config.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/lookup/v1/rls_config.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/lookup/v1/rls_config.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/lookup/v1/rls_config.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/address.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/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/expr.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/expr.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/extension.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/fault_common.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/http_filter_rbac.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/metadata.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/path.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/percent.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/range.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/rbac.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/regex.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/route.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/string.grpc.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.grpc.pb.cc
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.pb.h
${_gRPC_PROTO_GENS_DIR}/src/proto/grpc/testing/xds/v3/typed_struct.grpc.pb.h
test/core/xds/xds_route_config_resource_type_test.cc
test/cpp/util/cli_call.cc
test/cpp/util/cli_credentials.cc
test/cpp/util/proto_file_parser.cc
test/cpp/util/proto_reflection_descriptor_database.cc
test/cpp/util/service_describer.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(xds_route_config_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_route_config_resource_type_test
${_gRPC_BASELIB_LIBRARIES}
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ZLIB_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
absl::flags
grpc++
grpc_test_util
)
endif()
if(gRPC_BUILD_TESTS)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)

@ -13175,6 +13175,48 @@ targets:
- linux
- posix
- mac
- name: xds_route_config_resource_type_test
gtest: true
build: test
language: c++
headers:
- test/core/util/scoped_env_var.h
- test/cpp/util/cli_call.h
- test/cpp/util/cli_credentials.h
- test/cpp/util/config_grpc_cli.h
- test/cpp/util/proto_file_parser.h
- test/cpp/util/proto_reflection_descriptor_database.h
- test/cpp/util/service_describer.h
src:
- src/proto/grpc/lookup/v1/rls_config.proto
- src/proto/grpc/reflection/v1alpha/reflection.proto
- src/proto/grpc/testing/xds/v3/address.proto
- src/proto/grpc/testing/xds/v3/base.proto
- src/proto/grpc/testing/xds/v3/expr.proto
- src/proto/grpc/testing/xds/v3/extension.proto
- src/proto/grpc/testing/xds/v3/fault.proto
- src/proto/grpc/testing/xds/v3/fault_common.proto
- src/proto/grpc/testing/xds/v3/http_filter_rbac.proto
- src/proto/grpc/testing/xds/v3/metadata.proto
- src/proto/grpc/testing/xds/v3/path.proto
- src/proto/grpc/testing/xds/v3/percent.proto
- src/proto/grpc/testing/xds/v3/range.proto
- src/proto/grpc/testing/xds/v3/rbac.proto
- src/proto/grpc/testing/xds/v3/regex.proto
- src/proto/grpc/testing/xds/v3/route.proto
- src/proto/grpc/testing/xds/v3/string.proto
- src/proto/grpc/testing/xds/v3/typed_struct.proto
- test/core/xds/xds_route_config_resource_type_test.cc
- test/cpp/util/cli_call.cc
- test/cpp/util/cli_credentials.cc
- test/cpp/util/proto_file_parser.cc
- test/cpp/util/proto_reflection_descriptor_database.cc
- test/cpp/util/service_describer.cc
deps:
- absl/flags:flag
- grpc++
- grpc_test_util
uses_polling: false
- name: xds_routing_end2end_test
gtest: true
build: test

@ -638,23 +638,21 @@ void XdsResolver::XdsConfigSelector::MaybeAddCluster(const std::string& name) {
}
absl::optional<uint64_t> HeaderHashHelper(
const XdsRouteConfigResource::Route::RouteAction::HashPolicy& policy,
const XdsRouteConfigResource::Route::RouteAction::HashPolicy::Header&
header_policy,
grpc_metadata_batch* initial_metadata) {
GPR_ASSERT(policy.type ==
XdsRouteConfigResource::Route::RouteAction::HashPolicy::HEADER);
std::string value_buffer;
absl::optional<absl::string_view> header_value = XdsRouting::GetHeaderValue(
initial_metadata, policy.header_name, &value_buffer);
if (!header_value.has_value()) {
return absl::nullopt;
}
if (policy.regex != nullptr) {
initial_metadata, header_policy.header_name, &value_buffer);
if (!header_value.has_value()) return absl::nullopt;
if (header_policy.regex != nullptr) {
// If GetHeaderValue() did not already store the value in
// value_buffer, copy it there now, so we can modify it.
if (header_value->data() != value_buffer.data()) {
value_buffer = std::string(*header_value);
}
RE2::GlobalReplace(&value_buffer, *policy.regex, policy.regex_substitution);
RE2::GlobalReplace(&value_buffer, *header_policy.regex,
header_policy.regex_substitution);
header_value = value_buffer;
}
return XXH64(header_value->data(), header_value->size(), 0);
@ -732,17 +730,16 @@ XdsResolver::XdsConfigSelector::GetCallConfig(GetCallConfigArgs args) {
// Generate a hash.
absl::optional<uint64_t> hash;
for (const auto& hash_policy : route_action->hash_policies) {
absl::optional<uint64_t> new_hash;
switch (hash_policy.type) {
case XdsRouteConfigResource::Route::RouteAction::HashPolicy::HEADER:
new_hash = HeaderHashHelper(hash_policy, args.initial_metadata);
break;
case XdsRouteConfigResource::Route::RouteAction::HashPolicy::CHANNEL_ID:
new_hash = resolver_->channel_id();
break;
default:
GPR_ASSERT(0);
}
absl::optional<uint64_t> new_hash = Match(
hash_policy.policy,
[&](const XdsRouteConfigResource::Route::RouteAction::HashPolicy::
Header& header) {
return HeaderHashHelper(header, args.initial_metadata);
},
[&](const XdsRouteConfigResource::Route::RouteAction::HashPolicy::
ChannelId&) -> absl::optional<uint64_t> {
return resolver_->channel_id();
});
if (new_hash.has_value()) {
// Rotating the old value prevents duplicate hash rules from cancelling
// each other out and preserves all of the entropy

@ -20,11 +20,10 @@
#include <stddef.h>
#include <algorithm>
#include <map>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/types/variant.h"
#include "upb/json_encode.h"
@ -33,10 +32,7 @@
#include <grpc/support/log.h>
#include "src/core/lib/config/core_configuration.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/load_balancing/lb_policy_registry.h"
#include "src/proto/grpc/lookup/v1/rls_config.upb.h"
#include "src/proto/grpc/lookup/v1/rls_config.upbdefs.h"
@ -46,34 +42,38 @@ namespace grpc_core {
// XdsRouteLookupClusterSpecifierPlugin
//
const char* kXdsRouteLookupClusterSpecifierPluginConfigName =
"grpc.lookup.v1.RouteLookupClusterSpecifier";
absl::string_view XdsRouteLookupClusterSpecifierPlugin::ConfigProtoName()
const {
return "grpc.lookup.v1.RouteLookupClusterSpecifier";
}
void XdsRouteLookupClusterSpecifierPlugin::PopulateSymtab(
upb_DefPool* symtab) const {
grpc_lookup_v1_RouteLookupConfig_getmsgdef(symtab);
}
absl::StatusOr<std::string>
XdsRouteLookupClusterSpecifierPlugin::GenerateLoadBalancingPolicyConfig(
XdsExtension extension, upb_Arena* arena, upb_DefPool* symtab) const {
Json XdsRouteLookupClusterSpecifierPlugin::GenerateLoadBalancingPolicyConfig(
XdsExtension extension, upb_Arena* arena, upb_DefPool* symtab,
ValidationErrors* errors) const {
absl::string_view* serialized_plugin_config =
absl::get_if<absl::string_view>(&extension.value);
if (serialized_plugin_config == nullptr) {
return absl::InvalidArgumentError("could not parse plugin config");
errors->AddError("could not parse plugin config");
return {};
}
const auto* specifier = grpc_lookup_v1_RouteLookupClusterSpecifier_parse(
serialized_plugin_config->data(), serialized_plugin_config->size(),
arena);
if (specifier == nullptr) {
return absl::InvalidArgumentError("Could not parse plugin config");
errors->AddError("could not parse plugin config");
return {};
}
const auto* plugin_config =
grpc_lookup_v1_RouteLookupClusterSpecifier_route_lookup_config(specifier);
if (plugin_config == nullptr) {
return absl::InvalidArgumentError(
"Could not get route lookup config from route lookup cluster "
"specifier");
ValidationErrors::ScopedField field(errors, ".route_lookup_config");
errors->AddError("field not present");
return {};
}
upb::Status status;
const upb_MessageDef* msg_type =
@ -81,42 +81,23 @@ XdsRouteLookupClusterSpecifierPlugin::GenerateLoadBalancingPolicyConfig(
size_t json_size = upb_JsonEncode(plugin_config, msg_type, symtab, 0, nullptr,
0, status.ptr());
if (json_size == static_cast<size_t>(-1)) {
return absl::InvalidArgumentError(
absl::StrCat("failed to dump proto to JSON: ",
errors->AddError(absl::StrCat("failed to dump proto to JSON: ",
upb_Status_ErrorMessage(status.ptr())));
return {};
}
void* buf = upb_Arena_Malloc(arena, json_size + 1);
upb_JsonEncode(plugin_config, msg_type, symtab, 0,
reinterpret_cast<char*>(buf), json_size + 1, status.ptr());
Json::Object rls_policy;
auto json = Json::Parse(reinterpret_cast<char*>(buf));
GPR_ASSERT(json.ok());
rls_policy["routeLookupConfig"] = std::move(*json);
Json::Object cds_policy;
cds_policy["cds_experimental"] = Json::Object();
Json::Array child_policy;
child_policy.emplace_back(std::move(cds_policy));
rls_policy["childPolicy"] = std::move(child_policy);
rls_policy["childPolicyConfigTargetFieldName"] = "cluster";
Json::Object policy;
policy["rls_experimental"] = std::move(rls_policy);
Json::Array policies;
policies.emplace_back(std::move(policy));
Json lb_policy_config(std::move(policies));
// TODO(roth): If/when we ever add a second plugin, refactor this code
// somehow such that we automatically validate the resulting config against
// the gRPC LB policy registry instead of requiring each plugin to do that
// itself.
auto config =
CoreConfiguration::Get().lb_policy_registry().ParseLoadBalancingConfig(
lb_policy_config);
if (!config.ok()) {
return absl::InvalidArgumentError(absl::StrCat(
kXdsRouteLookupClusterSpecifierPluginConfigName,
" ClusterSpecifierPlugin returned invalid LB policy config: ",
config.status().message()));
}
return lb_policy_config.Dump();
return Json::Array{Json::Object{
{"rls_experimental",
Json::Object{
{"routeLookupConfig", std::move(*json)},
{"childPolicy",
Json::Array{Json::Object{{"cds_experimental", Json::Object()}}}},
{"childPolicyConfigTargetFieldName", "cluster"},
}}}};
}
//
@ -124,14 +105,13 @@ XdsRouteLookupClusterSpecifierPlugin::GenerateLoadBalancingPolicyConfig(
//
XdsClusterSpecifierPluginRegistry::XdsClusterSpecifierPluginRegistry() {
RegisterPlugin(std::make_unique<XdsRouteLookupClusterSpecifierPlugin>(),
kXdsRouteLookupClusterSpecifierPluginConfigName);
RegisterPlugin(std::make_unique<XdsRouteLookupClusterSpecifierPlugin>());
}
void XdsClusterSpecifierPluginRegistry::RegisterPlugin(
std::unique_ptr<XdsClusterSpecifierPluginImpl> plugin,
absl::string_view config_proto_type_name) {
registry_[config_proto_type_name] = std::move(plugin);
std::unique_ptr<XdsClusterSpecifierPluginImpl> plugin) {
absl::string_view name = plugin->ConfigProtoName();
registry_[name] = std::move(plugin);
}
const XdsClusterSpecifierPluginImpl*

@ -21,15 +21,15 @@
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "upb/arena.h"
#include "upb/def.h"
#include "src/core/ext/xds/xds_common_types.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/json/json.h"
namespace grpc_core {
@ -37,21 +37,27 @@ class XdsClusterSpecifierPluginImpl {
public:
virtual ~XdsClusterSpecifierPluginImpl() = default;
// Returns the config proto message name.
virtual absl::string_view ConfigProtoName() const = 0;
// Loads the proto message into the upb symtab.
virtual void PopulateSymtab(upb_DefPool* symtab) const = 0;
// Returns the LB policy config in JSON form.
virtual absl::StatusOr<std::string> GenerateLoadBalancingPolicyConfig(
XdsExtension extension, upb_Arena* arena, upb_DefPool* symtab) const = 0;
virtual Json GenerateLoadBalancingPolicyConfig(
XdsExtension extension, upb_Arena* arena, upb_DefPool* symtab,
ValidationErrors* errors) const = 0;
};
class XdsRouteLookupClusterSpecifierPlugin
: public XdsClusterSpecifierPluginImpl {
absl::string_view ConfigProtoName() const override;
void PopulateSymtab(upb_DefPool* symtab) const override;
absl::StatusOr<std::string> GenerateLoadBalancingPolicyConfig(
XdsExtension extension, upb_Arena* arena,
upb_DefPool* symtab) const override;
Json GenerateLoadBalancingPolicyConfig(
XdsExtension extension, upb_Arena* arena, upb_DefPool* symtab,
ValidationErrors* errors) const override;
};
class XdsClusterSpecifierPluginRegistry {
@ -74,8 +80,7 @@ class XdsClusterSpecifierPluginRegistry {
return *this;
}
void RegisterPlugin(std::unique_ptr<XdsClusterSpecifierPluginImpl> plugin,
absl::string_view config_proto_type_name);
void RegisterPlugin(std::unique_ptr<XdsClusterSpecifierPluginImpl> plugin);
void PopulateSymtab(upb_DefPool* symtab) const;

@ -454,7 +454,7 @@ absl::optional<XdsExtension> ExtractXdsExtension(
ValidationErrors::ScopedField field(errors, ".type_url");
if (extension.type.empty()) {
errors->AddError("field not present");
return;
return false;
}
size_t pos = extension.type.rfind('/');
if (pos == absl::string_view::npos || pos == extension.type.size() - 1) {
@ -462,9 +462,10 @@ absl::optional<XdsExtension> ExtractXdsExtension(
} else {
extension.type = extension.type.substr(pos + 1);
}
return true;
};
extension.type = UpbStringToAbsl(google_protobuf_Any_type_url(any));
strip_type_prefix();
if (!strip_type_prefix()) return absl::nullopt;
extension.validation_fields.emplace_back(
errors, absl::StrCat(".value[", extension.type, "]"));
absl::string_view any_value = UpbStringToAbsl(google_protobuf_Any_value(any));
@ -478,7 +479,7 @@ absl::optional<XdsExtension> ExtractXdsExtension(
}
extension.type =
UpbStringToAbsl(xds_type_v3_TypedStruct_type_url(typed_struct));
strip_type_prefix();
if (!strip_type_prefix()) return absl::nullopt;
extension.validation_fields.emplace_back(
errors, absl::StrCat(".value[", extension.type, "]"));
auto* protobuf_struct = xds_type_v3_TypedStruct_value(typed_struct);

@ -395,15 +395,10 @@ XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse(
const google_protobuf_Any* typed_config =
envoy_extensions_filters_network_http_connection_manager_v3_HttpFilter_typed_config(
http_filter);
if (typed_config == nullptr) {
if (!is_optional) errors->AddError("field not present");
continue;
}
auto extension = ExtractXdsExtension(context, typed_config, errors);
const XdsHttpFilterImpl* filter_impl = nullptr;
if (extension.has_value()) {
filter_impl = http_filter_registry.GetFilterForType(extension->type);
}
if (!extension.has_value()) continue;
const XdsHttpFilterImpl* filter_impl =
http_filter_registry.GetFilterForType(extension->type);
if (filter_impl == nullptr) {
if (!is_optional) errors->AddError("unsupported filter type");
continue;
@ -463,13 +458,9 @@ XdsListenerResource::HttpConnectionManager HttpConnectionManagerParse(
const envoy_config_route_v3_RouteConfiguration* route_config =
envoy_extensions_filters_network_http_connection_manager_v3_HttpConnectionManager_route_config(
http_connection_manager_proto);
auto rds_update = XdsRouteConfigResource::Parse(context, route_config);
if (!rds_update.ok()) {
ValidationErrors::ScopedField field(errors, ".route_config");
errors->AddError(rds_update.status().message());
} else {
http_connection_manager.route_config = std::move(*rds_update);
}
http_connection_manager.route_config =
XdsRouteConfigResource::Parse(context, route_config, errors);
} else {
// Validate that RDS must be used to get the route_config dynamically.
const envoy_extensions_filters_network_http_connection_manager_v3_Rds* rds =
@ -506,15 +497,11 @@ absl::StatusOr<XdsListenerResource> LdsResourceParseClient(
ValidationErrors::ScopedField field(&errors, "api_listener.api_listener");
auto* api_listener_field =
envoy_config_listener_v3_ApiListener_api_listener(api_listener);
if (api_listener_field == nullptr) {
errors.AddError("field not present");
} else {
auto extension = ExtractXdsExtension(context, api_listener_field, &errors);
if (extension.has_value()) {
lds_update.listener = HttpConnectionManagerParse(
/*is_client=*/true, context, std::move(*extension), &errors);
}
}
if (!errors.ok()) return errors.status("errors validating ApiListener");
return std::move(lds_update);
}

File diff suppressed because it is too large Load Diff

@ -27,7 +27,6 @@
#include <string>
#include <vector>
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
@ -44,6 +43,7 @@
#include "src/core/ext/xds/xds_resource_type_impl.h"
#include "src/core/lib/channel/status_util.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
#include "src/core/lib/matchers/matchers.h"
namespace grpc_core {
@ -102,25 +102,35 @@ struct XdsRouteConfigResource : public XdsResourceType::ResourceData {
struct RouteAction {
struct HashPolicy {
enum Type { HEADER, CHANNEL_ID };
Type type;
bool terminal = false;
// Fields used for type HEADER.
struct Header {
std::string header_name;
std::unique_ptr<RE2> regex = nullptr;
std::unique_ptr<RE2> regex;
std::string regex_substitution;
HashPolicy() {}
Header() = default;
// Copyable.
HashPolicy(const HashPolicy& other);
HashPolicy& operator=(const HashPolicy& other);
Header(const Header& other);
Header& operator=(const Header& other);
// Moveable.
HashPolicy(HashPolicy&& other) noexcept;
HashPolicy& operator=(HashPolicy&& other) noexcept;
// Movable.
Header(Header&& other) noexcept;
Header& operator=(Header&& other) noexcept;
bool operator==(const HashPolicy& other) const;
bool operator==(const Header& other) const;
std::string ToString() const;
};
struct ChannelId {
bool operator==(const ChannelId&) const { return true; }
};
absl::variant<Header, ChannelId> policy;
bool terminal = false;
bool operator==(const HashPolicy& other) const {
return policy == other.policy && terminal == other.terminal;
}
std::string ToString() const;
};
@ -210,9 +220,10 @@ struct XdsRouteConfigResource : public XdsResourceType::ResourceData {
}
std::string ToString() const;
static absl::StatusOr<XdsRouteConfigResource> Parse(
static XdsRouteConfigResource Parse(
const XdsResourceType::DecodeContext& context,
const envoy_config_route_v3_RouteConfiguration* route_config);
const envoy_config_route_v3_RouteConfiguration* route_config,
ValidationErrors* errors);
};
class XdsRouteConfigResourceType

@ -22,7 +22,11 @@
#include <string.h>
#include <algorithm>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "src/core/lib/gpr/useful.h"
@ -114,6 +118,20 @@ bool grpc_status_code_from_int(int status_int, grpc_status_code* status) {
namespace grpc_core {
namespace internal {
std::string StatusCodeSet::ToString() const {
std::vector<absl::string_view> codes;
for (size_t i = 0; i < GPR_ARRAY_SIZE(g_status_string_entries); ++i) {
if (Contains(g_status_string_entries[i].status)) {
codes.emplace_back(g_status_string_entries[i].str);
}
}
return absl::StrCat("{", absl::StrJoin(codes, ","), "}");
}
} // namespace internal
absl::Status MaybeRewriteIllegalStatusCode(absl::Status status,
absl::string_view source) {
switch (status.code()) {

@ -21,6 +21,8 @@
#include <grpc/support/port_platform.h>
#include <string>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
@ -47,7 +49,10 @@ class StatusCodeSet {
public:
bool Empty() const { return status_code_mask_ == 0; }
void Add(grpc_status_code status) { status_code_mask_ |= (1 << status); }
StatusCodeSet& Add(grpc_status_code status) {
status_code_mask_ |= (1 << status);
return *this;
}
bool Contains(grpc_status_code status) const {
return status_code_mask_ & (1 << status);
@ -57,6 +62,8 @@ class StatusCodeSet {
return status_code_mask_ == other.status_code_mask_;
}
std::string ToString() const;
private:
int status_code_mask_ = 0; // A bitfield of status codes in the set.
};

@ -22,6 +22,7 @@
#include "absl/strings/ascii.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
namespace grpc_core {
@ -37,7 +38,8 @@ absl::StatusOr<StringMatcher> StringMatcher::Create(Type type,
auto regex_matcher = std::make_unique<RE2>(std::string(matcher));
if (!regex_matcher->ok()) {
return absl::InvalidArgumentError(
"Invalid regex string specified in matcher.");
absl::StrCat("Invalid regex string specified in matcher: ",
regex_matcher->error()));
}
return StringMatcher(std::move(regex_matcher));
} else {

@ -188,6 +188,8 @@ message RouteMatch {
// on :path, etc. The issue with that is it is unclear how to generically deal with query string
// stripping. This needs more thought.]
type.matcher.v3.RegexMatcher safe_regex = 10;
string path_separated_prefix = 14;
}
// Indicates that prefix/path matching should be case insensitive. The default

@ -86,7 +86,9 @@ TEST(StringMatcherTest, InvalidRegex) {
EXPECT_FALSE(string_matcher.ok());
EXPECT_EQ(string_matcher.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(string_matcher.status().message(),
"Invalid regex string specified in matcher.");
"Invalid regex string specified in matcher: "
"invalid character class range: b-a")
<< string_matcher.status();
}
TEST(StringMatcherTest, SafeRegexMatchCaseSensitive) {
@ -161,7 +163,9 @@ TEST(HeaderMatcherTest, InvalidRegex) {
EXPECT_FALSE(header_matcher.ok());
EXPECT_EQ(header_matcher.status().code(), absl::StatusCode::kInvalidArgument);
EXPECT_EQ(header_matcher.status().message(),
"Invalid regex string specified in matcher.");
"Invalid regex string specified in matcher: "
"invalid character class range: b-a")
<< header_matcher.status();
}
TEST(HeaderMatcherTest, RangeMatcherValidRange) {

@ -245,6 +245,28 @@ grpc_cc_test(
],
)
grpc_cc_test(
name = "xds_route_config_resource_type_test",
srcs = ["xds_route_config_resource_type_test.cc"],
external_deps = ["gtest"],
language = "C++",
uses_event_engine = False,
uses_polling = False,
deps = [
"//:gpr",
"//:grpc",
"//src/core:grpc_xds_client",
"//src/proto/grpc/lookup/v1:rls_config_proto",
"//src/proto/grpc/testing/xds/v3:fault_proto",
"//src/proto/grpc/testing/xds/v3:http_filter_rbac_proto",
"//src/proto/grpc/testing/xds/v3:route_proto",
"//src/proto/grpc/testing/xds/v3:typed_struct_proto",
"//test/core/util:grpc_test_util",
"//test/core/util:scoped_env_var",
"//test/cpp/util:grpc_cli_utils",
],
)
grpc_cc_test(
name = "xds_cluster_resource_type_test",
srcs = ["xds_cluster_resource_type_test.cc"],

@ -578,37 +578,6 @@ TEST_P(HttpConnectionManagerTest, HttpFilterMissingConfig) {
<< decode_result.resource.status();
}
TEST_P(HttpConnectionManagerTest, HttpFilterMissingConfigButOptional) {
HttpConnectionManager hcm;
auto* filter = hcm.add_http_filters();
filter->set_name("foo");
filter->set_is_optional(true);
filter = hcm.add_http_filters();
filter->set_name("router");
filter->mutable_typed_config()->PackFrom(Router());
auto* rds = hcm.mutable_rds();
rds->set_route_config_name("rds_name");
rds->mutable_config_source()->mutable_self();
Listener listener = MakeListener(hcm);
std::string serialized_resource;
ASSERT_TRUE(listener.SerializeToString(&serialized_resource));
auto* resource_type = XdsListenerResourceType::Get();
auto decode_result =
resource_type->Decode(decode_context_, serialized_resource);
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<XdsListenerResource&>(**decode_result.resource);
auto http_connection_manager = GetHCMConfig(resource);
ASSERT_TRUE(http_connection_manager.has_value());
ASSERT_EQ(http_connection_manager->http_filters.size(), 1UL);
auto& router = http_connection_manager->http_filters[0];
EXPECT_EQ(router.name, "router");
EXPECT_EQ(router.config.config_proto_type_name,
"envoy.extensions.filters.http.router.v3.Router");
EXPECT_EQ(router.config.config, Json()) << router.config.config.Dump();
}
TEST_P(HttpConnectionManagerTest, HttpFilterTypeNotSupported) {
HttpConnectionManager hcm;
auto* filter = hcm.add_http_filters();

File diff suppressed because it is too large Load Diff

@ -467,7 +467,9 @@ TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpRouteError) {
kDefaultRouteConfigurationName, kDefaultClusterName)),
ClientResourceStatus::NACKED,
EqUpdateFailureState(
::testing::HasSubstr("VirtualHost has no domains"), "2"))));
::testing::HasSubstr(
"field:virtual_hosts[0].domains error:must be non-empty"),
"2"))));
} else {
ok = ::testing::Value(
csds_response.config(0).generic_xds_configs(),
@ -478,7 +480,12 @@ TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpRouteError) {
kDefaultClusterName))),
ClientResourceStatus::NACKED,
EqUpdateFailureState(
::testing::HasSubstr("VirtualHost has no domains"), "2"))));
::testing::HasSubstr(
"field:api_listener.api_listener.value[envoy.extensions"
".filters.network.http_connection_manager.v3"
".HttpConnectionManager].route_config.virtual_hosts[0]"
".domains error:must be non-empty"),
"2"))));
}
if (ok) return; // TEST PASSED!
gpr_sleep_until(

@ -1796,62 +1796,6 @@ TEST_P(XdsServerRdsTest, Basic) {
SendRpc([this]() { return CreateInsecureChannel(); }, {}, {});
}
TEST_P(XdsServerRdsTest, NacksInvalidDomainPattern) {
RouteConfiguration route_config = default_server_route_config_;
route_config.mutable_virtual_hosts()->at(0).add_domains("");
SetServerListenerNameAndRouteConfiguration(
balancer_.get(), default_server_listener_, backends_[0]->port(),
route_config);
backends_[0]->Start();
const auto response_state = WaitForRouteConfigNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("Invalid domain pattern \"\""));
}
TEST_P(XdsServerRdsTest, NacksEmptyDomainsList) {
RouteConfiguration route_config = default_server_route_config_;
route_config.mutable_virtual_hosts()->at(0).clear_domains();
SetServerListenerNameAndRouteConfiguration(
balancer_.get(), default_server_listener_, backends_[0]->port(),
route_config);
backends_[0]->Start();
const auto response_state = WaitForRouteConfigNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("VirtualHost has no domains"));
}
TEST_P(XdsServerRdsTest, NacksEmptyRoutesList) {
RouteConfiguration route_config = default_server_route_config_;
route_config.mutable_virtual_hosts()->at(0).clear_routes();
SetServerListenerNameAndRouteConfiguration(
balancer_.get(), default_server_listener_, backends_[0]->port(),
route_config);
backends_[0]->Start();
const auto response_state = WaitForRouteConfigNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No route found in the virtual host"));
}
TEST_P(XdsServerRdsTest, NacksEmptyMatch) {
RouteConfiguration route_config = default_server_route_config_;
route_config.mutable_virtual_hosts()
->at(0)
.mutable_routes()
->at(0)
.clear_match();
SetServerListenerNameAndRouteConfiguration(
balancer_.get(), default_server_listener_, backends_[0]->port(),
route_config);
backends_[0]->Start();
const auto response_state = WaitForRouteConfigNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("Match can't be null"));
}
TEST_P(XdsServerRdsTest, FailsRouteMatchesOtherThanNonForwardingAction) {
SetServerListenerNameAndRouteConfiguration(
balancer_.get(), default_server_listener_, backends_[0]->port(),

@ -161,224 +161,6 @@ TEST_P(RlsTest, XdsRoutingClusterSpecifierPlugin) {
EXPECT_EQ(kNumEchoRpcs, backends_[1]->backend_service()->request_count());
}
TEST_P(RlsTest, XdsRoutingClusterSpecifierPluginNotUsedInAllVhosts) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RLS_LB");
CreateAndStartBackends(2);
const char* kNewClusterName = "new_cluster";
const char* kNewEdsServiceName = "new_eds_service_name";
const size_t kNumEchoRpcs = 5;
// Populate new EDS resources.
EdsResourceArgs args({
{"locality0", CreateEndpointsForBackends(0, 1)},
});
EdsResourceArgs args1({
{"locality0", CreateEndpointsForBackends(1, 2)},
});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
balancer_->ads_service()->SetEdsResource(
BuildEdsResource(args1, kNewEdsServiceName));
// Populate new CDS resources.
Cluster new_cluster = default_cluster_;
new_cluster.set_name(kNewClusterName);
new_cluster.mutable_eds_cluster_config()->set_service_name(
kNewEdsServiceName);
balancer_->ads_service()->SetCdsResource(new_cluster);
// Prepare the RLSLookupConfig and configure all the keys; change route
// configurations to use cluster specifier plugin.
rls_server_->rls_service()->SetResponse(
BuildRlsRequest({{kRlsTestKey, kRlsTestValue},
{kRlsHostKey, kServerName},
{kRlsServiceKey, kRlsServiceValue},
{kRlsMethodKey, kRlsMethodValue},
{kRlsConstantKey, kRlsConstantValue}}),
BuildRlsResponse({kNewClusterName}));
RouteLookupConfig route_lookup_config;
auto* key_builder = route_lookup_config.add_grpc_keybuilders();
auto* name = key_builder->add_names();
name->set_service(kRlsServiceValue);
name->set_method(kRlsMethodValue);
auto* header = key_builder->add_headers();
header->set_key(kRlsTestKey);
header->add_names(kRlsTestKey1);
header->add_names("key2");
auto* extra_keys = key_builder->mutable_extra_keys();
extra_keys->set_host(kRlsHostKey);
extra_keys->set_service(kRlsServiceKey);
extra_keys->set_method(kRlsMethodKey);
(*key_builder->mutable_constant_keys())[kRlsConstantKey] = kRlsConstantValue;
route_lookup_config.set_lookup_service(
absl::StrCat("localhost:", rls_server_->port()));
route_lookup_config.set_cache_size_bytes(5000);
RouteLookupClusterSpecifier rls;
*rls.mutable_route_lookup_config() = std::move(route_lookup_config);
RouteConfiguration new_route_config = default_route_config_;
auto* plugin = new_route_config.add_cluster_specifier_plugins();
plugin->mutable_extension()->set_name(kRlsClusterSpecifierPluginInstanceName);
plugin->mutable_extension()->mutable_typed_config()->PackFrom(rls);
// Duplicate the virtual host, but with a different domain.
*new_route_config.add_virtual_hosts() = new_route_config.virtual_hosts(0);
new_route_config.mutable_virtual_hosts(1)->clear_domains();
new_route_config.mutable_virtual_hosts(1)->add_domains("www.example.com");
// In the original virtual host, set the default route to use the RLS plugin.
auto* default_route =
new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
default_route->mutable_route()->set_cluster_specifier_plugin(
kRlsClusterSpecifierPluginInstanceName);
SetRouteConfiguration(balancer_.get(), new_route_config);
auto rpc_options = RpcOptions().set_metadata({{kRlsTestKey1, kRlsTestValue}});
WaitForAllBackends(DEBUG_LOCATION, 1, 2, /*check_status=*/nullptr,
WaitForBackendOptions(), rpc_options);
CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, rpc_options);
// Make sure RPCs all go to the correct backend.
EXPECT_EQ(kNumEchoRpcs, backends_[1]->backend_service()->request_count());
}
TEST_P(RlsTest, XdsRoutingClusterSpecifierPluginNacksUndefinedSpecifier) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RLS_LB");
RouteConfiguration new_route_config = default_route_config_;
auto* default_route =
new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
// Set Cluster Specifier Plugin to something that does not exist.
default_route->mutable_route()->set_cluster_specifier_plugin(
kRlsClusterSpecifierPluginInstanceName);
SetRouteConfiguration(balancer_.get(), new_route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(absl::StrCat(
"RouteAction cluster contains cluster specifier plugin "
"name not configured: ",
kRlsClusterSpecifierPluginInstanceName)));
}
TEST_P(RlsTest, XdsRoutingClusterSpecifierPluginNacksDuplicateSpecifier) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RLS_LB");
// Prepare the RLSLookupConfig: change route configurations to use cluster
// specifier plugin.
RouteLookupConfig route_lookup_config;
auto* key_builder = route_lookup_config.add_grpc_keybuilders();
auto* name = key_builder->add_names();
name->set_service(kRlsServiceValue);
name->set_method(kRlsMethodValue);
auto* header = key_builder->add_headers();
header->set_key(kRlsTestKey);
header->add_names(kRlsTestKey1);
route_lookup_config.set_lookup_service(
absl::StrCat("localhost:", rls_server_->port()));
route_lookup_config.set_cache_size_bytes(5000);
RouteLookupClusterSpecifier rls;
*rls.mutable_route_lookup_config() = std::move(route_lookup_config);
RouteConfiguration new_route_config = default_route_config_;
auto* plugin = new_route_config.add_cluster_specifier_plugins();
plugin->mutable_extension()->set_name(kRlsClusterSpecifierPluginInstanceName);
plugin->mutable_extension()->mutable_typed_config()->PackFrom(rls);
auto* duplicate_plugin = new_route_config.add_cluster_specifier_plugins();
duplicate_plugin->mutable_extension()->set_name(
kRlsClusterSpecifierPluginInstanceName);
duplicate_plugin->mutable_extension()->mutable_typed_config()->PackFrom(rls);
auto* default_route =
new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
default_route->mutable_route()->set_cluster_specifier_plugin(
kRlsClusterSpecifierPluginInstanceName);
SetRouteConfiguration(balancer_.get(), new_route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(absl::StrCat(
"Duplicated definition of cluster_specifier_plugin ",
kRlsClusterSpecifierPluginInstanceName)));
}
TEST_P(RlsTest,
XdsRoutingClusterSpecifierPluginNacksUnknownSpecifierProtoNotOptional) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RLS_LB");
// Prepare the RLSLookupConfig: change route configurations to use cluster
// specifier plugin.
RouteLookupConfig route_lookup_config;
RouteConfiguration new_route_config = default_route_config_;
auto* plugin = new_route_config.add_cluster_specifier_plugins();
plugin->mutable_extension()->set_name(kRlsClusterSpecifierPluginInstanceName);
// Instead of grpc.lookup.v1.RouteLookupClusterSpecifier, let's say we
// mistakenly packed the inner RouteLookupConfig instead.
plugin->mutable_extension()->mutable_typed_config()->PackFrom(
route_lookup_config);
auto* default_route =
new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
default_route->mutable_route()->set_cluster_specifier_plugin(
kRlsClusterSpecifierPluginInstanceName);
SetRouteConfiguration(balancer_.get(), new_route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("Unknown ClusterSpecifierPlugin type "
"grpc.lookup.v1.RouteLookupConfig"));
}
TEST_P(RlsTest,
XdsRoutingClusterSpecifierPluginIgnoreUnknownSpecifierProtoOptional) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RLS_LB");
CreateAndStartBackends(1);
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// Prepare the RLSLookupConfig: change route configurations to use cluster
// specifier plugin.
RouteLookupConfig route_lookup_config;
RouteConfiguration new_route_config = default_route_config_;
auto* plugin = new_route_config.add_cluster_specifier_plugins();
plugin->mutable_extension()->set_name(kRlsClusterSpecifierPluginInstanceName);
// Instead of grpc.lookup.v1.RouteLookupClusterSpecifier, let's say we
// mistakenly packed the inner RouteLookupConfig instead.
plugin->mutable_extension()->mutable_typed_config()->PackFrom(
route_lookup_config);
plugin->set_is_optional(true);
auto* route = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route->mutable_route()->set_cluster_specifier_plugin(
kRlsClusterSpecifierPluginInstanceName);
auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes();
default_route->mutable_match()->set_prefix("");
default_route->mutable_route()->set_cluster(kDefaultClusterName);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Ensure we ignore the cluster specifier plugin and send traffic according to
// the default route.
WaitForAllBackends(DEBUG_LOCATION);
}
TEST_P(RlsTest, XdsRoutingRlsClusterSpecifierPluginNacksRequiredMatch) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_RLS_LB");
// Prepare the RLSLookupConfig and configure all the keys; add required_match
// field which should not be there.
RouteLookupConfig route_lookup_config;
auto* key_builder = route_lookup_config.add_grpc_keybuilders();
auto* name = key_builder->add_names();
name->set_service(kRlsServiceValue);
name->set_method(kRlsMethodValue);
auto* header = key_builder->add_headers();
header->set_key(kRlsTestKey);
header->add_names(kRlsTestKey1);
header->set_required_match(true);
route_lookup_config.set_lookup_service(
absl::StrCat("localhost:", rls_server_->port()));
route_lookup_config.set_cache_size_bytes(5000);
RouteLookupClusterSpecifier rls;
*rls.mutable_route_lookup_config() = std::move(route_lookup_config);
RouteConfiguration new_route_config = default_route_config_;
auto* plugin = new_route_config.add_cluster_specifier_plugins();
plugin->mutable_extension()->set_name(kRlsClusterSpecifierPluginInstanceName);
plugin->mutable_extension()->mutable_typed_config()->PackFrom(rls);
auto* default_route =
new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
default_route->mutable_route()->set_cluster_specifier_plugin(
kRlsClusterSpecifierPluginInstanceName);
SetRouteConfiguration(balancer_.get(), new_route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr(
"field:routeLookupConfig.grpcKeybuilders[0].headers[0].requiredMatch "
"error:must not be present"));
}
TEST_P(RlsTest, XdsRoutingClusterSpecifierPluginDisabled) {
CreateAndStartBackends(1);
// Populate new EDS resources.

@ -148,21 +148,6 @@ MATCHER_P2(AdjustedClockInRange, t1, t2, "equals time") {
return ok;
}
TEST_P(LdsRdsTest, DefaultRouteSpecifiesSlashPrefix) {
CreateAndStartBackends(1);
RouteConfiguration route_config = default_route_config_;
route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_match()
->set_prefix("/");
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// We need to wait for all backends to come online.
WaitForAllBackends(DEBUG_LOCATION);
}
// Tests that LDS client ACKs but fails if matching domain can't be found in
// the LDS response.
TEST_P(LdsRdsTest, NoMatchedDomain) {
@ -231,165 +216,31 @@ TEST_P(LdsRdsTest, NoMatchingRoute) {
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Tests that LDS client should ignore route which has query_parameters.
TEST_P(LdsRdsTest, RouteMatchHasQueryParameters) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
route1->mutable_match()->add_query_parameters();
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should send a ACK if route match has a prefix
// that is either empty or a single slash
TEST_P(LdsRdsTest, RouteMatchHasValidPrefixEmptyOrSingleSlash) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("");
auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes();
default_route->mutable_match()->set_prefix("/");
default_route->mutable_route()->set_cluster(kDefaultClusterName);
SetRouteConfiguration(balancer_.get(), route_config);
(void)SendRpc();
const auto response_state = RouteConfigurationResponseState(balancer_.get());
ASSERT_TRUE(response_state.has_value());
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Tests that LDS client should ignore route which has a path
// prefix string does not start with "/".
TEST_P(LdsRdsTest, RouteMatchHasInvalidPrefixNoLeadingSlash) {
// Testing just one example of an invalid resource here.
// Unit tests for XdsRouteConfigResourceType have exhaustive tests for all
// of the invalid cases.
TEST_P(LdsRdsTest, NacksInvalidRouteConfig) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("grpc.testing.EchoTest1Service/");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has a prefix
// string with more than 2 slashes.
TEST_P(LdsRdsTest, RouteMatchHasInvalidPrefixExtraContent) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/Echo1/");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has a prefix
// string "//".
TEST_P(LdsRdsTest, RouteMatchHasInvalidPrefixDoubleSlash) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("//");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has path
// but it's empty.
TEST_P(LdsRdsTest, RouteMatchHasInvalidPathEmptyPath) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_path("");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has path
// string does not start with "/".
TEST_P(LdsRdsTest, RouteMatchHasInvalidPathNoLeadingSlash) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_path("grpc.testing.EchoTest1Service/Echo1");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has path
// string that has too many slashes; for example, ends with "/".
TEST_P(LdsRdsTest, RouteMatchHasInvalidPathTooManySlashes) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service/Echo1/");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has path
// string that has only 1 slash: missing "/" between service and method.
TEST_P(LdsRdsTest, RouteMatchHasInvalidPathOnlyOneSlash) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service.Echo1");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has path
// string that is missing service.
TEST_P(LdsRdsTest, RouteMatchHasInvalidPathMissingService) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_path("//Echo1");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Tests that LDS client should ignore route which has path
// string that is missing method.
TEST_P(LdsRdsTest, RouteMatchHasInvalidPathMissingMethod) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service/");
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("No valid routes specified."));
}
// Test that LDS client should reject route which has invalid path regex.
TEST_P(LdsRdsTest, RouteMatchHasInvalidPathRegex) {
const char* kNewCluster1Name = "new_cluster_1";
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->mutable_safe_regex()->set_regex("a[z-a]");
route1->mutable_route()->set_cluster(kNewCluster1Name);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"path matcher: Invalid regex string specified in matcher."));
EXPECT_EQ(
response_state->error_message,
absl::StrCat(
"xDS response validation errors: [resource index 0: ",
GetParam().enable_rds_testing()
? "route_config_name: INVALID_ARGUMENT: "
"errors validating RouteConfiguration resource: ["
"field:"
: "server.example.com: INVALID_ARGUMENT: "
"errors validating ApiListener: ["
"field:api_listener.api_listener.value["
"envoy.extensions.filters.network.http_connection_manager.v3"
".HttpConnectionManager].route_config.",
"virtual_hosts[0].routes "
"error:no valid routes in VirtualHost]]"));
}
// Tests that LDS client should fail RPCs with UNAVAILABLE status code if the
@ -409,158 +260,6 @@ TEST_P(LdsRdsTest, MatchingRouteHasNoRouteAction) {
"Matching route has inappropriate action");
}
TEST_P(LdsRdsTest, RouteActionClusterHasEmptyClusterName) {
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
route1->mutable_route()->set_cluster("");
auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes();
default_route->mutable_match()->set_prefix("");
default_route->mutable_route()->set_cluster(kDefaultClusterName);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr("RouteAction cluster contains empty cluster name."));
}
TEST_P(LdsRdsTest, RouteActionWeightedTargetHasIncorrectTotalWeightSet) {
const size_t kWeight75 = 75;
const char* kNewCluster1Name = "new_cluster_1";
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
auto* weighted_cluster1 =
route1->mutable_route()->mutable_weighted_clusters()->add_clusters();
weighted_cluster1->set_name(kNewCluster1Name);
weighted_cluster1->mutable_weight()->set_value(kWeight75);
route1->mutable_route()
->mutable_weighted_clusters()
->mutable_total_weight()
->set_value(kWeight75 + 1);
auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes();
default_route->mutable_match()->set_prefix("");
default_route->mutable_route()->set_cluster(kDefaultClusterName);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"RouteAction weighted_cluster has incorrect total weight"));
}
TEST_P(LdsRdsTest, RouteActionWeightedClusterHasZeroTotalWeight) {
const char* kNewCluster1Name = "new_cluster_1";
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
auto* weighted_cluster1 =
route1->mutable_route()->mutable_weighted_clusters()->add_clusters();
weighted_cluster1->set_name(kNewCluster1Name);
weighted_cluster1->mutable_weight()->set_value(0);
route1->mutable_route()
->mutable_weighted_clusters()
->mutable_total_weight()
->set_value(0);
auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes();
default_route->mutable_match()->set_prefix("");
default_route->mutable_route()->set_cluster(kDefaultClusterName);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr(
"RouteAction weighted_cluster has no valid clusters specified."));
}
TEST_P(LdsRdsTest, RouteActionWeightedTargetClusterHasEmptyClusterName) {
const size_t kWeight75 = 75;
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
auto* weighted_cluster1 =
route1->mutable_route()->mutable_weighted_clusters()->add_clusters();
weighted_cluster1->set_name("");
weighted_cluster1->mutable_weight()->set_value(kWeight75);
route1->mutable_route()
->mutable_weighted_clusters()
->mutable_total_weight()
->set_value(kWeight75);
auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes();
default_route->mutable_match()->set_prefix("");
default_route->mutable_route()->set_cluster(kDefaultClusterName);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("RouteAction weighted_cluster cluster "
"contains empty cluster name."));
}
TEST_P(LdsRdsTest, RouteActionWeightedTargetClusterHasNoWeight) {
const size_t kWeight75 = 75;
const char* kNewCluster1Name = "new_cluster_1";
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
auto* weighted_cluster1 =
route1->mutable_route()->mutable_weighted_clusters()->add_clusters();
weighted_cluster1->set_name(kNewCluster1Name);
route1->mutable_route()
->mutable_weighted_clusters()
->mutable_total_weight()
->set_value(kWeight75);
auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes();
default_route->mutable_match()->set_prefix("");
default_route->mutable_route()->set_cluster(kDefaultClusterName);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"RouteAction weighted_cluster cluster missing weight"));
}
TEST_P(LdsRdsTest, RouteHeaderMatchInvalidRegex) {
const char* kNewCluster1Name = "new_cluster_1";
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
auto* header_matcher1 = route1->mutable_match()->add_headers();
header_matcher1->set_name("header1");
header_matcher1->mutable_safe_regex_match()->set_regex("a[z-a]");
route1->mutable_route()->set_cluster(kNewCluster1Name);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr(
"header matcher: Invalid regex string specified in matcher."));
}
TEST_P(LdsRdsTest, RouteHeaderMatchInvalidRange) {
const char* kNewCluster1Name = "new_cluster_1";
RouteConfiguration route_config = default_route_config_;
auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0);
route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/");
auto* header_matcher1 = route1->mutable_match()->add_headers();
header_matcher1->set_name("header1");
header_matcher1->mutable_range_match()->set_start(1001);
header_matcher1->mutable_range_match()->set_end(1000);
route1->mutable_route()->set_cluster(kNewCluster1Name);
SetRouteConfiguration(balancer_.get(), route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr(
"header matcher: Invalid range specifier specified: end cannot be "
"smaller than start."));
}
// Tests that LDS client should choose the default route (with no matching
// specified) after unable to find a match with previous routes.
TEST_P(LdsRdsTest, XdsRoutingPathMatching) {
@ -1942,55 +1641,6 @@ TEST_P(LdsRdsTest,
EXPECT_EQ(1, backends_[0]->backend_service()->request_count());
}
TEST_P(LdsRdsTest, XdsRetryPolicyInvalidNumRetriesZero) {
CreateAndStartBackends(1);
// Populate new EDS resources.
EdsResourceArgs args({
{"locality0", CreateEndpointsForBackends(0, 1)},
});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// Construct route config to set retry policy.
RouteConfiguration new_route_config = default_route_config_;
auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
auto* retry_policy = route1->mutable_route()->mutable_retry_policy();
retry_policy->set_retry_on("deadline-exceeded");
// Setting num_retries to zero is not valid.
retry_policy->mutable_num_retries()->set_value(0);
SetRouteConfiguration(balancer_.get(), new_route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr(
"RouteAction RetryPolicy num_retries set to invalid value 0."));
}
TEST_P(LdsRdsTest, XdsRetryPolicyRetryBackOffMissingBaseInterval) {
CreateAndStartBackends(1);
// Populate new EDS resources.
EdsResourceArgs args({
{"locality0", CreateEndpointsForBackends(0, 1)},
});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// Construct route config to set retry policy.
RouteConfiguration new_route_config = default_route_config_;
auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0);
auto* retry_policy = route1->mutable_route()->mutable_retry_policy();
retry_policy->set_retry_on("deadline-exceeded");
retry_policy->mutable_num_retries()->set_value(1);
// RetryBackoff is there but base interval is missing.
SetProtoDuration(
grpc_core::Duration::Milliseconds(250),
retry_policy->mutable_retry_back_off()->mutable_max_interval());
SetRouteConfiguration(balancer_.get(), new_route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr(
"RouteAction RetryPolicy RetryBackoff missing base interval."));
}
TEST_P(LdsRdsTest, XdsRoutingHeadersMatching) {
CreateAndStartBackends(2);
const char* kNewClusterName = "new_cluster";
@ -2401,354 +2051,6 @@ TEST_P(LdsRdsTest, XdsRoutingChangeRoutesWithoutChangingClusters) {
EXPECT_EQ(1, backends_[1]->backend_service2()->request_count());
}
// Test that we NACK unknown filter types in VirtualHost.
TEST_P(LdsRdsTest, RejectsUnknownHttpFilterTypeInVirtualHost) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config =
route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(Listener());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("no filter registered for config type "
"envoy.config.listener.v3.Listener"));
}
// Test that we ignore optional unknown filter types in VirtualHost.
TEST_P(LdsRdsTest, IgnoresOptionalUnknownHttpFilterTypeInVirtualHost) {
CreateAndStartBackends(1);
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config =
route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config();
::envoy::config::route::v3::FilterConfig filter_config;
filter_config.mutable_config()->PackFrom(Listener());
filter_config.set_is_optional(true);
(*per_filter_config)["unknown"].PackFrom(filter_config);
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
WaitForAllBackends(DEBUG_LOCATION);
auto response_state = RouteConfigurationResponseState(balancer_.get());
ASSERT_TRUE(response_state.has_value());
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Test that we NACK filters without configs in VirtualHost.
TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInVirtualHost) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config =
route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"];
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"no filter config specified for filter name unknown"));
}
// Test that we NACK filters without configs in FilterConfig in VirtualHost.
TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInFilterConfigInVirtualHost) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config =
route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(
::envoy::config::route::v3::FilterConfig());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"no filter config specified for filter name unknown"));
}
// Test that we ignore optional filters without configs in VirtualHost.
TEST_P(LdsRdsTest, IgnoresOptionalHttpFilterWithoutConfigInVirtualHost) {
CreateAndStartBackends(1);
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config =
route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config();
::envoy::config::route::v3::FilterConfig filter_config;
filter_config.set_is_optional(true);
(*per_filter_config)["unknown"].PackFrom(filter_config);
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
EdsResourceArgs args({
{"locality0", CreateEndpointsForBackends()},
});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
WaitForAllBackends(DEBUG_LOCATION);
auto response_state = RouteConfigurationResponseState(balancer_.get());
ASSERT_TRUE(response_state.has_value());
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Test that we NACK unparseable filter types in VirtualHost.
TEST_P(LdsRdsTest, RejectsUnparseableHttpFilterTypeInVirtualHost) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config =
route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(
envoy::extensions::filters::http::router::v3::Router());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr("router filter does not support config override"));
}
// Test that we NACK unknown filter types in Route.
TEST_P(LdsRdsTest, RejectsUnknownHttpFilterTypeInRoute) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(Listener());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("no filter registered for config type "
"envoy.config.listener.v3.Listener"));
}
// Test that we ignore optional unknown filter types in Route.
TEST_P(LdsRdsTest, IgnoresOptionalUnknownHttpFilterTypeInRoute) {
CreateAndStartBackends(1);
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_typed_per_filter_config();
::envoy::config::route::v3::FilterConfig filter_config;
filter_config.mutable_config()->PackFrom(Listener());
filter_config.set_is_optional(true);
(*per_filter_config)["unknown"].PackFrom(filter_config);
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
WaitForAllBackends(DEBUG_LOCATION);
auto response_state = RouteConfigurationResponseState(balancer_.get());
ASSERT_TRUE(response_state.has_value());
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Test that we NACK filters without configs in Route.
TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInRoute) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"];
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"no filter config specified for filter name unknown"));
}
// Test that we NACK filters without configs in FilterConfig in Route.
TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInFilterConfigInRoute) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(
::envoy::config::route::v3::FilterConfig());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"no filter config specified for filter name unknown"));
}
// Test that we ignore optional filters without configs in Route.
TEST_P(LdsRdsTest, IgnoresOptionalHttpFilterWithoutConfigInRoute) {
CreateAndStartBackends(1);
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_typed_per_filter_config();
::envoy::config::route::v3::FilterConfig filter_config;
filter_config.set_is_optional(true);
(*per_filter_config)["unknown"].PackFrom(filter_config);
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
WaitForAllBackends(DEBUG_LOCATION);
auto response_state = RouteConfigurationResponseState(balancer_.get());
ASSERT_TRUE(response_state.has_value());
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Test that we NACK unparseable filter types in Route.
TEST_P(LdsRdsTest, RejectsUnparseableHttpFilterTypeInRoute) {
RouteConfiguration route_config = default_route_config_;
auto* per_filter_config = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(
envoy::extensions::filters::http::router::v3::Router());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr("router filter does not support config override"));
}
// Test that we NACK unknown filter types in ClusterWeight.
TEST_P(LdsRdsTest, RejectsUnknownHttpFilterTypeInClusterWeight) {
RouteConfiguration route_config = default_route_config_;
auto* cluster_weight = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_weighted_clusters()
->add_clusters();
cluster_weight->set_name(kDefaultClusterName);
cluster_weight->mutable_weight()->set_value(100);
auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(Listener());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr("no filter registered for config type "
"envoy.config.listener.v3.Listener"));
}
// Test that we ignore optional unknown filter types in ClusterWeight.
TEST_P(LdsRdsTest, IgnoresOptionalUnknownHttpFilterTypeInClusterWeight) {
CreateAndStartBackends(1);
RouteConfiguration route_config = default_route_config_;
auto* cluster_weight = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_weighted_clusters()
->add_clusters();
cluster_weight->set_name(kDefaultClusterName);
cluster_weight->mutable_weight()->set_value(100);
auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config();
::envoy::config::route::v3::FilterConfig filter_config;
filter_config.mutable_config()->PackFrom(Listener());
filter_config.set_is_optional(true);
(*per_filter_config)["unknown"].PackFrom(filter_config);
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
WaitForAllBackends(DEBUG_LOCATION);
auto response_state = RouteConfigurationResponseState(balancer_.get());
ASSERT_TRUE(response_state.has_value());
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Test that we NACK filters without configs in ClusterWeight.
TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInClusterWeight) {
RouteConfiguration route_config = default_route_config_;
auto* cluster_weight = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_weighted_clusters()
->add_clusters();
cluster_weight->set_name(kDefaultClusterName);
cluster_weight->mutable_weight()->set_value(100);
auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"];
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"no filter config specified for filter name unknown"));
}
// Test that we NACK filters without configs in FilterConfig in ClusterWeight.
TEST_P(LdsRdsTest,
RejectsHttpFilterWithoutConfigInFilterConfigInClusterWeight) {
RouteConfiguration route_config = default_route_config_;
auto* cluster_weight = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_weighted_clusters()
->add_clusters();
cluster_weight->set_name(kDefaultClusterName);
cluster_weight->mutable_weight()->set_value(100);
auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(
::envoy::config::route::v3::FilterConfig());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(response_state->error_message,
::testing::HasSubstr(
"no filter config specified for filter name unknown"));
}
// Test that we ignore optional filters without configs in ClusterWeight.
TEST_P(LdsRdsTest, IgnoresOptionalHttpFilterWithoutConfigInClusterWeight) {
CreateAndStartBackends(1);
RouteConfiguration route_config = default_route_config_;
auto* cluster_weight = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_weighted_clusters()
->add_clusters();
cluster_weight->set_name(kDefaultClusterName);
cluster_weight->mutable_weight()->set_value(100);
auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config();
::envoy::config::route::v3::FilterConfig filter_config;
filter_config.set_is_optional(true);
(*per_filter_config)["unknown"].PackFrom(filter_config);
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
WaitForAllBackends(DEBUG_LOCATION);
auto response_state = RouteConfigurationResponseState(balancer_.get());
ASSERT_TRUE(response_state.has_value());
EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED);
}
// Test that we NACK unparseable filter types in ClusterWeight.
TEST_P(LdsRdsTest, RejectsUnparseableHttpFilterTypeInClusterWeight) {
RouteConfiguration route_config = default_route_config_;
auto* cluster_weight = route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_weighted_clusters()
->add_clusters();
cluster_weight->set_name(kDefaultClusterName);
cluster_weight->mutable_weight()->set_value(100);
auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config();
(*per_filter_config)["unknown"].PackFrom(
envoy::extensions::filters::http::router::v3::Router());
SetListenerAndRouteConfiguration(balancer_.get(), default_listener_,
route_config);
const auto response_state = WaitForRdsNack(DEBUG_LOCATION);
ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK";
EXPECT_THAT(
response_state->error_message,
::testing::HasSubstr("router filter does not support config override"));
}
} // namespace
} // namespace testing
} // namespace grpc

@ -8551,6 +8551,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_route_config_resource_type_test",
"platforms": [
"linux",
"mac",
"posix",
"windows"
],
"uses_polling": false
},
{
"args": [],
"boringssl": true,

Loading…
Cancel
Save