Skip to content

Commit bd6af59

Browse files
authored
xds: improve code readability of server FilterChain parsing
- Improve code flow and variable names - Reduce nesting - Add comments between logical blocks - Add comments explaining some xDS/gRPC nuances
1 parent 67fc2e1 commit bd6af59

File tree

3 files changed

+127
-72
lines changed

3 files changed

+127
-72
lines changed

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

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,11 @@ static EnvoyServerProtoData.Listener parseServerSideListener(
146146
Listener proto, TlsContextManager tlsContextManager,
147147
FilterRegistry filterRegistry, Set<String> certProviderInstances, XdsResourceType.Args args)
148148
throws ResourceInvalidException {
149-
if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND)
150-
&& !proto.getTrafficDirection().equals(TrafficDirection.UNSPECIFIED)) {
149+
TrafficDirection trafficDirection = proto.getTrafficDirection();
150+
if (!trafficDirection.equals(TrafficDirection.INBOUND)
151+
&& !trafficDirection.equals(TrafficDirection.UNSPECIFIED)) {
151152
throw new ResourceInvalidException(
152-
"Listener " + proto.getName() + " with invalid traffic direction: "
153-
+ proto.getTrafficDirection());
153+
"Listener " + proto.getName() + " with invalid traffic direction: " + trafficDirection);
154154
}
155155
if (!proto.getListenerFiltersList().isEmpty()) {
156156
throw new ResourceInvalidException(
@@ -178,16 +178,29 @@ static EnvoyServerProtoData.Listener parseServerSideListener(
178178
}
179179

180180
ImmutableList.Builder<FilterChain> filterChains = ImmutableList.builder();
181-
Set<FilterChainMatch> uniqueSet = new HashSet<>();
181+
Set<FilterChainMatch> filterChainMatchSet = new HashSet<>();
182+
int i = 0;
182183
for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) {
184+
// May be empty. If it's not empty, required to be unique.
185+
String filterChainName = fc.getName();
186+
if (filterChainName.isEmpty()) {
187+
// Generate a name, so we can identify it in the logs.
188+
filterChainName = "chain_" + i;
189+
}
183190
filterChains.add(
184-
parseFilterChain(fc, tlsContextManager, filterRegistry, uniqueSet,
185-
certProviderInstances, args));
191+
parseFilterChain(fc, filterChainName, tlsContextManager, filterRegistry,
192+
filterChainMatchSet, certProviderInstances, args));
193+
i++;
186194
}
195+
187196
FilterChain defaultFilterChain = null;
188197
if (proto.hasDefaultFilterChain()) {
198+
String defaultFilterChainName = proto.getDefaultFilterChain().getName();
199+
if (defaultFilterChainName.isEmpty()) {
200+
defaultFilterChainName = "chain_default";
201+
}
189202
defaultFilterChain = parseFilterChain(
190-
proto.getDefaultFilterChain(), tlsContextManager, filterRegistry,
203+
proto.getDefaultFilterChain(), defaultFilterChainName, tlsContextManager, filterRegistry,
191204
null, certProviderInstances, args);
192205
}
193206

@@ -198,36 +211,44 @@ static EnvoyServerProtoData.Listener parseServerSideListener(
198211
@VisibleForTesting
199212
static FilterChain parseFilterChain(
200213
io.envoyproxy.envoy.config.listener.v3.FilterChain proto,
201-
TlsContextManager tlsContextManager, FilterRegistry filterRegistry,
202-
Set<FilterChainMatch> uniqueSet, Set<String> certProviderInstances, XdsResourceType.Args args)
214+
String filterChainName,
215+
TlsContextManager tlsContextManager,
216+
FilterRegistry filterRegistry,
217+
// null disables FilterChainMatch uniqueness check, used for defaultFilterChain
218+
@Nullable Set<FilterChainMatch> filterChainMatchSet,
219+
Set<String> certProviderInstances,
220+
XdsResourceType.Args args)
203221
throws ResourceInvalidException {
222+
// FilterChain contains L4 filters, so we ensure it contains only HCM.
204223
if (proto.getFiltersCount() != 1) {
205-
throw new ResourceInvalidException("FilterChain " + proto.getName()
224+
throw new ResourceInvalidException("FilterChain " + filterChainName
206225
+ " should contain exact one HttpConnectionManager filter");
207226
}
208-
io.envoyproxy.envoy.config.listener.v3.Filter filter = proto.getFiltersList().get(0);
209-
if (!filter.hasTypedConfig()) {
227+
io.envoyproxy.envoy.config.listener.v3.Filter l4Filter = proto.getFiltersList().get(0);
228+
if (!l4Filter.hasTypedConfig()) {
210229
throw new ResourceInvalidException(
211-
"FilterChain " + proto.getName() + " contains filter " + filter.getName()
230+
"FilterChain " + filterChainName + " contains filter " + l4Filter.getName()
212231
+ " without typed_config");
213232
}
214-
Any any = filter.getTypedConfig();
215-
// HttpConnectionManager is the only supported network filter at the moment.
233+
Any any = l4Filter.getTypedConfig();
216234
if (!any.getTypeUrl().equals(TYPE_URL_HTTP_CONNECTION_MANAGER)) {
217235
throw new ResourceInvalidException(
218-
"FilterChain " + proto.getName() + " contains filter " + filter.getName()
236+
"FilterChain " + filterChainName + " contains filter " + l4Filter.getName()
219237
+ " with unsupported typed_config type " + any.getTypeUrl());
220238
}
239+
240+
// Parse HCM.
221241
HttpConnectionManager hcmProto;
222242
try {
223243
hcmProto = any.unpack(HttpConnectionManager.class);
224244
} catch (InvalidProtocolBufferException e) {
225-
throw new ResourceInvalidException("FilterChain " + proto.getName() + " with filter "
226-
+ filter.getName() + " failed to unpack message", e);
245+
throw new ResourceInvalidException("FilterChain " + filterChainName + " with filter "
246+
+ l4Filter.getName() + " failed to unpack message", e);
227247
}
228248
io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager(
229249
hcmProto, filterRegistry, false /* isForClient */, args);
230250

251+
// Parse Transport Socket.
231252
EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null;
232253
if (proto.hasTransportSocket()) {
233254
if (!TRANSPORT_SOCKET_NAME_TLS.equals(proto.getTransportSocket().getName())) {
@@ -239,18 +260,23 @@ static FilterChain parseFilterChain(
239260
downstreamTlsContextProto =
240261
proto.getTransportSocket().getTypedConfig().unpack(DownstreamTlsContext.class);
241262
} catch (InvalidProtocolBufferException e) {
242-
throw new ResourceInvalidException("FilterChain " + proto.getName()
263+
throw new ResourceInvalidException("FilterChain " + filterChainName
243264
+ " failed to unpack message", e);
244265
}
245266
downstreamTlsContext =
246267
EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext(
247268
validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances));
248269
}
249270

271+
// Parse FilterChainMatch.
250272
FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch());
251-
checkForUniqueness(uniqueSet, filterChainMatch);
273+
// null used to skip this check for defaultFilterChain.
274+
if (filterChainMatchSet != null) {
275+
validateFilterChainMatchForUniqueness(filterChainMatchSet, filterChainMatch);
276+
}
277+
252278
return FilterChain.create(
253-
proto.getName(),
279+
filterChainName,
254280
filterChainMatch,
255281
httpConnectionManager,
256282
downstreamTlsContext,
@@ -284,15 +310,15 @@ static DownstreamTlsContext validateDownstreamTlsContext(
284310
return downstreamTlsContext;
285311
}
286312

287-
private static void checkForUniqueness(Set<FilterChainMatch> uniqueSet,
313+
private static void validateFilterChainMatchForUniqueness(
314+
Set<FilterChainMatch> filterChainMatchSet,
288315
FilterChainMatch filterChainMatch) throws ResourceInvalidException {
289-
if (uniqueSet != null) {
290-
List<FilterChainMatch> crossProduct = getCrossProduct(filterChainMatch);
291-
for (FilterChainMatch cur : crossProduct) {
292-
if (!uniqueSet.add(cur)) {
293-
throw new ResourceInvalidException("FilterChainMatch must be unique. "
294-
+ "Found duplicate: " + cur);
295-
}
316+
// Flattens complex FilterChainMatch into a list of simple FilterChainMatch'es.
317+
List<FilterChainMatch> crossProduct = getCrossProduct(filterChainMatch);
318+
for (FilterChainMatch cur : crossProduct) {
319+
if (!filterChainMatchSet.add(cur)) {
320+
throw new ResourceInvalidException("FilterChainMatch must be unique. "
321+
+ "Found duplicate: " + cur);
296322
}
297323
}
298324
}

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

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@
6060
import java.io.IOException;
6161
import java.net.SocketAddress;
6262
import java.util.ArrayList;
63-
import java.util.Collections;
6463
import java.util.HashMap;
6564
import java.util.HashSet;
6665
import java.util.List;
@@ -459,47 +458,69 @@ private void shutdown() {
459458
}
460459

461460
private void updateSelector() {
462-
Map<FilterChain, AtomicReference<ServerRoutingConfig>> filterChainRouting = new HashMap<>();
461+
// This is regenerated in generateRoutingConfig() calls below.
463462
savedRdsRoutingConfigRef.clear();
463+
464+
// Prepare server routing config map.
465+
ImmutableMap.Builder<FilterChain, AtomicReference<ServerRoutingConfig>> routingConfigs =
466+
ImmutableMap.builder();
464467
for (FilterChain filterChain: filterChains) {
465-
filterChainRouting.put(filterChain, generateRoutingConfig(filterChain));
468+
routingConfigs.put(filterChain, generateRoutingConfig(filterChain));
466469
}
467-
FilterChainSelector selector = new FilterChainSelector(
468-
Collections.unmodifiableMap(filterChainRouting),
469-
defaultFilterChain == null ? null : defaultFilterChain.sslContextProviderSupplier(),
470-
defaultFilterChain == null ? new AtomicReference<ServerRoutingConfig>() :
471-
generateRoutingConfig(defaultFilterChain));
472-
List<SslContextProviderSupplier> toRelease = getSuppliersInUse();
470+
471+
// Prepare the new selector.
472+
FilterChainSelector selector;
473+
if (defaultFilterChain != null) {
474+
selector = new FilterChainSelector(
475+
routingConfigs.build(),
476+
defaultFilterChain.sslContextProviderSupplier(),
477+
generateRoutingConfig(defaultFilterChain));
478+
} else {
479+
selector = new FilterChainSelector(routingConfigs.build(), null, new AtomicReference<>());
480+
}
481+
482+
// Prepare the list of current selector's resources to close later.
483+
List<SslContextProviderSupplier> oldSslSuppliers = getSuppliersInUse();
484+
485+
// Swap the selectors, initiate a graceful shutdown of the old one.
473486
logger.log(Level.FINEST, "Updating selector {0}", selector);
474487
filterChainSelectorManager.updateSelector(selector);
475-
for (SslContextProviderSupplier e: toRelease) {
476-
e.close();
488+
489+
// Release old resources.
490+
for (SslContextProviderSupplier supplier: oldSslSuppliers) {
491+
supplier.close();
477492
}
493+
494+
// Now that we have valid Transport Socket config, we can start/restart listening on a port.
478495
startDelegateServer();
479496
}
480497

481498
private AtomicReference<ServerRoutingConfig> generateRoutingConfig(FilterChain filterChain) {
482499
HttpConnectionManager hcm = filterChain.httpConnectionManager();
500+
ImmutableMap<Route, ServerInterceptor> interceptors;
501+
502+
// Inlined routes.
483503
if (hcm.virtualHosts() != null) {
484-
ImmutableMap<Route, ServerInterceptor> interceptors = generatePerRouteInterceptors(
485-
hcm.httpFilterConfigs(), hcm.virtualHosts());
486-
return new AtomicReference<>(ServerRoutingConfig.create(hcm.virtualHosts(),interceptors));
504+
interceptors = generatePerRouteInterceptors(hcm.httpFilterConfigs(), hcm.virtualHosts());
505+
return new AtomicReference<>(ServerRoutingConfig.create(hcm.virtualHosts(), interceptors));
506+
}
507+
508+
// Routes from RDS.
509+
RouteDiscoveryState rds = routeDiscoveryStates.get(hcm.rdsName());
510+
checkNotNull(rds, "rds");
511+
512+
ServerRoutingConfig routingConfig;
513+
ImmutableList<VirtualHost> savedVhosts = rds.savedVirtualHosts;
514+
if (savedVhosts != null) {
515+
interceptors = generatePerRouteInterceptors(hcm.httpFilterConfigs(), savedVhosts);
516+
routingConfig = ServerRoutingConfig.create(savedVhosts, interceptors);
487517
} else {
488-
RouteDiscoveryState rds = routeDiscoveryStates.get(hcm.rdsName());
489-
checkNotNull(rds, "rds");
490-
AtomicReference<ServerRoutingConfig> serverRoutingConfigRef = new AtomicReference<>();
491-
if (rds.savedVirtualHosts != null) {
492-
ImmutableMap<Route, ServerInterceptor> interceptors = generatePerRouteInterceptors(
493-
hcm.httpFilterConfigs(), rds.savedVirtualHosts);
494-
ServerRoutingConfig serverRoutingConfig =
495-
ServerRoutingConfig.create(rds.savedVirtualHosts, interceptors);
496-
serverRoutingConfigRef.set(serverRoutingConfig);
497-
} else {
498-
serverRoutingConfigRef.set(ServerRoutingConfig.FAILING_ROUTING_CONFIG);
499-
}
500-
savedRdsRoutingConfigRef.put(filterChain, serverRoutingConfigRef);
501-
return serverRoutingConfigRef;
518+
routingConfig = ServerRoutingConfig.FAILING_ROUTING_CONFIG;
502519
}
520+
521+
AtomicReference<ServerRoutingConfig> routingConfigRef = new AtomicReference<>(routingConfig);
522+
savedRdsRoutingConfigRef.put(filterChain, routingConfigRef);
523+
return routingConfigRef;
503524
}
504525

505526
private ImmutableMap<Route, ServerInterceptor> generatePerRouteInterceptors(

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,7 +2719,7 @@ public void parseFilterChain_noHcm() throws ResourceInvalidException {
27192719
thrown.expectMessage(
27202720
"FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter");
27212721
XdsListenerResource.parseFilterChain(
2722-
filterChain, null, filterRegistry, null, null,
2722+
filterChain, "filter-chain-foo", null, filterRegistry, null, null,
27232723
getXdsResourceTypeArgs(true));
27242724
}
27252725

@@ -2738,7 +2738,7 @@ public void parseFilterChain_duplicateFilter() throws ResourceInvalidException {
27382738
thrown.expectMessage(
27392739
"FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter");
27402740
XdsListenerResource.parseFilterChain(
2741-
filterChain, null, filterRegistry, null, null,
2741+
filterChain, "filter-chain-foo", null, filterRegistry, null, null,
27422742
getXdsResourceTypeArgs(true));
27432743
}
27442744

@@ -2757,7 +2757,7 @@ public void parseFilterChain_filterMissingTypedConfig() throws ResourceInvalidEx
27572757
"FilterChain filter-chain-foo contains filter envoy.http_connection_manager "
27582758
+ "without typed_config");
27592759
XdsListenerResource.parseFilterChain(
2760-
filterChain, null, filterRegistry, null, null,
2760+
filterChain, "filter-chain-foo", null, filterRegistry, null, null,
27612761
getXdsResourceTypeArgs(true));
27622762
}
27632763

@@ -2780,13 +2780,13 @@ public void parseFilterChain_unsupportedFilter() throws ResourceInvalidException
27802780
"FilterChain filter-chain-foo contains filter unsupported with unsupported "
27812781
+ "typed_config type unsupported-type-url");
27822782
XdsListenerResource.parseFilterChain(
2783-
filterChain, null, filterRegistry, null, null,
2783+
filterChain, "filter-chain-foo", null, filterRegistry, null, null,
27842784
getXdsResourceTypeArgs(true));
27852785
}
27862786

27872787
@Test
27882788
public void parseFilterChain_noName() throws ResourceInvalidException {
2789-
FilterChain filterChain1 =
2789+
FilterChain filterChain0 =
27902790
FilterChain.newBuilder()
27912791
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
27922792
.addFilters(buildHttpConnectionManagerFilter(
@@ -2796,9 +2796,11 @@ public void parseFilterChain_noName() throws ResourceInvalidException {
27962796
.setTypedConfig(Any.pack(Router.newBuilder().build()))
27972797
.build()))
27982798
.build();
2799-
FilterChain filterChain2 =
2799+
2800+
FilterChain filterChain1 =
28002801
FilterChain.newBuilder()
2801-
.setFilterChainMatch(FilterChainMatch.getDefaultInstance())
2802+
.setFilterChainMatch(
2803+
FilterChainMatch.newBuilder().addAllSourcePorts(Arrays.asList(443, 8080)))
28022804
.addFilters(buildHttpConnectionManagerFilter(
28032805
HttpFilter.newBuilder()
28042806
.setName("http-filter-bar")
@@ -2807,13 +2809,19 @@ public void parseFilterChain_noName() throws ResourceInvalidException {
28072809
.build()))
28082810
.build();
28092811

2810-
EnvoyServerProtoData.FilterChain parsedFilterChain1 = XdsListenerResource.parseFilterChain(
2811-
filterChain1, null, filterRegistry, null,
2812-
null, getXdsResourceTypeArgs(true));
2813-
EnvoyServerProtoData.FilterChain parsedFilterChain2 = XdsListenerResource.parseFilterChain(
2814-
filterChain2, null, filterRegistry, null,
2815-
null, getXdsResourceTypeArgs(true));
2816-
assertThat(parsedFilterChain1.name()).isEqualTo(parsedFilterChain2.name());
2812+
Listener listenerProto =
2813+
Listener.newBuilder()
2814+
.setName("listener1")
2815+
.setTrafficDirection(TrafficDirection.INBOUND)
2816+
.addAllFilterChains(Arrays.asList(filterChain0, filterChain1))
2817+
.setDefaultFilterChain(filterChain0)
2818+
.build();
2819+
EnvoyServerProtoData.Listener listener = XdsListenerResource.parseServerSideListener(
2820+
listenerProto, null, filterRegistry, null, getXdsResourceTypeArgs(true));
2821+
2822+
assertThat(listener.filterChains().get(0).name()).isEqualTo("chain_0");
2823+
assertThat(listener.filterChains().get(1).name()).isEqualTo("chain_1");
2824+
assertThat(listener.defaultFilterChain().name()).isEqualTo("chain_default");
28172825
}
28182826

28192827
@Test

0 commit comments

Comments
 (0)