xds: Fix load reporting when pick first is used for locality-routing. (#11495)

* Determine subchannel's network locality from connected address, instead of assuming that all addresses for a subchannel are in the same locality.
This commit is contained in:
Vindhya Ningegowda 2024-08-31 16:07:53 -07:00 committed by GitHub
parent 421e2371e9
commit 1dae144f0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 351 additions and 63 deletions

View File

@ -0,0 +1,31 @@
/*
* Copyright 2024 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc;
/**
* An internal class. Do not use.
*
* <p>An interface to provide the attributes for address connected by subchannel.
*/
@Internal
public interface InternalSubchannelAddressAttributes {
/**
* Return attributes of the server address connected by sub channel.
*/
public Attributes getConnectedAddressAttributes();
}

View File

@ -1428,6 +1428,18 @@ public abstract class LoadBalancer {
public Object getInternalSubchannel() {
throw new UnsupportedOperationException();
}
/**
* (Internal use only) returns attributes of the address subchannel is connected to.
*
* <p>Warning: this is INTERNAL API, is not supposed to be used by external users, and may
* change without notice. If you think you must use it, please file an issue and we can consider
* removing its "internal" status.
*/
@Internal
public Attributes getConnectedAddressAttributes() {
throw new UnsupportedOperationException();
}
}
/**

View File

@ -157,6 +157,8 @@ final class InternalSubchannel implements InternalInstrumented<ChannelStats>, Tr
private Status shutdownReason;
private volatile Attributes connectedAddressAttributes;
InternalSubchannel(List<EquivalentAddressGroup> addressGroups, String authority, String userAgent,
BackoffPolicy.Provider backoffPolicyProvider,
ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor,
@ -525,6 +527,13 @@ final class InternalSubchannel implements InternalInstrumented<ChannelStats>, Tr
return channelStatsFuture;
}
/**
* Return attributes for server address connected by sub channel.
*/
public Attributes getConnectedAddressAttributes() {
return connectedAddressAttributes;
}
ConnectivityState getState() {
return state.getState();
}
@ -568,6 +577,7 @@ final class InternalSubchannel implements InternalInstrumented<ChannelStats>, Tr
} else if (pendingTransport == transport) {
activeTransport = transport;
pendingTransport = null;
connectedAddressAttributes = addressIndex.getCurrentEagAttributes();
gotoNonErrorState(READY);
}
}

View File

@ -2044,6 +2044,11 @@ final class ManagedChannelImpl extends ManagedChannel implements
subchannel.updateAddresses(addrs);
}
@Override
public Attributes getConnectedAddressAttributes() {
return subchannel.getConnectedAddressAttributes();
}
private List<EquivalentAddressGroup> stripOverrideAuthorityAttributes(
List<EquivalentAddressGroup> eags) {
List<EquivalentAddressGroup> eagsWithoutOverrideAttr = new ArrayList<>();

View File

@ -1339,6 +1339,32 @@ public class InternalSubchannelTest {
assertThat(index.getCurrentAddress()).isSameInstanceAs(addr2);
}
@Test
public void connectedAddressAttributes_ready() {
SocketAddress addr = new SocketAddress() {};
Attributes attr = Attributes.newBuilder().set(Attributes.Key.create("some-key"), "1").build();
createInternalSubchannel(new EquivalentAddressGroup(Arrays.asList(addr), attr));
assertEquals(IDLE, internalSubchannel.getState());
assertNoCallbackInvoke();
assertNull(internalSubchannel.obtainActiveTransport());
assertNull(internalSubchannel.getConnectedAddressAttributes());
assertExactCallbackInvokes("onStateChange:CONNECTING");
assertEquals(CONNECTING, internalSubchannel.getState());
verify(mockTransportFactory).newClientTransport(
eq(addr),
eq(createClientTransportOptions().setEagAttributes(attr)),
isA(TransportLogger.class));
assertNull(internalSubchannel.getConnectedAddressAttributes());
internalSubchannel.obtainActiveTransport();
transports.peek().listener.transportReady();
assertExactCallbackInvokes("onStateChange:READY");
assertEquals(READY, internalSubchannel.getState());
assertEquals(attr, internalSubchannel.getConnectedAddressAttributes());
}
/** Create ClientTransportOptions. Should not be reused if it may be mutated. */
private ClientTransportFactory.ClientTransportOptions createClientTransportOptions() {
return new ClientTransportFactory.ClientTransportOptions()

View File

@ -74,11 +74,17 @@ public abstract class ForwardingSubchannel extends LoadBalancer.Subchannel {
return delegate().getInternalSubchannel();
}
@Override
public void updateAddresses(List<EquivalentAddressGroup> addrs) {
delegate().updateAddresses(addrs);
}
@Override
public Attributes getConnectedAddressAttributes() {
return delegate().getConnectedAddressAttributes();
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();

View File

@ -27,6 +27,7 @@ import io.grpc.Attributes;
import io.grpc.ClientStreamTracer;
import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
import io.grpc.EquivalentAddressGroup;
import io.grpc.InternalLogId;
import io.grpc.LoadBalancer;
@ -59,6 +60,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
/**
@ -77,10 +79,8 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_CIRCUIT_BREAKING"))
|| Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_CIRCUIT_BREAKING"));
private static final Attributes.Key<ClusterLocalityStats> ATTR_CLUSTER_LOCALITY_STATS =
Attributes.Key.create("io.grpc.xds.ClusterImplLoadBalancer.clusterLocalityStats");
private static final Attributes.Key<String> ATTR_CLUSTER_LOCALITY_NAME =
Attributes.Key.create("io.grpc.xds.ClusterImplLoadBalancer.clusterLocalityName");
private static final Attributes.Key<AtomicReference<ClusterLocality>> ATTR_CLUSTER_LOCALITY =
Attributes.Key.create("io.grpc.xds.ClusterImplLoadBalancer.clusterLocality");
private final XdsLogger logger;
private final Helper helper;
@ -213,36 +213,45 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
@Override
public Subchannel createSubchannel(CreateSubchannelArgs args) {
List<EquivalentAddressGroup> addresses = withAdditionalAttributes(args.getAddresses());
Locality locality = args.getAddresses().get(0).getAttributes().get(
InternalXdsAttributes.ATTR_LOCALITY); // all addresses should be in the same locality
String localityName = args.getAddresses().get(0).getAttributes().get(
InternalXdsAttributes.ATTR_LOCALITY_NAME);
// Endpoint addresses resolved by ClusterResolverLoadBalancer should always contain
// attributes with its locality, including endpoints in LOGICAL_DNS clusters.
// In case of not (which really shouldn't), loads are aggregated under an empty locality.
if (locality == null) {
locality = Locality.create("", "", "");
localityName = "";
}
final ClusterLocalityStats localityStats =
(lrsServerInfo == null)
? null
: xdsClient.addClusterLocalityStats(lrsServerInfo, cluster,
edsServiceName, locality);
// This value for ClusterLocality is not recommended for general use.
// Currently, we extract locality data from the first address, even before the subchannel is
// READY.
// This is mainly to accommodate scenarios where a Load Balancing API (like "pick first")
// might return the subchannel before it is READY. Typically, we wouldn't report load for such
// selections because the channel will disregard the chosen (not-ready) subchannel.
// However, we needed to ensure this case is handled.
ClusterLocality clusterLocality = createClusterLocalityFromAttributes(
args.getAddresses().get(0).getAttributes());
AtomicReference<ClusterLocality> localityAtomicReference = new AtomicReference<>(
clusterLocality);
Attributes attrs = args.getAttributes().toBuilder()
.set(ATTR_CLUSTER_LOCALITY_STATS, localityStats)
.set(ATTR_CLUSTER_LOCALITY_NAME, localityName)
.set(ATTR_CLUSTER_LOCALITY, localityAtomicReference)
.build();
args = args.toBuilder().setAddresses(addresses).setAttributes(attrs).build();
final Subchannel subchannel = delegate().createSubchannel(args);
return new ForwardingSubchannel() {
@Override
public void start(SubchannelStateListener listener) {
delegate().start(new SubchannelStateListener() {
@Override
public void onSubchannelState(ConnectivityStateInfo newState) {
if (newState.getState().equals(ConnectivityState.READY)) {
// Get locality based on the connected address attributes
ClusterLocality updatedClusterLocality = createClusterLocalityFromAttributes(
subchannel.getConnectedAddressAttributes());
ClusterLocality oldClusterLocality = localityAtomicReference
.getAndSet(updatedClusterLocality);
oldClusterLocality.release();
}
listener.onSubchannelState(newState);
}
});
}
@Override
public void shutdown() {
if (localityStats != null) {
localityStats.release();
}
localityAtomicReference.get().release();
delegate().shutdown();
}
@ -274,6 +283,28 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
return newAddresses;
}
private ClusterLocality createClusterLocalityFromAttributes(Attributes addressAttributes) {
Locality locality = addressAttributes.get(InternalXdsAttributes.ATTR_LOCALITY);
String localityName = addressAttributes.get(InternalXdsAttributes.ATTR_LOCALITY_NAME);
// Endpoint addresses resolved by ClusterResolverLoadBalancer should always contain
// attributes with its locality, including endpoints in LOGICAL_DNS clusters.
// In case of not (which really shouldn't), loads are aggregated under an empty
// locality.
if (locality == null) {
locality = Locality.create("", "", "");
localityName = "";
}
final ClusterLocalityStats localityStats =
(lrsServerInfo == null)
? null
: xdsClient.addClusterLocalityStats(lrsServerInfo, cluster,
edsServiceName, locality);
return new ClusterLocality(localityStats, localityName);
}
@Override
protected Helper delegate() {
return helper;
@ -361,18 +392,23 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
"Cluster max concurrent requests limit exceeded"));
}
}
final ClusterLocalityStats stats =
result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY_STATS);
if (stats != null) {
String localityName =
result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY_NAME);
args.getPickDetailsConsumer().addOptionalLabel("grpc.lb.locality", localityName);
final AtomicReference<ClusterLocality> clusterLocality =
result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY);
ClientStreamTracer.Factory tracerFactory = new CountingStreamTracerFactory(
stats, inFlights, result.getStreamTracerFactory());
ClientStreamTracer.Factory orcaTracerFactory = OrcaPerRequestUtil.getInstance()
.newOrcaClientStreamTracerFactory(tracerFactory, new OrcaPerRpcListener(stats));
return PickResult.withSubchannel(result.getSubchannel(), orcaTracerFactory);
if (clusterLocality != null) {
ClusterLocalityStats stats = clusterLocality.get().getClusterLocalityStats();
if (stats != null) {
String localityName =
result.getSubchannel().getAttributes().get(ATTR_CLUSTER_LOCALITY).get()
.getClusterLocalityName();
args.getPickDetailsConsumer().addOptionalLabel("grpc.lb.locality", localityName);
ClientStreamTracer.Factory tracerFactory = new CountingStreamTracerFactory(
stats, inFlights, result.getStreamTracerFactory());
ClientStreamTracer.Factory orcaTracerFactory = OrcaPerRequestUtil.getInstance()
.newOrcaClientStreamTracerFactory(tracerFactory, new OrcaPerRpcListener(stats));
return PickResult.withSubchannel(result.getSubchannel(), orcaTracerFactory);
}
}
}
return result;
@ -447,4 +483,33 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
stats.recordBackendLoadMetricStats(report.getNamedMetrics());
}
}
/**
* Represents the {@link ClusterLocalityStats} and network locality name of a cluster.
*/
static final class ClusterLocality {
private final ClusterLocalityStats clusterLocalityStats;
private final String clusterLocalityName;
@VisibleForTesting
ClusterLocality(ClusterLocalityStats localityStats, String localityName) {
this.clusterLocalityStats = localityStats;
this.clusterLocalityName = localityName;
}
ClusterLocalityStats getClusterLocalityStats() {
return clusterLocalityStats;
}
String getClusterLocalityName() {
return clusterLocalityName;
}
@VisibleForTesting
void release() {
if (clusterLocalityStats != null) {
clusterLocalityStats.release();
}
}
}
}

View File

@ -91,7 +91,7 @@ public final class LoadStatsManager2 {
String cluster, @Nullable String edsServiceName) {
checkState(allDropStats.containsKey(cluster)
&& allDropStats.get(cluster).containsKey(edsServiceName),
"stats for cluster %s, edsServiceName %s not exits", cluster, edsServiceName);
"stats for cluster %s, edsServiceName %s do not exist", cluster, edsServiceName);
ReferenceCounted<ClusterDropStats> ref = allDropStats.get(cluster).get(edsServiceName);
ref.release();
}

View File

@ -16,6 +16,7 @@
package io.grpc.xds;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
@ -29,6 +30,7 @@ import io.grpc.Attributes;
import io.grpc.CallOptions;
import io.grpc.ClientStreamTracer;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
import io.grpc.EquivalentAddressGroup;
import io.grpc.InsecureChannelCredentials;
import io.grpc.LoadBalancer;
@ -40,7 +42,9 @@ import io.grpc.LoadBalancer.PickSubchannelArgs;
import io.grpc.LoadBalancer.ResolvedAddresses;
import io.grpc.LoadBalancer.Subchannel;
import io.grpc.LoadBalancer.SubchannelPicker;
import io.grpc.LoadBalancer.SubchannelStateListener;
import io.grpc.LoadBalancerProvider;
import io.grpc.LoadBalancerRegistry;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.Status;
@ -76,9 +80,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -145,7 +151,7 @@ public class ClusterImplLoadBalancerTest {
return new AtomicLong();
}
};
private final Helper helper = new FakeLbHelper();
private final FakeLbHelper helper = new FakeLbHelper();
private PickSubchannelArgs pickSubchannelArgs = new PickSubchannelArgsImpl(
TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT,
new PickDetailsConsumer() {});
@ -272,9 +278,10 @@ public class ClusterImplLoadBalancerTest {
EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality);
deliverAddressesAndConfig(Collections.singletonList(endpoint), config);
FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers);
Subchannel subchannel = leafBalancer.helper.createSubchannel(
CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build());
leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY);
leafBalancer.createSubChannel();
FakeSubchannel fakeSubchannel = helper.subchannels.poll();
fakeSubchannel.setConnectedEagIndex(0);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY));
assertThat(currentState).isEqualTo(ConnectivityState.READY);
PickDetailsConsumer detailsConsumer = mock(PickDetailsConsumer.class);
@ -300,9 +307,10 @@ public class ClusterImplLoadBalancerTest {
EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality);
deliverAddressesAndConfig(Collections.singletonList(endpoint), config);
FakeLoadBalancer leafBalancer = Iterables.getOnlyElement(downstreamBalancers);
Subchannel subchannel = leafBalancer.helper.createSubchannel(
CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build());
leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY);
Subchannel subchannel = leafBalancer.createSubChannel();
FakeSubchannel fakeSubchannel = helper.subchannels.poll();
fakeSubchannel.setConnectedEagIndex(0);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY));
assertThat(currentState).isEqualTo(ConnectivityState.READY);
PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs);
assertThat(result.getStatus().isOk()).isTrue();
@ -357,7 +365,7 @@ public class ClusterImplLoadBalancerTest {
TOLERANCE).of(0.009);
streamTracer3.streamClosed(Status.OK);
subchannel.shutdown(); // stats recorder released
subchannel.shutdown(); // stats recorder released
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
// Locality load is reported for one last time in case of loads occurred since the previous
// load report.
@ -373,6 +381,95 @@ public class ClusterImplLoadBalancerTest {
assertThat(clusterStats.upstreamLocalityStatsList()).isEmpty(); // no longer reported
}
// TODO(dnvindhya): This test has been added as a fix to verify
// https://github.com/grpc/grpc-java/issues/11434.
// Once we update PickFirstLeafLoadBalancer as default LoadBalancer, update the test.
@Test
public void pickFirstLoadReport_onUpdateAddress() {
Locality locality1 =
Locality.create("test-region", "test-zone", "test-subzone");
Locality locality2 =
Locality.create("other-region", "other-zone", "other-subzone");
LoadBalancerProvider pickFirstProvider = LoadBalancerRegistry
.getDefaultRegistry().getProvider("pick_first");
Object pickFirstConfig = pickFirstProvider.parseLoadBalancingPolicyConfig(new HashMap<>())
.getConfig();
ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO,
null, Collections.<DropOverload>emptyList(),
GracefulSwitchLoadBalancer.createLoadBalancingPolicyConfig(pickFirstProvider,
pickFirstConfig),
null, Collections.emptyMap());
EquivalentAddressGroup endpoint1 = makeAddress("endpoint-addr1", locality1);
EquivalentAddressGroup endpoint2 = makeAddress("endpoint-addr2", locality2);
deliverAddressesAndConfig(Arrays.asList(endpoint1, endpoint2), config);
// Leaf balancer is created by Pick First. Get FakeSubchannel created to update attributes
// A real subchannel would get these attributes from the connected address's EAG locality.
FakeSubchannel fakeSubchannel = helper.subchannels.poll();
fakeSubchannel.setConnectedEagIndex(0);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY));
assertThat(currentState).isEqualTo(ConnectivityState.READY);
PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs);
assertThat(result.getStatus().isOk()).isTrue();
ClientStreamTracer streamTracer1 = result.getStreamTracerFactory().newClientStreamTracer(
ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // first RPC call
streamTracer1.streamClosed(Status.OK);
ClusterStats clusterStats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER));
UpstreamLocalityStats localityStats = Iterables.getOnlyElement(
clusterStats.upstreamLocalityStatsList());
assertThat(localityStats.locality()).isEqualTo(locality1);
assertThat(localityStats.totalIssuedRequests()).isEqualTo(1L);
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.IDLE));
loadBalancer.requestConnection();
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING));
// Faksubchannel mimics update address and returns different locality
fakeSubchannel.setConnectedEagIndex(1);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY));
result = currentPicker.pickSubchannel(pickSubchannelArgs);
assertThat(result.getStatus().isOk()).isTrue();
ClientStreamTracer streamTracer2 = result.getStreamTracerFactory().newClientStreamTracer(
ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // second RPC call
streamTracer2.streamClosed(Status.UNAVAILABLE);
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
UpstreamLocalityStats localityStats1 = Iterables.get(clusterStats.upstreamLocalityStatsList(),
0);
assertThat(localityStats1.locality()).isEqualTo(locality1);
assertThat(localityStats1.totalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats1.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats1.totalErrorRequests()).isEqualTo(0L);
UpstreamLocalityStats localityStats2 = Iterables.get(clusterStats.upstreamLocalityStatsList(),
1);
assertThat(localityStats2.locality()).isEqualTo(locality2);
assertThat(localityStats2.totalIssuedRequests()).isEqualTo(1L);
assertThat(localityStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats2.totalErrorRequests()).isEqualTo(1L);
loadBalancer.shutdown();
loadBalancer = null;
// No more references are held for localityStats1 hence dropped.
// Locality load is reported for one last time in case of loads occurred since the previous
// load report.
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
localityStats2 = Iterables.getOnlyElement(clusterStats.upstreamLocalityStatsList());
assertThat(localityStats2.locality()).isEqualTo(locality2);
assertThat(localityStats2.totalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats2.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats2.totalRequestsInProgress()).isEqualTo(0L);
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER)).isEmpty();
}
@Test
public void dropRpcsWithRespectToLbConfigDropCategories() {
LoadBalancerProvider weightedTargetProvider = new WeightedTargetLoadBalancerProvider();
@ -391,9 +488,11 @@ public class ClusterImplLoadBalancerTest {
assertThat(leafBalancer.name).isEqualTo("round_robin");
assertThat(Iterables.getOnlyElement(leafBalancer.addresses).getAddresses())
.isEqualTo(endpoint.getAddresses());
Subchannel subchannel = leafBalancer.helper.createSubchannel(
CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build());
leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY);
leafBalancer.createSubChannel();
FakeSubchannel fakeSubchannel = helper.subchannels.poll();
fakeSubchannel.setConnectedEagIndex(0);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY));
assertThat(currentState).isEqualTo(ConnectivityState.READY);
PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs);
assertThat(result.getStatus().isOk()).isFalse();
@ -470,9 +569,11 @@ public class ClusterImplLoadBalancerTest {
assertThat(leafBalancer.name).isEqualTo("round_robin");
assertThat(Iterables.getOnlyElement(leafBalancer.addresses).getAddresses())
.isEqualTo(endpoint.getAddresses());
Subchannel subchannel = leafBalancer.helper.createSubchannel(
CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build());
leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY);
leafBalancer.createSubChannel();
FakeSubchannel fakeSubchannel = helper.subchannels.poll();
fakeSubchannel.setConnectedEagIndex(0);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY));
assertThat(currentState).isEqualTo(ConnectivityState.READY);
assertThat(currentState).isEqualTo(ConnectivityState.READY);
for (int i = 0; i < maxConcurrentRequests; i++) {
PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs);
@ -562,9 +663,11 @@ public class ClusterImplLoadBalancerTest {
assertThat(leafBalancer.name).isEqualTo("round_robin");
assertThat(Iterables.getOnlyElement(leafBalancer.addresses).getAddresses())
.isEqualTo(endpoint.getAddresses());
Subchannel subchannel = leafBalancer.helper.createSubchannel(
CreateSubchannelArgs.newBuilder().setAddresses(leafBalancer.addresses).build());
leafBalancer.deliverSubchannelState(subchannel, ConnectivityState.READY);
leafBalancer.createSubChannel();
FakeSubchannel fakeSubchannel = helper.subchannels.poll();
fakeSubchannel.setConnectedEagIndex(0);
fakeSubchannel.updateState(ConnectivityStateInfo.forNonError(ConnectivityState.READY));
assertThat(currentState).isEqualTo(ConnectivityState.READY);
assertThat(currentState).isEqualTo(ConnectivityState.READY);
for (int i = 0; i < ClusterImplLoadBalancer.DEFAULT_PER_CLUSTER_MAX_CONCURRENT_REQUESTS; i++) {
PickResult result = currentPicker.pickSubchannel(pickSubchannelArgs);
@ -830,19 +933,24 @@ public class ClusterImplLoadBalancerTest {
downstreamBalancers.remove(this);
}
void deliverSubchannelState(final Subchannel subchannel, ConnectivityState state) {
SubchannelPicker picker = new SubchannelPicker() {
@Override
public PickResult pickSubchannel(PickSubchannelArgs args) {
return PickResult.withSubchannel(subchannel);
Subchannel createSubChannel() {
Subchannel subchannel = helper.createSubchannel(
CreateSubchannelArgs.newBuilder().setAddresses(addresses).build());
subchannel.start(infoObject -> {
if (infoObject.getState() == ConnectivityState.READY) {
helper.updateBalancingState(
ConnectivityState.READY,
new FixedResultPicker(PickResult.withSubchannel(subchannel)));
}
};
helper.updateBalancingState(state, picker);
});
return subchannel;
}
}
private final class FakeLbHelper extends LoadBalancer.Helper {
private final Queue<FakeSubchannel> subchannels = new LinkedList<>();
@Override
public SynchronizationContext getSynchronizationContext() {
return syncContext;
@ -857,7 +965,9 @@ public class ClusterImplLoadBalancerTest {
@Override
public Subchannel createSubchannel(CreateSubchannelArgs args) {
return new FakeSubchannel(args.getAddresses(), args.getAttributes());
FakeSubchannel subchannel = new FakeSubchannel(args.getAddresses(), args.getAttributes());
subchannels.add(subchannel);
return subchannel;
}
@Override
@ -869,17 +979,27 @@ public class ClusterImplLoadBalancerTest {
public String getAuthority() {
return AUTHORITY;
}
@Override
public void refreshNameResolution() {}
}
private static final class FakeSubchannel extends Subchannel {
private final List<EquivalentAddressGroup> eags;
private final Attributes attrs;
private SubchannelStateListener listener;
private Attributes connectedAttributes;
private FakeSubchannel(List<EquivalentAddressGroup> eags, Attributes attrs) {
this.eags = eags;
this.attrs = attrs;
}
@Override
public void start(SubchannelStateListener listener) {
this.listener = checkNotNull(listener, "listener");
}
@Override
public void shutdown() {
}
@ -901,6 +1021,19 @@ public class ClusterImplLoadBalancerTest {
@Override
public void updateAddresses(List<EquivalentAddressGroup> addrs) {
}
@Override
public Attributes getConnectedAddressAttributes() {
return connectedAttributes;
}
public void updateState(ConnectivityStateInfo newState) {
listener.onSubchannelState(newState);
}
public void setConnectedEagIndex(int eagIndex) {
this.connectedAttributes = eags.get(eagIndex).getAttributes();
}
}
private final class FakeXdsClient extends XdsClient {