[xDS] implement authority rewriting (#37087)

As per gRFC A81 (https://github.com/grpc/proposal/pull/435).

A few small improvements along the way:
- Use `DownCast<>` instead of `static_cast<>` in `ServiceConfigCallData::GetCallAttribute()`.
- Add a convenient templated wrapper for `ClientChannelLbCallState::GetCallAttribute()`, similar to the one on `ServiceConfigCallData`.
- Added a `ParseBoolValue()` helper to xds_common_types_parser, and use it in various places in xDS resource parsing.

Closes #37087

COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/37087 from markdroth:xds_authority_rewriting 3679d2b061
PiperOrigin-RevId: 650293912
pull/37183/head
Mark D. Roth 5 months ago committed by Copybara-Service
parent 5d9f743a40
commit e5c99ad94b
  1. 5
      src/core/BUILD
  2. 2
      src/core/client_channel/client_channel_filter.cc
  3. 6
      src/core/client_channel/client_channel_internal.h
  4. 18
      src/core/client_channel/lb_metadata.cc
  5. 6
      src/core/client_channel/lb_metadata.h
  6. 2
      src/core/client_channel/load_balanced_call_destination.cc
  7. 12
      src/core/load_balancing/lb_policy.h
  8. 3
      src/core/load_balancing/ring_hash/ring_hash.cc
  9. 29
      src/core/load_balancing/xds/xds_cluster_impl.cc
  10. 4
      src/core/load_balancing/xds/xds_cluster_manager.cc
  11. 4
      src/core/load_balancing/xds/xds_override_host.cc
  12. 3
      src/core/resolver/endpoint_addresses.h
  13. 3
      src/core/resolver/resolver.h
  14. 6
      src/core/resolver/xds/xds_dependency_manager.cc
  15. 4
      src/core/resolver/xds/xds_resolver.cc
  16. 6
      src/core/resolver/xds/xds_resolver_attributes.h
  17. 3
      src/core/service_config/service_config_call_data.h
  18. 8
      src/core/xds/grpc/xds_common_types_parser.cc
  19. 7
      src/core/xds/grpc/xds_common_types_parser.h
  20. 14
      src/core/xds/grpc/xds_endpoint_parser.cc
  21. 8
      src/core/xds/grpc/xds_lb_policy_registry.cc
  22. 17
      src/core/xds/grpc/xds_listener_parser.cc
  23. 6
      src/core/xds/grpc/xds_route_config.h
  24. 25
      src/core/xds/grpc/xds_route_config_parser.cc
  25. 11
      src/core/xds/grpc/xds_server_grpc.cc
  26. 2
      src/core/xds/grpc/xds_server_grpc.h
  27. 6
      src/proto/grpc/testing/xds/v3/endpoint.proto
  28. 24
      src/proto/grpc/testing/xds/v3/route.proto
  29. 102
      test/core/test_util/test_lb_policies.cc
  30. 7
      test/core/test_util/test_lb_policies.h
  31. 65
      test/core/xds/xds_bootstrap_test.cc
  32. 47
      test/core/xds/xds_endpoint_resource_type_test.cc
  33. 132
      test/core/xds/xds_route_config_resource_type_test.cc
  34. 266
      test/cpp/end2end/client_lb_end2end_test.cc
  35. 164
      test/cpp/end2end/xds/xds_cluster_end2end_test.cc
  36. 200
      test/cpp/end2end/xds/xds_cluster_type_end2end_test.cc
  37. 4
      test/cpp/end2end/xds/xds_end2end_test.cc
  38. 8
      test/cpp/end2end/xds/xds_end2end_test_lib.cc
  39. 18
      test/cpp/end2end/xds/xds_end2end_test_lib.h
  40. 6
      test/cpp/end2end/xds/xds_utils.cc
  41. 12
      test/cpp/end2end/xds/xds_utils.h

@ -3152,6 +3152,7 @@ grpc_cc_library(
deps = [
"arena",
"chunked_vector",
"down_cast",
"ref_counted",
"service_config_parser",
"slice_refcount",
@ -3251,6 +3252,7 @@ grpc_cc_library(
deps = [
"arena",
"call_destination",
"down_cast",
"grpc_service_config",
"lb_policy",
"unique_type_name",
@ -3564,6 +3566,7 @@ grpc_cc_library(
"absl/types:optional",
],
deps = [
"event_engine_common",
"lb_policy",
"metadata_batch",
],
@ -5839,6 +5842,7 @@ grpc_cc_library(
"delegating_helper",
"grpc_backend_metric_data",
"grpc_lb_xds_channel_args",
"grpc_resolver_xds_attributes",
"grpc_xds_client",
"json",
"json_args",
@ -6906,6 +6910,7 @@ grpc_cc_library(
deps = [
"grpc_service_config",
"unique_type_name",
"xds_route_config",
"//:gpr_platform",
],
)

@ -2647,6 +2647,8 @@ bool ClientChannelFilter::LoadBalancedCall::PickSubchannelImpl(
// Handle metadata mutations.
MetadataMutationHandler::Apply(complete_pick->metadata_mutations,
send_initial_metadata());
MaybeOverrideAuthority(std::move(complete_pick->authority_override),
send_initial_metadata());
return true;
},
// QueuePick

@ -26,6 +26,7 @@
#include <grpc/support/log.h>
#include "src/core/lib/gprpp/down_cast.h"
#include "src/core/lib/gprpp/unique_type_name.h"
#include "src/core/lib/resource_quota/arena.h"
#include "src/core/lib/transport/call_destination.h"
@ -49,6 +50,11 @@ namespace grpc_core {
// LB policies to access internal call attributes.
class ClientChannelLbCallState : public LoadBalancingPolicy::CallState {
public:
template <typename A>
A* GetCallAttribute() const {
return DownCast<A*>(GetCallAttribute(A::TypeName()));
}
virtual ServiceConfigCallData::CallAttributeInterface* GetCallAttribute(
UniqueTypeName type) const = 0;
virtual ClientCallTracer::CallAttemptTracer* GetCallAttemptTracer() const = 0;

@ -99,4 +99,22 @@ void MetadataMutationHandler::Apply(
}
}
//
// MaybeOverrideAuthority()
//
void MaybeOverrideAuthority(
grpc_event_engine::experimental::Slice authority_override,
grpc_metadata_batch* metadata) {
// Skip if no override requested.
if (authority_override.empty()) return;
// Skip if authority already set by the application on this RPC.
if (metadata->get_pointer(HttpAuthorityMetadata()) != nullptr) return;
// Otherwise, apply override.
Slice& authority =
grpc_event_engine::experimental::internal::SliceCast<Slice>(
authority_override);
metadata->Set(HttpAuthorityMetadata(), std::move(authority));
}
} // namespace grpc_core

@ -21,6 +21,8 @@
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include <grpc/event_engine/slice.h>
#include "src/core/lib/transport/metadata_batch.h"
#include "src/core/load_balancing/lb_policy.h"
@ -45,6 +47,10 @@ class MetadataMutationHandler {
grpc_metadata_batch* metadata);
};
void MaybeOverrideAuthority(
grpc_event_engine::experimental::Slice authority_override,
grpc_metadata_batch* metadata);
} // namespace grpc_core
#endif // GRPC_SRC_CORE_CLIENT_CHANNEL_LB_METADATA_H

@ -144,6 +144,8 @@ LoopCtl<absl::StatusOr<RefCountedPtr<UnstartedCallDestination>>> PickSubchannel(
// Apply metadata mutations, if any.
MetadataMutationHandler::Apply(complete_pick->metadata_mutations,
&client_initial_metadata);
MaybeOverrideAuthority(std::move(complete_pick->authority_override),
&client_initial_metadata);
// Return the connected subchannel.
return call_destination;
},

@ -220,13 +220,21 @@ class LoadBalancingPolicy : public InternallyRefCounted<LoadBalancingPolicy> {
/// Metadata mutations to be applied to the call.
MetadataMutations metadata_mutations;
/// Authority override for the RPC.
/// Will be used only if the application has not explicitly set
/// the authority for the RPC.
grpc_event_engine::experimental::Slice authority_override;
explicit Complete(
RefCountedPtr<SubchannelInterface> sc,
std::unique_ptr<SubchannelCallTrackerInterface> tracker = nullptr,
MetadataMutations md = MetadataMutations())
MetadataMutations md = MetadataMutations(),
grpc_event_engine::experimental::Slice authority =
grpc_event_engine::experimental::Slice())
: subchannel(std::move(sc)),
subchannel_call_tracker(std::move(tracker)),
metadata_mutations(std::move(md)) {}
metadata_mutations(std::move(md)),
authority_override(std::move(authority)) {}
};
/// Pick cannot be completed until something changes on the control

@ -300,8 +300,7 @@ class RingHash final : public LoadBalancingPolicy {
RingHash::PickResult RingHash::Picker::Pick(PickArgs args) {
auto* call_state = static_cast<ClientChannelLbCallState*>(args.call_state);
auto* hash_attribute = static_cast<RequestHashAttribute*>(
call_state->GetCallAttribute(RequestHashAttribute::TypeName()));
auto* hash_attribute = call_state->GetCallAttribute<RequestHashAttribute>();
if (hash_attribute == nullptr) {
return PickResult::Fail(absl::InternalError("hash attribute not present"));
}

@ -64,6 +64,7 @@
#include "src/core/load_balancing/xds/xds_channel_args.h"
#include "src/core/resolver/endpoint_addresses.h"
#include "src/core/resolver/xds/xds_dependency_manager.h"
#include "src/core/resolver/xds/xds_resolver_attributes.h"
#include "src/core/telemetry/call_tracer.h"
#include "src/core/util/json/json.h"
#include "src/core/util/json/json_args.h"
@ -201,9 +202,11 @@ class XdsClusterImplLb final : public LoadBalancingPolicy {
StatsSubchannelWrapper(
RefCountedPtr<SubchannelInterface> wrapped_subchannel,
LocalityData locality_data)
LocalityData locality_data, absl::string_view hostname)
: DelegatingSubchannel(std::move(wrapped_subchannel)),
locality_data_(std::move(locality_data)) {}
locality_data_(std::move(locality_data)),
hostname_(grpc_event_engine::experimental::Slice::FromCopiedString(
hostname)) {}
RefCountedStringValue locality() const {
return Match(
@ -225,8 +228,13 @@ class XdsClusterImplLb final : public LoadBalancingPolicy {
});
}
const grpc_event_engine::experimental::Slice& hostname() const {
return hostname_;
}
private:
LocalityData locality_data_;
grpc_event_engine::experimental::Slice hostname_;
};
// A picker that wraps the picker from the child to perform drops.
@ -454,6 +462,20 @@ LoadBalancingPolicy::PickResult XdsClusterImplLb::Picker::Pick(
locality_stats = subchannel_wrapper->locality_stats()->Ref(
DEBUG_LOCATION, "SubchannelCallTracker");
}
// Handle authority rewriting if needed.
if (!subchannel_wrapper->hostname().empty()) {
auto* route_state_attribute =
call_state->GetCallAttribute<XdsRouteStateAttribute>();
if (route_state_attribute != nullptr) {
auto* route_action =
absl::get_if<XdsRouteConfigResource::Route::RouteAction>(
&route_state_attribute->route().action);
if (route_action != nullptr && route_action->auto_host_rewrite) {
complete_pick->authority_override =
subchannel_wrapper->hostname().Ref();
}
}
}
// Unwrap subchannel to pass back up the stack.
complete_pick->subchannel = subchannel_wrapper->wrapped_subchannel();
// Inject subchannel call tracker to record call completion.
@ -808,7 +830,8 @@ RefCountedPtr<SubchannelInterface> XdsClusterImplLb::Helper::CreateSubchannel(
return MakeRefCounted<StatsSubchannelWrapper>(
parent()->channel_control_helper()->CreateSubchannel(
address, per_address_args, args),
std::move(locality_data));
std::move(locality_data),
per_address_args.GetString(GRPC_ARG_ADDRESS_NAME).value_or(""));
}
void XdsClusterImplLb::Helper::UpdateState(

@ -227,8 +227,8 @@ class XdsClusterManagerLb final : public LoadBalancingPolicy {
XdsClusterManagerLb::PickResult XdsClusterManagerLb::ClusterPicker::Pick(
PickArgs args) {
auto* call_state = static_cast<ClientChannelLbCallState*>(args.call_state);
auto* cluster_name_attribute = static_cast<XdsClusterAttribute*>(
call_state->GetCallAttribute(XdsClusterAttribute::TypeName()));
auto* cluster_name_attribute =
call_state->GetCallAttribute<XdsClusterAttribute>();
absl::string_view cluster_name;
if (cluster_name_attribute != nullptr) {
cluster_name = cluster_name_attribute->cluster();

@ -564,8 +564,8 @@ XdsOverrideHostLb::Picker::PickOverridenHost(
LoadBalancingPolicy::PickResult XdsOverrideHostLb::Picker::Pick(PickArgs args) {
auto* call_state = static_cast<ClientChannelLbCallState*>(args.call_state);
auto* override_host_attr = static_cast<XdsOverrideHostAttribute*>(
call_state->GetCallAttribute(XdsOverrideHostAttribute::TypeName()));
auto* override_host_attr =
call_state->GetCallAttribute<XdsOverrideHostAttribute>();
if (override_host_attr != nullptr) {
auto overridden_host_pick = PickOverridenHost(override_host_attr);
if (overridden_host_pick.has_value()) {

@ -40,6 +40,9 @@
// A channel arg indicating the weight of an address.
#define GRPC_ARG_ADDRESS_WEIGHT GRPC_ARG_NO_SUBCHANNEL_PREFIX "address.weight"
// Name associated with individual address, if available (e.g., DNS name).
#define GRPC_ARG_ADDRESS_NAME "grpc.address_name"
namespace grpc_core {
// A list of addresses for a given endpoint with an associated set of channel

@ -33,9 +33,6 @@
#include "src/core/resolver/server_address.h" // IWYU pragma: keep
#include "src/core/service_config/service_config.h"
// Name associated with individual address, if available.
#define GRPC_ARG_ADDRESS_NAME "grpc.address_name"
namespace grpc_core {
/// Interface for name resolution.

@ -709,7 +709,11 @@ void XdsDependencyManager::PopulateDnsUpdate(const std::string& dns_name,
locality.name = MakeRefCounted<XdsLocalityName>("", "", "");
locality.lb_weight = 1;
if (result.addresses.ok()) {
locality.endpoints = std::move(*result.addresses);
for (const auto& address : *result.addresses) {
locality.endpoints.emplace_back(
address.addresses(),
address.args().Set(GRPC_ARG_ADDRESS_NAME, dns_name));
}
dns_state->update.resolution_note = std::move(result.resolution_note);
} else if (result.resolution_note.empty()) {
dns_state->update.resolution_note =

@ -307,6 +307,10 @@ class XdsResolver final : public Resolver {
bool HasClusterForRoute(absl::string_view cluster_name) const override;
const XdsRouteConfigResource::Route& route() const override {
return route_->route;
}
private:
RefCountedPtr<RouteConfigData> route_config_data_;
RouteConfigData::RouteEntry* route_;

@ -23,6 +23,7 @@
#include "src/core/lib/gprpp/unique_type_name.h"
#include "src/core/service_config/service_config_call_data.h"
#include "src/core/xds/grpc/xds_route_config.h"
namespace grpc_core {
@ -53,8 +54,11 @@ class XdsRouteStateAttribute
return factory.Create();
}
virtual bool HasClusterForRoute(absl::string_view cluster_name) const = 0;
UniqueTypeName type() const override { return TypeName(); }
virtual bool HasClusterForRoute(absl::string_view cluster_name) const = 0;
virtual const XdsRouteConfigResource::Route& route() const = 0;
};
} // namespace grpc_core

@ -25,6 +25,7 @@
#include <grpc/support/port_platform.h>
#include "src/core/lib/gprpp/chunked_vector.h"
#include "src/core/lib/gprpp/down_cast.h"
#include "src/core/lib/gprpp/ref_counted_ptr.h"
#include "src/core/lib/gprpp/unique_type_name.h"
#include "src/core/lib/resource_quota/arena.h"
@ -85,7 +86,7 @@ class ServiceConfigCallData {
template <typename A>
A* GetCallAttribute() const {
return static_cast<A*>(GetCallAttribute(A::TypeName()));
return DownCast<A*>(GetCallAttribute(A::TypeName()));
}
CallAttributeInterface* GetCallAttribute(UniqueTypeName type) const {

@ -216,11 +216,9 @@ CertificateValidationContextParse(
ValidationErrors::ScopedField field(errors, ".verify_certificate_hash");
errors->AddError("feature unsupported");
}
auto* require_signed_certificate_timestamp =
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp(
certificate_validation_context_proto);
if (require_signed_certificate_timestamp != nullptr &&
google_protobuf_BoolValue_value(require_signed_certificate_timestamp)) {
if (ParseBoolValue(
envoy_extensions_transport_sockets_tls_v3_CertificateValidationContext_require_signed_certificate_timestamp(
certificate_validation_context_proto))) {
ValidationErrors::ScopedField field(
errors, ".require_signed_certificate_timestamp");
errors->AddError("feature unsupported");

@ -21,6 +21,7 @@
#include "envoy/extensions/transport_sockets/tls/v3/tls.upb.h"
#include "google/protobuf/any.upb.h"
#include "google/protobuf/duration.upb.h"
#include "google/protobuf/wrappers.upb.h"
#include "src/core/lib/gprpp/time.h"
#include "src/core/lib/gprpp/validation_errors.h"
@ -32,6 +33,12 @@ namespace grpc_core {
Duration ParseDuration(const google_protobuf_Duration* proto_duration,
ValidationErrors* errors);
inline bool ParseBoolValue(const google_protobuf_BoolValue* bool_value_proto,
bool default_value = false) {
if (bool_value_proto == nullptr) return default_value;
return google_protobuf_BoolValue_value(bool_value_proto);
}
CommonTlsContext CommonTlsContextParse(
const XdsResourceType::DecodeContext& context,
const envoy_extensions_transport_sockets_tls_v3_CommonTlsContext*

@ -140,6 +140,7 @@ absl::optional<EndpointAddresses> EndpointAddressesParse(
}
// endpoint
std::vector<grpc_resolved_address> addresses;
absl::string_view hostname;
{
ValidationErrors::ScopedField field(errors, ".endpoint");
const envoy_config_endpoint_v3_Endpoint* endpoint =
@ -169,13 +170,18 @@ absl::optional<EndpointAddresses> EndpointAddressesParse(
if (address.has_value()) addresses.push_back(*address);
}
}
hostname =
UpbStringToAbsl(envoy_config_endpoint_v3_Endpoint_hostname(endpoint));
}
if (addresses.empty()) return absl::nullopt;
// Convert to EndpointAddresses.
return EndpointAddresses(
addresses, ChannelArgs()
.Set(GRPC_ARG_ADDRESS_WEIGHT, weight)
.Set(GRPC_ARG_XDS_HEALTH_STATUS, status->status()));
auto args = ChannelArgs()
.Set(GRPC_ARG_ADDRESS_WEIGHT, weight)
.Set(GRPC_ARG_XDS_HEALTH_STATUS, status->status());
if (!hostname.empty()) {
args = args.Set(GRPC_ARG_ADDRESS_NAME, hostname);
}
return EndpointAddresses(addresses, args);
}
struct ParsedLocality {

@ -82,11 +82,9 @@ class ClientSideWeightedRoundRobinLbPolicyConfigFactory final
}
Json::Object config;
// enable_oob_load_report
auto* enable_oob_load_report =
envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_enable_oob_load_report(
resource);
if (enable_oob_load_report != nullptr &&
google_protobuf_BoolValue_value(enable_oob_load_report)) {
if (ParseBoolValue(
envoy_extensions_load_balancing_policies_client_side_weighted_round_robin_v3_ClientSideWeightedRoundRobin_enable_oob_load_report(
resource))) {
config["enableOobLoadReport"] = Json::FromBool(true);
}
// oob_reporting_period

@ -438,10 +438,9 @@ XdsListenerResource::DownstreamTlsContext DownstreamTlsContextParse(
"provider instance specified for validation");
}
}
auto* require_sni =
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni(
downstream_tls_context_proto);
if (require_sni != nullptr && google_protobuf_BoolValue_value(require_sni)) {
if (ParseBoolValue(
envoy_extensions_transport_sockets_tls_v3_DownstreamTlsContext_require_sni(
downstream_tls_context_proto))) {
ValidationErrors::ScopedField field(errors, ".require_sni");
errors->AddError("field unsupported");
}
@ -860,14 +859,10 @@ LdsResourceParseServer(const XdsResourceType::DecodeContext& context,
if (address.has_value()) tcp_listener.address = std::move(*address);
}
// use_original_dst
{
if (ParseBoolValue(
envoy_config_listener_v3_Listener_use_original_dst(listener))) {
ValidationErrors::ScopedField field(&errors, "use_original_dst");
const auto* use_original_dst =
envoy_config_listener_v3_Listener_use_original_dst(listener);
if (use_original_dst != nullptr &&
google_protobuf_BoolValue_value(use_original_dst)) {
errors.AddError("field not supported");
}
errors.AddError("field not supported");
}
// filter_chains
size_t num_filter_chains = 0;

@ -160,16 +160,20 @@ struct XdsRouteConfigResource : public XdsResourceType::ResourceData {
absl::variant<ClusterName, std::vector<ClusterWeight>,
ClusterSpecifierPluginName>
action;
// Storing the timeout duration from route action:
// RouteAction.max_stream_duration.grpc_timeout_header_max or
// RouteAction.max_stream_duration.max_stream_duration if the former is
// not set.
absl::optional<Duration> max_stream_duration;
bool auto_host_rewrite = false;
bool operator==(const RouteAction& other) const {
return hash_policies == other.hash_policies &&
retry_policy == other.retry_policy && action == other.action &&
max_stream_duration == other.max_stream_duration;
max_stream_duration == other.max_stream_duration &&
auto_host_rewrite == other.auto_host_rewrite;
}
std::string ToString() const;
};

@ -90,6 +90,15 @@ bool XdsRlsEnabled() {
return parse_succeeded && parsed_value;
}
// TODO(roth): Remove this once the feature passes interop tests.
bool XdsAuthorityRewriteEnabled() {
auto value = GetEnv("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
if (!value.has_value()) return false;
bool parsed_value;
bool parse_succeeded = gpr_parse_bool_value(value->c_str(), &parsed_value);
return parse_succeeded && parsed_value;
}
//
// XdsRouteConfigResourceParse()
//
@ -176,12 +185,9 @@ XdsRouteConfigResource::ClusterSpecifierPluginMap ClusterSpecifierPluginParse(
absl::optional<StringMatcher> RoutePathMatchParse(
const envoy_config_route_v3_RouteMatch* match, ValidationErrors* errors) {
bool case_sensitive = true;
auto* case_sensitive_ptr =
envoy_config_route_v3_RouteMatch_case_sensitive(match);
if (case_sensitive_ptr != nullptr) {
case_sensitive = google_protobuf_BoolValue_value(case_sensitive_ptr);
}
bool case_sensitive =
ParseBoolValue(envoy_config_route_v3_RouteMatch_case_sensitive(match),
/*default_value=*/true);
StringMatcher::Type type;
std::string match_string;
if (envoy_config_route_v3_RouteMatch_has_prefix(match)) {
@ -627,6 +633,13 @@ absl::optional<XdsRouteConfigResource::Route::RouteAction> RouteActionParse(
ValidationErrors::ScopedField field(errors, ".retry_policy");
route_action.retry_policy = RetryPolicyParse(context, retry_policy, errors);
}
// Host rewrite field.
if (XdsAuthorityRewriteEnabled() &&
DownCast<const GrpcXdsServer&>(context.server).TrustedXdsServer()) {
route_action.auto_host_rewrite =
ParseBoolValue(envoy_config_route_v3_RouteAction_auto_host_rewrite(
route_action_proto));
}
// Parse cluster specifier, which is one of several options.
if (envoy_config_route_v3_RouteAction_has_cluster(route_action_proto)) {
// Cluster name.

@ -37,6 +37,9 @@ namespace {
constexpr absl::string_view kServerFeatureIgnoreResourceDeletion =
"ignore_resource_deletion";
constexpr absl::string_view kServerFeatureTrustedXdsServer =
"trusted_xds_server";
} // namespace
bool GrpcXdsServer::IgnoreResourceDeletion() const {
@ -44,6 +47,11 @@ bool GrpcXdsServer::IgnoreResourceDeletion() const {
kServerFeatureIgnoreResourceDeletion)) != server_features_.end();
}
bool GrpcXdsServer::TrustedXdsServer() const {
return server_features_.find(std::string(kServerFeatureTrustedXdsServer)) !=
server_features_.end();
}
bool GrpcXdsServer::Equals(const XdsServer& other) const {
const auto& o = static_cast<const GrpcXdsServer&>(other);
return (server_uri_ == o.server_uri_ &&
@ -118,7 +126,8 @@ void GrpcXdsServer::JsonPostLoad(const Json& json, const JsonArgs& args,
const Json::Array& array = it->second.array();
for (const Json& feature_json : array) {
if (feature_json.type() == Json::Type::kString &&
(feature_json.string() == kServerFeatureIgnoreResourceDeletion)) {
(feature_json.string() == kServerFeatureIgnoreResourceDeletion ||
feature_json.string() == kServerFeatureTrustedXdsServer)) {
server_features_.insert(feature_json.string());
}
}

@ -36,6 +36,8 @@ class GrpcXdsServer final : public XdsBootstrap::XdsServer {
bool IgnoreResourceDeletion() const override;
bool TrustedXdsServer() const;
bool Equals(const XdsServer& other) const override;
std::string Key() const override;

@ -45,6 +45,12 @@ message Endpoint {
// and will be resolved via DNS.
core.v3.Address address = 1;
// The hostname associated with this endpoint. This hostname is not used for routing or address
// resolution. If provided, it will be associated with the endpoint, and can be used for features
// that require a hostname, like
// :ref:`auto_host_rewrite <envoy_v3_api_field_config.route.v3.RouteAction.auto_host_rewrite>`.
string hostname = 3;
// An ordered list of addresses that together with `address` comprise the
// list of addresses for an endpoint. The address given in the `address` is
// prepended to this list. It is assumed that the list must already be

@ -356,6 +356,30 @@ message RouteAction {
// Specifies the maximum stream duration for this route.
MaxStreamDuration max_stream_duration = 36;
oneof host_rewrite_specifier {
// Indicates that during forwarding, the host header will be swapped with
// the hostname of the upstream host chosen by the cluster manager. This
// option is applicable only when the destination cluster for a route is of
// type ``strict_dns`` or ``logical_dns``,
// or when :ref:`hostname <envoy_v3_api_field_config.endpoint.v3.Endpoint.hostname>`
// field is not empty. Setting this to true with other cluster types
// has no effect. Using this option will append the
// :ref:`config_http_conn_man_headers_x-forwarded-host` header if
// :ref:`append_x_forwarded_host <envoy_v3_api_field_config.route.v3.RouteAction.append_x_forwarded_host>`
// is set.
google.protobuf.BoolValue auto_host_rewrite = 7;
}
// If set, then a host rewrite action (one of
// :ref:`host_rewrite_literal <envoy_v3_api_field_config.route.v3.RouteAction.host_rewrite_literal>`,
// :ref:`auto_host_rewrite <envoy_v3_api_field_config.route.v3.RouteAction.auto_host_rewrite>`,
// :ref:`host_rewrite_header <envoy_v3_api_field_config.route.v3.RouteAction.host_rewrite_header>`, or
// :ref:`host_rewrite_path_regex <envoy_v3_api_field_config.route.v3.RouteAction.host_rewrite_path_regex>`)
// causes the original value of the host header, if any, to be appended to the
// :ref:`config_http_conn_man_headers_x-forwarded-host` HTTP header if it is different to the last value appended.
// This can be disabled by setting the runtime guard ``envoy_reloadable_features_append_xfh_idempotent`` to false.
bool append_x_forwarded_host = 38;
}
// .. attention::

@ -719,6 +719,102 @@ class QueueOnceLoadBalancingPolicyFactory : public LoadBalancingPolicyFactory {
}
};
//
// AuthorityOverrideLbPolicy: A load balancing policy that delegates to
// pick_first but adds an authority override on completed picks.
//
constexpr char kAuthorityOverridePolicyName[] = "authority_override_lb";
class AuthorityOverrideLoadBalancingPolicy
: public ForwardingLoadBalancingPolicy {
public:
explicit AuthorityOverrideLoadBalancingPolicy(Args args)
: ForwardingLoadBalancingPolicy(
std::make_unique<Helper>(
RefCountedPtr<AuthorityOverrideLoadBalancingPolicy>(this)),
std::move(args), "pick_first",
/*initial_refcount=*/2) {}
absl::string_view name() const override {
return kAuthorityOverridePolicyName;
}
absl::Status UpdateLocked(UpdateArgs args) override {
authority_override_ =
grpc_event_engine::experimental::Slice::FromCopiedString(
args.args.GetString(GRPC_ARG_TEST_LB_AUTHORITY_OVERRIDE)
.value_or(""));
return ForwardingLoadBalancingPolicy::UpdateLocked(std::move(args));
}
private:
class Picker : public SubchannelPicker {
public:
Picker(RefCountedPtr<SubchannelPicker> picker,
grpc_event_engine::experimental::Slice authority_override)
: picker_(std::move(picker)),
authority_override_(std::move(authority_override)) {}
PickResult Pick(PickArgs args) override {
auto pick_result = picker_->Pick(args);
auto* complete_pick =
absl::get_if<PickResult::Complete>(&pick_result.result);
if (complete_pick != nullptr) {
complete_pick->authority_override = authority_override_.Ref();
}
return pick_result;
}
private:
RefCountedPtr<SubchannelPicker> picker_;
grpc_event_engine::experimental::Slice authority_override_;
};
class Helper : public ParentOwningDelegatingChannelControlHelper<
AuthorityOverrideLoadBalancingPolicy> {
public:
explicit Helper(RefCountedPtr<AuthorityOverrideLoadBalancingPolicy> parent)
: ParentOwningDelegatingChannelControlHelper(std::move(parent)) {}
void UpdateState(grpc_connectivity_state state, const absl::Status& status,
RefCountedPtr<SubchannelPicker> picker) override {
parent_helper()->UpdateState(
state, status,
MakeRefCounted<Picker>(std::move(picker),
parent()->authority_override_.Ref()));
}
};
grpc_event_engine::experimental::Slice authority_override_;
};
class AuthorityOverrideLbConfig : public LoadBalancingPolicy::Config {
public:
absl::string_view name() const override {
return kAuthorityOverridePolicyName;
}
};
class AuthorityOverrideLoadBalancingPolicyFactory
: public LoadBalancingPolicyFactory {
public:
OrphanablePtr<LoadBalancingPolicy> CreateLoadBalancingPolicy(
LoadBalancingPolicy::Args args) const override {
return MakeOrphanable<AuthorityOverrideLoadBalancingPolicy>(
std::move(args));
}
absl::string_view name() const override {
return kAuthorityOverridePolicyName;
}
absl::StatusOr<RefCountedPtr<LoadBalancingPolicy::Config>>
ParseLoadBalancingConfig(const Json& /*json*/) const override {
return MakeRefCounted<AuthorityOverrideLbConfig>();
}
};
} // namespace
void RegisterTestPickArgsLoadBalancingPolicy(
@ -766,4 +862,10 @@ void RegisterQueueOnceLoadBalancingPolicy(CoreConfiguration::Builder* builder) {
std::make_unique<QueueOnceLoadBalancingPolicyFactory>());
}
void RegisterAuthorityOverrideLoadBalancingPolicy(
CoreConfiguration::Builder* builder) {
builder->lb_policy_registry()->RegisterLoadBalancingPolicyFactory(
std::make_unique<AuthorityOverrideLoadBalancingPolicyFactory>());
}
} // namespace grpc_core

@ -94,6 +94,13 @@ void RegisterFailLoadBalancingPolicy(CoreConfiguration::Builder* builder,
// Registers an LB policy called "queue_once" that queues at least one pick, and
// then delegates to PickFirst.
void RegisterQueueOnceLoadBalancingPolicy(CoreConfiguration::Builder* builder);
// Registers an LB policy called "authority_override_lb" that, if the following
// channel arg is set, adds an authority override to complete picks.
#define GRPC_ARG_TEST_LB_AUTHORITY_OVERRIDE "grpc.test.lb_authority_override"
void RegisterAuthorityOverrideLoadBalancingPolicy(
CoreConfiguration::Builder* builder);
} // namespace grpc_core
#endif // GRPC_TEST_CORE_TEST_UTIL_TEST_LB_POLICIES_H

@ -57,7 +57,8 @@ namespace grpc_core {
namespace testing {
namespace {
MATCHER_P2(EqXdsServer, name, creds_config_type, "equals XdsServer") {
MATCHER_P4(EqXdsServer, name, creds_config_type, ignore_resource_deletion,
trusted_xds_server, "equals XdsServer") {
auto* server = static_cast<const GrpcXdsServer*>(arg);
if (!::testing::ExplainMatchResult(::testing::Ne(nullptr), server,
result_listener)) {
@ -65,6 +66,11 @@ MATCHER_P2(EqXdsServer, name, creds_config_type, "equals XdsServer") {
}
bool ok = ::testing::ExplainMatchResult(name, server->server_uri(),
result_listener);
ok |=
::testing::ExplainMatchResult(server->IgnoreResourceDeletion(),
ignore_resource_deletion, result_listener);
ok |= ::testing::ExplainMatchResult(server->TrustedXdsServer(),
trusted_xds_server, result_listener);
auto creds_config = server->channel_creds_config();
if (!::testing::ExplainMatchResult(::testing::Ne(nullptr), creds_config,
result_listener)) {
@ -113,7 +119,10 @@ TEST(XdsBootstrapTest, Basic) {
" \"type\": \"fake\""
" }"
" ],"
" \"server_features\": [\"xds_v3\"]"
" \"server_features\": ["
" \"xds_v3\","
" \"ignore_resource_deletion\""
" ]"
" },"
" {"
" \"server_uri\": \"fake:///xds_server2\","
@ -138,7 +147,10 @@ TEST(XdsBootstrapTest, Basic) {
" \"type\": \"fake\""
" }"
" ],"
" \"server_features\": [\"xds_v3\"]"
" \"server_features\": ["
" \"xds_v3\","
" \"trusted_xds_server\""
" ]"
" }"
" ]"
" }"
@ -164,8 +176,8 @@ TEST(XdsBootstrapTest, Basic) {
auto bootstrap_or = GrpcXdsBootstrap::Create(json_str);
ASSERT_TRUE(bootstrap_or.ok()) << bootstrap_or.status();
auto bootstrap = std::move(*bootstrap_or);
EXPECT_THAT(bootstrap->servers(),
::testing::ElementsAre(EqXdsServer("fake:///lb1", "fake")));
EXPECT_THAT(bootstrap->servers(), ::testing::ElementsAre(EqXdsServer(
"fake:///lb1", "fake", false, false)));
EXPECT_EQ(bootstrap->authorities().size(), 2);
auto* authority = static_cast<const GrpcXdsBootstrap::GrpcAuthority*>(
bootstrap->LookupAuthority("xds.example.com"));
@ -173,16 +185,18 @@ TEST(XdsBootstrapTest, Basic) {
EXPECT_EQ(authority->client_listener_resource_name_template(),
"xdstp://xds.example.com/envoy.config.listener.v3.Listener/grpc/"
"server/%s");
EXPECT_THAT(authority->servers(), ::testing::ElementsAre(EqXdsServer(
"fake:///xds_server", "fake")));
EXPECT_THAT(authority->servers(),
::testing::ElementsAre(
EqXdsServer("fake:///xds_server", "fake", true, false)));
authority = static_cast<const GrpcXdsBootstrap::GrpcAuthority*>(
bootstrap->LookupAuthority("xds.example2.com"));
ASSERT_NE(authority, nullptr);
EXPECT_EQ(authority->client_listener_resource_name_template(),
"xdstp://xds.example2.com/envoy.config.listener.v3.Listener/grpc/"
"server/%s");
EXPECT_THAT(authority->servers(), ::testing::ElementsAre(EqXdsServer(
"fake:///xds_server3", "fake")));
EXPECT_THAT(authority->servers(),
::testing::ElementsAre(
EqXdsServer("fake:///xds_server3", "fake", false, true)));
ASSERT_NE(bootstrap->node(), nullptr);
EXPECT_EQ(bootstrap->node()->id(), "foo");
EXPECT_EQ(bootstrap->node()->cluster(), "bar");
@ -218,8 +232,8 @@ TEST(XdsBootstrapTest, ValidWithoutNode) {
auto bootstrap_or = GrpcXdsBootstrap::Create(json_str);
ASSERT_TRUE(bootstrap_or.ok()) << bootstrap_or.status();
auto bootstrap = std::move(*bootstrap_or);
EXPECT_THAT(bootstrap->servers(),
::testing::ElementsAre(EqXdsServer("fake:///lb", "fake")));
EXPECT_THAT(bootstrap->servers(), ::testing::ElementsAre(EqXdsServer(
"fake:///lb", "fake", false, false)));
EXPECT_EQ(bootstrap->node(), nullptr);
}
@ -237,7 +251,8 @@ TEST(XdsBootstrapTest, InsecureCreds) {
ASSERT_TRUE(bootstrap_or.ok()) << bootstrap_or.status();
auto bootstrap = std::move(*bootstrap_or);
EXPECT_THAT(bootstrap->servers(),
::testing::ElementsAre(EqXdsServer("fake:///lb", "insecure")));
::testing::ElementsAre(
EqXdsServer("fake:///lb", "insecure", false, false)));
EXPECT_EQ(bootstrap->node(), nullptr);
}
@ -270,8 +285,9 @@ TEST(XdsBootstrapTest, GoogleDefaultCreds) {
auto bootstrap_or = GrpcXdsBootstrap::Create(json_str);
ASSERT_TRUE(bootstrap_or.ok()) << bootstrap_or.status();
auto bootstrap = std::move(*bootstrap_or);
EXPECT_THAT(bootstrap->servers(), ::testing::ElementsAre(EqXdsServer(
"fake:///lb", "google_default")));
EXPECT_THAT(bootstrap->servers(),
::testing::ElementsAre(
EqXdsServer("fake:///lb", "google_default", false, false)));
EXPECT_EQ(bootstrap->node(), nullptr);
}
@ -712,7 +728,11 @@ TEST(XdsBootstrapTest, XdsServerToJsonAndParse) {
" \"ignore\": 0"
" }"
" ],"
" \"ignore\": 0"
" \"ignore\": 0,"
" \"server_features\": ["
" \"ignore_resource_deletion\","
" \"trusted_xds_server\""
" ]"
" }";
auto json = JsonParse(json_str);
ASSERT_TRUE(json.ok()) << json.status();
@ -798,16 +818,17 @@ TEST(XdsBootstrapTest, NoXdsServersEnvVar) {
auto bootstrap_or = GrpcXdsBootstrap::Create(json_str);
ASSERT_TRUE(bootstrap_or.ok()) << bootstrap_or.status();
auto bootstrap = std::move(*bootstrap_or);
EXPECT_THAT(bootstrap->servers(),
::testing::ElementsAre(EqXdsServer("fake:///lb1", "fake"),
EqXdsServer("fake:///lb2", "fake")));
EXPECT_THAT(
bootstrap->servers(),
::testing::ElementsAre(EqXdsServer("fake:///lb1", "fake", false, false),
EqXdsServer("fake:///lb2", "fake", false, false)));
auto* authority = static_cast<const GrpcXdsBootstrap::GrpcAuthority*>(
bootstrap->LookupAuthority("xds.example.com"));
ASSERT_NE(authority, nullptr);
EXPECT_THAT(
authority->servers(),
::testing::ElementsAre(EqXdsServer("fake:///xds_server", "fake"),
EqXdsServer("fake:///xds_server2", "fake")));
EXPECT_THAT(authority->servers(),
::testing::ElementsAre(
EqXdsServer("fake:///xds_server", "fake", false, false),
EqXdsServer("fake:///xds_server2", "fake", false, false)));
}
} // namespace

@ -732,6 +732,53 @@ TEST_F(XdsEndpointTest, IgnoresMultipleAddressesPerEndpointWhenNotEnabled) {
EXPECT_EQ(resource.drop_config, nullptr);
}
TEST_F(XdsEndpointTest, EndpointHostname) {
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()->mutable_endpoint();
endpoint->set_hostname("server.example.com");
auto* socket_address = 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);
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<const XdsEndpointResource&>(**decode_result.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()
.Set(GRPC_ARG_ADDRESS_WEIGHT, 1)
.Set(GRPC_ARG_XDS_HEALTH_STATUS,
XdsHealthStatus::HealthStatus::kUnknown)
.Set(GRPC_ARG_ADDRESS_NAME, "server.example.com"));
EXPECT_EQ(resource.drop_config, nullptr);
}
TEST_F(XdsEndpointTest, MissingEndpoint) {
ClusterLoadAssignment cla;
cla.set_cluster_name("foo");

@ -79,26 +79,28 @@ namespace {
class XdsRouteConfigTest : public ::testing::Test {
protected:
XdsRouteConfigTest()
: xds_client_(MakeXdsClient()),
explicit XdsRouteConfigTest(bool trusted_xds_server = false)
: xds_client_(MakeXdsClient(trusted_xds_server)),
decode_context_{xds_client_.get(),
*xds_client_->bootstrap().servers().front(),
&xds_route_config_resource_type_test_trace,
upb_def_pool_.ptr(), upb_arena_.ptr()} {}
static RefCountedPtr<XdsClient> MakeXdsClient() {
grpc_error_handle error;
static RefCountedPtr<XdsClient> MakeXdsClient(bool trusted_xds_server) {
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"
"}");
absl::StrCat("{\n"
" \"xds_servers\": [\n"
" {\n"
" \"server_uri\": \"xds.example.com\",\n"
" \"server_features\": [\n",
(trusted_xds_server ? "\"trusted_xds_server\"" : ""),
" ],\n"
" \"channel_creds\": [\n"
" {\"type\": \"google_default\"}\n"
" ]\n"
" }\n"
" ]\n"
"}"));
if (!bootstrap.ok()) {
Crash(absl::StrFormat("Error parsing bootstrap: %s",
bootstrap.status().ToString().c_str()));
@ -1585,6 +1587,108 @@ TEST_F(HashPolicyTest, InvalidPolicies) {
<< decode_result.resource.status();
}
//
// Authority rewrite tests
//
using AuthorityRewriteDisabledInBootstrapTest = XdsRouteConfigTest;
TEST_F(AuthorityRewriteDisabledInBootstrapTest, AutoHostRewriteIgnored) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
RouteConfiguration route_config;
route_config.set_name("foo");
auto* vhost = route_config.add_virtual_hosts();
vhost->add_domains("*");
auto* route_proto = vhost->add_routes();
route_proto->mutable_match()->set_prefix("");
auto* route_action = route_proto->mutable_route();
route_action->set_cluster("cluster1");
route_action->mutable_auto_host_rewrite()->set_value(true);
std::string serialized_resource;
ASSERT_TRUE(route_config.SerializeToString(&serialized_resource));
auto* resource_type = XdsRouteConfigResourceType::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<const XdsRouteConfigResource&>(**decode_result.resource);
ASSERT_EQ(resource.virtual_hosts.size(), 1UL);
ASSERT_EQ(resource.virtual_hosts[0].routes.size(), 1UL);
auto& route = resource.virtual_hosts[0].routes[0];
auto* action =
absl::get_if<XdsRouteConfigResource::Route::RouteAction>(&route.action);
ASSERT_NE(action, nullptr);
EXPECT_FALSE(action->auto_host_rewrite);
}
class AuthorityRewriteEnabledInBootstrapTest : public XdsRouteConfigTest {
protected:
AuthorityRewriteEnabledInBootstrapTest()
: XdsRouteConfigTest(/*trusted_xds_server=*/true) {}
};
TEST_F(AuthorityRewriteEnabledInBootstrapTest, AutoHostRewriteTrue) {
ScopedExperimentalEnvVar env_var("GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
RouteConfiguration route_config;
route_config.set_name("foo");
auto* vhost = route_config.add_virtual_hosts();
vhost->add_domains("*");
auto* route_proto = vhost->add_routes();
route_proto->mutable_match()->set_prefix("");
auto* route_action = route_proto->mutable_route();
route_action->set_cluster("cluster1");
route_action->mutable_auto_host_rewrite()->set_value(true);
std::string serialized_resource;
ASSERT_TRUE(route_config.SerializeToString(&serialized_resource));
auto* resource_type = XdsRouteConfigResourceType::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<const XdsRouteConfigResource&>(**decode_result.resource);
ASSERT_EQ(resource.virtual_hosts.size(), 1UL);
ASSERT_EQ(resource.virtual_hosts[0].routes.size(), 1UL);
auto& route = resource.virtual_hosts[0].routes[0];
auto* action =
absl::get_if<XdsRouteConfigResource::Route::RouteAction>(&route.action);
ASSERT_NE(action, nullptr);
EXPECT_TRUE(action->auto_host_rewrite);
}
TEST_F(AuthorityRewriteEnabledInBootstrapTest,
AutoHostRewriteIgnoredWithoutEnvVar) {
RouteConfiguration route_config;
route_config.set_name("foo");
auto* vhost = route_config.add_virtual_hosts();
vhost->add_domains("*");
auto* route_proto = vhost->add_routes();
route_proto->mutable_match()->set_prefix("");
auto* route_action = route_proto->mutable_route();
route_action->set_cluster("cluster1");
route_action->mutable_auto_host_rewrite()->set_value(true);
std::string serialized_resource;
ASSERT_TRUE(route_config.SerializeToString(&serialized_resource));
auto* resource_type = XdsRouteConfigResourceType::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<const XdsRouteConfigResource&>(**decode_result.resource);
ASSERT_EQ(resource.virtual_hosts.size(), 1UL);
ASSERT_EQ(resource.virtual_hosts[0].routes.size(), 1UL);
auto& route = resource.virtual_hosts[0].routes[0];
auto* action =
absl::get_if<XdsRouteConfigResource::Route::RouteAction>(&route.action);
ASSERT_NE(action, nullptr);
EXPECT_FALSE(action->auto_host_rewrite);
}
//
// WeightedCluster tests
//

@ -260,12 +260,10 @@ class FakeResolverResponseGeneratorWrapper {
response_generator_;
};
constexpr absl::string_view kDefaultAuthority = "default.example.com";
class ClientLbEnd2endTest : public ::testing::Test {
protected:
ClientLbEnd2endTest()
: server_host_("localhost"),
creds_(std::make_shared<FakeTransportSecurityChannelCredentials>()) {}
void SetUp() override { grpc_init(); }
void TearDown() override {
@ -273,25 +271,25 @@ class ClientLbEnd2endTest : public ::testing::Test {
servers_[i]->Shutdown();
}
servers_.clear();
creds_.reset();
grpc_shutdown();
}
void CreateServers(size_t num_servers,
std::vector<int> ports = std::vector<int>()) {
void CreateServers(
size_t num_servers, std::vector<int> ports = {},
std::shared_ptr<ServerCredentials> server_creds = nullptr) {
servers_.clear();
for (size_t i = 0; i < num_servers; ++i) {
int port = 0;
if (ports.size() == num_servers) port = ports[i];
servers_.emplace_back(new ServerData(port));
servers_.emplace_back(new ServerData(port, server_creds));
}
}
void StartServer(size_t index) { servers_[index]->Start(server_host_); }
void StartServer(size_t index) { servers_[index]->Start(); }
void StartServers(size_t num_servers,
std::vector<int> ports = std::vector<int>()) {
CreateServers(num_servers, std::move(ports));
void StartServers(size_t num_servers, std::vector<int> ports = {},
std::shared_ptr<ServerCredentials> server_creds = nullptr) {
CreateServers(num_servers, std::move(ports), std::move(server_creds));
for (size_t i = 0; i < num_servers; ++i) {
StartServer(i);
}
@ -315,19 +313,26 @@ class ClientLbEnd2endTest : public ::testing::Test {
std::shared_ptr<Channel> BuildChannel(
const std::string& lb_policy_name,
const FakeResolverResponseGeneratorWrapper& response_generator,
ChannelArguments args = ChannelArguments()) {
ChannelArguments args = ChannelArguments(),
std::shared_ptr<ChannelCredentials> channel_creds = nullptr) {
if (!lb_policy_name.empty()) {
args.SetLoadBalancingPolicyName(lb_policy_name);
} // else, default to pick first
args.SetPointer(GRPC_ARG_FAKE_RESOLVER_RESPONSE_GENERATOR,
response_generator.Get());
return grpc::CreateCustomChannel("fake:default.example.com", creds_, args);
if (channel_creds == nullptr) {
channel_creds =
std::make_shared<FakeTransportSecurityChannelCredentials>();
}
return grpc::CreateCustomChannel(absl::StrCat("fake:", kDefaultAuthority),
channel_creds, args);
}
Status SendRpc(
const std::unique_ptr<grpc::testing::EchoTestService::Stub>& stub,
EchoResponse* response = nullptr, int timeout_ms = 1000,
bool wait_for_ready = false, EchoRequest* request = nullptr) {
bool wait_for_ready = false, EchoRequest* request = nullptr,
std::string authority_override = "") {
EchoResponse local_response;
if (response == nullptr) response = &local_response;
EchoRequest local_request;
@ -336,6 +341,7 @@ class ClientLbEnd2endTest : public ::testing::Test {
request->mutable_param()->set_echo_metadata(true);
ClientContext context;
context.set_deadline(grpc_timeout_milliseconds_to_deadline(timeout_ms));
if (!authority_override.empty()) context.set_authority(authority_override);
if (wait_for_ready) context.set_wait_for_ready(true);
context.AddMetadata("foo", "1");
context.AddMetadata("bar", "2");
@ -403,6 +409,7 @@ class ClientLbEnd2endTest : public ::testing::Test {
struct ServerData {
const int port_;
const std::shared_ptr<ServerCredentials> server_creds_;
std::unique_ptr<Server> server_;
MyTestServiceImpl service_;
std::unique_ptr<experimental::ServerMetricRecorder> server_metric_recorder_;
@ -416,20 +423,27 @@ class ClientLbEnd2endTest : public ::testing::Test {
bool server_ready_ ABSL_GUARDED_BY(mu_) = false;
bool started_ ABSL_GUARDED_BY(mu_) = false;
explicit ServerData(int port = 0)
explicit ServerData(
int port = 0, std::shared_ptr<ServerCredentials> server_creds = nullptr)
: port_(port > 0 ? port : grpc_pick_unused_port_or_die()),
server_creds_(
server_creds == nullptr
? std::shared_ptr<
ServerCredentials>(new SecureServerCredentials(
grpc_fake_transport_security_server_credentials_create()))
: std::move(server_creds)),
server_metric_recorder_(experimental::ServerMetricRecorder::Create()),
orca_service_(
server_metric_recorder_.get(),
experimental::OrcaService::Options().set_min_report_duration(
absl::Seconds(0.1))) {}
void Start(const std::string& server_host) {
void Start() {
LOG(INFO) << "starting server on port " << port_;
grpc_core::MutexLock lock(&mu_);
started_ = true;
thread_ = std::make_unique<std::thread>(
std::bind(&ServerData::Serve, this, server_host));
thread_ =
std::make_unique<std::thread>(std::bind(&ServerData::Serve, this));
while (!server_ready_) {
cond_.Wait(&mu_);
}
@ -437,13 +451,10 @@ class ClientLbEnd2endTest : public ::testing::Test {
LOG(INFO) << "server startup complete";
}
void Serve(const std::string& server_host) {
std::ostringstream server_address;
server_address << server_host << ":" << port_;
void Serve() {
ServerBuilder builder;
std::shared_ptr<ServerCredentials> creds(new SecureServerCredentials(
grpc_fake_transport_security_server_credentials_create()));
builder.AddListeningPort(server_address.str(), std::move(creds));
builder.AddListeningPort(absl::StrCat("localhost:", port_),
server_creds_);
builder.RegisterService(&service_);
builder.RegisterService(&orca_service_);
if (enable_noop_health_check_service_) {
@ -599,9 +610,7 @@ class ClientLbEnd2endTest : public ::testing::Test {
"( \\([0-9]+\\))?");
}
const std::string server_host_;
std::vector<std::unique_ptr<ServerData>> servers_;
std::shared_ptr<ChannelCredentials> creds_;
};
TEST_F(ClientLbEnd2endTest, ChannelStateConnectingWhenResolving) {
@ -660,13 +669,31 @@ TEST_F(ClientLbEnd2endTest, ChannelIdleness) {
EXPECT_EQ(channel->GetState(false), GRPC_CHANNEL_READY);
}
TEST_F(ClientLbEnd2endTest, AuthorityOverrideOnChannel) {
//
// authority override tests
//
class AuthorityOverrideTest : public ClientLbEnd2endTest {
protected:
static void SetUpTestSuite() {
grpc_core::CoreConfiguration::Reset();
grpc_core::CoreConfiguration::RegisterBuilder(
[](grpc_core::CoreConfiguration::Builder* builder) {
grpc_core::RegisterAuthorityOverrideLoadBalancingPolicy(builder);
});
grpc_init();
}
static void TearDownTestSuite() {
grpc_shutdown();
grpc_core::CoreConfiguration::Reset();
}
};
TEST_F(AuthorityOverrideTest, NoOverride) {
StartServers(1);
// Set authority via channel arg.
FakeResolverResponseGeneratorWrapper response_generator;
ChannelArguments args;
args.SetString(GRPC_ARG_DEFAULT_AUTHORITY, "foo.example.com");
auto channel = BuildChannel("", response_generator, args);
auto channel = BuildChannel("", response_generator);
auto stub = BuildStub(channel);
response_generator.SetNextResolution(GetServersPorts());
// Send an RPC.
@ -678,10 +705,10 @@ TEST_F(ClientLbEnd2endTest, AuthorityOverrideOnChannel) {
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("foo.example.com", response.param().host());
EXPECT_EQ(kDefaultAuthority, response.param().host());
}
TEST_F(ClientLbEnd2endTest, AuthorityOverrideFromResolver) {
TEST_F(AuthorityOverrideTest, OverrideFromResolver) {
StartServers(1);
FakeResolverResponseGeneratorWrapper response_generator;
auto channel = BuildChannel("", response_generator);
@ -691,7 +718,51 @@ TEST_F(ClientLbEnd2endTest, AuthorityOverrideFromResolver) {
response_generator.SetNextResolution(
GetServersPorts(), /*service_config_json=*/nullptr,
grpc_core::ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY,
"foo.example.com"));
"from-resolver.example.com"));
// Send an RPC.
EchoRequest request;
request.mutable_param()->set_echo_host_from_authority_header(true);
EchoResponse response;
Status status = SendRpc(stub, &response, /*timeout_ms=*/1000,
/*wait_for_ready=*/false, &request);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("from-resolver.example.com", response.param().host());
}
TEST_F(AuthorityOverrideTest, OverrideOnChannel) {
StartServers(1);
// Set authority via channel arg.
FakeResolverResponseGeneratorWrapper response_generator;
ChannelArguments args;
args.SetString(GRPC_ARG_DEFAULT_AUTHORITY, "from-channel.example.com");
auto channel = BuildChannel("", response_generator, args);
auto stub = BuildStub(channel);
response_generator.SetNextResolution(GetServersPorts());
// Send an RPC.
EchoRequest request;
request.mutable_param()->set_echo_host_from_authority_header(true);
EchoResponse response;
Status status = SendRpc(stub, &response, /*timeout_ms=*/1000,
/*wait_for_ready=*/false, &request);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("from-channel.example.com", response.param().host());
}
TEST_F(AuthorityOverrideTest, OverrideFromLbPolicy) {
// We use InsecureCreds here to avoid the authority check in the fake
// security connector.
StartServers(1, {}, InsecureServerCredentials());
FakeResolverResponseGeneratorWrapper response_generator;
ChannelArguments args;
args.SetString(GRPC_ARG_TEST_LB_AUTHORITY_OVERRIDE, "from-lb.example.com");
auto channel = BuildChannel("authority_override_lb", response_generator, args,
InsecureChannelCredentials());
auto stub = BuildStub(channel);
response_generator.SetNextResolution(GetServersPorts());
// Send an RPC.
EchoRequest request;
request.mutable_param()->set_echo_host_from_authority_header(true);
@ -701,15 +772,38 @@ TEST_F(ClientLbEnd2endTest, AuthorityOverrideFromResolver) {
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("foo.example.com", response.param().host());
EXPECT_EQ("from-lb.example.com", response.param().host());
}
TEST_F(ClientLbEnd2endTest, AuthorityOverridePrecedence) {
TEST_F(AuthorityOverrideTest, PerRpcOverride) {
// We use InsecureCreds here to avoid the authority check in the fake
// security connector.
StartServers(1, {}, InsecureServerCredentials());
FakeResolverResponseGeneratorWrapper response_generator;
auto channel = BuildChannel("", response_generator, ChannelArguments(),
InsecureChannelCredentials());
auto stub = BuildStub(channel);
response_generator.SetNextResolution(GetServersPorts());
// Send an RPC.
EchoRequest request;
request.mutable_param()->set_echo_host_from_authority_header(true);
EchoResponse response;
Status status = SendRpc(stub, &response, /*timeout_ms=*/1000,
/*wait_for_ready=*/false, &request,
/*authority_override=*/"per-rpc.example.com");
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("per-rpc.example.com", response.param().host());
}
TEST_F(AuthorityOverrideTest,
ChannelOverrideTakesPrecedenceOverResolverOverride) {
StartServers(1);
// Set authority via channel arg.
FakeResolverResponseGeneratorWrapper response_generator;
ChannelArguments args;
args.SetString(GRPC_ARG_DEFAULT_AUTHORITY, "foo.example.com");
args.SetString(GRPC_ARG_DEFAULT_AUTHORITY, "from-channel.example.com");
auto channel = BuildChannel("", response_generator, args);
auto stub = BuildStub(channel);
// Inject resolver result that sets the per-address authority to a
@ -717,7 +811,32 @@ TEST_F(ClientLbEnd2endTest, AuthorityOverridePrecedence) {
response_generator.SetNextResolution(
GetServersPorts(), /*service_config_json=*/nullptr,
grpc_core::ChannelArgs().Set(GRPC_ARG_DEFAULT_AUTHORITY,
"bar.example.com"));
"from-resolver.example.com"));
// Send an RPC.
EchoRequest request;
request.mutable_param()->set_echo_host_from_authority_header(true);
EchoResponse response;
Status status = SendRpc(stub, &response, /*timeout_ms=*/1000,
/*wait_for_ready=*/false, &request);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("from-channel.example.com", response.param().host());
}
TEST_F(AuthorityOverrideTest,
LbPolicyOverrideTakesPrecedenceOverChannelOverride) {
// We use InsecureCreds here to avoid the authority check in the fake
// security connector.
StartServers(1, {}, InsecureServerCredentials());
FakeResolverResponseGeneratorWrapper response_generator;
ChannelArguments args;
args.SetString(GRPC_ARG_DEFAULT_AUTHORITY, "from-channel.example.com");
args.SetString(GRPC_ARG_TEST_LB_AUTHORITY_OVERRIDE, "from-lb.example.com");
auto channel = BuildChannel("authority_override_lb", response_generator, args,
InsecureChannelCredentials());
auto stub = BuildStub(channel);
response_generator.SetNextResolution(GetServersPorts());
// Send an RPC.
EchoRequest request;
request.mutable_param()->set_echo_host_from_authority_header(true);
@ -727,7 +846,32 @@ TEST_F(ClientLbEnd2endTest, AuthorityOverridePrecedence) {
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("foo.example.com", response.param().host());
EXPECT_EQ("from-lb.example.com", response.param().host());
}
TEST_F(AuthorityOverrideTest,
PerRpcOverrideTakesPrecedenceOverLbPolicyOverride) {
// We use InsecureCreds here to avoid the authority check in the fake
// security connector.
StartServers(1, {}, InsecureServerCredentials());
FakeResolverResponseGeneratorWrapper response_generator;
ChannelArguments args;
args.SetString(GRPC_ARG_TEST_LB_AUTHORITY_OVERRIDE, "from-lb.example.com");
auto channel = BuildChannel("authority_override_lb", response_generator, args,
InsecureChannelCredentials());
auto stub = BuildStub(channel);
response_generator.SetNextResolution(GetServersPorts());
// Send an RPC.
EchoRequest request;
request.mutable_param()->set_echo_host_from_authority_header(true);
EchoResponse response;
Status status = SendRpc(stub, &response, /*timeout_ms=*/1000,
/*wait_for_ready=*/false, &request,
/*authority_override=*/"per-rpc.example.com");
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
// Check that the right authority was seen by the server.
EXPECT_EQ("per-rpc.example.com", response.param().host());
}
//
@ -1058,12 +1202,18 @@ TEST_F(PickFirstTest, GlobalSubchannelPool) {
StartServers(kNumServers);
std::vector<int> ports = GetServersPorts();
// Create two channels that (by default) use the global subchannel pool.
// Use the same channel creds for both, so that they have the same
// subchannel keys.
auto channel_creds =
std::make_shared<FakeTransportSecurityChannelCredentials>();
FakeResolverResponseGeneratorWrapper response_generator1;
auto channel1 = BuildChannel("pick_first", response_generator1);
auto channel1 = BuildChannel("pick_first", response_generator1,
ChannelArguments(), channel_creds);
auto stub1 = BuildStub(channel1);
response_generator1.SetNextResolution(ports);
FakeResolverResponseGeneratorWrapper response_generator2;
auto channel2 = BuildChannel("pick_first", response_generator2);
auto channel2 = BuildChannel("pick_first", response_generator2,
ChannelArguments(), channel_creds);
auto stub2 = BuildStub(channel2);
response_generator2.SetNextResolution(ports);
WaitForServer(DEBUG_LOCATION, stub1, 0);
@ -1984,20 +2134,26 @@ TEST_F(RoundRobinTest, WithHealthCheckingInhibitPerChannel) {
// Start server.
const int kNumServers = 1;
StartServers(kNumServers);
// Use the same channel creds for both channels, so that they have the same
// subchannel keys.
auto channel_creds =
std::make_shared<FakeTransportSecurityChannelCredentials>();
// Create a channel with health-checking enabled.
ChannelArguments args;
args.SetServiceConfigJSON(
"{\"healthCheckConfig\": "
"{\"serviceName\": \"health_check_service_name\"}}");
FakeResolverResponseGeneratorWrapper response_generator1;
auto channel1 = BuildChannel("round_robin", response_generator1, args);
auto channel1 =
BuildChannel("round_robin", response_generator1, args, channel_creds);
auto stub1 = BuildStub(channel1);
std::vector<int> ports = GetServersPorts();
response_generator1.SetNextResolution(ports);
// Create a channel with health checking enabled but inhibited.
args.SetInt(GRPC_ARG_INHIBIT_HEALTH_CHECKING, 1);
FakeResolverResponseGeneratorWrapper response_generator2;
auto channel2 = BuildChannel("round_robin", response_generator2, args);
auto channel2 =
BuildChannel("round_robin", response_generator2, args, channel_creds);
auto stub2 = BuildStub(channel2);
response_generator2.SetNextResolution(ports);
// First channel should not become READY, because health checks should be
@ -2024,13 +2180,18 @@ TEST_F(RoundRobinTest, HealthCheckingServiceNamePerChannel) {
// Start server.
const int kNumServers = 1;
StartServers(kNumServers);
// Use the same channel creds for both channels, so that they have the same
// subchannel keys.
auto channel_creds =
std::make_shared<FakeTransportSecurityChannelCredentials>();
// Create a channel with health-checking enabled.
ChannelArguments args;
args.SetServiceConfigJSON(
"{\"healthCheckConfig\": "
"{\"serviceName\": \"health_check_service_name\"}}");
FakeResolverResponseGeneratorWrapper response_generator1;
auto channel1 = BuildChannel("round_robin", response_generator1, args);
auto channel1 =
BuildChannel("round_robin", response_generator1, args, channel_creds);
auto stub1 = BuildStub(channel1);
std::vector<int> ports = GetServersPorts();
response_generator1.SetNextResolution(ports);
@ -2041,7 +2202,8 @@ TEST_F(RoundRobinTest, HealthCheckingServiceNamePerChannel) {
"{\"healthCheckConfig\": "
"{\"serviceName\": \"health_check_service_name2\"}}");
FakeResolverResponseGeneratorWrapper response_generator2;
auto channel2 = BuildChannel("round_robin", response_generator2, args2);
auto channel2 =
BuildChannel("round_robin", response_generator2, args2, channel_creds);
auto stub2 = BuildStub(channel2);
response_generator2.SetNextResolution(ports);
// Allow health checks from channel 2 to succeed.
@ -2202,6 +2364,10 @@ TEST_F(ClientLbPickArgsTest, Basic) {
<< ArgsSeenListString(pick_args_seen_list);
}
//
// tests that LB policies can get the call's trailing metadata
//
class OrcaLoadReportBuilder {
public:
OrcaLoadReportBuilder() = default;
@ -2245,10 +2411,6 @@ class OrcaLoadReportBuilder {
OrcaLoadReport report_;
};
//
// tests that LB policies can get the call's trailing metadata
//
OrcaLoadReport BackendMetricDataToOrcaLoadReport(
const grpc_core::BackendMetricData& backend_metric_data) {
auto builder = OrcaLoadReportBuilder()
@ -2700,7 +2862,7 @@ TEST_F(ClientLbInterceptTrailingMetadataTest, BackendMetricDataMerge) {
}
//
// tests that address args from the resolver are visible to the LB policy
// tests that per-address args from the resolver are visible to the LB policy
//
class ClientLbAddressTest : public ClientLbEnd2endTest {
@ -2739,7 +2901,7 @@ class ClientLbAddressTest : public ClientLbEnd2endTest {
static ClientLbAddressTest* current_test_instance_;
grpc_core::Mutex mu_;
std::vector<std::string> addresses_seen_;
std::vector<std::string> addresses_seen_ ABSL_GUARDED_BY(&mu_);
};
ClientLbAddressTest* ClientLbAddressTest::current_test_instance_ = nullptr;

@ -1156,6 +1156,170 @@ TEST_P(EdsTest, DropAll) {
EXPECT_EQ(num_drops, kNumRpcs);
}
class EdsAuthorityRewriteTest : public XdsEnd2endTest {
protected:
void SetUp() override {} // Individual tests call InitClient().
};
INSTANTIATE_TEST_SUITE_P(XdsTest, EdsAuthorityRewriteTest,
::testing::Values(XdsTestType()), &XdsTestType::Name);
TEST_P(EdsAuthorityRewriteTest, AutoAuthorityRewrite) {
grpc_core::testing::ScopedExperimentalEnvVar env(
"GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
constexpr char kAltAuthority1[] = "alt_authority1";
constexpr char kAltAuthority2[] = "alt_authority2";
// Note: We use InsecureCreds, since FakeCreds are too picky about
// what authority gets sent.
InitClient(MakeBootstrapBuilder().SetTrustedXdsServer(),
/*lb_expected_authority=*/"",
/*xds_resource_does_not_exist_timeout_ms=*/0,
/*balancer_authority_override=*/"", /*args=*/nullptr,
InsecureChannelCredentials());
// Set auto_host_rewrite in the RouteConfig.
RouteConfiguration new_route_config = default_route_config_;
new_route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_auto_host_rewrite()
->set_value(true);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Create 3 backends. Backend 0 does not have a hostname, but 1 and 2 do.
CreateAndStartBackends(3, /*xds_enabled=*/false, InsecureServerCredentials());
EdsResourceArgs args(
{{"locality0",
{
CreateEndpoint(0),
CreateEndpoint(1, ::envoy::config::core::v3::HealthStatus::UNKNOWN,
/*lb_weight=*/1, /*additional_backend_indxes=*/{},
/*hostname=*/kAltAuthority1),
CreateEndpoint(2, ::envoy::config::core::v3::HealthStatus::UNKNOWN,
/*lb_weight=*/1, /*additional_backend_indxes=*/{},
/*hostname=*/kAltAuthority2),
}}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
WaitForAllBackends(DEBUG_LOCATION);
// Send one RPC for each backend, check the authority headers seen on
// the servers, and make sure we see the expected ones.
std::set<std::string> authorities_seen;
for (size_t i = 0; i < backends_.size(); ++i) {
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
authorities_seen.insert(response.param().host());
}
EXPECT_THAT(
authorities_seen,
::testing::ElementsAre(kAltAuthority1, kAltAuthority2, kServerName));
}
TEST_P(EdsAuthorityRewriteTest, NoRewriteWithoutEnvVar) {
constexpr char kAltAuthority[] = "alt_authority";
InitClient(MakeBootstrapBuilder().SetTrustedXdsServer());
// Set auto_host_rewrite in the RouteConfig.
RouteConfiguration new_route_config = default_route_config_;
new_route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_auto_host_rewrite()
->set_value(true);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Create a backend with a hostname in EDS.
CreateAndStartBackends(1);
EdsResourceArgs args(
{{"locality0",
{CreateEndpoint(0, ::envoy::config::core::v3::HealthStatus::UNKNOWN,
/*lb_weight=*/1, /*additional_backend_indxes=*/{},
/*hostname=*/kAltAuthority)}}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// Send an RPC and check the authority seen on the server side.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), kServerName);
}
TEST_P(EdsAuthorityRewriteTest, NoRewriteIfServerNotTrustedInBootstrap) {
grpc_core::testing::ScopedExperimentalEnvVar env(
"GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
constexpr char kAltAuthority[] = "alt_authority";
InitClient();
// Set auto_host_rewrite in the RouteConfig.
RouteConfiguration new_route_config = default_route_config_;
new_route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_auto_host_rewrite()
->set_value(true);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Create a backend with a hostname in EDS.
CreateAndStartBackends(1);
EdsResourceArgs args(
{{"locality0",
{CreateEndpoint(0, ::envoy::config::core::v3::HealthStatus::UNKNOWN,
/*lb_weight=*/1, /*additional_backend_indxes=*/{},
/*hostname=*/kAltAuthority)}}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// Send an RPC and check the authority seen on the server side.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), kServerName);
}
TEST_P(EdsAuthorityRewriteTest, NoRewriteIfNoHostnameInEds) {
grpc_core::testing::ScopedExperimentalEnvVar env(
"GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
InitClient(MakeBootstrapBuilder().SetTrustedXdsServer());
// Set auto_host_rewrite in the RouteConfig.
RouteConfiguration new_route_config = default_route_config_;
new_route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_auto_host_rewrite()
->set_value(true);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Create a backend with no hostname in EDS.
CreateAndStartBackends(1);
EdsResourceArgs args({{"locality0", {CreateEndpoint(0)}}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// Send an RPC and check the authority seen on the server side.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), kServerName);
}
TEST_P(EdsAuthorityRewriteTest, NoRewriteIfNotEnabledInRoute) {
grpc_core::testing::ScopedExperimentalEnvVar env(
"GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
constexpr char kAltAuthority[] = "alt_authority";
InitClient(MakeBootstrapBuilder().SetTrustedXdsServer());
// Create a backend with a hostname in EDS.
CreateAndStartBackends(1);
EdsResourceArgs args(
{{"locality0",
{CreateEndpoint(0, ::envoy::config::core::v3::HealthStatus::UNKNOWN,
/*lb_weight=*/1, /*additional_backend_indxes=*/{},
/*hostname=*/kAltAuthority)}}});
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args));
// Send an RPC and check the authority seen on the server side.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), kServerName);
}
//
// EDS failover tests
//

@ -47,19 +47,27 @@ using ::envoy::extensions::clusters::aggregate::v3::ClusterConfig;
class ClusterTypeTest : public XdsEnd2endTest {
protected:
void SetUp() override {
logical_dns_cluster_resolver_response_generator_ =
grpc_core::MakeRefCounted<grpc_core::FakeResolverResponseGenerator>();
InitClient();
ClusterTypeTest()
: logical_dns_cluster_resolver_response_generator_(
grpc_core::MakeRefCounted<
grpc_core::FakeResolverResponseGenerator>()) {}
// Subclasses must call this to initialize.
void LogicalDnsInitClient(
absl::optional<XdsBootstrapBuilder> builder = absl::nullopt,
std::shared_ptr<ChannelCredentials> credentials = nullptr) {
ChannelArguments args;
args.SetPointerWithVtable(
GRPC_ARG_XDS_LOGICAL_DNS_CLUSTER_FAKE_RESOLVER_RESPONSE_GENERATOR,
logical_dns_cluster_resolver_response_generator_.get(),
&grpc_core::FakeResolverResponseGenerator::kChannelArgPointerVtable);
ResetStub(/*failover_timeout_ms=*/0, &args);
InitClient(builder, /*lb_expected_authority=*/"",
/*xds_resource_does_not_exist_timeout_ms=*/0,
/*balancer_authority_override=*/"", /*args=*/&args,
std::move(credentials));
}
grpc_core::EndpointAddressesList CreateAddressListFromPortList(
static grpc_core::EndpointAddressesList CreateAddressListFromPortList(
const std::vector<int>& ports) {
grpc_core::EndpointAddressesList addresses;
for (int port : ports) {
@ -81,12 +89,16 @@ class ClusterTypeTest : public XdsEnd2endTest {
// LOGICAL_DNS cluster tests
//
using LogicalDNSClusterTest = ClusterTypeTest;
class LogicalDNSClusterTest : public ClusterTypeTest {
protected:
void SetUp() override {} // Individual tests call LogicalDnsInitClient().
};
INSTANTIATE_TEST_SUITE_P(XdsTest, LogicalDNSClusterTest,
::testing::Values(XdsTestType()), &XdsTestType::Name);
TEST_P(LogicalDNSClusterTest, Basic) {
LogicalDnsInitClient();
CreateAndStartBackends(1);
// Create Logical DNS Cluster
auto cluster = default_cluster_;
@ -112,6 +124,175 @@ TEST_P(LogicalDNSClusterTest, Basic) {
CheckRpcSendOk(DEBUG_LOCATION);
}
TEST_P(LogicalDNSClusterTest, AutoHostRewrite) {
grpc_core::testing::ScopedExperimentalEnvVar env(
"GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
constexpr char kDnsName[] = "dns.example.com";
// Set auto_host_rewrite in the RouteConfig.
RouteConfiguration new_route_config = default_route_config_;
new_route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_auto_host_rewrite()
->set_value(true);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Create Logical DNS Cluster
auto cluster = default_cluster_;
cluster.set_type(Cluster::LOGICAL_DNS);
auto* address = cluster.mutable_load_assignment()
->add_endpoints()
->add_lb_endpoints()
->mutable_endpoint()
->mutable_address()
->mutable_socket_address();
address->set_address(kDnsName);
address->set_port_value(443);
balancer_->ads_service()->SetCdsResource(cluster);
// Create client and server.
// Note: We use InsecureCreds, since FakeCreds are too picky about
// what authority gets sent.
LogicalDnsInitClient(MakeBootstrapBuilder().SetTrustedXdsServer(),
InsecureChannelCredentials());
CreateAndStartBackends(1, /*xds_enabled=*/false, InsecureServerCredentials());
// Set Logical DNS result
{
grpc_core::ExecCtx exec_ctx;
grpc_core::Resolver::Result result;
result.addresses = CreateAddressListFromPortList(GetBackendPorts());
logical_dns_cluster_resolver_response_generator_->SetResponseSynchronously(
std::move(result));
}
// Send RPC and verify the authority seen by the server.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), absl::StrCat(kDnsName, ":443"));
}
TEST_P(LogicalDNSClusterTest, NoAuthorityRewriteWithoutEnvVar) {
constexpr char kDnsName[] = "dns.example.com";
// Set auto_host_rewrite in the RouteConfig.
RouteConfiguration new_route_config = default_route_config_;
new_route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_auto_host_rewrite()
->set_value(true);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Create Logical DNS Cluster
auto cluster = default_cluster_;
cluster.set_type(Cluster::LOGICAL_DNS);
auto* address = cluster.mutable_load_assignment()
->add_endpoints()
->add_lb_endpoints()
->mutable_endpoint()
->mutable_address()
->mutable_socket_address();
address->set_address(kDnsName);
address->set_port_value(443);
balancer_->ads_service()->SetCdsResource(cluster);
// Create client and server.
LogicalDnsInitClient(MakeBootstrapBuilder().SetTrustedXdsServer());
CreateAndStartBackends(1);
// Set Logical DNS result
{
grpc_core::ExecCtx exec_ctx;
grpc_core::Resolver::Result result;
result.addresses = CreateAddressListFromPortList(GetBackendPorts());
logical_dns_cluster_resolver_response_generator_->SetResponseSynchronously(
std::move(result));
}
// Send RPC and verify the authority seen by the server.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), kServerName);
}
TEST_P(LogicalDNSClusterTest, NoAuthorityRewriteIfServerNotTrustedInBootstrap) {
grpc_core::testing::ScopedExperimentalEnvVar env(
"GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
constexpr char kDnsName[] = "dns.example.com";
// Set auto_host_rewrite in the RouteConfig.
RouteConfiguration new_route_config = default_route_config_;
new_route_config.mutable_virtual_hosts(0)
->mutable_routes(0)
->mutable_route()
->mutable_auto_host_rewrite()
->set_value(true);
SetRouteConfiguration(balancer_.get(), new_route_config);
// Create Logical DNS Cluster
auto cluster = default_cluster_;
cluster.set_type(Cluster::LOGICAL_DNS);
auto* address = cluster.mutable_load_assignment()
->add_endpoints()
->add_lb_endpoints()
->mutable_endpoint()
->mutable_address()
->mutable_socket_address();
address->set_address(kDnsName);
address->set_port_value(443);
balancer_->ads_service()->SetCdsResource(cluster);
// Create client and server.
LogicalDnsInitClient();
CreateAndStartBackends(1);
// Set Logical DNS result
{
grpc_core::ExecCtx exec_ctx;
grpc_core::Resolver::Result result;
result.addresses = CreateAddressListFromPortList(GetBackendPorts());
logical_dns_cluster_resolver_response_generator_->SetResponseSynchronously(
std::move(result));
}
// Send RPC and verify the authority seen by the server.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), kServerName);
}
TEST_P(LogicalDNSClusterTest, NoAuthorityRewriteIfNotEnabledInRoute) {
grpc_core::testing::ScopedExperimentalEnvVar env(
"GRPC_EXPERIMENTAL_XDS_AUTHORITY_REWRITE");
constexpr char kDnsName[] = "dns.example.com";
// Create Logical DNS Cluster
auto cluster = default_cluster_;
cluster.set_type(Cluster::LOGICAL_DNS);
auto* address = cluster.mutable_load_assignment()
->add_endpoints()
->add_lb_endpoints()
->mutable_endpoint()
->mutable_address()
->mutable_socket_address();
address->set_address(kDnsName);
address->set_port_value(443);
balancer_->ads_service()->SetCdsResource(cluster);
// Create client and server.
LogicalDnsInitClient(MakeBootstrapBuilder().SetTrustedXdsServer());
CreateAndStartBackends(1);
// Set Logical DNS result
{
grpc_core::ExecCtx exec_ctx;
grpc_core::Resolver::Result result;
result.addresses = CreateAddressListFromPortList(GetBackendPorts());
logical_dns_cluster_resolver_response_generator_->SetResponseSynchronously(
std::move(result));
}
// Send RPC and verify the authority seen by the server.
EchoResponse response;
Status status = SendRpc(
RpcOptions().set_echo_host_from_authority_header(true), &response);
EXPECT_TRUE(status.ok()) << "code=" << status.error_code()
<< " message=" << status.error_message();
EXPECT_EQ(response.param().host(), kServerName);
}
//
// aggregate cluster tests
//
@ -119,7 +300,10 @@ TEST_P(LogicalDNSClusterTest, Basic) {
// TODO(roth): Add tests showing that load reporting is enabled on a
// per-underlying-cluster basis.
using AggregateClusterTest = ClusterTypeTest;
class AggregateClusterTest : public ClusterTypeTest {
protected:
void SetUp() override { LogicalDnsInitClient(); }
};
INSTANTIATE_TEST_SUITE_P(XdsTest, AggregateClusterTest,
::testing::Values(XdsTestType()), &XdsTestType::Name);

@ -294,7 +294,7 @@ class XdsSecurityTest : public XdsEnd2endTest {
absl::StrJoin(fields, ",\n"));
InitClient(builder, /*lb_expected_authority=*/"",
/*xds_resource_does_not_exist_timeout_ms=*/0,
/*balancer_authority_override=*/"",
/*balancer_authority_override=*/"", /*args=*/nullptr,
CreateXdsChannelCredentials());
CreateAndStartBackends(2, /*xds_enabled=*/false,
CreateMtlsServerCredentials());
@ -927,7 +927,7 @@ class XdsServerSecurityTest : public XdsEnd2endTest {
absl::StrJoin(fields, ",\n"));
InitClient(builder, /*lb_expected_authority=*/"",
/*xds_resource_does_not_exist_timeout_ms=*/0,
/*balancer_authority_override=*/"",
/*balancer_authority_override=*/"", /*args=*/nullptr,
CreateXdsChannelCredentials());
CreateBackends(1, /*xds_enabled=*/true,
XdsServerCredentials(InsecureServerCredentials()));

@ -350,6 +350,9 @@ void XdsEnd2endTest::RpcOptions::SetupRpc(ClientContext* context,
if (server_notify_client_when_started) {
request->mutable_param()->set_server_notify_client_when_started(true);
}
if (echo_host_from_authority_header) {
request->mutable_param()->set_echo_host_from_authority_header(true);
}
}
//
@ -479,7 +482,7 @@ void XdsEnd2endTest::InitClient(
absl::optional<XdsBootstrapBuilder> builder,
std::string lb_expected_authority,
int xds_resource_does_not_exist_timeout_ms,
std::string balancer_authority_override,
std::string balancer_authority_override, ChannelArguments* args,
std::shared_ptr<ChannelCredentials> credentials) {
if (!builder.has_value()) {
builder = MakeBootstrapBuilder();
@ -529,8 +532,7 @@ void XdsEnd2endTest::InitClient(
grpc_core::internal::UnsetGlobalXdsClientsForTest();
}
// Create channel and stub.
ResetStub(/*failover_timeout_ms=*/0, /*args=*/nullptr,
std::move(credentials));
ResetStub(/*failover_timeout_ms=*/0, args, std::move(credentials));
}
void XdsEnd2endTest::ResetStub(

@ -475,15 +475,16 @@ class XdsEnd2endTest : public ::testing::TestWithParam<XdsTestType>,
size_t backend_idx,
::envoy::config::core::v3::HealthStatus health_status =
::envoy::config::core::v3::HealthStatus::UNKNOWN,
int lb_weight = 1, std::vector<size_t> additional_backend_indxees = {}) {
int lb_weight = 1, std::vector<size_t> additional_backend_indxes = {},
absl::string_view hostname = "") {
std::vector<int> additional_ports;
additional_ports.reserve(additional_backend_indxees.size());
for (size_t idx : additional_backend_indxees) {
additional_ports.reserve(additional_backend_indxes.size());
for (size_t idx : additional_backend_indxes) {
additional_ports.push_back(backends_[idx]->port());
}
return EdsResourceArgs::Endpoint(backends_[backend_idx]->port(),
health_status, lb_weight,
additional_ports);
health_status, lb_weight, additional_ports,
hostname);
}
// Creates a vector of endpoints for a specified range of backends,
@ -570,6 +571,7 @@ class XdsEnd2endTest : public ::testing::TestWithParam<XdsTestType>,
std::string lb_expected_authority = "",
int xds_resource_does_not_exist_timeout_ms = 0,
std::string balancer_authority_override = "",
ChannelArguments* args = nullptr,
std::shared_ptr<ChannelCredentials> credentials = nullptr);
XdsBootstrapBuilder MakeBootstrapBuilder() {
@ -610,6 +612,7 @@ class XdsEnd2endTest : public ::testing::TestWithParam<XdsTestType>,
StatusCode server_expected_error = StatusCode::OK;
absl::optional<xds::data::orca::v3::OrcaLoadReport> backend_metrics;
bool server_notify_client_when_started = false;
bool echo_host_from_authority_header = false;
RpcOptions() {}
@ -681,6 +684,11 @@ class XdsEnd2endTest : public ::testing::TestWithParam<XdsTestType>,
return *this;
}
RpcOptions& set_echo_host_from_authority_header(bool value) {
echo_host_from_authority_header = value;
return *this;
}
// Populates context and request.
void SetupRpc(ClientContext* context, EchoRequest* request) const;
};

@ -93,6 +93,9 @@ std::string XdsBootstrapBuilder::MakeXdsServersText(
if (ignore_resource_deletion_) {
server_features.push_back("\"ignore_resource_deletion\"");
}
if (trusted_xds_server_) {
server_features.push_back("\"trusted_xds_server\"");
}
std::vector<std::string> servers;
for (absl::string_view server_uri : server_uris) {
servers.emplace_back(absl::StrReplaceAll(
@ -365,6 +368,9 @@ ClusterLoadAssignment XdsResourceUtils::BuildEdsResource(
socket_address->set_address(grpc_core::LocalIp());
socket_address->set_port_value(port);
}
if (!endpoint.hostname.empty()) {
endpoint_proto->set_hostname(endpoint.hostname);
}
}
}
if (!args.drop_categories.empty()) {

@ -39,6 +39,10 @@ class XdsBootstrapBuilder {
ignore_resource_deletion_ = true;
return *this;
}
XdsBootstrapBuilder& SetTrustedXdsServer() {
trusted_xds_server_ = true;
return *this;
}
XdsBootstrapBuilder& SetServers(absl::Span<const absl::string_view> servers) {
servers_ = std::vector<std::string>(servers.begin(), servers.end());
return *this;
@ -100,6 +104,7 @@ class XdsBootstrapBuilder {
std::string MakeAuthorityText();
bool ignore_resource_deletion_ = false;
bool trusted_xds_server_ = false;
std::vector<std::string> servers_;
std::string xds_channel_creds_type_ = "fake";
std::string xds_channel_creds_config_;
@ -214,16 +219,19 @@ class XdsResourceUtils {
::envoy::config::core::v3::HealthStatus health_status =
::envoy::config::core::v3::HealthStatus::UNKNOWN,
int lb_weight = 1,
std::vector<int> additional_ports = {})
std::vector<int> additional_ports = {},
absl::string_view hostname = "")
: port(port),
health_status(health_status),
lb_weight(lb_weight),
additional_ports(std::move(additional_ports)) {}
additional_ports(std::move(additional_ports)),
hostname(hostname) {}
int port;
::envoy::config::core::v3::HealthStatus health_status;
int lb_weight;
std::vector<int> additional_ports;
std::string hostname;
};
// A locality.

Loading…
Cancel
Save