Skip to content

Commit 7b0a7eb

Browse files
authored
Allow injecting bootstrap info into xDS Filter API for config parsing (grpc#12724)
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.
1 parent 8c8dd2a commit 7b0a7eb

13 files changed

Lines changed: 198 additions & 88 deletions

xds/src/main/java/io/grpc/xds/FaultFilter.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ public FaultFilter newInstance(String name) {
104104
}
105105

106106
@Override
107-
public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
107+
public ConfigOrError<FaultConfig> parseFilterConfig(
108+
Message rawProtoMessage, FilterConfigParseContext context) {
108109
HTTPFault httpFaultProto;
109110
if (!(rawProtoMessage instanceof Any)) {
110111
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
@@ -119,8 +120,9 @@ public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
119120
}
120121

121122
@Override
122-
public ConfigOrError<FaultConfig> parseFilterConfigOverride(Message rawProtoMessage) {
123-
return parseFilterConfig(rawProtoMessage);
123+
public ConfigOrError<FaultConfig> parseFilterConfigOverride(
124+
Message rawProtoMessage, FilterConfigParseContext context) {
125+
return parseFilterConfig(rawProtoMessage, context);
124126
}
125127

126128
private static ConfigOrError<FaultConfig> parseHttpFault(HTTPFault httpFault) {

xds/src/main/java/io/grpc/xds/Filter.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616

1717
package io.grpc.xds;
1818

19+
20+
import com.google.auto.value.AutoValue;
1921
import com.google.common.base.MoreObjects;
2022
import com.google.protobuf.Message;
2123
import io.grpc.ClientInterceptor;
2224
import io.grpc.ServerInterceptor;
25+
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
26+
import io.grpc.xds.client.Bootstrapper.ServerInfo;
2327
import java.io.Closeable;
2428
import java.util.Objects;
2529
import java.util.concurrent.ScheduledExecutorService;
@@ -93,13 +97,15 @@ default boolean isServerFilter() {
9397
* Parses the top-level filter config from raw proto message. The message may be either a {@link
9498
* com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
9599
*/
96-
ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage);
100+
ConfigOrError<? extends FilterConfig> parseFilterConfig(
101+
Message rawProtoMessage, FilterConfigParseContext context);
97102

98103
/**
99104
* Parses the per-filter override filter config from raw proto message. The message may be
100105
* either a {@link com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
101106
*/
102-
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(Message rawProtoMessage);
107+
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
108+
Message rawProtoMessage, FilterConfigParseContext context);
103109
}
104110

105111
/** Uses the FilterConfigs produced above to produce an HTTP filter interceptor for clients. */
@@ -125,6 +131,27 @@ default ServerInterceptor buildServerInterceptor(
125131
@Override
126132
default void close() {}
127133

134+
/** Context carrying dynamic metadata for a filter. */
135+
@AutoValue
136+
abstract static class FilterConfigParseContext {
137+
abstract BootstrapInfo bootstrapInfo();
138+
139+
abstract ServerInfo serverInfo();
140+
141+
static Builder builder() {
142+
return new AutoValue_Filter_FilterConfigParseContext.Builder();
143+
}
144+
145+
@AutoValue.Builder
146+
abstract static class Builder {
147+
abstract Builder bootstrapInfo(BootstrapInfo info);
148+
149+
abstract Builder serverInfo(ServerInfo info);
150+
151+
abstract FilterConfigParseContext build();
152+
}
153+
}
154+
128155
/** Filter config with instance name. */
129156
final class NamedFilterConfig {
130157
// filter instance name

xds/src/main/java/io/grpc/xds/GcpAuthenticationFilter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ public GcpAuthenticationFilter newInstance(String name) {
8686
}
8787

8888
@Override
89-
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProtoMessage) {
89+
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(
90+
Message rawProtoMessage, FilterConfigParseContext context) {
9091
GcpAuthnFilterConfig gcpAuthnProto;
9192
if (!(rawProtoMessage instanceof Any)) {
9293
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
@@ -121,8 +122,8 @@ public ConfigOrError<GcpAuthenticationConfig> parseFilterConfig(Message rawProto
121122

122123
@Override
123124
public ConfigOrError<GcpAuthenticationConfig> parseFilterConfigOverride(
124-
Message rawProtoMessage) {
125-
return parseFilterConfig(rawProtoMessage);
125+
Message rawProtoMessage, FilterConfigParseContext context) {
126+
return parseFilterConfig(rawProtoMessage, context);
126127
}
127128
}
128129

xds/src/main/java/io/grpc/xds/RbacFilter.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ public RbacFilter newInstance(String name) {
9494
}
9595

9696
@Override
97-
public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
97+
public ConfigOrError<RbacConfig> parseFilterConfig(
98+
Message rawProtoMessage, FilterConfigParseContext context) {
9899
RBAC rbacProto;
99100
if (!(rawProtoMessage instanceof Any)) {
100101
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
@@ -109,7 +110,8 @@ public ConfigOrError<RbacConfig> parseFilterConfig(Message rawProtoMessage) {
109110
}
110111

111112
@Override
112-
public ConfigOrError<RbacConfig> parseFilterConfigOverride(Message rawProtoMessage) {
113+
public ConfigOrError<RbacConfig> parseFilterConfigOverride(
114+
Message rawProtoMessage, FilterConfigParseContext context) {
113115
RBACPerRoute rbacPerRoute;
114116
if (!(rawProtoMessage instanceof Any)) {
115117
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());

xds/src/main/java/io/grpc/xds/RouterFilter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ public RouterFilter newInstance(String name) {
6161
}
6262

6363
@Override
64-
public ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage) {
64+
public ConfigOrError<? extends FilterConfig> parseFilterConfig(
65+
Message rawProtoMessage, FilterConfigParseContext context) {
6566
return ConfigOrError.fromConfig(ROUTER_CONFIG);
6667
}
6768

6869
@Override
6970
public ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(
70-
Message rawProtoMessage) {
71+
Message rawProtoMessage, FilterConfigParseContext context) {
7172
return ConfigOrError.fromError("Router Filter should not have override config");
7273
}
7374
}

xds/src/main/java/io/grpc/xds/XdsListenerResource.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager(
527527
"HttpConnectionManager contains duplicate HttpFilter: " + filterName);
528528
}
529529
StructOrError<Filter.FilterConfig> filterConfig =
530-
parseHttpFilter(httpFilter, filterRegistry, isForClient);
530+
parseHttpFilter(httpFilter, filterRegistry, isForClient, args);
531531
if ((i == proto.getHttpFiltersCount() - 1)
532532
&& (filterConfig == null || !isTerminalFilter(filterConfig.getStruct()))) {
533533
throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: "
@@ -581,7 +581,8 @@ private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) {
581581
@Nullable // Returns null if the filter is optional but not supported.
582582
static StructOrError<Filter.FilterConfig> parseHttpFilter(
583583
io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter
584-
httpFilter, FilterRegistry filterRegistry, boolean isForClient) {
584+
httpFilter, FilterRegistry filterRegistry, boolean isForClient,
585+
XdsResourceType.Args args) {
585586
String filterName = httpFilter.getName();
586587
boolean isOptional = httpFilter.getIsOptional();
587588
if (!httpFilter.hasTypedConfig()) {
@@ -616,7 +617,13 @@ static StructOrError<Filter.FilterConfig> parseHttpFilter(
616617
"HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " + (
617618
isForClient ? "client" : "server"));
618619
}
619-
ConfigOrError<? extends FilterConfig> filterConfig = provider.parseFilterConfig(rawConfig);
620+
621+
Filter.FilterConfigParseContext filterContext = Filter.FilterConfigParseContext.builder()
622+
.bootstrapInfo(args.getBootstrapInfo())
623+
.serverInfo(args.getServerInfo())
624+
.build();
625+
ConfigOrError<? extends FilterConfig> filterConfig =
626+
provider.parseFilterConfig(rawConfig, filterContext);
620627
if (filterConfig.errorDetail != null) {
621628
return StructOrError.fromError(
622629
"Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail);

xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin;
3737
import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff;
3838
import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
39-
import io.envoyproxy.envoy.type.v3.FractionalPercent;
4039
import io.grpc.Status;
4140
import io.grpc.internal.GrpcUtil;
4241
import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig;
@@ -198,7 +197,7 @@ private static StructOrError<VirtualHost> parseVirtualHost(
198197
routes.add(route.getStruct());
199198
}
200199
StructOrError<Map<String, Filter.FilterConfig>> overrideConfigs =
201-
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry);
200+
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args);
202201
if (overrideConfigs.getErrorDetail() != null) {
203202
return StructOrError.fromError(
204203
"VirtualHost [" + proto.getName() + "] contains invalid HttpFilter config: "
@@ -210,7 +209,12 @@ private static StructOrError<VirtualHost> parseVirtualHost(
210209

211210
@VisibleForTesting
212211
static StructOrError<Map<String, FilterConfig>> parseOverrideFilterConfigs(
213-
Map<String, Any> rawFilterConfigMap, FilterRegistry filterRegistry) {
212+
Map<String, Any> rawFilterConfigMap, FilterRegistry filterRegistry,
213+
XdsResourceType.Args args) {
214+
Filter.FilterConfigParseContext context = Filter.FilterConfigParseContext.builder()
215+
.bootstrapInfo(args.getBootstrapInfo())
216+
.serverInfo(args.getServerInfo())
217+
.build();
214218
Map<String, FilterConfig> overrideConfigs = new HashMap<>();
215219
for (String name : rawFilterConfigMap.keySet()) {
216220
Any anyConfig = rawFilterConfigMap.get(name);
@@ -254,7 +258,7 @@ static StructOrError<Map<String, FilterConfig>> parseOverrideFilterConfigs(
254258
"HttpFilter [" + name + "](" + typeUrl + ") is required but unsupported");
255259
}
256260
ConfigOrError<? extends Filter.FilterConfig> filterConfig =
257-
provider.parseFilterConfigOverride(rawConfig);
261+
provider.parseFilterConfigOverride(rawConfig, context);
258262
if (filterConfig.errorDetail != null) {
259263
return StructOrError.fromError(
260264
"Invalid filter config for HttpFilter [" + name + "]: " + filterConfig.errorDetail);
@@ -281,7 +285,7 @@ static StructOrError<Route> parseRoute(
281285
}
282286

283287
StructOrError<Map<String, FilterConfig>> overrideConfigsOrError =
284-
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry);
288+
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args);
285289
if (overrideConfigsOrError.getErrorDetail() != null) {
286290
return StructOrError.fromError(
287291
"Route [" + proto.getName() + "] contains invalid HttpFilter config: "
@@ -331,12 +335,12 @@ static StructOrError<RouteMatch> parseRouteMatch(
331335

332336
FractionMatcher fractionMatch = null;
333337
if (proto.hasRuntimeFraction()) {
334-
StructOrError<FractionMatcher> parsedFraction =
335-
parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue());
336-
if (parsedFraction.getErrorDetail() != null) {
337-
return StructOrError.fromError(parsedFraction.getErrorDetail());
338+
try {
339+
fractionMatch =
340+
MatcherParser.parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue());
341+
} catch (IllegalArgumentException e) {
342+
return StructOrError.fromError(e.getMessage());
338343
}
339-
fractionMatch = parsedFraction.getStruct();
340344
}
341345

342346
List<HeaderMatcher> headerMatchers = new ArrayList<>();
@@ -377,26 +381,7 @@ static StructOrError<PathMatcher> parsePathMatcher(
377381
}
378382
}
379383

380-
private static StructOrError<FractionMatcher> parseFractionMatcher(FractionalPercent proto) {
381-
int numerator = proto.getNumerator();
382-
int denominator = 0;
383-
switch (proto.getDenominator()) {
384-
case HUNDRED:
385-
denominator = 100;
386-
break;
387-
case TEN_THOUSAND:
388-
denominator = 10_000;
389-
break;
390-
case MILLION:
391-
denominator = 1_000_000;
392-
break;
393-
case UNRECOGNIZED:
394-
default:
395-
return StructOrError.fromError(
396-
"Unrecognized fractional percent denominator: " + proto.getDenominator());
397-
}
398-
return StructOrError.fromStruct(FractionMatcher.create(numerator, denominator));
399-
}
384+
400385

401386
@VisibleForTesting
402387
static StructOrError<HeaderMatcher> parseHeaderMatcher(
@@ -490,7 +475,7 @@ static StructOrError<RouteAction> parseRouteAction(
490475
for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight
491476
: clusterWeights) {
492477
StructOrError<ClusterWeight> clusterWeightOrError =
493-
parseClusterWeight(clusterWeight, filterRegistry);
478+
parseClusterWeight(clusterWeight, filterRegistry, args);
494479
if (clusterWeightOrError.getErrorDetail() != null) {
495480
return StructOrError.fromError("RouteAction contains invalid ClusterWeight: "
496481
+ clusterWeightOrError.getErrorDetail());
@@ -599,9 +584,9 @@ private static StructOrError<VirtualHost.Route.RouteAction.RetryPolicy> parseRet
599584
@VisibleForTesting
600585
static StructOrError<VirtualHost.Route.RouteAction.ClusterWeight> parseClusterWeight(
601586
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto,
602-
FilterRegistry filterRegistry) {
587+
FilterRegistry filterRegistry, XdsResourceType.Args args) {
603588
StructOrError<Map<String, Filter.FilterConfig>> overrideConfigs =
604-
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry);
589+
parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry, args);
605590
if (overrideConfigs.getErrorDetail() != null) {
606591
return StructOrError.fromError(
607592
"ClusterWeight [" + proto.getName() + "] contains invalid HttpFilter config: "

xds/src/test/java/io/grpc/xds/FaultFilterTest.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType;
2727
import io.grpc.Status.Code;
2828
import io.grpc.internal.GrpcUtil;
29+
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
30+
import io.grpc.xds.client.Bootstrapper.ServerInfo;
31+
import io.grpc.xds.client.EnvoyProtoData.Node;
32+
import java.util.Collections;
2933
import org.junit.Test;
3034
import org.junit.runner.RunWith;
3135
import org.junit.runners.JUnit4;
@@ -45,11 +49,16 @@ public void filterType_clientOnly() {
4549
public void parseFaultAbort_convertHttpStatus() {
4650
Any rawConfig = Any.pack(
4751
HTTPFault.newBuilder().setAbort(FaultAbort.newBuilder().setHttpStatus(404)).build());
48-
FaultConfig faultConfig = FILTER_PROVIDER.parseFilterConfig(rawConfig).config;
52+
FaultConfig faultConfig = FILTER_PROVIDER.parseFilterConfig(
53+
rawConfig, getFilterContext()).config;
54+
assertThat(faultConfig.faultAbort()).isNotNull();
4955
assertThat(faultConfig.faultAbort().status().getCode())
5056
.isEqualTo(GrpcUtil.httpStatusToGrpcStatus(404).getCode());
5157

52-
FaultConfig faultConfigOverride = FILTER_PROVIDER.parseFilterConfigOverride(rawConfig).config;
58+
FaultConfig faultConfigOverride =
59+
FILTER_PROVIDER.parseFilterConfigOverride(
60+
rawConfig, getFilterContext()).config;
61+
assertThat(faultConfigOverride.faultAbort()).isNotNull();
5362
assertThat(faultConfigOverride.faultAbort().status().getCode())
5463
.isEqualTo(GrpcUtil.httpStatusToGrpcStatus(404).getCode());
5564
}
@@ -95,4 +104,17 @@ public void parseFaultAbort_withGrpcStatus() {
95104
.isEqualTo(FaultConfig.FractionalPercent.DenominatorType.MILLION);
96105
assertThat(faultAbort.status().getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
97106
}
107+
108+
private static Filter.FilterConfigParseContext getFilterContext() {
109+
return Filter.FilterConfigParseContext.builder()
110+
.bootstrapInfo(BootstrapInfo.builder()
111+
.servers(Collections.singletonList(
112+
ServerInfo.create(
113+
"test_target", Collections.emptyMap())))
114+
.node(Node.newBuilder().build())
115+
.build())
116+
.serverInfo(ServerInfo.create(
117+
"test_target", Collections.emptyMap(), false, true, false, false))
118+
.build();
119+
}
98120
}

0 commit comments

Comments
 (0)