xds: Configure outlier detection. (#9456)

Enables the new OutlierDetectionLoadBalancer when outlier detection is enabled
in the xDS cluster configuration.
This commit is contained in:
Terry Wilson 2022-08-18 16:06:17 -07:00 committed by GitHub
parent 128688ae4d
commit 81abb21e7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 789 additions and 117 deletions

View File

@ -31,6 +31,7 @@ import io.grpc.ClientStreamTracer.StreamInfo;
import io.grpc.ConnectivityState;
import io.grpc.ConnectivityStateInfo;
import io.grpc.EquivalentAddressGroup;
import io.grpc.Internal;
import io.grpc.LoadBalancer;
import io.grpc.Metadata;
import io.grpc.Status;
@ -58,7 +59,8 @@ import javax.annotation.Nullable;
* <p>This implements the outlier detection gRFC:
* https://github.com/grpc/proposal/blob/master/A50-xds-outlier-detection.md
*/
public class OutlierDetectionLoadBalancer extends LoadBalancer {
@Internal
public final class OutlierDetectionLoadBalancer extends LoadBalancer {
@VisibleForTesting
final AddressTrackerMap trackerMap;
@ -837,13 +839,13 @@ public class OutlierDetectionLoadBalancer extends LoadBalancer {
*/
public static final class OutlierDetectionLoadBalancerConfig {
final Long intervalNanos;
final Long baseEjectionTimeNanos;
final Long maxEjectionTimeNanos;
final Integer maxEjectionPercent;
final SuccessRateEjection successRateEjection;
final FailurePercentageEjection failurePercentageEjection;
final PolicySelection childPolicy;
public final Long intervalNanos;
public final Long baseEjectionTimeNanos;
public final Long maxEjectionTimeNanos;
public final Integer maxEjectionPercent;
public final SuccessRateEjection successRateEjection;
public final FailurePercentageEjection failurePercentageEjection;
public final PolicySelection childPolicy;
private OutlierDetectionLoadBalancerConfig(Long intervalNanos,
Long baseEjectionTimeNanos,
@ -932,10 +934,10 @@ public class OutlierDetectionLoadBalancer extends LoadBalancer {
/** The configuration for success rate ejection. */
public static class SuccessRateEjection {
final Integer stdevFactor;
final Integer enforcementPercentage;
final Integer minimumHosts;
final Integer requestVolume;
public final Integer stdevFactor;
public final Integer enforcementPercentage;
public final Integer minimumHosts;
public final Integer requestVolume;
SuccessRateEjection(Integer stdevFactor, Integer enforcementPercentage, Integer minimumHosts,
Integer requestVolume) {
@ -996,10 +998,10 @@ public class OutlierDetectionLoadBalancer extends LoadBalancer {
/** The configuration for failure percentage ejection. */
public static class FailurePercentageEjection {
final Integer threshold;
final Integer enforcementPercentage;
final Integer minimumHosts;
final Integer requestVolume;
public final Integer threshold;
public final Integer enforcementPercentage;
public final Integer minimumHosts;
public final Integer requestVolume;
FailurePercentageEjection(Integer threshold, Integer enforcementPercentage,
Integer minimumHosts, Integer requestVolume) {

View File

@ -16,6 +16,7 @@
package io.grpc.util;
import io.grpc.Internal;
import io.grpc.LoadBalancer;
import io.grpc.LoadBalancer.Helper;
import io.grpc.LoadBalancerProvider;
@ -33,6 +34,7 @@ import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerCon
import java.util.List;
import java.util.Map;
@Internal
public final class OutlierDetectionLoadBalancerProvider extends LoadBalancerProvider {
@Override

View File

@ -159,7 +159,7 @@ final class CdsLoadBalancer2 extends LoadBalancer {
instance = DiscoveryMechanism.forEds(
clusterState.name, clusterState.result.edsServiceName(),
clusterState.result.lrsServerInfo(), clusterState.result.maxConcurrentRequests(),
clusterState.result.upstreamTlsContext());
clusterState.result.upstreamTlsContext(), clusterState.result.outlierDetection());
} else { // logical DNS
instance = DiscoveryMechanism.forLogicalDns(
clusterState.name, clusterState.result.dnsHostName(),

View File

@ -90,6 +90,7 @@ import io.grpc.xds.EnvoyServerProtoData.CidrRange;
import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType;
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.Filter.ClientInterceptorBuilder;
import io.grpc.xds.Filter.FilterConfig;
@ -166,6 +167,10 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res
static boolean enableCustomLbConfig =
Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG"))
|| Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG"));
@VisibleForTesting
static boolean enableOutlierDetection =
!Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_OUTLIER_DETECTION"))
|| Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_OUTLIER_DETECTION"));
private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 =
"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2"
+ ".HttpConnectionManager";
@ -632,6 +637,65 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res
}
}
static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection(
io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection)
throws ResourceInvalidException {
if (outlierDetection.hasInterval()) {
if (!Durations.isValid(outlierDetection.getInterval())) {
throw new ResourceInvalidException("outlier_detection interval is not a valid Duration");
}
if (hasNegativeValues(outlierDetection.getInterval())) {
throw new ResourceInvalidException("outlier_detection interval has a negative value");
}
}
if (outlierDetection.hasBaseEjectionTime()) {
if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) {
throw new ResourceInvalidException(
"outlier_detection base_ejection_time is not a valid Duration");
}
if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) {
throw new ResourceInvalidException(
"outlier_detection base_ejection_time has a negative value");
}
}
if (outlierDetection.hasMaxEjectionTime()) {
if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) {
throw new ResourceInvalidException(
"outlier_detection max_ejection_time is not a valid Duration");
}
if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) {
throw new ResourceInvalidException(
"outlier_detection max_ejection_time has a negative value");
}
}
if (outlierDetection.hasMaxEjectionPercent()
&& outlierDetection.getMaxEjectionPercent().getValue() > 100) {
throw new ResourceInvalidException(
"outlier_detection max_ejection_percent is > 100");
}
if (outlierDetection.hasEnforcingSuccessRate()
&& outlierDetection.getEnforcingSuccessRate().getValue() > 100) {
throw new ResourceInvalidException(
"outlier_detection enforcing_success_rate is > 100");
}
if (outlierDetection.hasFailurePercentageThreshold()
&& outlierDetection.getFailurePercentageThreshold().getValue() > 100) {
throw new ResourceInvalidException(
"outlier_detection failure_percentage_threshold is > 100");
}
if (outlierDetection.hasEnforcingFailurePercentage()
&& outlierDetection.getEnforcingFailurePercentage().getValue() > 100) {
throw new ResourceInvalidException(
"outlier_detection enforcing_failure_percentage is > 100");
}
return outlierDetection;
}
static boolean hasNegativeValues(Duration duration) {
return duration.getSeconds() < 0 || duration.getNanos() < 0;
}
private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) {
if (commonTlsContext.hasTlsCertificateProviderInstance()) {
return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName();
@ -1704,6 +1768,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res
ServerInfo lrsServerInfo = null;
Long maxConcurrentRequests = null;
UpstreamTlsContext upstreamTlsContext = null;
OutlierDetection outlierDetection = null;
if (cluster.hasLrsServer()) {
if (!cluster.getLrsServer().hasSelf()) {
return StructOrError.fromError(
@ -1743,6 +1808,16 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res
"Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e);
}
}
if (cluster.hasOutlierDetection() && enableOutlierDetection) {
try {
outlierDetection = OutlierDetection.fromEnvoyOutlierDetection(
validateOutlierDetection(cluster.getOutlierDetection()));
} catch (ResourceInvalidException e) {
return StructOrError.fromError(
"Cluster " + clusterName + ": malformed outlier_detection: " + e);
}
}
DiscoveryType type = cluster.getType();
if (type == DiscoveryType.EDS) {
@ -1763,7 +1838,8 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res
edsResources.add(clusterName);
}
return StructOrError.fromStruct(CdsUpdate.forEds(
clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext));
clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext,
outlierDetection));
} else if (type.equals(DiscoveryType.LOGICAL_DNS)) {
if (!cluster.hasLoadAssignment()) {
return StructOrError.fromError(

View File

@ -38,6 +38,7 @@ import io.grpc.internal.ObjectPool;
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.util.ForwardingLoadBalancerHelper;
import io.grpc.util.GracefulSwitchLoadBalancer;
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig;
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
@ -45,6 +46,9 @@ import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.Dis
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyServerProtoData.FailurePercentageEjection;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
@ -176,7 +180,8 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
ClusterState state;
if (instance.type == DiscoveryMechanism.Type.EDS) {
state = new EdsClusterState(instance.cluster, instance.edsServiceName,
instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext);
instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext,
instance.outlierDetection);
} else { // logical DNS
state = new LogicalDnsClusterState(instance.cluster, instance.dnsHostName,
instance.lrsServerInfo, instance.maxConcurrentRequests, instance.tlsContext);
@ -316,6 +321,8 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
protected final Long maxConcurrentRequests;
@Nullable
protected final UpstreamTlsContext tlsContext;
@Nullable
protected final OutlierDetection outlierDetection;
// Resolution status, may contain most recent error encountered.
protected Status status = Status.OK;
// True if has received resolution result.
@ -327,11 +334,13 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
protected boolean shutdown;
private ClusterState(String name, @Nullable ServerInfo lrsServerInfo,
@Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) {
@Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext,
@Nullable OutlierDetection outlierDetection) {
this.name = name;
this.lrsServerInfo = lrsServerInfo;
this.maxConcurrentRequests = maxConcurrentRequests;
this.tlsContext = tlsContext;
this.outlierDetection = outlierDetection;
}
abstract void start();
@ -349,8 +358,8 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
private EdsClusterState(String name, @Nullable String edsServiceName,
@Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
@Nullable UpstreamTlsContext tlsContext) {
super(name, lrsServerInfo, maxConcurrentRequests, tlsContext);
@Nullable UpstreamTlsContext tlsContext, @Nullable OutlierDetection outlierDetection) {
super(name, lrsServerInfo, maxConcurrentRequests, tlsContext, outlierDetection);
this.edsServiceName = edsServiceName;
}
@ -434,7 +443,8 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
Map<String, PriorityChildConfig> priorityChildConfigs =
generateEdsBasedPriorityChildConfigs(
name, edsServiceName, lrsServerInfo, maxConcurrentRequests, tlsContext,
endpointLbPolicy, lbRegistry, prioritizedLocalityWeights, dropOverloads);
outlierDetection, endpointLbPolicy, lbRegistry, prioritizedLocalityWeights,
dropOverloads);
status = Status.OK;
resolved = true;
result = new ClusterResolutionResult(addresses, priorityChildConfigs,
@ -530,7 +540,7 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
private LogicalDnsClusterState(String name, String dnsHostName,
@Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
@Nullable UpstreamTlsContext tlsContext) {
super(name, lrsServerInfo, maxConcurrentRequests, tlsContext);
super(name, lrsServerInfo, maxConcurrentRequests, tlsContext, null);
this.dnsHostName = checkNotNull(dnsHostName, "dnsHostName");
nameResolverFactory =
checkNotNull(helper.getNameResolverRegistry().asFactory(), "nameResolverFactory");
@ -730,9 +740,9 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
private static Map<String, PriorityChildConfig> generateEdsBasedPriorityChildConfigs(
String cluster, @Nullable String edsServiceName, @Nullable ServerInfo lrsServerInfo,
@Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext,
PolicySelection endpointLbPolicy, LoadBalancerRegistry lbRegistry,
Map<String, Map<Locality, Integer>> prioritizedLocalityWeights,
List<DropOverload> dropOverloads) {
@Nullable OutlierDetection outlierDetection, PolicySelection endpointLbPolicy,
LoadBalancerRegistry lbRegistry, Map<String,
Map<Locality, Integer>> prioritizedLocalityWeights, List<DropOverload> dropOverloads) {
Map<String, PriorityChildConfig> configs = new HashMap<>();
for (String priority : prioritizedLocalityWeights.keySet()) {
ClusterImplConfig clusterImplConfig =
@ -740,15 +750,98 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
dropOverloads, endpointLbPolicy, tlsContext);
LoadBalancerProvider clusterImplLbProvider =
lbRegistry.getProvider(XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME);
PolicySelection clusterImplPolicy =
PolicySelection priorityChildPolicy =
new PolicySelection(clusterImplLbProvider, clusterImplConfig);
// If outlier detection has been configured we wrap the child policy in the outlier detection
// load balancer.
if (outlierDetection != null) {
LoadBalancerProvider outlierDetectionProvider = lbRegistry.getProvider(
"outlier_detection_experimental");
priorityChildPolicy = new PolicySelection(outlierDetectionProvider,
buildOutlierDetectionLbConfig(outlierDetection, priorityChildPolicy));
}
PriorityChildConfig priorityChildConfig =
new PriorityChildConfig(clusterImplPolicy, true /* ignoreReresolution */);
new PriorityChildConfig(priorityChildPolicy, true /* ignoreReresolution */);
configs.put(priority, priorityChildConfig);
}
return configs;
}
/**
* Converts {@link OutlierDetection} that represents the xDS configuration to {@link
* OutlierDetectionLoadBalancerConfig} that the {@link io.grpc.util.OutlierDetectionLoadBalancer}
* understands.
*/
private static OutlierDetectionLoadBalancerConfig buildOutlierDetectionLbConfig(
OutlierDetection outlierDetection, PolicySelection childPolicy) {
OutlierDetectionLoadBalancerConfig.Builder configBuilder
= new OutlierDetectionLoadBalancerConfig.Builder();
configBuilder.setChildPolicy(childPolicy);
if (outlierDetection.intervalNanos() != null) {
configBuilder.setIntervalNanos(outlierDetection.intervalNanos());
}
if (outlierDetection.baseEjectionTimeNanos() != null) {
configBuilder.setBaseEjectionTimeNanos(outlierDetection.baseEjectionTimeNanos());
}
if (outlierDetection.maxEjectionTimeNanos() != null) {
configBuilder.setMaxEjectionTimeNanos(outlierDetection.maxEjectionTimeNanos());
}
if (outlierDetection.maxEjectionPercent() != null) {
configBuilder.setMaxEjectionPercent(outlierDetection.maxEjectionPercent());
}
SuccessRateEjection successRate = outlierDetection.successRateEjection();
if (successRate != null) {
OutlierDetectionLoadBalancerConfig.SuccessRateEjection.Builder
successRateConfigBuilder = new OutlierDetectionLoadBalancerConfig
.SuccessRateEjection.Builder();
if (successRate.stdevFactor() != null) {
successRateConfigBuilder.setStdevFactor(successRate.stdevFactor());
}
if (successRate.enforcementPercentage() != null) {
successRateConfigBuilder.setEnforcementPercentage(successRate.enforcementPercentage());
}
if (successRate.minimumHosts() != null) {
successRateConfigBuilder.setMinimumHosts(successRate.minimumHosts());
}
if (successRate.requestVolume() != null) {
successRateConfigBuilder.setRequestVolume(successRate.requestVolume());
}
configBuilder.setSuccessRateEjection(successRateConfigBuilder.build());
}
FailurePercentageEjection failurePercentage = outlierDetection.failurePercentageEjection();
if (failurePercentage != null) {
OutlierDetectionLoadBalancerConfig.FailurePercentageEjection.Builder
failurePercentageConfigBuilder = new OutlierDetectionLoadBalancerConfig
.FailurePercentageEjection.Builder();
if (failurePercentage.threshold() != null) {
failurePercentageConfigBuilder.setThreshold(failurePercentage.threshold());
}
if (failurePercentage.enforcementPercentage() != null) {
failurePercentageConfigBuilder.setEnforcementPercentage(
failurePercentage.enforcementPercentage());
}
if (failurePercentage.minimumHosts() != null) {
failurePercentageConfigBuilder.setMinimumHosts(failurePercentage.minimumHosts());
}
if (failurePercentage.requestVolume() != null) {
failurePercentageConfigBuilder.setRequestVolume(failurePercentage.requestVolume());
}
configBuilder.setFailurePercentageEjection(failurePercentageConfigBuilder.build());
}
return configBuilder.build();
}
/**
* Generates a string that represents the priority in the LB policy config. The string is unique
* across priorities in all clusters and priorityName(c, p1) < priorityName(c, p2) iff p1 < p2.

View File

@ -26,6 +26,7 @@ import io.grpc.LoadBalancerProvider;
import io.grpc.NameResolver.ConfigOrError;
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import java.util.List;
import java.util.Map;
@ -124,6 +125,8 @@ public final class ClusterResolverLoadBalancerProvider extends LoadBalancerProvi
// Hostname for resolving endpoints via DNS. Only valid for LOGICAL_DNS clusters.
@Nullable
final String dnsHostName;
@Nullable
final OutlierDetection outlierDetection;
enum Type {
EDS,
@ -132,7 +135,8 @@ public final class ClusterResolverLoadBalancerProvider extends LoadBalancerProvi
private DiscoveryMechanism(String cluster, Type type, @Nullable String edsServiceName,
@Nullable String dnsHostName, @Nullable ServerInfo lrsServerInfo,
@Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext) {
@Nullable Long maxConcurrentRequests, @Nullable UpstreamTlsContext tlsContext,
@Nullable OutlierDetection outlierDetection) {
this.cluster = checkNotNull(cluster, "cluster");
this.type = checkNotNull(type, "type");
this.edsServiceName = edsServiceName;
@ -140,20 +144,22 @@ public final class ClusterResolverLoadBalancerProvider extends LoadBalancerProvi
this.lrsServerInfo = lrsServerInfo;
this.maxConcurrentRequests = maxConcurrentRequests;
this.tlsContext = tlsContext;
this.outlierDetection = outlierDetection;
}
static DiscoveryMechanism forEds(String cluster, @Nullable String edsServiceName,
@Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
@Nullable UpstreamTlsContext tlsContext) {
@Nullable UpstreamTlsContext tlsContext,
OutlierDetection outlierDetection) {
return new DiscoveryMechanism(cluster, Type.EDS, edsServiceName, null, lrsServerInfo,
maxConcurrentRequests, tlsContext);
maxConcurrentRequests, tlsContext, outlierDetection);
}
static DiscoveryMechanism forLogicalDns(String cluster, String dnsHostName,
@Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
@Nullable UpstreamTlsContext tlsContext) {
return new DiscoveryMechanism(cluster, Type.LOGICAL_DNS, null, dnsHostName,
lrsServerInfo, maxConcurrentRequests, tlsContext);
lrsServerInfo, maxConcurrentRequests, tlsContext, null);
}
@Override

View File

@ -19,6 +19,7 @@ package io.grpc.xds;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.util.Durations;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.grpc.Internal;
import io.grpc.xds.internal.sds.SslContextProviderSupplier;
@ -254,4 +255,148 @@ public final class EnvoyServerProtoData {
defaultFilterChain);
}
}
/**
* Corresponds to Envoy proto message {@link
* io.envoyproxy.envoy.config.cluster.v3.OutlierDetection}. Only the fields supported by gRPC are
* included.
*
* <p>Protobuf Duration fields are represented in their string format (e.g. "10s").
*/
@AutoValue
abstract static class OutlierDetection {
@Nullable
abstract Long intervalNanos();
@Nullable
abstract Long baseEjectionTimeNanos();
@Nullable
abstract Long maxEjectionTimeNanos();
@Nullable
abstract Integer maxEjectionPercent();
@Nullable
abstract SuccessRateEjection successRateEjection();
@Nullable
abstract FailurePercentageEjection failurePercentageEjection();
static OutlierDetection create(
@Nullable Long intervalNanos,
@Nullable Long baseEjectionTimeNanos,
@Nullable Long maxEjectionTimeNanos,
@Nullable Integer maxEjectionPercentage,
@Nullable SuccessRateEjection successRateEjection,
@Nullable FailurePercentageEjection failurePercentageEjection) {
return new AutoValue_EnvoyServerProtoData_OutlierDetection(intervalNanos,
baseEjectionTimeNanos, maxEjectionTimeNanos, maxEjectionPercentage, successRateEjection,
failurePercentageEjection);
}
static OutlierDetection fromEnvoyOutlierDetection(
io.envoyproxy.envoy.config.cluster.v3.OutlierDetection envoyOutlierDetection) {
Long intervalNanos = envoyOutlierDetection.hasInterval()
? Durations.toNanos(envoyOutlierDetection.getInterval()) : null;
Long baseEjectionTimeNanos = envoyOutlierDetection.hasBaseEjectionTime()
? Durations.toNanos(envoyOutlierDetection.getBaseEjectionTime()) : null;
Long maxEjectionTimeNanos = envoyOutlierDetection.hasMaxEjectionTime()
? Durations.toNanos(envoyOutlierDetection.getMaxEjectionTime()) : null;
Integer maxEjectionPercentage = envoyOutlierDetection.hasMaxEjectionPercent()
? envoyOutlierDetection.getMaxEjectionPercent().getValue() : null;
SuccessRateEjection successRateEjection;
// If success rate enforcement has been turned completely off, don't configure this ejection.
if (envoyOutlierDetection.hasEnforcingSuccessRate()
&& envoyOutlierDetection.getEnforcingSuccessRate().getValue() == 0) {
successRateEjection = null;
} else {
Integer stdevFactor = envoyOutlierDetection.hasSuccessRateStdevFactor()
? envoyOutlierDetection.getSuccessRateStdevFactor().getValue() : null;
Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingSuccessRate()
? envoyOutlierDetection.getEnforcingSuccessRate().getValue() : null;
Integer minimumHosts = envoyOutlierDetection.hasSuccessRateMinimumHosts()
? envoyOutlierDetection.getSuccessRateMinimumHosts().getValue() : null;
Integer requestVolume = envoyOutlierDetection.hasSuccessRateRequestVolume()
? envoyOutlierDetection.getSuccessRateMinimumHosts().getValue() : null;
successRateEjection = SuccessRateEjection.create(stdevFactor, enforcementPercentage,
minimumHosts, requestVolume);
}
FailurePercentageEjection failurePercentageEjection;
if (envoyOutlierDetection.hasEnforcingFailurePercentage()
&& envoyOutlierDetection.getEnforcingFailurePercentage().getValue() == 0) {
failurePercentageEjection = null;
} else {
Integer threshold = envoyOutlierDetection.hasFailurePercentageThreshold()
? envoyOutlierDetection.getFailurePercentageThreshold().getValue() : null;
Integer enforcementPercentage = envoyOutlierDetection.hasEnforcingFailurePercentage()
? envoyOutlierDetection.getEnforcingFailurePercentage().getValue() : null;
Integer minimumHosts = envoyOutlierDetection.hasFailurePercentageMinimumHosts()
? envoyOutlierDetection.getFailurePercentageMinimumHosts().getValue() : null;
Integer requestVolume = envoyOutlierDetection.hasFailurePercentageRequestVolume()
? envoyOutlierDetection.getFailurePercentageRequestVolume().getValue() : null;
failurePercentageEjection = FailurePercentageEjection.create(threshold,
enforcementPercentage, minimumHosts, requestVolume);
}
return create(intervalNanos, baseEjectionTimeNanos, maxEjectionTimeNanos,
maxEjectionPercentage, successRateEjection, failurePercentageEjection);
}
}
@AutoValue
abstract static class SuccessRateEjection {
@Nullable
abstract Integer stdevFactor();
@Nullable
abstract Integer enforcementPercentage();
@Nullable
abstract Integer minimumHosts();
@Nullable
abstract Integer requestVolume();
static SuccessRateEjection create(
@Nullable Integer stdevFactor,
@Nullable Integer enforcementPercentage,
@Nullable Integer minimumHosts,
@Nullable Integer requestVolume) {
return new AutoValue_EnvoyServerProtoData_SuccessRateEjection(stdevFactor,
enforcementPercentage, minimumHosts, requestVolume);
}
}
@AutoValue
abstract static class FailurePercentageEjection {
@Nullable
abstract Integer threshold();
@Nullable
abstract Integer enforcementPercentage();
@Nullable
abstract Integer minimumHosts();
@Nullable
abstract Integer requestVolume();
static FailurePercentageEjection create(
@Nullable Integer threshold,
@Nullable Integer enforcementPercentage,
@Nullable Integer minimumHosts,
@Nullable Integer requestVolume) {
return new AutoValue_EnvoyServerProtoData_FailurePercentageEjection(threshold,
enforcementPercentage, minimumHosts, requestVolume);
}
}
}

View File

@ -34,6 +34,7 @@ import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyServerProtoData.Listener;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
@ -221,6 +222,10 @@ abstract class XdsClient {
@Nullable
abstract ImmutableList<String> prioritizedClusterNames();
// Outlier detection configuration.
@Nullable
abstract OutlierDetection outlierDetection();
static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
checkNotNull(prioritizedClusterNames, "prioritizedClusterNames");
return new AutoValue_XdsClient_CdsUpdate.Builder()
@ -234,7 +239,8 @@ abstract class XdsClient {
static Builder forEds(String clusterName, @Nullable String edsServiceName,
@Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
@Nullable UpstreamTlsContext upstreamTlsContext) {
@Nullable UpstreamTlsContext upstreamTlsContext,
@Nullable OutlierDetection outlierDetection) {
return new AutoValue_XdsClient_CdsUpdate.Builder()
.clusterName(clusterName)
.clusterType(ClusterType.EDS)
@ -244,7 +250,8 @@ abstract class XdsClient {
.edsServiceName(edsServiceName)
.lrsServerInfo(lrsServerInfo)
.maxConcurrentRequests(maxConcurrentRequests)
.upstreamTlsContext(upstreamTlsContext);
.upstreamTlsContext(upstreamTlsContext)
.outlierDetection(outlierDetection);
}
static Builder forLogicalDns(String clusterName, String dnsHostName,
@ -284,8 +291,9 @@ abstract class XdsClient {
.add("dnsHostName", dnsHostName())
.add("lrsServerInfo", lrsServerInfo())
.add("maxConcurrentRequests", maxConcurrentRequests())
// Exclude upstreamTlsContext as its string representation is cumbersome.
.add("prioritizedClusterNames", prioritizedClusterNames())
// Exclude upstreamTlsContext and outlierDetection as their string representations are
// cumbersome.
.toString();
}
@ -341,6 +349,8 @@ abstract class XdsClient {
// Private, use CdsUpdate.forAggregate() instead.
protected abstract Builder prioritizedClusterNames(List<String> prioritizedClusterNames);
protected abstract Builder outlierDetection(OutlierDetection outlierDetection);
abstract CdsUpdate build();
}
}

View File

@ -50,6 +50,8 @@ import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig;
import io.grpc.xds.RingHashLoadBalancer.RingHashConfig;
@ -85,6 +87,9 @@ public class CdsLoadBalancer2Test {
ServerInfo.create("lrs.googleapis.com", InsecureChannelCredentials.create(), true);
private final UpstreamTlsContext upstreamTlsContext =
CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe", true);
private final OutlierDetection outlierDetection = OutlierDetection.create(
null, null, null, null, SuccessRateEjection.create(null, null, null, null), null);
private final SynchronizationContext syncContext = new SynchronizationContext(
new Thread.UncaughtExceptionHandler() {
@ -154,7 +159,8 @@ public class CdsLoadBalancer2Test {
@Test
public void discoverTopLevelEdsCluster() {
CdsUpdate update =
CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext)
CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext,
outlierDetection)
.roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(CLUSTER, update);
assertThat(childBalancers).hasSize(1);
@ -164,7 +170,7 @@ public class CdsLoadBalancer2Test {
assertThat(childLbConfig.discoveryMechanisms).hasSize(1);
DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms);
assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME,
null, LRS_SERVER_INFO, 100L, upstreamTlsContext);
null, LRS_SERVER_INFO, 100L, upstreamTlsContext, outlierDetection);
assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName()).isEqualTo("round_robin");
}
@ -181,7 +187,7 @@ public class CdsLoadBalancer2Test {
assertThat(childLbConfig.discoveryMechanisms).hasSize(1);
DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms);
assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.LOGICAL_DNS, null,
DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext);
DNS_HOST_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext, null);
assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName())
.isEqualTo("least_request_experimental");
assertThat(((LeastRequestConfig) childLbConfig.lbPolicy.getConfig()).choiceCount).isEqualTo(3);
@ -201,7 +207,7 @@ public class CdsLoadBalancer2Test {
@Test
public void nonAggregateCluster_resourceUpdate() {
CdsUpdate update =
CdsUpdate.forEds(CLUSTER, null, null, 100L, upstreamTlsContext)
CdsUpdate.forEds(CLUSTER, null, null, 100L, upstreamTlsContext, outlierDetection)
.roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(CLUSTER, update);
assertThat(childBalancers).hasSize(1);
@ -209,15 +215,15 @@ public class CdsLoadBalancer2Test {
ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config;
DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms);
assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, null, null, null,
100L, upstreamTlsContext);
100L, upstreamTlsContext, outlierDetection);
update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, null)
.roundRobinLbPolicy().build();
update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, null,
outlierDetection).roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(CLUSTER, update);
childLbConfig = (ClusterResolverConfig) childBalancer.config;
instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms);
assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME,
null, LRS_SERVER_INFO, 200L, null);
null, LRS_SERVER_INFO, 200L, null, outlierDetection);
}
@Test
@ -231,7 +237,7 @@ public class CdsLoadBalancer2Test {
ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config;
DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms);
assertDiscoveryMechanism(instance, CLUSTER, DiscoveryMechanism.Type.LOGICAL_DNS, null,
DNS_HOST_NAME, null, 100L, upstreamTlsContext);
DNS_HOST_NAME, null, 100L, upstreamTlsContext, null);
xdsClient.deliverResourceNotExist(CLUSTER);
assertThat(childBalancer.shutdown).isTrue();
@ -265,9 +271,8 @@ public class CdsLoadBalancer2Test {
assertThat(xdsClient.watchers.keySet()).containsExactly(
CLUSTER, cluster1, cluster2, cluster3, cluster4);
assertThat(childBalancers).isEmpty();
CdsUpdate update3 =
CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext)
.roundRobinLbPolicy().build();
CdsUpdate update3 = CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L,
upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(cluster3, update3);
assertThat(childBalancers).isEmpty();
CdsUpdate update2 =
@ -276,7 +281,7 @@ public class CdsLoadBalancer2Test {
xdsClient.deliverCdsUpdate(cluster2, update2);
assertThat(childBalancers).isEmpty();
CdsUpdate update4 =
CdsUpdate.forEds(cluster4, null, LRS_SERVER_INFO, 300L, null)
CdsUpdate.forEds(cluster4, null, LRS_SERVER_INFO, 300L, null, outlierDetection)
.roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(cluster4, update4);
assertThat(childBalancers).hasSize(1); // all non-aggregate clusters discovered
@ -286,12 +291,12 @@ public class CdsLoadBalancer2Test {
assertThat(childLbConfig.discoveryMechanisms).hasSize(3);
// Clusters on higher level has higher priority: [cluster2, cluster3, cluster4]
assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster2,
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, null, 100L, null);
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, null, 100L, null, null);
assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster3,
DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L,
upstreamTlsContext);
upstreamTlsContext, outlierDetection);
assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(2), cluster4,
DiscoveryMechanism.Type.EDS, null, null, LRS_SERVER_INFO, 300L, null);
DiscoveryMechanism.Type.EDS, null, null, LRS_SERVER_INFO, 300L, null, outlierDetection);
assertThat(childLbConfig.lbPolicy.getProvider().getPolicyName())
.isEqualTo("ring_hash_experimental"); // dominated by top-level cluster's config
assertThat(((RingHashConfig) childLbConfig.lbPolicy.getConfig()).minRingSize).isEqualTo(100L);
@ -326,9 +331,8 @@ public class CdsLoadBalancer2Test {
.roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(CLUSTER, update);
assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster1, cluster2);
CdsUpdate update1 =
CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext)
.roundRobinLbPolicy().build();
CdsUpdate update1 = CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L,
upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(cluster1, update1);
CdsUpdate update2 =
CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null)
@ -339,9 +343,10 @@ public class CdsLoadBalancer2Test {
assertThat(childLbConfig.discoveryMechanisms).hasSize(2);
assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster1,
DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L,
upstreamTlsContext);
upstreamTlsContext, outlierDetection);
assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster2,
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null);
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null,
null);
// Revoke cluster1, should still be able to proceed with cluster2.
xdsClient.deliverResourceNotExist(cluster1);
@ -349,7 +354,8 @@ public class CdsLoadBalancer2Test {
childLbConfig = (ClusterResolverConfig) childBalancer.config;
assertThat(childLbConfig.discoveryMechanisms).hasSize(1);
assertDiscoveryMechanism(Iterables.getOnlyElement(childLbConfig.discoveryMechanisms), cluster2,
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null);
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null,
null);
verify(helper, never()).updateBalancingState(
eq(ConnectivityState.TRANSIENT_FAILURE), any(SubchannelPicker.class));
@ -374,9 +380,8 @@ public class CdsLoadBalancer2Test {
.roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(CLUSTER, update);
assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster1, cluster2);
CdsUpdate update1 =
CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L, upstreamTlsContext)
.roundRobinLbPolicy().build();
CdsUpdate update1 = CdsUpdate.forEds(cluster1, EDS_SERVICE_NAME, LRS_SERVER_INFO, 200L,
upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(cluster1, update1);
CdsUpdate update2 =
CdsUpdate.forLogicalDns(cluster2, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null)
@ -387,9 +392,10 @@ public class CdsLoadBalancer2Test {
assertThat(childLbConfig.discoveryMechanisms).hasSize(2);
assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(0), cluster1,
DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME, null, LRS_SERVER_INFO, 200L,
upstreamTlsContext);
upstreamTlsContext, outlierDetection);
assertDiscoveryMechanism(childLbConfig.discoveryMechanisms.get(1), cluster2,
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null);
DiscoveryMechanism.Type.LOGICAL_DNS, null, DNS_HOST_NAME, LRS_SERVER_INFO, 100L, null,
null);
xdsClient.deliverResourceNotExist(CLUSTER);
assertThat(xdsClient.watchers.keySet())
@ -428,16 +434,15 @@ public class CdsLoadBalancer2Test {
.roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(cluster2, update2);
assertThat(xdsClient.watchers.keySet()).containsExactly(CLUSTER, cluster2, cluster3);
CdsUpdate update3 =
CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext)
.roundRobinLbPolicy().build();
CdsUpdate update3 = CdsUpdate.forEds(cluster3, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L,
upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(cluster3, update3);
FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers);
ClusterResolverConfig childLbConfig = (ClusterResolverConfig) childBalancer.config;
assertThat(childLbConfig.discoveryMechanisms).hasSize(1);
DiscoveryMechanism instance = Iterables.getOnlyElement(childLbConfig.discoveryMechanisms);
assertDiscoveryMechanism(instance, cluster3, DiscoveryMechanism.Type.EDS, EDS_SERVICE_NAME,
null, LRS_SERVER_INFO, 100L, upstreamTlsContext);
null, LRS_SERVER_INFO, 100L, upstreamTlsContext, outlierDetection);
// cluster2 revoked
xdsClient.deliverResourceNotExist(cluster2);
@ -506,9 +511,8 @@ public class CdsLoadBalancer2Test {
@Test
public void handleNameResolutionErrorFromUpstream_afterChildLbCreated_fallThrough() {
CdsUpdate update =
CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext)
.roundRobinLbPolicy().build();
CdsUpdate update = CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L,
upstreamTlsContext, outlierDetection).roundRobinLbPolicy().build();
xdsClient.deliverCdsUpdate(CLUSTER, update);
FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers);
assertThat(childBalancer.shutdown).isFalse();
@ -523,7 +527,8 @@ public class CdsLoadBalancer2Test {
public void unknownLbProvider() {
try {
xdsClient.deliverCdsUpdate(CLUSTER,
CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext)
CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext,
outlierDetection)
.lbPolicyConfig(ImmutableMap.of("unknown", ImmutableMap.of("foo", "bar"))).build());
} catch (Exception e) {
assertThat(e).hasCauseThat().hasMessageThat().contains("No provider available");
@ -536,8 +541,8 @@ public class CdsLoadBalancer2Test {
public void invalidLbConfig() {
try {
xdsClient.deliverCdsUpdate(CLUSTER,
CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext)
.lbPolicyConfig(
CdsUpdate.forEds(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_INFO, 100L, upstreamTlsContext,
outlierDetection).lbPolicyConfig(
ImmutableMap.of("ring_hash_experimental", ImmutableMap.of("minRingSize", "-1")))
.build());
} catch (Exception e) {
@ -561,7 +566,7 @@ public class CdsLoadBalancer2Test {
private static void assertDiscoveryMechanism(DiscoveryMechanism instance, String name,
DiscoveryMechanism.Type type, @Nullable String edsServiceName, @Nullable String dnsHostName,
@Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests,
@Nullable UpstreamTlsContext tlsContext) {
@Nullable UpstreamTlsContext tlsContext, @Nullable OutlierDetection outlierDetection) {
assertThat(instance.cluster).isEqualTo(name);
assertThat(instance.type).isEqualTo(type);
assertThat(instance.edsServiceName).isEqualTo(edsServiceName);
@ -569,6 +574,7 @@ public class CdsLoadBalancer2Test {
assertThat(instance.lrsServerInfo).isEqualTo(lrsServerInfo);
assertThat(instance.maxConcurrentRequests).isEqualTo(maxConcurrentRequests);
assertThat(instance.tlsContext).isEqualTo(tlsContext);
assertThat(instance.outlierDetection).isEqualTo(outlierDetection);
}
private final class FakeLoadBalancerProvider extends LoadBalancerProvider {

View File

@ -37,8 +37,12 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.protobuf.Any;
import com.google.protobuf.Duration;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.UInt32Value;
import com.google.protobuf.util.Durations;
import io.envoyproxy.envoy.config.cluster.v3.OutlierDetection;
import io.envoyproxy.envoy.config.route.v3.FilterConfig;
import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateProviderPluginInstance;
@ -66,12 +70,15 @@ import io.grpc.xds.AbstractXdsClient.ResourceType;
import io.grpc.xds.Bootstrapper.AuthorityInfo;
import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.ClientXdsClient.ResourceInvalidException;
import io.grpc.xds.ClientXdsClient.XdsChannelFactory;
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.EnvoyServerProtoData.FailurePercentageEjection;
import io.grpc.xds.EnvoyServerProtoData.FilterChain;
import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection;
import io.grpc.xds.FaultConfig.FractionalPercent.DenominatorType;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.XdsClient.CdsResourceWatcher;
@ -211,7 +218,7 @@ public abstract class ClientXdsClientTestBase {
// CDS test resources.
private final Any testClusterRoundRobin =
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null,
null, false, null, "envoy.transport_sockets.tls", null
null, false, null, "envoy.transport_sockets.tls", null, null
));
// EDS test resources.
@ -1021,7 +1028,7 @@ public abstract class ClientXdsClientTestBase {
"xdstp://authority.xds.com/envoy.config.listener.v3.Listener/cluster1";
Any testClusterConfig = Any.pack(mf.buildEdsCluster(
cdsResourceNameWithWrongType, null, "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null));
"envoy.transport_sockets.tls", null, null));
call.sendResponse(CDS, testClusterConfig, VERSION_1, "0000");
call.verifyRequestNack(
CDS, cdsResourceName, "", "0000", NODE,
@ -1615,9 +1622,9 @@ public abstract class ClientXdsClientTestBase {
List<Any> clusters = ImmutableList.of(
Any.pack(mf.buildEdsCluster("cluster-bar.googleapis.com", null, "round_robin", null,
null, false, null, "envoy.transport_sockets.tls", null)),
null, false, null, "envoy.transport_sockets.tls", null, null)),
Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null,
null, false, null, "envoy.transport_sockets.tls", null)));
null, false, null, "envoy.transport_sockets.tls", null, null)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
// Client sent an ACK CDS request.
@ -1692,13 +1699,13 @@ public abstract class ClientXdsClientTestBase {
// CDS -> {A, B, C}, version 1
ImmutableMap<String, Any> resourcesV1 = ImmutableMap.of(
"A", Any.pack(mf.buildEdsCluster("A", "A.1", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
"B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
"C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)));
call.sendResponse(CDS, resourcesV1.values().asList(), VERSION_1, "0000");
// {A, B, C} -> ACK, version 1
@ -1711,7 +1718,7 @@ public abstract class ClientXdsClientTestBase {
// Failed to parse endpoint B
ImmutableMap<String, Any> resourcesV2 = ImmutableMap.of(
"A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
"B", Any.pack(mf.buildClusterInvalid("B")));
call.sendResponse(CDS, resourcesV2.values().asList(), VERSION_2, "0001");
@ -1733,10 +1740,10 @@ public abstract class ClientXdsClientTestBase {
// CDS -> {B, C} version 3
ImmutableMap<String, Any> resourcesV3 = ImmutableMap.of(
"B", Any.pack(mf.buildEdsCluster("B", "B.3", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
"C", Any.pack(mf.buildEdsCluster("C", "C.3", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)));
call.sendResponse(CDS, resourcesV3.values().asList(), VERSION_3, "0002");
// {A} -> does not exit
@ -1774,13 +1781,13 @@ public abstract class ClientXdsClientTestBase {
// CDS -> {A, B, C}, version 1
ImmutableMap<String, Any> resourcesV1 = ImmutableMap.of(
"A", Any.pack(mf.buildEdsCluster("A", "A.1", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
"B", Any.pack(mf.buildEdsCluster("B", "B.1", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
"C", Any.pack(mf.buildEdsCluster("C", "C.1", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)));
call.sendResponse(CDS, resourcesV1.values().asList(), VERSION_1, "0000");
// {A, B, C} -> ACK, version 1
@ -1806,7 +1813,7 @@ public abstract class ClientXdsClientTestBase {
// Failed to parse endpoint B
ImmutableMap<String, Any> resourcesV2 = ImmutableMap.of(
"A", Any.pack(mf.buildEdsCluster("A", "A.2", "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
"B", Any.pack(mf.buildClusterInvalid("B")));
call.sendResponse(CDS, resourcesV2.values().asList(), VERSION_2, "0001");
@ -1876,7 +1883,7 @@ public abstract class ClientXdsClientTestBase {
Message leastRequestConfig = mf.buildLeastRequestLbConfig(3);
Any clusterRingHash = Any.pack(
mf.buildEdsCluster(CDS_RESOURCE, null, "least_request_experimental", null,
leastRequestConfig, false, null, "envoy.transport_sockets.tls", null
leastRequestConfig, false, null, "envoy.transport_sockets.tls", null, null
));
call.sendResponse(ResourceType.CDS, clusterRingHash, VERSION_1, "0000");
@ -1907,7 +1914,7 @@ public abstract class ClientXdsClientTestBase {
Message ringHashConfig = mf.buildRingHashLbConfig("xx_hash", 10L, 100L);
Any clusterRingHash = Any.pack(
mf.buildEdsCluster(CDS_RESOURCE, null, "ring_hash_experimental", ringHashConfig, null,
false, null, "envoy.transport_sockets.tls", null
false, null, "envoy.transport_sockets.tls", null, null
));
call.sendResponse(ResourceType.CDS, clusterRingHash, VERSION_1, "0000");
@ -1962,7 +1969,7 @@ public abstract class ClientXdsClientTestBase {
DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher);
Any clusterCircuitBreakers = Any.pack(
mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200)));
"envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200), null));
call.sendResponse(CDS, clusterCircuitBreakers, VERSION_1, "0000");
// Client sent an ACK CDS request.
@ -1999,13 +2006,13 @@ public abstract class ClientXdsClientTestBase {
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
null, null, true,
mf.buildUpstreamTlsContext("cert-instance-name", "cert1"),
"envoy.transport_sockets.tls", null));
"envoy.transport_sockets.tls", null, null));
List<Any> clusters = ImmutableList.of(
Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com",
"dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)),
clusterEds,
Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null,
false, null, "envoy.transport_sockets.tls", null)));
false, null, "envoy.transport_sockets.tls", null, null)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
// Client sent an ACK CDS request.
@ -2035,13 +2042,13 @@ public abstract class ClientXdsClientTestBase {
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
null, null,true,
mf.buildNewUpstreamTlsContext("cert-instance-name", "cert1"),
"envoy.transport_sockets.tls", null));
"envoy.transport_sockets.tls", null, null));
List<Any> clusters = ImmutableList.of(
Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com",
"dns-service-bar.googleapis.com", 443, "round_robin", null, null, false, null, null)),
clusterEds,
Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null,
false, null, "envoy.transport_sockets.tls", null)));
false, null, "envoy.transport_sockets.tls", null, null)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
// Client sent an ACK CDS request.
@ -2069,7 +2076,7 @@ public abstract class ClientXdsClientTestBase {
List<Any> clusters = ImmutableList.of(Any
.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
null, null, true,
mf.buildUpstreamTlsContext(null, null), "envoy.transport_sockets.tls", null)));
mf.buildUpstreamTlsContext(null, null), "envoy.transport_sockets.tls", null, null)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
// The response NACKed with errors indicating indices of the failed resources.
@ -2082,6 +2089,224 @@ public abstract class ClientXdsClientTestBase {
verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg);
}
/**
* CDS response containing OutlierDetection for a cluster.
*/
@Test
@SuppressWarnings("deprecation")
public void cdsResponseWithOutlierDetection() {
Assume.assumeTrue(useProtocolV3());
ClientXdsClient.enableOutlierDetection = true;
DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher);
OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder()
.setInterval(Durations.fromNanos(100))
.setBaseEjectionTime(Durations.fromNanos(100))
.setMaxEjectionTime(Durations.fromNanos(100))
.setMaxEjectionPercent(UInt32Value.of(100))
.setSuccessRateStdevFactor(UInt32Value.of(100))
.setEnforcingSuccessRate(UInt32Value.of(100))
.setSuccessRateMinimumHosts(UInt32Value.of(100))
.setSuccessRateRequestVolume(UInt32Value.of(100))
.setFailurePercentageThreshold(UInt32Value.of(100))
.setEnforcingFailurePercentage(UInt32Value.of(100))
.setFailurePercentageMinimumHosts(UInt32Value.of(100))
.setFailurePercentageRequestVolume(UInt32Value.of(100)).build();
// Management server sends back CDS response with UpstreamTlsContext.
Any clusterEds =
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
null, null, true,
mf.buildUpstreamTlsContext("cert-instance-name", "cert1"),
"envoy.transport_sockets.tls", null, outlierDetectionXds));
List<Any> clusters = ImmutableList.of(
Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com",
"dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)),
clusterEds,
Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null,
false, null, "envoy.transport_sockets.tls", null, outlierDetectionXds)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
// Client sent an ACK CDS request.
call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE);
verify(cdsResourceWatcher, times(1)).onChanged(cdsUpdateCaptor.capture());
CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue();
// The outlier detection config in CdsUpdate should match what we get from xDS.
EnvoyServerProtoData.OutlierDetection outlierDetection = cdsUpdate.outlierDetection();
assertThat(outlierDetection).isNotNull();
assertThat(outlierDetection.intervalNanos()).isEqualTo(100);
assertThat(outlierDetection.baseEjectionTimeNanos()).isEqualTo(100);
assertThat(outlierDetection.maxEjectionTimeNanos()).isEqualTo(100);
assertThat(outlierDetection.maxEjectionPercent()).isEqualTo(100);
SuccessRateEjection successRateEjection = outlierDetection.successRateEjection();
assertThat(successRateEjection).isNotNull();
assertThat(successRateEjection.stdevFactor()).isEqualTo(100);
assertThat(successRateEjection.enforcementPercentage()).isEqualTo(100);
assertThat(successRateEjection.minimumHosts()).isEqualTo(100);
assertThat(successRateEjection.requestVolume()).isEqualTo(100);
FailurePercentageEjection failurePercentageEjection
= outlierDetection.failurePercentageEjection();
assertThat(failurePercentageEjection).isNotNull();
assertThat(failurePercentageEjection.threshold()).isEqualTo(100);
assertThat(failurePercentageEjection.enforcementPercentage()).isEqualTo(100);
assertThat(failurePercentageEjection.minimumHosts()).isEqualTo(100);
assertThat(failurePercentageEjection.requestVolume()).isEqualTo(100);
verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterEds, VERSION_1, TIME_INCREMENT);
verifySubscribedResourcesMetadataSizes(0, 1, 0, 0);
}
/**
* CDS response containing OutlierDetection for a cluster, but support has not been enabled.
*/
@Test
@SuppressWarnings("deprecation")
public void cdsResponseWithOutlierDetection_supportDisabled() {
Assume.assumeTrue(useProtocolV3());
ClientXdsClient.enableOutlierDetection = false;
DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher);
OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder()
.setInterval(Durations.fromNanos(100)).build();
// Management server sends back CDS response with UpstreamTlsContext.
Any clusterEds =
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
null, null, true,
mf.buildUpstreamTlsContext("cert-instance-name", "cert1"),
"envoy.transport_sockets.tls", null, outlierDetectionXds));
List<Any> clusters = ImmutableList.of(
Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com",
"dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)),
clusterEds,
Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null,
false, null, "envoy.transport_sockets.tls", null, outlierDetectionXds)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
// Client sent an ACK CDS request.
call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE);
verify(cdsResourceWatcher, times(1)).onChanged(cdsUpdateCaptor.capture());
CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue();
assertThat(cdsUpdate.outlierDetection()).isNull();
verifyResourceMetadataAcked(CDS, CDS_RESOURCE, clusterEds, VERSION_1, TIME_INCREMENT);
verifySubscribedResourcesMetadataSizes(0, 1, 0, 0);
}
/**
* CDS response containing OutlierDetection for a cluster.
*/
@Test
@SuppressWarnings("deprecation")
public void cdsResponseWithInvalidOutlierDetectionNacks() {
Assume.assumeTrue(useProtocolV3());
ClientXdsClient.enableOutlierDetection = true;
DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher);
OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder()
.setMaxEjectionPercent(UInt32Value.of(101)).build();
// Management server sends back CDS response with UpstreamTlsContext.
Any clusterEds =
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
null, null, true,
mf.buildUpstreamTlsContext("cert-instance-name", "cert1"),
"envoy.transport_sockets.tls", null, outlierDetectionXds));
List<Any> clusters = ImmutableList.of(
Any.pack(mf.buildLogicalDnsCluster("cluster-bar.googleapis.com",
"dns-service-bar.googleapis.com", 443, "round_robin", null, null,false, null, null)),
clusterEds,
Any.pack(mf.buildEdsCluster("cluster-baz.googleapis.com", null, "round_robin", null, null,
false, null, "envoy.transport_sockets.tls", null, outlierDetectionXds)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: "
+ "Cluster cluster.googleapis.com: malformed outlier_detection: "
+ "io.grpc.xds.ClientXdsClient$ResourceInvalidException: outlier_detection "
+ "max_ejection_percent is > 100";
call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg));
verify(cdsResourceWatcher).onError(errorCaptor.capture());
verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg);
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_invalidInterval() throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(Long.MAX_VALUE))
.build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_negativeInterval() throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(-1))
.build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_invalidBaseEjectionTime() throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder()
.setBaseEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE))
.build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_negativeBaseEjectionTime() throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setBaseEjectionTime(Duration.newBuilder().setSeconds(-1))
.build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_invalidMaxEjectionTime() throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder()
.setMaxEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE))
.build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_negativeMaxEjectionTime() throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setMaxEjectionTime(Duration.newBuilder().setSeconds(-1))
.build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_maxEjectionPercentTooHigh() throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setMaxEjectionPercent(UInt32Value.of(101)).build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_enforcingSuccessRateTooHigh()
throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setEnforcingSuccessRate(UInt32Value.of(101)).build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_failurePercentageThresholdTooHigh()
throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setFailurePercentageThreshold(UInt32Value.of(101)).build());
}
@Test(expected = ResourceInvalidException.class)
public void validateOutlierDetection_enforcingFailurePercentageTooHigh()
throws ResourceInvalidException {
ClientXdsClient.validateOutlierDetection(
OutlierDetection.newBuilder().setEnforcingFailurePercentage(UInt32Value.of(101)).build());
}
/**
* CDS response containing UpstreamTlsContext with bad transportSocketName for a cluster.
*/
@ -2094,7 +2319,8 @@ public abstract class ClientXdsClientTestBase {
List<Any> clusters = ImmutableList.of(Any
.pack(mf.buildEdsCluster(CDS_RESOURCE, "eds-cluster-foo.googleapis.com", "round_robin",
null, null, true,
mf.buildUpstreamTlsContext("secret1", "cert1"), "envoy.transport_sockets.bad", null)));
mf.buildUpstreamTlsContext("secret1", "cert1"), "envoy.transport_sockets.bad", null,
null)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
// The response NACKed with errors indicating indices of the failed resources.
@ -2169,7 +2395,7 @@ public abstract class ClientXdsClientTestBase {
String edsService = "eds-service-bar.googleapis.com";
Any clusterEds = Any.pack(
mf.buildEdsCluster(CDS_RESOURCE, edsService, "round_robin", null, null, true, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
));
call.sendResponse(CDS, clusterEds, VERSION_2, "0001");
call.verifyRequest(CDS, CDS_RESOURCE, VERSION_2, "0001", NODE);
@ -2199,17 +2425,17 @@ public abstract class ClientXdsClientTestBase {
String transportSocketName = "envoy.transport_sockets.tls";
Any roundRobinConfig = Any.pack(
mf.buildEdsCluster(CDS_RESOURCE, edsService, "round_robin", null, null, true, null,
transportSocketName, null
transportSocketName, null, null
));
Any ringHashConfig = Any.pack(
mf.buildEdsCluster(CDS_RESOURCE, edsService, "ring_hash_experimental",
mf.buildRingHashLbConfig("xx_hash", 1, 2), null, true, null,
transportSocketName, null
transportSocketName, null, null
));
Any leastRequestConfig = Any.pack(
mf.buildEdsCluster(CDS_RESOURCE, edsService, "least_request_experimental",
null, mf.buildLeastRequestLbConfig(2), true, null,
transportSocketName, null
transportSocketName, null, null
));
// Configure with round robin, the update should be sent to the watcher.
@ -2332,7 +2558,7 @@ public abstract class ClientXdsClientTestBase {
Any.pack(mf.buildLogicalDnsCluster(CDS_RESOURCE, dnsHostAddr, dnsHostPort, "round_robin",
null, null, false, null, null)),
Any.pack(mf.buildEdsCluster(cdsResourceTwo, edsService, "round_robin", null, null, true,
null, "envoy.transport_sockets.tls", null)));
null, "envoy.transport_sockets.tls", null, null)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
verify(cdsResourceWatcher).onChanged(cdsUpdateCaptor.capture());
CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue();
@ -2643,10 +2869,10 @@ public abstract class ClientXdsClientTestBase {
DiscoveryRpcCall call = resourceDiscoveryCalls.poll();
List<Any> clusters = ImmutableList.of(
Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, null, true, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)),
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, EDS_RESOURCE, "round_robin", null, null, false,
null, "envoy.transport_sockets.tls", null)));
null, "envoy.transport_sockets.tls", null, null)));
call.sendResponse(CDS, clusters, VERSION_1, "0000");
verify(cdsWatcher).onChanged(cdsUpdateCaptor.capture());
CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue();
@ -2692,9 +2918,9 @@ public abstract class ClientXdsClientTestBase {
clusters = ImmutableList.of(
Any.pack(mf.buildEdsCluster(resource, null, "round_robin", null, null, true, null,
"envoy.transport_sockets.tls", null)), // no change
"envoy.transport_sockets.tls", null, null)), // no change
Any.pack(mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null,
"envoy.transport_sockets.tls", null
"envoy.transport_sockets.tls", null, null
)));
call.sendResponse(CDS, clusters, VERSION_2, "0001");
verify(cdsResourceWatcher, times(2)).onChanged(cdsUpdateCaptor.capture());
@ -3256,7 +3482,7 @@ public abstract class ClientXdsClientTestBase {
protected abstract Message buildEdsCluster(String clusterName, @Nullable String edsServiceName,
String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig,
boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName,
@Nullable Message circuitBreakers);
@Nullable Message circuitBreakers, @Nullable Message outlierDetection);
protected abstract Message buildLogicalDnsCluster(String clusterName, String dnsHostAddr,
int dnsHostPort, String lbPolicy, @Nullable Message ringHashLbConfig,

View File

@ -52,6 +52,7 @@ import io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig;
import io.envoyproxy.envoy.api.v2.auth.UpstreamTlsContext;
import io.envoyproxy.envoy.api.v2.cluster.CircuitBreakers;
import io.envoyproxy.envoy.api.v2.cluster.CircuitBreakers.Thresholds;
import io.envoyproxy.envoy.api.v2.cluster.OutlierDetection;
import io.envoyproxy.envoy.api.v2.core.Address;
import io.envoyproxy.envoy.api.v2.core.AggregatedConfigSource;
import io.envoyproxy.envoy.api.v2.core.ApiConfigSource;
@ -420,10 +421,10 @@ public class ClientXdsClientV2Test extends ClientXdsClientTestBase {
String lbPolicy, @Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig,
boolean enableLrs,
@Nullable Message upstreamTlsContext, String transportSocketName,
@Nullable Message circuitBreakers) {
@Nullable Message circuitBreakers, @Nullable Message outlierDetection) {
Cluster.Builder builder = initClusterBuilder(
clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig,
enableLrs, upstreamTlsContext, circuitBreakers);
enableLrs, upstreamTlsContext, circuitBreakers, outlierDetection);
builder.setType(DiscoveryType.EDS);
EdsClusterConfig.Builder edsClusterConfigBuilder = EdsClusterConfig.newBuilder();
edsClusterConfigBuilder.setEdsConfig(
@ -442,7 +443,7 @@ public class ClientXdsClientV2Test extends ClientXdsClientTestBase {
@Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers) {
Cluster.Builder builder = initClusterBuilder(
clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig,
enableLrs, upstreamTlsContext, circuitBreakers);
enableLrs, upstreamTlsContext, circuitBreakers, null);
builder.setType(DiscoveryType.LOGICAL_DNS);
builder.setLoadAssignment(
ClusterLoadAssignment.newBuilder().addEndpoints(
@ -483,7 +484,7 @@ public class ClientXdsClientV2Test extends ClientXdsClientTestBase {
private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy,
@Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig,
boolean enableLrs, @Nullable Message upstreamTlsContext,
@Nullable Message circuitBreakers) {
@Nullable Message circuitBreakers, @Nullable Message outlierDetection) {
Cluster.Builder builder = Cluster.newBuilder();
builder.setName(clusterName);
if (lbPolicy.equals("round_robin")) {
@ -511,6 +512,9 @@ public class ClientXdsClientV2Test extends ClientXdsClientTestBase {
if (circuitBreakers != null) {
builder.setCircuitBreakers((CircuitBreakers) circuitBreakers);
}
if (outlierDetection != null) {
builder.setOutlierDetection((OutlierDetection) outlierDetection);
}
return builder;
}

View File

@ -41,6 +41,7 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.LeastRequestLbConfig;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.RingHashLbConfig;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.RingHashLbConfig.HashFunction;
import io.envoyproxy.envoy.config.cluster.v3.OutlierDetection;
import io.envoyproxy.envoy.config.core.v3.Address;
import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource;
import io.envoyproxy.envoy.config.core.v3.ConfigSource;
@ -476,10 +477,10 @@ public class ClientXdsClientV3Test extends ClientXdsClientTestBase {
String lbPolicy, @Nullable Message ringHashLbConfig,
@Nullable Message leastRequestLbConfig, boolean enableLrs,
@Nullable Message upstreamTlsContext, String transportSocketName,
@Nullable Message circuitBreakers) {
@Nullable Message circuitBreakers, @Nullable Message outlierDetection) {
Cluster.Builder builder = initClusterBuilder(
clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig,
enableLrs, upstreamTlsContext, transportSocketName, circuitBreakers);
enableLrs, upstreamTlsContext, transportSocketName, circuitBreakers, outlierDetection);
builder.setType(DiscoveryType.EDS);
EdsClusterConfig.Builder edsClusterConfigBuilder = EdsClusterConfig.newBuilder();
edsClusterConfigBuilder.setEdsConfig(
@ -498,7 +499,7 @@ public class ClientXdsClientV3Test extends ClientXdsClientTestBase {
@Nullable Message upstreamTlsContext, @Nullable Message circuitBreakers) {
Cluster.Builder builder = initClusterBuilder(
clusterName, lbPolicy, ringHashLbConfig, leastRequestLbConfig,
enableLrs, upstreamTlsContext, "envoy.transport_sockets.tls", circuitBreakers);
enableLrs, upstreamTlsContext, "envoy.transport_sockets.tls", circuitBreakers, null);
builder.setType(DiscoveryType.LOGICAL_DNS);
builder.setLoadAssignment(
ClusterLoadAssignment.newBuilder().addEndpoints(
@ -539,7 +540,7 @@ public class ClientXdsClientV3Test extends ClientXdsClientTestBase {
private Cluster.Builder initClusterBuilder(String clusterName, String lbPolicy,
@Nullable Message ringHashLbConfig, @Nullable Message leastRequestLbConfig,
boolean enableLrs, @Nullable Message upstreamTlsContext, String transportSocketName,
@Nullable Message circuitBreakers) {
@Nullable Message circuitBreakers, @Nullable Message outlierDetection) {
Cluster.Builder builder = Cluster.newBuilder();
builder.setName(clusterName);
if (lbPolicy.equals("round_robin")) {
@ -567,6 +568,9 @@ public class ClientXdsClientV3Test extends ClientXdsClientTestBase {
if (circuitBreakers != null) {
builder.setCircuitBreakers((CircuitBreakers) circuitBreakers);
}
if (outlierDetection != null) {
builder.setOutlierDetection((OutlierDetection) outlierDetection);
}
return builder;
}

View File

@ -58,6 +58,8 @@ import io.grpc.internal.FakeClock.ScheduledTask;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.ObjectPool;
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.util.OutlierDetectionLoadBalancer.OutlierDetectionLoadBalancerConfig;
import io.grpc.util.OutlierDetectionLoadBalancerProvider;
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
@ -65,6 +67,9 @@ import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.Dis
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyServerProtoData.FailurePercentageEjection;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
@ -116,10 +121,18 @@ public class ClusterResolverLoadBalancerTest {
Locality.create("test-region-3", "test-zone-3", "test-subzone-3");
private final UpstreamTlsContext tlsContext =
CommonTlsContextTestsUtil.buildUpstreamTlsContext("google_cloud_private_spiffe", true);
private final OutlierDetection outlierDetection = OutlierDetection.create(
100L, 100L, 100L, 100, SuccessRateEjection.create(100, 100, 100, 100),
FailurePercentageEjection.create(100, 100, 100, 100));
private final DiscoveryMechanism edsDiscoveryMechanism1 =
DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext);
DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext,
null);
private final DiscoveryMechanism edsDiscoveryMechanism2 =
DiscoveryMechanism.forEds(CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_INFO, 200L, tlsContext);
DiscoveryMechanism.forEds(CLUSTER2, EDS_SERVICE_NAME2, LRS_SERVER_INFO, 200L, tlsContext,
null);
private final DiscoveryMechanism edsDiscoveryMechanismWithOutlierDetection =
DiscoveryMechanism.forEds(CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L, tlsContext,
outlierDetection);
private final DiscoveryMechanism logicalDnsDiscoveryMechanism =
DiscoveryMechanism.forLogicalDns(CLUSTER_DNS, DNS_HOST_NAME, LRS_SERVER_INFO, 300L, null);
@ -182,6 +195,7 @@ public class ClusterResolverLoadBalancerTest {
lbRegistry.register(new FakeLoadBalancerProvider(WEIGHTED_TARGET_POLICY_NAME));
lbRegistry.register(
new FakeLoadBalancerProvider("pick_first")); // needed by logical_dns
lbRegistry.register(new OutlierDetectionLoadBalancerProvider());
NameResolver.Args args = NameResolver.Args.newBuilder()
.setDefaultPort(8080)
.setProxyDetector(GrpcUtil.NOOP_PROXY_DETECTOR)
@ -317,6 +331,90 @@ public class ClusterResolverLoadBalancerTest {
locality1, 100);
}
@Test
public void edsClustersWithOutlierDetection() {
ClusterResolverConfig config = new ClusterResolverConfig(
Collections.singletonList(edsDiscoveryMechanismWithOutlierDetection), leastRequest);
deliverLbConfig(config);
assertThat(xdsClient.watchers.keySet()).containsExactly(EDS_SERVICE_NAME1);
assertThat(childBalancers).isEmpty();
// Simple case with one priority and one locality
EquivalentAddressGroup endpoint = makeAddress("endpoint-addr-1");
LocalityLbEndpoints localityLbEndpoints =
LocalityLbEndpoints.create(
Arrays.asList(
LbEndpoint.create(endpoint, 0 /* loadBalancingWeight */, true)),
100 /* localityWeight */, 1 /* priority */);
xdsClient.deliverClusterLoadAssignment(
EDS_SERVICE_NAME1,
ImmutableMap.of(locality1, localityLbEndpoints));
assertThat(childBalancers).hasSize(1);
FakeLoadBalancer childBalancer = Iterables.getOnlyElement(childBalancers);
assertThat(childBalancer.addresses).hasSize(1);
EquivalentAddressGroup addr = childBalancer.addresses.get(0);
assertThat(addr.getAddresses()).isEqualTo(endpoint.getAddresses());
assertThat(childBalancer.name).isEqualTo(PRIORITY_POLICY_NAME);
PriorityLbConfig priorityLbConfig = (PriorityLbConfig) childBalancer.config;
assertThat(priorityLbConfig.priorities).containsExactly(CLUSTER1 + "[child1]");
PriorityChildConfig priorityChildConfig =
Iterables.getOnlyElement(priorityLbConfig.childConfigs.values());
// The child config for priority should be outlier detection.
assertThat(priorityChildConfig.policySelection.getProvider().getPolicyName())
.isEqualTo("outlier_detection_experimental");
OutlierDetectionLoadBalancerConfig outlierDetectionConfig =
(OutlierDetectionLoadBalancerConfig) priorityChildConfig.policySelection.getConfig();
// The outlier detection config should faithfully represent what came down from xDS.
assertThat(outlierDetectionConfig.intervalNanos).isEqualTo(outlierDetection.intervalNanos());
assertThat(outlierDetectionConfig.baseEjectionTimeNanos).isEqualTo(
outlierDetection.baseEjectionTimeNanos());
assertThat(outlierDetectionConfig.baseEjectionTimeNanos).isEqualTo(
outlierDetection.baseEjectionTimeNanos());
assertThat(outlierDetectionConfig.maxEjectionTimeNanos).isEqualTo(
outlierDetection.maxEjectionTimeNanos());
assertThat(outlierDetectionConfig.maxEjectionPercent).isEqualTo(
outlierDetection.maxEjectionPercent());
OutlierDetectionLoadBalancerConfig.SuccessRateEjection successRateEjection
= outlierDetectionConfig.successRateEjection;
assertThat(successRateEjection.stdevFactor).isEqualTo(
outlierDetection.successRateEjection().stdevFactor());
assertThat(successRateEjection.enforcementPercentage).isEqualTo(
outlierDetection.successRateEjection().enforcementPercentage());
assertThat(successRateEjection.minimumHosts).isEqualTo(
outlierDetection.successRateEjection().minimumHosts());
assertThat(successRateEjection.requestVolume).isEqualTo(
outlierDetection.successRateEjection().requestVolume());
OutlierDetectionLoadBalancerConfig.FailurePercentageEjection failurePercentageEjection
= outlierDetectionConfig.failurePercentageEjection;
assertThat(failurePercentageEjection.threshold).isEqualTo(
outlierDetection.failurePercentageEjection().threshold());
assertThat(failurePercentageEjection.enforcementPercentage).isEqualTo(
outlierDetection.failurePercentageEjection().enforcementPercentage());
assertThat(failurePercentageEjection.minimumHosts).isEqualTo(
outlierDetection.failurePercentageEjection().minimumHosts());
assertThat(failurePercentageEjection.requestVolume).isEqualTo(
outlierDetection.failurePercentageEjection().requestVolume());
// The wrapped configuration should not have been tampered with.
ClusterImplConfig clusterImplConfig =
(ClusterImplConfig) outlierDetectionConfig.childPolicy.getConfig();
assertClusterImplConfig(clusterImplConfig, CLUSTER1, EDS_SERVICE_NAME1, LRS_SERVER_INFO, 100L,
tlsContext, Collections.<DropOverload>emptyList(), WRR_LOCALITY_POLICY_NAME);
WrrLocalityConfig wrrLocalityConfig =
(WrrLocalityConfig) clusterImplConfig.childPolicy.getConfig();
assertThat(wrrLocalityConfig.childPolicy.getProvider().getPolicyName()).isEqualTo(
"least_request_experimental");
assertThat(
childBalancer.attributes.get(InternalXdsAttributes.ATTR_LOCALITY_WEIGHTS)).containsEntry(
locality1, 100);
}
@Test
public void onlyEdsClusters_receivedEndpoints() {
ClusterResolverConfig config = new ClusterResolverConfig(