Skip to content

Commit 2b87b01

Browse files
authored
xds: Change how xDS filters are created by introducing Filter.Provider (#11883)
This is the first step towards supporting filter state retention in Java. The mechanism will be similar to the one described in [A83] (https://github.com/grpc/proposal/blob/master/A83-xds-gcp-authn-filter.md#filter-call-credentials-cache) for C-core, and will serve the same purpose. However, the implementation details are very different due to the different nature of xDS HTTP filter support in C-core and Java. In Java, xDS HTTP filters are backed by classes implementing `io.grpc.xds.Filter`, from here just called "Filters". To support Filter state retention (next PR), Java's xDS implementation must be able to create unique Filter instances per: - Per HCM `envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager` - Per filter name as specified in `envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter.name` This PR **does not** implements Filter state retention, but lays the groundwork for it by changing how filters are registered and instantiated. To achieve this, all existing Filter classes had to be updated to the new instantiation mechanism described below. Prior to these this PR, Filters had no livecycle. FilterRegistry provided singleton instances for a given typeUrl. This PR introduces a new interface `Filter.Provider`, which instantiates Filter classes. All functionality that doesn't need an instance of a Filter is moved to the Filter.Provider. This includes parsing filter config proto into FilterConfig and determining the filter kind (client-side, server-side, or both). This PR is limited to refactoring, and there's no changes to the existing behavior. Note that all Filter Providers still return singleton Filter instances. However, with this PR, it is now possible to create Providers that return a new Filter instance each time `newInstance` is called.
1 parent 7136070 commit 2b87b01

18 files changed

+579
-410
lines changed

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

+91-79
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
import io.grpc.internal.GrpcUtil;
4646
import io.grpc.xds.FaultConfig.FaultAbort;
4747
import io.grpc.xds.FaultConfig.FaultDelay;
48-
import io.grpc.xds.Filter.ClientInterceptorBuilder;
4948
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
5049
import java.util.Locale;
5150
import java.util.concurrent.Executor;
@@ -56,10 +55,11 @@
5655
import javax.annotation.Nullable;
5756

5857
/** HttpFault filter implementation. */
59-
final class FaultFilter implements Filter, ClientInterceptorBuilder {
58+
final class FaultFilter implements Filter {
6059

61-
static final FaultFilter INSTANCE =
60+
private static final FaultFilter INSTANCE =
6261
new FaultFilter(ThreadSafeRandomImpl.instance, new AtomicLong());
62+
6363
@VisibleForTesting
6464
static final Metadata.Key<String> HEADER_DELAY_KEY =
6565
Metadata.Key.of("x-envoy-fault-delay-request", Metadata.ASCII_STRING_MARSHALLER);
@@ -87,96 +87,108 @@ final class FaultFilter implements Filter, ClientInterceptorBuilder {
8787
this.activeFaultCounter = activeFaultCounter;
8888
}
8989

90-
@Override
91-
public String[] typeUrls() {
92-
return new String[] { TYPE_URL };
93-
}
94-
95-
@Override
96-
public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
97-
HTTPFault httpFaultProto;
98-
if (!(rawProtoMessage instanceof Any)) {
99-
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
90+
static final class Provider implements Filter.Provider {
91+
@Override
92+
public String[] typeUrls() {
93+
return new String[]{TYPE_URL};
10094
}
101-
Any anyMessage = (Any) rawProtoMessage;
102-
try {
103-
httpFaultProto = anyMessage.unpack(HTTPFault.class);
104-
} catch (InvalidProtocolBufferException e) {
105-
return ConfigOrError.fromError("Invalid proto: " + e);
95+
96+
@Override
97+
public boolean isClientFilter() {
98+
return true;
10699
}
107-
return parseHttpFault(httpFaultProto);
108-
}
109100

110-
private static ConfigOrError<FaultConfig> parseHttpFault(HTTPFault httpFault) {
111-
FaultDelay faultDelay = null;
112-
FaultAbort faultAbort = null;
113-
if (httpFault.hasDelay()) {
114-
faultDelay = parseFaultDelay(httpFault.getDelay());
101+
@Override
102+
public FaultFilter newInstance() {
103+
return INSTANCE;
115104
}
116-
if (httpFault.hasAbort()) {
117-
ConfigOrError<FaultAbort> faultAbortOrError = parseFaultAbort(httpFault.getAbort());
118-
if (faultAbortOrError.errorDetail != null) {
119-
return ConfigOrError.fromError(
120-
"HttpFault contains invalid FaultAbort: " + faultAbortOrError.errorDetail);
105+
106+
@Override
107+
public ConfigOrError<FaultConfig> parseFilterConfig(Message rawProtoMessage) {
108+
HTTPFault httpFaultProto;
109+
if (!(rawProtoMessage instanceof Any)) {
110+
return ConfigOrError.fromError("Invalid config type: " + rawProtoMessage.getClass());
121111
}
122-
faultAbort = faultAbortOrError.config;
123-
}
124-
Integer maxActiveFaults = null;
125-
if (httpFault.hasMaxActiveFaults()) {
126-
maxActiveFaults = httpFault.getMaxActiveFaults().getValue();
127-
if (maxActiveFaults < 0) {
128-
maxActiveFaults = Integer.MAX_VALUE;
112+
Any anyMessage = (Any) rawProtoMessage;
113+
try {
114+
httpFaultProto = anyMessage.unpack(HTTPFault.class);
115+
} catch (InvalidProtocolBufferException e) {
116+
return ConfigOrError.fromError("Invalid proto: " + e);
129117
}
118+
return parseHttpFault(httpFaultProto);
130119
}
131-
return ConfigOrError.fromConfig(FaultConfig.create(faultDelay, faultAbort, maxActiveFaults));
132-
}
133120

134-
private static FaultDelay parseFaultDelay(
135-
io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay faultDelay) {
136-
FaultConfig.FractionalPercent percent = parsePercent(faultDelay.getPercentage());
137-
if (faultDelay.hasHeaderDelay()) {
138-
return FaultDelay.forHeader(percent);
121+
@Override
122+
public ConfigOrError<FaultConfig> parseFilterConfigOverride(Message rawProtoMessage) {
123+
return parseFilterConfig(rawProtoMessage);
139124
}
140-
return FaultDelay.forFixedDelay(Durations.toNanos(faultDelay.getFixedDelay()), percent);
141-
}
142125

143-
@VisibleForTesting
144-
static ConfigOrError<FaultAbort> parseFaultAbort(
145-
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort faultAbort) {
146-
FaultConfig.FractionalPercent percent = parsePercent(faultAbort.getPercentage());
147-
switch (faultAbort.getErrorTypeCase()) {
148-
case HEADER_ABORT:
149-
return ConfigOrError.fromConfig(FaultAbort.forHeader(percent));
150-
case HTTP_STATUS:
151-
return ConfigOrError.fromConfig(FaultAbort.forStatus(
152-
GrpcUtil.httpStatusToGrpcStatus(faultAbort.getHttpStatus()), percent));
153-
case GRPC_STATUS:
154-
return ConfigOrError.fromConfig(FaultAbort.forStatus(
155-
Status.fromCodeValue(faultAbort.getGrpcStatus()), percent));
156-
case ERRORTYPE_NOT_SET:
157-
default:
158-
return ConfigOrError.fromError(
159-
"Unknown error type case: " + faultAbort.getErrorTypeCase());
126+
private static ConfigOrError<FaultConfig> parseHttpFault(HTTPFault httpFault) {
127+
FaultDelay faultDelay = null;
128+
FaultAbort faultAbort = null;
129+
if (httpFault.hasDelay()) {
130+
faultDelay = parseFaultDelay(httpFault.getDelay());
131+
}
132+
if (httpFault.hasAbort()) {
133+
ConfigOrError<FaultAbort> faultAbortOrError = parseFaultAbort(httpFault.getAbort());
134+
if (faultAbortOrError.errorDetail != null) {
135+
return ConfigOrError.fromError(
136+
"HttpFault contains invalid FaultAbort: " + faultAbortOrError.errorDetail);
137+
}
138+
faultAbort = faultAbortOrError.config;
139+
}
140+
Integer maxActiveFaults = null;
141+
if (httpFault.hasMaxActiveFaults()) {
142+
maxActiveFaults = httpFault.getMaxActiveFaults().getValue();
143+
if (maxActiveFaults < 0) {
144+
maxActiveFaults = Integer.MAX_VALUE;
145+
}
146+
}
147+
return ConfigOrError.fromConfig(FaultConfig.create(faultDelay, faultAbort, maxActiveFaults));
160148
}
161-
}
162149

163-
private static FaultConfig.FractionalPercent parsePercent(FractionalPercent proto) {
164-
switch (proto.getDenominator()) {
165-
case HUNDRED:
166-
return FaultConfig.FractionalPercent.perHundred(proto.getNumerator());
167-
case TEN_THOUSAND:
168-
return FaultConfig.FractionalPercent.perTenThousand(proto.getNumerator());
169-
case MILLION:
170-
return FaultConfig.FractionalPercent.perMillion(proto.getNumerator());
171-
case UNRECOGNIZED:
172-
default:
173-
throw new IllegalArgumentException("Unknown denominator type: " + proto.getDenominator());
150+
private static FaultDelay parseFaultDelay(
151+
io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay faultDelay) {
152+
FaultConfig.FractionalPercent percent = parsePercent(faultDelay.getPercentage());
153+
if (faultDelay.hasHeaderDelay()) {
154+
return FaultDelay.forHeader(percent);
155+
}
156+
return FaultDelay.forFixedDelay(Durations.toNanos(faultDelay.getFixedDelay()), percent);
174157
}
175-
}
176158

177-
@Override
178-
public ConfigOrError<FaultConfig> parseFilterConfigOverride(Message rawProtoMessage) {
179-
return parseFilterConfig(rawProtoMessage);
159+
@VisibleForTesting
160+
static ConfigOrError<FaultAbort> parseFaultAbort(
161+
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort faultAbort) {
162+
FaultConfig.FractionalPercent percent = parsePercent(faultAbort.getPercentage());
163+
switch (faultAbort.getErrorTypeCase()) {
164+
case HEADER_ABORT:
165+
return ConfigOrError.fromConfig(FaultAbort.forHeader(percent));
166+
case HTTP_STATUS:
167+
return ConfigOrError.fromConfig(FaultAbort.forStatus(
168+
GrpcUtil.httpStatusToGrpcStatus(faultAbort.getHttpStatus()), percent));
169+
case GRPC_STATUS:
170+
return ConfigOrError.fromConfig(FaultAbort.forStatus(
171+
Status.fromCodeValue(faultAbort.getGrpcStatus()), percent));
172+
case ERRORTYPE_NOT_SET:
173+
default:
174+
return ConfigOrError.fromError(
175+
"Unknown error type case: " + faultAbort.getErrorTypeCase());
176+
}
177+
}
178+
179+
private static FaultConfig.FractionalPercent parsePercent(FractionalPercent proto) {
180+
switch (proto.getDenominator()) {
181+
case HUNDRED:
182+
return FaultConfig.FractionalPercent.perHundred(proto.getNumerator());
183+
case TEN_THOUSAND:
184+
return FaultConfig.FractionalPercent.perTenThousand(proto.getNumerator());
185+
case MILLION:
186+
return FaultConfig.FractionalPercent.perMillion(proto.getNumerator());
187+
case UNRECOGNIZED:
188+
default:
189+
throw new IllegalArgumentException("Unknown denominator type: " + proto.getDenominator());
190+
}
191+
}
180192
}
181193

182194
@Nullable

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

+62-28
Original file line numberDiff line numberDiff line change
@@ -25,48 +25,82 @@
2525
import javax.annotation.Nullable;
2626

2727
/**
28-
* Defines the parsing functionality of an HTTP filter. A Filter may optionally implement either
29-
* {@link ClientInterceptorBuilder} or {@link ServerInterceptorBuilder} or both, indicating it is
30-
* capable of working on the client side or server side or both, respectively.
28+
* Defines the parsing functionality of an HTTP filter.
29+
*
30+
* <p>A Filter may optionally implement either {@link Filter#buildClientInterceptor} or
31+
* {@link Filter#buildServerInterceptor} or both, and return true from corresponding
32+
* {@link Provider#isClientFilter()}, {@link Provider#isServerFilter()} to indicate that the filter
33+
* is capable of working on the client side or server side or both, respectively.
3134
*/
3235
interface Filter {
3336

34-
/**
35-
* The proto message types supported by this filter. A filter will be registered by each of its
36-
* supported message types.
37-
*/
38-
String[] typeUrls();
37+
/** Represents an opaque data structure holding configuration for a filter. */
38+
interface FilterConfig {
39+
String typeUrl();
40+
}
3941

4042
/**
41-
* Parses the top-level filter config from raw proto message. The message may be either a {@link
42-
* com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
43+
* Common interface for filter providers.
4344
*/
44-
ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage);
45+
interface Provider {
46+
/**
47+
* The proto message types supported by this filter. A filter will be registered by each of its
48+
* supported message types.
49+
*/
50+
String[] typeUrls();
4551

46-
/**
47-
* Parses the per-filter override filter config from raw proto message. The message may be either
48-
* a {@link com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
49-
*/
50-
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(Message rawProtoMessage);
52+
/**
53+
* Whether the filter can be installed on the client side.
54+
*
55+
* <p>Returns true if the filter implements {@link Filter#buildClientInterceptor}.
56+
*/
57+
default boolean isClientFilter() {
58+
return false;
59+
}
5160

52-
/** Represents an opaque data structure holding configuration for a filter. */
53-
interface FilterConfig {
54-
String typeUrl();
61+
/**
62+
* Whether the filter can be installed into xDS-enabled servers.
63+
*
64+
* <p>Returns true if the filter implements {@link Filter#buildServerInterceptor}.
65+
*/
66+
default boolean isServerFilter() {
67+
return false;
68+
}
69+
70+
/**
71+
* Creates a new instance of the filter.
72+
*
73+
* <p>Returns a filter instance registered with the same typeUrls as the provider,
74+
* capable of working with the same FilterConfig type returned by provider's parse functions.
75+
*/
76+
Filter newInstance();
77+
78+
/**
79+
* Parses the top-level filter config from raw proto message. The message may be either a {@link
80+
* com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
81+
*/
82+
ConfigOrError<? extends FilterConfig> parseFilterConfig(Message rawProtoMessage);
83+
84+
/**
85+
* Parses the per-filter override filter config from raw proto message. The message may be
86+
* either a {@link com.google.protobuf.Any} or a {@link com.google.protobuf.Struct}.
87+
*/
88+
ConfigOrError<? extends FilterConfig> parseFilterConfigOverride(Message rawProtoMessage);
5589
}
5690

5791
/** Uses the FilterConfigs produced above to produce an HTTP filter interceptor for clients. */
58-
interface ClientInterceptorBuilder {
59-
@Nullable
60-
ClientInterceptor buildClientInterceptor(
61-
FilterConfig config, @Nullable FilterConfig overrideConfig,
62-
ScheduledExecutorService scheduler);
92+
@Nullable
93+
default ClientInterceptor buildClientInterceptor(
94+
FilterConfig config, @Nullable FilterConfig overrideConfig,
95+
ScheduledExecutorService scheduler) {
96+
return null;
6397
}
6498

6599
/** Uses the FilterConfigs produced above to produce an HTTP filter interceptor for the server. */
66-
interface ServerInterceptorBuilder {
67-
@Nullable
68-
ServerInterceptor buildServerInterceptor(
69-
FilterConfig config, @Nullable FilterConfig overrideConfig);
100+
@Nullable
101+
default ServerInterceptor buildServerInterceptor(
102+
FilterConfig config, @Nullable FilterConfig overrideConfig) {
103+
return null;
70104
}
71105

72106
/** Filter config with instance name. */

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

+8-8
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,21 @@
2323

2424
/**
2525
* A registry for all supported {@link Filter}s. Filters can be queried from the registry
26-
* by any of the {@link Filter#typeUrls() type URLs}.
26+
* by any of the {@link Filter.Provider#typeUrls() type URLs}.
2727
*/
2828
final class FilterRegistry {
2929
private static FilterRegistry instance;
3030

31-
private final Map<String, Filter> supportedFilters = new HashMap<>();
31+
private final Map<String, Filter.Provider> supportedFilters = new HashMap<>();
3232

3333
private FilterRegistry() {}
3434

3535
static synchronized FilterRegistry getDefaultRegistry() {
3636
if (instance == null) {
3737
instance = newRegistry().register(
38-
FaultFilter.INSTANCE,
39-
RouterFilter.INSTANCE,
40-
RbacFilter.INSTANCE);
38+
new FaultFilter.Provider(),
39+
new RouterFilter.Provider(),
40+
new RbacFilter.Provider());
4141
}
4242
return instance;
4343
}
@@ -48,8 +48,8 @@ static FilterRegistry newRegistry() {
4848
}
4949

5050
@VisibleForTesting
51-
FilterRegistry register(Filter... filters) {
52-
for (Filter filter : filters) {
51+
FilterRegistry register(Filter.Provider... filters) {
52+
for (Filter.Provider filter : filters) {
5353
for (String typeUrl : filter.typeUrls()) {
5454
supportedFilters.put(typeUrl, filter);
5555
}
@@ -58,7 +58,7 @@ FilterRegistry register(Filter... filters) {
5858
}
5959

6060
@Nullable
61-
Filter get(String typeUrl) {
61+
Filter.Provider get(String typeUrl) {
6262
return supportedFilters.get(typeUrl);
6363
}
6464
}

0 commit comments

Comments
 (0)