xds: Include XdsConfig as a CallOption

This allows Filters to access the xds configuration for their own
processing. From gRFC A83:

> This data is available via the XdsConfig attribute introduced in A74.
> If the xDS ConfigSelector is not already passing that attribute to the
> filters, it will need to be changed to do so.
This commit is contained in:
Eric Anderson 2025-03-18 15:17:13 -07:00
parent a57c14a51e
commit bc3c764058
2 changed files with 15 additions and 3 deletions

View File

@ -94,6 +94,8 @@ final class XdsNameResolver extends NameResolver {
static final CallOptions.Key<String> CLUSTER_SELECTION_KEY = static final CallOptions.Key<String> CLUSTER_SELECTION_KEY =
CallOptions.Key.create("io.grpc.xds.CLUSTER_SELECTION_KEY"); CallOptions.Key.create("io.grpc.xds.CLUSTER_SELECTION_KEY");
static final CallOptions.Key<XdsConfig> XDS_CONFIG_CALL_OPTION_KEY =
CallOptions.Key.create("io.grpc.xds.XDS_CONFIG_CALL_OPTION_KEY");
static final CallOptions.Key<Long> RPC_HASH_KEY = static final CallOptions.Key<Long> RPC_HASH_KEY =
CallOptions.Key.create("io.grpc.xds.RPC_HASH_KEY"); CallOptions.Key.create("io.grpc.xds.RPC_HASH_KEY");
static final CallOptions.Key<Boolean> AUTO_HOST_REWRITE_KEY = static final CallOptions.Key<Boolean> AUTO_HOST_REWRITE_KEY =
@ -467,6 +469,7 @@ final class XdsNameResolver extends NameResolver {
"Failed to parse service config (method config)")); "Failed to parse service config (method config)"));
} }
final String finalCluster = cluster; final String finalCluster = cluster;
final XdsConfig xdsConfig = routingCfg.xdsConfig;
final long hash = generateHash(routeAction.hashPolicies(), headers); final long hash = generateHash(routeAction.hashPolicies(), headers);
class ClusterSelectionInterceptor implements ClientInterceptor { class ClusterSelectionInterceptor implements ClientInterceptor {
@Override @Override
@ -475,6 +478,7 @@ final class XdsNameResolver extends NameResolver {
final Channel next) { final Channel next) {
CallOptions callOptionsForCluster = CallOptions callOptionsForCluster =
callOptions.withOption(CLUSTER_SELECTION_KEY, finalCluster) callOptions.withOption(CLUSTER_SELECTION_KEY, finalCluster)
.withOption(XDS_CONFIG_CALL_OPTION_KEY, xdsConfig)
.withOption(RPC_HASH_KEY, hash); .withOption(RPC_HASH_KEY, hash);
if (routeAction.autoHostRewrite()) { if (routeAction.autoHostRewrite()) {
callOptionsForCluster = callOptionsForCluster.withOption(AUTO_HOST_REWRITE_KEY, true); callOptionsForCluster = callOptionsForCluster.withOption(AUTO_HOST_REWRITE_KEY, true);
@ -801,7 +805,7 @@ final class XdsNameResolver extends NameResolver {
} }
// Make newly added clusters selectable by config selector and deleted clusters no longer // Make newly added clusters selectable by config selector and deleted clusters no longer
// selectable. // selectable.
routingConfig = new RoutingConfig(httpMaxStreamDurationNano, routesData.build()); routingConfig = new RoutingConfig(xdsConfig, httpMaxStreamDurationNano, routesData.build());
for (String cluster : deletedClusters) { for (String cluster : deletedClusters) {
int count = clusterRefs.get(cluster).refCount.decrementAndGet(); int count = clusterRefs.get(cluster).refCount.decrementAndGet();
if (count == 0) { if (count == 0) {
@ -879,17 +883,21 @@ final class XdsNameResolver extends NameResolver {
* VirtualHost-level configuration for request routing. * VirtualHost-level configuration for request routing.
*/ */
private static class RoutingConfig { private static class RoutingConfig {
private final long fallbackTimeoutNano; final XdsConfig xdsConfig;
final long fallbackTimeoutNano;
final ImmutableList<RouteData> routes; final ImmutableList<RouteData> routes;
final Status errorStatus; final Status errorStatus;
private RoutingConfig(long fallbackTimeoutNano, ImmutableList<RouteData> routes) { private RoutingConfig(
XdsConfig xdsConfig, long fallbackTimeoutNano, ImmutableList<RouteData> routes) {
this.xdsConfig = checkNotNull(xdsConfig, "xdsConfig");
this.fallbackTimeoutNano = fallbackTimeoutNano; this.fallbackTimeoutNano = fallbackTimeoutNano;
this.routes = checkNotNull(routes, "routes"); this.routes = checkNotNull(routes, "routes");
this.errorStatus = null; this.errorStatus = null;
} }
private RoutingConfig(Status errorStatus) { private RoutingConfig(Status errorStatus) {
this.xdsConfig = null;
this.fallbackTimeoutNano = 0; this.fallbackTimeoutNano = 0;
this.routes = null; this.routes = null;
this.errorStatus = checkNotNull(errorStatus, "errorStatus"); this.errorStatus = checkNotNull(errorStatus, "errorStatus");

View File

@ -1672,6 +1672,10 @@ public class XdsNameResolverTest {
clientCall.start(new NoopClientCallListener<>(), new Metadata()); clientCall.start(new NoopClientCallListener<>(), new Metadata());
assertThat(testCall.callOptions.getOption(XdsNameResolver.CLUSTER_SELECTION_KEY)) assertThat(testCall.callOptions.getOption(XdsNameResolver.CLUSTER_SELECTION_KEY))
.isEqualTo("cluster:" + expectedCluster); .isEqualTo("cluster:" + expectedCluster);
XdsConfig xdsConfig =
testCall.callOptions.getOption(XdsNameResolver.XDS_CONFIG_CALL_OPTION_KEY);
assertThat(xdsConfig).isNotNull();
assertThat(xdsConfig.getClusters()).containsKey(expectedCluster); // Without "cluster:" prefix
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
Map<String, ?> config = (Map<String, ?>) result.getConfig(); Map<String, ?> config = (Map<String, ?>) result.getConfig();
if (expectedTimeoutSec != null) { if (expectedTimeoutSec != null) {