Skip to content

Commit e2b938d

Browse files
committed
Gracefully handle tcp listeners. Fix error processing in XdsNR
1 parent 36ebe5e commit e2b938d

5 files changed

Lines changed: 143 additions & 87 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ XdsConfigBuilder setVirtualHost(VirtualHost virtualHost) {
243243
XdsConfig build() {
244244
checkNotNull(listener, "listener");
245245
checkNotNull(route, "route");
246+
checkNotNull(virtualHost, "virtualHost");
246247
return new XdsConfig(listener, route, clusters, virtualHost);
247248
}
248249
}

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

Lines changed: 96 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ final class XdsDependencyManager implements XdsConfig.XdsClusterSubscriptionRegi
8787
syncContext.execute(() -> addWatcher(new LdsWatcher(listenerName)));
8888
}
8989

90-
public static String toContextStr(String typeName, String resourceName) {
90+
public static String toContextStr(String typeName, String resourceName) {
9191
return typeName + " resource: " + resourceName;
9292
}
9393

@@ -280,8 +280,7 @@ XdsConfig buildConfig() {
280280
// Iterate watchers and build the XdsConfig
281281

282282
// Will only be 1 listener and 1 route resource
283-
RdsUpdate rdsUpdate = null;
284-
XdsWatcherBase<?> routeSource = null;
283+
RdsUpdateSupplier routeSource = null;
285284
for (XdsWatcherBase<XdsListenerResource.LdsUpdate> ldsWatcher :
286285
getWatchers(XdsListenerResource.getInstance()).values()) {
287286
if (!ldsWatcher.getData().hasValue()) {
@@ -290,23 +289,15 @@ XdsConfig buildConfig() {
290289
}
291290
XdsListenerResource.LdsUpdate ldsUpdate = ldsWatcher.getData().getValue();
292291
builder.setListener(ldsUpdate);
293-
if (ldsUpdate.httpConnectionManager() != null
294-
&& ldsUpdate.httpConnectionManager().virtualHosts() != null) {
295-
rdsUpdate = new RdsUpdate(ldsUpdate.httpConnectionManager().virtualHosts());
296-
routeSource = ldsWatcher;
297-
}
292+
routeSource = ((LdsWatcher) ldsWatcher).getRouteSource();
298293
}
299294

300-
XdsWatcherBase<RdsUpdate> rdsWatcher = getWatchers(XdsRouteConfigureResource.getInstance())
301-
.values().stream().findFirst().orElse(null);
302-
if (rdsWatcher != null) {
303-
if (!rdsWatcher.getData().hasValue()) {
304-
xdsConfigWatcher.onError(rdsWatcher.toContextString(), rdsWatcher.getData().getStatus());
305-
return null;
306-
}
307-
rdsUpdate = rdsWatcher.getData().getValue();
308-
routeSource = rdsWatcher;
295+
StatusOr<RdsUpdate> statusOrRdsUpdate = routeSource.getRdsUpdate();
296+
if (!statusOrRdsUpdate.hasValue()) {
297+
xdsConfigWatcher.onError(routeSource.toContextString(), statusOrRdsUpdate.getStatus());
298+
return null;
309299
}
300+
RdsUpdate rdsUpdate = statusOrRdsUpdate.getValue();
310301
builder.setRoute(rdsUpdate);
311302

312303
VirtualHost activeVirtualHost =
@@ -492,13 +483,14 @@ private void addClusterWatcher(String clusterName, Object parentContext, int dep
492483
}
493484

494485
private void updateRoutes(List<VirtualHost> virtualHosts, Object newParentContext,
495-
VirtualHost oldVirtualHost, boolean sameParentContext) {
496-
Set<String> oldClusters = getClusterNamesFromVirtualHost(oldVirtualHost);
497-
486+
List<VirtualHost> oldVirtualHosts, boolean sameParentContext) {
487+
VirtualHost oldVirtualHost =
488+
RoutingUtils.findVirtualHostForHostName(oldVirtualHosts, dataPlaneAuthority);
498489
VirtualHost virtualHost =
499490
RoutingUtils.findVirtualHostForHostName(virtualHosts, dataPlaneAuthority);
500-
Set<String> newClusters =
501-
virtualHost != null ? getClusterNamesFromVirtualHost(virtualHost) : Collections.emptySet();
491+
492+
Set<String> newClusters = getClusterNamesFromVirtualHost(virtualHost);
493+
Set<String> oldClusters = getClusterNamesFromVirtualHost(oldVirtualHost);
502494

503495
if (sameParentContext) {
504496
// Calculate diffs.
@@ -537,24 +529,6 @@ private static Set<String> getClusterNamesFromVirtualHost(VirtualHost virtualHos
537529
return clusters;
538530
}
539531

540-
@Nullable
541-
private VirtualHost getActiveVirtualHost() {
542-
TypeWatchers<?> rdsWatchers = resourceWatchers.get(XdsRouteConfigureResource.getInstance());
543-
if (rdsWatchers == null) {
544-
return null;
545-
}
546-
547-
RdsWatcher activeRdsWatcher =
548-
(RdsWatcher) rdsWatchers.watchers.values().stream().findFirst().orElse(null);
549-
if (activeRdsWatcher == null || activeRdsWatcher.missingResult()
550-
|| !activeRdsWatcher.getData().hasValue()) {
551-
return null;
552-
}
553-
554-
return RoutingUtils.findVirtualHostForHostName(
555-
activeRdsWatcher.getData().getValue().virtualHosts, dataPlaneAuthority);
556-
}
557-
558532
private CdsWatcher getCluster(String clusterName) {
559533
return (CdsWatcher) resourceWatchers.get(CLUSTER_RESOURCE).watchers.get(clusterName);
560534
}
@@ -660,12 +634,19 @@ protected void setDataAsStatus(Status status) {
660634
this.data = StatusOr.fromStatus(status);
661635
}
662636

663-
String toContextString() {
637+
public String toContextString() {
664638
return toContextStr(type.typeName(), resourceName);
665639
}
666640
}
667641

668-
private class LdsWatcher extends XdsWatcherBase<XdsListenerResource.LdsUpdate> {
642+
private interface RdsUpdateSupplier {
643+
StatusOr<RdsUpdate> getRdsUpdate();
644+
645+
String toContextString();
646+
}
647+
648+
private class LdsWatcher extends XdsWatcherBase<XdsListenerResource.LdsUpdate>
649+
implements RdsUpdateSupplier {
669650
String rdsName;
670651

671652
private LdsWatcher(String resourceName) {
@@ -677,9 +658,20 @@ public void onChanged(XdsListenerResource.LdsUpdate update) {
677658
checkNotNull(update, "update");
678659

679660
HttpConnectionManager httpConnectionManager = update.httpConnectionManager();
680-
List<VirtualHost> virtualHosts = httpConnectionManager.virtualHosts();
681-
String rdsName = httpConnectionManager.rdsName();
682-
VirtualHost activeVirtualHost = getActiveVirtualHost();
661+
List<VirtualHost> virtualHosts;
662+
String rdsName;
663+
if (httpConnectionManager == null) {
664+
// TCP listener. Unsupported config
665+
virtualHosts = Collections.emptyList(); // Not null, to not delegate to RDS
666+
rdsName = null;
667+
} else {
668+
virtualHosts = httpConnectionManager.virtualHosts();
669+
rdsName = httpConnectionManager.rdsName();
670+
}
671+
StatusOr<RdsUpdate> activeRdsUpdate = getRouteSource().getRdsUpdate();
672+
List<VirtualHost> activeVirtualHosts = activeRdsUpdate.hasValue()
673+
? activeRdsUpdate.getValue().virtualHosts
674+
: Collections.emptyList();
683675

684676
boolean changedRdsName = !Objects.equals(rdsName, this.rdsName);
685677
if (changedRdsName) {
@@ -688,7 +680,7 @@ public void onChanged(XdsListenerResource.LdsUpdate update) {
688680

689681
if (virtualHosts != null) {
690682
// No RDS watcher since we are getting RDS updates via LDS
691-
updateRoutes(virtualHosts, this, activeVirtualHost, this.rdsName == null);
683+
updateRoutes(virtualHosts, this, activeVirtualHosts, this.rdsName == null);
692684
this.rdsName = null;
693685
} else if (changedRdsName) {
694686
this.rdsName = rdsName;
@@ -720,8 +712,7 @@ private void cleanUpRdsWatcher() {
720712
logger.log(XdsLogger.XdsLogLevel.DEBUG, "Stop watching RDS resource {0}", rdsName);
721713

722714
// Cleanup clusters (as appropriate) that had the old rds watcher as a parent
723-
if (!oldRdsWatcher.hasDataValue() || !oldRdsWatcher.getData().hasValue()
724-
|| resourceWatchers.get(CLUSTER_RESOURCE) == null) {
715+
if (!oldRdsWatcher.hasDataValue() || resourceWatchers.get(CLUSTER_RESOURCE) == null) {
725716
return;
726717
}
727718
for (XdsWatcherBase<?> watcher :
@@ -732,16 +723,58 @@ private void cleanUpRdsWatcher() {
732723
}
733724

734725
private RdsWatcher getRdsWatcher() {
726+
if (rdsName == null) {
727+
return null;
728+
}
735729
TypeWatchers<?> watchers = resourceWatchers.get(XdsRouteConfigureResource.getInstance());
736-
if (watchers == null || rdsName == null || watchers.watchers.isEmpty()) {
730+
if (watchers == null) {
737731
return null;
738732
}
739-
740733
return (RdsWatcher) watchers.watchers.get(rdsName);
741734
}
735+
736+
public RdsUpdateSupplier getRouteSource() {
737+
if (!hasDataValue()) {
738+
return this;
739+
}
740+
HttpConnectionManager hcm = getData().getValue().httpConnectionManager();
741+
if (hcm == null) {
742+
return this;
743+
}
744+
List<VirtualHost> virtualHosts = hcm.virtualHosts();
745+
if (virtualHosts != null) {
746+
return this;
747+
}
748+
RdsWatcher rdsWatcher = getRdsWatcher();
749+
assert rdsWatcher != null;
750+
return rdsWatcher;
751+
}
752+
753+
@Override
754+
public StatusOr<RdsUpdate> getRdsUpdate() {
755+
if (missingResult()) {
756+
return StatusOr.fromStatus(Status.UNAVAILABLE.withDescription("Not yet loaded"));
757+
}
758+
if (!getData().hasValue()) {
759+
return StatusOr.fromStatus(getData().getStatus());
760+
}
761+
HttpConnectionManager hcm = getData().getValue().httpConnectionManager();
762+
if (hcm == null) {
763+
return StatusOr.fromStatus(
764+
Status.UNAVAILABLE.withDescription("Not an API listener"));
765+
}
766+
List<VirtualHost> virtualHosts = hcm.virtualHosts();
767+
if (virtualHosts == null) {
768+
// Code shouldn't trigger this case, as it should be calling RdsWatcher instead. This would
769+
// be easily implemented with getRdsWatcher().getRdsUpdate(), but getting here is likely a
770+
// bug
771+
return StatusOr.fromStatus(Status.INTERNAL.withDescription("Routes are in RDS, not LDS"));
772+
}
773+
return StatusOr.fromValue(new RdsUpdate(virtualHosts));
774+
}
742775
}
743776

744-
private class RdsWatcher extends XdsWatcherBase<RdsUpdate> {
777+
private class RdsWatcher extends XdsWatcherBase<RdsUpdate> implements RdsUpdateSupplier {
745778

746779
public RdsWatcher(String resourceName) {
747780
super(XdsRouteConfigureResource.getInstance(), checkNotNull(resourceName, "resourceName"));
@@ -750,13 +783,11 @@ public RdsWatcher(String resourceName) {
750783
@Override
751784
public void onChanged(RdsUpdate update) {
752785
checkNotNull(update, "update");
753-
RdsUpdate oldData = hasDataValue() ? getData().getValue() : null;
754-
VirtualHost oldVirtualHost =
755-
(oldData != null)
756-
? RoutingUtils.findVirtualHostForHostName(oldData.virtualHosts, dataPlaneAuthority)
757-
: null;
786+
List<VirtualHost> oldVirtualHosts = hasDataValue()
787+
? getData().getValue().virtualHosts
788+
: Collections.emptyList();
758789
setData(update);
759-
updateRoutes(update.virtualHosts, this, oldVirtualHost, true);
790+
updateRoutes(update.virtualHosts, this, oldVirtualHosts, true);
760791
maybePublishConfig();
761792
}
762793

@@ -770,6 +801,14 @@ public void onResourceDoesNotExist(String resourceName) {
770801
xdsConfigWatcher.onResourceDoesNotExist(toContextString());
771802
lastXdsConfig = null; // Published an empty result
772803
}
804+
805+
@Override
806+
public StatusOr<RdsUpdate> getRdsUpdate() {
807+
if (missingResult()) {
808+
return StatusOr.fromStatus(Status.UNAVAILABLE.withDescription("Not yet loaded"));
809+
}
810+
return getData();
811+
}
773812
}
774813

775814
private class CdsWatcher extends XdsWatcherBase<XdsClusterResource.CdsUpdate> {

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

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,6 @@ final class XdsNameResolver extends NameResolver {
133133
private XdsClient xdsClient;
134134
private CallCounterProvider callCounterProvider;
135135
private ResolveState resolveState;
136-
// Workaround for https://github.com/grpc/grpc-java/issues/8886 . This should be handled in
137-
// XdsClient instead of here.
138-
private boolean receivedConfig;
139136

140137
XdsNameResolver(
141138
URI targetUri, String name, @Nullable String overrideAuthority,
@@ -319,7 +316,6 @@ private void updateResolutionResult() {
319316
.setServiceConfig(parsedServiceConfig)
320317
.build();
321318
listener.onResult(result);
322-
receivedConfig = true;
323319
}
324320

325321
/**
@@ -673,22 +669,21 @@ public void onUpdate(XdsConfig update) {
673669

674670
// Process Route
675671
HttpConnectionManager httpConnectionManager = update.getListener().httpConnectionManager();
676-
VirtualHost virtualHost = update.getVirtualHost(); // httpConnectionManager.virtualHosts();
672+
VirtualHost virtualHost = update.getVirtualHost();
677673

678674
updateRoutes(virtualHost, httpConnectionManager.httpMaxStreamDurationNano(),
679675
httpConnectionManager.httpFilterConfigs());
680676
}
681677

682678
@Override
683679
public void onError(String resourceContext, Status error) {
684-
if (stopped || receivedConfig) {
680+
if (stopped) {
685681
return;
686682
}
687-
String errorMsg = String.format("Unable to load %s. xDS server returned: %s: %s",
683+
String errorMsg = String.format("Error for %s: %s: %s",
688684
resourceContext, error.getCode(), error.getDescription());
689-
listener.onError(Status.UNAVAILABLE.withCause(error.getCause()).withDescription(errorMsg));
690-
691-
sendEmptyResult(errorMsg);
685+
logger.log(XdsLogLevel.INFO, errorMsg);
686+
cleanUpRoutes(errorMsg);
692687
}
693688

694689
@Override
@@ -703,13 +698,6 @@ public void onResourceDoesNotExist(String resourceContext) {
703698

704699
private void updateRoutes(@Nullable VirtualHost virtualHost, long httpMaxStreamDurationNano,
705700
@Nullable List<NamedFilterConfig> filterConfigs) {
706-
if (virtualHost == null) {
707-
String error = "Failed to find virtual host matching hostname: " + authority;
708-
logger.log(XdsLogLevel.WARNING, error);
709-
cleanUpRoutes(error);
710-
return;
711-
}
712-
713701
List<Route> routes = virtualHost.routes();
714702
ImmutableList.Builder<RouteData> routesData = ImmutableList.builder();
715703

@@ -867,7 +855,6 @@ private void cleanUpRoutes(String error) {
867855
// failure information for addresses yet still providing a service config, the config seector
868856
// could be avoided.
869857
sendEmptyResult(error);
870-
receivedConfig = true;
871858
}
872859

873860
private void sendEmptyResult(String error) {

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,23 @@ public void testMissingLds() {
399399
testWatcher.verifyStats(0, 0, 1);
400400
}
401401

402+
@Test
403+
public void testTcpListenerErrors() {
404+
Listener serverListener =
405+
ControlPlaneRule.buildServerListener().toBuilder().setName(serverName).build();
406+
controlPlaneService.setXdsConfig(ADS_TYPE_URL_LDS, ImmutableMap.of(serverName, serverListener));
407+
xdsDependencyManager = new XdsDependencyManager(xdsClient, xdsConfigWatcher, syncContext,
408+
serverName, serverName, nameResolverArgs, scheduler);
409+
410+
fakeClock.forwardTime(16, TimeUnit.SECONDS);
411+
Status expectedStatus = Status.UNAVAILABLE.withDescription("Not an API listener");
412+
String context = toContextStr(XdsListenerResource.getInstance().typeName(), serverName);
413+
verify(xdsConfigWatcher, timeout(1000))
414+
.onError(eq(context), argThat(new XdsTestUtils.StatusMatcher(expectedStatus)));
415+
416+
testWatcher.verifyStats(0, 1, 0);
417+
}
418+
402419
@Test
403420
public void testMissingRds() {
404421
Listener clientListener =

0 commit comments

Comments
 (0)