mirror of https://github.com/grpc/grpc-java.git
xds: migrate to restructured xds loadbalancer
* Deleted: + XdsLoadBalancer + XdsLbState + XdsComms + Their test classes (except XdsCommsTest) * Moved/Renamed: + AdsStreamCallback -> LookasideChannelCallback + AdsStreamCallback2 -> AdsStreamCallback + XdsComms.LocalityInfo -> ClusterLoadAssignmentData.LocalityInfo + XdsComms.LbEndpoint -> ClusterLoadAssignmentData.LbEndpoint + XdsComms.DropOverload -> ClusterLoadAssignmentData.DropOverload + XdsLocality -> ClusterLoadAssignmentData.XdsLocality * XdsComms2 is not renamed because it's temporary. * XdsLoadBalancer2 is not renamed because otherwise the diff is hard to read.
This commit is contained in:
parent
90b3c88fe2
commit
dba7e9c36f
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.xds;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import io.envoyproxy.envoy.api.v2.core.SocketAddress;
|
||||||
|
import io.grpc.EquivalentAddressGroup;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains data types for ClusterLoadAssignment.
|
||||||
|
*/
|
||||||
|
final class ClusterLoadAssignmentData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@code XdsLocality} object is simply a POJO representation for {@link
|
||||||
|
* io.envoyproxy.envoy.api.v2.core.Locality}, with only details needed for {@link
|
||||||
|
* XdsLoadBalancer2}.
|
||||||
|
*/
|
||||||
|
static final class XdsLocality {
|
||||||
|
private final String region;
|
||||||
|
private final String zone;
|
||||||
|
private final String subzone;
|
||||||
|
|
||||||
|
/** Must only be used for testing. */
|
||||||
|
@VisibleForTesting
|
||||||
|
XdsLocality(String region, String zone, String subzone) {
|
||||||
|
this.region = region;
|
||||||
|
this.zone = zone;
|
||||||
|
this.subzone = subzone;
|
||||||
|
}
|
||||||
|
|
||||||
|
static XdsLocality fromLocalityProto(io.envoyproxy.envoy.api.v2.core.Locality locality) {
|
||||||
|
return new XdsLocality(
|
||||||
|
/* region = */ locality.getRegion(),
|
||||||
|
/* zone = */ locality.getZone(),
|
||||||
|
/* subzone = */ locality.getSubZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
io.envoyproxy.envoy.api.v2.core.Locality toLocalityProto() {
|
||||||
|
return io.envoyproxy.envoy.api.v2.core.Locality.newBuilder()
|
||||||
|
.setRegion(region)
|
||||||
|
.setZone(zone)
|
||||||
|
.setSubZone(subzone)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String getRegion() {
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getZone() {
|
||||||
|
return zone;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSubzone() {
|
||||||
|
return subzone;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
XdsLocality locality = (XdsLocality) o;
|
||||||
|
return Objects.equal(region, locality.region)
|
||||||
|
&& Objects.equal(zone, locality.zone)
|
||||||
|
&& Objects.equal(subzone, locality.subzone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(region, zone, subzone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("region", region)
|
||||||
|
.add("zone", zone)
|
||||||
|
.add("subzone", subzone)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about the locality from EDS response.
|
||||||
|
*/
|
||||||
|
static final class LocalityInfo {
|
||||||
|
final List<EquivalentAddressGroup> eags;
|
||||||
|
final List<Integer> endPointWeights;
|
||||||
|
final int localityWeight;
|
||||||
|
final int priority;
|
||||||
|
|
||||||
|
LocalityInfo(Collection<LbEndpoint> lbEndPoints, int localityWeight, int priority) {
|
||||||
|
List<EquivalentAddressGroup> eags = new ArrayList<>(lbEndPoints.size());
|
||||||
|
List<Integer> endPointWeights = new ArrayList<>(lbEndPoints.size());
|
||||||
|
for (LbEndpoint lbEndPoint : lbEndPoints) {
|
||||||
|
eags.add(lbEndPoint.eag);
|
||||||
|
endPointWeights.add(lbEndPoint.endPointWeight);
|
||||||
|
}
|
||||||
|
this.eags = Collections.unmodifiableList(eags);
|
||||||
|
this.endPointWeights = Collections.unmodifiableList(new ArrayList<>(endPointWeights));
|
||||||
|
this.localityWeight = localityWeight;
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LocalityInfo that = (LocalityInfo) o;
|
||||||
|
return localityWeight == that.localityWeight
|
||||||
|
&& priority == that.priority
|
||||||
|
&& Objects.equal(eags, that.eags)
|
||||||
|
&& Objects.equal(endPointWeights, that.endPointWeights);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(eags, endPointWeights, localityWeight, priority);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class LbEndpoint {
|
||||||
|
final EquivalentAddressGroup eag;
|
||||||
|
final int endPointWeight;
|
||||||
|
|
||||||
|
LbEndpoint(io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint lbEndpointProto) {
|
||||||
|
this(
|
||||||
|
new EquivalentAddressGroup(ImmutableList.of(fromEnvoyProtoAddress(lbEndpointProto))),
|
||||||
|
lbEndpointProto.getLoadBalancingWeight().getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
LbEndpoint(EquivalentAddressGroup eag, int endPointWeight) {
|
||||||
|
this.eag = eag;
|
||||||
|
this.endPointWeight = endPointWeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static java.net.SocketAddress fromEnvoyProtoAddress(
|
||||||
|
io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint lbEndpointProto) {
|
||||||
|
SocketAddress socketAddress = lbEndpointProto.getEndpoint().getAddress().getSocketAddress();
|
||||||
|
return new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class DropOverload {
|
||||||
|
final String category;
|
||||||
|
final int dropsPerMillion;
|
||||||
|
|
||||||
|
DropOverload(String category, int dropsPerMillion) {
|
||||||
|
this.category = category;
|
||||||
|
this.dropsPerMillion = dropsPerMillion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DropOverload that = (DropOverload) o;
|
||||||
|
return dropsPerMillion == that.dropsPerMillion && Objects.equal(category, that.category);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(category, dropsPerMillion);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("category", category)
|
||||||
|
.add("dropsPerMillion", dropsPerMillion)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,7 +37,7 @@ interface LoadReportClient {
|
||||||
* no-op.
|
* no-op.
|
||||||
*
|
*
|
||||||
* <p>This method is not thread-safe and should be called from the same synchronized context
|
* <p>This method is not thread-safe and should be called from the same synchronized context
|
||||||
* returned by {@link XdsLoadBalancer.Helper#getSynchronizationContext}.
|
* returned by {@link XdsLoadBalancer2.Helper#getSynchronizationContext}.
|
||||||
*
|
*
|
||||||
* @param callback containing methods to be invoked for passing information received from load
|
* @param callback containing methods to be invoked for passing information received from load
|
||||||
* reporting responses to xDS load balancer.
|
* reporting responses to xDS load balancer.
|
||||||
|
|
@ -49,7 +49,7 @@ interface LoadReportClient {
|
||||||
* {@link LoadReportClient} is no-op.
|
* {@link LoadReportClient} is no-op.
|
||||||
*
|
*
|
||||||
* <p>This method is not thread-safe and should be called from the same synchronized context
|
* <p>This method is not thread-safe and should be called from the same synchronized context
|
||||||
* returned by {@link XdsLoadBalancer.Helper#getSynchronizationContext}.
|
* returned by {@link XdsLoadBalancer2.Helper#getSynchronizationContext}.
|
||||||
*/
|
*/
|
||||||
void stopLoadReporting();
|
void stopLoadReporting();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ import javax.annotation.concurrent.NotThreadSafe;
|
||||||
* Client of xDS load reporting service.
|
* Client of xDS load reporting service.
|
||||||
*
|
*
|
||||||
* <p>Methods in this class are expected to be called in the same synchronized context that {@link
|
* <p>Methods in this class are expected to be called in the same synchronized context that {@link
|
||||||
* XdsLoadBalancer.Helper#getSynchronizationContext} returns.
|
* XdsLoadBalancer2.Helper#getSynchronizationContext} returns.
|
||||||
*/
|
*/
|
||||||
@NotThreadSafe
|
@NotThreadSafe
|
||||||
final class LoadReportClientImpl implements LoadReportClient {
|
final class LoadReportClientImpl implements LoadReportClient {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package io.grpc.xds;
|
package io.grpc.xds;
|
||||||
|
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats;
|
import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,7 +27,7 @@ import javax.annotation.Nullable;
|
||||||
* (i.e., Google backends) are aggregated in locality granularity (i.e., Google cluster) while the
|
* (i.e., Google backends) are aggregated in locality granularity (i.e., Google cluster) while the
|
||||||
* numbers of dropped calls are aggregated in cluster granularity.
|
* numbers of dropped calls are aggregated in cluster granularity.
|
||||||
*
|
*
|
||||||
* <p>An {@code LoadStatsStore} lives the same span of lifecycle as {@link XdsLoadBalancer} and
|
* <p>An {@code LoadStatsStore} lives the same span of lifecycle as {@link XdsLoadBalancer2} and
|
||||||
* only tracks loads for localities exposed by remote traffic director. A proper usage should be
|
* only tracks loads for localities exposed by remote traffic director. A proper usage should be
|
||||||
*
|
*
|
||||||
* <ol>
|
* <ol>
|
||||||
|
|
@ -60,7 +61,7 @@ interface LoadStatsStore {
|
||||||
* reporting.
|
* reporting.
|
||||||
*
|
*
|
||||||
* <p>This method is not thread-safe and should be called from the same synchronized context
|
* <p>This method is not thread-safe and should be called from the same synchronized context
|
||||||
* returned by {@link XdsLoadBalancer.Helper#getSynchronizationContext}.
|
* returned by {@link XdsLoadBalancer2.Helper#getSynchronizationContext}.
|
||||||
*/
|
*/
|
||||||
ClusterStats generateLoadReport();
|
ClusterStats generateLoadReport();
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@ interface LoadStatsStore {
|
||||||
* balancer discovery responses before recording loads for those localities.
|
* balancer discovery responses before recording loads for those localities.
|
||||||
*
|
*
|
||||||
* <p>This method is not thread-safe and should be called from the same synchronized context
|
* <p>This method is not thread-safe and should be called from the same synchronized context
|
||||||
* returned by {@link XdsLoadBalancer.Helper#getSynchronizationContext}.
|
* returned by {@link XdsLoadBalancer2.Helper#getSynchronizationContext}.
|
||||||
*/
|
*/
|
||||||
void addLocality(XdsLocality locality);
|
void addLocality(XdsLocality locality);
|
||||||
|
|
||||||
|
|
@ -87,7 +88,7 @@ interface LoadStatsStore {
|
||||||
* waste and keep including zero-load upstream locality stats in generated load reports.
|
* waste and keep including zero-load upstream locality stats in generated load reports.
|
||||||
*
|
*
|
||||||
* <p>This method is not thread-safe and should be called from the same synchronized context
|
* <p>This method is not thread-safe and should be called from the same synchronized context
|
||||||
* returned by {@link XdsLoadBalancer.Helper#getSynchronizationContext}.
|
* returned by {@link XdsLoadBalancer2.Helper#getSynchronizationContext}.
|
||||||
*/
|
*/
|
||||||
void removeLocality(XdsLocality locality);
|
void removeLocality(XdsLocality locality);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats;
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats;
|
import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats;
|
||||||
import io.grpc.xds.ClientLoadCounter.ClientLoadSnapshot;
|
import io.grpc.xds.ClientLoadCounter.ClientLoadSnapshot;
|
||||||
import io.grpc.xds.ClientLoadCounter.MetricValue;
|
import io.grpc.xds.ClientLoadCounter.MetricValue;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,12 @@ import io.grpc.util.ForwardingLoadBalancerHelper;
|
||||||
import io.grpc.xds.ClientLoadCounter.LoadRecordingSubchannelPicker;
|
import io.grpc.xds.ClientLoadCounter.LoadRecordingSubchannelPicker;
|
||||||
import io.grpc.xds.ClientLoadCounter.MetricsObservingSubchannelPicker;
|
import io.grpc.xds.ClientLoadCounter.MetricsObservingSubchannelPicker;
|
||||||
import io.grpc.xds.ClientLoadCounter.MetricsRecordingListener;
|
import io.grpc.xds.ClientLoadCounter.MetricsRecordingListener;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.DropOverload;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.LocalityInfo;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import io.grpc.xds.InterLocalityPicker.WeightedChildPicker;
|
import io.grpc.xds.InterLocalityPicker.WeightedChildPicker;
|
||||||
import io.grpc.xds.OrcaOobUtil.OrcaReportingConfig;
|
import io.grpc.xds.OrcaOobUtil.OrcaReportingConfig;
|
||||||
import io.grpc.xds.OrcaOobUtil.OrcaReportingHelperWrapper;
|
import io.grpc.xds.OrcaOobUtil.OrcaReportingHelperWrapper;
|
||||||
import io.grpc.xds.XdsComms.DropOverload;
|
|
||||||
import io.grpc.xds.XdsComms.LocalityInfo;
|
|
||||||
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
|
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment;
|
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment;
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload;
|
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints;
|
import io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints;
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent;
|
import io.envoyproxy.envoy.type.FractionalPercent;
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType;
|
import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType;
|
||||||
|
|
@ -31,10 +30,12 @@ import io.grpc.ManagedChannel;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.internal.ExponentialBackoffPolicy;
|
import io.grpc.internal.ExponentialBackoffPolicy;
|
||||||
import io.grpc.internal.GrpcUtil;
|
import io.grpc.internal.GrpcUtil;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.DropOverload;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.LbEndpoint;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.LocalityInfo;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import io.grpc.xds.LoadReportClient.LoadReportCallback;
|
import io.grpc.xds.LoadReportClient.LoadReportCallback;
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
import io.grpc.xds.XdsComms2.AdsStreamCallback;
|
||||||
import io.grpc.xds.XdsComms.LbEndpoint;
|
|
||||||
import io.grpc.xds.XdsComms.LocalityInfo;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -50,11 +51,11 @@ final class LookasideChannelLb extends LoadBalancer {
|
||||||
private final XdsComms2 xdsComms2;
|
private final XdsComms2 xdsComms2;
|
||||||
|
|
||||||
LookasideChannelLb(
|
LookasideChannelLb(
|
||||||
Helper helper, AdsStreamCallback adsCallback, ManagedChannel lbChannel,
|
Helper helper, LookasideChannelCallback lookasideChannelCallback, ManagedChannel lbChannel,
|
||||||
LocalityStore localityStore) {
|
LocalityStore localityStore) {
|
||||||
this(
|
this(
|
||||||
helper,
|
helper,
|
||||||
adsCallback,
|
lookasideChannelCallback,
|
||||||
lbChannel,
|
lbChannel,
|
||||||
new LoadReportClientImpl(
|
new LoadReportClientImpl(
|
||||||
lbChannel, helper, GrpcUtil.STOPWATCH_SUPPLIER, new ExponentialBackoffPolicy.Provider(),
|
lbChannel, helper, GrpcUtil.STOPWATCH_SUPPLIER, new ExponentialBackoffPolicy.Provider(),
|
||||||
|
|
@ -65,7 +66,7 @@ final class LookasideChannelLb extends LoadBalancer {
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
LookasideChannelLb(
|
LookasideChannelLb(
|
||||||
Helper helper,
|
Helper helper,
|
||||||
AdsStreamCallback adsCallback,
|
LookasideChannelCallback lookasideChannelCallback,
|
||||||
ManagedChannel lbChannel,
|
ManagedChannel lbChannel,
|
||||||
LoadReportClient lrsClient,
|
LoadReportClient lrsClient,
|
||||||
final LocalityStore localityStore) {
|
final LocalityStore localityStore) {
|
||||||
|
|
@ -79,10 +80,10 @@ final class LookasideChannelLb extends LoadBalancer {
|
||||||
};
|
};
|
||||||
this.lrsClient = lrsClient;
|
this.lrsClient = lrsClient;
|
||||||
|
|
||||||
AdsStreamCallback2 adsCallback2 = new AdsStreamCallback2Impl(
|
AdsStreamCallback adsCallback = new AdsStreamCallbackImpl(
|
||||||
adsCallback, lrsClient, lrsCallback, localityStore) ;
|
lookasideChannelCallback, lrsClient, lrsCallback, localityStore) ;
|
||||||
xdsComms2 = new XdsComms2(
|
xdsComms2 = new XdsComms2(
|
||||||
lbChannel, helper, adsCallback2, new ExponentialBackoffPolicy.Provider(),
|
lbChannel, helper, adsCallback, new ExponentialBackoffPolicy.Provider(),
|
||||||
GrpcUtil.STOPWATCH_SUPPLIER);
|
GrpcUtil.STOPWATCH_SUPPLIER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,30 +124,18 @@ final class LookasideChannelLb extends LoadBalancer {
|
||||||
lbChannel.shutdown();
|
lbChannel.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(zdapeng): The old AdsStreamCallback will be renamed to LookasideChannelCallback,
|
private static final class AdsStreamCallbackImpl implements AdsStreamCallback {
|
||||||
// and AdsStreamCallback2 will be renamed to AdsStreamCallback
|
|
||||||
/**
|
|
||||||
* Callback on ADS stream events. The callback methods should be called in a proper {@link
|
|
||||||
* io.grpc.SynchronizationContext}.
|
|
||||||
*/
|
|
||||||
interface AdsStreamCallback2 {
|
|
||||||
void onEdsResponse(ClusterLoadAssignment clusterLoadAssignment);
|
|
||||||
|
|
||||||
void onError();
|
final LookasideChannelCallback lookasideChannelCallback;
|
||||||
}
|
|
||||||
|
|
||||||
private static final class AdsStreamCallback2Impl implements AdsStreamCallback2 {
|
|
||||||
|
|
||||||
final AdsStreamCallback adsCallback;
|
|
||||||
final LoadReportClient lrsClient;
|
final LoadReportClient lrsClient;
|
||||||
final LoadReportCallback lrsCallback;
|
final LoadReportCallback lrsCallback;
|
||||||
final LocalityStore localityStore;
|
final LocalityStore localityStore;
|
||||||
boolean firstEdsResponseReceived;
|
boolean firstEdsResponseReceived;
|
||||||
|
|
||||||
AdsStreamCallback2Impl(
|
AdsStreamCallbackImpl(
|
||||||
AdsStreamCallback adsCallback, LoadReportClient lrsClient, LoadReportCallback lrsCallback,
|
LookasideChannelCallback lookasideChannelCallback, LoadReportClient lrsClient,
|
||||||
LocalityStore localityStore) {
|
LoadReportCallback lrsCallback, LocalityStore localityStore) {
|
||||||
this.adsCallback = adsCallback;
|
this.lookasideChannelCallback = lookasideChannelCallback;
|
||||||
this.lrsClient = lrsClient;
|
this.lrsClient = lrsClient;
|
||||||
this.lrsCallback = lrsCallback;
|
this.lrsCallback = lrsCallback;
|
||||||
this.localityStore = localityStore;
|
this.localityStore = localityStore;
|
||||||
|
|
@ -156,25 +145,22 @@ final class LookasideChannelLb extends LoadBalancer {
|
||||||
public void onEdsResponse(ClusterLoadAssignment clusterLoadAssignment) {
|
public void onEdsResponse(ClusterLoadAssignment clusterLoadAssignment) {
|
||||||
if (!firstEdsResponseReceived) {
|
if (!firstEdsResponseReceived) {
|
||||||
firstEdsResponseReceived = true;
|
firstEdsResponseReceived = true;
|
||||||
adsCallback.onWorking();
|
lookasideChannelCallback.onWorking();
|
||||||
lrsClient.startLoadReporting(lrsCallback);
|
lrsClient.startLoadReporting(lrsCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DropOverload> dropOverloadsProto =
|
List<ClusterLoadAssignment.Policy.DropOverload> dropOverloadsProto =
|
||||||
clusterLoadAssignment.getPolicy().getDropOverloadsList();
|
clusterLoadAssignment.getPolicy().getDropOverloadsList();
|
||||||
ImmutableList.Builder<XdsComms.DropOverload> dropOverloadsBuilder
|
ImmutableList.Builder<DropOverload> dropOverloadsBuilder = ImmutableList.builder();
|
||||||
= ImmutableList.builder();
|
for (ClusterLoadAssignment.Policy.DropOverload dropOverload : dropOverloadsProto) {
|
||||||
for (ClusterLoadAssignment.Policy.DropOverload dropOverload
|
|
||||||
: dropOverloadsProto) {
|
|
||||||
int rateInMillion = rateInMillion(dropOverload.getDropPercentage());
|
int rateInMillion = rateInMillion(dropOverload.getDropPercentage());
|
||||||
dropOverloadsBuilder.add(new XdsComms.DropOverload(
|
dropOverloadsBuilder.add(new DropOverload(dropOverload.getCategory(), rateInMillion));
|
||||||
dropOverload.getCategory(), rateInMillion));
|
|
||||||
if (rateInMillion == 1000_000) {
|
if (rateInMillion == 1000_000) {
|
||||||
adsCallback.onAllDrop();
|
lookasideChannelCallback.onAllDrop();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImmutableList<XdsComms.DropOverload> dropOverloads = dropOverloadsBuilder.build();
|
ImmutableList<DropOverload> dropOverloads = dropOverloadsBuilder.build();
|
||||||
localityStore.updateDropPercentage(dropOverloads);
|
localityStore.updateDropPercentage(dropOverloads);
|
||||||
|
|
||||||
List<LocalityLbEndpoints> localities = clusterLoadAssignment.getEndpointsList();
|
List<LocalityLbEndpoints> localities = clusterLoadAssignment.getEndpointsList();
|
||||||
|
|
@ -203,7 +189,30 @@ final class LookasideChannelLb extends LoadBalancer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError() {
|
public void onError() {
|
||||||
adsCallback.onError();
|
lookasideChannelCallback.onError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback on ADS stream events. The callback methods should be called in a proper {@link
|
||||||
|
* io.grpc.SynchronizationContext}.
|
||||||
|
*/
|
||||||
|
interface LookasideChannelCallback {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once the response observer receives the first response.
|
||||||
|
*/
|
||||||
|
void onWorking();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once an error occurs in ADS stream.
|
||||||
|
*/
|
||||||
|
void onError();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Once receives a response indicating that 100% of calls should be dropped.
|
||||||
|
*/
|
||||||
|
void onAllDrop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import io.grpc.NameResolver.ConfigOrError;
|
||||||
import io.grpc.util.ForwardingLoadBalancer;
|
import io.grpc.util.ForwardingLoadBalancer;
|
||||||
import io.grpc.util.GracefulSwitchLoadBalancer;
|
import io.grpc.util.GracefulSwitchLoadBalancer;
|
||||||
import io.grpc.xds.LocalityStore.LocalityStoreImpl;
|
import io.grpc.xds.LocalityStore.LocalityStoreImpl;
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
import io.grpc.xds.LookasideChannelLb.LookasideChannelCallback;
|
||||||
import io.grpc.xds.XdsLoadBalancerProvider.XdsConfig;
|
import io.grpc.xds.XdsLoadBalancerProvider.XdsConfig;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
@ -38,26 +38,26 @@ import java.util.logging.Logger;
|
||||||
/** Lookaside load balancer that handles balancer name changes. */
|
/** Lookaside load balancer that handles balancer name changes. */
|
||||||
final class LookasideLb extends ForwardingLoadBalancer {
|
final class LookasideLb extends ForwardingLoadBalancer {
|
||||||
|
|
||||||
private final AdsStreamCallback adsCallback;
|
private final LookasideChannelCallback lookasideChannelCallback;
|
||||||
private final LookasideChannelLbFactory lookasideChannelLbFactory;
|
private final LookasideChannelLbFactory lookasideChannelLbFactory;
|
||||||
private final GracefulSwitchLoadBalancer lookasideChannelLb;
|
private final GracefulSwitchLoadBalancer lookasideChannelLb;
|
||||||
private final LoadBalancerRegistry lbRegistry;
|
private final LoadBalancerRegistry lbRegistry;
|
||||||
|
|
||||||
private String balancerName;
|
private String balancerName;
|
||||||
|
|
||||||
LookasideLb(Helper lookasideLbHelper, AdsStreamCallback adsCallback) {
|
LookasideLb(Helper lookasideLbHelper, LookasideChannelCallback lookasideChannelCallback) {
|
||||||
this(
|
this(
|
||||||
lookasideLbHelper, adsCallback, new LookasideChannelLbFactoryImpl(),
|
lookasideLbHelper, lookasideChannelCallback, new LookasideChannelLbFactoryImpl(),
|
||||||
LoadBalancerRegistry.getDefaultRegistry());
|
LoadBalancerRegistry.getDefaultRegistry());
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
LookasideLb(
|
LookasideLb(
|
||||||
Helper lookasideLbHelper,
|
Helper lookasideLbHelper,
|
||||||
AdsStreamCallback adsCallback,
|
LookasideChannelCallback lookasideChannelCallback,
|
||||||
LookasideChannelLbFactory lookasideChannelLbFactory,
|
LookasideChannelLbFactory lookasideChannelLbFactory,
|
||||||
LoadBalancerRegistry lbRegistry) {
|
LoadBalancerRegistry lbRegistry) {
|
||||||
this.adsCallback = adsCallback;
|
this.lookasideChannelCallback = lookasideChannelCallback;
|
||||||
this.lookasideChannelLbFactory = lookasideChannelLbFactory;
|
this.lookasideChannelLbFactory = lookasideChannelLbFactory;
|
||||||
this.lbRegistry = lbRegistry;
|
this.lbRegistry = lbRegistry;
|
||||||
this.lookasideChannelLb = new GracefulSwitchLoadBalancer(lookasideLbHelper);
|
this.lookasideChannelLb = new GracefulSwitchLoadBalancer(lookasideLbHelper);
|
||||||
|
|
@ -113,23 +113,25 @@ final class LookasideLb extends ForwardingLoadBalancer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
public LoadBalancer newLoadBalancer(Helper helper) {
|
||||||
return lookasideChannelLbFactory.newLoadBalancer(helper, adsCallback, balancerName);
|
return lookasideChannelLbFactory.newLoadBalancer(
|
||||||
|
helper, lookasideChannelCallback, balancerName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
interface LookasideChannelLbFactory {
|
interface LookasideChannelLbFactory {
|
||||||
LoadBalancer newLoadBalancer(Helper helper, AdsStreamCallback adsCallback, String balancerName);
|
LoadBalancer newLoadBalancer(
|
||||||
|
Helper helper, LookasideChannelCallback lookasideChannelCallback, String balancerName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class LookasideChannelLbFactoryImpl implements LookasideChannelLbFactory {
|
private static final class LookasideChannelLbFactoryImpl implements LookasideChannelLbFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoadBalancer newLoadBalancer(Helper helper, AdsStreamCallback adsCallback,
|
public LoadBalancer newLoadBalancer(
|
||||||
String balancerName) {
|
Helper helper, LookasideChannelCallback lookasideChannelCallback, String balancerName) {
|
||||||
return new LookasideChannelLb(
|
return new LookasideChannelLb(
|
||||||
helper, adsCallback, initLbChannel(helper, balancerName),
|
helper, lookasideChannelCallback, initLbChannel(helper, balancerName),
|
||||||
new LocalityStoreImpl(helper, LoadBalancerRegistry.getDefaultRegistry()));
|
new LocalityStoreImpl(helper, LoadBalancerRegistry.getDefaultRegistry()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,461 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.base.Stopwatch;
|
|
||||||
import com.google.common.base.Supplier;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
|
||||||
import com.google.protobuf.Struct;
|
|
||||||
import com.google.protobuf.Value;
|
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryResponse;
|
|
||||||
import io.envoyproxy.envoy.api.v2.core.Node;
|
|
||||||
import io.envoyproxy.envoy.api.v2.core.SocketAddress;
|
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints;
|
|
||||||
import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc;
|
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent;
|
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType;
|
|
||||||
import io.grpc.ChannelLogger.ChannelLogLevel;
|
|
||||||
import io.grpc.EquivalentAddressGroup;
|
|
||||||
import io.grpc.LoadBalancer.Helper;
|
|
||||||
import io.grpc.ManagedChannel;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.SynchronizationContext.ScheduledHandle;
|
|
||||||
import io.grpc.internal.BackoffPolicy;
|
|
||||||
import io.grpc.stub.StreamObserver;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import javax.annotation.CheckForNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ADS client implementation.
|
|
||||||
*/
|
|
||||||
final class XdsComms {
|
|
||||||
private final ManagedChannel channel;
|
|
||||||
private final Helper helper;
|
|
||||||
private final BackoffPolicy.Provider backoffPolicyProvider;
|
|
||||||
private final Supplier<Stopwatch> stopwatchSupplier;
|
|
||||||
|
|
||||||
@CheckForNull
|
|
||||||
private ScheduledHandle adsRpcRetryTimer;
|
|
||||||
|
|
||||||
// never null
|
|
||||||
private BackoffPolicy adsRpcRetryPolicy;
|
|
||||||
// never null
|
|
||||||
private AdsStream adsStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about the locality from EDS response.
|
|
||||||
*/
|
|
||||||
// TODO(zdapeng): move this class out.
|
|
||||||
static final class LocalityInfo {
|
|
||||||
final List<EquivalentAddressGroup> eags;
|
|
||||||
final List<Integer> endPointWeights;
|
|
||||||
final int localityWeight;
|
|
||||||
final int priority;
|
|
||||||
|
|
||||||
LocalityInfo(Collection<LbEndpoint> lbEndPoints, int localityWeight, int priority) {
|
|
||||||
List<EquivalentAddressGroup> eags = new ArrayList<>(lbEndPoints.size());
|
|
||||||
List<Integer> endPointWeights = new ArrayList<>(lbEndPoints.size());
|
|
||||||
for (LbEndpoint lbEndPoint : lbEndPoints) {
|
|
||||||
eags.add(lbEndPoint.eag);
|
|
||||||
endPointWeights.add(lbEndPoint.endPointWeight);
|
|
||||||
}
|
|
||||||
this.eags = Collections.unmodifiableList(eags);
|
|
||||||
this.endPointWeights = Collections.unmodifiableList(new ArrayList<>(endPointWeights));
|
|
||||||
this.localityWeight = localityWeight;
|
|
||||||
this.priority = priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LocalityInfo that = (LocalityInfo) o;
|
|
||||||
return localityWeight == that.localityWeight
|
|
||||||
&& priority == that.priority
|
|
||||||
&& Objects.equal(eags, that.eags)
|
|
||||||
&& Objects.equal(endPointWeights, that.endPointWeights);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(eags, endPointWeights, localityWeight, priority);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class LbEndpoint {
|
|
||||||
final EquivalentAddressGroup eag;
|
|
||||||
final int endPointWeight;
|
|
||||||
|
|
||||||
LbEndpoint(io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint lbEndpointProto) {
|
|
||||||
|
|
||||||
this(
|
|
||||||
new EquivalentAddressGroup(ImmutableList.of(fromEnvoyProtoAddress(lbEndpointProto))),
|
|
||||||
lbEndpointProto.getLoadBalancingWeight().getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
LbEndpoint(EquivalentAddressGroup eag, int endPointWeight) {
|
|
||||||
this.eag = eag;
|
|
||||||
this.endPointWeight = endPointWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static java.net.SocketAddress fromEnvoyProtoAddress(
|
|
||||||
io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint lbEndpointProto) {
|
|
||||||
SocketAddress socketAddress = lbEndpointProto.getEndpoint().getAddress().getSocketAddress();
|
|
||||||
return new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final class DropOverload {
|
|
||||||
final String category;
|
|
||||||
final int dropsPerMillion;
|
|
||||||
|
|
||||||
DropOverload(String category, int dropsPerMillion) {
|
|
||||||
this.category = category;
|
|
||||||
this.dropsPerMillion = dropsPerMillion;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
DropOverload that = (DropOverload) o;
|
|
||||||
return dropsPerMillion == that.dropsPerMillion && Objects.equal(category, that.category);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(category, dropsPerMillion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return MoreObjects.toStringHelper(this)
|
|
||||||
.add("category", category)
|
|
||||||
.add("dropsPerMillion", dropsPerMillion)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class AdsStream {
|
|
||||||
static final String EDS_TYPE_URL =
|
|
||||||
"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment";
|
|
||||||
static final String TRAFFICDIRECTOR_GRPC_HOSTNAME = "TRAFFICDIRECTOR_GRPC_HOSTNAME";
|
|
||||||
final LocalityStore localityStore;
|
|
||||||
|
|
||||||
final AdsStreamCallback adsStreamCallback;
|
|
||||||
|
|
||||||
final StreamObserver<DiscoveryRequest> xdsRequestWriter;
|
|
||||||
|
|
||||||
final Stopwatch retryStopwatch = stopwatchSupplier.get().start();
|
|
||||||
|
|
||||||
final StreamObserver<DiscoveryResponse> xdsResponseReader =
|
|
||||||
new StreamObserver<DiscoveryResponse>() {
|
|
||||||
|
|
||||||
// Must be accessed in SynchronizationContext
|
|
||||||
boolean firstEdsResponseReceived;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(final DiscoveryResponse value) {
|
|
||||||
|
|
||||||
class HandleResponseRunnable implements Runnable {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
String typeUrl = value.getTypeUrl();
|
|
||||||
if (EDS_TYPE_URL.equals(typeUrl)) {
|
|
||||||
// Assuming standard mode.
|
|
||||||
|
|
||||||
ClusterLoadAssignment clusterLoadAssignment;
|
|
||||||
try {
|
|
||||||
// maybe better to run this deserialization task out of syncContext?
|
|
||||||
clusterLoadAssignment =
|
|
||||||
value.getResources(0).unpack(ClusterLoadAssignment.class);
|
|
||||||
} catch (InvalidProtocolBufferException | RuntimeException e) {
|
|
||||||
cancelRpc("Received invalid EDS response", e);
|
|
||||||
adsStreamCallback.onError();
|
|
||||||
scheduleRetry();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.getChannelLogger().log(
|
|
||||||
ChannelLogLevel.DEBUG,
|
|
||||||
"Received an EDS response: {0}", clusterLoadAssignment);
|
|
||||||
if (!firstEdsResponseReceived) {
|
|
||||||
firstEdsResponseReceived = true;
|
|
||||||
adsStreamCallback.onWorking();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ClusterLoadAssignment.Policy.DropOverload> dropOverloadsProto =
|
|
||||||
clusterLoadAssignment.getPolicy().getDropOverloadsList();
|
|
||||||
ImmutableList.Builder<DropOverload> dropOverloadsBuilder
|
|
||||||
= ImmutableList.builder();
|
|
||||||
for (ClusterLoadAssignment.Policy.DropOverload dropOverload
|
|
||||||
: dropOverloadsProto) {
|
|
||||||
int rateInMillion = rateInMillion(dropOverload.getDropPercentage());
|
|
||||||
dropOverloadsBuilder.add(new DropOverload(
|
|
||||||
dropOverload.getCategory(), rateInMillion));
|
|
||||||
if (rateInMillion == 1000_000) {
|
|
||||||
adsStreamCallback.onAllDrop();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImmutableList<DropOverload> dropOverloads = dropOverloadsBuilder.build();
|
|
||||||
localityStore.updateDropPercentage(dropOverloads);
|
|
||||||
|
|
||||||
List<LocalityLbEndpoints> localities = clusterLoadAssignment.getEndpointsList();
|
|
||||||
ImmutableMap.Builder<XdsLocality, LocalityInfo> localityEndpointsMapping =
|
|
||||||
new ImmutableMap.Builder<>();
|
|
||||||
for (LocalityLbEndpoints localityLbEndpoints : localities) {
|
|
||||||
io.envoyproxy.envoy.api.v2.core.Locality localityProto =
|
|
||||||
localityLbEndpoints.getLocality();
|
|
||||||
XdsLocality locality = XdsLocality.fromLocalityProto(localityProto);
|
|
||||||
List<LbEndpoint> lbEndPoints = new ArrayList<>();
|
|
||||||
for (io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint lbEndpoint
|
|
||||||
: localityLbEndpoints.getLbEndpointsList()) {
|
|
||||||
lbEndPoints.add(new LbEndpoint(lbEndpoint));
|
|
||||||
}
|
|
||||||
int localityWeight = localityLbEndpoints.getLoadBalancingWeight().getValue();
|
|
||||||
int priority = localityLbEndpoints.getPriority();
|
|
||||||
|
|
||||||
if (localityWeight != 0) {
|
|
||||||
localityEndpointsMapping.put(
|
|
||||||
locality, new LocalityInfo(lbEndPoints, localityWeight, priority));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
localityStore.updateLocalityStore(localityEndpointsMapping.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
helper.getSynchronizationContext().execute(new HandleResponseRunnable());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable t) {
|
|
||||||
helper.getSynchronizationContext().execute(
|
|
||||||
new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
closed = true;
|
|
||||||
if (cancelled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
adsStreamCallback.onError();
|
|
||||||
scheduleRetry();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
onError(Status.INTERNAL.withDescription("Server closed the ADS streaming RPC")
|
|
||||||
.asException());
|
|
||||||
}
|
|
||||||
|
|
||||||
// run in SynchronizationContext
|
|
||||||
void scheduleRetry() {
|
|
||||||
if (channel.isShutdown()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkState(
|
|
||||||
cancelled || closed,
|
|
||||||
"Scheduling retry while the stream is neither cancelled nor closed");
|
|
||||||
|
|
||||||
checkState(
|
|
||||||
adsRpcRetryTimer == null, "Scheduling retry while a retry is already pending");
|
|
||||||
|
|
||||||
class AdsRpcRetryTask implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
adsRpcRetryTimer = null;
|
|
||||||
refreshAdsStream();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstEdsResponseReceived) {
|
|
||||||
// Reset the backoff sequence if balancer has sent the initial response
|
|
||||||
adsRpcRetryPolicy = backoffPolicyProvider.get();
|
|
||||||
// Retry immediately
|
|
||||||
helper.getSynchronizationContext().execute(new AdsRpcRetryTask());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
adsRpcRetryTimer = helper.getSynchronizationContext().schedule(
|
|
||||||
new AdsRpcRetryTask(),
|
|
||||||
adsRpcRetryPolicy.nextBackoffNanos() - retryStopwatch.elapsed(TimeUnit.NANOSECONDS),
|
|
||||||
TimeUnit.NANOSECONDS,
|
|
||||||
helper.getScheduledExecutorService());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
boolean cancelled;
|
|
||||||
boolean closed;
|
|
||||||
|
|
||||||
AdsStream(AdsStreamCallback adsStreamCallback, LocalityStore localityStore) {
|
|
||||||
this.adsStreamCallback = adsStreamCallback;
|
|
||||||
this.xdsRequestWriter = AggregatedDiscoveryServiceGrpc.newStub(channel).withWaitForReady()
|
|
||||||
.streamAggregatedResources(xdsResponseReader);
|
|
||||||
this.localityStore = localityStore;
|
|
||||||
|
|
||||||
checkState(adsRpcRetryTimer == null, "Creating AdsStream while retry is pending");
|
|
||||||
// Assuming standard mode, and send EDS request only
|
|
||||||
DiscoveryRequest edsRequest =
|
|
||||||
DiscoveryRequest.newBuilder()
|
|
||||||
.setNode(Node.newBuilder()
|
|
||||||
.setMetadata(Struct.newBuilder()
|
|
||||||
.putFields(
|
|
||||||
"endpoints_required",
|
|
||||||
Value.newBuilder().setBoolValue(true).build())))
|
|
||||||
.setTypeUrl(EDS_TYPE_URL)
|
|
||||||
// In the future, the right resource name can be obtained from CDS response.
|
|
||||||
.addResourceNames(helper.getAuthority()).build();
|
|
||||||
helper.getChannelLogger().log(ChannelLogLevel.DEBUG, "Sending EDS request {0}", edsRequest);
|
|
||||||
xdsRequestWriter.onNext(edsRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
AdsStream(AdsStream adsStream) {
|
|
||||||
this(adsStream.adsStreamCallback, adsStream.localityStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
// run in SynchronizationContext
|
|
||||||
void cancelRpc(String message, Throwable cause) {
|
|
||||||
if (cancelled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cancelled = true;
|
|
||||||
xdsRequestWriter.onError(
|
|
||||||
Status.CANCELLED.withDescription(message).withCause(cause).asRuntimeException());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int rateInMillion(FractionalPercent fractionalPercent) {
|
|
||||||
int numerator = fractionalPercent.getNumerator();
|
|
||||||
checkArgument(numerator >= 0, "numerator shouldn't be negative in %s", fractionalPercent);
|
|
||||||
|
|
||||||
DenominatorType type = fractionalPercent.getDenominator();
|
|
||||||
switch (type) {
|
|
||||||
case TEN_THOUSAND:
|
|
||||||
numerator *= 100;
|
|
||||||
break;
|
|
||||||
case HUNDRED:
|
|
||||||
numerator *= 100_00;
|
|
||||||
break;
|
|
||||||
case MILLION:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("unknown denominator type of " + fractionalPercent);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numerator > 1000_000) {
|
|
||||||
numerator = 1000_000;
|
|
||||||
}
|
|
||||||
|
|
||||||
return numerator;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts a new ADS streaming RPC.
|
|
||||||
*/
|
|
||||||
XdsComms(
|
|
||||||
ManagedChannel channel, Helper helper, AdsStreamCallback adsStreamCallback,
|
|
||||||
LocalityStore localityStore, BackoffPolicy.Provider backoffPolicyProvider,
|
|
||||||
Supplier<Stopwatch> stopwatchSupplier) {
|
|
||||||
this.channel = checkNotNull(channel, "channel");
|
|
||||||
this.helper = checkNotNull(helper, "helper");
|
|
||||||
this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
|
|
||||||
this.adsStream = new AdsStream(
|
|
||||||
checkNotNull(adsStreamCallback, "adsStreamCallback"),
|
|
||||||
checkNotNull(localityStore, "localityStore"));
|
|
||||||
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
|
||||||
this.adsRpcRetryPolicy = backoffPolicyProvider.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// run in SynchronizationContext
|
|
||||||
void refreshAdsStream() {
|
|
||||||
checkState(!channel.isShutdown(), "channel is alreday shutdown");
|
|
||||||
|
|
||||||
if (adsStream.closed || adsStream.cancelled) {
|
|
||||||
cancelRetryTimer();
|
|
||||||
adsStream = new AdsStream(adsStream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run in SynchronizationContext
|
|
||||||
// TODO: Change method name to shutdown or shutdownXdsComms if that gives better semantics (
|
|
||||||
// cancel LB RPC and clean up retry timer).
|
|
||||||
void shutdownLbRpc(String message) {
|
|
||||||
adsStream.cancelRpc(message, null);
|
|
||||||
cancelRetryTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
// run in SynchronizationContext
|
|
||||||
private void cancelRetryTimer() {
|
|
||||||
if (adsRpcRetryTimer != null) {
|
|
||||||
adsRpcRetryTimer.cancel();
|
|
||||||
adsRpcRetryTimer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback on ADS stream events. The callback methods should be called in a proper {@link
|
|
||||||
* io.grpc.SynchronizationContext}.
|
|
||||||
*/
|
|
||||||
interface AdsStreamCallback {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Once the response observer receives the first response.
|
|
||||||
*/
|
|
||||||
void onWorking();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Once an error occurs in ADS stream.
|
|
||||||
*/
|
|
||||||
void onError();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Once receives a response indicating that 100% of calls should be dropped.
|
|
||||||
*/
|
|
||||||
void onAllDrop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -36,7 +36,6 @@ import io.grpc.Status;
|
||||||
import io.grpc.SynchronizationContext.ScheduledHandle;
|
import io.grpc.SynchronizationContext.ScheduledHandle;
|
||||||
import io.grpc.internal.BackoffPolicy;
|
import io.grpc.internal.BackoffPolicy;
|
||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import io.grpc.xds.LookasideChannelLb.AdsStreamCallback2;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.annotation.CheckForNull;
|
import javax.annotation.CheckForNull;
|
||||||
|
|
||||||
|
|
@ -63,7 +62,7 @@ final class XdsComms2 {
|
||||||
static final String EDS_TYPE_URL =
|
static final String EDS_TYPE_URL =
|
||||||
"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment";
|
"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment";
|
||||||
|
|
||||||
final AdsStreamCallback2 adsStreamCallback2;
|
final AdsStreamCallback adsStreamCallback;
|
||||||
final StreamObserver<DiscoveryRequest> xdsRequestWriter;
|
final StreamObserver<DiscoveryRequest> xdsRequestWriter;
|
||||||
final Stopwatch retryStopwatch = stopwatchSupplier.get().start();
|
final Stopwatch retryStopwatch = stopwatchSupplier.get().start();
|
||||||
|
|
||||||
|
|
@ -90,7 +89,7 @@ final class XdsComms2 {
|
||||||
value.getResources(0).unpack(ClusterLoadAssignment.class);
|
value.getResources(0).unpack(ClusterLoadAssignment.class);
|
||||||
} catch (InvalidProtocolBufferException | RuntimeException e) {
|
} catch (InvalidProtocolBufferException | RuntimeException e) {
|
||||||
cancelRpc("Received invalid EDS response", e);
|
cancelRpc("Received invalid EDS response", e);
|
||||||
adsStreamCallback2.onError();
|
adsStreamCallback.onError();
|
||||||
scheduleRetry();
|
scheduleRetry();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -98,7 +97,8 @@ final class XdsComms2 {
|
||||||
helper.getChannelLogger().log(
|
helper.getChannelLogger().log(
|
||||||
ChannelLogLevel.DEBUG,
|
ChannelLogLevel.DEBUG,
|
||||||
"Received an EDS response: {0}", clusterLoadAssignment);
|
"Received an EDS response: {0}", clusterLoadAssignment);
|
||||||
adsStreamCallback2.onEdsResponse(clusterLoadAssignment);
|
firstEdsResponseReceived = true;
|
||||||
|
adsStreamCallback.onEdsResponse(clusterLoadAssignment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,7 +116,7 @@ final class XdsComms2 {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
adsStreamCallback2.onError();
|
adsStreamCallback.onError();
|
||||||
scheduleRetry();
|
scheduleRetry();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -168,8 +168,8 @@ final class XdsComms2 {
|
||||||
boolean cancelled;
|
boolean cancelled;
|
||||||
boolean closed;
|
boolean closed;
|
||||||
|
|
||||||
AdsStream(AdsStreamCallback2 adsStreamCallback2) {
|
AdsStream(AdsStreamCallback adsStreamCallback) {
|
||||||
this.adsStreamCallback2 = adsStreamCallback2;
|
this.adsStreamCallback = adsStreamCallback;
|
||||||
this.xdsRequestWriter = AggregatedDiscoveryServiceGrpc.newStub(channel).withWaitForReady()
|
this.xdsRequestWriter = AggregatedDiscoveryServiceGrpc.newStub(channel).withWaitForReady()
|
||||||
.streamAggregatedResources(xdsResponseReader);
|
.streamAggregatedResources(xdsResponseReader);
|
||||||
|
|
||||||
|
|
@ -190,7 +190,7 @@ final class XdsComms2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
AdsStream(AdsStream adsStream) {
|
AdsStream(AdsStream adsStream) {
|
||||||
this(adsStream.adsStreamCallback2);
|
this(adsStream.adsStreamCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// run in SynchronizationContext
|
// run in SynchronizationContext
|
||||||
|
|
@ -208,13 +208,13 @@ final class XdsComms2 {
|
||||||
* Starts a new ADS streaming RPC.
|
* Starts a new ADS streaming RPC.
|
||||||
*/
|
*/
|
||||||
XdsComms2(
|
XdsComms2(
|
||||||
ManagedChannel channel, Helper helper, AdsStreamCallback2 adsStreamCallback2,
|
ManagedChannel channel, Helper helper, AdsStreamCallback adsStreamCallback,
|
||||||
BackoffPolicy.Provider backoffPolicyProvider, Supplier<Stopwatch> stopwatchSupplier) {
|
BackoffPolicy.Provider backoffPolicyProvider, Supplier<Stopwatch> stopwatchSupplier) {
|
||||||
this.channel = checkNotNull(channel, "channel");
|
this.channel = checkNotNull(channel, "channel");
|
||||||
this.helper = checkNotNull(helper, "helper");
|
this.helper = checkNotNull(helper, "helper");
|
||||||
this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
|
this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
|
||||||
this.adsStream = new AdsStream(
|
this.adsStream = new AdsStream(
|
||||||
checkNotNull(adsStreamCallback2, "adsStreamCallback2"));
|
checkNotNull(adsStreamCallback, "adsStreamCallback"));
|
||||||
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
||||||
this.adsRpcRetryPolicy = backoffPolicyProvider.get();
|
this.adsRpcRetryPolicy = backoffPolicyProvider.get();
|
||||||
}
|
}
|
||||||
|
|
@ -244,4 +244,14 @@ final class XdsComms2 {
|
||||||
adsRpcRetryTimer = null;
|
adsRpcRetryTimer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback on ADS stream events. The callback methods should be called in a proper {@link
|
||||||
|
* io.grpc.SynchronizationContext}.
|
||||||
|
*/
|
||||||
|
interface AdsStreamCallback {
|
||||||
|
void onEdsResponse(ClusterLoadAssignment clusterLoadAssignment);
|
||||||
|
|
||||||
|
void onError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import io.grpc.Attributes;
|
|
||||||
import io.grpc.ConnectivityStateInfo;
|
|
||||||
import io.grpc.EquivalentAddressGroup;
|
|
||||||
import io.grpc.LoadBalancer.Helper;
|
|
||||||
import io.grpc.LoadBalancer.Subchannel;
|
|
||||||
import io.grpc.ManagedChannel;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.internal.BackoffPolicy;
|
|
||||||
import io.grpc.internal.GrpcUtil;
|
|
||||||
import io.grpc.internal.ServiceConfigUtil.LbConfig;
|
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The states of an XDS working session of {@link XdsLoadBalancer}. Created when XdsLoadBalancer
|
|
||||||
* switches to the current mode. Shutdown and discarded when XdsLoadBalancer switches to another
|
|
||||||
* mode.
|
|
||||||
*
|
|
||||||
* <p>There might be two implementations:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>Standard plugin: No child plugin specified in lb config. Lb will send CDS request,
|
|
||||||
* and then EDS requests. EDS requests request for endpoints.</li>
|
|
||||||
* <li>Custom plugin: Child plugin specified in lb config. Lb will send EDS directly. EDS requests
|
|
||||||
* do not request for endpoints.</li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
class XdsLbState {
|
|
||||||
|
|
||||||
final String balancerName;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
final LbConfig childPolicy;
|
|
||||||
|
|
||||||
private final LocalityStore localityStore;
|
|
||||||
private final Helper helper;
|
|
||||||
private final ManagedChannel channel;
|
|
||||||
private final AdsStreamCallback adsStreamCallback;
|
|
||||||
private final BackoffPolicy.Provider backoffPolicyProvider;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private XdsComms xdsComms;
|
|
||||||
|
|
||||||
XdsLbState(
|
|
||||||
String balancerName,
|
|
||||||
@Nullable LbConfig childPolicy,
|
|
||||||
Helper helper,
|
|
||||||
LocalityStore localityStore,
|
|
||||||
ManagedChannel channel,
|
|
||||||
AdsStreamCallback adsStreamCallback,
|
|
||||||
BackoffPolicy.Provider backoffPolicyProvider) {
|
|
||||||
this.balancerName = checkNotNull(balancerName, "balancerName");
|
|
||||||
this.childPolicy = childPolicy;
|
|
||||||
this.helper = checkNotNull(helper, "helper");
|
|
||||||
this.localityStore = checkNotNull(localityStore, "localityStore");
|
|
||||||
this.channel = checkNotNull(channel, "channel");
|
|
||||||
this.adsStreamCallback = checkNotNull(adsStreamCallback, "adsStreamCallback");
|
|
||||||
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
|
||||||
}
|
|
||||||
|
|
||||||
final void handleResolvedAddressGroups(
|
|
||||||
List<EquivalentAddressGroup> servers, Attributes attributes) {
|
|
||||||
|
|
||||||
// start XdsComms if not already alive
|
|
||||||
if (xdsComms != null) {
|
|
||||||
xdsComms.refreshAdsStream();
|
|
||||||
} else {
|
|
||||||
// TODO(zdapeng): pass a helper that has the right ChannelLogger for the oobChannel
|
|
||||||
xdsComms = new XdsComms(
|
|
||||||
channel, helper, adsStreamCallback, localityStore, backoffPolicyProvider,
|
|
||||||
GrpcUtil.STOPWATCH_SUPPLIER);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: maybe update picker
|
|
||||||
}
|
|
||||||
|
|
||||||
final void handleNameResolutionError(Status error) {
|
|
||||||
// NO-OP?
|
|
||||||
}
|
|
||||||
|
|
||||||
final void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo newState) {
|
|
||||||
// TODO: maybe update picker
|
|
||||||
localityStore.handleSubchannelState(subchannel, newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
ManagedChannel shutdownAndReleaseChannel(String message) {
|
|
||||||
localityStore.reset();
|
|
||||||
if (xdsComms != null) {
|
|
||||||
xdsComms.shutdownLbRpc(message);
|
|
||||||
xdsComms = null;
|
|
||||||
}
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,479 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static io.grpc.ConnectivityState.READY;
|
|
||||||
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
|
|
||||||
import static io.grpc.xds.XdsLoadBalancerProvider.XDS_POLICY_NAME;
|
|
||||||
import static java.util.logging.Level.FINEST;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import io.grpc.Attributes;
|
|
||||||
import io.grpc.ChannelLogger.ChannelLogLevel;
|
|
||||||
import io.grpc.ConnectivityState;
|
|
||||||
import io.grpc.ConnectivityStateInfo;
|
|
||||||
import io.grpc.EquivalentAddressGroup;
|
|
||||||
import io.grpc.LoadBalancer;
|
|
||||||
import io.grpc.LoadBalancerRegistry;
|
|
||||||
import io.grpc.ManagedChannel;
|
|
||||||
import io.grpc.ManagedChannelBuilder;
|
|
||||||
import io.grpc.NameResolver.ConfigOrError;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.SynchronizationContext.ScheduledHandle;
|
|
||||||
import io.grpc.internal.BackoffPolicy;
|
|
||||||
import io.grpc.internal.GrpcAttributes;
|
|
||||||
import io.grpc.internal.ServiceConfigUtil.LbConfig;
|
|
||||||
import io.grpc.util.ForwardingLoadBalancerHelper;
|
|
||||||
import io.grpc.xds.LoadReportClient.LoadReportCallback;
|
|
||||||
import io.grpc.xds.LoadReportClientImpl.LoadReportClientFactory;
|
|
||||||
import io.grpc.xds.LocalityStore.LocalityStoreImpl;
|
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
|
||||||
import io.grpc.xds.XdsLoadBalancerProvider.XdsConfig;
|
|
||||||
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import javax.annotation.CheckForNull;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link LoadBalancer} that uses the XDS protocol.
|
|
||||||
*/
|
|
||||||
final class XdsLoadBalancer extends LoadBalancer {
|
|
||||||
|
|
||||||
private final LocalityStore localityStore;
|
|
||||||
private final Helper helper;
|
|
||||||
private final LoadBalancerRegistry lbRegistry;
|
|
||||||
private final FallbackManager fallbackManager;
|
|
||||||
private final BackoffPolicy.Provider backoffPolicyProvider;
|
|
||||||
private final LoadReportClientFactory lrsClientFactory;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private LoadReportClient lrsClient;
|
|
||||||
@Nullable
|
|
||||||
private XdsLbState xdsLbState;
|
|
||||||
private final AdsStreamCallback adsStreamCallback = new AdsStreamCallback() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWorking() {
|
|
||||||
if (fallbackManager.childPolicyHasBeenReady) {
|
|
||||||
// cancel Fallback-After-Startup timer if there's any
|
|
||||||
fallbackManager.cancelFallbackTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
fallbackManager.childBalancerWorked = true;
|
|
||||||
lrsClient.startLoadReporting(lrsCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError() {
|
|
||||||
if (!fallbackManager.childBalancerWorked) {
|
|
||||||
// start Fallback-at-Startup immediately
|
|
||||||
fallbackManager.useFallbackPolicy();
|
|
||||||
} else if (fallbackManager.childPolicyHasBeenReady) {
|
|
||||||
// TODO: schedule a timer for Fallback-After-Startup
|
|
||||||
} // else: the Fallback-at-Startup timer is still pending, noop and wait
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAllDrop() {
|
|
||||||
fallbackManager.cancelFallback();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final LoadReportCallback lrsCallback =
|
|
||||||
new LoadReportCallback() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReportResponse(long reportIntervalNano) {
|
|
||||||
localityStore.updateOobMetricsReportInterval(reportIntervalNano);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private LbConfig fallbackPolicy;
|
|
||||||
|
|
||||||
XdsLoadBalancer(Helper helper, LoadBalancerRegistry lbRegistry,
|
|
||||||
BackoffPolicy.Provider backoffPolicyProvider) {
|
|
||||||
this(helper, lbRegistry, backoffPolicyProvider, LoadReportClientFactory.getInstance(),
|
|
||||||
new FallbackManager(helper, lbRegistry));
|
|
||||||
}
|
|
||||||
|
|
||||||
private XdsLoadBalancer(Helper helper,
|
|
||||||
LoadBalancerRegistry lbRegistry,
|
|
||||||
BackoffPolicy.Provider backoffPolicyProvider,
|
|
||||||
LoadReportClientFactory lrsClientFactory,
|
|
||||||
FallbackManager fallbackManager) {
|
|
||||||
this(helper, lbRegistry, backoffPolicyProvider, lrsClientFactory, fallbackManager,
|
|
||||||
new LocalityStoreImpl(new LocalityStoreHelper(helper, fallbackManager), lbRegistry));
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
XdsLoadBalancer(Helper helper,
|
|
||||||
LoadBalancerRegistry lbRegistry,
|
|
||||||
BackoffPolicy.Provider backoffPolicyProvider,
|
|
||||||
LoadReportClientFactory lrsClientFactory,
|
|
||||||
FallbackManager fallbackManager,
|
|
||||||
LocalityStore localityStore) {
|
|
||||||
this.helper = checkNotNull(helper, "helper");
|
|
||||||
this.lbRegistry = checkNotNull(lbRegistry, "lbRegistry");
|
|
||||||
this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
|
|
||||||
this.lrsClientFactory = checkNotNull(lrsClientFactory, "lrsClientFactory");
|
|
||||||
this.fallbackManager = checkNotNull(fallbackManager, "fallbackManager");
|
|
||||||
this.localityStore = checkNotNull(localityStore, "localityStore");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class LocalityStoreHelper extends ForwardingLoadBalancerHelper {
|
|
||||||
|
|
||||||
final Helper delegate;
|
|
||||||
final FallbackManager fallbackManager;
|
|
||||||
|
|
||||||
LocalityStoreHelper(Helper delegate, FallbackManager fallbackManager) {
|
|
||||||
this.delegate = checkNotNull(delegate, "delegate");
|
|
||||||
this.fallbackManager = checkNotNull(fallbackManager, "fallbackManager");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Helper delegate() {
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
|
|
||||||
|
|
||||||
if (newState == READY) {
|
|
||||||
checkState(
|
|
||||||
fallbackManager.childBalancerWorked,
|
|
||||||
"channel goes to READY before the load balancer even worked");
|
|
||||||
fallbackManager.childPolicyHasBeenReady = true;
|
|
||||||
fallbackManager.cancelFallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fallbackManager.isInFallbackMode()) {
|
|
||||||
delegate.getChannelLogger().log(
|
|
||||||
ChannelLogLevel.INFO, "Picker updated - state: {0}, picker: {1}", newState, newPicker);
|
|
||||||
delegate.updateBalancingState(newState, newPicker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
|
|
||||||
List<EquivalentAddressGroup> servers = resolvedAddresses.getAddresses();
|
|
||||||
Attributes attributes = resolvedAddresses.getAttributes();
|
|
||||||
Map<String, ?> newRawLbConfig = checkNotNull(
|
|
||||||
attributes.get(ATTR_LOAD_BALANCING_CONFIG), "ATTR_LOAD_BALANCING_CONFIG not available");
|
|
||||||
|
|
||||||
ConfigOrError cfg =
|
|
||||||
XdsLoadBalancerProvider.parseLoadBalancingConfigPolicy(newRawLbConfig, lbRegistry);
|
|
||||||
if (cfg.getError() != null) {
|
|
||||||
throw cfg.getError().asRuntimeException();
|
|
||||||
}
|
|
||||||
XdsConfig xdsConfig = (XdsConfig) cfg.getConfig();
|
|
||||||
fallbackPolicy = xdsConfig.fallbackPolicy;
|
|
||||||
fallbackManager.updateFallbackServers(servers, attributes, fallbackPolicy);
|
|
||||||
fallbackManager.startFallbackTimer();
|
|
||||||
handleNewConfig(xdsConfig);
|
|
||||||
xdsLbState.handleResolvedAddressGroups(servers, attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleNewConfig(XdsConfig xdsConfig) {
|
|
||||||
String newBalancerName = xdsConfig.balancerName;
|
|
||||||
LbConfig childPolicy = xdsConfig.childPolicy;
|
|
||||||
ManagedChannel lbChannel;
|
|
||||||
if (xdsLbState == null) {
|
|
||||||
lbChannel = initLbChannel(helper, newBalancerName);
|
|
||||||
lrsClient =
|
|
||||||
lrsClientFactory.createLoadReportClient(lbChannel, helper, backoffPolicyProvider,
|
|
||||||
localityStore.getLoadStatsStore());
|
|
||||||
} else if (!newBalancerName.equals(xdsLbState.balancerName)) {
|
|
||||||
lrsClient.stopLoadReporting();
|
|
||||||
ManagedChannel oldChannel =
|
|
||||||
xdsLbState.shutdownAndReleaseChannel(
|
|
||||||
String.format("Changing balancer name from %s to %s", xdsLbState.balancerName,
|
|
||||||
newBalancerName));
|
|
||||||
oldChannel.shutdown();
|
|
||||||
lbChannel = initLbChannel(helper, newBalancerName);
|
|
||||||
lrsClient =
|
|
||||||
lrsClientFactory.createLoadReportClient(lbChannel, helper, backoffPolicyProvider,
|
|
||||||
localityStore.getLoadStatsStore());
|
|
||||||
} else if (!Objects.equal(
|
|
||||||
getPolicyNameOrNull(childPolicy),
|
|
||||||
getPolicyNameOrNull(xdsLbState.childPolicy))) {
|
|
||||||
// Changing child policy does not affect load reporting.
|
|
||||||
lbChannel =
|
|
||||||
xdsLbState.shutdownAndReleaseChannel(
|
|
||||||
String.format("Changing child policy from %s to %s", xdsLbState.childPolicy,
|
|
||||||
childPolicy));
|
|
||||||
} else { // effectively no change in policy, keep xdsLbState unchanged
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
xdsLbState =
|
|
||||||
new XdsLbState(newBalancerName, childPolicy, helper, localityStore, lbChannel,
|
|
||||||
adsStreamCallback, backoffPolicyProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ManagedChannel initLbChannel(Helper helper, String balancerName) {
|
|
||||||
ManagedChannel channel;
|
|
||||||
try {
|
|
||||||
channel = helper.createResolvingOobChannel(balancerName);
|
|
||||||
} catch (UnsupportedOperationException uoe) {
|
|
||||||
// Temporary solution until createResolvingOobChannel is implemented
|
|
||||||
// FIXME (https://github.com/grpc/grpc-java/issues/5495)
|
|
||||||
Logger logger = Logger.getLogger(XdsLoadBalancer.class.getName());
|
|
||||||
if (logger.isLoggable(FINEST)) {
|
|
||||||
logger.log(
|
|
||||||
FINEST,
|
|
||||||
"createResolvingOobChannel() not supported by the helper: " + helper,
|
|
||||||
uoe);
|
|
||||||
logger.log(
|
|
||||||
FINEST,
|
|
||||||
"creating oob channel for target {0} using default ManagedChannelBuilder",
|
|
||||||
balancerName);
|
|
||||||
}
|
|
||||||
channel = ManagedChannelBuilder.forTarget(balancerName).build();
|
|
||||||
}
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String getPolicyNameOrNull(@Nullable LbConfig config) {
|
|
||||||
if (config == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return config.getPolicyName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleNameResolutionError(Status error) {
|
|
||||||
if (xdsLbState != null) {
|
|
||||||
xdsLbState.handleNameResolutionError(error);
|
|
||||||
}
|
|
||||||
if (fallbackManager.isInFallbackMode()) {
|
|
||||||
fallbackManager.fallbackBalancer.handleNameResolutionError(error);
|
|
||||||
}
|
|
||||||
if (xdsLbState == null && !fallbackManager.isInFallbackMode()) {
|
|
||||||
helper.updateBalancingState(TRANSIENT_FAILURE, new ErrorPicker(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is only for the subchannel that is created by the child/fallback balancer using the
|
|
||||||
* old API {@link LoadBalancer.Helper#createSubchannel(EquivalentAddressGroup, Attributes)} or
|
|
||||||
* {@link LoadBalancer.Helper#createSubchannel(List, Attributes)}. Otherwise, it either won't be
|
|
||||||
* called or won't have any effect.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
|
||||||
public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo newState) {
|
|
||||||
if (fallbackManager.isInFallbackMode()) {
|
|
||||||
fallbackManager.fallbackBalancer.handleSubchannelState(subchannel, newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// xdsLbState should never be null here since handleSubchannelState cannot be called while the
|
|
||||||
// lb is shutdown.
|
|
||||||
xdsLbState.handleSubchannelState(subchannel, newState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
if (xdsLbState != null) {
|
|
||||||
lrsClient.stopLoadReporting();
|
|
||||||
lrsClient = null;
|
|
||||||
ManagedChannel channel = xdsLbState.shutdownAndReleaseChannel("Client shutdown");
|
|
||||||
channel.shutdown();
|
|
||||||
xdsLbState = null;
|
|
||||||
}
|
|
||||||
fallbackManager.cancelFallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canHandleEmptyAddressListFromNameResolution() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
XdsLbState getXdsLbStateForTest() {
|
|
||||||
return xdsLbState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final class FallbackManager {
|
|
||||||
|
|
||||||
private static final long FALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); // same as grpclb
|
|
||||||
|
|
||||||
private final Helper helper;
|
|
||||||
private final LoadBalancerRegistry lbRegistry;
|
|
||||||
|
|
||||||
private LbConfig fallbackPolicy;
|
|
||||||
|
|
||||||
// read-only for outer class
|
|
||||||
private LoadBalancer fallbackBalancer;
|
|
||||||
|
|
||||||
// Scheduled only once. Never reset.
|
|
||||||
@CheckForNull
|
|
||||||
private ScheduledHandle fallbackTimer;
|
|
||||||
|
|
||||||
private List<EquivalentAddressGroup> fallbackServers = ImmutableList.of();
|
|
||||||
private Attributes fallbackAttributes;
|
|
||||||
|
|
||||||
// allow value write by outer class
|
|
||||||
private boolean childBalancerWorked;
|
|
||||||
private boolean childPolicyHasBeenReady;
|
|
||||||
|
|
||||||
FallbackManager(Helper helper, LoadBalancerRegistry lbRegistry) {
|
|
||||||
this.helper = checkNotNull(helper, "helper");
|
|
||||||
this.lbRegistry = checkNotNull(lbRegistry, "lbRegistry");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fallback mode being on indicates that an update from child LBs will be ignored unless the
|
|
||||||
* update triggers turning off the fallback mode first.
|
|
||||||
*/
|
|
||||||
boolean isInFallbackMode() {
|
|
||||||
return fallbackBalancer != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cancelFallbackTimer() {
|
|
||||||
if (fallbackTimer != null) {
|
|
||||||
fallbackTimer.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cancelFallback() {
|
|
||||||
cancelFallbackTimer();
|
|
||||||
if (fallbackBalancer != null) {
|
|
||||||
helper.getChannelLogger().log(
|
|
||||||
ChannelLogLevel.INFO, "Shutting down XDS fallback balancer");
|
|
||||||
fallbackBalancer.shutdown();
|
|
||||||
fallbackBalancer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void useFallbackPolicy() {
|
|
||||||
if (fallbackBalancer != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelFallbackTimer();
|
|
||||||
|
|
||||||
helper.getChannelLogger().log(
|
|
||||||
ChannelLogLevel.INFO, "Using XDS fallback policy");
|
|
||||||
|
|
||||||
final class FallbackBalancerHelper extends ForwardingLoadBalancerHelper {
|
|
||||||
LoadBalancer balancer;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
|
|
||||||
checkNotNull(balancer, "there is a bug");
|
|
||||||
if (balancer != fallbackBalancer) {
|
|
||||||
// ignore updates from a misbehaving shutdown fallback balancer
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
helper.getChannelLogger().log(
|
|
||||||
ChannelLogLevel.INFO,
|
|
||||||
"Picker updated - state: {0}, picker: {1}", newState, newPicker);
|
|
||||||
super.updateBalancingState(newState, newPicker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Helper delegate() {
|
|
||||||
return helper;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FallbackBalancerHelper fallbackBalancerHelper = new FallbackBalancerHelper();
|
|
||||||
fallbackBalancer = lbRegistry.getProvider(fallbackPolicy.getPolicyName())
|
|
||||||
.newLoadBalancer(fallbackBalancerHelper);
|
|
||||||
fallbackBalancerHelper.balancer = fallbackBalancer;
|
|
||||||
propagateFallbackAddresses();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateFallbackServers(
|
|
||||||
List<EquivalentAddressGroup> servers, Attributes attributes,
|
|
||||||
LbConfig fallbackPolicy) {
|
|
||||||
this.fallbackServers = servers;
|
|
||||||
this.fallbackAttributes = Attributes.newBuilder()
|
|
||||||
.setAll(attributes)
|
|
||||||
.set(ATTR_LOAD_BALANCING_CONFIG, fallbackPolicy.getRawConfigValue())
|
|
||||||
.build();
|
|
||||||
LbConfig currentFallbackPolicy = this.fallbackPolicy;
|
|
||||||
this.fallbackPolicy = fallbackPolicy;
|
|
||||||
if (fallbackBalancer != null) {
|
|
||||||
if (fallbackPolicy.getPolicyName().equals(currentFallbackPolicy.getPolicyName())) {
|
|
||||||
propagateFallbackAddresses();
|
|
||||||
} else {
|
|
||||||
fallbackBalancer.shutdown();
|
|
||||||
fallbackBalancer = null;
|
|
||||||
useFallbackPolicy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void propagateFallbackAddresses() {
|
|
||||||
String fallbackPolicyName = fallbackPolicy.getPolicyName();
|
|
||||||
List<EquivalentAddressGroup> servers = fallbackServers;
|
|
||||||
|
|
||||||
// Some addresses in the list may be grpclb-v1 balancer addresses, so if the fallback policy
|
|
||||||
// does not support grpclb-v1 balancer addresses, then we need to exclude them from the list.
|
|
||||||
if (!fallbackPolicyName.equals("grpclb") && !fallbackPolicyName.equals(XDS_POLICY_NAME)) {
|
|
||||||
ImmutableList.Builder<EquivalentAddressGroup> backends = ImmutableList.builder();
|
|
||||||
for (EquivalentAddressGroup eag : fallbackServers) {
|
|
||||||
if (eag.getAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY) == null) {
|
|
||||||
backends.add(eag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
servers = backends.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(zhangkun83): FIXME(#5496): this is a temporary hack.
|
|
||||||
if (servers.isEmpty()
|
|
||||||
&& !fallbackBalancer.canHandleEmptyAddressListFromNameResolution()) {
|
|
||||||
fallbackBalancer.handleNameResolutionError(Status.UNAVAILABLE.withDescription(
|
|
||||||
"NameResolver returned no usable address."
|
|
||||||
+ " addrs=" + fallbackServers + ", attrs=" + fallbackAttributes));
|
|
||||||
} else {
|
|
||||||
// TODO(carl-mastrangelo): propagate the load balancing config policy
|
|
||||||
fallbackBalancer.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(servers)
|
|
||||||
.setAttributes(fallbackAttributes)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void startFallbackTimer() {
|
|
||||||
if (fallbackTimer == null) {
|
|
||||||
class FallbackTask implements Runnable {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
useFallbackPolicy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fallbackTimer = helper.getSynchronizationContext().schedule(
|
|
||||||
new FallbackTask(), FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS,
|
|
||||||
helper.getScheduledExecutorService());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,7 @@ import io.grpc.LoadBalancer;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.SynchronizationContext.ScheduledHandle;
|
import io.grpc.SynchronizationContext.ScheduledHandle;
|
||||||
import io.grpc.util.ForwardingLoadBalancerHelper;
|
import io.grpc.util.ForwardingLoadBalancerHelper;
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
import io.grpc.xds.LookasideChannelLb.LookasideChannelCallback;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.annotation.CheckForNull;
|
import javax.annotation.CheckForNull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
@ -46,7 +46,7 @@ final class XdsLoadBalancer2 extends LoadBalancer {
|
||||||
private final Helper helper;
|
private final Helper helper;
|
||||||
private final LoadBalancer lookasideLb;
|
private final LoadBalancer lookasideLb;
|
||||||
private final LoadBalancer.Factory fallbackLbFactory;
|
private final LoadBalancer.Factory fallbackLbFactory;
|
||||||
private final AdsStreamCallback adsCallback = new AdsStreamCallback() {
|
private final LookasideChannelCallback lookasideChannelCallback = new LookasideChannelCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onWorking() {
|
public void onWorking() {
|
||||||
if (childPolicyHasBeenReady) {
|
if (childPolicyHasBeenReady) {
|
||||||
|
|
@ -93,7 +93,8 @@ final class XdsLoadBalancer2 extends LoadBalancer {
|
||||||
LookasideLbFactory lookasideLbFactory,
|
LookasideLbFactory lookasideLbFactory,
|
||||||
LoadBalancer.Factory fallbackLbFactory) {
|
LoadBalancer.Factory fallbackLbFactory) {
|
||||||
this.helper = helper;
|
this.helper = helper;
|
||||||
this.lookasideLb = lookasideLbFactory.newLoadBalancer(new LookasideLbHelper(), adsCallback);
|
this.lookasideLb = lookasideLbFactory.newLoadBalancer(new LookasideLbHelper(),
|
||||||
|
lookasideChannelCallback);
|
||||||
this.fallbackLbFactory = fallbackLbFactory;
|
this.fallbackLbFactory = fallbackLbFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,13 +247,14 @@ final class XdsLoadBalancer2 extends LoadBalancer {
|
||||||
/** Factory of a look-aside load balancer. The interface itself is for convenience in test. */
|
/** Factory of a look-aside load balancer. The interface itself is for convenience in test. */
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
interface LookasideLbFactory {
|
interface LookasideLbFactory {
|
||||||
LoadBalancer newLoadBalancer(Helper helper, AdsStreamCallback adsCallback);
|
LoadBalancer newLoadBalancer(Helper helper, LookasideChannelCallback lookasideChannelCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class LookasideLbFactoryImpl implements LookasideLbFactory {
|
private static final class LookasideLbFactoryImpl implements LookasideLbFactory {
|
||||||
@Override
|
@Override
|
||||||
public LoadBalancer newLoadBalancer(Helper lookasideLbHelper, AdsStreamCallback adsCallback) {
|
public LoadBalancer newLoadBalancer(
|
||||||
return new LookasideLb(lookasideLbHelper, adsCallback);
|
Helper lookasideLbHelper, LookasideChannelCallback lookasideChannelCallback) {
|
||||||
|
return new LookasideLb(lookasideLbHelper, lookasideChannelCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import io.grpc.LoadBalancerProvider;
|
||||||
import io.grpc.LoadBalancerRegistry;
|
import io.grpc.LoadBalancerRegistry;
|
||||||
import io.grpc.NameResolver.ConfigOrError;
|
import io.grpc.NameResolver.ConfigOrError;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.internal.ExponentialBackoffPolicy;
|
|
||||||
import io.grpc.internal.ServiceConfigUtil;
|
import io.grpc.internal.ServiceConfigUtil;
|
||||||
import io.grpc.internal.ServiceConfigUtil.LbConfig;
|
import io.grpc.internal.ServiceConfigUtil.LbConfig;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -66,8 +65,7 @@ public final class XdsLoadBalancerProvider extends LoadBalancerProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
public LoadBalancer newLoadBalancer(Helper helper) {
|
||||||
return new XdsLoadBalancer(helper, LoadBalancerRegistry.getDefaultRegistry(),
|
return new XdsLoadBalancer2(helper);
|
||||||
new ExponentialBackoffPolicy.Provider());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.base.MoreObjects;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@code XdsLocality} object is simply a POJO representation for {@link
|
|
||||||
* io.envoyproxy.envoy.api.v2.core.Locality}, with only details needed for {@link XdsLoadBalancer}.
|
|
||||||
*/
|
|
||||||
final class XdsLocality {
|
|
||||||
private final String region;
|
|
||||||
private final String zone;
|
|
||||||
private final String subzone;
|
|
||||||
|
|
||||||
/** Must only be used for testing. */
|
|
||||||
@VisibleForTesting
|
|
||||||
XdsLocality(String region, String zone, String subzone) {
|
|
||||||
this.region = region;
|
|
||||||
this.zone = zone;
|
|
||||||
this.subzone = subzone;
|
|
||||||
}
|
|
||||||
|
|
||||||
static XdsLocality fromLocalityProto(io.envoyproxy.envoy.api.v2.core.Locality locality) {
|
|
||||||
return new XdsLocality(
|
|
||||||
/* region = */ locality.getRegion(),
|
|
||||||
/* zone = */ locality.getZone(),
|
|
||||||
/* subzone = */ locality.getSubZone());
|
|
||||||
}
|
|
||||||
|
|
||||||
io.envoyproxy.envoy.api.v2.core.Locality toLocalityProto() {
|
|
||||||
return io.envoyproxy.envoy.api.v2.core.Locality.newBuilder()
|
|
||||||
.setRegion(region)
|
|
||||||
.setZone(zone)
|
|
||||||
.setSubZone(subzone)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
String getRegion() {
|
|
||||||
return region;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getZone() {
|
|
||||||
return zone;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getSubzone() {
|
|
||||||
return subzone;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
XdsLocality locality = (XdsLocality) o;
|
|
||||||
return Objects.equal(region, locality.region)
|
|
||||||
&& Objects.equal(zone, locality.zone)
|
|
||||||
&& Objects.equal(subzone, locality.subzone);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(region, zone, subzone);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return MoreObjects.toStringHelper(this)
|
|
||||||
.add("region", region)
|
|
||||||
.add("zone", zone)
|
|
||||||
.add("subzone", subzone)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -20,18 +20,19 @@ import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import com.google.common.testing.EqualsTester;
|
import com.google.common.testing.EqualsTester;
|
||||||
import io.envoyproxy.envoy.api.v2.core.Locality;
|
import io.envoyproxy.envoy.api.v2.core.Locality;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link XdsLocality}.
|
* Unit tests for {@link ClusterLoadAssignmentData}.
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class XdsLocalityTest {
|
public class ClusterLoadAssignmentDataTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void convertToAndFromLocalityProto() {
|
public void xdsLocality_convertToAndFromLocalityProto() {
|
||||||
Locality locality =
|
Locality locality =
|
||||||
Locality.newBuilder()
|
Locality.newBuilder()
|
||||||
.setRegion("test_region")
|
.setRegion("test_region")
|
||||||
|
|
@ -50,7 +51,7 @@ public class XdsLocalityTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void equal() {
|
public void xdsLocality_equal() {
|
||||||
new EqualsTester()
|
new EqualsTester()
|
||||||
.addEqualityGroup(
|
.addEqualityGroup(
|
||||||
new XdsLocality("region-a", "zone-a", "subzone-a"),
|
new XdsLocality("region-a", "zone-a", "subzone-a"),
|
||||||
|
|
@ -65,7 +66,7 @@ public class XdsLocalityTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hash() {
|
public void xdsLocality_hash() {
|
||||||
assertThat(new XdsLocality("region", "zone", "subzone").hashCode())
|
assertThat(new XdsLocality("region", "zone", "subzone").hashCode())
|
||||||
.isEqualTo(new XdsLocality("region", "zone","subzone").hashCode());
|
.isEqualTo(new XdsLocality("region", "zone","subzone").hashCode());
|
||||||
}
|
}
|
||||||
|
|
@ -1,285 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import io.grpc.Attributes;
|
|
||||||
import io.grpc.ChannelLogger;
|
|
||||||
import io.grpc.EquivalentAddressGroup;
|
|
||||||
import io.grpc.LoadBalancer;
|
|
||||||
import io.grpc.LoadBalancer.Helper;
|
|
||||||
import io.grpc.LoadBalancer.ResolvedAddresses;
|
|
||||||
import io.grpc.LoadBalancerProvider;
|
|
||||||
import io.grpc.LoadBalancerRegistry;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.Status.Code;
|
|
||||||
import io.grpc.SynchronizationContext;
|
|
||||||
import io.grpc.internal.FakeClock;
|
|
||||||
import io.grpc.internal.GrpcAttributes;
|
|
||||||
import io.grpc.internal.ServiceConfigUtil.LbConfig;
|
|
||||||
import io.grpc.xds.XdsLoadBalancer.FallbackManager;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.ArgumentMatchers;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit test for {@link FallbackManager}.
|
|
||||||
*/
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class FallbackManagerTest {
|
|
||||||
|
|
||||||
private static final long FALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
|
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
|
||||||
private final LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry();
|
|
||||||
|
|
||||||
private final LoadBalancerProvider fakeFallbackLbProvider = new LoadBalancerProvider() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPolicyName() {
|
|
||||||
return fallbackPolicy.getPolicyName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
|
||||||
return fakeFallbackLb;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final LoadBalancerProvider fakeRoundRonbinLbProvider = new LoadBalancerProvider() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPolicyName() {
|
|
||||||
return "round_robin";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
|
||||||
return fakeRoundRobinLb;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final SynchronizationContext syncContext = new SynchronizationContext(
|
|
||||||
new Thread.UncaughtExceptionHandler() {
|
|
||||||
@Override
|
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Helper helper;
|
|
||||||
@Mock
|
|
||||||
private LoadBalancer fakeRoundRobinLb;
|
|
||||||
@Mock
|
|
||||||
private LoadBalancer fakeFallbackLb;
|
|
||||||
@Mock
|
|
||||||
private ChannelLogger channelLogger;
|
|
||||||
|
|
||||||
private FallbackManager fallbackManager;
|
|
||||||
private LbConfig fallbackPolicy;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
doReturn(syncContext).when(helper).getSynchronizationContext();
|
|
||||||
doReturn(fakeClock.getScheduledExecutorService()).when(helper).getScheduledExecutorService();
|
|
||||||
doReturn(channelLogger).when(helper).getChannelLogger();
|
|
||||||
fallbackPolicy = new LbConfig("test_policy", new HashMap<String, Void>());
|
|
||||||
lbRegistry.register(fakeRoundRonbinLbProvider);
|
|
||||||
lbRegistry.register(fakeFallbackLbProvider);
|
|
||||||
fallbackManager = new FallbackManager(helper, lbRegistry);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
assertThat(fakeClock.getPendingTasks()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void useFallbackWhenTimeout() {
|
|
||||||
fallbackManager.startFallbackTimer();
|
|
||||||
List<EquivalentAddressGroup> eags = ImmutableList.of(
|
|
||||||
new EquivalentAddressGroup(ImmutableList.<SocketAddress>of(new InetSocketAddress(8080))));
|
|
||||||
fallbackManager.updateFallbackServers(
|
|
||||||
eags, Attributes.EMPTY, fallbackPolicy);
|
|
||||||
|
|
||||||
assertThat(fallbackManager.isInFallbackMode()).isFalse();
|
|
||||||
verify(fakeFallbackLb, never())
|
|
||||||
.handleResolvedAddresses(ArgumentMatchers.any(ResolvedAddresses.class));
|
|
||||||
|
|
||||||
fakeClock.forwardTime(FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
assertThat(fallbackManager.isInFallbackMode()).isTrue();
|
|
||||||
verify(fakeFallbackLb).handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(eags)
|
|
||||||
.setAttributes(
|
|
||||||
Attributes.newBuilder()
|
|
||||||
.set(
|
|
||||||
LoadBalancer.ATTR_LOAD_BALANCING_CONFIG,
|
|
||||||
fallbackPolicy.getRawConfigValue())
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallback_handleBackendsEagsOnly() {
|
|
||||||
fallbackManager.startFallbackTimer();
|
|
||||||
EquivalentAddressGroup eag0 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8080)));
|
|
||||||
Attributes attributes = Attributes
|
|
||||||
.newBuilder()
|
|
||||||
.set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "this is a balancer address")
|
|
||||||
.build();
|
|
||||||
EquivalentAddressGroup eag1 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8081)), attributes);
|
|
||||||
EquivalentAddressGroup eag2 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8082)));
|
|
||||||
List<EquivalentAddressGroup> eags = ImmutableList.of(eag0, eag1, eag2);
|
|
||||||
fallbackManager.updateFallbackServers(
|
|
||||||
eags, Attributes.EMPTY, fallbackPolicy);
|
|
||||||
|
|
||||||
fakeClock.forwardTime(FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
assertThat(fallbackManager.isInFallbackMode()).isTrue();
|
|
||||||
verify(fakeFallbackLb).handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(ImmutableList.of(eag0, eag2))
|
|
||||||
.setAttributes(
|
|
||||||
Attributes.newBuilder()
|
|
||||||
.set(
|
|
||||||
LoadBalancer.ATTR_LOAD_BALANCING_CONFIG,
|
|
||||||
fallbackPolicy.getRawConfigValue())
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallback_handleGrpclbAddresses() {
|
|
||||||
lbRegistry.deregister(fakeFallbackLbProvider);
|
|
||||||
fallbackPolicy = new LbConfig("grpclb", new HashMap<String, Void>());
|
|
||||||
lbRegistry.register(fakeFallbackLbProvider);
|
|
||||||
|
|
||||||
fallbackManager.startFallbackTimer();
|
|
||||||
EquivalentAddressGroup eag0 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8080)));
|
|
||||||
Attributes attributes = Attributes
|
|
||||||
.newBuilder()
|
|
||||||
.set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "this is a balancer address")
|
|
||||||
.build();
|
|
||||||
EquivalentAddressGroup eag1 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8081)), attributes);
|
|
||||||
EquivalentAddressGroup eag2 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8082)));
|
|
||||||
List<EquivalentAddressGroup> eags = ImmutableList.of(eag0, eag1, eag2);
|
|
||||||
fallbackManager.updateFallbackServers(
|
|
||||||
eags, Attributes.EMPTY, fallbackPolicy);
|
|
||||||
|
|
||||||
fakeClock.forwardTime(FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
assertThat(fallbackManager.isInFallbackMode()).isTrue();
|
|
||||||
verify(fakeFallbackLb).handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(eags)
|
|
||||||
.setAttributes(
|
|
||||||
Attributes.newBuilder()
|
|
||||||
.set(
|
|
||||||
LoadBalancer.ATTR_LOAD_BALANCING_CONFIG,
|
|
||||||
fallbackPolicy.getRawConfigValue())
|
|
||||||
.build())
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallback_onlyGrpclbAddresses_NoBackendAddress() {
|
|
||||||
lbRegistry.deregister(fakeFallbackLbProvider);
|
|
||||||
fallbackPolicy = new LbConfig("not_grpclb", new HashMap<String, Void>());
|
|
||||||
lbRegistry.register(fakeFallbackLbProvider);
|
|
||||||
|
|
||||||
fallbackManager.startFallbackTimer();
|
|
||||||
Attributes attributes = Attributes
|
|
||||||
.newBuilder()
|
|
||||||
.set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "this is a balancer address")
|
|
||||||
.build();
|
|
||||||
EquivalentAddressGroup eag1 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8081)), attributes);
|
|
||||||
EquivalentAddressGroup eag2 = new EquivalentAddressGroup(
|
|
||||||
ImmutableList.<SocketAddress>of(new InetSocketAddress(8082)), attributes);
|
|
||||||
List<EquivalentAddressGroup> eags = ImmutableList.of(eag1, eag2);
|
|
||||||
fallbackManager.updateFallbackServers(
|
|
||||||
eags, Attributes.EMPTY, fallbackPolicy);
|
|
||||||
|
|
||||||
fakeClock.forwardTime(FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
assertThat(fallbackManager.isInFallbackMode()).isTrue();
|
|
||||||
ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
|
|
||||||
verify(fakeFallbackLb).handleNameResolutionError(statusCaptor.capture());
|
|
||||||
assertThat(statusCaptor.getValue().getCode()).isEqualTo(Code.UNAVAILABLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cancelFallback() {
|
|
||||||
fallbackManager.startFallbackTimer();
|
|
||||||
List<EquivalentAddressGroup> eags = ImmutableList.of(
|
|
||||||
new EquivalentAddressGroup(ImmutableList.<SocketAddress>of(new InetSocketAddress(8080))));
|
|
||||||
fallbackManager.updateFallbackServers(
|
|
||||||
eags, Attributes.EMPTY, fallbackPolicy);
|
|
||||||
|
|
||||||
fallbackManager.cancelFallback();
|
|
||||||
|
|
||||||
fakeClock.forwardTime(FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
assertThat(fallbackManager.isInFallbackMode()).isFalse();
|
|
||||||
verify(fakeFallbackLb, never())
|
|
||||||
.handleResolvedAddresses(ArgumentMatchers.any(ResolvedAddresses.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -24,6 +24,7 @@ import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests;
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats;
|
import io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats;
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats;
|
import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats;
|
||||||
import io.grpc.xds.ClientLoadCounter.MetricValue;
|
import io.grpc.xds.ClientLoadCounter.MetricValue;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -71,7 +72,8 @@ public class LoadStatsStoreImplTest {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UpstreamLocalityStats buildUpstreamLocalityStats(XdsLocality locality,
|
private static UpstreamLocalityStats buildUpstreamLocalityStats(
|
||||||
|
XdsLocality locality,
|
||||||
long callsSucceed,
|
long callsSucceed,
|
||||||
long callsInProgress,
|
long callsInProgress,
|
||||||
long callsFailed,
|
long callsFailed,
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ import io.grpc.internal.FakeClock.ScheduledTask;
|
||||||
import io.grpc.internal.FakeClock.TaskFilter;
|
import io.grpc.internal.FakeClock.TaskFilter;
|
||||||
import io.grpc.xds.ClientLoadCounter.LoadRecordingStreamTracerFactory;
|
import io.grpc.xds.ClientLoadCounter.LoadRecordingStreamTracerFactory;
|
||||||
import io.grpc.xds.ClientLoadCounter.MetricsRecordingListener;
|
import io.grpc.xds.ClientLoadCounter.MetricsRecordingListener;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.DropOverload;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.LbEndpoint;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.LocalityInfo;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import io.grpc.xds.InterLocalityPicker.WeightedChildPicker;
|
import io.grpc.xds.InterLocalityPicker.WeightedChildPicker;
|
||||||
import io.grpc.xds.LocalityStore.LocalityStoreImpl;
|
import io.grpc.xds.LocalityStore.LocalityStoreImpl;
|
||||||
import io.grpc.xds.LocalityStore.LocalityStoreImpl.PickerFactory;
|
import io.grpc.xds.LocalityStore.LocalityStoreImpl.PickerFactory;
|
||||||
|
|
@ -69,9 +73,6 @@ import io.grpc.xds.OrcaOobUtil.OrcaOobReportListener;
|
||||||
import io.grpc.xds.OrcaOobUtil.OrcaReportingConfig;
|
import io.grpc.xds.OrcaOobUtil.OrcaReportingConfig;
|
||||||
import io.grpc.xds.OrcaOobUtil.OrcaReportingHelperWrapper;
|
import io.grpc.xds.OrcaOobUtil.OrcaReportingHelperWrapper;
|
||||||
import io.grpc.xds.OrcaPerRequestUtil.OrcaPerRequestReportListener;
|
import io.grpc.xds.OrcaPerRequestUtil.OrcaPerRequestReportListener;
|
||||||
import io.grpc.xds.XdsComms.DropOverload;
|
|
||||||
import io.grpc.xds.XdsComms.LbEndpoint;
|
|
||||||
import io.grpc.xds.XdsComms.LocalityInfo;
|
|
||||||
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
|
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,11 @@ import io.grpc.inprocess.InProcessServerBuilder;
|
||||||
import io.grpc.internal.testing.StreamRecorder;
|
import io.grpc.internal.testing.StreamRecorder;
|
||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import io.grpc.testing.GrpcCleanupRule;
|
import io.grpc.testing.GrpcCleanupRule;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.DropOverload;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.LocalityInfo;
|
||||||
|
import io.grpc.xds.ClusterLoadAssignmentData.XdsLocality;
|
||||||
import io.grpc.xds.LoadReportClient.LoadReportCallback;
|
import io.grpc.xds.LoadReportClient.LoadReportCallback;
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
import io.grpc.xds.LookasideChannelLb.LookasideChannelCallback;
|
||||||
import io.grpc.xds.XdsComms.DropOverload;
|
|
||||||
import io.grpc.xds.XdsComms.LocalityInfo;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
@ -103,7 +104,7 @@ public class LookasideChannelLbTest {
|
||||||
@Mock
|
@Mock
|
||||||
private Helper helper;
|
private Helper helper;
|
||||||
@Mock
|
@Mock
|
||||||
private AdsStreamCallback adsStreamCallback;
|
private LookasideChannelCallback lookasideChannelCallback;
|
||||||
@Mock
|
@Mock
|
||||||
private LoadReportClient loadReportClient;
|
private LoadReportClient loadReportClient;
|
||||||
@Mock
|
@Mock
|
||||||
|
|
@ -168,17 +169,17 @@ public class LookasideChannelLbTest {
|
||||||
doReturn(loadStatsStore).when(localityStore).getLoadStatsStore();
|
doReturn(loadStatsStore).when(localityStore).getLoadStatsStore();
|
||||||
|
|
||||||
lookasideChannelLb = new LookasideChannelLb(
|
lookasideChannelLb = new LookasideChannelLb(
|
||||||
helper, adsStreamCallback, channel, loadReportClient, localityStore);
|
helper, lookasideChannelCallback, channel, loadReportClient, localityStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void firstAndSecondEdsResponseReceived() {
|
public void firstAndSecondEdsResponseReceived() {
|
||||||
verify(adsStreamCallback, never()).onWorking();
|
verify(lookasideChannelCallback, never()).onWorking();
|
||||||
verify(loadReportClient, never()).startLoadReporting(any(LoadReportCallback.class));
|
verify(loadReportClient, never()).startLoadReporting(any(LoadReportCallback.class));
|
||||||
|
|
||||||
// first EDS response
|
// first EDS response
|
||||||
serverResponseWriter.onNext(edsResponse);
|
serverResponseWriter.onNext(edsResponse);
|
||||||
verify(adsStreamCallback).onWorking();
|
verify(lookasideChannelCallback).onWorking();
|
||||||
ArgumentCaptor<LoadReportCallback> loadReportCallbackCaptor =
|
ArgumentCaptor<LoadReportCallback> loadReportCallbackCaptor =
|
||||||
ArgumentCaptor.forClass(LoadReportCallback.class);
|
ArgumentCaptor.forClass(LoadReportCallback.class);
|
||||||
verify(loadReportClient).startLoadReporting(loadReportCallbackCaptor.capture());
|
verify(loadReportClient).startLoadReporting(loadReportCallbackCaptor.capture());
|
||||||
|
|
@ -186,14 +187,14 @@ public class LookasideChannelLbTest {
|
||||||
|
|
||||||
// second EDS response
|
// second EDS response
|
||||||
serverResponseWriter.onNext(edsResponse);
|
serverResponseWriter.onNext(edsResponse);
|
||||||
verify(adsStreamCallback, times(1)).onWorking();
|
verify(lookasideChannelCallback, times(1)).onWorking();
|
||||||
verify(loadReportClient, times(1)).startLoadReporting(any(LoadReportCallback.class));
|
verify(loadReportClient, times(1)).startLoadReporting(any(LoadReportCallback.class));
|
||||||
|
|
||||||
verify(localityStore, never()).updateOobMetricsReportInterval(anyLong());
|
verify(localityStore, never()).updateOobMetricsReportInterval(anyLong());
|
||||||
loadReportCallback.onReportResponse(1234);
|
loadReportCallback.onReportResponse(1234);
|
||||||
verify(localityStore).updateOobMetricsReportInterval(1234);
|
verify(localityStore).updateOobMetricsReportInterval(1234);
|
||||||
|
|
||||||
verify(adsStreamCallback, never()).onError();
|
verify(lookasideChannelCallback, never()).onError();
|
||||||
|
|
||||||
lookasideChannelLb.shutdown();
|
lookasideChannelLb.shutdown();
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +236,7 @@ public class LookasideChannelLbTest {
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
verify(adsStreamCallback, never()).onAllDrop();
|
verify(lookasideChannelCallback, never()).onAllDrop();
|
||||||
verify(localityStore).updateDropPercentage(ImmutableList.of(
|
verify(localityStore).updateDropPercentage(ImmutableList.of(
|
||||||
new DropOverload("cat_1", 300_00),
|
new DropOverload("cat_1", 300_00),
|
||||||
new DropOverload("cat_2", 45_00),
|
new DropOverload("cat_2", 45_00),
|
||||||
|
|
@ -270,12 +271,12 @@ public class LookasideChannelLbTest {
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
verify(adsStreamCallback).onAllDrop();
|
verify(lookasideChannelCallback).onAllDrop();
|
||||||
verify(localityStore).updateDropPercentage(ImmutableList.of(
|
verify(localityStore).updateDropPercentage(ImmutableList.of(
|
||||||
new DropOverload("cat_1", 300_00),
|
new DropOverload("cat_1", 300_00),
|
||||||
new DropOverload("cat_2", 100_00_00)));
|
new DropOverload("cat_2", 100_00_00)));
|
||||||
|
|
||||||
verify(adsStreamCallback, never()).onError();
|
verify(lookasideChannelCallback, never()).onError();
|
||||||
|
|
||||||
lookasideChannelLb.shutdown();
|
lookasideChannelLb.shutdown();
|
||||||
}
|
}
|
||||||
|
|
@ -348,13 +349,13 @@ public class LookasideChannelLbTest {
|
||||||
XdsLocality locality1 = XdsLocality.fromLocalityProto(localityProto1);
|
XdsLocality locality1 = XdsLocality.fromLocalityProto(localityProto1);
|
||||||
LocalityInfo localityInfo1 = new LocalityInfo(
|
LocalityInfo localityInfo1 = new LocalityInfo(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new XdsComms.LbEndpoint(endpoint11),
|
new ClusterLoadAssignmentData.LbEndpoint(endpoint11),
|
||||||
new XdsComms.LbEndpoint(endpoint12)),
|
new ClusterLoadAssignmentData.LbEndpoint(endpoint12)),
|
||||||
1, 0);
|
1, 0);
|
||||||
LocalityInfo localityInfo2 = new LocalityInfo(
|
LocalityInfo localityInfo2 = new LocalityInfo(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new XdsComms.LbEndpoint(endpoint21),
|
new ClusterLoadAssignmentData.LbEndpoint(endpoint21),
|
||||||
new XdsComms.LbEndpoint(endpoint22)),
|
new ClusterLoadAssignmentData.LbEndpoint(endpoint22)),
|
||||||
2, 0);
|
2, 0);
|
||||||
XdsLocality locality2 = XdsLocality.fromLocalityProto(localityProto2);
|
XdsLocality locality2 = XdsLocality.fromLocalityProto(localityProto2);
|
||||||
|
|
||||||
|
|
@ -364,16 +365,16 @@ public class LookasideChannelLbTest {
|
||||||
assertThat(localityEndpointsMappingCaptor.getValue()).containsExactly(
|
assertThat(localityEndpointsMappingCaptor.getValue()).containsExactly(
|
||||||
locality1, localityInfo1, locality2, localityInfo2).inOrder();
|
locality1, localityInfo1, locality2, localityInfo2).inOrder();
|
||||||
|
|
||||||
verify(adsStreamCallback, never()).onError();
|
verify(lookasideChannelCallback, never()).onError();
|
||||||
|
|
||||||
lookasideChannelLb.shutdown();
|
lookasideChannelLb.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void verifyRpcErrorPropagation() {
|
public void verifyRpcErrorPropagation() {
|
||||||
verify(adsStreamCallback, never()).onError();
|
verify(lookasideChannelCallback, never()).onError();
|
||||||
serverResponseWriter.onError(new RuntimeException());
|
serverResponseWriter.onError(new RuntimeException());
|
||||||
verify(adsStreamCallback).onError();
|
verify(lookasideChannelCallback).onError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -397,8 +398,8 @@ public class LookasideChannelLbTest {
|
||||||
// Simulates a syntactically incorrect EDS response.
|
// Simulates a syntactically incorrect EDS response.
|
||||||
serverResponseWriter.onNext(DiscoveryResponse.getDefaultInstance());
|
serverResponseWriter.onNext(DiscoveryResponse.getDefaultInstance());
|
||||||
verify(loadReportClient, never()).startLoadReporting(any(LoadReportCallback.class));
|
verify(loadReportClient, never()).startLoadReporting(any(LoadReportCallback.class));
|
||||||
verify(adsStreamCallback, never()).onWorking();
|
verify(lookasideChannelCallback, never()).onWorking();
|
||||||
verify(adsStreamCallback, never()).onError();
|
verify(lookasideChannelCallback, never()).onError();
|
||||||
|
|
||||||
// Simulate a syntactically correct EDS response.
|
// Simulate a syntactically correct EDS response.
|
||||||
DiscoveryResponse edsResponse =
|
DiscoveryResponse edsResponse =
|
||||||
|
|
@ -408,7 +409,7 @@ public class LookasideChannelLbTest {
|
||||||
.build();
|
.build();
|
||||||
serverResponseWriter.onNext(edsResponse);
|
serverResponseWriter.onNext(edsResponse);
|
||||||
|
|
||||||
verify(adsStreamCallback).onWorking();
|
verify(lookasideChannelCallback).onWorking();
|
||||||
|
|
||||||
ArgumentCaptor<LoadReportCallback> lrsCallbackCaptor = ArgumentCaptor.forClass(null);
|
ArgumentCaptor<LoadReportCallback> lrsCallbackCaptor = ArgumentCaptor.forClass(null);
|
||||||
verify(loadReportClient).startLoadReporting(lrsCallbackCaptor.capture());
|
verify(loadReportClient).startLoadReporting(lrsCallbackCaptor.capture());
|
||||||
|
|
@ -417,13 +418,15 @@ public class LookasideChannelLbTest {
|
||||||
|
|
||||||
// Simulate another EDS response from the same remote balancer.
|
// Simulate another EDS response from the same remote balancer.
|
||||||
serverResponseWriter.onNext(edsResponse);
|
serverResponseWriter.onNext(edsResponse);
|
||||||
verifyNoMoreInteractions(adsStreamCallback, loadReportClient);
|
verifyNoMoreInteractions(lookasideChannelCallback, loadReportClient);
|
||||||
|
|
||||||
// Simulate an EDS error response.
|
// Simulate an EDS error response.
|
||||||
serverResponseWriter.onError(Status.ABORTED.asException());
|
serverResponseWriter.onError(Status.ABORTED.asException());
|
||||||
verify(adsStreamCallback).onError();
|
verify(lookasideChannelCallback).onError();
|
||||||
|
|
||||||
verifyNoMoreInteractions(adsStreamCallback, loadReportClient);
|
verifyNoMoreInteractions(lookasideChannelCallback, loadReportClient);
|
||||||
verify(localityStore, times(1)).updateOobMetricsReportInterval(anyLong()); // only once
|
verify(localityStore, times(1)).updateOobMetricsReportInterval(anyLong()); // only once
|
||||||
|
|
||||||
|
lookasideChannelLb.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ import io.grpc.LoadBalancer.ResolvedAddresses;
|
||||||
import io.grpc.LoadBalancer.SubchannelPicker;
|
import io.grpc.LoadBalancer.SubchannelPicker;
|
||||||
import io.grpc.LoadBalancerRegistry;
|
import io.grpc.LoadBalancerRegistry;
|
||||||
import io.grpc.internal.JsonParser;
|
import io.grpc.internal.JsonParser;
|
||||||
|
import io.grpc.xds.LookasideChannelLb.LookasideChannelCallback;
|
||||||
import io.grpc.xds.LookasideLb.LookasideChannelLbFactory;
|
import io.grpc.xds.LookasideLb.LookasideChannelLbFactory;
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -55,7 +55,7 @@ public class LookasideLbTest {
|
||||||
new LookasideChannelLbFactory() {
|
new LookasideChannelLbFactory() {
|
||||||
@Override
|
@Override
|
||||||
public LoadBalancer newLoadBalancer(
|
public LoadBalancer newLoadBalancer(
|
||||||
Helper helper, AdsStreamCallback adsCallback, String balancerName) {
|
Helper helper, LookasideChannelCallback lookasideChannelCallback, String balancerName) {
|
||||||
// just return a mock and record helper and balancer.
|
// just return a mock and record helper and balancer.
|
||||||
helpers.add(helper);
|
helpers.add(helper);
|
||||||
LoadBalancer balancer = mock(LoadBalancer.class);
|
LoadBalancer balancer = mock(LoadBalancer.class);
|
||||||
|
|
@ -65,7 +65,8 @@ public class LookasideLbTest {
|
||||||
};
|
};
|
||||||
|
|
||||||
private LoadBalancer lookasideLb = new LookasideLb(
|
private LoadBalancer lookasideLb = new LookasideLb(
|
||||||
helper, mock(AdsStreamCallback.class), lookasideChannelLbFactory, new LoadBalancerRegistry());
|
helper, mock(LookasideChannelCallback.class), lookasideChannelLbFactory,
|
||||||
|
new LoadBalancerRegistry());
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -163,7 +164,7 @@ public class LookasideLbTest {
|
||||||
public void handleResolvedAddress_createLbChannel()
|
public void handleResolvedAddress_createLbChannel()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
// Test balancer created with the default real LookasideChannelLbFactory
|
// Test balancer created with the default real LookasideChannelLbFactory
|
||||||
lookasideLb = new LookasideLb(helper, mock(AdsStreamCallback.class));
|
lookasideLb = new LookasideLb(helper, mock(LookasideChannelCallback.class));
|
||||||
String lbConfigRaw11 = "{'balancerName' : 'dns:///balancer1.example.com:8080'}"
|
String lbConfigRaw11 = "{'balancerName' : 'dns:///balancer1.example.com:8080'}"
|
||||||
.replace("'", "\"");
|
.replace("'", "\"");
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
|
|
||||||
|
|
@ -30,12 +30,9 @@ import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.protobuf.Any;
|
import com.google.protobuf.Any;
|
||||||
import com.google.protobuf.UInt32Value;
|
import com.google.protobuf.UInt32Value;
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment;
|
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment;
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
|
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryResponse;
|
import io.envoyproxy.envoy.api.v2.DiscoveryResponse;
|
||||||
import io.envoyproxy.envoy.api.v2.core.Address;
|
import io.envoyproxy.envoy.api.v2.core.Address;
|
||||||
|
|
@ -45,8 +42,6 @@ import io.envoyproxy.envoy.api.v2.endpoint.Endpoint;
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint;
|
import io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint;
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints;
|
import io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints;
|
||||||
import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase;
|
import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase;
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent;
|
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType;
|
|
||||||
import io.grpc.ChannelLogger;
|
import io.grpc.ChannelLogger;
|
||||||
import io.grpc.LoadBalancer;
|
import io.grpc.LoadBalancer;
|
||||||
import io.grpc.LoadBalancer.Helper;
|
import io.grpc.LoadBalancer.Helper;
|
||||||
|
|
@ -62,23 +57,19 @@ import io.grpc.internal.FakeClock;
|
||||||
import io.grpc.internal.testing.StreamRecorder;
|
import io.grpc.internal.testing.StreamRecorder;
|
||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import io.grpc.testing.GrpcCleanupRule;
|
import io.grpc.testing.GrpcCleanupRule;
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
import io.grpc.xds.XdsComms2.AdsStreamCallback;
|
||||||
import io.grpc.xds.XdsComms.DropOverload;
|
|
||||||
import io.grpc.xds.XdsComms.LocalityInfo;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.Captor;
|
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link XdsComms}.
|
* Unit tests for {@link XdsComms2}.
|
||||||
*/
|
*/
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class XdsCommsTest {
|
public class XdsCommsTest {
|
||||||
|
|
@ -99,15 +90,11 @@ public class XdsCommsTest {
|
||||||
@Mock
|
@Mock
|
||||||
private AdsStreamCallback adsStreamCallback;
|
private AdsStreamCallback adsStreamCallback;
|
||||||
@Mock
|
@Mock
|
||||||
private LocalityStore localityStore;
|
|
||||||
@Mock
|
|
||||||
private BackoffPolicy.Provider backoffPolicyProvider;
|
private BackoffPolicy.Provider backoffPolicyProvider;
|
||||||
@Mock
|
@Mock
|
||||||
private BackoffPolicy backoffPolicy1;
|
private BackoffPolicy backoffPolicy1;
|
||||||
@Mock
|
@Mock
|
||||||
private BackoffPolicy backoffPolicy2;
|
private BackoffPolicy backoffPolicy2;
|
||||||
@Captor
|
|
||||||
private ArgumentCaptor<ImmutableMap<XdsLocality, LocalityInfo>> localityEndpointsMappingCaptor;
|
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
private final SynchronizationContext syncContext = new SynchronizationContext(
|
private final SynchronizationContext syncContext = new SynchronizationContext(
|
||||||
|
|
@ -123,7 +110,7 @@ public class XdsCommsTest {
|
||||||
private StreamObserver<DiscoveryResponse> responseWriter;
|
private StreamObserver<DiscoveryResponse> responseWriter;
|
||||||
|
|
||||||
private ManagedChannel channel;
|
private ManagedChannel channel;
|
||||||
private XdsComms xdsComms;
|
private XdsComms2 xdsComms;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
|
|
@ -195,14 +182,14 @@ public class XdsCommsTest {
|
||||||
doReturn(backoffPolicy1, backoffPolicy2).when(backoffPolicyProvider).get();
|
doReturn(backoffPolicy1, backoffPolicy2).when(backoffPolicyProvider).get();
|
||||||
doReturn(10L, 100L, 1000L).when(backoffPolicy1).nextBackoffNanos();
|
doReturn(10L, 100L, 1000L).when(backoffPolicy1).nextBackoffNanos();
|
||||||
doReturn(20L, 200L).when(backoffPolicy2).nextBackoffNanos();
|
doReturn(20L, 200L).when(backoffPolicy2).nextBackoffNanos();
|
||||||
xdsComms = new XdsComms(
|
xdsComms = new XdsComms2(
|
||||||
channel, helper, adsStreamCallback, localityStore, backoffPolicyProvider,
|
channel, helper, adsStreamCallback, backoffPolicyProvider,
|
||||||
fakeClock.getStopwatchSupplier());
|
fakeClock.getStopwatchSupplier());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shutdownLbRpc_verifyChannelNotShutdown() throws Exception {
|
public void shutdownLbRpc_verifyChannelNotShutdown() throws Exception {
|
||||||
xdsComms.shutdownLbRpc("shutdown msg1");
|
xdsComms.shutdownLbRpc();
|
||||||
assertTrue(streamRecorder.awaitCompletion(1, TimeUnit.SECONDS));
|
assertTrue(streamRecorder.awaitCompletion(1, TimeUnit.SECONDS));
|
||||||
assertEquals(Status.Code.CANCELLED, Status.fromThrowable(streamRecorder.getError()).getCode());
|
assertEquals(Status.Code.CANCELLED, Status.fromThrowable(streamRecorder.getError()).getCode());
|
||||||
assertFalse(channel.isShutdown());
|
assertFalse(channel.isShutdown());
|
||||||
|
|
@ -210,13 +197,13 @@ public class XdsCommsTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cancel() throws Exception {
|
public void cancel() throws Exception {
|
||||||
xdsComms.shutdownLbRpc("cause1");
|
xdsComms.shutdownLbRpc();
|
||||||
assertTrue(streamRecorder.awaitCompletion(1, TimeUnit.SECONDS));
|
assertTrue(streamRecorder.awaitCompletion(1, TimeUnit.SECONDS));
|
||||||
assertEquals(Status.Code.CANCELLED, Status.fromThrowable(streamRecorder.getError()).getCode());
|
assertEquals(Status.Code.CANCELLED, Status.fromThrowable(streamRecorder.getError()).getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standardMode_sendEdsRequest_getEdsResponse_withNoDrop() {
|
public void handleEdsResponse() {
|
||||||
assertThat(streamRecorder.getValues()).hasSize(1);
|
assertThat(streamRecorder.getValues()).hasSize(1);
|
||||||
DiscoveryRequest request = streamRecorder.getValues().get(0);
|
DiscoveryRequest request = streamRecorder.getValues().get(0);
|
||||||
assertThat(request.getTypeUrl()).isEqualTo(EDS_TYPE_URL);
|
assertThat(request.getTypeUrl()).isEqualTo(EDS_TYPE_URL);
|
||||||
|
|
@ -266,8 +253,7 @@ public class XdsCommsTest {
|
||||||
.setAddress("addr31").setPortValue(31))))
|
.setAddress("addr31").setPortValue(31))))
|
||||||
.setLoadBalancingWeight(UInt32Value.of(31))
|
.setLoadBalancingWeight(UInt32Value.of(31))
|
||||||
.build();
|
.build();
|
||||||
DiscoveryResponse edsResponse = DiscoveryResponse.newBuilder()
|
ClusterLoadAssignment clusterLoadAssignment = ClusterLoadAssignment.newBuilder()
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.newBuilder()
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
||||||
.setLocality(localityProto1)
|
.setLocality(localityProto1)
|
||||||
.addLbEndpoints(endpoint11)
|
.addLbEndpoints(endpoint11)
|
||||||
|
|
@ -282,37 +268,16 @@ public class XdsCommsTest {
|
||||||
.setLocality(localityProto3)
|
.setLocality(localityProto3)
|
||||||
.addLbEndpoints(endpoint3)
|
.addLbEndpoints(endpoint3)
|
||||||
.setLoadBalancingWeight(UInt32Value.of(0)))
|
.setLoadBalancingWeight(UInt32Value.of(0)))
|
||||||
.build()))
|
.build();
|
||||||
|
DiscoveryResponse edsResponse = DiscoveryResponse.newBuilder()
|
||||||
|
.addResources(Any.pack(clusterLoadAssignment))
|
||||||
.setTypeUrl(EDS_TYPE_URL)
|
.setTypeUrl(EDS_TYPE_URL)
|
||||||
.build();
|
.build();
|
||||||
responseWriter.onNext(edsResponse);
|
responseWriter.onNext(edsResponse);
|
||||||
|
|
||||||
verify(adsStreamCallback).onWorking();
|
verify(adsStreamCallback).onEdsResponse(clusterLoadAssignment);
|
||||||
|
|
||||||
XdsLocality locality1 = XdsLocality.fromLocalityProto(localityProto1);
|
ClusterLoadAssignment clusterLoadAssignment2 = ClusterLoadAssignment.newBuilder()
|
||||||
LocalityInfo localityInfo1 = new LocalityInfo(
|
|
||||||
ImmutableList.of(
|
|
||||||
new XdsComms.LbEndpoint(endpoint11),
|
|
||||||
new XdsComms.LbEndpoint(endpoint12)),
|
|
||||||
1,
|
|
||||||
0);
|
|
||||||
LocalityInfo localityInfo2 = new LocalityInfo(
|
|
||||||
ImmutableList.of(
|
|
||||||
new XdsComms.LbEndpoint(endpoint21),
|
|
||||||
new XdsComms.LbEndpoint(endpoint22)),
|
|
||||||
2,
|
|
||||||
0);
|
|
||||||
XdsLocality locality2 = XdsLocality.fromLocalityProto(localityProto2);
|
|
||||||
|
|
||||||
InOrder inOrder = inOrder(localityStore);
|
|
||||||
inOrder.verify(localityStore).updateDropPercentage(ImmutableList.<DropOverload>of());
|
|
||||||
inOrder.verify(localityStore).updateLocalityStore(localityEndpointsMappingCaptor.capture());
|
|
||||||
assertThat(localityEndpointsMappingCaptor.getValue()).containsExactly(
|
|
||||||
locality1, localityInfo1, locality2, localityInfo2).inOrder();
|
|
||||||
|
|
||||||
|
|
||||||
edsResponse = DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.newBuilder()
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
||||||
.setLocality(localityProto2)
|
.setLocality(localityProto2)
|
||||||
.addLbEndpoints(endpoint21)
|
.addLbEndpoints(endpoint21)
|
||||||
|
|
@ -323,155 +288,17 @@ public class XdsCommsTest {
|
||||||
.addLbEndpoints(endpoint11)
|
.addLbEndpoints(endpoint11)
|
||||||
.addLbEndpoints(endpoint12)
|
.addLbEndpoints(endpoint12)
|
||||||
.setLoadBalancingWeight(UInt32Value.of(1)))
|
.setLoadBalancingWeight(UInt32Value.of(1)))
|
||||||
.build()))
|
.build();
|
||||||
|
edsResponse = DiscoveryResponse.newBuilder()
|
||||||
|
.addResources(Any.pack(clusterLoadAssignment2))
|
||||||
.setTypeUrl(EDS_TYPE_URL)
|
.setTypeUrl(EDS_TYPE_URL)
|
||||||
.build();
|
.build();
|
||||||
responseWriter.onNext(edsResponse);
|
responseWriter.onNext(edsResponse);
|
||||||
|
|
||||||
verify(adsStreamCallback, times(1)).onWorking();
|
verify(adsStreamCallback).onEdsResponse(clusterLoadAssignment2);
|
||||||
verifyNoMoreInteractions(adsStreamCallback);
|
verifyNoMoreInteractions(adsStreamCallback);
|
||||||
inOrder.verify(localityStore).updateDropPercentage(ImmutableList.<DropOverload>of());
|
|
||||||
inOrder.verify(localityStore).updateLocalityStore(localityEndpointsMappingCaptor.capture());
|
|
||||||
assertThat(localityEndpointsMappingCaptor.getValue()).containsExactly(
|
|
||||||
locality2, localityInfo2, locality1, localityInfo1).inOrder();
|
|
||||||
|
|
||||||
xdsComms.shutdownLbRpc("End test");
|
xdsComms.shutdownLbRpc();
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void standardMode_sendEdsRequest_getEdsResponse_withDrops() {
|
|
||||||
Locality localityProto1 = Locality.newBuilder()
|
|
||||||
.setRegion("region1").setZone("zone1").setSubZone("subzone1").build();
|
|
||||||
LbEndpoint endpoint11 = LbEndpoint.newBuilder()
|
|
||||||
.setEndpoint(Endpoint.newBuilder()
|
|
||||||
.setAddress(Address.newBuilder()
|
|
||||||
.setSocketAddress(SocketAddress.newBuilder()
|
|
||||||
.setAddress("addr11").setPortValue(11))))
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(11))
|
|
||||||
.build();
|
|
||||||
Locality localityProto2 = Locality.newBuilder()
|
|
||||||
.setRegion("region2").setZone("zone2").setSubZone("subzone2").build();
|
|
||||||
LbEndpoint endpoint21 = LbEndpoint.newBuilder()
|
|
||||||
.setEndpoint(Endpoint.newBuilder()
|
|
||||||
.setAddress(Address.newBuilder()
|
|
||||||
.setSocketAddress(SocketAddress.newBuilder()
|
|
||||||
.setAddress("addr21").setPortValue(21))))
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(21))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
DiscoveryResponse edsResponseWithDrops = DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.newBuilder()
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
|
||||||
.setLocality(localityProto2)
|
|
||||||
.addLbEndpoints(endpoint21)
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(2)))
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
|
||||||
.setLocality(localityProto1)
|
|
||||||
.addLbEndpoints(endpoint11)
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(1)))
|
|
||||||
.setPolicy(Policy.newBuilder()
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("throttle")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(123).setDenominator(DenominatorType.MILLION).build())
|
|
||||||
.build())
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("lb")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(456).setDenominator(DenominatorType.TEN_THOUSAND).build())
|
|
||||||
.build())
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("fake_category")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(78).setDenominator(DenominatorType.HUNDRED).build())
|
|
||||||
.build())
|
|
||||||
.build())
|
|
||||||
.build()))
|
|
||||||
.setTypeUrl(EDS_TYPE_URL)
|
|
||||||
.build();
|
|
||||||
responseWriter.onNext(edsResponseWithDrops);
|
|
||||||
|
|
||||||
verify(adsStreamCallback).onWorking();
|
|
||||||
verifyNoMoreInteractions(adsStreamCallback);
|
|
||||||
InOrder inOrder = inOrder(localityStore);
|
|
||||||
inOrder.verify(localityStore).updateDropPercentage(ImmutableList.of(
|
|
||||||
new DropOverload("throttle", 123),
|
|
||||||
new DropOverload("lb", 456_00),
|
|
||||||
new DropOverload("fake_category", 78_00_00)));
|
|
||||||
inOrder.verify(localityStore).updateLocalityStore(localityEndpointsMappingCaptor.capture());
|
|
||||||
|
|
||||||
XdsLocality locality1 = XdsLocality.fromLocalityProto(localityProto1);
|
|
||||||
LocalityInfo localityInfo1 = new LocalityInfo(
|
|
||||||
ImmutableList.of(new XdsComms.LbEndpoint(endpoint11)), 1, 0);
|
|
||||||
LocalityInfo localityInfo2 = new LocalityInfo(
|
|
||||||
ImmutableList.of(new XdsComms.LbEndpoint(endpoint21)), 2, 0);
|
|
||||||
XdsLocality locality2 = XdsLocality.fromLocalityProto(localityProto2);
|
|
||||||
assertThat(localityEndpointsMappingCaptor.getValue()).containsExactly(
|
|
||||||
locality2, localityInfo2, locality1, localityInfo1).inOrder();
|
|
||||||
|
|
||||||
DiscoveryResponse edsResponseWithAllDrops = DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.newBuilder()
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
|
||||||
.setLocality(localityProto2)
|
|
||||||
.addLbEndpoints(endpoint21)
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(2)))
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
|
||||||
.setLocality(localityProto1)
|
|
||||||
.addLbEndpoints(endpoint11)
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(1)))
|
|
||||||
.setPolicy(Policy.newBuilder()
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("throttle")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(123).setDenominator(DenominatorType.MILLION).build())
|
|
||||||
.build())
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("lb")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(456).setDenominator(DenominatorType.TEN_THOUSAND).build())
|
|
||||||
.build())
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("fake_category")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(789).setDenominator(DenominatorType.HUNDRED).build())
|
|
||||||
.build())
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("fake_category_2")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(78).setDenominator(DenominatorType.HUNDRED).build())
|
|
||||||
.build())
|
|
||||||
.build())
|
|
||||||
.build()))
|
|
||||||
.setTypeUrl(EDS_TYPE_URL)
|
|
||||||
.build();
|
|
||||||
responseWriter.onNext(edsResponseWithAllDrops);
|
|
||||||
|
|
||||||
verify(adsStreamCallback, times(1)).onWorking();
|
|
||||||
verify(adsStreamCallback).onAllDrop();
|
|
||||||
verify(adsStreamCallback, never()).onError();
|
|
||||||
inOrder.verify(localityStore).updateDropPercentage(ImmutableList.of(
|
|
||||||
new DropOverload("throttle", 123),
|
|
||||||
new DropOverload("lb", 456_00),
|
|
||||||
new DropOverload("fake_category", 1000_000)));
|
|
||||||
inOrder.verify(localityStore).updateLocalityStore(localityEndpointsMappingCaptor.capture());
|
|
||||||
assertThat(localityEndpointsMappingCaptor.getValue()).containsExactly(
|
|
||||||
locality2, localityInfo2, locality1, localityInfo1).inOrder();
|
|
||||||
|
|
||||||
xdsComms.shutdownLbRpc("End test");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -500,7 +327,7 @@ public class XdsCommsTest {
|
||||||
* Verify retry is scheduled. Verify the 6th PRC starts after backoff.
|
* Verify retry is scheduled. Verify the 6th PRC starts after backoff.
|
||||||
*
|
*
|
||||||
* <p>The 6th RPC fails with response observer onError() without receiving initial response.
|
* <p>The 6th RPC fails with response observer onError() without receiving initial response.
|
||||||
* Verify retry is scheduled. Call {@link XdsComms#shutdownLbRpc(String)}, verify retry timer is
|
* Verify retry is scheduled. Call {@link XdsComms2#shutdownLbRpc()}, verify retry timer is
|
||||||
* cancelled.
|
* cancelled.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -653,7 +480,7 @@ public class XdsCommsTest {
|
||||||
assertEquals(1, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
|
assertEquals(1, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
|
||||||
|
|
||||||
// Shutdown cancels retry
|
// Shutdown cancels retry
|
||||||
xdsComms.shutdownLbRpc("shutdown");
|
xdsComms.shutdownLbRpc();
|
||||||
assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
|
assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -666,6 +493,6 @@ public class XdsCommsTest {
|
||||||
xdsComms.refreshAdsStream();
|
xdsComms.refreshAdsStream();
|
||||||
assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
|
assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
|
||||||
|
|
||||||
xdsComms.shutdownLbRpc("End test");
|
xdsComms.shutdownLbRpc();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,156 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryResponse;
|
|
||||||
import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase;
|
|
||||||
import io.grpc.Attributes;
|
|
||||||
import io.grpc.ChannelLogger;
|
|
||||||
import io.grpc.EquivalentAddressGroup;
|
|
||||||
import io.grpc.LoadBalancer.Helper;
|
|
||||||
import io.grpc.ManagedChannel;
|
|
||||||
import io.grpc.SynchronizationContext;
|
|
||||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
|
||||||
import io.grpc.inprocess.InProcessServerBuilder;
|
|
||||||
import io.grpc.internal.BackoffPolicy;
|
|
||||||
import io.grpc.internal.FakeClock;
|
|
||||||
import io.grpc.internal.testing.StreamRecorder;
|
|
||||||
import io.grpc.stub.StreamObserver;
|
|
||||||
import io.grpc.testing.GrpcCleanupRule;
|
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for {@link XdsLbState}.
|
|
||||||
*/
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class XdsLbStateTest {
|
|
||||||
private static final String BALANCER_NAME = "balancerName";
|
|
||||||
@Rule
|
|
||||||
public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule();
|
|
||||||
@Mock
|
|
||||||
private Helper helper;
|
|
||||||
@Mock
|
|
||||||
private AdsStreamCallback adsStreamCallback;
|
|
||||||
@Mock
|
|
||||||
private LocalityStore localityStore;
|
|
||||||
@Mock
|
|
||||||
private BackoffPolicy.Provider backoffPolicyProvider;
|
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
|
||||||
|
|
||||||
private final SynchronizationContext syncContext = new SynchronizationContext(
|
|
||||||
new Thread.UncaughtExceptionHandler() {
|
|
||||||
@Override
|
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
private final StreamRecorder<DiscoveryRequest> streamRecorder = StreamRecorder.create();
|
|
||||||
private ManagedChannel channel;
|
|
||||||
private XdsLbState xdsLbState;
|
|
||||||
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
doReturn(syncContext).when(helper).getSynchronizationContext();
|
|
||||||
doReturn(fakeClock.getScheduledExecutorService()).when(helper).getScheduledExecutorService();
|
|
||||||
doReturn("fake_authority").when(helper).getAuthority();
|
|
||||||
doReturn(mock(ChannelLogger.class)).when(helper).getChannelLogger();
|
|
||||||
|
|
||||||
String serverName = InProcessServerBuilder.generateName();
|
|
||||||
|
|
||||||
AggregatedDiscoveryServiceImplBase serviceImpl = new AggregatedDiscoveryServiceImplBase() {
|
|
||||||
@Override
|
|
||||||
public StreamObserver<DiscoveryRequest> streamAggregatedResources(
|
|
||||||
final StreamObserver<DiscoveryResponse> responseObserver) {
|
|
||||||
return new StreamObserver<DiscoveryRequest>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(DiscoveryRequest value) {
|
|
||||||
streamRecorder.onNext(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable t) {
|
|
||||||
streamRecorder.onError(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
streamRecorder.onCompleted();
|
|
||||||
responseObserver.onCompleted();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cleanupRule.register(
|
|
||||||
InProcessServerBuilder
|
|
||||||
.forName(serverName)
|
|
||||||
.addService(serviceImpl)
|
|
||||||
.directExecutor()
|
|
||||||
.build()
|
|
||||||
.start());
|
|
||||||
channel =
|
|
||||||
cleanupRule.register(InProcessChannelBuilder.forName(serverName).directExecutor().build());
|
|
||||||
doReturn(channel).when(helper).createResolvingOobChannel(BALANCER_NAME);
|
|
||||||
|
|
||||||
xdsLbState = new XdsLbState(
|
|
||||||
BALANCER_NAME, null, helper, localityStore, channel, adsStreamCallback,
|
|
||||||
backoffPolicyProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shutdownResetsLocalityStore() {
|
|
||||||
xdsLbState.shutdownAndReleaseChannel("Client shutdown");
|
|
||||||
verify(localityStore).reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shutdownDoesNotTearDownChannel() {
|
|
||||||
ManagedChannel lbChannel = xdsLbState.shutdownAndReleaseChannel("Client shutdown");
|
|
||||||
assertThat(lbChannel).isSameInstanceAs(channel);
|
|
||||||
assertThat(channel.isShutdown()).isFalse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void handleResolvedAddressGroupsTriggerEds() throws Exception {
|
|
||||||
xdsLbState.handleResolvedAddressGroups(
|
|
||||||
ImmutableList.<EquivalentAddressGroup>of(), Attributes.EMPTY);
|
|
||||||
|
|
||||||
assertThat(streamRecorder.firstValue().get().getTypeUrl())
|
|
||||||
.isEqualTo("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment");
|
|
||||||
|
|
||||||
xdsLbState.shutdownAndReleaseChannel("End test");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -40,7 +40,7 @@ import io.grpc.LoadBalancer.SubchannelPicker;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.SynchronizationContext;
|
import io.grpc.SynchronizationContext;
|
||||||
import io.grpc.internal.FakeClock;
|
import io.grpc.internal.FakeClock;
|
||||||
import io.grpc.xds.XdsComms.AdsStreamCallback;
|
import io.grpc.xds.LookasideChannelLb.LookasideChannelCallback;
|
||||||
import io.grpc.xds.XdsLoadBalancer2.LookasideLbFactory;
|
import io.grpc.xds.XdsLoadBalancer2.LookasideLbFactory;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -75,7 +75,7 @@ public class XdsLoadBalancer2Test {
|
||||||
@Mock
|
@Mock
|
||||||
private Helper helper;
|
private Helper helper;
|
||||||
private LoadBalancer xdsLoadBalancer;
|
private LoadBalancer xdsLoadBalancer;
|
||||||
private AdsStreamCallback adsCallback;
|
private LookasideChannelCallback lookasideChannelCallback;
|
||||||
|
|
||||||
private Helper lookasideLbHelper;
|
private Helper lookasideLbHelper;
|
||||||
private final List<LoadBalancer> lookasideLbs = new ArrayList<>();
|
private final List<LoadBalancer> lookasideLbs = new ArrayList<>();
|
||||||
|
|
@ -89,10 +89,11 @@ public class XdsLoadBalancer2Test {
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
LookasideLbFactory lookasideLbFactory = new LookasideLbFactory() {
|
LookasideLbFactory lookasideLbFactory = new LookasideLbFactory() {
|
||||||
@Override
|
@Override
|
||||||
public LoadBalancer newLoadBalancer(Helper helper, AdsStreamCallback adsCallback) {
|
public LoadBalancer newLoadBalancer(
|
||||||
|
Helper helper, LookasideChannelCallback lookasideChannelCallback) {
|
||||||
// just return a mock and record the input and output
|
// just return a mock and record the input and output
|
||||||
lookasideLbHelper = helper;
|
lookasideLbHelper = helper;
|
||||||
XdsLoadBalancer2Test.this.adsCallback = adsCallback;
|
XdsLoadBalancer2Test.this.lookasideChannelCallback = lookasideChannelCallback;
|
||||||
LoadBalancer lookasideLb = mock(LoadBalancer.class);
|
LoadBalancer lookasideLb = mock(LoadBalancer.class);
|
||||||
lookasideLbs.add(lookasideLb);
|
lookasideLbs.add(lookasideLb);
|
||||||
return lookasideLb;
|
return lookasideLb;
|
||||||
|
|
@ -141,7 +142,7 @@ public class XdsLoadBalancer2Test {
|
||||||
public void timeoutAtStartup_expectUseFallback_thenBackendReady_expectExitFallback() {
|
public void timeoutAtStartup_expectUseFallback_thenBackendReady_expectExitFallback() {
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
fakeClock.forwardTime(9, TimeUnit.SECONDS);
|
fakeClock.forwardTime(9, TimeUnit.SECONDS);
|
||||||
adsCallback.onWorking();
|
lookasideChannelCallback.onWorking();
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
fakeClock.forwardTime(1, TimeUnit.SECONDS);
|
fakeClock.forwardTime(1, TimeUnit.SECONDS);
|
||||||
verifyInFallbackMode();
|
verifyInFallbackMode();
|
||||||
|
|
@ -161,7 +162,7 @@ public class XdsLoadBalancer2Test {
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
||||||
|
|
||||||
adsCallback.onWorking();
|
lookasideChannelCallback.onWorking();
|
||||||
SubchannelPicker subchannelPicker = mock(SubchannelPicker.class);
|
SubchannelPicker subchannelPicker = mock(SubchannelPicker.class);
|
||||||
lookasideLbHelper.updateBalancingState(READY, subchannelPicker);
|
lookasideLbHelper.updateBalancingState(READY, subchannelPicker);
|
||||||
verify(helper).updateBalancingState(READY, subchannelPicker);
|
verify(helper).updateBalancingState(READY, subchannelPicker);
|
||||||
|
|
@ -176,7 +177,7 @@ public class XdsLoadBalancer2Test {
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
||||||
|
|
||||||
adsCallback.onAllDrop();
|
lookasideChannelCallback.onAllDrop();
|
||||||
assertThat(fakeClock.getPendingTasks()).isEmpty();
|
assertThat(fakeClock.getPendingTasks()).isEmpty();
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
|
|
||||||
|
|
@ -188,7 +189,7 @@ public class XdsLoadBalancer2Test {
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
||||||
|
|
||||||
adsCallback.onError();
|
lookasideChannelCallback.onError();
|
||||||
verifyInFallbackMode();
|
verifyInFallbackMode();
|
||||||
|
|
||||||
assertThat(fallbackLbs).hasSize(1);
|
assertThat(fallbackLbs).hasSize(1);
|
||||||
|
|
@ -198,8 +199,8 @@ public class XdsLoadBalancer2Test {
|
||||||
public void lookasideChannelSeeingEdsResponseThenFailsBeforeTimeoutAtStartup() {
|
public void lookasideChannelSeeingEdsResponseThenFailsBeforeTimeoutAtStartup() {
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
||||||
adsCallback.onWorking();
|
lookasideChannelCallback.onWorking();
|
||||||
adsCallback.onError();
|
lookasideChannelCallback.onError();
|
||||||
verifyNotInFallbackMode();
|
verifyNotInFallbackMode();
|
||||||
|
|
||||||
fakeClock.forwardTime(10, TimeUnit.SECONDS);
|
fakeClock.forwardTime(10, TimeUnit.SECONDS);
|
||||||
|
|
@ -220,7 +221,7 @@ public class XdsLoadBalancer2Test {
|
||||||
.build();
|
.build();
|
||||||
xdsLoadBalancer.handleResolvedAddresses(resolvedAddresses);
|
xdsLoadBalancer.handleResolvedAddresses(resolvedAddresses);
|
||||||
|
|
||||||
adsCallback.onError();
|
lookasideChannelCallback.onError();
|
||||||
LoadBalancer fallbackLb = Iterables.getLast(fallbackLbs);
|
LoadBalancer fallbackLb = Iterables.getLast(fallbackLbs);
|
||||||
verify(fallbackLb).handleResolvedAddresses(same(resolvedAddresses));
|
verify(fallbackLb).handleResolvedAddresses(same(resolvedAddresses));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,812 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static io.grpc.ConnectivityState.CONNECTING;
|
|
||||||
import static io.grpc.ConnectivityState.READY;
|
|
||||||
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
|
|
||||||
import static io.grpc.LoadBalancer.ATTR_LOAD_BALANCING_CONFIG;
|
|
||||||
import static io.grpc.xds.XdsSubchannelPickers.BUFFER_PICKER;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.mockito.AdditionalAnswers.delegatesTo;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.ArgumentMatchers.isA;
|
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
|
||||||
import static org.mockito.Mockito.doReturn;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
||||||
|
|
||||||
import com.google.protobuf.Any;
|
|
||||||
import com.google.protobuf.UInt32Value;
|
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment;
|
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryResponse;
|
|
||||||
import io.envoyproxy.envoy.api.v2.core.Address;
|
|
||||||
import io.envoyproxy.envoy.api.v2.core.Locality;
|
|
||||||
import io.envoyproxy.envoy.api.v2.core.SocketAddress;
|
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.Endpoint;
|
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.LbEndpoint;
|
|
||||||
import io.envoyproxy.envoy.api.v2.endpoint.LocalityLbEndpoints;
|
|
||||||
import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase;
|
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent;
|
|
||||||
import io.envoyproxy.envoy.type.FractionalPercent.DenominatorType;
|
|
||||||
import io.grpc.Attributes;
|
|
||||||
import io.grpc.CallOptions;
|
|
||||||
import io.grpc.ChannelLogger;
|
|
||||||
import io.grpc.ConnectivityState;
|
|
||||||
import io.grpc.EquivalentAddressGroup;
|
|
||||||
import io.grpc.LoadBalancer;
|
|
||||||
import io.grpc.LoadBalancer.Helper;
|
|
||||||
import io.grpc.LoadBalancer.PickSubchannelArgs;
|
|
||||||
import io.grpc.LoadBalancer.ResolvedAddresses;
|
|
||||||
import io.grpc.LoadBalancer.SubchannelPicker;
|
|
||||||
import io.grpc.LoadBalancerProvider;
|
|
||||||
import io.grpc.LoadBalancerRegistry;
|
|
||||||
import io.grpc.ManagedChannel;
|
|
||||||
import io.grpc.MethodDescriptor;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.SynchronizationContext;
|
|
||||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
|
||||||
import io.grpc.inprocess.InProcessServerBuilder;
|
|
||||||
import io.grpc.internal.BackoffPolicy;
|
|
||||||
import io.grpc.internal.FakeClock;
|
|
||||||
import io.grpc.internal.FakeClock.TaskFilter;
|
|
||||||
import io.grpc.internal.JsonParser;
|
|
||||||
import io.grpc.internal.testing.StreamRecorder;
|
|
||||||
import io.grpc.stub.StreamObserver;
|
|
||||||
import io.grpc.testing.GrpcCleanupRule;
|
|
||||||
import io.grpc.xds.XdsSubchannelPickers.ErrorPicker;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.ArgumentMatchers;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for {@link XdsLoadBalancer}.
|
|
||||||
*/
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class XdsLoadBalancerTest {
|
|
||||||
@Rule
|
|
||||||
public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule();
|
|
||||||
@Mock
|
|
||||||
private Helper helper;
|
|
||||||
@Mock
|
|
||||||
private LoadBalancer fallbackBalancer1;
|
|
||||||
@Mock
|
|
||||||
private LoadBalancer fakeBalancer2;
|
|
||||||
@Mock
|
|
||||||
private BackoffPolicy.Provider backoffPolicyProvider;
|
|
||||||
private XdsLoadBalancer lb;
|
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
|
||||||
private final StreamRecorder<DiscoveryRequest> streamRecorder = StreamRecorder.create();
|
|
||||||
|
|
||||||
private final LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry();
|
|
||||||
|
|
||||||
private Helper fallbackHelper1;
|
|
||||||
|
|
||||||
private final LoadBalancerProvider lbProvider1 = new LoadBalancerProvider() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPolicyName() {
|
|
||||||
return "fallback_1";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
|
||||||
fallbackHelper1 = helper;
|
|
||||||
return fallbackBalancer1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final LoadBalancerProvider lbProvider2 = new LoadBalancerProvider() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPolicyName() {
|
|
||||||
return "supported_2";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
|
||||||
return fakeBalancer2;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Locality localityProto1 = Locality.newBuilder()
|
|
||||||
.setRegion("region1").setZone("zone1").setSubZone("subzone1").build();
|
|
||||||
private final LbEndpoint endpoint11 = LbEndpoint.newBuilder()
|
|
||||||
.setEndpoint(Endpoint.newBuilder()
|
|
||||||
.setAddress(Address.newBuilder()
|
|
||||||
.setSocketAddress(SocketAddress.newBuilder()
|
|
||||||
.setAddress("addr11").setPortValue(11))))
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(11))
|
|
||||||
.build();
|
|
||||||
private final DiscoveryResponse edsResponse = DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.newBuilder()
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
|
||||||
.setLocality(localityProto1)
|
|
||||||
.addLbEndpoints(endpoint11)
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(1)))
|
|
||||||
.build()))
|
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private Helper childHelper;
|
|
||||||
@Mock
|
|
||||||
private LoadBalancer childBalancer;
|
|
||||||
|
|
||||||
private final LoadBalancerProvider roundRobin = new LoadBalancerProvider() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPolicyName() {
|
|
||||||
return "round_robin";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
|
||||||
childHelper = helper;
|
|
||||||
return childBalancer;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final SynchronizationContext syncContext = new SynchronizationContext(
|
|
||||||
new Thread.UncaughtExceptionHandler() {
|
|
||||||
@Override
|
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
private final TaskFilter fallbackTaskFilter = new TaskFilter() {
|
|
||||||
@Override
|
|
||||||
public boolean shouldAccept(Runnable runnable) {
|
|
||||||
return runnable.toString().contains("FallbackTask");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private ManagedChannel oobChannel1;
|
|
||||||
private ManagedChannel oobChannel2;
|
|
||||||
private ManagedChannel oobChannel3;
|
|
||||||
|
|
||||||
private StreamObserver<DiscoveryResponse> serverResponseWriter;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
lbRegistry.register(lbProvider1);
|
|
||||||
lbRegistry.register(lbProvider2);
|
|
||||||
lbRegistry.register(roundRobin);
|
|
||||||
lb = new XdsLoadBalancer(helper, lbRegistry, backoffPolicyProvider);
|
|
||||||
doReturn(syncContext).when(helper).getSynchronizationContext();
|
|
||||||
doReturn(fakeClock.getScheduledExecutorService()).when(helper).getScheduledExecutorService();
|
|
||||||
doReturn(mock(ChannelLogger.class)).when(helper).getChannelLogger();
|
|
||||||
doReturn("fake_authority").when(helper).getAuthority();
|
|
||||||
|
|
||||||
String serverName = InProcessServerBuilder.generateName();
|
|
||||||
|
|
||||||
AggregatedDiscoveryServiceImplBase serviceImpl = new AggregatedDiscoveryServiceImplBase() {
|
|
||||||
@Override
|
|
||||||
public StreamObserver<DiscoveryRequest> streamAggregatedResources(
|
|
||||||
final StreamObserver<DiscoveryResponse> responseObserver) {
|
|
||||||
serverResponseWriter = responseObserver;
|
|
||||||
|
|
||||||
return new StreamObserver<DiscoveryRequest>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(DiscoveryRequest value) {
|
|
||||||
streamRecorder.onNext(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable t) {
|
|
||||||
streamRecorder.onError(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
streamRecorder.onCompleted();
|
|
||||||
responseObserver.onCompleted();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cleanupRule.register(
|
|
||||||
InProcessServerBuilder
|
|
||||||
.forName(serverName)
|
|
||||||
.directExecutor()
|
|
||||||
.addService(serviceImpl)
|
|
||||||
.build()
|
|
||||||
.start());
|
|
||||||
|
|
||||||
InProcessChannelBuilder channelBuilder =
|
|
||||||
InProcessChannelBuilder.forName(serverName).directExecutor();
|
|
||||||
oobChannel1 = mock(
|
|
||||||
ManagedChannel.class,
|
|
||||||
delegatesTo(cleanupRule.register(channelBuilder.build())));
|
|
||||||
oobChannel2 = mock(
|
|
||||||
ManagedChannel.class,
|
|
||||||
delegatesTo(cleanupRule.register(channelBuilder.build())));
|
|
||||||
oobChannel3 = mock(
|
|
||||||
ManagedChannel.class,
|
|
||||||
delegatesTo(cleanupRule.register(channelBuilder.build())));
|
|
||||||
|
|
||||||
doReturn(oobChannel1).doReturn(oobChannel2).doReturn(oobChannel3)
|
|
||||||
.when(helper).createResolvingOobChannel(anyString());
|
|
||||||
|
|
||||||
// To write less tedious code for tests, allow fallbackBalancer to handle empty address list.
|
|
||||||
doReturn(true).when(fallbackBalancer1).canHandleEmptyAddressListFromNameResolution();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
lb.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void canHandleEmptyAddressListFromNameResolution() {
|
|
||||||
assertTrue(lb.canHandleEmptyAddressListFromNameResolution());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resolverEvent_standardModeToStandardMode() throws Exception {
|
|
||||||
String lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"unsupported\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
Attributes attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
XdsLbState xdsLbState1 = lb.getXdsLbStateForTest();
|
|
||||||
assertThat(xdsLbState1.childPolicy).isNull();
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
verify(oobChannel1)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
|
|
||||||
|
|
||||||
lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig2 = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig2).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
XdsLbState xdsLbState2 = lb.getXdsLbStateForTest();
|
|
||||||
assertThat(xdsLbState2.childPolicy).isNull();
|
|
||||||
assertThat(xdsLbState2).isSameInstanceAs(xdsLbState1);
|
|
||||||
|
|
||||||
// verify oobChannel is unchanged
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
// verify ADS stream is unchanged
|
|
||||||
verify(oobChannel1)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resolverEvent_standardModeToCustomMode() throws Exception {
|
|
||||||
String lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"unsupported\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
Attributes attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
verify(oobChannel1)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
|
|
||||||
lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"supported_2\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig2 = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig2).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(lb.getXdsLbStateForTest().childPolicy).isNotNull();
|
|
||||||
|
|
||||||
// verify oobChannel is unchanged
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
// verify ADS stream is reset
|
|
||||||
verify(oobChannel1, times(2))
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resolverEvent_customModeToStandardMode() throws Exception {
|
|
||||||
String lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"supported_2\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
Attributes attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
verify(oobChannel1)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
|
|
||||||
assertThat(lb.getXdsLbStateForTest().childPolicy).isNotNull();
|
|
||||||
|
|
||||||
lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"unsupported\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig2 = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig2).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(lb.getXdsLbStateForTest().childPolicy).isNull();
|
|
||||||
|
|
||||||
// verify oobChannel is unchanged
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
// verify ADS stream is reset
|
|
||||||
verify(oobChannel1, times(2))
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resolverEvent_customModeToCustomMode() throws Exception {
|
|
||||||
String lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"supported_2\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
Attributes attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(lb.getXdsLbStateForTest().childPolicy).isNotNull();
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
verify(oobChannel1)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
|
|
||||||
lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"fallback_1\" : {\"key\" : \"val\"}}, {\"unfallback_1\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig2 = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig2).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(lb.getXdsLbStateForTest().childPolicy).isNotNull();
|
|
||||||
// verify oobChannel is unchanged
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
// verify ADS stream is reset
|
|
||||||
verify(oobChannel1, times(2))
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resolverEvent_balancerNameChange() throws Exception {
|
|
||||||
String lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"unsupported\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
Attributes attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
verify(helper).createResolvingOobChannel(anyString());
|
|
||||||
verify(oobChannel1)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
|
|
||||||
lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8443\","
|
|
||||||
+ "\"childPolicy\" : [{\"fallback_1\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig2 = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig2).build();
|
|
||||||
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(lb.getXdsLbStateForTest().childPolicy).isNotNull();
|
|
||||||
|
|
||||||
// verify oobChannel is unchanged
|
|
||||||
verify(helper, times(2)).createResolvingOobChannel(anyString());
|
|
||||||
verify(oobChannel1)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
verify(oobChannel2)
|
|
||||||
.newCall(ArgumentMatchers.<MethodDescriptor<?, ?>>any(),
|
|
||||||
ArgumentMatchers.<CallOptions>any());
|
|
||||||
verifyNoMoreInteractions(oobChannel3);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resolutionErrorAtStartup() {
|
|
||||||
lb.handleNameResolutionError(Status.UNAVAILABLE);
|
|
||||||
|
|
||||||
assertNull(childHelper);
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
verify(helper).updateBalancingState(same(TRANSIENT_FAILURE), isA(ErrorPicker.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resolutionErrorAtFallback() throws Exception {
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallback1Attributes())
|
|
||||||
.build());
|
|
||||||
// let fallback timer expire
|
|
||||||
assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1);
|
|
||||||
ArgumentCaptor<ResolvedAddresses> captor = ArgumentCaptor.forClass(ResolvedAddresses.class);
|
|
||||||
verify(fallbackBalancer1).handleResolvedAddresses(captor.capture());
|
|
||||||
assertThat(captor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG))
|
|
||||||
.containsExactly("fallback_1_option", "yes");
|
|
||||||
|
|
||||||
Status status = Status.UNAVAILABLE.withDescription("resolution error");
|
|
||||||
lb.handleNameResolutionError(status);
|
|
||||||
verify(fallbackBalancer1).handleNameResolutionError(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallback_AdsNotWorkingYetTimerExpired() throws Exception {
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallback1Attributes())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertNull(childHelper);
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
|
|
||||||
assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1);
|
|
||||||
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).isEmpty();
|
|
||||||
assertNull(childHelper);
|
|
||||||
assertNotNull(fallbackHelper1);
|
|
||||||
ArgumentCaptor<ResolvedAddresses> captor = ArgumentCaptor.forClass(ResolvedAddresses.class);
|
|
||||||
verify(fallbackBalancer1).handleResolvedAddresses(captor.capture());
|
|
||||||
assertThat(captor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG))
|
|
||||||
.containsExactly("fallback_1_option", "yes");
|
|
||||||
|
|
||||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
|
||||||
fallbackHelper1.updateBalancingState(CONNECTING, picker);
|
|
||||||
verify(helper).updateBalancingState(CONNECTING, picker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void allDropCancelsFallbackTimer() throws Exception {
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallback1Attributes())
|
|
||||||
.build());
|
|
||||||
DiscoveryResponse edsResponse = DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.newBuilder()
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
|
||||||
.setLocality(localityProto1)
|
|
||||||
.addLbEndpoints(endpoint11)
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(1)))
|
|
||||||
.setPolicy(Policy.newBuilder()
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("throttle")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(100).setDenominator(DenominatorType.HUNDRED).build())
|
|
||||||
.build()))
|
|
||||||
.build()))
|
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
|
||||||
.build();
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).isEmpty();
|
|
||||||
assertNotNull(childHelper);
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
verify(fallbackBalancer1, never()).handleResolvedAddresses(any(ResolvedAddresses.class));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void allDropExitFallbackMode() throws Exception {
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallback1Attributes())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
// let the fallback timer expire
|
|
||||||
assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1);
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).isEmpty();
|
|
||||||
assertNull(childHelper);
|
|
||||||
assertNotNull(fallbackHelper1);
|
|
||||||
|
|
||||||
// receives EDS response with 100% drop
|
|
||||||
DiscoveryResponse edsResponse = DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.newBuilder()
|
|
||||||
.addEndpoints(LocalityLbEndpoints.newBuilder()
|
|
||||||
.setLocality(localityProto1)
|
|
||||||
.addLbEndpoints(endpoint11)
|
|
||||||
.setLoadBalancingWeight(UInt32Value.of(1)))
|
|
||||||
.setPolicy(Policy.newBuilder()
|
|
||||||
.addDropOverloads(
|
|
||||||
io.envoyproxy.envoy.api.v2.ClusterLoadAssignment.Policy.DropOverload
|
|
||||||
.newBuilder()
|
|
||||||
.setCategory("throttle")
|
|
||||||
.setDropPercentage(FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(100).setDenominator(DenominatorType.HUNDRED).build())
|
|
||||||
.build()))
|
|
||||||
.build()))
|
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
|
||||||
.build();
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
verify(fallbackBalancer1).shutdown();
|
|
||||||
assertNotNull(childHelper);
|
|
||||||
|
|
||||||
ArgumentCaptor<SubchannelPicker> subchannelPickerCaptor =
|
|
||||||
ArgumentCaptor.forClass(SubchannelPicker.class);
|
|
||||||
verify(helper).updateBalancingState(same(CONNECTING), subchannelPickerCaptor.capture());
|
|
||||||
assertThat(subchannelPickerCaptor.getValue().pickSubchannel(mock(PickSubchannelArgs.class))
|
|
||||||
.isDrop()).isTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallback_ErrorWithoutReceivingEdsResponse() throws Exception {
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallback1Attributes())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertNull(childHelper);
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).hasSize(1);
|
|
||||||
|
|
||||||
serverResponseWriter.onError(new Exception("fake error"));
|
|
||||||
|
|
||||||
// goes to fallback-at-startup mode immediately
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).isEmpty();
|
|
||||||
assertNull(childHelper);
|
|
||||||
assertNotNull(fallbackHelper1);
|
|
||||||
// verify fallback balancer is working
|
|
||||||
ArgumentCaptor<ResolvedAddresses> captor = ArgumentCaptor.forClass(ResolvedAddresses.class);
|
|
||||||
verify(fallbackBalancer1).handleResolvedAddresses(captor.capture());
|
|
||||||
assertThat(captor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG))
|
|
||||||
.containsExactly("fallback_1_option", "yes");
|
|
||||||
|
|
||||||
SubchannelPicker picker = mock(SubchannelPicker.class);
|
|
||||||
fallbackHelper1.updateBalancingState(CONNECTING, picker);
|
|
||||||
verify(helper).updateBalancingState(CONNECTING, picker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallback_EdsResponseReceivedThenErrorBeforeBackendReady() throws Exception {
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallback1Attributes())
|
|
||||||
.build());
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
assertNotNull(childHelper);
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
verify(helper).updateBalancingState(CONNECTING, BUFFER_PICKER);
|
|
||||||
|
|
||||||
serverResponseWriter.onError(new Exception("fake error"));
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).hasSize(1);
|
|
||||||
// verify fallback balancer is not started
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
verify(fallbackBalancer1, never()).handleResolvedAddresses(any(ResolvedAddresses.class));
|
|
||||||
|
|
||||||
SubchannelPicker picker1 = mock(SubchannelPicker.class);
|
|
||||||
childHelper.updateBalancingState(CONNECTING, picker1);
|
|
||||||
verify(helper, times(2)).updateBalancingState(CONNECTING, BUFFER_PICKER);
|
|
||||||
childHelper.updateBalancingState(TRANSIENT_FAILURE, picker1);
|
|
||||||
verify(helper).updateBalancingState(same(TRANSIENT_FAILURE), isA(ErrorPicker.class));
|
|
||||||
|
|
||||||
assertThat(fakeClock.forwardTime(10, TimeUnit.SECONDS)).isEqualTo(1);
|
|
||||||
// verify fallback balancer is working
|
|
||||||
ArgumentCaptor<ResolvedAddresses> captor = ArgumentCaptor.forClass(ResolvedAddresses.class);
|
|
||||||
assertNotNull(fallbackHelper1);
|
|
||||||
verify(fallbackBalancer1).handleResolvedAddresses(captor.capture());
|
|
||||||
assertThat(captor.getValue().getAttributes().get(ATTR_LOAD_BALANCING_CONFIG))
|
|
||||||
.containsExactly("fallback_1_option", "yes");
|
|
||||||
|
|
||||||
SubchannelPicker picker2 = mock(SubchannelPicker.class);
|
|
||||||
childHelper.updateBalancingState(CONNECTING, picker2);
|
|
||||||
// verify childHelper no more delegates updateBalancingState to parent helper
|
|
||||||
verify(helper, times(3)).updateBalancingState(
|
|
||||||
any(ConnectivityState.class), any(SubchannelPicker.class));
|
|
||||||
|
|
||||||
SubchannelPicker picker3 = mock(SubchannelPicker.class);
|
|
||||||
fallbackHelper1.updateBalancingState(CONNECTING, picker3);
|
|
||||||
verify(helper).updateBalancingState(CONNECTING, picker3);
|
|
||||||
|
|
||||||
SubchannelPicker picker4 = mock(SubchannelPicker.class);
|
|
||||||
childHelper.updateBalancingState(READY, picker4);
|
|
||||||
verify(fallbackBalancer1).shutdown();
|
|
||||||
verify(helper).updateBalancingState(same(READY), isA(InterLocalityPicker.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fallback_AdsErrorWithActiveSubchannel() throws Exception {
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallback1Attributes())
|
|
||||||
.build());
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
assertNotNull(childHelper);
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).hasSize(1);
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
verify(helper).updateBalancingState(CONNECTING, BUFFER_PICKER);
|
|
||||||
|
|
||||||
childHelper.updateBalancingState(READY, mock(SubchannelPicker.class));
|
|
||||||
verify(helper).updateBalancingState(same(READY), isA(InterLocalityPicker.class));
|
|
||||||
assertThat(fakeClock.getPendingTasks(fallbackTaskFilter)).isEmpty();
|
|
||||||
|
|
||||||
serverResponseWriter.onError(new Exception("fake error"));
|
|
||||||
assertNull(fallbackHelper1);
|
|
||||||
verify(fallbackBalancer1, never()).handleResolvedAddresses(
|
|
||||||
any(ResolvedAddresses.class));
|
|
||||||
|
|
||||||
// verify childHelper still delegates updateBalancingState to parent helper
|
|
||||||
childHelper.updateBalancingState(CONNECTING, mock(SubchannelPicker.class));
|
|
||||||
verify(helper, times(2)).updateBalancingState(CONNECTING, BUFFER_PICKER);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Attributes standardModeWithFallback1Attributes() throws Exception {
|
|
||||||
String lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"fallbackPolicy\" : [{\"fallback_1\" : { \"fallback_1_option\" : \"yes\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
return Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shutdown_cleanupTimers() throws Exception {
|
|
||||||
String lbConfigRaw = "{ "
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"unsupported\" : {\"key\" : \"val\"}}, {\"unsupported_2\" : {}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"unsupported\" : {}}, {\"fallback_1\" : {\"key\" : \"val\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
Attributes attrs = Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
lb.handleResolvedAddresses(
|
|
||||||
ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(attrs)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(fakeClock.getPendingTasks()).isNotEmpty();
|
|
||||||
lb.shutdown();
|
|
||||||
assertThat(fakeClock.getPendingTasks()).isEmpty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,416 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019 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.xds;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static io.grpc.LoadBalancer.ATTR_LOAD_BALANCING_CONFIG;
|
|
||||||
import static org.mockito.AdditionalAnswers.delegatesTo;
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
|
||||||
import static org.mockito.Mockito.inOrder;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
import com.google.protobuf.Any;
|
|
||||||
import io.envoyproxy.envoy.api.v2.ClusterLoadAssignment;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryRequest;
|
|
||||||
import io.envoyproxy.envoy.api.v2.DiscoveryResponse;
|
|
||||||
import io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase;
|
|
||||||
import io.grpc.Attributes;
|
|
||||||
import io.grpc.ChannelLogger;
|
|
||||||
import io.grpc.EquivalentAddressGroup;
|
|
||||||
import io.grpc.LoadBalancer;
|
|
||||||
import io.grpc.LoadBalancer.Helper;
|
|
||||||
import io.grpc.LoadBalancer.ResolvedAddresses;
|
|
||||||
import io.grpc.LoadBalancerProvider;
|
|
||||||
import io.grpc.LoadBalancerRegistry;
|
|
||||||
import io.grpc.ManagedChannel;
|
|
||||||
import io.grpc.Status;
|
|
||||||
import io.grpc.Status.Code;
|
|
||||||
import io.grpc.SynchronizationContext;
|
|
||||||
import io.grpc.inprocess.InProcessChannelBuilder;
|
|
||||||
import io.grpc.inprocess.InProcessServerBuilder;
|
|
||||||
import io.grpc.internal.BackoffPolicy;
|
|
||||||
import io.grpc.internal.FakeClock;
|
|
||||||
import io.grpc.internal.JsonParser;
|
|
||||||
import io.grpc.internal.testing.StreamRecorder;
|
|
||||||
import io.grpc.stub.StreamObserver;
|
|
||||||
import io.grpc.testing.GrpcCleanupRule;
|
|
||||||
import io.grpc.xds.LoadReportClient.LoadReportCallback;
|
|
||||||
import io.grpc.xds.LoadReportClientImpl.LoadReportClientFactory;
|
|
||||||
import io.grpc.xds.XdsLoadBalancer.FallbackManager;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
import org.junit.runners.JUnit4;
|
|
||||||
import org.mockito.ArgumentCaptor;
|
|
||||||
import org.mockito.InOrder;
|
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for {@link XdsLoadBalancer}, especially for interactions between
|
|
||||||
* {@link XdsLoadBalancer} and {@link LoadReportClient}.
|
|
||||||
*/
|
|
||||||
@RunWith(JUnit4.class)
|
|
||||||
public class XdsLoadBalancerWithLrsTest {
|
|
||||||
private static final String SERVICE_AUTHORITY = "test authority";
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public final GrpcCleanupRule cleanupRule = new GrpcCleanupRule();
|
|
||||||
|
|
||||||
private final SynchronizationContext syncContext = new SynchronizationContext(
|
|
||||||
new Thread.UncaughtExceptionHandler() {
|
|
||||||
@Override
|
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private Helper helper;
|
|
||||||
@Mock
|
|
||||||
private BackoffPolicy.Provider backoffPolicyProvider;
|
|
||||||
@Mock
|
|
||||||
private LocalityStore localityStore;
|
|
||||||
@Mock
|
|
||||||
private LoadReportClientFactory lrsClientFactory;
|
|
||||||
@Mock
|
|
||||||
private LoadReportClient lrsClient;
|
|
||||||
@Mock
|
|
||||||
private LoadStatsStore loadStatsStore;
|
|
||||||
@Mock
|
|
||||||
private LoadBalancer fallbackBalancer;
|
|
||||||
@Mock
|
|
||||||
private LoadBalancer mockBalancer;
|
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
|
||||||
private final LoadBalancerRegistry lbRegistry = new LoadBalancerRegistry();
|
|
||||||
private final StreamRecorder<DiscoveryRequest> streamRecorder = StreamRecorder.create();
|
|
||||||
private final LoadBalancerProvider fallBackLbProvider = new LoadBalancerProvider() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPolicyName() {
|
|
||||||
return "fallback";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
|
||||||
fallBackLbHelper = helper;
|
|
||||||
return fallbackBalancer;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private final LoadBalancerProvider lbProvider = new LoadBalancerProvider() {
|
|
||||||
@Override
|
|
||||||
public boolean isAvailable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPolicyName() {
|
|
||||||
return "supported";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LoadBalancer newLoadBalancer(Helper helper) {
|
|
||||||
return mockBalancer;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private Helper fallBackLbHelper;
|
|
||||||
private StreamObserver<DiscoveryResponse> serverResponseWriter;
|
|
||||||
private ManagedChannel oobChannel1;
|
|
||||||
private ManagedChannel oobChannel2;
|
|
||||||
private ManagedChannel oobChannel3;
|
|
||||||
private LoadBalancer xdsLoadBalancer;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
MockitoAnnotations.initMocks(this);
|
|
||||||
String serverName = InProcessServerBuilder.generateName();
|
|
||||||
AggregatedDiscoveryServiceImplBase serviceImpl = new AggregatedDiscoveryServiceImplBase() {
|
|
||||||
@Override
|
|
||||||
public StreamObserver<DiscoveryRequest> streamAggregatedResources(
|
|
||||||
final StreamObserver<DiscoveryResponse> responseObserver) {
|
|
||||||
serverResponseWriter = responseObserver;
|
|
||||||
|
|
||||||
return new StreamObserver<DiscoveryRequest>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(DiscoveryRequest value) {
|
|
||||||
streamRecorder.onNext(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable t) {
|
|
||||||
streamRecorder.onError(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
streamRecorder.onCompleted();
|
|
||||||
responseObserver.onCompleted();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
cleanupRule.register(
|
|
||||||
InProcessServerBuilder
|
|
||||||
.forName(serverName)
|
|
||||||
.directExecutor()
|
|
||||||
.addService(serviceImpl)
|
|
||||||
.build()
|
|
||||||
.start());
|
|
||||||
|
|
||||||
InProcessChannelBuilder channelBuilder =
|
|
||||||
InProcessChannelBuilder.forName(serverName).directExecutor();
|
|
||||||
oobChannel1 = mock(
|
|
||||||
ManagedChannel.class,
|
|
||||||
delegatesTo(cleanupRule.register(channelBuilder.build())));
|
|
||||||
oobChannel2 = mock(
|
|
||||||
ManagedChannel.class,
|
|
||||||
delegatesTo(cleanupRule.register(channelBuilder.build())));
|
|
||||||
oobChannel3 = mock(
|
|
||||||
ManagedChannel.class,
|
|
||||||
delegatesTo(cleanupRule.register(channelBuilder.build())));
|
|
||||||
|
|
||||||
lbRegistry.register(fallBackLbProvider);
|
|
||||||
lbRegistry.register(lbProvider);
|
|
||||||
when(helper.getSynchronizationContext()).thenReturn(syncContext);
|
|
||||||
when(helper.getScheduledExecutorService()).thenReturn(fakeClock.getScheduledExecutorService());
|
|
||||||
when(helper.getAuthority()).thenReturn(SERVICE_AUTHORITY);
|
|
||||||
when(helper.getChannelLogger()).thenReturn(mock(ChannelLogger.class));
|
|
||||||
when(helper.createResolvingOobChannel(anyString()))
|
|
||||||
.thenReturn(oobChannel1, oobChannel2, oobChannel3);
|
|
||||||
when(localityStore.getLoadStatsStore()).thenReturn(loadStatsStore);
|
|
||||||
when(lrsClientFactory.createLoadReportClient(any(ManagedChannel.class), any(Helper.class),
|
|
||||||
any(BackoffPolicy.Provider.class), any(LoadStatsStore.class))).thenReturn(lrsClient);
|
|
||||||
|
|
||||||
xdsLoadBalancer =
|
|
||||||
new XdsLoadBalancer(helper, lbRegistry, backoffPolicyProvider, lrsClientFactory,
|
|
||||||
new FallbackManager(helper, lbRegistry), localityStore);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() {
|
|
||||||
xdsLoadBalancer.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests load reporting is initiated after receiving the first valid EDS response from the traffic
|
|
||||||
* director, then its operation is independent of load balancing until xDS load balancer is
|
|
||||||
* shutdown.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void reportLoadAfterReceivingFirstEdsResponseUntilShutdown() throws Exception {
|
|
||||||
xdsLoadBalancer.handleResolvedAddresses(ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallbackAttributes())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
verify(lrsClientFactory)
|
|
||||||
.createLoadReportClient(same(oobChannel1), same(helper), same(backoffPolicyProvider),
|
|
||||||
same(loadStatsStore));
|
|
||||||
assertThat(streamRecorder.getValues()).hasSize(1);
|
|
||||||
|
|
||||||
// Let fallback timer elapse and xDS load balancer enters fallback mode on startup.
|
|
||||||
assertThat(fakeClock.getPendingTasks()).hasSize(1);
|
|
||||||
assertThat(fallBackLbHelper).isNull();
|
|
||||||
fakeClock.forwardTime(10, TimeUnit.SECONDS);
|
|
||||||
assertThat(fallBackLbHelper).isNotNull();
|
|
||||||
|
|
||||||
verify(lrsClient, never()).startLoadReporting(any(LoadReportCallback.class));
|
|
||||||
|
|
||||||
// Simulates a syntactically incorrect EDS response.
|
|
||||||
serverResponseWriter.onNext(DiscoveryResponse.getDefaultInstance());
|
|
||||||
verify(lrsClient, never()).startLoadReporting(any(LoadReportCallback.class));
|
|
||||||
|
|
||||||
ArgumentCaptor<LoadReportCallback> lrsCallbackCaptor = ArgumentCaptor.forClass(null);
|
|
||||||
|
|
||||||
// Simulate a syntactically correct EDS response.
|
|
||||||
DiscoveryResponse edsResponse =
|
|
||||||
DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.getDefaultInstance()))
|
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
|
||||||
.build();
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
verify(lrsClient).startLoadReporting(lrsCallbackCaptor.capture());
|
|
||||||
lrsCallbackCaptor.getValue().onReportResponse(19543);
|
|
||||||
verify(localityStore).updateOobMetricsReportInterval(19543);
|
|
||||||
|
|
||||||
// Simulate another EDS response from the same remote balancer.
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
|
|
||||||
// Simulate an EDS error response.
|
|
||||||
serverResponseWriter.onError(Status.ABORTED.asException());
|
|
||||||
|
|
||||||
// Shutdown xDS load balancer.
|
|
||||||
xdsLoadBalancer.shutdown();
|
|
||||||
verify(lrsClient).stopLoadReporting();
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(lrsClientFactory, lrsClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests load report client sends load to new traffic director when xDS load balancer talks to
|
|
||||||
* the remote balancer.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void reportLoadToNewTrafficDirectorAfterBalancerNameChange() throws Exception {
|
|
||||||
InOrder inOrder = inOrder(lrsClientFactory, lrsClient);
|
|
||||||
xdsLoadBalancer.handleResolvedAddresses(ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallbackAttributes())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
inOrder.verify(lrsClientFactory)
|
|
||||||
.createLoadReportClient(same(oobChannel1), same(helper), same(backoffPolicyProvider),
|
|
||||||
same(loadStatsStore));
|
|
||||||
assertThat(streamRecorder.getValues()).hasSize(1);
|
|
||||||
inOrder.verify(lrsClient, never()).startLoadReporting(any(LoadReportCallback.class));
|
|
||||||
|
|
||||||
// Simulate receiving a new service config with balancer name changed before xDS protocol is
|
|
||||||
// established.
|
|
||||||
Map<String, ?> newLbConfig =
|
|
||||||
(Map<String, ?>) JsonParser.parse(
|
|
||||||
"{\"balancerName\" : \"dns:///another.balancer.example.com:8080\","
|
|
||||||
+ "\"fallbackPolicy\" : [{\"fallback\" : { \"fallback_option\" : \"yes\"}}]}");
|
|
||||||
|
|
||||||
xdsLoadBalancer.handleResolvedAddresses(ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, newLbConfig).build())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(oobChannel1.isShutdown()).isTrue();
|
|
||||||
assertThat(streamRecorder.getValues()).hasSize(2);
|
|
||||||
inOrder.verify(lrsClientFactory)
|
|
||||||
.createLoadReportClient(same(oobChannel2), same(helper), same(backoffPolicyProvider),
|
|
||||||
same(loadStatsStore));
|
|
||||||
|
|
||||||
// Simulate a syntactically correct EDS response.
|
|
||||||
DiscoveryResponse edsResponse =
|
|
||||||
DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.getDefaultInstance()))
|
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
|
||||||
.build();
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
inOrder.verify(lrsClient).startLoadReporting(any(LoadReportCallback.class));
|
|
||||||
|
|
||||||
// Simulate receiving a new service config with balancer name changed.
|
|
||||||
newLbConfig = (Map<String, ?>) JsonParser.parse(
|
|
||||||
"{\"balancerName\" : \"dns:///third.balancer.example.com:8080\","
|
|
||||||
+ "\"fallbackPolicy\" : [{\"fallback\" : { \"fallback_option\" : \"yes\"}}]}");
|
|
||||||
|
|
||||||
xdsLoadBalancer.handleResolvedAddresses(ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, newLbConfig).build())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(oobChannel2.isShutdown()).isTrue();
|
|
||||||
assertThat(streamRecorder.getValues()).hasSize(3);
|
|
||||||
inOrder.verify(lrsClient).stopLoadReporting();
|
|
||||||
inOrder.verify(lrsClientFactory)
|
|
||||||
.createLoadReportClient(same(oobChannel3), same(helper), same(backoffPolicyProvider),
|
|
||||||
same(loadStatsStore));
|
|
||||||
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
inOrder.verify(lrsClient).startLoadReporting(any(LoadReportCallback.class));
|
|
||||||
|
|
||||||
inOrder.verifyNoMoreInteractions();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests the case that load reporting is not interrupted when child balancing policy changes,
|
|
||||||
* even though xDS balancer refreshes discovery RPC with the traffic director.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void loadReportNotAffectedWhenChildPolicyChanges() throws Exception {
|
|
||||||
xdsLoadBalancer.handleResolvedAddresses(ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(standardModeWithFallbackAttributes())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
verify(lrsClientFactory)
|
|
||||||
.createLoadReportClient(same(oobChannel1), same(helper), same(backoffPolicyProvider),
|
|
||||||
same(loadStatsStore));
|
|
||||||
assertThat(streamRecorder.getValues()).hasSize(1);
|
|
||||||
|
|
||||||
// Simulate a syntactically correct EDS response.
|
|
||||||
DiscoveryResponse edsResponse =
|
|
||||||
DiscoveryResponse.newBuilder()
|
|
||||||
.addResources(Any.pack(ClusterLoadAssignment.getDefaultInstance()))
|
|
||||||
.setTypeUrl("type.googleapis.com/envoy.api.v2.ClusterLoadAssignment")
|
|
||||||
.build();
|
|
||||||
serverResponseWriter.onNext(edsResponse);
|
|
||||||
verify(lrsClient).startLoadReporting(any(LoadReportCallback.class));
|
|
||||||
|
|
||||||
// Simulate receiving a new service config with child policy changed.
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> newLbConfig =
|
|
||||||
(Map<String, ?>) JsonParser.parse(
|
|
||||||
"{\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"childPolicy\" : [{\"supported\" : {\"key\" : \"val\"}}],"
|
|
||||||
+ "\"fallbackPolicy\" : [{\"fallback\" : { \"fallback_option\" : \"yes\"}}]}");
|
|
||||||
|
|
||||||
xdsLoadBalancer.handleResolvedAddresses(ResolvedAddresses.newBuilder()
|
|
||||||
.setAddresses(Collections.<EquivalentAddressGroup>emptyList())
|
|
||||||
.setAttributes(Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, newLbConfig).build())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
assertThat(oobChannel1.isShutdown()).isFalse();
|
|
||||||
assertThat(Status.fromThrowable(streamRecorder.getError()).getCode())
|
|
||||||
.isEqualTo(Code.CANCELLED);
|
|
||||||
assertThat(streamRecorder.getValues()).hasSize(2);
|
|
||||||
verify(lrsClient, never()).stopLoadReporting();
|
|
||||||
|
|
||||||
verifyNoMoreInteractions(lrsClientFactory, lrsClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Attributes standardModeWithFallbackAttributes() throws Exception {
|
|
||||||
String lbConfigRaw = "{"
|
|
||||||
+ "\"balancerName\" : \"dns:///balancer.example.com:8080\","
|
|
||||||
+ "\"fallbackPolicy\" : [{\"fallback\" : { \"fallback_option\" : \"yes\"}}]"
|
|
||||||
+ "}";
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Map<String, ?> lbConfig = (Map<String, ?>) JsonParser.parse(lbConfigRaw);
|
|
||||||
return Attributes.newBuilder().set(ATTR_LOAD_BALANCING_CONFIG, lbConfig).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue