From e4e9ce3a39adfaa9ef95e6843970ceff2bdd6f57 Mon Sep 17 00:00:00 2001 From: Saurav Date: Tue, 17 Mar 2026 09:00:43 +0000 Subject: [PATCH 1/3] feat(xds): Allow injecting bootstrap info into xDS Filter API for config parsing Extend the xDS Filter API to support injecting bootstrap information into filters during configuration parsing. This allows filters to access context information (e.g., allowed gRPC services) from the resource loading layer during configuration validation and parsing. - Update `Filter.Provider.parseFilterConfig` and `parseFilterConfigOverride` to accept a `FilterContext` parameter. - Introduce `BootstrapInfoGrpcServiceContextProvider` to encapsulate bootstrap info for context resolution. - Update `XdsListenerResource` and `XdsRouteConfigureResource` to construct and pass `FilterContext` during configuration parsing. - Update sub-filters (`FaultFilter`, `RbacFilter`, `GcpAuthenticationFilter`, `RouterFilter`) to match the updated `FilterContext` signature. Known Gaps & Limitations: 1. **MetricHolder**: Propagation of `MetricHolder` is not supported with this approach currently and is planned for support in a later phase. 2. **NameResolverRegistry**: Propagation is deferred for consistency. While it could be passed from `XdsNameResolver` on the client side, there is no equivalent mechanism on the server side. To ensure consistent behavior, `DefaultRegistry` is used when validating schemes and creating channels. --- ...otstrapInfoGrpcServiceContextProvider.java | 73 +++++++++ .../main/java/io/grpc/xds/FaultFilter.java | 8 +- xds/src/main/java/io/grpc/xds/Filter.java | 27 +++- .../io/grpc/xds/GcpAuthenticationFilter.java | 7 +- xds/src/main/java/io/grpc/xds/RbacFilter.java | 6 +- .../main/java/io/grpc/xds/RouterFilter.java | 5 +- .../java/io/grpc/xds/XdsListenerResource.java | 14 +- .../grpc/xds/XdsRouteConfigureResource.java | 20 ++- ...rapInfoGrpcServiceContextProviderTest.java | 139 ++++++++++++++++++ .../java/io/grpc/xds/FaultFilterTest.java | 14 +- .../grpc/xds/GcpAuthenticationFilterTest.java | 22 ++- .../grpc/xds/GrpcXdsClientImplDataTest.java | 56 ++++--- .../test/java/io/grpc/xds/RbacFilterTest.java | 27 +++- .../test/java/io/grpc/xds/StatefulFilter.java | 5 +- .../test/java/io/grpc/xds/XdsTestUtils.java | 7 +- 15 files changed, 369 insertions(+), 61 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java create mode 100644 xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java diff --git a/xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java b/xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java new file mode 100644 index 00000000000..864108ff431 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright 2026 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import io.grpc.NameResolverRegistry; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; +import io.grpc.xds.client.Bootstrapper.ServerInfo; +import io.grpc.xds.internal.grpcservice.AllowedGrpcService; +import io.grpc.xds.internal.grpcservice.AllowedGrpcServices; +import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContext; +import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Optional; + +/** + * Concrete implementation of {@link GrpcServiceXdsContextProvider} that uses + * {@link BootstrapInfo} data to resolve context. + */ +final class BootstrapInfoGrpcServiceContextProvider + implements GrpcServiceXdsContextProvider { + + private final boolean isTrustedControlPlane; + private final AllowedGrpcServices allowedGrpcServices; + private final NameResolverRegistry nameResolverRegistry; + + BootstrapInfoGrpcServiceContextProvider(BootstrapInfo bootstrapInfo, ServerInfo serverInfo) { + this.isTrustedControlPlane = serverInfo.isTrustedXdsServer(); + this.allowedGrpcServices = bootstrapInfo.allowedGrpcServices() + .filter(AllowedGrpcServices.class::isInstance) + .map(AllowedGrpcServices.class::cast) + .orElse(AllowedGrpcServices.empty()); + this.nameResolverRegistry = NameResolverRegistry.getDefaultRegistry(); + } + + @Override + public GrpcServiceXdsContext getContextForTarget(String targetUri) { + Optional validAllowedGrpcService = + Optional.ofNullable(allowedGrpcServices.services().get(targetUri)); + + boolean isTargetUriSchemeSupported = false; + try { + URI uri = new URI(targetUri); + String scheme = uri.getScheme(); + if (scheme != null) { + isTargetUriSchemeSupported = + nameResolverRegistry.getProviderForScheme(scheme) != null; + } + } catch (URISyntaxException e) { + // Fallback or ignore if not a valid URI + } + + return GrpcServiceXdsContext.create( + isTrustedControlPlane, + validAllowedGrpcService, + isTargetUriSchemeSupported + ); + } +} diff --git a/xds/src/main/java/io/grpc/xds/FaultFilter.java b/xds/src/main/java/io/grpc/xds/FaultFilter.java index 0f3bb5b0557..e0533889d74 100644 --- a/xds/src/main/java/io/grpc/xds/FaultFilter.java +++ b/xds/src/main/java/io/grpc/xds/FaultFilter.java @@ -104,7 +104,8 @@ public FaultFilter newInstance(String name) { } @Override - public ConfigOrError parseFilterConfig(Message rawProtoMessage) { + public ConfigOrError parseFilterConfig( + Message rawProtoMessage, FilterContext context) { HTTPFault httpFaultProto; if (!(rawProtoMessage instanceof Any)) { return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass()); @@ -119,8 +120,9 @@ public ConfigOrError parseFilterConfig(Message rawProtoMessage) { } @Override - public ConfigOrError parseFilterConfigOverride(Message rawProtoMessage) { - return parseFilterConfig(rawProtoMessage); + public ConfigOrError parseFilterConfigOverride( + Message rawProtoMessage, FilterContext context) { + return parseFilterConfig(rawProtoMessage, context); } private static ConfigOrError parseHttpFault(HTTPFault httpFault) { diff --git a/xds/src/main/java/io/grpc/xds/Filter.java b/xds/src/main/java/io/grpc/xds/Filter.java index 416d929becf..0fa5b8af128 100644 --- a/xds/src/main/java/io/grpc/xds/Filter.java +++ b/xds/src/main/java/io/grpc/xds/Filter.java @@ -16,10 +16,12 @@ package io.grpc.xds; + import com.google.common.base.MoreObjects; import com.google.protobuf.Message; import io.grpc.ClientInterceptor; import io.grpc.ServerInterceptor; +import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; import java.io.Closeable; import java.util.Objects; import java.util.concurrent.ScheduledExecutorService; @@ -93,13 +95,15 @@ default boolean isServerFilter() { * Parses the top-level filter config from raw proto message. The message may be either a {@link * com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}. */ - ConfigOrError parseFilterConfig(Message rawProtoMessage); + ConfigOrError parseFilterConfig( + Message rawProtoMessage, FilterContext context); /** * Parses the per-filter override filter config from raw proto message. The message may be * either a {@link com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}. */ - ConfigOrError parseFilterConfigOverride(Message rawProtoMessage); + ConfigOrError parseFilterConfigOverride( + Message rawProtoMessage, FilterContext context); } /** Uses the FilterConfigs produced above to produce an HTTP filter interceptor for clients. */ @@ -125,6 +129,25 @@ default ServerInterceptor buildServerInterceptor( @Override default void close() {} + /** Context carrying dynamic metadata for a filter. */ + @com.google.auto.value.AutoValue + abstract class FilterContext { + public abstract GrpcServiceXdsContextProvider grpcServiceContextProvider(); + + public static Builder builder() { + return new AutoValue_Filter_FilterContext.Builder(); + } + + + @com.google.auto.value.AutoValue.Builder + public abstract static class Builder { + public abstract Builder grpcServiceContextProvider( + GrpcServiceXdsContextProvider provider); + + public abstract FilterContext build(); + } + } + /** Filter config with instance name. */ final class NamedFilterConfig { // filter instance name diff --git a/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java b/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java index 8ec02f4f809..78d20edec46 100644 --- a/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java +++ b/xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java @@ -86,7 +86,8 @@ public GcpAuthenticationFilter newInstance(String name) { } @Override - public ConfigOrError parseFilterConfig(Message rawProtoMessage) { + public ConfigOrError parseFilterConfig( + Message rawProtoMessage, FilterContext context) { GcpAuthnFilterConfig gcpAuthnProto; if (!(rawProtoMessage instanceof Any)) { return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass()); @@ -121,8 +122,8 @@ public ConfigOrError parseFilterConfig(Message rawProto @Override public ConfigOrError parseFilterConfigOverride( - Message rawProtoMessage) { - return parseFilterConfig(rawProtoMessage); + Message rawProtoMessage, FilterContext context) { + return parseFilterConfig(rawProtoMessage, context); } } diff --git a/xds/src/main/java/io/grpc/xds/RbacFilter.java b/xds/src/main/java/io/grpc/xds/RbacFilter.java index 91df1e68802..035bfd06607 100644 --- a/xds/src/main/java/io/grpc/xds/RbacFilter.java +++ b/xds/src/main/java/io/grpc/xds/RbacFilter.java @@ -94,7 +94,8 @@ public RbacFilter newInstance(String name) { } @Override - public ConfigOrError parseFilterConfig(Message rawProtoMessage) { + public ConfigOrError parseFilterConfig( + Message rawProtoMessage, FilterContext context) { RBAC rbacProto; if (!(rawProtoMessage instanceof Any)) { return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass()); @@ -109,7 +110,8 @@ public ConfigOrError parseFilterConfig(Message rawProtoMessage) { } @Override - public ConfigOrError parseFilterConfigOverride(Message rawProtoMessage) { + public ConfigOrError parseFilterConfigOverride( + Message rawProtoMessage, FilterContext context) { RBACPerRoute rbacPerRoute; if (!(rawProtoMessage instanceof Any)) { return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass()); diff --git a/xds/src/main/java/io/grpc/xds/RouterFilter.java b/xds/src/main/java/io/grpc/xds/RouterFilter.java index 504c4213149..c80e57c9010 100644 --- a/xds/src/main/java/io/grpc/xds/RouterFilter.java +++ b/xds/src/main/java/io/grpc/xds/RouterFilter.java @@ -61,13 +61,14 @@ public RouterFilter newInstance(String name) { } @Override - public ConfigOrError parseFilterConfig(Message rawProtoMessage) { + public ConfigOrError parseFilterConfig( + Message rawProtoMessage, FilterContext context) { return ConfigOrError.fromConfig(ROUTER_CONFIG); } @Override public ConfigOrError parseFilterConfigOverride( - Message rawProtoMessage) { + Message rawProtoMessage, FilterContext context) { return ConfigOrError.fromError("Router Filter should not have override config"); } } diff --git a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java index 041b659b4c3..4aff4a7f2ad 100644 --- a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java @@ -527,7 +527,7 @@ static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( "HttpConnectionManager contains duplicate HttpFilter: " + filterName); } StructOrError filterConfig = - parseHttpFilter(httpFilter, filterRegistry, isForClient); + parseHttpFilter(httpFilter, filterRegistry, isForClient, args); if ((i == proto.getHttpFiltersCount() - 1) && (filterConfig == null || !isTerminalFilter(filterConfig.getStruct()))) { throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: " @@ -581,7 +581,8 @@ private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) { @Nullable // Returns null if the filter is optional but not supported. static StructOrError parseHttpFilter( io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter - httpFilter, FilterRegistry filterRegistry, boolean isForClient) { + httpFilter, FilterRegistry filterRegistry, boolean isForClient, + XdsResourceType.Args args) { String filterName = httpFilter.getName(); boolean isOptional = httpFilter.getIsOptional(); if (!httpFilter.hasTypedConfig()) { @@ -616,7 +617,14 @@ static StructOrError parseHttpFilter( "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " + ( isForClient ? "client" : "server")); } - ConfigOrError filterConfig = provider.parseFilterConfig(rawConfig); + + BootstrapInfoGrpcServiceContextProvider contextProvider = + new BootstrapInfoGrpcServiceContextProvider(args.getBootstrapInfo(), args.getServerInfo()); + Filter.FilterContext filterContext = Filter.FilterContext.builder() + .grpcServiceContextProvider(contextProvider) + .build(); + ConfigOrError filterConfig = + provider.parseFilterConfig(rawConfig, filterContext); if (filterConfig.errorDetail != null) { return StructOrError.fromError( "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail); diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java index 24ec0659b42..0bb0c48cd65 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -198,7 +198,7 @@ private static StructOrError parseVirtualHost( routes.add(route.getStruct()); } StructOrError> overrideConfigs = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args); if (overrideConfigs.getErrorDetail() != null) { return StructOrError.fromError( "VirtualHost [" + proto.getName() + "] contains invalid HttpFilter config: " @@ -210,7 +210,13 @@ private static StructOrError parseVirtualHost( @VisibleForTesting static StructOrError> parseOverrideFilterConfigs( - Map rawFilterConfigMap, FilterRegistry filterRegistry) { + Map rawFilterConfigMap, FilterRegistry filterRegistry, + XdsResourceType.Args args) { + BootstrapInfoGrpcServiceContextProvider grpcServiceContextProvider = + new BootstrapInfoGrpcServiceContextProvider(args.getBootstrapInfo(), args.getServerInfo()); + Filter.FilterContext context = Filter.FilterContext.builder() + .grpcServiceContextProvider(grpcServiceContextProvider) + .build(); Map overrideConfigs = new HashMap<>(); for (String name : rawFilterConfigMap.keySet()) { Any anyConfig = rawFilterConfigMap.get(name); @@ -254,7 +260,7 @@ static StructOrError> parseOverrideFilterConfigs( "HttpFilter [" + name + "](" + typeUrl + ") is required but unsupported"); } ConfigOrError filterConfig = - provider.parseFilterConfigOverride(rawConfig); + provider.parseFilterConfigOverride(rawConfig, context); if (filterConfig.errorDetail != null) { return StructOrError.fromError( "Invalid filter config for HttpFilter [" + name + "]: " + filterConfig.errorDetail); @@ -281,7 +287,7 @@ static StructOrError parseRoute( } StructOrError> overrideConfigsOrError = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args); if (overrideConfigsOrError.getErrorDetail() != null) { return StructOrError.fromError( "Route [" + proto.getName() + "] contains invalid HttpFilter config: " @@ -490,7 +496,7 @@ static StructOrError parseRouteAction( for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight : clusterWeights) { StructOrError clusterWeightOrError = - parseClusterWeight(clusterWeight, filterRegistry); + parseClusterWeight(clusterWeight, filterRegistry, args); if (clusterWeightOrError.getErrorDetail() != null) { return StructOrError.fromError("RouteAction contains invalid ClusterWeight: " + clusterWeightOrError.getErrorDetail()); @@ -599,9 +605,9 @@ private static StructOrError parseRet @VisibleForTesting static StructOrError parseClusterWeight( io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto, - FilterRegistry filterRegistry) { + FilterRegistry filterRegistry, XdsResourceType.Args args) { StructOrError> overrideConfigs = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args); if (overrideConfigs.getErrorDetail() != null) { return StructOrError.fromError( "ClusterWeight [" + proto.getName() + "] contains invalid HttpFilter config: " diff --git a/xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java b/xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java new file mode 100644 index 00000000000..ab42f634daa --- /dev/null +++ b/xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java @@ -0,0 +1,139 @@ +/* + * Copyright 2026 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.xds; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import io.grpc.ChannelCredentials; +import io.grpc.InsecureChannelCredentials; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; +import io.grpc.xds.client.Bootstrapper.ServerInfo; +import io.grpc.xds.client.EnvoyProtoData; +import io.grpc.xds.internal.grpcservice.AllowedGrpcService; +import io.grpc.xds.internal.grpcservice.AllowedGrpcServices; +import io.grpc.xds.internal.grpcservice.ChannelCredsConfig; +import io.grpc.xds.internal.grpcservice.ConfiguredChannelCredentials; +import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContext; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link BootstrapInfoGrpcServiceContextProvider}. + */ +@RunWith(JUnit4.class) +public class BootstrapInfoGrpcServiceContextProviderTest { + + private static final ChannelCredentials CREDENTIALS = InsecureChannelCredentials.create(); + private static final ChannelCredsConfig DUMMY_CONFIG = () -> "dummy"; + private static final EnvoyProtoData.Node DUMMY_NODE = + EnvoyProtoData.Node.newBuilder().setId("node-id").build(); + + private static final BootstrapInfo DUMMY_BOOTSTRAP = BootstrapInfo.builder() + .servers(ImmutableList.of()) + .node(DUMMY_NODE) + .build(); + + private static ServerInfo createServerInfo(boolean isTrusted) { + return ServerInfo.create("xds:///any", CREDENTIALS, false, isTrusted, false, false); + } + + @Test + public void getContextForTarget_trustedServer() { + ServerInfo serverInfo = createServerInfo(true); + BootstrapInfoGrpcServiceContextProvider provider = + new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, serverInfo); + + GrpcServiceXdsContext context = provider.getContextForTarget("xds:///any"); + assertThat(context.isTrustedControlPlane()).isTrue(); + } + + @Test + public void getContextForTarget_untrustedServer() { + ServerInfo serverInfo = createServerInfo(false); + BootstrapInfoGrpcServiceContextProvider provider = + new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, serverInfo); + + GrpcServiceXdsContext context = provider.getContextForTarget("xds:///any"); + assertThat(context.isTrustedControlPlane()).isFalse(); + } + + @Test + public void getContextForTarget_allowedGrpcServices() { + ConfiguredChannelCredentials creds = ConfiguredChannelCredentials.create( + CREDENTIALS, DUMMY_CONFIG); + AllowedGrpcService allowedService = AllowedGrpcService.builder() + .configuredChannelCredentials(creds) + .build(); + + Map servicesMap = new HashMap<>(); + servicesMap.put("xds:///target1", allowedService); + AllowedGrpcServices allowedGrpcServices = AllowedGrpcServices.create(servicesMap); + + BootstrapInfo bootstrapInfo = BootstrapInfo.builder() + .servers(ImmutableList.of()) + .node(DUMMY_NODE) + .allowedGrpcServices(Optional.of(allowedGrpcServices)) + .build(); + + BootstrapInfoGrpcServiceContextProvider provider = + new BootstrapInfoGrpcServiceContextProvider(bootstrapInfo, createServerInfo(false)); + + GrpcServiceXdsContext context = provider.getContextForTarget("xds:///target1"); + assertThat(context.validAllowedGrpcService().isPresent()).isTrue(); + assertThat(context.validAllowedGrpcService().get()).isEqualTo(allowedService); + + // Target not in map + GrpcServiceXdsContext context2 = provider.getContextForTarget("xds:///target2"); + assertThat(context2.validAllowedGrpcService().isPresent()).isFalse(); + } + + @Test + public void getContextForTarget_schemeSupported() { + BootstrapInfoGrpcServiceContextProvider provider = + new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, createServerInfo(false)); + + assertThat(provider.getContextForTarget("dns:///foo").isTargetUriSchemeSupported()).isTrue(); + assertThat(provider.getContextForTarget("unknown:///foo").isTargetUriSchemeSupported()) + .isFalse(); + } + + @Test + public void getContextForTarget_invalidUri() { + BootstrapInfoGrpcServiceContextProvider provider = + new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, createServerInfo(false)); + + GrpcServiceXdsContext context = provider.getContextForTarget("invalid:uri:with:colons"); + assertThat(context.isTargetUriSchemeSupported()).isFalse(); + } + + @Test + public void getContextForTarget_invalidAllowedGrpcServicesTypeFallbackToEmpty() { + BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(ImmutableList.of()) + .node(DUMMY_NODE).allowedGrpcServices(Optional.of("invalid_type_string")).build(); + + BootstrapInfoGrpcServiceContextProvider provider = + new BootstrapInfoGrpcServiceContextProvider(bootstrapInfo, createServerInfo(false)); + + GrpcServiceXdsContext context = provider.getContextForTarget("xds:///any"); + assertThat(context.validAllowedGrpcService().isPresent()).isFalse(); + } +} diff --git a/xds/src/test/java/io/grpc/xds/FaultFilterTest.java b/xds/src/test/java/io/grpc/xds/FaultFilterTest.java index 8f0a33951b0..f74e39e727f 100644 --- a/xds/src/test/java/io/grpc/xds/FaultFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/FaultFilterTest.java @@ -17,6 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; import com.google.protobuf.Any; import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort; @@ -26,6 +27,7 @@ import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType; import io.grpc.Status.Code; import io.grpc.internal.GrpcUtil; +import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -45,11 +47,14 @@ public void filterType_clientOnly() { public void parseFaultAbort_convertHttpStatus() { Any rawConfig = Any.pack( HTTPFault.newBuilder().setAbort(FaultAbort.newBuilder().setHttpStatus(404)).build()); - FaultConfig faultConfig = FILTER_PROVIDER.parseFilterConfig(rawConfig).config; + FaultConfig faultConfig = FILTER_PROVIDER.parseFilterConfig( + rawConfig, getFilterContext()).config; assertThat(faultConfig.faultAbort().status().getCode()) .isEqualTo(GrpcUtil.httpStatusToGrpcStatus(404).getCode()); - FaultConfig faultConfigOverride = FILTER_PROVIDER.parseFilterConfigOverride(rawConfig).config; + FaultConfig faultConfigOverride = + FILTER_PROVIDER.parseFilterConfigOverride( + rawConfig, getFilterContext()).config; assertThat(faultConfigOverride.faultAbort().status().getCode()) .isEqualTo(GrpcUtil.httpStatusToGrpcStatus(404).getCode()); } @@ -95,4 +100,9 @@ public void parseFaultAbort_withGrpcStatus() { .isEqualTo(FaultConfig.FractionalPercent.DenominatorType.MILLION); assertThat(faultAbort.status().getCode()).isEqualTo(Code.DEADLINE_EXCEEDED); } + + private static Filter.FilterContext getFilterContext() { + return Filter.FilterContext.builder() + .grpcServiceContextProvider(mock(GrpcServiceXdsContextProvider.class)).build(); + } } diff --git a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java index f252c6f4ec1..2a1ee36d0e9 100644 --- a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java @@ -68,6 +68,7 @@ import io.grpc.xds.client.Locality; import io.grpc.xds.client.XdsResourceType; import io.grpc.xds.client.XdsResourceType.ResourceInvalidException; +import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; import java.io.IOException; import java.util.Collections; import java.util.HashMap; @@ -112,8 +113,8 @@ public void testParseFilterConfig_withValidConfig() { .build(); Any anyMessage = Any.pack(config); - ConfigOrError result = FILTER_PROVIDER.parseFilterConfig(anyMessage); - + ConfigOrError result = + FILTER_PROVIDER.parseFilterConfig(anyMessage, getFilterContext()); assertNotNull(result.config); assertNull(result.errorDetail); assertEquals(20L, result.config.getCacheSize()); @@ -126,8 +127,8 @@ public void testParseFilterConfig_withZeroCacheSize() { .build(); Any anyMessage = Any.pack(config); - ConfigOrError result = FILTER_PROVIDER.parseFilterConfig(anyMessage); - + ConfigOrError result = + FILTER_PROVIDER.parseFilterConfig(anyMessage, getFilterContext()); assertNull(result.config); assertNotNull(result.errorDetail); assertTrue(result.errorDetail.contains("cache_config.cache_size must be greater than zero")); @@ -137,7 +138,7 @@ public void testParseFilterConfig_withZeroCacheSize() { public void testParseFilterConfig_withInvalidMessageType() { Message invalidMessage = Empty.getDefaultInstance(); ConfigOrError result = - FILTER_PROVIDER.parseFilterConfig(invalidMessage); + FILTER_PROVIDER.parseFilterConfig(invalidMessage, getFilterContext()); assertNull(result.config); assertThat(result.errorDetail).contains("Invalid config type"); @@ -468,8 +469,9 @@ private static LdsUpdate getLdsUpdate() { private static RdsUpdate getRdsUpdate() { RouteConfiguration routeConfiguration = buildRouteConfiguration("my-server", RDS_NAME, CLUSTER_NAME); - XdsResourceType.Args args = new XdsResourceType.Args( - XdsTestUtils.EMPTY_BOOTSTRAPPER_SERVER_INFO, "0", "0", null, null, null); + XdsResourceType.Args args = + new XdsResourceType.Args(XdsTestUtils.EMPTY_BOOTSTRAPPER_SERVER_INFO, "0", "0", + XdsTestUtils.EMPTY_BOOTSTRAP, null, null); try { return XdsRouteConfigureResource.getInstance().doParse(args, routeConfiguration); } catch (ResourceInvalidException ex) { @@ -521,4 +523,10 @@ private static CdsUpdate getCdsUpdateWithIncorrectAudienceWrapper() throws IOExc .lbPolicyConfig(getWrrLbConfigAsMap()); return cdsUpdate.parsedMetadata(parsedMetadata.build()).build(); } + + private static Filter.FilterContext getFilterContext() { + return Filter.FilterContext.builder() + .grpcServiceContextProvider(Mockito.mock(GrpcServiceXdsContextProvider.class)) + .build(); + } } diff --git a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java index be29e5e719f..7d88f9ebf94 100644 --- a/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java +++ b/xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java @@ -1049,7 +1049,9 @@ public void parseClusterWeight() { .setWeight(UInt32Value.newBuilder().setValue(30)) .build(); ClusterWeight clusterWeight = - XdsRouteConfigureResource.parseClusterWeight(proto, filterRegistry).getStruct(); + XdsRouteConfigureResource + .parseClusterWeight(proto, filterRegistry, getXdsResourceTypeArgs(true)) + .getStruct(); assertThat(clusterWeight.name()).isEqualTo("cluster-foo"); assertThat(clusterWeight.weight()).isEqualTo(30); } @@ -1255,7 +1257,8 @@ public void parseHttpFilter_unsupportedButOptional() { .setIsOptional(true) .setTypedConfig(Any.pack(StringValue.of("unsupported"))) .build(); - assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true)).isNull(); + assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true, + getXdsResourceTypeArgs(true))).isNull(); } private static class SimpleFilterConfig implements FilterConfig { @@ -1294,12 +1297,14 @@ public TestFilter newInstance(String name) { } @Override - public ConfigOrError parseFilterConfig(Message rawProtoMessage) { + public ConfigOrError parseFilterConfig(Message rawProtoMessage, + FilterContext context) { return ConfigOrError.fromConfig(new SimpleFilterConfig(rawProtoMessage)); } @Override - public ConfigOrError parseFilterConfigOverride(Message rawProtoMessage) { + public ConfigOrError parseFilterConfigOverride(Message rawProtoMessage, + FilterContext context) { return ConfigOrError.fromConfig(new SimpleFilterConfig(rawProtoMessage)); } } @@ -1319,7 +1324,7 @@ public void parseHttpFilter_typedStructMigration() { .setValue(rawStruct) .build())).build(); FilterConfig config = XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, - true).getStruct(); + true, getXdsResourceTypeArgs(true)).getStruct(); assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); HttpFilter httpFilterNewTypeStruct = HttpFilter.newBuilder() @@ -1330,7 +1335,7 @@ public void parseHttpFilter_typedStructMigration() { .setValue(rawStruct) .build())).build(); config = XdsListenerResource.parseHttpFilter(httpFilterNewTypeStruct, filterRegistry, - true).getStruct(); + true, getXdsResourceTypeArgs(true)).getStruct(); assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); } @@ -1356,7 +1361,7 @@ public void parseOverrideHttpFilter_typedStructMigration() { .build()) ); Map map = XdsRouteConfigureResource.parseOverrideFilterConfigs( - rawFilterMap, filterRegistry).getStruct(); + rawFilterMap, filterRegistry, getXdsResourceTypeArgs(true)).getStruct(); assertThat(((SimpleFilterConfig)map.get("struct-0")).getConfig()).isEqualTo(rawStruct0); assertThat(((SimpleFilterConfig)map.get("struct-1")).getConfig()).isEqualTo(rawStruct1); } @@ -1368,7 +1373,8 @@ public void parseHttpFilter_unsupportedAndRequired() { .setName("unsupported.filter") .setTypedConfig(Any.pack(StringValue.of("string value"))) .build(); - assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true) + assertThat(XdsListenerResource + .parseHttpFilter(httpFilter, filterRegistry, true, getXdsResourceTypeArgs(true)) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported " @@ -1385,7 +1391,8 @@ public void parseHttpFilter_routerFilterForClient() { .setTypedConfig(Any.pack(Router.getDefaultInstance())) .build(); FilterConfig config = XdsListenerResource.parseHttpFilter( - httpFilter, filterRegistry, true /* isForClient */).getStruct(); + httpFilter, filterRegistry, true /* isForClient */, getXdsResourceTypeArgs(true)) + .getStruct(); assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL); } @@ -1399,7 +1406,8 @@ public void parseHttpFilter_routerFilterForServer() { .setTypedConfig(Any.pack(Router.getDefaultInstance())) .build(); FilterConfig config = XdsListenerResource.parseHttpFilter( - httpFilter, filterRegistry, false /* isForClient */).getStruct(); + httpFilter, filterRegistry, false /* isForClient */, getXdsResourceTypeArgs(false)) + .getStruct(); assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL); } @@ -1426,7 +1434,8 @@ public void parseHttpFilter_faultConfigForClient() { .build())) .build(); FilterConfig config = XdsListenerResource.parseHttpFilter( - httpFilter, filterRegistry, true /* isForClient */).getStruct(); + httpFilter, filterRegistry, true /* isForClient */, getXdsResourceTypeArgs(true)) + .getStruct(); assertThat(config).isInstanceOf(FaultConfig.class); } @@ -1453,7 +1462,8 @@ public void parseHttpFilter_faultConfigUnsupportedForServer() { .build())) .build(); StructOrError config = - XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */); + XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */, + getXdsResourceTypeArgs(false)); assertThat(config.getErrorDetail()).isEqualTo( "HttpFilter [envoy.fault](" + FaultFilter.TYPE_URL + ") is required but " + "unsupported for server"); @@ -1482,7 +1492,8 @@ public void parseHttpFilter_rbacConfigForServer() { .build())) .build(); FilterConfig config = XdsListenerResource.parseHttpFilter( - httpFilter, filterRegistry, false /* isForClient */).getStruct(); + httpFilter, filterRegistry, false /* isForClient */, getXdsResourceTypeArgs(false)) + .getStruct(); assertThat(config).isInstanceOf(RbacConfig.class); } @@ -1509,7 +1520,8 @@ public void parseHttpFilter_rbacConfigUnsupportedForClient() { .build())) .build(); StructOrError config = - XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */); + XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */, + getXdsResourceTypeArgs(true)); assertThat(config.getErrorDetail()).isEqualTo( "HttpFilter [envoy.auth](" + RbacFilter.TYPE_URL + ") is required but " + "unsupported for client"); @@ -1534,7 +1546,8 @@ public void parseOverrideRbacFilterConfig() { .build(); Map configOverrides = ImmutableMap.of("envoy.auth", Any.pack(rbacPerRoute)); Map parsedConfigs = - XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry, + getXdsResourceTypeArgs(true)) .getStruct(); assertThat(parsedConfigs).hasSize(1); assertThat(parsedConfigs).containsKey("envoy.auth"); @@ -1555,7 +1568,8 @@ public void parseOverrideFilterConfigs_unsupportedButOptional() { .setIsOptional(true).setConfig(Any.pack(StringValue.of("string value"))) .build())); Map parsedConfigs = - XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry, + getXdsResourceTypeArgs(true)) .getStruct(); assertThat(parsedConfigs).hasSize(1); assertThat(parsedConfigs).containsKey("envoy.fault"); @@ -1574,7 +1588,9 @@ public void parseOverrideFilterConfigs_unsupportedAndRequired() { Any.pack(io.envoyproxy.envoy.config.route.v3.FilterConfig.newBuilder() .setIsOptional(false).setConfig(Any.pack(StringValue.of("string value"))) .build())); - assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + assertThat(XdsRouteConfigureResource + .parseOverrideFilterConfigs(configOverrides, filterRegistry, + getXdsResourceTypeArgs(true)) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported"); @@ -1584,7 +1600,9 @@ public void parseOverrideFilterConfigs_unsupportedAndRequired() { Any.pack(httpFault), "unsupported.filter", Any.pack(StringValue.of("string value"))); - assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + assertThat(XdsRouteConfigureResource + .parseOverrideFilterConfigs(configOverrides, filterRegistry, + getXdsResourceTypeArgs(true)) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported"); @@ -3614,7 +3632,7 @@ private static Filter buildHttpConnectionManagerFilter(HttpFilter... httpFilters private XdsResourceType.Args getXdsResourceTypeArgs(boolean isTrustedServer) { return new XdsResourceType.Args( - ServerInfo.create("http://td", "", false, isTrustedServer, false, false), "1.0", null, null, null, null + ServerInfo.create("http://td", "", false, isTrustedServer, false, false), "1.0", null, XdsTestUtils.EMPTY_BOOTSTRAP, null, null ); } } diff --git a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java index 334e159dd1d..ca59ab4e524 100644 --- a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java @@ -299,7 +299,7 @@ public void handleException() { .putPolicies("policy-name", Policy.newBuilder().setCondition(Expr.newBuilder().build()).build()) .build()).build(); - result = FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto)); + result = FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto), getFilterContext()); assertThat(result.errorDetail).isNotNull(); } @@ -321,7 +321,8 @@ public void overrideConfig() { RbacConfig original = RbacConfig.create(authconfig); RBACPerRoute rbacPerRoute = RBACPerRoute.newBuilder().build(); - RbacConfig override = FILTER_PROVIDER.parseFilterConfigOverride(Any.pack(rbacPerRoute)).config; + RbacConfig override = FILTER_PROVIDER.parseFilterConfigOverride(Any.pack(rbacPerRoute), + getFilterContext()).config; assertThat(override).isEqualTo(RbacConfig.create(null)); ServerInterceptor interceptor = FILTER_PROVIDER.newInstance(name).buildServerInterceptor(original, override); @@ -346,22 +347,26 @@ public void ignoredConfig() { Message rawProto = io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC.newBuilder() .setRules(RBAC.newBuilder().setAction(Action.LOG) .putPolicies("policy-name", Policy.newBuilder().build()).build()).build(); - ConfigOrError result = FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto)); + ConfigOrError result = + FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto), getFilterContext()); assertThat(result.config).isEqualTo(RbacConfig.create(null)); } @Test public void testOrderIndependenceOfPolicies() { Message rawProto = buildComplexRbac(ImmutableList.of(1, 2, 3, 4, 5, 6), true); - ConfigOrError ascFirst = FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto)); + ConfigOrError ascFirst = + FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto), getFilterContext()); rawProto = buildComplexRbac(ImmutableList.of(1, 2, 3, 4, 5, 6), false); - ConfigOrError ascLast = FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto)); + ConfigOrError ascLast = + FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto), getFilterContext()); assertThat(ascFirst.config).isEqualTo(ascLast.config); rawProto = buildComplexRbac(ImmutableList.of(6, 5, 4, 3, 2, 1), true); - ConfigOrError decFirst = FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto)); + ConfigOrError decFirst = + FILTER_PROVIDER.parseFilterConfig(Any.pack(rawProto), getFilterContext()); assertThat(ascFirst.config).isEqualTo(decFirst.config); } @@ -390,7 +395,7 @@ private ConfigOrError parseRaw(List permissionList, List principalList) { Message rawProto = buildRbac(permissionList, principalList); Any proto = Any.pack(rawProto); - return FILTER_PROVIDER.parseFilterConfig(proto); + return FILTER_PROVIDER.parseFilterConfig(proto, getFilterContext()); } private io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBAC buildRbac( @@ -458,6 +463,12 @@ private ConfigOrError parseOverride(List permissionList, RBACPerRoute rbacPerRoute = RBACPerRoute.newBuilder().setRbac( buildRbac(permissionList, principalList)).build(); Any proto = Any.pack(rbacPerRoute); - return FILTER_PROVIDER.parseFilterConfigOverride(proto); + return FILTER_PROVIDER.parseFilterConfigOverride(proto, getFilterContext()); + } + + private Filter.FilterContext getFilterContext() { + return Filter.FilterContext.builder().grpcServiceContextProvider(mock( + io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider.class)) + .build(); } } diff --git a/xds/src/test/java/io/grpc/xds/StatefulFilter.java b/xds/src/test/java/io/grpc/xds/StatefulFilter.java index 4ef662c7ccd..7626222dc04 100644 --- a/xds/src/test/java/io/grpc/xds/StatefulFilter.java +++ b/xds/src/test/java/io/grpc/xds/StatefulFilter.java @@ -128,12 +128,13 @@ public synchronized int getCount() { } @Override - public ConfigOrError parseFilterConfig(Message rawProtoMessage) { + public ConfigOrError parseFilterConfig(Message rawProtoMessage, FilterContext context) { return ConfigOrError.fromConfig(Config.fromProto(rawProtoMessage, typeUrl)); } @Override - public ConfigOrError parseFilterConfigOverride(Message rawProtoMessage) { + public ConfigOrError parseFilterConfigOverride( + Message rawProtoMessage, FilterContext context) { return ConfigOrError.fromConfig(Config.fromProto(rawProtoMessage, typeUrl)); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsTestUtils.java b/xds/src/test/java/io/grpc/xds/XdsTestUtils.java index f81957ee311..93113411b5e 100644 --- a/xds/src/test/java/io/grpc/xds/XdsTestUtils.java +++ b/xds/src/test/java/io/grpc/xds/XdsTestUtils.java @@ -88,6 +88,11 @@ public class XdsTestUtils { static final Bootstrapper.ServerInfo EMPTY_BOOTSTRAPPER_SERVER_INFO = Bootstrapper.ServerInfo.create( "td.googleapis.com", InsecureChannelCredentials.create(), false, true, false, false); + static final Bootstrapper.BootstrapInfo EMPTY_BOOTSTRAP = + Bootstrapper.BootstrapInfo.builder() + .servers(com.google.common.collect.ImmutableList.of(EMPTY_BOOTSTRAPPER_SERVER_INFO)) + .node(io.grpc.xds.client.EnvoyProtoData.Node.newBuilder().setId("node-id").build()) + .build(); public static final String ENDPOINT_HOSTNAME = "data-host"; public static final int ENDPOINT_PORT = 1234; @@ -252,7 +257,7 @@ static XdsConfig getDefaultXdsConfig(String serverHostName) RouteConfiguration routeConfiguration = buildRouteConfiguration(serverHostName, RDS_NAME, CLUSTER_NAME); XdsResourceType.Args args = new XdsResourceType.Args( - EMPTY_BOOTSTRAPPER_SERVER_INFO, "0", "0", null, null, null); + EMPTY_BOOTSTRAPPER_SERVER_INFO, "0", "0", EMPTY_BOOTSTRAP, null, null); XdsRouteConfigureResource.RdsUpdate rdsUpdate = XdsRouteConfigureResource.getInstance().doParse(args, routeConfiguration); From 8194fe31a4d6ebf647862102f3f26aa6841ffcd7 Mon Sep 17 00:00:00 2001 From: Saurav Date: Fri, 27 Mar 2026 13:34:44 +0000 Subject: [PATCH 2/3] Fixup 12724: Eliminate GrpcService..Provider classes --- ...otstrapInfoGrpcServiceContextProvider.java | 73 --------- xds/src/main/java/io/grpc/xds/Filter.java | 24 +-- .../java/io/grpc/xds/XdsListenerResource.java | 5 +- .../grpc/xds/XdsRouteConfigureResource.java | 5 +- ...rapInfoGrpcServiceContextProviderTest.java | 139 ------------------ .../java/io/grpc/xds/FaultFilterTest.java | 18 ++- .../grpc/xds/GcpAuthenticationFilterTest.java | 13 +- .../test/java/io/grpc/xds/RbacFilterTest.java | 18 ++- 8 files changed, 58 insertions(+), 237 deletions(-) delete mode 100644 xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java delete mode 100644 xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java diff --git a/xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java b/xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java deleted file mode 100644 index 864108ff431..00000000000 --- a/xds/src/main/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProvider.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2026 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.grpc.xds; - -import io.grpc.NameResolverRegistry; -import io.grpc.xds.client.Bootstrapper.BootstrapInfo; -import io.grpc.xds.client.Bootstrapper.ServerInfo; -import io.grpc.xds.internal.grpcservice.AllowedGrpcService; -import io.grpc.xds.internal.grpcservice.AllowedGrpcServices; -import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContext; -import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Optional; - -/** - * Concrete implementation of {@link GrpcServiceXdsContextProvider} that uses - * {@link BootstrapInfo} data to resolve context. - */ -final class BootstrapInfoGrpcServiceContextProvider - implements GrpcServiceXdsContextProvider { - - private final boolean isTrustedControlPlane; - private final AllowedGrpcServices allowedGrpcServices; - private final NameResolverRegistry nameResolverRegistry; - - BootstrapInfoGrpcServiceContextProvider(BootstrapInfo bootstrapInfo, ServerInfo serverInfo) { - this.isTrustedControlPlane = serverInfo.isTrustedXdsServer(); - this.allowedGrpcServices = bootstrapInfo.allowedGrpcServices() - .filter(AllowedGrpcServices.class::isInstance) - .map(AllowedGrpcServices.class::cast) - .orElse(AllowedGrpcServices.empty()); - this.nameResolverRegistry = NameResolverRegistry.getDefaultRegistry(); - } - - @Override - public GrpcServiceXdsContext getContextForTarget(String targetUri) { - Optional validAllowedGrpcService = - Optional.ofNullable(allowedGrpcServices.services().get(targetUri)); - - boolean isTargetUriSchemeSupported = false; - try { - URI uri = new URI(targetUri); - String scheme = uri.getScheme(); - if (scheme != null) { - isTargetUriSchemeSupported = - nameResolverRegistry.getProviderForScheme(scheme) != null; - } - } catch (URISyntaxException e) { - // Fallback or ignore if not a valid URI - } - - return GrpcServiceXdsContext.create( - isTrustedControlPlane, - validAllowedGrpcService, - isTargetUriSchemeSupported - ); - } -} diff --git a/xds/src/main/java/io/grpc/xds/Filter.java b/xds/src/main/java/io/grpc/xds/Filter.java index 0fa5b8af128..d70b3063a50 100644 --- a/xds/src/main/java/io/grpc/xds/Filter.java +++ b/xds/src/main/java/io/grpc/xds/Filter.java @@ -17,11 +17,13 @@ package io.grpc.xds; +import com.google.auto.value.AutoValue; import com.google.common.base.MoreObjects; import com.google.protobuf.Message; import io.grpc.ClientInterceptor; import io.grpc.ServerInterceptor; -import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; +import io.grpc.xds.client.Bootstrapper.ServerInfo; import java.io.Closeable; import java.util.Objects; import java.util.concurrent.ScheduledExecutorService; @@ -130,21 +132,23 @@ default ServerInterceptor buildServerInterceptor( default void close() {} /** Context carrying dynamic metadata for a filter. */ - @com.google.auto.value.AutoValue - abstract class FilterContext { - public abstract GrpcServiceXdsContextProvider grpcServiceContextProvider(); + @AutoValue + abstract static class FilterContext { + abstract BootstrapInfo bootstrapInfo(); - public static Builder builder() { + abstract ServerInfo serverInfo(); + + static Builder builder() { return new AutoValue_Filter_FilterContext.Builder(); } + @AutoValue.Builder + abstract static class Builder { + abstract Builder bootstrapInfo(BootstrapInfo info); - @com.google.auto.value.AutoValue.Builder - public abstract static class Builder { - public abstract Builder grpcServiceContextProvider( - GrpcServiceXdsContextProvider provider); + abstract Builder serverInfo(ServerInfo info); - public abstract FilterContext build(); + abstract FilterContext build(); } } diff --git a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java index 4aff4a7f2ad..4bf1b0066c2 100644 --- a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java @@ -618,10 +618,9 @@ static StructOrError parseHttpFilter( isForClient ? "client" : "server")); } - BootstrapInfoGrpcServiceContextProvider contextProvider = - new BootstrapInfoGrpcServiceContextProvider(args.getBootstrapInfo(), args.getServerInfo()); Filter.FilterContext filterContext = Filter.FilterContext.builder() - .grpcServiceContextProvider(contextProvider) + .bootstrapInfo(args.getBootstrapInfo()) + .serverInfo(args.getServerInfo()) .build(); ConfigOrError filterConfig = provider.parseFilterConfig(rawConfig, filterContext); diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java index 0bb0c48cd65..890a2936861 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -212,10 +212,9 @@ private static StructOrError parseVirtualHost( static StructOrError> parseOverrideFilterConfigs( Map rawFilterConfigMap, FilterRegistry filterRegistry, XdsResourceType.Args args) { - BootstrapInfoGrpcServiceContextProvider grpcServiceContextProvider = - new BootstrapInfoGrpcServiceContextProvider(args.getBootstrapInfo(), args.getServerInfo()); Filter.FilterContext context = Filter.FilterContext.builder() - .grpcServiceContextProvider(grpcServiceContextProvider) + .bootstrapInfo(args.getBootstrapInfo()) + .serverInfo(args.getServerInfo()) .build(); Map overrideConfigs = new HashMap<>(); for (String name : rawFilterConfigMap.keySet()) { diff --git a/xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java b/xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java deleted file mode 100644 index ab42f634daa..00000000000 --- a/xds/src/test/java/io/grpc/xds/BootstrapInfoGrpcServiceContextProviderTest.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2026 The gRPC Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.grpc.xds; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.collect.ImmutableList; -import io.grpc.ChannelCredentials; -import io.grpc.InsecureChannelCredentials; -import io.grpc.xds.client.Bootstrapper.BootstrapInfo; -import io.grpc.xds.client.Bootstrapper.ServerInfo; -import io.grpc.xds.client.EnvoyProtoData; -import io.grpc.xds.internal.grpcservice.AllowedGrpcService; -import io.grpc.xds.internal.grpcservice.AllowedGrpcServices; -import io.grpc.xds.internal.grpcservice.ChannelCredsConfig; -import io.grpc.xds.internal.grpcservice.ConfiguredChannelCredentials; -import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContext; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** - * Unit tests for {@link BootstrapInfoGrpcServiceContextProvider}. - */ -@RunWith(JUnit4.class) -public class BootstrapInfoGrpcServiceContextProviderTest { - - private static final ChannelCredentials CREDENTIALS = InsecureChannelCredentials.create(); - private static final ChannelCredsConfig DUMMY_CONFIG = () -> "dummy"; - private static final EnvoyProtoData.Node DUMMY_NODE = - EnvoyProtoData.Node.newBuilder().setId("node-id").build(); - - private static final BootstrapInfo DUMMY_BOOTSTRAP = BootstrapInfo.builder() - .servers(ImmutableList.of()) - .node(DUMMY_NODE) - .build(); - - private static ServerInfo createServerInfo(boolean isTrusted) { - return ServerInfo.create("xds:///any", CREDENTIALS, false, isTrusted, false, false); - } - - @Test - public void getContextForTarget_trustedServer() { - ServerInfo serverInfo = createServerInfo(true); - BootstrapInfoGrpcServiceContextProvider provider = - new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, serverInfo); - - GrpcServiceXdsContext context = provider.getContextForTarget("xds:///any"); - assertThat(context.isTrustedControlPlane()).isTrue(); - } - - @Test - public void getContextForTarget_untrustedServer() { - ServerInfo serverInfo = createServerInfo(false); - BootstrapInfoGrpcServiceContextProvider provider = - new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, serverInfo); - - GrpcServiceXdsContext context = provider.getContextForTarget("xds:///any"); - assertThat(context.isTrustedControlPlane()).isFalse(); - } - - @Test - public void getContextForTarget_allowedGrpcServices() { - ConfiguredChannelCredentials creds = ConfiguredChannelCredentials.create( - CREDENTIALS, DUMMY_CONFIG); - AllowedGrpcService allowedService = AllowedGrpcService.builder() - .configuredChannelCredentials(creds) - .build(); - - Map servicesMap = new HashMap<>(); - servicesMap.put("xds:///target1", allowedService); - AllowedGrpcServices allowedGrpcServices = AllowedGrpcServices.create(servicesMap); - - BootstrapInfo bootstrapInfo = BootstrapInfo.builder() - .servers(ImmutableList.of()) - .node(DUMMY_NODE) - .allowedGrpcServices(Optional.of(allowedGrpcServices)) - .build(); - - BootstrapInfoGrpcServiceContextProvider provider = - new BootstrapInfoGrpcServiceContextProvider(bootstrapInfo, createServerInfo(false)); - - GrpcServiceXdsContext context = provider.getContextForTarget("xds:///target1"); - assertThat(context.validAllowedGrpcService().isPresent()).isTrue(); - assertThat(context.validAllowedGrpcService().get()).isEqualTo(allowedService); - - // Target not in map - GrpcServiceXdsContext context2 = provider.getContextForTarget("xds:///target2"); - assertThat(context2.validAllowedGrpcService().isPresent()).isFalse(); - } - - @Test - public void getContextForTarget_schemeSupported() { - BootstrapInfoGrpcServiceContextProvider provider = - new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, createServerInfo(false)); - - assertThat(provider.getContextForTarget("dns:///foo").isTargetUriSchemeSupported()).isTrue(); - assertThat(provider.getContextForTarget("unknown:///foo").isTargetUriSchemeSupported()) - .isFalse(); - } - - @Test - public void getContextForTarget_invalidUri() { - BootstrapInfoGrpcServiceContextProvider provider = - new BootstrapInfoGrpcServiceContextProvider(DUMMY_BOOTSTRAP, createServerInfo(false)); - - GrpcServiceXdsContext context = provider.getContextForTarget("invalid:uri:with:colons"); - assertThat(context.isTargetUriSchemeSupported()).isFalse(); - } - - @Test - public void getContextForTarget_invalidAllowedGrpcServicesTypeFallbackToEmpty() { - BootstrapInfo bootstrapInfo = BootstrapInfo.builder().servers(ImmutableList.of()) - .node(DUMMY_NODE).allowedGrpcServices(Optional.of("invalid_type_string")).build(); - - BootstrapInfoGrpcServiceContextProvider provider = - new BootstrapInfoGrpcServiceContextProvider(bootstrapInfo, createServerInfo(false)); - - GrpcServiceXdsContext context = provider.getContextForTarget("xds:///any"); - assertThat(context.validAllowedGrpcService().isPresent()).isFalse(); - } -} diff --git a/xds/src/test/java/io/grpc/xds/FaultFilterTest.java b/xds/src/test/java/io/grpc/xds/FaultFilterTest.java index f74e39e727f..494df4bed92 100644 --- a/xds/src/test/java/io/grpc/xds/FaultFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/FaultFilterTest.java @@ -17,7 +17,6 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; import com.google.protobuf.Any; import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort; @@ -27,7 +26,10 @@ import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType; import io.grpc.Status.Code; import io.grpc.internal.GrpcUtil; -import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; +import io.grpc.xds.client.Bootstrapper.ServerInfo; +import io.grpc.xds.client.EnvoyProtoData.Node; +import java.util.Collections; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -49,12 +51,14 @@ public void parseFaultAbort_convertHttpStatus() { HTTPFault.newBuilder().setAbort(FaultAbort.newBuilder().setHttpStatus(404)).build()); FaultConfig faultConfig = FILTER_PROVIDER.parseFilterConfig( rawConfig, getFilterContext()).config; + assertThat(faultConfig.faultAbort()).isNotNull(); assertThat(faultConfig.faultAbort().status().getCode()) .isEqualTo(GrpcUtil.httpStatusToGrpcStatus(404).getCode()); FaultConfig faultConfigOverride = FILTER_PROVIDER.parseFilterConfigOverride( rawConfig, getFilterContext()).config; + assertThat(faultConfigOverride.faultAbort()).isNotNull(); assertThat(faultConfigOverride.faultAbort().status().getCode()) .isEqualTo(GrpcUtil.httpStatusToGrpcStatus(404).getCode()); } @@ -103,6 +107,14 @@ public void parseFaultAbort_withGrpcStatus() { private static Filter.FilterContext getFilterContext() { return Filter.FilterContext.builder() - .grpcServiceContextProvider(mock(GrpcServiceXdsContextProvider.class)).build(); + .bootstrapInfo(BootstrapInfo.builder() + .servers(Collections.singletonList( + ServerInfo.create( + "test_target", Collections.emptyMap()))) + .node(Node.newBuilder().build()) + .build()) + .serverInfo(ServerInfo.create( + "test_target", Collections.emptyMap(), false, true, false, false)) + .build(); } } diff --git a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java index 2a1ee36d0e9..579701580b2 100644 --- a/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/GcpAuthenticationFilterTest.java @@ -65,10 +65,12 @@ import io.grpc.xds.XdsEndpointResource.EdsUpdate; import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; +import io.grpc.xds.client.Bootstrapper.ServerInfo; +import io.grpc.xds.client.EnvoyProtoData.Node; import io.grpc.xds.client.Locality; import io.grpc.xds.client.XdsResourceType; import io.grpc.xds.client.XdsResourceType.ResourceInvalidException; -import io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider; import java.io.IOException; import java.util.Collections; import java.util.HashMap; @@ -526,7 +528,14 @@ private static CdsUpdate getCdsUpdateWithIncorrectAudienceWrapper() throws IOExc private static Filter.FilterContext getFilterContext() { return Filter.FilterContext.builder() - .grpcServiceContextProvider(Mockito.mock(GrpcServiceXdsContextProvider.class)) + .bootstrapInfo(BootstrapInfo.builder() + .servers(Collections.singletonList( + ServerInfo.create( + "test_target", Collections.emptyMap()))) + .node(Node.newBuilder().build()) + .build()) + .serverInfo(ServerInfo.create( + "test_target", Collections.emptyMap(), false, true, false, false)) .build(); } } diff --git a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java index ca59ab4e524..1dd0d93b119 100644 --- a/xds/src/test/java/io/grpc/xds/RbacFilterTest.java +++ b/xds/src/test/java/io/grpc/xds/RbacFilterTest.java @@ -54,6 +54,9 @@ import io.grpc.Status; import io.grpc.testing.TestMethodDescriptors; import io.grpc.xds.Filter.FilterConfig; +import io.grpc.xds.client.Bootstrapper.BootstrapInfo; +import io.grpc.xds.client.Bootstrapper.ServerInfo; +import io.grpc.xds.client.EnvoyProtoData.Node; import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine; import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AlwaysTrueMatcher; import io.grpc.xds.internal.rbac.engine.GrpcAuthorizationEngine.AuthConfig; @@ -120,7 +123,7 @@ public void ipPortParser() { } @Test - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings("unchecked") public void portRangeParser() { List permissionList = Arrays.asList( Permission.newBuilder().setDestinationPortRange( @@ -467,8 +470,15 @@ private ConfigOrError parseOverride(List permissionList, } private Filter.FilterContext getFilterContext() { - return Filter.FilterContext.builder().grpcServiceContextProvider(mock( - io.grpc.xds.internal.grpcservice.GrpcServiceXdsContextProvider.class)) - .build(); + return Filter.FilterContext.builder() + .bootstrapInfo(BootstrapInfo.builder() + .servers(Collections.singletonList( + ServerInfo.create( + "test_target", Collections.emptyMap()))) + .node(Node.newBuilder().build()) + .build()) + .serverInfo(ServerInfo.create( + "test_target", Collections.emptyMap(), false, true, false, false)) + .build(); } } From 51bb4ab4fdc06555717cab86cb6d594d550aa690 Mon Sep 17 00:00:00 2001 From: Saurav Date: Wed, 1 Apr 2026 08:24:28 +0000 Subject: [PATCH 3/3] Fixup #12724: Share fractional matcher parser --- .../grpc/xds/XdsRouteConfigureResource.java | 32 ++++--------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java index 890a2936861..dc213a452d7 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -36,7 +36,6 @@ import io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin; import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; -import io.envoyproxy.envoy.type.v3.FractionalPercent; import io.grpc.Status; import io.grpc.internal.GrpcUtil; import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; @@ -336,12 +335,12 @@ static StructOrError parseRouteMatch( FractionMatcher fractionMatch = null; if (proto.hasRuntimeFraction()) { - StructOrError parsedFraction = - parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue()); - if (parsedFraction.getErrorDetail() != null) { - return StructOrError.fromError(parsedFraction.getErrorDetail()); + try { + fractionMatch = + MatcherParser.parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue()); + } catch (IllegalArgumentException e) { + return StructOrError.fromError(e.getMessage()); } - fractionMatch = parsedFraction.getStruct(); } List headerMatchers = new ArrayList<>(); @@ -382,26 +381,7 @@ static StructOrError parsePathMatcher( } } - private static StructOrError parseFractionMatcher(FractionalPercent proto) { - int numerator = proto.getNumerator(); - int denominator = 0; - switch (proto.getDenominator()) { - case HUNDRED: - denominator = 100; - break; - case TEN_THOUSAND: - denominator = 10_000; - break; - case MILLION: - denominator = 1_000_000; - break; - case UNRECOGNIZED: - default: - return StructOrError.fromError( - "Unrecognized fractional percent denominator: " + proto.getDenominator()); - } - return StructOrError.fromStruct(FractionMatcher.create(numerator, denominator)); - } + @VisibleForTesting static StructOrError parseHeaderMatcher(