diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java index c50f844d388..f8975ce7828 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java @@ -18,10 +18,10 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.ConnectivityState.TRANSIENT_FAILURE; +import static io.grpc.xds.XdsLbPolicies.CDS_POLICY_NAME; import static io.grpc.xds.XdsLbPolicies.CLUSTER_RESOLVER_POLICY_NAME; +import static io.grpc.xds.XdsLbPolicies.PRIORITY_POLICY_NAME; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.CheckReturnValue; import io.grpc.InternalLogId; import io.grpc.LoadBalancer; @@ -33,6 +33,7 @@ import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; +import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; import io.grpc.xds.XdsClusterResource.CdsUpdate; import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType; import io.grpc.xds.XdsConfig.Subscription; @@ -41,10 +42,11 @@ import io.grpc.xds.XdsConfig.XdsClusterConfig.EndpointConfig; import io.grpc.xds.client.XdsLogger; import io.grpc.xds.client.XdsLogger.XdsLogLevel; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Load balancer for cds_experimental LB policy. One instance per top-level cluster. @@ -55,19 +57,15 @@ final class CdsLoadBalancer2 extends LoadBalancer { private final XdsLogger logger; private final Helper helper; private final LoadBalancerRegistry lbRegistry; + private GracefulSwitchLoadBalancer delegate; // Following fields are effectively final. private String clusterName; private Subscription clusterSubscription; - private LoadBalancer childLb; - CdsLoadBalancer2(Helper helper) { - this(helper, LoadBalancerRegistry.getDefaultRegistry()); - } - - @VisibleForTesting CdsLoadBalancer2(Helper helper, LoadBalancerRegistry lbRegistry) { this.helper = checkNotNull(helper, "helper"); this.lbRegistry = checkNotNull(lbRegistry, "lbRegistry"); + this.delegate = new GracefulSwitchLoadBalancer(helper); logger = XdsLogger.withLogId(InternalLogId.allocate("cds-lb", helper.getAuthority())); logger.log(XdsLogLevel.INFO, "Created"); } @@ -91,7 +89,7 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { if (clusterSubscription == null) { // Should be impossible, because XdsDependencyManager wouldn't have generated this return fail(Status.INTERNAL.withDescription( - errorPrefix() + "Unable to find non-dynamic root cluster")); + errorPrefix() + "Unable to find non-dynamic cluster")); } // The dynamic cluster must not have loaded yet return Status.OK; @@ -100,42 +98,25 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { return fail(clusterConfigOr.getStatus()); } XdsClusterConfig clusterConfig = clusterConfigOr.getValue(); - List leafNames; - if (clusterConfig.getChildren() instanceof AggregateConfig) { - leafNames = ((AggregateConfig) clusterConfig.getChildren()).getLeafNames(); - } else if (clusterConfig.getChildren() instanceof EndpointConfig) { - leafNames = ImmutableList.of(clusterName); - } else { - return fail(Status.INTERNAL.withDescription( - errorPrefix() + "Unexpected cluster children type: " - + clusterConfig.getChildren().getClass())); - } - if (leafNames.isEmpty()) { - // Should be impossible, because XdsClusterResource validated this - return fail(Status.UNAVAILABLE.withDescription( - errorPrefix() + "Zero leaf clusters for root cluster " + clusterName)); - } - Status noneFoundError = Status.INTERNAL - .withDescription(errorPrefix() + "No leaves and no error; this is a bug"); - List instances = new ArrayList<>(); - for (String leafName : leafNames) { - StatusOr leafConfigOr = xdsConfig.getClusters().get(leafName); - if (!leafConfigOr.hasValue()) { - noneFoundError = leafConfigOr.getStatus(); - continue; - } - if (!(leafConfigOr.getValue().getChildren() instanceof EndpointConfig)) { - noneFoundError = Status.INTERNAL.withDescription( - errorPrefix() + "Unexpected child " + leafName + " cluster children type: " - + leafConfigOr.getValue().getChildren().getClass()); - continue; + NameResolver.ConfigOrError configOrError; + Object gracefulConfig; + if (clusterConfig.getChildren() instanceof EndpointConfig) { + // The LB policy config is provided in service_config.proto/JSON format. + configOrError = + GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig( + Arrays.asList(clusterConfig.getClusterResource().lbPolicyConfig()), + lbRegistry); + if (configOrError.getError() != null) { + // Should be impossible, because XdsClusterResource validated this + return fail(Status.INTERNAL.withDescription( + errorPrefix() + "Unable to parse the LB config: " + configOrError.getError())); } - CdsUpdate result = leafConfigOr.getValue().getClusterResource(); + CdsUpdate result = clusterConfig.getClusterResource(); DiscoveryMechanism instance; if (result.clusterType() == ClusterType.EDS) { instance = DiscoveryMechanism.forEds( - leafName, + clusterName, result.edsServiceName(), result.lrsServerInfo(), result.maxConcurrentRequests(), @@ -144,45 +125,49 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { result.outlierDetection()); } else { instance = DiscoveryMechanism.forLogicalDns( - leafName, + clusterName, result.dnsHostName(), result.lrsServerInfo(), result.maxConcurrentRequests(), result.upstreamTlsContext(), result.filterMetadata()); } - instances.add(instance); - } - if (instances.isEmpty()) { - return fail(noneFoundError); - } - - // The LB policy config is provided in service_config.proto/JSON format. - NameResolver.ConfigOrError configOrError = - GracefulSwitchLoadBalancer.parseLoadBalancingPolicyConfig( - Arrays.asList(clusterConfig.getClusterResource().lbPolicyConfig()), lbRegistry); - if (configOrError.getError() != null) { - // Should be impossible, because XdsClusterResource validated this + gracefulConfig = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig( + lbRegistry.getProvider(CLUSTER_RESOLVER_POLICY_NAME), + new ClusterResolverConfig( + instance, + configOrError.getConfig(), + clusterConfig.getClusterResource().isHttp11ProxyAvailable())); + } else if (clusterConfig.getChildren() instanceof AggregateConfig) { + Map priorityChildConfigs = new HashMap<>(); + List leafClusters = ((AggregateConfig) clusterConfig.getChildren()).getLeafNames(); + for (String childCluster: leafClusters) { + priorityChildConfigs.put(childCluster, + new PriorityChildConfig( + GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig( + lbRegistry.getProvider(CDS_POLICY_NAME), + new CdsConfig(childCluster)), + false)); + } + gracefulConfig = GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig( + lbRegistry.getProvider(PRIORITY_POLICY_NAME), + new PriorityLoadBalancerProvider.PriorityLbConfig( + Collections.unmodifiableMap(priorityChildConfigs), leafClusters)); + } else { return fail(Status.INTERNAL.withDescription( - errorPrefix() + "Unable to parse the LB config: " + configOrError.getError())); + errorPrefix() + "Unexpected cluster children type: " + + clusterConfig.getChildren().getClass())); } - ClusterResolverConfig config = new ClusterResolverConfig( - Collections.unmodifiableList(instances), - configOrError.getConfig(), - clusterConfig.getClusterResource().isHttp11ProxyAvailable()); - if (childLb == null) { - childLb = lbRegistry.getProvider(CLUSTER_RESOLVER_POLICY_NAME).newLoadBalancer(helper); - } - return childLb.acceptResolvedAddresses( - resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(config).build()); + return delegate.acceptResolvedAddresses( + resolvedAddresses.toBuilder().setLoadBalancingPolicyConfig(gracefulConfig).build()); } @Override public void handleNameResolutionError(Status error) { logger.log(XdsLogLevel.WARNING, "Received name resolution error: {0}", error); - if (childLb != null) { - childLb.handleNameResolutionError(error); + if (delegate != null) { + delegate.handleNameResolutionError(error); } else { helper.updateBalancingState( TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error))); @@ -192,22 +177,17 @@ public void handleNameResolutionError(Status error) { @Override public void shutdown() { logger.log(XdsLogLevel.INFO, "Shutdown"); - if (childLb != null) { - childLb.shutdown(); - childLb = null; - } + delegate.shutdown(); if (clusterSubscription != null) { clusterSubscription.close(); clusterSubscription = null; } + delegate = new GracefulSwitchLoadBalancer(helper); } @CheckReturnValue // don't forget to return up the stack after the fail call private Status fail(Status error) { - if (childLb != null) { - childLb.shutdown(); - childLb = null; - } + delegate.shutdown(); helper.updateBalancingState( TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error))); return Status.OK; // XdsNameResolver isn't a polling NR, so this value doesn't matter diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancerProvider.java index 9b242822f6c..875af9089ed 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancerProvider.java @@ -23,6 +23,7 @@ import io.grpc.LoadBalancer; import io.grpc.LoadBalancer.Helper; import io.grpc.LoadBalancerProvider; +import io.grpc.LoadBalancerRegistry; import io.grpc.NameResolver.ConfigOrError; import io.grpc.Status; import io.grpc.internal.JsonUtil; @@ -51,9 +52,24 @@ public String getPolicyName() { return XdsLbPolicies.CDS_POLICY_NAME; } + private final LoadBalancerRegistry loadBalancerRegistry; + + public CdsLoadBalancerProvider() { + this.loadBalancerRegistry = null; + } + + public CdsLoadBalancerProvider(LoadBalancerRegistry loadBalancerRegistry) { + this.loadBalancerRegistry = loadBalancerRegistry; + } + @Override public LoadBalancer newLoadBalancer(Helper helper) { - return new CdsLoadBalancer2(helper); + LoadBalancerRegistry loadBalancerRegistry = this.loadBalancerRegistry; + if (loadBalancerRegistry == null) { + loadBalancerRegistry = LoadBalancerRegistry.getDefaultRegistry(); + } + + return new CdsLoadBalancer2(helper, loadBalancerRegistry); } @Override diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 080760303bf..e333c46750c 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -168,8 +168,8 @@ public LoadBalancer newLoadBalancer(Helper helper) { */ private final class ClusterResolverLbState extends LoadBalancer { private final Helper helper; - private final List clusters = new ArrayList<>(); - private final Map clusterStates = new HashMap<>(); + private ClusterState clusterState; + private String cluster; private Object endpointLbConfig; private ResolvedAddresses resolvedAddresses; private LoadBalancer childLb; @@ -185,21 +185,18 @@ public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) { ClusterResolverConfig config = (ClusterResolverConfig) resolvedAddresses.getLoadBalancingPolicyConfig(); endpointLbConfig = config.lbConfig; - for (DiscoveryMechanism instance : config.discoveryMechanisms) { - clusters.add(instance.cluster); - ClusterState state; - if (instance.type == DiscoveryMechanism.Type.EDS) { - state = new EdsClusterState(instance.cluster, instance.edsServiceName, - instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext, - instance.filterMetadata, instance.outlierDetection); - } else { // logical DNS - state = new LogicalDnsClusterState(instance.cluster, instance.dnsHostName, - instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext, - instance.filterMetadata); - } - clusterStates.put(instance.cluster, state); - state.start(); - } + DiscoveryMechanism instance = config.discoveryMechanism; + cluster = instance.cluster; + if (instance.type == DiscoveryMechanism.Type.EDS) { + clusterState = new EdsClusterState(instance.cluster, instance.edsServiceName, + instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext, + instance.filterMetadata, instance.outlierDetection); + } else { // logical DNS + clusterState = new LogicalDnsClusterState(instance.cluster, instance.dnsHostName, + instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext, + instance.filterMetadata); + } + clusterState.start(); return Status.OK; } @@ -215,9 +212,7 @@ public void handleNameResolutionError(Status error) { @Override public void shutdown() { - for (ClusterState state : clusterStates.values()) { - state.shutdown(); - } + clusterState.shutdown(); if (childLb != null) { childLb.shutdown(); } @@ -229,24 +224,21 @@ private void handleEndpointResourceUpdate() { List priorities = new ArrayList<>(); // totally ordered priority list Status endpointNotFound = Status.OK; - for (String cluster : clusters) { - ClusterState state = clusterStates.get(cluster); - // Propagate endpoints to the child LB policy only after all clusters have been resolved. - if (!state.resolved && state.status.isOk()) { - return; - } - if (state.result != null) { - addresses.addAll(state.result.addresses); - priorityChildConfigs.putAll(state.result.priorityChildConfigs); - priorities.addAll(state.result.priorities); - } else { - endpointNotFound = state.status; - } + // Propagate endpoints to the child LB policy only after all clusters have been resolved. + if (!clusterState.resolved && clusterState.status.isOk()) { + return; + } + if (clusterState.result != null) { + addresses.addAll(clusterState.result.addresses); + priorityChildConfigs.putAll(clusterState.result.priorityChildConfigs); + priorities.addAll(clusterState.result.priorities); + } else { + endpointNotFound = clusterState.status; } if (addresses.isEmpty()) { if (endpointNotFound.isOk()) { endpointNotFound = Status.UNAVAILABLE.withDescription( - "No usable endpoint from cluster(s): " + clusters); + "No usable endpoint from cluster: " + cluster); } else { endpointNotFound = Status.UNAVAILABLE.withCause(endpointNotFound.getCause()) @@ -274,22 +266,12 @@ private void handleEndpointResourceUpdate() { } private void handleEndpointResolutionError() { - boolean allInError = true; - Status error = null; - for (String cluster : clusters) { - ClusterState state = clusterStates.get(cluster); - if (state.status.isOk()) { - allInError = false; - } else { - error = state.status; - } - } - if (allInError) { + if (!clusterState.status.isOk()) { if (childLb != null) { - childLb.handleNameResolutionError(error); + childLb.handleNameResolutionError(clusterState.status); } else { helper.updateBalancingState( - TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error))); + TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(clusterState.status))); } } } @@ -306,10 +288,8 @@ private RefreshableHelper(Helper delegate) { @Override public void refreshNameResolution() { - for (ClusterState state : clusterStates.values()) { - if (state instanceof LogicalDnsClusterState) { - ((LogicalDnsClusterState) state).refresh(); - } + if (clusterState instanceof LogicalDnsClusterState) { + ((LogicalDnsClusterState) clusterState).refresh(); } } diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java index b5dcb271368..d871205f8eb 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancerProvider.java @@ -30,7 +30,6 @@ import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.client.Bootstrapper.ServerInfo; -import java.util.List; import java.util.Map; import java.util.Objects; import javax.annotation.Nullable; @@ -70,15 +69,15 @@ public LoadBalancer newLoadBalancer(Helper helper) { } static final class ClusterResolverConfig { - // Ordered list of clusters to be resolved. - final List discoveryMechanisms; + // Cluster to be resolved. + final DiscoveryMechanism discoveryMechanism; // GracefulSwitch configuration final Object lbConfig; private final boolean isHttp11ProxyAvailable; - ClusterResolverConfig(List discoveryMechanisms, Object lbConfig, + ClusterResolverConfig(DiscoveryMechanism discoveryMechanism, Object lbConfig, boolean isHttp11ProxyAvailable) { - this.discoveryMechanisms = checkNotNull(discoveryMechanisms, "discoveryMechanisms"); + this.discoveryMechanism = checkNotNull(discoveryMechanism, "discoveryMechanism"); this.lbConfig = checkNotNull(lbConfig, "lbConfig"); this.isHttp11ProxyAvailable = isHttp11ProxyAvailable; } @@ -89,7 +88,7 @@ boolean isHttp11ProxyAvailable() { @Override public int hashCode() { - return Objects.hash(discoveryMechanisms, lbConfig); + return Objects.hash(discoveryMechanism, lbConfig); } @Override @@ -101,14 +100,14 @@ public boolean equals(Object o) { return false; } ClusterResolverConfig that = (ClusterResolverConfig) o; - return discoveryMechanisms.equals(that.discoveryMechanisms) + return discoveryMechanism.equals(that.discoveryMechanism) && lbConfig.equals(that.lbConfig); } @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("discoveryMechanisms", discoveryMechanisms) + .add("discoveryMechanism", discoveryMechanism) .add("lbConfig", lbConfig) .toString(); } diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index 258e2909203..435289e6189 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat; import static io.grpc.xds.XdsLbPolicies.CLUSTER_RESOLVER_POLICY_NAME; +import static io.grpc.xds.XdsLbPolicies.PRIORITY_POLICY_NAME; import static io.grpc.xds.XdsTestControlPlaneService.ADS_TYPE_URL_CDS; import static io.grpc.xds.XdsTestControlPlaneService.ADS_TYPE_URL_EDS; import static io.grpc.xds.XdsTestControlPlaneService.ADS_TYPE_URL_LDS; @@ -27,6 +28,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import com.github.xds.type.v3.TypedStruct; import com.google.common.collect.ImmutableMap; @@ -157,7 +159,9 @@ public void setUp() throws Exception { new LeastRequestLoadBalancerProvider())); lbRegistry.register(new FakeLoadBalancerProvider("wrr_locality_experimental", new WrrLocalityLoadBalancerProvider())); - loadBalancer = new CdsLoadBalancer2(helper, lbRegistry); + CdsLoadBalancerProvider cdsLoadBalancerProvider = new CdsLoadBalancerProvider(lbRegistry); + lbRegistry.register(cdsLoadBalancerProvider); + loadBalancer = (CdsLoadBalancer2) cdsLoadBalancerProvider.newLoadBalancer(helper); cleanupRule.register(InProcessServerBuilder .forName("control-plane.example.com") @@ -169,6 +173,8 @@ public void setUp() throws Exception { SynchronizationContext syncContext = new SynchronizationContext((t, e) -> { throw new AssertionError(e); }); + when(helper.getSynchronizationContext()).thenReturn(syncContext); + when(helper.getScheduledExecutorService()).thenReturn(fakeClock.getScheduledExecutorService()); NameResolver.Args nameResolverArgs = NameResolver.Args.newBuilder() .setDefaultPort(8080) @@ -246,13 +252,12 @@ public void discoverTopLevelEdsCluster() { FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); assertThat(childBalancer.name).isEqualTo(CLUSTER_RESOLVER_POLICY_NAME); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; - assertThat(childLbConfig.discoveryMechanisms).isEqualTo( - Arrays.asList( - DiscoveryMechanism.forEds( - CLUSTER, EDS_SERVICE_NAME, lrsServerInfo, 100L, upstreamTlsContext, - Collections.emptyMap(), io.grpc.xds.EnvoyServerProtoData.OutlierDetection.create( - null, null, null, null, SuccessRateEjection.create(null, null, null, null), - FailurePercentageEjection.create(null, null, null, null))))); + assertThat(childLbConfig.discoveryMechanism).isEqualTo( + DiscoveryMechanism.forEds( + CLUSTER, EDS_SERVICE_NAME, lrsServerInfo, 100L, upstreamTlsContext, + Collections.emptyMap(), io.grpc.xds.EnvoyServerProtoData.OutlierDetection.create( + null, null, null, null, SuccessRateEjection.create(null, null, null, null), + FailurePercentageEjection.create(null, null, null, null)))); assertThat( GracefulSwitchLoadBalancerAccessor.getChildProvider(childLbConfig.lbConfig).getPolicyName()) .isEqualTo("wrr_locality_experimental"); @@ -296,11 +301,10 @@ public void discoverTopLevelLogicalDnsCluster() { FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); assertThat(childBalancer.name).isEqualTo(CLUSTER_RESOLVER_POLICY_NAME); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; - assertThat(childLbConfig.discoveryMechanisms).isEqualTo( - Arrays.asList( - DiscoveryMechanism.forLogicalDns( - CLUSTER, "dns.example.com:1111", lrsServerInfo, 100L, upstreamTlsContext, - Collections.emptyMap()))); + assertThat(childLbConfig.discoveryMechanism).isEqualTo( + DiscoveryMechanism.forLogicalDns( + CLUSTER, "dns.example.com:1111", lrsServerInfo, 100L, upstreamTlsContext, + Collections.emptyMap())); assertThat( GracefulSwitchLoadBalancerAccessor.getChildProvider(childLbConfig.lbConfig).getPolicyName()) .isEqualTo("wrr_locality_experimental"); @@ -332,10 +336,9 @@ public void nonAggregateCluster_resourceUpdate() { assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; - assertThat(childLbConfig.discoveryMechanisms).isEqualTo( - Arrays.asList( + assertThat(childLbConfig.discoveryMechanism).isEqualTo( DiscoveryMechanism.forEds( - CLUSTER, EDS_SERVICE_NAME, null, 100L, null, Collections.emptyMap(), null))); + CLUSTER, EDS_SERVICE_NAME, null, 100L, null, Collections.emptyMap(), null)); cluster = EDS_CLUSTER.toBuilder() .setCircuitBreakers(CircuitBreakers.newBuilder() @@ -348,10 +351,9 @@ public void nonAggregateCluster_resourceUpdate() { assertThat(childBalancers).hasSize(1); childBalancer = Iterables.getOnlyElement(childBalancers); childLbConfig = (ClusterResolverConfig) childBalancer.config; - assertThat(childLbConfig.discoveryMechanisms).isEqualTo( - Arrays.asList( + assertThat(childLbConfig.discoveryMechanism).isEqualTo( DiscoveryMechanism.forEds( - CLUSTER, EDS_SERVICE_NAME, null, 200L, null, Collections.emptyMap(), null))); + CLUSTER, EDS_SERVICE_NAME, null, 200L, null, Collections.emptyMap(), null)); } @Test @@ -363,10 +365,9 @@ public void nonAggregateCluster_resourceRevoked() { assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; - assertThat(childLbConfig.discoveryMechanisms).isEqualTo( - Arrays.asList( + assertThat(childLbConfig.discoveryMechanism).isEqualTo( DiscoveryMechanism.forEds( - CLUSTER, EDS_SERVICE_NAME, null, null, null, Collections.emptyMap(), null))); + CLUSTER, EDS_SERVICE_NAME, null, null, null, Collections.emptyMap(), null)); controlPlaneService.setXdsConfig(ADS_TYPE_URL_CDS, ImmutableMap.of()); @@ -395,10 +396,9 @@ public void dynamicCluster() { assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; - assertThat(childLbConfig.discoveryMechanisms).isEqualTo( - Arrays.asList( + assertThat(childLbConfig.discoveryMechanism).isEqualTo( DiscoveryMechanism.forEds( - clusterName, EDS_SERVICE_NAME, null, null, null, Collections.emptyMap(), null))); + clusterName, EDS_SERVICE_NAME, null, null, null, Collections.emptyMap(), null)); assertThat(this.lastXdsConfig.getClusters()).containsKey(clusterName); shutdownLoadBalancer(); @@ -406,7 +406,12 @@ public void dynamicCluster() { } @Test - public void discoverAggregateCluster() { + public void discoverAggregateCluster_createsPriorityLbPolicy() { + lbRegistry.register(new FakeLoadBalancerProvider(PRIORITY_POLICY_NAME)); + CdsLoadBalancerProvider cdsLoadBalancerProvider = new CdsLoadBalancerProvider(lbRegistry); + lbRegistry.register(cdsLoadBalancerProvider); + loadBalancer = (CdsLoadBalancer2) cdsLoadBalancerProvider.newLoadBalancer(helper); + String cluster1 = "cluster-01.googleapis.com"; String cluster2 = "cluster-02.googleapis.com"; String cluster3 = "cluster-03.googleapis.com"; @@ -454,25 +459,79 @@ public void discoverAggregateCluster() { .setMaxRequests(UInt32Value.newBuilder().setValue(100)))) .build(), cluster4, EDS_CLUSTER.toBuilder().setName(cluster4).build())); - startXdsDepManager(); + startXdsDepManager(); verify(helper, never()).updateBalancingState(eq(ConnectivityState.TRANSIENT_FAILURE), any()); assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); - assertThat(childBalancer.name).isEqualTo(CLUSTER_RESOLVER_POLICY_NAME); - ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config; - // Clusters are resolved recursively, later duplicates removed: [cluster3, cluster4, cluster2] - assertThat(childLbConfig.discoveryMechanisms).isEqualTo( - Arrays.asList( - DiscoveryMechanism.forEds( - cluster3, EDS_SERVICE_NAME, null, 100L, null, Collections.emptyMap(), null), - DiscoveryMechanism.forEds( - cluster4, EDS_SERVICE_NAME, null, null, null, Collections.emptyMap(), null), - DiscoveryMechanism.forLogicalDns( - cluster2, "dns.example.com:1111", null, null, null, Collections.emptyMap()))); + assertThat(childBalancer.name).isEqualTo(PRIORITY_POLICY_NAME); + PriorityLoadBalancerProvider.PriorityLbConfig childLbConfig = + (PriorityLoadBalancerProvider.PriorityLbConfig) childBalancer.config; + assertThat(childLbConfig.priorities).hasSize(3); + assertThat(childLbConfig.priorities.get(0)).isEqualTo(cluster3); + assertThat(childLbConfig.priorities.get(1)).isEqualTo(cluster4); + assertThat(childLbConfig.priorities.get(2)).isEqualTo(cluster2); + assertThat(childLbConfig.childConfigs).hasSize(3); + PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig childConfig3 = + childLbConfig.childConfigs.get(cluster3); assertThat( - GracefulSwitchLoadBalancerAccessor.getChildProvider(childLbConfig.lbConfig).getPolicyName()) - .isEqualTo("ring_hash_experimental"); // dominated by top-level cluster's config + GracefulSwitchLoadBalancerAccessor.getChildProvider(childConfig3.childConfig).getPolicyName()) + .isEqualTo("cds_experimental"); + PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig childConfig4 = + childLbConfig.childConfigs.get(cluster4); + assertThat( + GracefulSwitchLoadBalancerAccessor.getChildProvider(childConfig4.childConfig).getPolicyName()) + .isEqualTo("cds_experimental"); + PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig childConfig2 = + childLbConfig.childConfigs.get(cluster2); + assertThat( + GracefulSwitchLoadBalancerAccessor.getChildProvider(childConfig2.childConfig).getPolicyName()) + .isEqualTo("cds_experimental"); + } + + @Test + // Both priorities will get tried using real priority LB policy. + public void discoverAggregateCluster_testChildCdsLbPolicyParsing() { + lbRegistry.register(new PriorityLoadBalancerProvider()); + CdsLoadBalancerProvider cdsLoadBalancerProvider = new CdsLoadBalancerProvider(lbRegistry); + lbRegistry.register(cdsLoadBalancerProvider); + loadBalancer = (CdsLoadBalancer2) cdsLoadBalancerProvider.newLoadBalancer(helper); + + String cluster1 = "cluster-01.googleapis.com"; + String cluster2 = "cluster-02.googleapis.com"; + controlPlaneService.setXdsConfig(ADS_TYPE_URL_CDS, ImmutableMap.of( + // CLUSTER (aggr.) -> [cluster1 (EDS), cluster2 (EDS)] + CLUSTER, Cluster.newBuilder() + .setName(CLUSTER) + .setClusterType(Cluster.CustomClusterType.newBuilder() + .setName("envoy.clusters.aggregate") + .setTypedConfig(Any.pack(ClusterConfig.newBuilder() + .addClusters(cluster1) + .addClusters(cluster2) + .build()))) + .build(), + cluster1, EDS_CLUSTER.toBuilder().setName(cluster1).build(), + cluster2, EDS_CLUSTER.toBuilder().setName(cluster2).build())); + startXdsDepManager(); + + verify(helper, never()).updateBalancingState(eq(ConnectivityState.TRANSIENT_FAILURE), any()); + assertThat(childBalancers).hasSize(2); + ClusterResolverConfig cluster1ResolverConfig = + (ClusterResolverConfig) childBalancers.get(0).config; + assertThat(cluster1ResolverConfig.discoveryMechanism.cluster) + .isEqualTo("cluster-01.googleapis.com"); + assertThat(cluster1ResolverConfig.discoveryMechanism.type) + .isEqualTo(DiscoveryMechanism.Type.EDS); + assertThat(cluster1ResolverConfig.discoveryMechanism.edsServiceName) + .isEqualTo("backend-service-1.googleapis.com"); + ClusterResolverConfig cluster2ResolverConfig = + (ClusterResolverConfig) childBalancers.get(1).config; + assertThat(cluster2ResolverConfig.discoveryMechanism.cluster) + .isEqualTo("cluster-02.googleapis.com"); + assertThat(cluster2ResolverConfig.discoveryMechanism.type) + .isEqualTo(DiscoveryMechanism.Type.EDS); + assertThat(cluster2ResolverConfig.discoveryMechanism.edsServiceName) + .isEqualTo("backend-service-1.googleapis.com"); } @Test @@ -500,6 +559,11 @@ public void aggregateCluster_noChildren() { @Test public void aggregateCluster_noNonAggregateClusterExits_returnErrorPicker() { + lbRegistry.register(new PriorityLoadBalancerProvider()); + CdsLoadBalancerProvider cdsLoadBalancerProvider = new CdsLoadBalancerProvider(lbRegistry); + lbRegistry.register(cdsLoadBalancerProvider); + loadBalancer = (CdsLoadBalancer2) cdsLoadBalancerProvider.newLoadBalancer(helper); + String cluster1 = "cluster-01.googleapis.com"; controlPlaneService.setXdsConfig(ADS_TYPE_URL_CDS, ImmutableMap.of( // CLUSTER (aggr.) -> [cluster1 (missing)] @@ -550,8 +614,8 @@ public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThroug loadBalancer.handleNameResolutionError(Status.UNAVAILABLE.withDescription("unreachable")); assertThat(childBalancer.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE); assertThat(childBalancer.upstreamError.getDescription()).isEqualTo("unreachable"); - verify(helper, never()).updateBalancingState( - any(ConnectivityState.class), any(SubchannelPicker.class)); + verify(helper).updateBalancingState( + eq(ConnectivityState.CONNECTING), any(SubchannelPicker.class)); } @Test diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index d701f281c01..c6b65d24b9d 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -244,7 +244,7 @@ public void tearDown() { @Test public void edsClustersWithRingHashEndpointLbPolicy() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanism1), ringHash, false); + edsDiscoveryMechanism1, ringHash, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); @@ -310,7 +310,7 @@ public void edsClustersWithRingHashEndpointLbPolicy() { @Test public void edsClustersWithLeastRequestEndpointLbPolicy() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanism1), leastRequest, false); + edsDiscoveryMechanism1, leastRequest, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); @@ -357,7 +357,7 @@ public void edsClustersWithLeastRequestEndpointLbPolicy() { @Test public void edsClustersEndpointHostname_addedToAddressAttribute() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanismWithOutlierDetection), leastRequest, false); + edsDiscoveryMechanismWithOutlierDetection, leastRequest, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); @@ -384,7 +384,7 @@ public void edsClustersEndpointHostname_addedToAddressAttribute() { @Test public void endpointAddressRewritten_whenProxyMetadataIsInEndpointMetadata() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanismWithOutlierDetection), leastRequest, true); + edsDiscoveryMechanismWithOutlierDetection, leastRequest, true); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); @@ -403,7 +403,7 @@ public void endpointAddressRewritten_whenProxyMetadataIsInEndpointMetadata() { LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( Arrays.asList( LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true, - "hostname1", endpointMetadata)), + "hostname1", endpointMetadata)), 100 /* localityWeight */, 1 /* priority */, localityMetadata); xdsClient.deliverClusterLoadAssignment( @@ -431,7 +431,7 @@ public void endpointAddressRewritten_whenProxyMetadataIsInEndpointMetadata() { @Test public void endpointAddressRewritten_whenProxyMetadataIsInLocalityMetadata() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanismWithOutlierDetection), leastRequest, true); + edsDiscoveryMechanismWithOutlierDetection, leastRequest, true); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); @@ -450,7 +450,7 @@ public void endpointAddressRewritten_whenProxyMetadataIsInLocalityMetadata() { LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( Arrays.asList( LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true, - "hostname2", endpointMetadata)), + "hostname2", endpointMetadata)), 100 /* localityWeight */, 1 /* priority */, localityMetadata); xdsClient.deliverClusterLoadAssignment( @@ -478,48 +478,36 @@ public void endpointAddressRewritten_whenProxyMetadataIsInLocalityMetadata() { @Test public void onlyEdsClusters_receivedEndpoints() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, edsDiscoveryMechanism2), roundRobin, false); + edsDiscoveryMechanism2, roundRobin, false); deliverLbConfig(config); - assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1, EDS_SERVICE_NAME2); + assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME2); assertThat(childBalancers).isEmpty(); // CLUSTER1 has priority 1 (priority3), which has locality 2, which has endpoint3. // CLUSTER2 has priority 1 (priority1) and 2 (priority2); priority1 has locality1, // which has endpoint1 and endpoint2; priority2 has locality3, which has endpoint4. EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); - EquivalentAddressGroup endpoint3 = makeAddress("endpoint-addr-3"); EquivalentAddressGroup endpoint4 = makeAddress("endpoint-addr-4"); LocalityLbEndpoints localityLbEndpoints1 = - LocalityLbEndpoints.create( - Arrays.asList( - LbEndpoint.create(endpoint1, 100, - true, "hostname1", ImmutableMap.of()), - LbEndpoint.create(endpoint2, 100, - true, "hostname1", ImmutableMap.of())), - 70 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); - LocalityLbEndpoints localityLbEndpoints2 = - LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint3, 100, true, - "hostname2", ImmutableMap.of())), - 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); + LocalityLbEndpoints.create( + Arrays.asList( + LbEndpoint.create(endpoint1, 100, + true, "hostname1", ImmutableMap.of()), + LbEndpoint.create(endpoint2, 100, + true, "hostname1", ImmutableMap.of())), + 70 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); LocalityLbEndpoints localityLbEndpoints3 = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint4, 100, true, - "hostname3", ImmutableMap.of())), + "hostname3", ImmutableMap.of())), 20 /* localityWeight */, 2 /* priority */, ImmutableMap.of()); String priority1 = CLUSTER2 + "[child1]"; String priority2 = CLUSTER2 + "[child2]"; - String priority3 = CLUSTER1 + "[child1]"; // CLUSTER2: locality1 with priority 1 and locality3 with priority 2. xdsClient.deliverClusterLoadAssignment( - EDS_SERVICE_NAME2, - ImmutableMap.of(locality1, localityLbEndpoints1, locality3, localityLbEndpoints3)); - assertThat(childBalancers).isEmpty(); // not created until all clusters resolved - - // CLUSTER1: locality2 with priority 1. - xdsClient.deliverClusterLoadAssignment( - EDS_SERVICE_NAME1, Collections.singletonMap(locality2, localityLbEndpoints2)); + EDS_SERVICE_NAME2, + ImmutableMap.of(locality1, localityLbEndpoints1, locality3, localityLbEndpoints3)); // Endpoints of all clusters have been resolved. assertThat(childBalancers).hasSize(1); @@ -527,12 +515,12 @@ public void onlyEdsClusters_receivedEndpoints() { assertThat(childBalancer.name).isEqualTo(PRIORITY_POLICY_NAME); PriorityLbConfig priorityLbConfig = (PriorityLbConfig) childBalancer.config; assertThat(priorityLbConfig.priorities) - .containsExactly(priority3, priority1, priority2).inOrder(); + .containsExactly(priority1, priority2).inOrder(); PriorityChildConfig priorityChildConfig1 = priorityLbConfig.childConfigs.get(priority1); assertThat(priorityChildConfig1.ignoreReresolution).isTrue(); assertThat(GracefulSwitchLoadBalancerAccessor.getChildProvider(priorityChildConfig1.childConfig) - .getPolicyName()) + .getPolicyName()) .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig1 = (ClusterImplConfig) GracefulSwitchLoadBalancerAccessor.getChildConfig(priorityChildConfig1.childConfig); @@ -547,7 +535,7 @@ public void onlyEdsClusters_receivedEndpoints() { PriorityChildConfig priorityChildConfig2 = priorityLbConfig.childConfigs.get(priority2); assertThat(priorityChildConfig2.ignoreReresolution).isTrue(); assertThat(GracefulSwitchLoadBalancerAccessor.getChildProvider(priorityChildConfig2.childConfig) - .getPolicyName()) + .getPolicyName()) .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig2 = (ClusterImplConfig) GracefulSwitchLoadBalancerAccessor.getChildConfig(priorityChildConfig2.childConfig); @@ -559,15 +547,6 @@ public void onlyEdsClusters_receivedEndpoints() { GracefulSwitchLoadBalancerAccessor.getChildProvider(wrrLocalityConfig2.childConfig); assertThat(childProvider2.getPolicyName()).isEqualTo("round_robin"); - PriorityChildConfig priorityChildConfig3 = priorityLbConfig.childConfigs.get(priority3); - assertThat(priorityChildConfig3.ignoreReresolution).isTrue(); - assertThat(GracefulSwitchLoadBalancerAccessor.getChildProvider(priorityChildConfig3.childConfig) - .getPolicyName()) - .isEqualTo(CLUSTER_IMPL_POLICY_NAME); - ClusterImplConfig clusterImplConfig3 = (ClusterImplConfig) - GracefulSwitchLoadBalancerAccessor.getChildConfig(priorityChildConfig3.childConfig); - assertClusterImplConfig(clusterImplConfig3, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, - tlsContext, Collections.emptyList(), WRR_LOCALITY_POLICY_NAME); WrrLocalityConfig wrrLocalityConfig3 = (WrrLocalityConfig) GracefulSwitchLoadBalancerAccessor.getChildConfig(clusterImplConfig1.childConfig); LoadBalancerProvider childProvider3 = @@ -594,7 +573,7 @@ public void onlyEdsClusters_receivedEndpoints() { private void verifyEdsPriorityNames(List want, Map... updates) { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism2), roundRobin, false); + edsDiscoveryMechanism2, roundRobin, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME2); assertThat(childBalancers).isEmpty(); @@ -616,7 +595,7 @@ private void verifyEdsPriorityNames(List want, public void edsUpdatePriorityName_twoPriorities() { verifyEdsPriorityNames(Arrays.asList(CLUSTER2 + "[child1]", CLUSTER2 + "[child2]"), ImmutableMap.of(locality1, createEndpoints(1), - locality2, createEndpoints(2) + locality2, createEndpoints(2) )); } @@ -652,8 +631,8 @@ public void edsUpdatePriorityName_mergeTwoPriorities() { locality3, createEndpoints(3), locality2, createEndpoints(2)), ImmutableMap.of(locality1, createEndpoints(2), - locality3, createEndpoints(1), - locality2, createEndpoints(1) + locality3, createEndpoints(1), + locality2, createEndpoints(1) )); } @@ -661,76 +640,64 @@ private LocalityLbEndpoints createEndpoints(int priority) { return LocalityLbEndpoints.create( Arrays.asList( LbEndpoint.create(makeAddress("endpoint-addr-1"), 100, - true, "hostname1", ImmutableMap.of()), + true, "hostname1", ImmutableMap.of()), LbEndpoint.create(makeAddress("endpoint-addr-2"), 100, - true, "hostname2", ImmutableMap.of())), + true, "hostname2", ImmutableMap.of())), 70 /* localityWeight */, priority /* priority */, ImmutableMap.of()); } @Test public void onlyEdsClusters_resourceNeverExist_returnErrorPicker() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, edsDiscoveryMechanism2), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); - assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1, EDS_SERVICE_NAME2); + assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); reset(helper); xdsClient.deliverResourceNotFound(EDS_SERVICE_NAME1); - verify(helper, never()).updateBalancingState( - any(ConnectivityState.class), any(SubchannelPicker.class)); // wait for CLUSTER2's results - xdsClient.deliverResourceNotFound(EDS_SERVICE_NAME2); verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); assertPicker( pickerCaptor.getValue(), Status.UNAVAILABLE.withDescription( - "No usable endpoint from cluster(s): " + Arrays.asList(CLUSTER1, CLUSTER2)), + "No usable endpoint from cluster: " + CLUSTER1), null); } @Test - public void onlyEdsClusters_allResourcesRevoked_shutDownChildLbPolicy() { + public void edsCluster_resourcesRevoked_shutDownChildLbPolicy() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, edsDiscoveryMechanism2), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); - assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1, EDS_SERVICE_NAME2); + assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); reset(helper); EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); - EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint1, 100, true, - "hostname1", ImmutableMap.of())), + "hostname1", ImmutableMap.of())), 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); - LocalityLbEndpoints localityLbEndpoints2 = - LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint2, 100, true, - "hostname2", ImmutableMap.of())), - 20 /* localityWeight */, 2 /* priority */, ImmutableMap.of()); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints1)); - xdsClient.deliverClusterLoadAssignment( - EDS_SERVICE_NAME2, Collections.singletonMap(locality2, localityLbEndpoints2)); assertThat(childBalancers).hasSize(1); // child LB policy created FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); - assertThat(((PriorityLbConfig) childBalancer.config).priorities).hasSize(2); - assertAddressesEqual(Arrays.asList(endpoint1, endpoint2), childBalancer.addresses); + assertThat(((PriorityLbConfig) childBalancer.config).priorities).hasSize(1); + assertAddressesEqual(Arrays.asList(endpoint1), childBalancer.addresses); - xdsClient.deliverResourceNotFound(EDS_SERVICE_NAME2); xdsClient.deliverResourceNotFound(EDS_SERVICE_NAME1); verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); Status expectedError = Status.UNAVAILABLE.withDescription( - "No usable endpoint from cluster(s): " + Arrays.asList(CLUSTER1, CLUSTER2)); + "No usable endpoint from cluster: " + CLUSTER1); assertPicker(pickerCaptor.getValue(), expectedError, null); } @Test public void handleEdsResource_ignoreUnhealthyEndpoints() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanism1), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); @@ -738,9 +705,9 @@ public void handleEdsResource_ignoreUnhealthyEndpoints() { LocalityLbEndpoints.create( Arrays.asList( LbEndpoint.create(endpoint1, 100, false /* isHealthy */, - "hostname1", ImmutableMap.of()), + "hostname1", ImmutableMap.of()), LbEndpoint.create(endpoint2, 100, true /* isHealthy */, - "hostname2", ImmutableMap.of())), + "hostname2", ImmutableMap.of())), 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); @@ -752,19 +719,19 @@ public void handleEdsResource_ignoreUnhealthyEndpoints() { @Test public void handleEdsResource_ignoreLocalitiesWithNoHealthyEndpoints() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanism1), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint1, 100, false /* isHealthy */, - "hostname1", ImmutableMap.of())), + "hostname1", ImmutableMap.of())), 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); LocalityLbEndpoints localityLbEndpoints2 = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint2, 100, true /* isHealthy */, - "hostname2", ImmutableMap.of())), + "hostname2", ImmutableMap.of())), 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, @@ -779,20 +746,20 @@ public void handleEdsResource_ignoreLocalitiesWithNoHealthyEndpoints() { @Test public void handleEdsResource_ignorePrioritiesWithNoHealthyEndpoints() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanism1), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints1 = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint1, 100, false /* isHealthy */, - "hostname1", ImmutableMap.of())), + "hostname1", ImmutableMap.of())), 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); LocalityLbEndpoints localityLbEndpoints2 = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint2, 200, true /* isHealthy */, - "hostname2", ImmutableMap.of())), - 10 /* localityWeight */, 2 /* priority */, ImmutableMap.of()); + "hostname2", ImmutableMap.of())), + 10 /* localityWeight */, 2 /* priority */, ImmutableMap.of()); String priority2 = CLUSTER1 + "[child2]"; xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, @@ -805,7 +772,7 @@ public void handleEdsResource_ignorePrioritiesWithNoHealthyEndpoints() { @Test public void handleEdsResource_noHealthyEndpoint() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(edsDiscoveryMechanism1), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1"); LocalityLbEndpoints localityLbEndpoints = @@ -822,7 +789,7 @@ public void handleEdsResource_noHealthyEndpoint() { assertPicker( pickerCaptor.getValue(), Status.UNAVAILABLE.withDescription( - "No usable endpoint from cluster(s): " + Collections.singleton(CLUSTER1)), + "No usable endpoint from cluster: " + CLUSTER1), null); } @@ -840,7 +807,7 @@ public void oldListenerCallback_onlyLogicalDnsCluster_endpointsResolved() { void do_onlyLogicalDnsCluster_endpointsResolved() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(logicalDnsDiscoveryMechanism), roundRobin, false); + logicalDnsDiscoveryMechanism, roundRobin, false); deliverLbConfig(config); FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); @@ -856,7 +823,7 @@ void do_onlyLogicalDnsCluster_endpointsResolved() { PriorityChildConfig priorityChildConfig = priorityLbConfig.childConfigs.get(priority); assertThat(priorityChildConfig.ignoreReresolution).isFalse(); assertThat(GracefulSwitchLoadBalancerAccessor.getChildProvider(priorityChildConfig.childConfig) - .getPolicyName()) + .getPolicyName()) .isEqualTo(CLUSTER_IMPL_POLICY_NAME); ClusterImplConfig clusterImplConfig = (ClusterImplConfig) GracefulSwitchLoadBalancerAccessor.getChildConfig(priorityChildConfig.childConfig); @@ -872,7 +839,7 @@ void do_onlyLogicalDnsCluster_endpointsResolved() { @Test public void onlyLogicalDnsCluster_handleRefreshNameResolution() { ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(logicalDnsDiscoveryMechanism), roundRobin, false); + logicalDnsDiscoveryMechanism, roundRobin, false); deliverLbConfig(config); FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); @@ -899,9 +866,9 @@ public void oldListenerCallback_resolutionError_backoffAndRefresh() { void do_onlyLogicalDnsCluster_resolutionError_backoffAndRefresh() { InOrder inOrder = Mockito.inOrder(helper, backoffPolicyProvider, - backoffPolicy1, backoffPolicy2); + backoffPolicy1, backoffPolicy2); ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(logicalDnsDiscoveryMechanism), roundRobin, false); + logicalDnsDiscoveryMechanism, roundRobin, false); deliverLbConfig(config); FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); @@ -947,7 +914,7 @@ void do_onlyLogicalDnsCluster_resolutionError_backoffAndRefresh() { public void onlyLogicalDnsCluster_refreshNameResolutionRaceWithResolutionError() { InOrder inOrder = Mockito.inOrder(backoffPolicyProvider, backoffPolicy1, backoffPolicy2); ClusterResolverConfig config = new ClusterResolverConfig( - Collections.singletonList(logicalDnsDiscoveryMechanism), roundRobin, false); + logicalDnsDiscoveryMechanism, roundRobin, false); deliverLbConfig(config); FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); @@ -983,119 +950,22 @@ public void onlyLogicalDnsCluster_refreshNameResolutionRaceWithResolutionError() } @Test - public void edsClustersAndLogicalDnsCluster_receivedEndpoints() { - ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, logicalDnsDiscoveryMechanism), roundRobin, false); - deliverLbConfig(config); - assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); - FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); - assertThat(childBalancers).isEmpty(); - EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); // DNS endpoint - EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); // DNS endpoint - EquivalentAddressGroup endpoint3 = makeAddress("endpoint-addr-3"); // EDS endpoint - resolver.deliverEndpointAddresses(Arrays.asList(endpoint1, endpoint2)); - LocalityLbEndpoints localityLbEndpoints = - LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint3, 100, true, - "hostname3", ImmutableMap.of())), - 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); - xdsClient.deliverClusterLoadAssignment( - EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); - - assertThat(childBalancers).hasSize(1); - FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); - assertThat(((PriorityLbConfig) childBalancer.config).priorities) - .containsExactly(CLUSTER1 + "[child1]", CLUSTER_DNS + "[child0]").inOrder(); - assertAddressesEqual(Arrays.asList(endpoint3, endpoint1, endpoint2), - childBalancer.addresses); // ordered by cluster then addresses - assertAddressesEqual(AddressFilter.filter(AddressFilter.filter( - childBalancer.addresses, CLUSTER1 + "[child1]"), - "{region=\"test-region-1\", zone=\"test-zone-1\", sub_zone=\"test-subzone-1\"}"), - Collections.singletonList(endpoint3)); - assertAddressesEqual(AddressFilter.filter(AddressFilter.filter( - childBalancer.addresses, CLUSTER_DNS + "[child0]"), - "{region=\"\", zone=\"\", sub_zone=\"\"}"), - Arrays.asList(endpoint1, endpoint2)); - } - - @Test - public void noEdsResourceExists_useDnsResolutionResults() { - ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, logicalDnsDiscoveryMechanism), roundRobin, false); - deliverLbConfig(config); - assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); - FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); - assertThat(childBalancers).isEmpty(); - reset(helper); - xdsClient.deliverResourceNotFound(EDS_SERVICE_NAME1); - verify(helper, never()).updateBalancingState( - any(ConnectivityState.class), any(SubchannelPicker.class)); // wait for DNS results - - EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); - EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); - resolver.deliverEndpointAddresses(Arrays.asList(endpoint1, endpoint2)); - assertThat(childBalancers).hasSize(1); - FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); - String priority = Iterables.getOnlyElement( - ((PriorityLbConfig) childBalancer.config).priorities); - assertThat(priority).isEqualTo(CLUSTER_DNS + "[child0]"); - assertAddressesEqual(Arrays.asList(endpoint1, endpoint2), childBalancer.addresses); - } - - @Test - public void edsResourceRevoked_dnsResolutionError_shutDownChildLbPolicyAndReturnErrorPicker() { - ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, logicalDnsDiscoveryMechanism), roundRobin, false); - deliverLbConfig(config); - assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); - FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); - assertThat(childBalancers).isEmpty(); - reset(helper); - EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1"); - LocalityLbEndpoints localityLbEndpoints = - LocalityLbEndpoints.create( - Collections.singletonList(LbEndpoint.create(endpoint, 100, true, - "hostname1", ImmutableMap.of())), - 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); - xdsClient.deliverClusterLoadAssignment( - EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); - resolver.deliverError(Status.UNKNOWN.withDescription("I am lost")); - assertThat(childBalancers).hasSize(1); - FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); - assertThat(((PriorityLbConfig) childBalancer.config).priorities) - .containsExactly(CLUSTER1 + "[child1]"); - assertAddressesEqual(Collections.singletonList(endpoint), childBalancer.addresses); - assertThat(childBalancer.shutdown).isFalse(); - xdsClient.deliverResourceNotFound(EDS_SERVICE_NAME1); - assertThat(childBalancer.shutdown).isTrue(); - verify(helper).updateBalancingState( - eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); - assertPicker(pickerCaptor.getValue(), - Status.UNAVAILABLE.withDescription("I am lost"), null); - } - - @Test - public void resolutionErrorAfterChildLbCreated_propagateErrorIfAllClustersEncounterError() { + public void resolutionErrorAfterChildLbCreated_propagateError() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, logicalDnsDiscoveryMechanism), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); - FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); reset(helper); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1"); LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint, 100, true, - "hostname1", ImmutableMap.of())), + "hostname1", ImmutableMap.of())), 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); xdsClient.deliverClusterLoadAssignment( - EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); - assertThat(childBalancers).isEmpty(); // not created until all clusters resolved. - - resolver.deliverError(Status.UNKNOWN.withDescription("I am lost")); + EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); - // DNS resolution failed, but there are EDS endpoints can be used. assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); // child LB created assertThat(childBalancer.upstreamError).isNull(); // should not propagate error to child LB @@ -1103,60 +973,53 @@ public void resolutionErrorAfterChildLbCreated_propagateErrorIfAllClustersEncoun xdsClient.deliverError(Status.RESOURCE_EXHAUSTED.withDescription("out of memory")); assertThat(childBalancer.upstreamError).isNotNull(); // last cluster's (DNS) error propagated - assertThat(childBalancer.upstreamError.getCode()).isEqualTo(Code.UNKNOWN); - assertThat(childBalancer.upstreamError.getDescription()).isEqualTo("I am lost"); + assertThat(childBalancer.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(childBalancer.upstreamError.getDescription()) + .isEqualTo("Unable to load EDS backend-service-foo.googleapis.com. xDS server returned: " + + "RESOURCE_EXHAUSTED: out of memory"); assertThat(childBalancer.shutdown).isFalse(); verify(helper, never()).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), any(SubchannelPicker.class)); } @Test - public void resolutionErrorBeforeChildLbCreated_returnErrorPickerIfAllClustersEncounterError() { + public void resolutionErrorBeforeChildLbCreated_returnErrorPicker() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, logicalDnsDiscoveryMechanism), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); - FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); reset(helper); - xdsClient.deliverError(Status.UNIMPLEMENTED.withDescription("not found")); + xdsClient.deliverError(Status.RESOURCE_EXHAUSTED.withDescription("OOM")); assertThat(childBalancers).isEmpty(); - verify(helper, never()).updateBalancingState( - eq(ConnectivityState.TRANSIENT_FAILURE), any(SubchannelPicker.class)); // wait for DNS - Status dnsError = Status.UNKNOWN.withDescription("I am lost"); - resolver.deliverError(dnsError); verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); - assertPicker( - pickerCaptor.getValue(), - Status.UNAVAILABLE.withDescription(dnsError.getDescription()), - null); + PickResult result = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); + Status actualStatus = result.getStatus(); + assertThat(actualStatus.getCode()).isEqualTo(Status.Code.UNAVAILABLE); + assertThat(actualStatus.getDescription()).contains("RESOURCE_EXHAUSTED: OOM"); } @Test - public void resolutionErrorBeforeChildLbCreated_edsOnly_returnErrorPicker() { + public void handleNameResolutionErrorFromUpstream_eds_beforeChildLbCreated_returnErrorPicker() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertThat(childBalancers).isEmpty(); reset(helper); - xdsClient.deliverError(Status.RESOURCE_EXHAUSTED.withDescription("OOM")); - assertThat(childBalancers).isEmpty(); + Status upstreamError = Status.UNAVAILABLE.withDescription("unreachable"); + loadBalancer.handleNameResolutionError(upstreamError); verify(helper).updateBalancingState( eq(ConnectivityState.TRANSIENT_FAILURE), pickerCaptor.capture()); - PickResult result = pickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class)); - Status actualStatus = result.getStatus(); - assertThat(actualStatus.getCode()).isEqualTo(Status.Code.UNAVAILABLE); - assertThat(actualStatus.getDescription()).contains("RESOURCE_EXHAUSTED: OOM"); + assertPicker(pickerCaptor.getValue(), upstreamError, null); } @Test - public void handleNameResolutionErrorFromUpstream_beforeChildLbCreated_returnErrorPicker() { + public void handleNameResolutionErrorFromUpstream_lDns_beforeChildLbCreated_returnErrorPicker() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, logicalDnsDiscoveryMechanism), roundRobin, false); + logicalDnsDiscoveryMechanism, roundRobin, false); deliverLbConfig(config); - assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); reset(helper); @@ -1168,16 +1031,14 @@ public void handleNameResolutionErrorFromUpstream_beforeChildLbCreated_returnErr } @Test - public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThrough() { + public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_eds_fallThrough() { ClusterResolverConfig config = new ClusterResolverConfig( - Arrays.asList(edsDiscoveryMechanism1, logicalDnsDiscoveryMechanism), roundRobin, false); + edsDiscoveryMechanism1, roundRobin, false); deliverLbConfig(config); assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1); - FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); assertThat(childBalancers).isEmpty(); reset(helper); EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr-1"); - EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); LocalityLbEndpoints localityLbEndpoints = LocalityLbEndpoints.create( Collections.singletonList(LbEndpoint.create(endpoint1, 100, true, @@ -1185,12 +1046,34 @@ public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThroug 10 /* localityWeight */, 1 /* priority */, ImmutableMap.of()); xdsClient.deliverClusterLoadAssignment( EDS_SERVICE_NAME1, Collections.singletonMap(locality1, localityLbEndpoints)); + assertThat(childBalancers).hasSize(1); + FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); + assertThat(((PriorityLbConfig) childBalancer.config).priorities) + .containsExactly(CLUSTER1 + "[child1]"); + assertAddressesEqual(Arrays.asList(endpoint1), childBalancer.addresses); + + loadBalancer.handleNameResolutionError(Status.UNAVAILABLE.withDescription("unreachable")); + assertThat(childBalancer.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE); + assertThat(childBalancer.upstreamError.getDescription()).isEqualTo("unreachable"); + verify(helper, never()).updateBalancingState( + any(ConnectivityState.class), any(SubchannelPicker.class)); + } + + @Test + public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_logicalDns_fallThrough() { + ClusterResolverConfig config = new ClusterResolverConfig( + logicalDnsDiscoveryMechanism, roundRobin, false); + deliverLbConfig(config); + FakeNameResolver resolver = assertResolverCreated("/" + DNS_HOST_NAME); + assertThat(childBalancers).isEmpty(); + reset(helper); + EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr-2"); resolver.deliverEndpointAddresses(Collections.singletonList(endpoint2)); assertThat(childBalancers).hasSize(1); FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers); assertThat(((PriorityLbConfig) childBalancer.config).priorities) - .containsExactly(CLUSTER1 + "[child1]", CLUSTER_DNS + "[child0]"); - assertAddressesEqual(Arrays.asList(endpoint1, endpoint2), childBalancer.addresses); + .containsExactly(CLUSTER_DNS + "[child0]"); + assertAddressesEqual(Arrays.asList(endpoint2), childBalancer.addresses); loadBalancer.handleNameResolutionError(Status.UNAVAILABLE.withDescription("unreachable")); assertThat(childBalancer.upstreamError.getCode()).isEqualTo(Code.UNAVAILABLE);