xds: clean up value-typed classes (#7863)

This change cleans up most value-typed classes in EnvoyProtoData, which represent immutable xDS configurations used in gRPC. This introduces AutoValue for reducing the amount of boilerplate code for pure data classes.

Not all value-typed classes in xDS have been migrated, some would need more invasive refactoring and would be done next. This change is a pure no-op refactoring. No behavior change should be introduced.

For more details, see PR description.
This commit is contained in:
Chengyuan Zhang 2021-02-05 12:48:38 -08:00 committed by GitHub
parent f0cf435b85
commit 01ed082281
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2381 additions and 3325 deletions

View File

@ -60,6 +60,7 @@ subprojects {
protobufVersion = '3.12.0' protobufVersion = '3.12.0'
protocVersion = protobufVersion protocVersion = protobufVersion
opencensusVersion = '0.28.0' opencensusVersion = '0.28.0'
autovalueVersion = '1.7.4'
configureProtoCompilation = { configureProtoCompilation = {
String generatedSourcePath = "${projectDir}/src/generated" String generatedSourcePath = "${projectDir}/src/generated"
@ -142,6 +143,8 @@ subprojects {
libraries = [ libraries = [
android_annotations: "com.google.android:annotations:4.1.1.4", android_annotations: "com.google.android:annotations:4.1.1.4",
animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.19", animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.19",
autovalue: "com.google.auto.value:auto-value:${autovalueVersion}",
autovalue_annotation: "com.google.auto.value:auto-value-annotations:${autovalueVersion}",
errorprone: "com.google.errorprone:error_prone_annotations:2.4.0", errorprone: "com.google.errorprone:error_prone_annotations:2.4.0",
cronet_api: 'org.chromium.net:cronet-api:76.3809.111', cronet_api: 'org.chromium.net:cronet-api:76.3809.111',
cronet_embedded: 'org.chromium.net:cronet-embedded:66.3359.158', cronet_embedded: 'org.chromium.net:cronet-embedded:66.3359.158',

View File

@ -13,7 +13,13 @@ description = "gRPC: XDS plugin"
it.options.compilerArgs += [ it.options.compilerArgs += [
// valueOf(int) in RoutingPriority has been deprecated // valueOf(int) in RoutingPriority has been deprecated
"-Xlint:-deprecation", "-Xlint:-deprecation",
// only has AutoValue annotation processor
"-Xlint:-processing",
] ]
appendToProperty(
it.options.errorprone.excludedPaths,
".*/build/generated/sources/annotationProcessor/java/.*",
"|")
} }
evaluationDependsOn(project(':grpc-core').path) evaluationDependsOn(project(':grpc-core').path)
@ -27,7 +33,8 @@ dependencies {
project(path: ':grpc-alts', configuration: 'shadow'), project(path: ':grpc-alts', configuration: 'shadow'),
libraries.gson, libraries.gson,
libraries.re2j, libraries.re2j,
libraries.bouncycastle libraries.bouncycastle,
libraries.autovalue_annotation
def nettyDependency = implementation project(':grpc-netty') def nettyDependency = implementation project(':grpc-netty')
implementation (libraries.opencensus_proto) { implementation (libraries.opencensus_proto) {
@ -44,6 +51,7 @@ dependencies {
testImplementation project(':grpc-core').sourceSets.test.output testImplementation project(':grpc-core').sourceSets.test.output
annotationProcessor libraries.autovalue
compileOnly libraries.javax_annotation, compileOnly libraries.javax_annotation,
// At runtime use the epoll included in grpc-netty-shaded // At runtime use the epoll included in grpc-netty-shaded
libraries.netty_epoll libraries.netty_epoll

View File

@ -27,7 +27,6 @@ import io.grpc.internal.GrpcUtil;
import io.grpc.internal.GrpcUtil.GrpcBuildVersion; import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
import io.grpc.internal.JsonParser; import io.grpc.internal.JsonParser;
import io.grpc.internal.JsonUtil; import io.grpc.internal.JsonUtil;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsLogger.XdsLogLevel;
import java.io.IOException; import java.io.IOException;
@ -214,7 +213,7 @@ public class BootstrapperImpl implements Bootstrapper {
if (rawLocality.containsKey("sub_zone")) { if (rawLocality.containsKey("sub_zone")) {
logger.log(XdsLogLevel.INFO, "Locality sub_zone: {0}", subZone); logger.log(XdsLogLevel.INFO, "Locality sub_zone: {0}", subZone);
} }
Locality locality = new Locality(region, zone, subZone); Locality locality = Locality.create(region, zone, subZone);
nodeBuilder.setLocality(locality); nodeBuilder.setLocality(locality);
} }
} }

View File

@ -17,15 +17,18 @@
package io.grpc.xds; package io.grpc.xds;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static io.grpc.xds.EnvoyProtoData.HTTP_FAULT_FILTER_NAME; import static com.google.common.base.Preconditions.checkNotNull;
import static io.grpc.xds.EnvoyProtoData.TRANSPORT_SOCKET_NAME_TLS; import static io.grpc.xds.EnvoyProtoData.TRANSPORT_SOCKET_NAME_TLS;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.Any; import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.Durations; import com.google.protobuf.util.Durations;
import com.google.re2j.Pattern;
import com.google.re2j.PatternSyntaxException;
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds; import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.cluster.v3.Cluster;
import io.envoyproxy.envoy.config.cluster.v3.Cluster.CustomClusterType; import io.envoyproxy.envoy.config.cluster.v3.Cluster.CustomClusterType;
@ -34,31 +37,41 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy;
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions; import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
import io.envoyproxy.envoy.config.core.v3.RoutingPriority; import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint;
import io.envoyproxy.envoy.config.listener.v3.Listener; import io.envoyproxy.envoy.config.listener.v3.Listener;
import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
import io.envoyproxy.envoy.config.route.v3.VirtualHost; import io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter; import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
import io.envoyproxy.envoy.type.v3.FractionalPercent;
import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType;
import io.grpc.EquivalentAddressGroup;
import io.grpc.ManagedChannel; import io.grpc.ManagedChannel;
import io.grpc.Status; 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.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyProtoData.HttpFault; import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.EnvoyProtoData.StructOrError;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.HttpFault.FaultAbort;
import io.grpc.xds.HttpFault.FaultDelay;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
import io.grpc.xds.Matchers.FractionMatcher;
import io.grpc.xds.Matchers.HeaderMatcher;
import io.grpc.xds.Matchers.PathMatcher;
import io.grpc.xds.VirtualHost.Route;
import io.grpc.xds.VirtualHost.Route.RouteAction;
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
import io.grpc.xds.VirtualHost.Route.RouteMatch;
import io.grpc.xds.XdsClient.CdsUpdate.AggregateClusterConfig; import io.grpc.xds.XdsClient.CdsUpdate.AggregateClusterConfig;
import io.grpc.xds.XdsClient.CdsUpdate.ClusterType; import io.grpc.xds.XdsClient.CdsUpdate.ClusterType;
import io.grpc.xds.XdsClient.CdsUpdate.EdsClusterConfig; import io.grpc.xds.XdsClient.CdsUpdate.EdsClusterConfig;
import io.grpc.xds.XdsClient.CdsUpdate.LogicalDnsClusterConfig; import io.grpc.xds.XdsClient.CdsUpdate.LogicalDnsClusterConfig;
import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsLogger.XdsLogLevel;
import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -82,6 +95,7 @@ final class ClientXdsClient extends AbstractXdsClient {
static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15; static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15;
@VisibleForTesting @VisibleForTesting
static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate"; static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
private static final String HTTP_FAULT_FILTER_NAME = "envoy.fault";
private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 =
"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2"
+ ".HttpConnectionManager"; + ".HttpConnectionManager";
@ -176,7 +190,7 @@ final class ClientXdsClient extends AbstractXdsClient {
hasFaultInjection = true; hasFaultInjection = true;
if (httpFilter.hasTypedConfig()) { if (httpFilter.hasTypedConfig()) {
StructOrError<HttpFault> httpFaultOrError = StructOrError<HttpFault> httpFaultOrError =
HttpFault.decodeFaultFilterConfig(httpFilter.getTypedConfig()); decodeFaultFilterConfig(httpFilter.getTypedConfig());
if (httpFaultOrError.getErrorDetail() != null) { if (httpFaultOrError.getErrorDetail() != null) {
nackResponse(ResourceType.LDS, nonce, nackResponse(ResourceType.LDS, nonce,
"Listener " + listenerName + " contains invalid HttpFault filter: " "Listener " + listenerName + " contains invalid HttpFault filter: "
@ -189,10 +203,10 @@ final class ClientXdsClient extends AbstractXdsClient {
} }
} }
if (hcm.hasRouteConfig()) { if (hcm.hasRouteConfig()) {
List<EnvoyProtoData.VirtualHost> virtualHosts = new ArrayList<>(); List<VirtualHost> virtualHosts = new ArrayList<>();
for (VirtualHost virtualHostProto : hcm.getRouteConfig().getVirtualHostsList()) { for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto
StructOrError<EnvoyProtoData.VirtualHost> virtualHost = : hcm.getRouteConfig().getVirtualHostsList()) {
EnvoyProtoData.VirtualHost.fromEnvoyProtoVirtualHost(virtualHostProto); StructOrError<VirtualHost> virtualHost = parseVirtualHost(virtualHostProto);
if (virtualHost.getErrorDetail() != null) { if (virtualHost.getErrorDetail() != null) {
nackResponse(ResourceType.LDS, nonce, nackResponse(ResourceType.LDS, nonce,
"Listener " + listenerName + " contains invalid virtual host: " "Listener " + listenerName + " contains invalid virtual host: "
@ -237,6 +251,386 @@ final class ClientXdsClient extends AbstractXdsClient {
} }
} }
private static StructOrError<VirtualHost> parseVirtualHost(
io.envoyproxy.envoy.config.route.v3.VirtualHost proto) {
String name = proto.getName();
List<Route> routes = new ArrayList<>(proto.getRoutesCount());
for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) {
StructOrError<Route> route = parseRoute(routeProto);
if (route == null) {
continue;
}
if (route.getErrorDetail() != null) {
return StructOrError.fromError(
"Virtual host [" + name + "] contains invalid route : " + route.getErrorDetail());
}
routes.add(route.getStruct());
}
HttpFault httpFault = null;
Map<String, Any> filterConfigMap = proto.getTypedPerFilterConfigMap();
if (filterConfigMap.containsKey(HTTP_FAULT_FILTER_NAME)) {
Any rawFaultFilterConfig = filterConfigMap.get(HTTP_FAULT_FILTER_NAME);
StructOrError<HttpFault> httpFaultOrError = decodeFaultFilterConfig(rawFaultFilterConfig);
if (httpFaultOrError.getErrorDetail() != null) {
return StructOrError.fromError(
"Virtual host [" + name + "] contains invalid HttpFault filter : "
+ httpFaultOrError.getErrorDetail());
}
httpFault = httpFaultOrError.getStruct();
}
return StructOrError.fromStruct(VirtualHost.create(
name, proto.getDomainsList(), routes, httpFault));
}
@VisibleForTesting
@Nullable
static StructOrError<Route> parseRoute(io.envoyproxy.envoy.config.route.v3.Route proto) {
StructOrError<RouteMatch> routeMatch = parseRouteMatch(proto.getMatch());
if (routeMatch == null) {
return null;
}
if (routeMatch.getErrorDetail() != null) {
return StructOrError.fromError(
"Invalid route [" + proto.getName() + "]: " + routeMatch.getErrorDetail());
}
StructOrError<RouteAction> routeAction;
switch (proto.getActionCase()) {
case ROUTE:
routeAction = parseRouteAction(proto.getRoute());
break;
case REDIRECT:
return StructOrError.fromError("Unsupported action type: redirect");
case DIRECT_RESPONSE:
return StructOrError.fromError("Unsupported action type: direct_response");
case FILTER_ACTION:
return StructOrError.fromError("Unsupported action type: filter_action");
case ACTION_NOT_SET:
default:
return StructOrError.fromError("Unknown action type: " + proto.getActionCase());
}
if (routeAction == null) {
return null;
}
if (routeAction.getErrorDetail() != null) {
return StructOrError.fromError(
"Invalid route [" + proto.getName() + "]: " + routeAction.getErrorDetail());
}
HttpFault httpFault = null;
Map<String, Any> filterConfigMap = proto.getTypedPerFilterConfigMap();
if (filterConfigMap.containsKey(HTTP_FAULT_FILTER_NAME)) {
Any rawFaultFilterConfig = filterConfigMap.get(HTTP_FAULT_FILTER_NAME);
StructOrError<HttpFault> httpFaultOrError = decodeFaultFilterConfig(rawFaultFilterConfig);
if (httpFaultOrError.getErrorDetail() != null) {
return StructOrError.fromError(
"Route [" + proto.getName() + "] contains invalid HttpFault filter: "
+ httpFaultOrError.getErrorDetail());
}
httpFault = httpFaultOrError.getStruct();
}
return StructOrError.fromStruct(Route.create(
routeMatch.getStruct(), routeAction.getStruct(), httpFault));
}
@VisibleForTesting
@Nullable
static StructOrError<RouteMatch> parseRouteMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch proto) {
if (proto.getQueryParametersCount() != 0) {
return null;
}
StructOrError<PathMatcher> pathMatch = parsePathMatcher(proto);
if (pathMatch.getErrorDetail() != null) {
return StructOrError.fromError(pathMatch.getErrorDetail());
}
FractionMatcher fractionMatch = null;
if (proto.hasRuntimeFraction()) {
StructOrError<FractionMatcher> parsedFraction =
parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue());
if (parsedFraction.getErrorDetail() != null) {
return StructOrError.fromError(parsedFraction.getErrorDetail());
}
fractionMatch = parsedFraction.getStruct();
}
List<HeaderMatcher> headerMatchers = new ArrayList<>();
for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher hmProto : proto.getHeadersList()) {
StructOrError<HeaderMatcher> headerMatcher = parseHeaderMatcher(hmProto);
if (headerMatcher.getErrorDetail() != null) {
return StructOrError.fromError(headerMatcher.getErrorDetail());
}
headerMatchers.add(headerMatcher.getStruct());
}
return StructOrError.fromStruct(RouteMatch.create(
pathMatch.getStruct(), headerMatchers, fractionMatch));
}
@VisibleForTesting
static StructOrError<PathMatcher> parsePathMatcher(
io.envoyproxy.envoy.config.route.v3.RouteMatch proto) {
boolean caseSensitive = proto.getCaseSensitive().getValue();
switch (proto.getPathSpecifierCase()) {
case PREFIX:
return StructOrError.fromStruct(
PathMatcher.fromPrefix(proto.getPrefix(), caseSensitive));
case PATH:
return StructOrError.fromStruct(PathMatcher.fromPath(proto.getPath(), caseSensitive));
case SAFE_REGEX:
String rawPattern = proto.getSafeRegex().getRegex();
Pattern safeRegEx;
try {
safeRegEx = Pattern.compile(rawPattern);
} catch (PatternSyntaxException e) {
return StructOrError.fromError("Malformed safe regex pattern: " + e.getMessage());
}
return StructOrError.fromStruct(PathMatcher.fromRegEx(safeRegEx));
case PATHSPECIFIER_NOT_SET:
default:
return StructOrError.fromError("Unknown path match type");
}
}
private static StructOrError<FractionMatcher> parseFractionMatcher(
io.envoyproxy.envoy.type.v3.FractionalPercent proto) {
int numerator = proto.getNumerator();
int denominator = 0;
switch (proto.getDenominator()) {
case HUNDRED:
denominator = 100;
break;
case TEN_THOUSAND:
denominator = 10_000;
break;
case MILLION:
denominator = 1_000_000;
break;
case UNRECOGNIZED:
default:
return StructOrError.fromError(
"Unrecognized fractional percent denominator: " + proto.getDenominator());
}
return StructOrError.fromStruct(FractionMatcher.create(numerator, denominator));
}
@VisibleForTesting
static StructOrError<HeaderMatcher> parseHeaderMatcher(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
switch (proto.getHeaderMatchSpecifierCase()) {
case EXACT_MATCH:
return StructOrError.fromStruct(HeaderMatcher.forExactValue(
proto.getName(), proto.getExactMatch(), proto.getInvertMatch()));
case SAFE_REGEX_MATCH:
String rawPattern = proto.getSafeRegexMatch().getRegex();
Pattern safeRegExMatch;
try {
safeRegExMatch = Pattern.compile(rawPattern);
} catch (PatternSyntaxException e) {
return StructOrError.fromError(
"HeaderMatcher [" + proto.getName() + "] contains malformed safe regex pattern: "
+ e.getMessage());
}
return StructOrError.fromStruct(HeaderMatcher.forSafeRegEx(
proto.getName(), safeRegExMatch, proto.getInvertMatch()));
case RANGE_MATCH:
HeaderMatcher.Range rangeMatch = HeaderMatcher.Range.create(
proto.getRangeMatch().getStart(), proto.getRangeMatch().getEnd());
return StructOrError.fromStruct(HeaderMatcher.forRange(
proto.getName(), rangeMatch, proto.getInvertMatch()));
case PRESENT_MATCH:
return StructOrError.fromStruct(HeaderMatcher.forPresent(
proto.getName(), proto.getPresentMatch(), proto.getInvertMatch()));
case PREFIX_MATCH:
return StructOrError.fromStruct(HeaderMatcher.forPrefix(
proto.getName(), proto.getPrefixMatch(), proto.getInvertMatch()));
case SUFFIX_MATCH:
return StructOrError.fromStruct(HeaderMatcher.forSuffix(
proto.getName(), proto.getSuffixMatch(), proto.getInvertMatch()));
case HEADERMATCHSPECIFIER_NOT_SET:
default:
return StructOrError.fromError("Unknown header matcher type");
}
}
@VisibleForTesting
@Nullable
static StructOrError<RouteAction> parseRouteAction(
io.envoyproxy.envoy.config.route.v3.RouteAction proto) {
Long timeoutNano = null;
if (proto.hasMaxStreamDuration()) {
io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration maxStreamDuration
= proto.getMaxStreamDuration();
if (maxStreamDuration.hasGrpcTimeoutHeaderMax()) {
timeoutNano = Durations.toNanos(maxStreamDuration.getGrpcTimeoutHeaderMax());
} else if (maxStreamDuration.hasMaxStreamDuration()) {
timeoutNano = Durations.toNanos(maxStreamDuration.getMaxStreamDuration());
}
}
List<ClusterWeight> weightedClusters;
switch (proto.getClusterSpecifierCase()) {
case CLUSTER:
return StructOrError.fromStruct(RouteAction.forCluster(proto.getCluster(), timeoutNano));
case CLUSTER_HEADER:
return null;
case WEIGHTED_CLUSTERS:
List<io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight> clusterWeights
= proto.getWeightedClusters().getClustersList();
if (clusterWeights.isEmpty()) {
return StructOrError.fromError("No cluster found in weighted cluster list");
}
weightedClusters = new ArrayList<>();
for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight
: clusterWeights) {
StructOrError<ClusterWeight> clusterWeightOrError = parseClusterWeight(clusterWeight);
if (clusterWeightOrError.getErrorDetail() != null) {
return StructOrError.fromError("RouteAction contains invalid ClusterWeight: "
+ clusterWeightOrError.getErrorDetail());
}
weightedClusters.add(clusterWeightOrError.getStruct());
}
// TODO(chengyuanzhang): validate if the sum of weights equals to total weight.
return StructOrError.fromStruct(RouteAction.forWeightedClusters(
weightedClusters, timeoutNano));
case CLUSTERSPECIFIER_NOT_SET:
default:
return StructOrError.fromError(
"Unknown cluster specifier: " + proto.getClusterSpecifierCase());
}
}
@VisibleForTesting
static StructOrError<ClusterWeight> parseClusterWeight(
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto) {
HttpFault httpFault = null;
Map<String, Any> filterConfigMap = proto.getTypedPerFilterConfigMap();
if (filterConfigMap.containsKey(HTTP_FAULT_FILTER_NAME)) {
Any rawFaultFilterConfig = filterConfigMap.get(HTTP_FAULT_FILTER_NAME);
StructOrError<HttpFault> httpFaultOrError = decodeFaultFilterConfig(rawFaultFilterConfig);
if (httpFaultOrError.getErrorDetail() != null) {
return StructOrError.fromError(
"ClusterWeight [" + proto.getName() + "] contains invalid HttpFault filter: "
+ httpFaultOrError.getErrorDetail());
}
httpFault = httpFaultOrError.getStruct();
}
return StructOrError.fromStruct(
ClusterWeight.create(proto.getName(), proto.getWeight().getValue(), httpFault));
}
private static StructOrError<HttpFault> decodeFaultFilterConfig(Any rawFaultFilterConfig) {
if (rawFaultFilterConfig.getTypeUrl().equals(
"type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault")) {
rawFaultFilterConfig = rawFaultFilterConfig.toBuilder().setTypeUrl(
"type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault").build();
}
HTTPFault httpFaultProto;
try {
httpFaultProto = rawFaultFilterConfig.unpack(HTTPFault.class);
} catch (InvalidProtocolBufferException e) {
return StructOrError.fromError("Invalid proto: " + e);
}
return parseHttpFault(httpFaultProto);
}
private static StructOrError<HttpFault> parseHttpFault(HTTPFault httpFault) {
FaultDelay faultDelay = null;
FaultAbort faultAbort = null;
if (httpFault.hasDelay()) {
faultDelay = parseFaultDelay(httpFault.getDelay());
}
if (httpFault.hasAbort()) {
StructOrError<FaultAbort> faultAbortOrError = parseFaultAbort(httpFault.getAbort());
if (faultAbortOrError.getErrorDetail() != null) {
return StructOrError.fromError(
"HttpFault contains invalid FaultAbort: " + faultAbortOrError.getErrorDetail());
}
faultAbort = faultAbortOrError.getStruct();
}
if (faultDelay == null && faultAbort == null) {
return StructOrError.fromError(
"Invalid HttpFault: neither fault_delay nor fault_abort is specified");
}
String upstreamCluster = httpFault.getUpstreamCluster();
List<String> downstreamNodes = httpFault.getDownstreamNodesList();
List<HeaderMatcher> headers = new ArrayList<>();
for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto : httpFault.getHeadersList()) {
StructOrError<HeaderMatcher> headerMatcherOrError = parseHeaderMatcher(proto);
if (headerMatcherOrError.getErrorDetail() != null) {
return StructOrError.fromError(
"HttpFault contains invalid header matcher: "
+ headerMatcherOrError.getErrorDetail());
}
headers.add(headerMatcherOrError.getStruct());
}
Integer maxActiveFaults = null;
if (httpFault.hasMaxActiveFaults()) {
maxActiveFaults = httpFault.getMaxActiveFaults().getValue();
if (maxActiveFaults < 0) {
maxActiveFaults = Integer.MAX_VALUE;
}
}
return StructOrError.fromStruct(HttpFault.create(
faultDelay, faultAbort, upstreamCluster, downstreamNodes, headers, maxActiveFaults));
}
private static FaultDelay parseFaultDelay(
io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay faultDelay) {
int rate = getRatePerMillion(faultDelay.getPercentage());
if (faultDelay.hasHeaderDelay()) {
return FaultDelay.forHeader(rate);
}
return FaultDelay.forFixedDelay(Durations.toNanos(faultDelay.getFixedDelay()), rate);
}
@VisibleForTesting
static StructOrError<FaultAbort> parseFaultAbort(
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort faultAbort) {
int rate = getRatePerMillion(faultAbort.getPercentage());
switch (faultAbort.getErrorTypeCase()) {
case HEADER_ABORT:
return StructOrError.fromStruct(FaultAbort.forHeader(rate));
case HTTP_STATUS:
return StructOrError.fromStruct(FaultAbort.forStatus(
convertHttpStatus(faultAbort.getHttpStatus()), rate));
case GRPC_STATUS:
return StructOrError.fromStruct(FaultAbort.forStatus(
Status.fromCodeValue(faultAbort.getGrpcStatus()), rate));
case ERRORTYPE_NOT_SET:
default:
return StructOrError.fromError(
"Unknown error type case: " + faultAbort.getErrorTypeCase());
}
}
private static Status convertHttpStatus(int httpCode) {
Status status;
switch (httpCode) {
case 400:
status = Status.INTERNAL;
break;
case 401:
status = Status.UNAUTHENTICATED;
break;
case 403:
status = Status.PERMISSION_DENIED;
break;
case 404:
status = Status.UNIMPLEMENTED;
break;
case 429:
case 502:
case 503:
case 504:
status = Status.UNAVAILABLE;
break;
default:
status = Status.UNKNOWN;
}
return status.withDescription("HTTP code: " + httpCode);
}
@Override @Override
protected void handleRdsResponse(String versionInfo, List<Any> resources, String nonce) { protected void handleRdsResponse(String versionInfo, List<Any> resources, String nonce) {
// Unpack RouteConfiguration messages. // Unpack RouteConfiguration messages.
@ -262,11 +656,11 @@ final class ClientXdsClient extends AbstractXdsClient {
for (Map.Entry<String, RouteConfiguration> entry : routeConfigs.entrySet()) { for (Map.Entry<String, RouteConfiguration> entry : routeConfigs.entrySet()) {
String routeConfigName = entry.getKey(); String routeConfigName = entry.getKey();
RouteConfiguration routeConfig = entry.getValue(); RouteConfiguration routeConfig = entry.getValue();
List<EnvoyProtoData.VirtualHost> virtualHosts = List<VirtualHost> virtualHosts =
new ArrayList<>(routeConfig.getVirtualHostsCount()); new ArrayList<>(routeConfig.getVirtualHostsCount());
for (VirtualHost virtualHostProto : routeConfig.getVirtualHostsList()) { for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto
StructOrError<EnvoyProtoData.VirtualHost> virtualHost = : routeConfig.getVirtualHostsList()) {
EnvoyProtoData.VirtualHost.fromEnvoyProtoVirtualHost(virtualHostProto); StructOrError<VirtualHost> virtualHost = parseVirtualHost(virtualHostProto);
if (virtualHost.getErrorDetail() != null) { if (virtualHost.getErrorDetail() != null) {
nackResponse(ResourceType.RDS, nonce, "RouteConfiguration " + routeConfigName nackResponse(ResourceType.RDS, nonce, "RouteConfiguration " + routeConfigName
+ " contains invalid virtual host: " + virtualHost.getErrorDetail()); + " contains invalid virtual host: " + virtualHost.getErrorDetail());
@ -508,45 +902,35 @@ final class ClientXdsClient extends AbstractXdsClient {
Map<Locality, LocalityLbEndpoints> localityLbEndpointsMap = new LinkedHashMap<>(); Map<Locality, LocalityLbEndpoints> localityLbEndpointsMap = new LinkedHashMap<>();
List<DropOverload> dropOverloads = new ArrayList<>(); List<DropOverload> dropOverloads = new ArrayList<>();
int maxPriority = -1; int maxPriority = -1;
for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpoints for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto
: assignment.getEndpointsList()) { : assignment.getEndpointsList()) {
// Filter out localities without or with 0 weight. StructOrError<LocalityLbEndpoints> localityLbEndpoints =
if (!localityLbEndpoints.hasLoadBalancingWeight() parseLocalityLbEndpoints(localityLbEndpointsProto);
|| localityLbEndpoints.getLoadBalancingWeight().getValue() < 1) { if (localityLbEndpoints == null) {
continue; continue;
} }
int localityPriority = localityLbEndpoints.getPriority(); if (localityLbEndpoints.getErrorDetail() != null) {
if (localityPriority < 0) { nackResponse(ResourceType.EDS, nonce, "ClusterLoadAssignment " + clusterName + ": "
nackResponse(ResourceType.EDS, nonce, + localityLbEndpoints.getErrorDetail());
"ClusterLoadAssignment " + clusterName + " : locality with negative priority.");
return; return;
} }
maxPriority = Math.max(maxPriority, localityPriority); maxPriority = Math.max(maxPriority, localityLbEndpoints.getStruct().priority());
priorities.add(localityPriority); priorities.add(localityLbEndpoints.getStruct().priority());
// The endpoint field of each lb_endpoints must be set. // Note endpoints with health status other than HEALTHY and UNKNOWN are still
// Inside of it: the address field must be set.
for (LbEndpoint lbEndpoint : localityLbEndpoints.getLbEndpointsList()) {
if (!lbEndpoint.getEndpoint().hasAddress()) {
nackResponse(ResourceType.EDS, nonce,
"ClusterLoadAssignment " + clusterName + " : endpoint with no address.");
return;
}
}
// Note endpoints with health status other than UNHEALTHY and UNKNOWN are still
// handed over to watching parties. It is watching parties' responsibility to // handed over to watching parties. It is watching parties' responsibility to
// filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy(). // filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy().
localityLbEndpointsMap.put( localityLbEndpointsMap.put(
Locality.fromEnvoyProtoLocality(localityLbEndpoints.getLocality()), parseLocality(localityLbEndpointsProto.getLocality()),
LocalityLbEndpoints.fromEnvoyProtoLocalityLbEndpoints(localityLbEndpoints)); localityLbEndpoints.getStruct());
} }
if (priorities.size() != maxPriority + 1) { if (priorities.size() != maxPriority + 1) {
nackResponse(ResourceType.EDS, nonce, nackResponse(ResourceType.EDS, nonce,
"ClusterLoadAssignment " + clusterName + " : sparse priorities."); "ClusterLoadAssignment " + clusterName + " : sparse priorities.");
return; return;
} }
for (ClusterLoadAssignment.Policy.DropOverload dropOverload for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto
: assignment.getPolicy().getDropOverloadsList()) { : assignment.getPolicy().getDropOverloadsList()) {
dropOverloads.add(DropOverload.fromEnvoyProtoDropOverload(dropOverload)); dropOverloads.add(parseDropOverload(dropOverloadProto));
} }
EdsUpdate update = new EdsUpdate(clusterName, localityLbEndpointsMap, dropOverloads); EdsUpdate update = new EdsUpdate(clusterName, localityLbEndpointsMap, dropOverloads);
edsUpdates.put(clusterName, update); edsUpdates.put(clusterName, update);
@ -561,6 +945,72 @@ final class ClientXdsClient extends AbstractXdsClient {
} }
} }
private static Locality parseLocality(io.envoyproxy.envoy.config.core.v3.Locality proto) {
return Locality.create(proto.getRegion(), proto.getZone(), proto.getSubZone());
}
private static DropOverload parseDropOverload(
io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload proto) {
return DropOverload.create(proto.getCategory(), getRatePerMillion(proto.getDropPercentage()));
}
@VisibleForTesting
@Nullable
static StructOrError<LocalityLbEndpoints> parseLocalityLbEndpoints(
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto) {
// Filter out localities without or with 0 weight.
if (!proto.hasLoadBalancingWeight() || proto.getLoadBalancingWeight().getValue() < 1) {
return null;
}
if (proto.getPriority() < 0) {
return StructOrError.fromError("negative priority");
}
List<LbEndpoint> endpoints = new ArrayList<>(proto.getLbEndpointsCount());
for (io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint endpoint : proto.getLbEndpointsList()) {
// The endpoint field of each lb_endpoints must be set.
// Inside of it: the address field must be set.
if (!endpoint.hasEndpoint() || !endpoint.getEndpoint().hasAddress()) {
return StructOrError.fromError("LbEndpoint with no endpoint/address");
}
io.envoyproxy.envoy.config.core.v3.SocketAddress socketAddress =
endpoint.getEndpoint().getAddress().getSocketAddress();
InetSocketAddress addr =
new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue());
boolean isHealthy =
endpoint.getHealthStatus() == io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY
|| endpoint.getHealthStatus()
== io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN;
endpoints.add(LbEndpoint.create(
new EquivalentAddressGroup(ImmutableList.<java.net.SocketAddress>of(addr)),
endpoint.getLoadBalancingWeight().getValue(), isHealthy));
}
return StructOrError.fromStruct(LocalityLbEndpoints.create(
endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority()));
}
private static int getRatePerMillion(FractionalPercent percent) {
int numerator = percent.getNumerator();
DenominatorType type = percent.getDenominator();
switch (type) {
case TEN_THOUSAND:
numerator *= 100;
break;
case HUNDRED:
numerator *= 10_000;
break;
case MILLION:
break;
case UNRECOGNIZED:
default:
throw new IllegalArgumentException("Unknown denominator type of " + percent);
}
if (numerator > 1_000_000 || numerator < 0) {
numerator = 1_000_000;
}
return numerator;
}
@Override @Override
protected void handleStreamClosed(Status error) { protected void handleStreamClosed(Status error) {
cleanUpResourceTimers(); cleanUpResourceTimers();
@ -933,4 +1383,53 @@ final class ClientXdsClient extends AbstractXdsClient {
} }
} }
} }
@VisibleForTesting
static final class StructOrError<T> {
/**
* Returns a {@link StructOrError} for the successfully converted data object.
*/
private static <T> StructOrError<T> fromStruct(T struct) {
return new StructOrError<>(struct);
}
/**
* Returns a {@link StructOrError} for the failure to convert the data object.
*/
private static <T> StructOrError<T> fromError(String errorDetail) {
return new StructOrError<>(errorDetail);
}
private final String errorDetail;
private final T struct;
private StructOrError(T struct) {
this.struct = checkNotNull(struct, "struct");
this.errorDetail = null;
}
private StructOrError(String errorDetail) {
this.struct = null;
this.errorDetail = checkNotNull(errorDetail, "errorDetail");
}
/**
* Returns struct if exists, otherwise null.
*/
@VisibleForTesting
@Nullable
T getStruct() {
return struct;
}
/**
* Returns error detail if exists, otherwise null.
*/
@VisibleForTesting
@Nullable
String getErrorDetail() {
return errorDetail;
}
}
} }

View File

@ -34,8 +34,7 @@ import io.grpc.util.ForwardingClientStreamTracer;
import io.grpc.util.ForwardingLoadBalancerHelper; import io.grpc.util.ForwardingLoadBalancerHelper;
import io.grpc.util.ForwardingSubchannel; import io.grpc.util.ForwardingSubchannel;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
@ -213,7 +212,7 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
// attributes with its locality, including endpoints in LOGICAL_DNS clusters. // attributes with its locality, including endpoints in LOGICAL_DNS clusters.
// In case of not (which really shouldn't), loads are aggregated under an empty locality. // In case of not (which really shouldn't), loads are aggregated under an empty locality.
if (locality == null) { if (locality == null) {
locality = new Locality("", "", ""); locality = Locality.create("", "", "");
} }
final ClusterLocalityStats localityStats = xdsClient.addClusterLocalityStats( final ClusterLocalityStats localityStats = xdsClient.addClusterLocalityStats(
cluster, edsServiceName, locality); cluster, edsServiceName, locality);
@ -292,14 +291,14 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
public PickResult pickSubchannel(PickSubchannelArgs args) { public PickResult pickSubchannel(PickSubchannelArgs args) {
for (DropOverload dropOverload : dropPolicies) { for (DropOverload dropOverload : dropPolicies) {
int rand = random.nextInt(1_000_000); int rand = random.nextInt(1_000_000);
if (rand < dropOverload.getDropsPerMillion()) { if (rand < dropOverload.dropsPerMillion()) {
logger.log(XdsLogLevel.INFO, "Drop request with category: {0}", logger.log(XdsLogLevel.INFO, "Drop request with category: {0}",
dropOverload.getCategory()); dropOverload.category());
if (dropStats != null) { if (dropStats != null) {
dropStats.recordDroppedRequest(dropOverload.getCategory()); dropStats.recordDroppedRequest(dropOverload.category());
} }
return PickResult.withDrop( return PickResult.withDrop(
Status.UNAVAILABLE.withDescription("Dropped: " + dropOverload.getCategory())); Status.UNAVAILABLE.withDescription("Dropped: " + dropOverload.category()));
} }
} }
final PickResult result = delegate.pickSubchannel(args); final PickResult result = delegate.pickSubchannel(args);

View File

@ -26,7 +26,7 @@ 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.internal.ServiceConfigUtil.PolicySelection; import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;

View File

@ -43,10 +43,9 @@ import io.grpc.util.GracefulSwitchLoadBalancer;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
@ -78,7 +77,7 @@ import javax.annotation.Nullable;
*/ */
final class ClusterResolverLoadBalancer extends LoadBalancer { final class ClusterResolverLoadBalancer extends LoadBalancer {
private static final Locality LOGICAL_DNS_CLUSTER_LOCALITY = new Locality("", "", ""); private static final Locality LOGICAL_DNS_CLUSTER_LOCALITY = Locality.create("", "", "");
private final XdsLogger logger; private final XdsLogger logger;
private final String authority; private final String authority;
private final SynchronizationContext syncContext; private final SynchronizationContext syncContext;
@ -385,16 +384,16 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>(); Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>();
for (Locality locality : localityLbEndpoints.keySet()) { for (Locality locality : localityLbEndpoints.keySet()) {
LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality); LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
int priority = localityLbInfo.getPriority(); int priority = localityLbInfo.priority();
String priorityName = priorityName(name, priority); String priorityName = priorityName(name, priority);
boolean discard = true; boolean discard = true;
for (LbEndpoint endpoint : localityLbInfo.getEndpoints()) { for (LbEndpoint endpoint : localityLbInfo.endpoints()) {
if (endpoint.isHealthy()) { if (endpoint.isHealthy()) {
discard = false; discard = false;
Attributes attr = endpoint.getAddress().getAttributes().toBuilder() Attributes attr = endpoint.eag().getAttributes().toBuilder()
.set(InternalXdsAttributes.ATTR_LOCALITY, locality).build(); .set(InternalXdsAttributes.ATTR_LOCALITY, locality).build();
EquivalentAddressGroup eag = EquivalentAddressGroup eag =
new EquivalentAddressGroup(endpoint.getAddress().getAddresses(), attr); new EquivalentAddressGroup(endpoint.eag().getAddresses(), attr);
eag = AddressFilter.setPathFilter( eag = AddressFilter.setPathFilter(
eag, Arrays.asList(priorityName, localityName(locality))); eag, Arrays.asList(priorityName, localityName(locality)));
addresses.add(eag); addresses.add(eag);
@ -409,7 +408,7 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>()); prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>());
} }
prioritizedLocalityWeights.get(priorityName).put( prioritizedLocalityWeights.get(priorityName).put(
locality, localityLbInfo.getLocalityWeight()); locality, localityLbInfo.localityWeight());
} }
if (prioritizedLocalityWeights.isEmpty()) { if (prioritizedLocalityWeights.isEmpty()) {
// Will still update the result, as if the cluster resource is revoked. // Will still update the result, as if the cluster resource is revoked.

View File

@ -0,0 +1,86 @@
/*
* Copyright 2021 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.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import io.grpc.EquivalentAddressGroup;
import java.net.InetSocketAddress;
import java.util.List;
/** Locality and endpoint level load balancing configurations. */
final class Endpoints {
private Endpoints() {}
/** Represents a group of endpoints belong to a single locality. */
@AutoValue
abstract static class LocalityLbEndpoints {
// Endpoints to be load balanced.
abstract ImmutableList<LbEndpoint> endpoints();
// Locality's weight for inter-locality load balancing.
abstract int localityWeight();
// Locality's priority level.
abstract int priority();
static LocalityLbEndpoints create(List<LbEndpoint> endpoints, int localityWeight,
int priority) {
return new AutoValue_Endpoints_LocalityLbEndpoints(
ImmutableList.copyOf(endpoints), localityWeight, priority);
}
}
/** Represents a single endpoint to be load balanced. */
@AutoValue
abstract static class LbEndpoint {
// The endpoint address to be connected to.
abstract EquivalentAddressGroup eag();
// Endpoint's wight for load balancing.
abstract int loadBalancingWeight();
// Whether the endpoint is healthy.
abstract boolean isHealthy();
static LbEndpoint create(EquivalentAddressGroup eag, int loadBalancingWeight,
boolean isHealthy) {
return new AutoValue_Endpoints_LbEndpoint(eag, loadBalancingWeight, isHealthy);
}
// Only for testing.
@VisibleForTesting
static LbEndpoint create(
String address, int port, int loadBalancingWeight, boolean isHealthy) {
return LbEndpoint.create(new EquivalentAddressGroup(new InetSocketAddress(address, port)),
loadBalancingWeight, isHealthy);
}
}
/** Represents a drop policy. */
@AutoValue
abstract static class DropOverload {
abstract String category();
abstract int dropsPerMillion();
static DropOverload create(String category, int dropsPerMillion) {
return new AutoValue_Endpoints_DropOverload(category, dropsPerMillion);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,101 @@
/*
* Copyright 2021 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 com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import io.grpc.Status;
import io.grpc.xds.Matchers.HeaderMatcher;
import java.util.List;
import javax.annotation.Nullable;
/** Fault injection configurations. */
@AutoValue
abstract class HttpFault {
@Nullable
abstract FaultDelay faultDelay();
@Nullable
abstract FaultAbort faultAbort();
abstract String upstreamCluster();
abstract ImmutableList<String> downstreamNodes();
abstract ImmutableList<HeaderMatcher> headers();
@Nullable
abstract Integer maxActiveFaults();
static HttpFault create(@Nullable FaultDelay faultDelay, @Nullable FaultAbort faultAbort,
String upstreamCluster, List<String> downstreamNodes, List<HeaderMatcher> headers,
@Nullable Integer maxActiveFaults) {
return new AutoValue_HttpFault(faultDelay, faultAbort, upstreamCluster,
ImmutableList.copyOf(downstreamNodes), ImmutableList.copyOf(headers), maxActiveFaults);
}
/** Fault configurations for aborting requests. */
@AutoValue
abstract static class FaultDelay {
@Nullable
abstract Long delayNanos();
abstract boolean headerDelay();
abstract int ratePerMillion();
static FaultDelay forFixedDelay(long delayNanos, int ratePerMillion) {
return FaultDelay.create(delayNanos, false, ratePerMillion);
}
static FaultDelay forHeader(int ratePerMillion) {
return FaultDelay.create(null, true, ratePerMillion);
}
private static FaultDelay create(
@Nullable Long delayNanos, boolean headerDelay, int ratePerMillion) {
return new AutoValue_HttpFault_FaultDelay(delayNanos, headerDelay, ratePerMillion);
}
}
/** Fault configurations for delaying requests. */
@AutoValue
abstract static class FaultAbort {
@Nullable
abstract Status status();
abstract boolean headerAbort();
abstract int ratePerMillion();
static FaultAbort forStatus(Status status, int ratePerMillion) {
checkNotNull(status, "status");
return FaultAbort.create(status, false, ratePerMillion);
}
static FaultAbort forHeader(int ratePerMillion) {
return FaultAbort.create(null, true, ratePerMillion);
}
private static FaultAbort create(
@Nullable Status status, boolean headerAbort, int ratePerMillion) {
return new AutoValue_HttpFault_FaultAbort(status, headerAbort, ratePerMillion);
}
}
}

View File

@ -22,7 +22,6 @@ import io.grpc.Grpc;
import io.grpc.Internal; import io.grpc.Internal;
import io.grpc.NameResolver; import io.grpc.NameResolver;
import io.grpc.internal.ObjectPool; import io.grpc.internal.ObjectPool;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
import io.grpc.xds.internal.sds.SslContextProviderSupplier; import io.grpc.xds.internal.sds.SslContextProviderSupplier;

View File

@ -35,8 +35,10 @@ import io.grpc.SynchronizationContext;
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.EnvoyProtoData.ClusterStats;
import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.Stats.ClusterStats;
import io.grpc.xds.Stats.DroppedRequests;
import io.grpc.xds.Stats.UpstreamLocalityStats;
import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsLogger.XdsLogLevel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -346,7 +348,7 @@ final class LoadReportClient {
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest.newBuilder() io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest.newBuilder()
.setNode(node.toEnvoyProtoNodeV2()); .setNode(node.toEnvoyProtoNodeV2());
for (ClusterStats stats : clusterStatsList) { for (ClusterStats stats : clusterStatsList) {
requestBuilder.addClusterStats(stats.toEnvoyProtoClusterStatsV2()); requestBuilder.addClusterStats(buildClusterStats(stats));
} }
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest request = requestBuilder.build(); io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest request = requestBuilder.build();
lrsRequestWriterV2.onNext(requestBuilder.build()); lrsRequestWriterV2.onNext(requestBuilder.build());
@ -357,6 +359,37 @@ final class LoadReportClient {
void sendError(Exception error) { void sendError(Exception error) {
lrsRequestWriterV2.onError(error); lrsRequestWriterV2.onError(error);
} }
private io.envoyproxy.envoy.api.v2.endpoint.ClusterStats buildClusterStats(
ClusterStats stats) {
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.Builder builder =
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.newBuilder()
.setClusterName(stats.clusterName());
if (stats.clusterServiceName() != null) {
builder.setClusterServiceName(stats.clusterServiceName());
}
for (UpstreamLocalityStats upstreamLocalityStats : stats.upstreamLocalityStatsList()) {
builder.addUpstreamLocalityStats(
io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.newBuilder()
.setLocality(
io.envoyproxy.envoy.api.v2.core.Locality.newBuilder()
.setRegion(upstreamLocalityStats.locality().region())
.setZone(upstreamLocalityStats.locality().zone())
.setSubZone(upstreamLocalityStats.locality().subZone()))
.setTotalSuccessfulRequests(upstreamLocalityStats.totalSuccessfulRequests())
.setTotalErrorRequests(upstreamLocalityStats.totalErrorRequests())
.setTotalRequestsInProgress(upstreamLocalityStats.totalRequestsInProgress())
.setTotalIssuedRequests(upstreamLocalityStats.totalIssuedRequests()));
}
for (DroppedRequests droppedRequests : stats.droppedRequestsList()) {
builder.addDroppedRequests(
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests.newBuilder()
.setCategory(droppedRequests.category())
.setDroppedCount(droppedRequests.droppedCount()));
}
return builder.setTotalDroppedRequests(stats.totalDroppedRequests())
.setLoadReportInterval(Durations.fromNanos(stats.loadReportIntervalNano())).build();
}
} }
private final class LrsStreamV3 extends LrsStream { private final class LrsStreamV3 extends LrsStream {
@ -410,7 +443,7 @@ final class LoadReportClient {
LoadStatsRequest.Builder requestBuilder = LoadStatsRequest.Builder requestBuilder =
LoadStatsRequest.newBuilder().setNode(node.toEnvoyProtoNode()); LoadStatsRequest.newBuilder().setNode(node.toEnvoyProtoNode());
for (ClusterStats stats : clusterStatsList) { for (ClusterStats stats : clusterStatsList) {
requestBuilder.addClusterStats(stats.toEnvoyProtoClusterStats()); requestBuilder.addClusterStats(buildClusterStats(stats));
} }
LoadStatsRequest request = requestBuilder.build(); LoadStatsRequest request = requestBuilder.build();
lrsRequestWriterV3.onNext(request); lrsRequestWriterV3.onNext(request);
@ -421,5 +454,38 @@ final class LoadReportClient {
void sendError(Exception error) { void sendError(Exception error) {
lrsRequestWriterV3.onError(error); lrsRequestWriterV3.onError(error);
} }
private io.envoyproxy.envoy.config.endpoint.v3.ClusterStats buildClusterStats(
ClusterStats stats) {
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.Builder builder =
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.newBuilder()
.setClusterName(stats.clusterName());
if (stats.clusterServiceName() != null) {
builder.setClusterServiceName(stats.clusterServiceName());
}
for (UpstreamLocalityStats upstreamLocalityStats : stats.upstreamLocalityStatsList()) {
builder.addUpstreamLocalityStats(
io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.newBuilder()
.setLocality(
io.envoyproxy.envoy.config.core.v3.Locality.newBuilder()
.setRegion(upstreamLocalityStats.locality().region())
.setZone(upstreamLocalityStats.locality().zone())
.setSubZone(upstreamLocalityStats.locality().subZone()))
.setTotalSuccessfulRequests(upstreamLocalityStats.totalSuccessfulRequests())
.setTotalErrorRequests(upstreamLocalityStats.totalErrorRequests())
.setTotalRequestsInProgress(upstreamLocalityStats.totalRequestsInProgress())
.setTotalIssuedRequests(upstreamLocalityStats.totalIssuedRequests()));
}
for (DroppedRequests droppedRequests : stats.droppedRequestsList()) {
builder.addDroppedRequests(
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests.newBuilder()
.setCategory(droppedRequests.category())
.setDroppedCount(droppedRequests.droppedCount()));
}
return builder
.setTotalDroppedRequests(stats.totalDroppedRequests())
.setLoadReportInterval(Durations.fromNanos(stats.loadReportIntervalNano()))
.build();
}
} }
} }

View File

@ -23,10 +23,9 @@ import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.xds.EnvoyProtoData.ClusterStats; import io.grpc.xds.Stats.ClusterStats;
import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests; import io.grpc.xds.Stats.DroppedRequests;
import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.Stats.UpstreamLocalityStats;
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -153,9 +152,9 @@ final class LoadStatsManager2 {
if (clusterDropStats != null) { if (clusterDropStats != null) {
Set<String> toDiscard = new HashSet<>(); Set<String> toDiscard = new HashSet<>();
for (String edsServiceName : clusterDropStats.keySet()) { for (String edsServiceName : clusterDropStats.keySet()) {
ClusterStats.Builder builder = ClusterStats.newBuilder().setClusterName(cluster); ClusterStats.Builder builder = ClusterStats.newBuilder().clusterName(cluster);
if (edsServiceName != null) { if (edsServiceName != null) {
builder.setClusterServiceName(edsServiceName); builder.clusterServiceName(edsServiceName);
} }
ReferenceCounted<ClusterDropStats> ref = clusterDropStats.get(edsServiceName); ReferenceCounted<ClusterDropStats> ref = clusterDropStats.get(edsServiceName);
if (ref.getReferenceCount() == 0) { // stats object no longer needed after snapshot if (ref.getReferenceCount() == 0) { // stats object no longer needed after snapshot
@ -164,12 +163,12 @@ final class LoadStatsManager2 {
ClusterDropStatsSnapshot dropStatsSnapshot = ref.get().snapshot(); ClusterDropStatsSnapshot dropStatsSnapshot = ref.get().snapshot();
long totalCategorizedDrops = 0L; long totalCategorizedDrops = 0L;
for (Map.Entry<String, Long> entry : dropStatsSnapshot.categorizedDrops.entrySet()) { for (Map.Entry<String, Long> entry : dropStatsSnapshot.categorizedDrops.entrySet()) {
builder.addDroppedRequests(new DroppedRequests(entry.getKey(), entry.getValue())); builder.addDroppedRequests(DroppedRequests.create(entry.getKey(), entry.getValue()));
totalCategorizedDrops += entry.getValue(); totalCategorizedDrops += entry.getValue();
} }
builder.setTotalDroppedRequests( builder.totalDroppedRequests(
totalCategorizedDrops + dropStatsSnapshot.uncategorizedDrops); totalCategorizedDrops + dropStatsSnapshot.uncategorizedDrops);
builder.setLoadReportIntervalNanos(dropStatsSnapshot.durationNano); builder.loadReportIntervalNano(dropStatsSnapshot.durationNano);
statsReportBuilders.put(edsServiceName, builder); statsReportBuilders.put(edsServiceName, builder);
} }
clusterDropStats.keySet().removeAll(toDiscard); clusterDropStats.keySet().removeAll(toDiscard);
@ -180,9 +179,9 @@ final class LoadStatsManager2 {
for (String edsServiceName : clusterLoadStats.keySet()) { for (String edsServiceName : clusterLoadStats.keySet()) {
ClusterStats.Builder builder = statsReportBuilders.get(edsServiceName); ClusterStats.Builder builder = statsReportBuilders.get(edsServiceName);
if (builder == null) { if (builder == null) {
builder = ClusterStats.newBuilder().setClusterName(cluster); builder = ClusterStats.newBuilder().clusterName(cluster);
if (edsServiceName != null) { if (edsServiceName != null) {
builder.setClusterServiceName(edsServiceName); builder.clusterServiceName(edsServiceName);
} }
statsReportBuilders.put(edsServiceName, builder); statsReportBuilders.put(edsServiceName, builder);
} }
@ -196,17 +195,14 @@ final class LoadStatsManager2 {
if (ref.getReferenceCount() == 0 && snapshot.callsInProgress == 0) { if (ref.getReferenceCount() == 0 && snapshot.callsInProgress == 0) {
localitiesToDiscard.add(locality); localitiesToDiscard.add(locality);
} }
UpstreamLocalityStats.Builder localityStatsBuilder = UpstreamLocalityStats.newBuilder(); UpstreamLocalityStats upstreamLocalityStats = UpstreamLocalityStats.create(
localityStatsBuilder.setLocality(locality); locality, snapshot.callsIssued, snapshot.callsSucceeded, snapshot.callsFailed,
localityStatsBuilder.setTotalIssuedRequests(snapshot.callsIssued); snapshot.callsInProgress);
localityStatsBuilder.setTotalSuccessfulRequests(snapshot.callsSucceeded); builder.addUpstreamLocalityStats(upstreamLocalityStats);
localityStatsBuilder.setTotalErrorRequests(snapshot.callsFailed);
localityStatsBuilder.setTotalRequestsInProgress(snapshot.callsInProgress);
builder.addUpstreamLocalityStats(localityStatsBuilder.build());
// Use the max (drops/loads) recording interval as the overall interval for the // Use the max (drops/loads) recording interval as the overall interval for the
// cluster's stats. In general, they should be mostly identical. // cluster's stats. In general, they should be mostly identical.
builder.setLoadReportIntervalNanos( builder.loadReportIntervalNano(
Math.max(builder.getLoadReportIntervalNanos(), snapshot.durationNano)); Math.max(builder.loadReportIntervalNano(), snapshot.durationNano));
} }
localityStats.keySet().removeAll(localitiesToDiscard); localityStats.keySet().removeAll(localitiesToDiscard);
if (localityStats.isEmpty()) { if (localityStats.isEmpty()) {

View File

@ -0,0 +1,33 @@
/*
* Copyright 2021 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.auto.value.AutoValue;
/** Represents a network locality. */
@AutoValue
abstract class Locality {
abstract String region();
abstract String zone();
abstract String subZone();
static Locality create(String region, String zone, String subZone) {
return new AutoValue_Locality(region, zone, subZone);
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2021 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 com.google.auto.value.AutoValue;
import com.google.re2j.Pattern;
import javax.annotation.Nullable;
/** A group of request matchers. */
final class Matchers {
private Matchers() {}
/** Matcher for HTTP request path. */
@AutoValue
abstract static class PathMatcher {
// Exact full path to be matched.
@Nullable
abstract String path();
// Path prefix to be matched.
@Nullable
abstract String prefix();
// Regular expression pattern of the path to be matched.
@Nullable
abstract Pattern regEx();
// Whether case sensitivity is taken into account for matching.
// Only valid for full path matching or prefix matching.
abstract boolean caseSensitive();
static PathMatcher fromPath(String path, boolean caseSensitive) {
checkNotNull(path, "path");
return PathMatcher.create(path, null, null, caseSensitive);
}
static PathMatcher fromPrefix(String prefix, boolean caseSensitive) {
checkNotNull(prefix, "prefix");
return PathMatcher.create(null, prefix, null, caseSensitive);
}
static PathMatcher fromRegEx(Pattern regEx) {
checkNotNull(regEx, "regEx");
return PathMatcher.create(null, null, regEx, false /* doesn't matter */);
}
private static PathMatcher create(@Nullable String path, @Nullable String prefix,
@Nullable Pattern regEx, boolean caseSensitive) {
return new AutoValue_Matchers_PathMatcher(path, prefix, regEx, caseSensitive);
}
}
/** Matcher for HTTP request headers. */
@AutoValue
abstract static class HeaderMatcher {
// Name of the header to be matched.
abstract String name();
// Matches exact header value.
@Nullable
abstract String exactValue();
// Matches header value with the regular expression pattern.
@Nullable
abstract Pattern safeRegEx();
// Matches header value an integer value in the range.
@Nullable
abstract Range range();
// Matches header presence.
@Nullable
abstract Boolean present();
// Matches header value with the prefix.
@Nullable
abstract String prefix();
// Matches header value with the suffix.
@Nullable
abstract String suffix();
// Whether the matching semantics is inverted. E.g., present && !inverted -> !present
abstract boolean inverted();
static HeaderMatcher forExactValue(String name, String exactValue, boolean inverted) {
checkNotNull(name, "name");
checkNotNull(exactValue, "exactValue");
return HeaderMatcher.create(name, exactValue, null, null, null, null, null, inverted);
}
static HeaderMatcher forSafeRegEx(String name, Pattern safeRegEx, boolean inverted) {
checkNotNull(name, "name");
checkNotNull(safeRegEx, "safeRegEx");
return HeaderMatcher.create(name, null, safeRegEx, null, null, null, null, inverted);
}
static HeaderMatcher forRange(String name, Range range, boolean inverted) {
checkNotNull(name, "name");
checkNotNull(range, "range");
return HeaderMatcher.create(name, null, null, range, null, null, null, inverted);
}
static HeaderMatcher forPresent(String name, boolean present, boolean inverted) {
checkNotNull(name, "name");
return HeaderMatcher.create(name, null, null, null, present, null, null, inverted);
}
static HeaderMatcher forPrefix(String name, String prefix, boolean inverted) {
checkNotNull(name, "name");
checkNotNull(prefix, "prefix");
return HeaderMatcher.create(name, null, null, null, null, prefix, null, inverted);
}
static HeaderMatcher forSuffix(String name, String suffix, boolean inverted) {
checkNotNull(name, "name");
checkNotNull(suffix, "suffix");
return HeaderMatcher.create(name, null, null, null, null, null, suffix, inverted);
}
private static HeaderMatcher create(String name, @Nullable String exactValue,
@Nullable Pattern safeRegEx, @Nullable Range range,
@Nullable Boolean present, @Nullable String prefix,
@Nullable String suffix, boolean inverted) {
checkNotNull(name, "name");
return new AutoValue_Matchers_HeaderMatcher(name, exactValue, safeRegEx, range, present,
prefix, suffix, inverted);
}
/** Represents an integer range. */
@AutoValue
abstract static class Range {
abstract long start();
abstract long end();
static Range create(long start, long end) {
return new AutoValue_Matchers_HeaderMatcher_Range(start, end);
}
}
}
/** Represents a fractional value. */
@AutoValue
abstract static class FractionMatcher {
abstract int numerator();
abstract int denominator();
static FractionMatcher create(int numerator, int denominator) {
return new AutoValue_Matchers_FractionMatcher(numerator, denominator);
}
}
}

View File

@ -1,447 +0,0 @@
/*
* Copyright 2020 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.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.re2j.Pattern;
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* A {@link RouteMatch} represents a group of routing rules used by a logical route to filter RPCs.
*/
final class RouteMatch {
private final PathMatcher pathMatch;
private final List<HeaderMatcher> headerMatchers;
@Nullable
private final FractionMatcher fractionMatch;
@VisibleForTesting
RouteMatch(PathMatcher pathMatch, List<HeaderMatcher> headerMatchers,
@Nullable FractionMatcher fractionMatch) {
this.pathMatch = pathMatch;
this.fractionMatch = fractionMatch;
this.headerMatchers = headerMatchers;
}
static RouteMatch withPathExactOnly(String pathExact) {
return new RouteMatch(PathMatcher.fromPath(pathExact, true),
Collections.<HeaderMatcher>emptyList(), null);
}
/**
* Returns {@code true} if a request with the given path and headers passes all the rules
* specified by this RouteMatch.
*
* <p>The request's headers are given as a key-values mapping, where multiple values can
* be mapped to the same key.
*
* <p>Match is not deterministic if a runtime fraction match rule presents in this RouteMatch.
*/
boolean matches(String path, Map<String, Iterable<String>> headers) {
if (!pathMatch.matches(path)) {
return false;
}
for (HeaderMatcher headerMatcher : headerMatchers) {
Iterable<String> headerValues = headers.get(headerMatcher.getName());
// Special cases for hiding headers: "grpc-previous-rpc-attempts".
if (headerMatcher.getName().equals("grpc-previous-rpc-attempts")) {
headerValues = null;
}
// Special case for exposing headers: "content-type".
if (headerMatcher.getName().equals("content-type")) {
headerValues = Collections.singletonList("application/grpc");
}
if (!headerMatcher.matchesValue(headerValues)) {
return false;
}
}
return fractionMatch == null || fractionMatch.matches();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RouteMatch that = (RouteMatch) o;
return Objects.equals(pathMatch, that.pathMatch)
&& Objects.equals(fractionMatch, that.fractionMatch)
&& Objects.equals(headerMatchers, that.headerMatchers);
}
@Override
public int hashCode() {
return Objects.hash(pathMatch, fractionMatch, headerMatchers);
}
@Override
public String toString() {
ToStringHelper toStringHelper =
MoreObjects.toStringHelper(this).add("pathMatch", pathMatch);
if (fractionMatch != null) {
toStringHelper.add("fractionMatch", fractionMatch);
}
return toStringHelper.add("headerMatchers", headerMatchers).toString();
}
static final class PathMatcher {
// Exactly one of the following fields is non-null.
@Nullable
private final String path;
@Nullable
private final String prefix;
@Nullable
private final Pattern regEx;
private final boolean caseSensitive;
private PathMatcher(@Nullable String path, @Nullable String prefix, @Nullable Pattern regEx,
boolean caseSensitive) {
this.path = path;
this.prefix = prefix;
this.regEx = regEx;
this.caseSensitive = caseSensitive;
}
static PathMatcher fromPath(String path, boolean caseSensitive) {
return new PathMatcher(path, null, null, caseSensitive);
}
static PathMatcher fromPrefix(String prefix, boolean caseSensitive) {
return new PathMatcher(null, prefix, null, caseSensitive);
}
static PathMatcher fromRegEx(Pattern regEx) {
return new PathMatcher(null, null, regEx, false /* doesn't matter */);
}
boolean matches(String fullMethodName) {
if (path != null) {
return caseSensitive ? path.equals(fullMethodName) : path.equalsIgnoreCase(fullMethodName);
} else if (prefix != null) {
return caseSensitive
? fullMethodName.startsWith(prefix)
: fullMethodName.toLowerCase().startsWith(prefix.toLowerCase());
}
return regEx.matches(fullMethodName);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
PathMatcher that = (PathMatcher) o;
return Objects.equals(path, that.path)
&& Objects.equals(prefix, that.prefix)
&& Objects.equals(caseSensitive, that.caseSensitive)
&& Objects.equals(
regEx == null ? null : regEx.pattern(),
that.regEx == null ? null : that.regEx.pattern());
}
@Override
public int hashCode() {
return Objects.hash(path, prefix, caseSensitive, regEx == null ? null : regEx.pattern());
}
@Override
public String toString() {
ToStringHelper toStringHelper =
MoreObjects.toStringHelper(this);
if (path != null) {
toStringHelper.add("path", path).add("caseSensitive", caseSensitive);
}
if (prefix != null) {
toStringHelper.add("prefix", prefix).add("caseSensitive", caseSensitive);
}
if (regEx != null) {
toStringHelper.add("regEx", regEx.pattern());
}
return toStringHelper.toString();
}
}
/**
* Matching rules for a specific HTTP/2 header.
*/
static final class HeaderMatcher {
private final String name;
// Exactly one of the following fields is non-null.
@Nullable
private final String exactMatch;
@Nullable
private final Pattern safeRegExMatch;
@Nullable
private final Range rangeMatch;
@Nullable
private final Boolean presentMatch;
@Nullable
private final String prefixMatch;
@Nullable
private final String suffixMatch;
private final boolean isInvertedMatch;
// TODO(chengyuanzhang): use builder to enforce oneof semantics would be better.
HeaderMatcher(
String name,
@Nullable String exactMatch, @Nullable Pattern safeRegExMatch, @Nullable Range rangeMatch,
@Nullable Boolean presentMatch, @Nullable String prefixMatch, @Nullable String suffixMatch,
boolean isInvertedMatch) {
this.name = name;
this.exactMatch = exactMatch;
this.safeRegExMatch = safeRegExMatch;
this.rangeMatch = rangeMatch;
this.presentMatch = presentMatch;
this.prefixMatch = prefixMatch;
this.suffixMatch = suffixMatch;
this.isInvertedMatch = isInvertedMatch;
}
private boolean matchesValue(@Nullable Iterable<String> values) {
if (presentMatch != null) {
return (values == null) == presentMatch.equals(isInvertedMatch);
}
if (values == null) {
return false;
}
String valueStr = Joiner.on(",").join(values);
boolean baseMatch;
if (exactMatch != null) {
baseMatch = exactMatch.equals(valueStr);
} else if (safeRegExMatch != null) {
baseMatch = safeRegExMatch.matches(valueStr);
} else if (rangeMatch != null) {
long numValue;
try {
numValue = Long.parseLong(valueStr);
baseMatch = rangeMatch.contains(numValue);
} catch (NumberFormatException ignored) {
baseMatch = false;
}
} else if (prefixMatch != null) {
baseMatch = valueStr.startsWith(prefixMatch);
} else {
baseMatch = valueStr.endsWith(suffixMatch);
}
return baseMatch != isInvertedMatch;
}
String getName() {
return name;
}
String getExactMatch() {
return exactMatch;
}
Pattern getRegExMatch() {
return safeRegExMatch;
}
Range getRangeMatch() {
return rangeMatch;
}
Boolean getPresentMatch() {
return presentMatch;
}
String getPrefixMatch() {
return prefixMatch;
}
String getSuffixMatch() {
return suffixMatch;
}
boolean isInvertedMatch() {
return isInvertedMatch;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
HeaderMatcher that = (HeaderMatcher) o;
return Objects.equals(name, that.name)
&& Objects.equals(exactMatch, that.exactMatch)
&& Objects.equals(
safeRegExMatch == null ? null : safeRegExMatch.pattern(),
that.safeRegExMatch == null ? null : that.safeRegExMatch.pattern())
&& Objects.equals(rangeMatch, that.rangeMatch)
&& Objects.equals(presentMatch, that.presentMatch)
&& Objects.equals(prefixMatch, that.prefixMatch)
&& Objects.equals(suffixMatch, that.suffixMatch)
&& Objects.equals(isInvertedMatch, that.isInvertedMatch);
}
@Override
public int hashCode() {
return Objects.hash(
name, exactMatch, safeRegExMatch == null ? null : safeRegExMatch.pattern(),
rangeMatch, presentMatch, prefixMatch, suffixMatch, isInvertedMatch);
}
@Override
public String toString() {
ToStringHelper toStringHelper =
MoreObjects.toStringHelper(this).add("name", name);
if (exactMatch != null) {
toStringHelper.add("exactMatch", exactMatch);
}
if (safeRegExMatch != null) {
toStringHelper.add("safeRegExMatch", safeRegExMatch.pattern());
}
if (rangeMatch != null) {
toStringHelper.add("rangeMatch", rangeMatch);
}
if (presentMatch != null) {
toStringHelper.add("presentMatch", presentMatch);
}
if (prefixMatch != null) {
toStringHelper.add("prefixMatch", prefixMatch);
}
if (suffixMatch != null) {
toStringHelper.add("suffixMatch", suffixMatch);
}
return toStringHelper.add("isInvertedMatch", isInvertedMatch).toString();
}
static final class Range {
private final long start;
private final long end;
Range(long start, long end) {
this.start = start;
this.end = end;
}
boolean contains(long value) {
return value >= start && value < end;
}
long getStart() {
return start;
}
long getEnd() {
return end;
}
@Override
public int hashCode() {
return Objects.hash(start, end);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Range that = (Range) o;
return Objects.equals(start, that.start)
&& Objects.equals(end, that.end);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("start", start)
.add("end", end)
.toString();
}
}
}
static final class FractionMatcher {
private final int numerator;
private final int denominator;
private final ThreadSafeRandom rand;
FractionMatcher(int numerator, int denominator) {
this(numerator, denominator, ThreadSafeRandomImpl.instance);
}
@VisibleForTesting
FractionMatcher(int numerator, int denominator, ThreadSafeRandom rand) {
this.numerator = numerator;
this.denominator = denominator;
this.rand = rand;
}
private boolean matches() {
return rand.nextInt(denominator) < numerator;
}
int getNumerator() {
return numerator;
}
int getDenominator() {
return denominator;
}
@Override
public int hashCode() {
return Objects.hash(numerator, denominator);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
FractionMatcher that = (FractionMatcher) o;
return Objects.equals(numerator, that.numerator)
&& Objects.equals(denominator, that.denominator);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("numerator", numerator)
.add("denominator", denominator)
.toString();
}
}
}

View File

@ -0,0 +1,110 @@
/*
* Copyright 2021 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.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import javax.annotation.Nullable;
/** Represents client load stats. */
final class Stats {
private Stats() {}
/** Cluster-level load stats. */
@AutoValue
abstract static class ClusterStats {
abstract String clusterName();
@Nullable
abstract String clusterServiceName();
abstract ImmutableList<UpstreamLocalityStats> upstreamLocalityStatsList();
abstract ImmutableList<DroppedRequests> droppedRequestsList();
abstract long totalDroppedRequests();
abstract long loadReportIntervalNano();
static Builder newBuilder() {
return new AutoValue_Stats_ClusterStats.Builder()
.totalDroppedRequests(0L) // default initialization
.loadReportIntervalNano(0L);
}
@AutoValue.Builder
abstract static class Builder {
abstract Builder clusterName(String clusterName);
abstract Builder clusterServiceName(String clusterServiceName);
abstract ImmutableList.Builder<UpstreamLocalityStats> upstreamLocalityStatsListBuilder();
Builder addUpstreamLocalityStats(UpstreamLocalityStats upstreamLocalityStats) {
upstreamLocalityStatsListBuilder().add(upstreamLocalityStats);
return this;
}
abstract ImmutableList.Builder<DroppedRequests> droppedRequestsListBuilder();
Builder addDroppedRequests(DroppedRequests droppedRequests) {
droppedRequestsListBuilder().add(droppedRequests);
return this;
}
abstract Builder totalDroppedRequests(long totalDroppedRequests);
abstract Builder loadReportIntervalNano(long loadReportIntervalNano);
abstract long loadReportIntervalNano();
abstract ClusterStats build();
}
}
/** Stats for dropped requests. */
@AutoValue
abstract static class DroppedRequests {
abstract String category();
abstract long droppedCount();
static DroppedRequests create(String category, long droppedCount) {
return new AutoValue_Stats_DroppedRequests(category, droppedCount);
}
}
/** Load stats aggregated in locality level. */
@AutoValue
abstract static class UpstreamLocalityStats {
abstract Locality locality();
abstract long totalIssuedRequests();
abstract long totalSuccessfulRequests();
abstract long totalErrorRequests();
abstract long totalRequestsInProgress();
static UpstreamLocalityStats create(Locality locality, long totalIssuedRequests,
long totalSuccessfulRequests, long totalErrorRequests, long totalRequestsInProgress) {
return new AutoValue_Stats_UpstreamLocalityStats(locality, totalIssuedRequests,
totalSuccessfulRequests, totalErrorRequests, totalRequestsInProgress);
}
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright 2021 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 com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import io.grpc.xds.Matchers.FractionMatcher;
import io.grpc.xds.Matchers.HeaderMatcher;
import io.grpc.xds.Matchers.PathMatcher;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
/** Reprsents an upstream virtual host. */
@AutoValue
abstract class VirtualHost {
// The canonical name of this virtual host.
abstract String name();
// The list of domains (host/authority header) that will be matched to this virtual host.
abstract ImmutableList<String> domains();
// The list of routes that will be matched, in order, for incoming requests.
abstract ImmutableList<Route> routes();
@Nullable
abstract HttpFault httpFault();
public static VirtualHost create(String name, List<String> domains, List<Route> routes,
@Nullable HttpFault httpFault) {
return new AutoValue_VirtualHost(name, ImmutableList.copyOf(domains),
ImmutableList.copyOf(routes), httpFault);
}
@AutoValue
abstract static class Route {
abstract RouteMatch routeMatch();
abstract RouteAction routeAction();
@Nullable
abstract HttpFault httpFault();
static Route create(RouteMatch routeMatch, RouteAction routeAction,
@Nullable HttpFault httpFault) {
return new AutoValue_VirtualHost_Route(routeMatch, routeAction, httpFault);
}
@AutoValue
abstract static class RouteMatch {
abstract PathMatcher pathMatcher();
abstract ImmutableList<HeaderMatcher> headerMatchers();
@Nullable
abstract FractionMatcher fractionMatcher();
// TODO(chengyuanzhang): maybe delete me.
@VisibleForTesting
static RouteMatch withPathExactOnly(String path) {
return RouteMatch.create(PathMatcher.fromPath(path, true),
Collections.<HeaderMatcher>emptyList(), null);
}
static RouteMatch create(PathMatcher pathMatcher,
List<HeaderMatcher> headerMatchers, @Nullable FractionMatcher fractionMatcher) {
return new AutoValue_VirtualHost_Route_RouteMatch(pathMatcher,
ImmutableList.copyOf(headerMatchers), fractionMatcher);
}
}
@AutoValue
abstract static class RouteAction {
@Nullable
abstract Long timeoutNano();
@Nullable
abstract String cluster();
@Nullable
abstract ImmutableList<ClusterWeight> weightedClusters();
static RouteAction forCluster(String cluster, @Nullable Long timeoutNano) {
checkNotNull(cluster, "cluster");
return RouteAction.create(timeoutNano, cluster, null);
}
static RouteAction forWeightedClusters(List<ClusterWeight> weightedClusters,
@Nullable Long timeoutNano) {
checkNotNull(weightedClusters, "weightedClusters");
checkArgument(!weightedClusters.isEmpty(), "empty cluster list");
return RouteAction.create(timeoutNano, null, weightedClusters);
}
private static RouteAction create(@Nullable Long timeoutNano, @Nullable String cluster,
@Nullable List<ClusterWeight> weightedClusters) {
return new AutoValue_VirtualHost_Route_RouteAction(timeoutNano, cluster,
weightedClusters == null ? null : ImmutableList.copyOf(weightedClusters));
}
@AutoValue
abstract static class ClusterWeight {
abstract String name();
abstract int weight();
@Nullable
abstract HttpFault httpFault();
static ClusterWeight create(String name, int weight, @Nullable HttpFault httpFault) {
return new AutoValue_VirtualHost_Route_RouteAction_ClusterWeight(name, weight,
httpFault);
}
}
}
}
}

View File

@ -22,11 +22,8 @@ import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper; import com.google.common.base.MoreObjects.ToStringHelper;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyProtoData.HttpFault; import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.VirtualHost;
import io.grpc.xds.EnvoyServerProtoData.Listener; import io.grpc.xds.EnvoyServerProtoData.Listener;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterDropStats;

View File

@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.google.gson.Gson; import com.google.gson.Gson;
import io.grpc.Attributes; import io.grpc.Attributes;
@ -39,11 +40,14 @@ import io.grpc.Status;
import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext;
import io.grpc.internal.GrpcUtil; import io.grpc.internal.GrpcUtil;
import io.grpc.internal.ObjectPool; import io.grpc.internal.ObjectPool;
import io.grpc.xds.EnvoyProtoData.ClusterWeight; import io.grpc.xds.Matchers.FractionMatcher;
import io.grpc.xds.EnvoyProtoData.Route; import io.grpc.xds.Matchers.HeaderMatcher;
import io.grpc.xds.EnvoyProtoData.RouteAction; import io.grpc.xds.Matchers.PathMatcher;
import io.grpc.xds.EnvoyProtoData.VirtualHost;
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl; import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
import io.grpc.xds.VirtualHost.Route;
import io.grpc.xds.VirtualHost.Route.RouteAction;
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
import io.grpc.xds.VirtualHost.Route.RouteMatch;
import io.grpc.xds.XdsClient.LdsResourceWatcher; import io.grpc.xds.XdsClient.LdsResourceWatcher;
import io.grpc.xds.XdsClient.LdsUpdate; import io.grpc.xds.XdsClient.LdsUpdate;
import io.grpc.xds.XdsClient.RdsResourceWatcher; import io.grpc.xds.XdsClient.RdsResourceWatcher;
@ -218,7 +222,7 @@ final class XdsNameResolver extends NameResolver {
boolean exactMatchFound = false; // true if a virtual host with exactly matched domain found boolean exactMatchFound = false; // true if a virtual host with exactly matched domain found
VirtualHost targetVirtualHost = null; // target VirtualHost with longest matched domain VirtualHost targetVirtualHost = null; // target VirtualHost with longest matched domain
for (VirtualHost vHost : virtualHosts) { for (VirtualHost vHost : virtualHosts) {
for (String domain : vHost.getDomains()) { for (String domain : vHost.domains()) {
boolean selected = false; boolean selected = false;
if (matchHostName(hostName, domain)) { // matching if (matchHostName(hostName, domain)) { // matching
if (!domain.contains("*")) { // exact matching if (!domain.contains("*")) { // exact matching
@ -320,8 +324,8 @@ final class XdsNameResolver extends NameResolver {
Route selectedRoute = null; Route selectedRoute = null;
do { do {
for (Route route : routingConfig.routes) { for (Route route : routingConfig.routes) {
if (route.getRouteMatch().matches( if (matchRoute(route.routeMatch(), "/" + args.getMethodDescriptor().getFullMethodName(),
"/" + args.getMethodDescriptor().getFullMethodName(), asciiHeaders)) { asciiHeaders, random)) {
selectedRoute = route; selectedRoute = route;
break; break;
} }
@ -330,20 +334,20 @@ final class XdsNameResolver extends NameResolver {
return Result.forError( return Result.forError(
Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC")); Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"));
} }
RouteAction action = selectedRoute.getRouteAction(); RouteAction action = selectedRoute.routeAction();
if (action.getCluster() != null) { if (action.cluster() != null) {
cluster = action.getCluster(); cluster = action.cluster();
} else if (action.getWeightedCluster() != null) { } else if (action.weightedClusters() != null) {
int totalWeight = 0; int totalWeight = 0;
for (ClusterWeight weightedCluster : action.getWeightedCluster()) { for (ClusterWeight weightedCluster : action.weightedClusters()) {
totalWeight += weightedCluster.getWeight(); totalWeight += weightedCluster.weight();
} }
int select = random.nextInt(totalWeight); int select = random.nextInt(totalWeight);
int accumulator = 0; int accumulator = 0;
for (ClusterWeight weightedCluster : action.getWeightedCluster()) { for (ClusterWeight weightedCluster : action.weightedClusters()) {
accumulator += weightedCluster.getWeight(); accumulator += weightedCluster.weight();
if (select < accumulator) { if (select < accumulator) {
cluster = weightedCluster.getName(); cluster = weightedCluster.name();
break; break;
} }
} }
@ -352,7 +356,7 @@ final class XdsNameResolver extends NameResolver {
// TODO(chengyuanzhang): avoid service config generation and parsing for each call. // TODO(chengyuanzhang): avoid service config generation and parsing for each call.
Map<String, ?> rawServiceConfig = Collections.emptyMap(); Map<String, ?> rawServiceConfig = Collections.emptyMap();
if (enableTimeout) { if (enableTimeout) {
Long timeoutNano = selectedRoute.getRouteAction().getTimeoutNano(); Long timeoutNano = selectedRoute.routeAction().timeoutNano();
if (timeoutNano == null) { if (timeoutNano == null) {
timeoutNano = routingConfig.fallbackTimeoutNano; timeoutNano = routingConfig.fallbackTimeoutNano;
} }
@ -442,6 +446,76 @@ final class XdsNameResolver extends NameResolver {
} }
} }
@VisibleForTesting
static boolean matchRoute(RouteMatch routeMatch, String fullMethodName,
Map<String, Iterable<String>> headers, ThreadSafeRandom random) {
if (!matchPath(routeMatch.pathMatcher(), fullMethodName)) {
return false;
}
for (HeaderMatcher headerMatcher : routeMatch.headerMatchers()) {
Iterable<String> headerValues = headers.get(headerMatcher.name());
// Special cases for hiding headers: "grpc-previous-rpc-attempts".
if (headerMatcher.name().equals("grpc-previous-rpc-attempts")) {
headerValues = null;
}
// Special case for exposing headers: "content-type".
if (headerMatcher.name().equals("content-type")) {
headerValues = Collections.singletonList("application/grpc");
}
if (!matchHeader(headerMatcher, headerValues)) {
return false;
}
}
FractionMatcher fraction = routeMatch.fractionMatcher();
return fraction == null || random.nextInt(fraction.denominator()) < fraction.numerator();
}
@VisibleForTesting
static boolean matchPath(PathMatcher pathMatcher, String fullMethodName) {
if (pathMatcher.path() != null) {
return pathMatcher.caseSensitive()
? pathMatcher.path().equals(fullMethodName)
: pathMatcher.path().equalsIgnoreCase(fullMethodName);
} else if (pathMatcher.prefix() != null) {
return pathMatcher.caseSensitive()
? fullMethodName.startsWith(pathMatcher.prefix())
: fullMethodName.toLowerCase().startsWith(pathMatcher.prefix().toLowerCase());
}
return pathMatcher.regEx().matches(fullMethodName);
}
@VisibleForTesting
static boolean matchHeader(HeaderMatcher headerMatcher,
@Nullable Iterable<String> headerValues) {
if (headerMatcher.present() != null) {
return (headerValues == null) == headerMatcher.present().equals(headerMatcher.inverted());
}
if (headerValues == null) {
return false;
}
String valueStr = Joiner.on(",").join(headerValues);
boolean baseMatch;
if (headerMatcher.exactValue() != null) {
baseMatch = headerMatcher.exactValue().equals(valueStr);
} else if (headerMatcher.safeRegEx() != null) {
baseMatch = headerMatcher.safeRegEx().matches(valueStr);
} else if (headerMatcher.range() != null) {
long numValue;
try {
numValue = Long.parseLong(valueStr);
baseMatch = numValue >= headerMatcher.range().start()
&& numValue <= headerMatcher.range().end();
} catch (NumberFormatException ignored) {
baseMatch = false;
}
} else if (headerMatcher.prefix() != null) {
baseMatch = valueStr.startsWith(headerMatcher.prefix());
} else {
baseMatch = valueStr.endsWith(headerMatcher.suffix());
}
return baseMatch != headerMatcher.inverted();
}
private class ResolveState implements LdsResourceWatcher { private class ResolveState implements LdsResourceWatcher {
private final ConfigOrError emptyServiceConfig = private final ConfigOrError emptyServiceConfig =
serviceConfigParser.parseServiceConfig(Collections.<String, Object>emptyMap()); serviceConfigParser.parseServiceConfig(Collections.<String, Object>emptyMap());
@ -537,15 +611,15 @@ final class XdsNameResolver extends NameResolver {
listener.onResult(emptyResult); listener.onResult(emptyResult);
return; return;
} }
List<Route> routes = virtualHost.getRoutes(); List<Route> routes = virtualHost.routes();
Set<String> clusters = new HashSet<>(); Set<String> clusters = new HashSet<>();
for (Route route : routes) { for (Route route : routes) {
RouteAction action = route.getRouteAction(); RouteAction action = route.routeAction();
if (action.getCluster() != null) { if (action.cluster() != null) {
clusters.add(action.getCluster()); clusters.add(action.cluster());
} else if (action.getWeightedCluster() != null) { } else if (action.weightedClusters() != null) {
for (ClusterWeight weighedCluster : action.getWeightedCluster()) { for (ClusterWeight weighedCluster : action.weightedClusters()) {
clusters.add(weighedCluster.getName()); clusters.add(weighedCluster.name());
} }
} }
} }

View File

@ -29,7 +29,6 @@ import io.grpc.internal.GrpcUtil;
import io.grpc.internal.GrpcUtil.GrpcBuildVersion; import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
import io.grpc.xds.Bootstrapper.BootstrapInfo; import io.grpc.xds.Bootstrapper.BootstrapInfo;
import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Node;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -118,7 +117,7 @@ public class BootstrapperImplTest {
getNodeBuilder() getNodeBuilder()
.setId("ENVOY_NODE_ID") .setId("ENVOY_NODE_ID")
.setCluster("ENVOY_CLUSTER") .setCluster("ENVOY_CLUSTER")
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE")) .setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setMetadata( .setMetadata(
ImmutableMap.of( ImmutableMap.of(
"TRAFFICDIRECTOR_INTERCEPTION_PORT", "TRAFFICDIRECTOR_INTERCEPTION_PORT",
@ -176,7 +175,7 @@ public class BootstrapperImplTest {
getNodeBuilder() getNodeBuilder()
.setId("ENVOY_NODE_ID") .setId("ENVOY_NODE_ID")
.setCluster("ENVOY_CLUSTER") .setCluster("ENVOY_CLUSTER")
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE")) .setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setMetadata( .setMetadata(
ImmutableMap.of( ImmutableMap.of(
"TRAFFICDIRECTOR_INTERCEPTION_PORT", "TRAFFICDIRECTOR_INTERCEPTION_PORT",
@ -224,7 +223,7 @@ public class BootstrapperImplTest {
getNodeBuilder() getNodeBuilder()
.setId("ENVOY_NODE_ID") .setId("ENVOY_NODE_ID")
.setCluster("ENVOY_CLUSTER") .setCluster("ENVOY_CLUSTER")
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE")) .setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setMetadata( .setMetadata(
ImmutableMap.of( ImmutableMap.of(
"TRAFFICDIRECTOR_INTERCEPTION_PORT", "TRAFFICDIRECTOR_INTERCEPTION_PORT",

View File

@ -0,0 +1,598 @@
/*
* Copyright 2021 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 com.google.protobuf.UInt32Value;
import com.google.protobuf.util.Durations;
import com.google.re2j.Pattern;
import io.envoyproxy.envoy.config.core.v3.Address;
import io.envoyproxy.envoy.config.core.v3.Locality;
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
import io.envoyproxy.envoy.config.endpoint.v3.Endpoint;
import io.envoyproxy.envoy.config.route.v3.DirectResponseAction;
import io.envoyproxy.envoy.config.route.v3.FilterAction;
import io.envoyproxy.envoy.config.route.v3.RedirectAction;
import io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration;
import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.HeaderAbort;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.v3.FractionalPercent;
import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType;
import io.envoyproxy.envoy.type.v3.Int64Range;
import io.grpc.Status.Code;
import io.grpc.xds.ClientXdsClient.StructOrError;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.HttpFault.FaultAbort;
import io.grpc.xds.Matchers.FractionMatcher;
import io.grpc.xds.Matchers.HeaderMatcher;
import io.grpc.xds.Matchers.PathMatcher;
import io.grpc.xds.VirtualHost.Route;
import io.grpc.xds.VirtualHost.Route.RouteAction;
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
import io.grpc.xds.VirtualHost.Route.RouteMatch;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ClientXdsClientDataTest {
@Test
public void parseRoute_withRouteAction() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method"))
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo"))
.build();
StructOrError<Route> struct = ClientXdsClient.parseRoute(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
Route.create(
RouteMatch.create(PathMatcher.fromPath("/service/method", false),
Collections.<HeaderMatcher>emptyList(), null),
RouteAction.forCluster("cluster-foo", null), null));
}
@Test
public void parseRoute_withUnsupportedActionTypes() {
StructOrError<Route> res;
io.envoyproxy.envoy.config.route.v3.Route redirectRoute =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
.setRedirect(RedirectAction.getDefaultInstance())
.build();
res = ClientXdsClient.parseRoute(redirectRoute);
assertThat(res.getStruct()).isNull();
assertThat(res.getErrorDetail()).isEqualTo("Unsupported action type: redirect");
io.envoyproxy.envoy.config.route.v3.Route directResponseRoute =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
.setDirectResponse(DirectResponseAction.getDefaultInstance())
.build();
res = ClientXdsClient.parseRoute(directResponseRoute);
assertThat(res.getStruct()).isNull();
assertThat(res.getErrorDetail()).isEqualTo("Unsupported action type: direct_response");
io.envoyproxy.envoy.config.route.v3.Route filterRoute =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
.setFilterAction(FilterAction.getDefaultInstance())
.build();
res = ClientXdsClient.parseRoute(filterRoute);
assertThat(res.getStruct()).isNull();
assertThat(res.getErrorDetail()).isEqualTo("Unsupported action type: filter_action");
}
@Test
public void parseRoute_skipRouteWithUnsupportedMatcher() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method")
.addQueryParameters(
io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher
.getDefaultInstance())) // query parameter not supported
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo"))
.build();
assertThat(ClientXdsClient.parseRoute(proto)).isNull();
}
@Test
public void parseRoute_skipRouteWithUnsupportedAction() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method"))
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setClusterHeader("cluster header")) // cluster_header action not supported
.build();
assertThat(ClientXdsClient.parseRoute(proto)).isNull();
}
@Test
public void parseRouteMatch_withHeaderMatcher() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPrefix("")
.addHeaders(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":scheme")
.setPrefixMatch("http"))
.addHeaders(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setExactMatch("PUT"))
.build();
StructOrError<RouteMatch> struct = ClientXdsClient.parseRouteMatch(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
RouteMatch.create(
PathMatcher.fromPrefix("", false),
Arrays.asList(
HeaderMatcher.forPrefix(":scheme", "http", false),
HeaderMatcher.forExactValue(":method", "PUT", false)),
null));
}
@Test
public void parseRouteMatch_withRuntimeFractionMatcher() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPrefix("")
.setRuntimeFraction(
RuntimeFractionalPercent.newBuilder()
.setDefaultValue(
FractionalPercent.newBuilder()
.setNumerator(30)
.setDenominator(FractionalPercent.DenominatorType.HUNDRED)))
.build();
StructOrError<RouteMatch> struct = ClientXdsClient.parseRouteMatch(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
RouteMatch.create(
PathMatcher.fromPrefix( "", false), Collections.<HeaderMatcher>emptyList(),
FractionMatcher.create(30, 100)));
}
@Test
public void parsePathMatcher_withFullPath() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method")
.build();
StructOrError<PathMatcher> struct = ClientXdsClient.parsePathMatcher(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
PathMatcher.fromPath("/service/method", false));
}
@Test
public void parsePathMatcher_withPrefix() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix("/").build();
StructOrError<PathMatcher> struct = ClientXdsClient.parsePathMatcher(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
PathMatcher.fromPrefix("/", false));
}
@Test
public void parsePathMatcher_withSafeRegEx() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setSafeRegex(RegexMatcher.newBuilder().setRegex("."))
.build();
StructOrError<PathMatcher> struct = ClientXdsClient.parsePathMatcher(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(PathMatcher.fromRegEx(Pattern.compile(".")));
}
@Test
public void parseHeaderMatcher_withExactMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setExactMatch("PUT")
.build();
StructOrError<HeaderMatcher> struct1 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct()).isEqualTo(
HeaderMatcher.forExactValue(":method", "PUT", false));
}
@Test
public void parseHeaderMatcher_withSafeRegExMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("P*"))
.build();
StructOrError<HeaderMatcher> struct3 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct3.getErrorDetail()).isNull();
assertThat(struct3.getStruct()).isEqualTo(
HeaderMatcher.forSafeRegEx(":method", Pattern.compile("P*"), false));
}
@Test
public void parseHeaderMatcher_withRangeMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("timeout")
.setRangeMatch(Int64Range.newBuilder().setStart(10L).setEnd(20L))
.build();
StructOrError<HeaderMatcher> struct4 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct4.getErrorDetail()).isNull();
assertThat(struct4.getStruct()).isEqualTo(
HeaderMatcher.forRange("timeout", HeaderMatcher.Range.create(10L, 20L), false));
}
@Test
public void parseHeaderMatcher_withPresentMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("user-agent")
.setPresentMatch(true)
.build();
StructOrError<HeaderMatcher> struct5 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct5.getErrorDetail()).isNull();
assertThat(struct5.getStruct()).isEqualTo(
HeaderMatcher.forPresent("user-agent", true, false));
}
@Test
public void parseHeaderMatcher_withPrefixMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("authority")
.setPrefixMatch("service-foo")
.build();
StructOrError<HeaderMatcher> struct6 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct6.getErrorDetail()).isNull();
assertThat(struct6.getStruct()).isEqualTo(
HeaderMatcher.forPrefix("authority", "service-foo", false));
}
@Test
public void parseHeaderMatcher_withSuffixMatch() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("authority")
.setSuffixMatch("googleapis.com")
.build();
StructOrError<HeaderMatcher> struct7 = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct7.getErrorDetail()).isNull();
assertThat(struct7.getStruct()).isEqualTo(
HeaderMatcher.forSuffix("authority", "googleapis.com", false));
}
@Test
public void parseHeaderMatcher_malformedRegExPattern() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("["))
.build();
StructOrError<HeaderMatcher> struct = ClientXdsClient.parseHeaderMatcher(proto);
assertThat(struct.getErrorDetail()).isNotNull();
assertThat(struct.getStruct()).isNull();
}
@Test
public void parseRouteAction_withCluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct = ClientXdsClient.parseRouteAction(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo");
assertThat(struct.getStruct().weightedClusters()).isNull();
}
@Test
public void parseRouteAction_withWeightedCluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setWeightedClusters(
WeightedCluster.newBuilder()
.addClusters(
WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-foo")
.setWeight(UInt32Value.newBuilder().setValue(30)))
.addClusters(WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-bar")
.setWeight(UInt32Value.newBuilder().setValue(70))))
.build();
StructOrError<RouteAction> struct = ClientXdsClient.parseRouteAction(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().cluster()).isNull();
assertThat(struct.getStruct().weightedClusters()).containsExactly(
ClusterWeight.create("cluster-foo", 30, null),
ClusterWeight.create("cluster-bar", 70, null));
}
@Test
public void parseRouteAction_withTimeoutByGrpcTimeoutHeaderMax() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setGrpcTimeoutHeaderMax(Durations.fromSeconds(5L))
.setMaxStreamDuration(Durations.fromMillis(20L)))
.build();
StructOrError<RouteAction> struct = ClientXdsClient.parseRouteAction(proto);
assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}
@Test
public void parseRouteAction_withTimeoutByMaxStreamDuration() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setMaxStreamDuration(Durations.fromSeconds(5L)))
.build();
StructOrError<RouteAction> struct = ClientXdsClient.parseRouteAction(proto);
assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}
@Test
public void parseRouteAction_withTimeoutUnset() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct = ClientXdsClient.parseRouteAction(proto);
assertThat(struct.getStruct().timeoutNano()).isNull();
}
@Test
public void parseClusterWeight() {
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto =
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight.newBuilder()
.setName("cluster-foo")
.setWeight(UInt32Value.newBuilder().setValue(30))
.build();
ClusterWeight clusterWeight = ClientXdsClient.parseClusterWeight(proto).getStruct();
assertThat(clusterWeight.name()).isEqualTo("cluster-foo");
assertThat(clusterWeight.weight()).isEqualTo(30);
}
// TODO(zdapeng): add tests for parseClusterWeight with HttpFault.
// TODO(zdapeng): add tests for parseHttpFault.
@Test
public void parseFaultAbort_withHeaderAbort() {
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort proto =
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(20).setDenominator(DenominatorType.HUNDRED))
.setHeaderAbort(HeaderAbort.getDefaultInstance()).build();
FaultAbort faultAbort = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(faultAbort.headerAbort()).isTrue();
assertThat(faultAbort.ratePerMillion()).isEqualTo(200_000);
}
@Test
public void parseFaultAbort_withHttpStatus() {
FaultAbort res;
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort proto;
proto = io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(100).setDenominator(DenominatorType.TEN_THOUSAND))
.setHttpStatus(400).build();
res = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(res.ratePerMillion()).isEqualTo(10_000);
assertThat(res.status().getCode()).isEqualTo(Code.INTERNAL);
proto = io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(100).setDenominator(DenominatorType.TEN_THOUSAND))
.setHttpStatus(401).build();
res = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(res.ratePerMillion()).isEqualTo(10_000);
assertThat(res.status().getCode()).isEqualTo(Code.UNAUTHENTICATED);
proto = io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(100).setDenominator(DenominatorType.TEN_THOUSAND))
.setHttpStatus(403).build();
res = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(res.ratePerMillion()).isEqualTo(10_000);
assertThat(res.status().getCode()).isEqualTo(Code.PERMISSION_DENIED);
proto = io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(100).setDenominator(DenominatorType.TEN_THOUSAND))
.setHttpStatus(404).build();
res = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(res.ratePerMillion()).isEqualTo(10_000);
assertThat(res.status().getCode()).isEqualTo(Code.UNIMPLEMENTED);
proto = io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(100).setDenominator(DenominatorType.TEN_THOUSAND))
.setHttpStatus(503).build();
res = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(res.ratePerMillion()).isEqualTo(10_000);
assertThat(res.status().getCode()).isEqualTo(Code.UNAVAILABLE);
proto = io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(100).setDenominator(DenominatorType.TEN_THOUSAND))
.setHttpStatus(500).build();
res = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(res.ratePerMillion()).isEqualTo(10_000);
assertThat(res.status().getCode()).isEqualTo(Code.UNKNOWN);
}
@Test
public void parseFaultAbort_withGrpcStatus() {
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort proto =
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort.newBuilder()
.setPercentage(FractionalPercent.newBuilder()
.setNumerator(600).setDenominator(DenominatorType.MILLION))
.setGrpcStatus(Code.DEADLINE_EXCEEDED.value()).build();
FaultAbort faultAbort = ClientXdsClient.parseFaultAbort(proto).getStruct();
assertThat(faultAbort.ratePerMillion()).isEqualTo(600);
assertThat(faultAbort.status().getCode()).isEqualTo(Code.DEADLINE_EXCEEDED);
}
@Test
public void parseLocalityLbEndpoints_withHealthyEndpoints() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
LocalityLbEndpoints.create(
Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1));
}
@Test
public void parseLocalityLbEndpoints_treatUnknownHealthAsHealthy() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
LocalityLbEndpoints.create(
Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, true)), 100, 1));
}
@Test
public void parseLocalityLbEndpoints_withUnHealthyEndpoints() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNHEALTHY)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct()).isEqualTo(
LocalityLbEndpoints.create(
Collections.singletonList(LbEndpoint.create("172.14.14.5", 8888, 20, false)), 100, 1));
}
@Test
public void parseLocalityLbEndpoints_ignorZeroWeightLocality() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(0)) // locality weight
.setPriority(1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
assertThat(ClientXdsClient.parseLocalityLbEndpoints(proto)).isNull();
}
@Test
public void parseLocalityLbEndpoints_invalidPriority() {
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto =
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints.newBuilder()
.setLocality(Locality.newBuilder()
.setRegion("region-foo").setZone("zone-foo").setSubZone("subZone-foo"))
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(100)) // locality weight
.setPriority(-1)
.addLbEndpoints(io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint.newBuilder()
.setEndpoint(Endpoint.newBuilder()
.setAddress(Address.newBuilder()
.setSocketAddress(
SocketAddress.newBuilder()
.setAddress("172.14.14.5").setPortValue(8888))))
.setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN)
.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight
.build();
StructOrError<LocalityLbEndpoints> struct = ClientXdsClient.parseLocalityLbEndpoints(proto);
assertThat(struct.getErrorDetail()).isEqualTo("negative priority");
}
}

View File

@ -43,11 +43,9 @@ import io.grpc.internal.FakeClock.ScheduledTask;
import io.grpc.internal.FakeClock.TaskFilter; import io.grpc.internal.FakeClock.TaskFilter;
import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.GrpcCleanupRule;
import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.AbstractXdsClient.ResourceType;
import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyProtoData.HttpFault; import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
@ -374,18 +372,18 @@ public abstract class ClientXdsClientTestBase {
assertThat(ldsUpdate.virtualHosts).hasSize(2); assertThat(ldsUpdate.virtualHosts).hasSize(2);
assertThat(ldsUpdate.hasFaultInjection).isTrue(); assertThat(ldsUpdate.hasFaultInjection).isTrue();
assertThat(ldsUpdate.httpFault).isNull(); assertThat(ldsUpdate.httpFault).isNull();
HttpFault httpFault = ldsUpdate.virtualHosts.get(0).getHttpFault(); HttpFault httpFault = ldsUpdate.virtualHosts.get(0).httpFault();
assertThat(httpFault.faultDelay.delayNanos).isEqualTo(300); assertThat(httpFault.faultDelay().delayNanos()).isEqualTo(300);
assertThat(httpFault.faultDelay.ratePerMillion).isEqualTo(1000); assertThat(httpFault.faultDelay().ratePerMillion()).isEqualTo(1000);
assertThat(httpFault.faultAbort).isNull(); assertThat(httpFault.faultAbort()).isNull();
assertThat(httpFault.upstreamCluster).isEqualTo("cluster1"); assertThat(httpFault.upstreamCluster()).isEqualTo("cluster1");
assertThat(httpFault.maxActiveFaults).isEqualTo(100); assertThat(httpFault.maxActiveFaults()).isEqualTo(100);
httpFault = ldsUpdate.virtualHosts.get(1).getHttpFault(); httpFault = ldsUpdate.virtualHosts.get(1).httpFault();
assertThat(httpFault.faultDelay).isNull(); assertThat(httpFault.faultDelay()).isNull();
assertThat(httpFault.faultAbort.status.getCode()).isEqualTo(Status.Code.UNAVAILABLE); assertThat(httpFault.faultAbort().status().getCode()).isEqualTo(Status.Code.UNAVAILABLE);
assertThat(httpFault.faultAbort.ratePerMillion).isEqualTo(2000); assertThat(httpFault.faultAbort().ratePerMillion()).isEqualTo(2000);
assertThat(httpFault.upstreamCluster).isEqualTo("cluster2"); assertThat(httpFault.upstreamCluster()).isEqualTo("cluster2");
assertThat(httpFault.maxActiveFaults).isEqualTo(101); assertThat(httpFault.maxActiveFaults()).isEqualTo(101);
} }
@Test @Test
@ -942,17 +940,17 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE); assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies) assertThat(edsUpdate.dropPolicies)
.containsExactly( .containsExactly(
new DropOverload("lb", 200), DropOverload.create("lb", 200),
new DropOverload("throttle", 1000)); DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap) assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly( .containsExactly(
new Locality("region1", "zone1", "subzone1"), Locality.create("region1", "zone1", "subzone1"),
new LocalityLbEndpoints( LocalityLbEndpoints.create(
ImmutableList.of( ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080, LbEndpoint.create("192.168.0.1", 8080,
2, true)), 1, 0), 2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"), Locality.create("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1)); LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
} }
@Test @Test
@ -991,17 +989,17 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE); assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies) assertThat(edsUpdate.dropPolicies)
.containsExactly( .containsExactly(
new DropOverload("lb", 200), DropOverload.create("lb", 200),
new DropOverload("throttle", 1000)); DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap) assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly( .containsExactly(
new Locality("region1", "zone1", "subzone1"), Locality.create("region1", "zone1", "subzone1"),
new LocalityLbEndpoints( LocalityLbEndpoints.create(
ImmutableList.of( ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080, LbEndpoint.create("192.168.0.1", 8080,
2, true)), 1, 0), 2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"), Locality.create("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1)); LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
call.verifyNoMoreRequest(); call.verifyNoMoreRequest();
} }
@ -1050,16 +1048,16 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE); assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies) assertThat(edsUpdate.dropPolicies)
.containsExactly( .containsExactly(
new DropOverload("lb", 200), DropOverload.create("lb", 200),
new DropOverload("throttle", 1000)); DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap) assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly( .containsExactly(
new Locality("region1", "zone1", "subzone1"), Locality.create("region1", "zone1", "subzone1"),
new LocalityLbEndpoints( LocalityLbEndpoints.create(
ImmutableList.of( ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0), LbEndpoint.create("192.168.0.1", 8080, 2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"), Locality.create("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1)); LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
clusterLoadAssignments = clusterLoadAssignments =
ImmutableList.of( ImmutableList.of(
@ -1079,10 +1077,10 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.dropPolicies).isEmpty(); assertThat(edsUpdate.dropPolicies).isEmpty();
assertThat(edsUpdate.localityLbEndpointsMap) assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly( .containsExactly(
new Locality("region2", "zone2", "subzone2"), Locality.create("region2", "zone2", "subzone2"),
new LocalityLbEndpoints( LocalityLbEndpoints.create(
ImmutableList.of( ImmutableList.of(
new LbEndpoint("172.44.2.2", 8000, 3, true)), 2, 0)); LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0));
} }
@Test @Test
@ -1187,16 +1185,16 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE); assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies) assertThat(edsUpdate.dropPolicies)
.containsExactly( .containsExactly(
new DropOverload("lb", 200), DropOverload.create("lb", 200),
new DropOverload("throttle", 1000)); DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap) assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly( .containsExactly(
new Locality("region1", "zone1", "subzone1"), Locality.create("region1", "zone1", "subzone1"),
new LocalityLbEndpoints( LocalityLbEndpoints.create(
ImmutableList.of( ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0), LbEndpoint.create("192.168.0.1", 8080, 2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"), Locality.create("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1)); LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
verifyNoMoreInteractions(watcher1, watcher2); verifyNoMoreInteractions(watcher1, watcher2);
clusterLoadAssignments = clusterLoadAssignments =
@ -1217,20 +1215,20 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.dropPolicies).isEmpty(); assertThat(edsUpdate.dropPolicies).isEmpty();
assertThat(edsUpdate.localityLbEndpointsMap) assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly( .containsExactly(
new Locality("region2", "zone2", "subzone2"), Locality.create("region2", "zone2", "subzone2"),
new LocalityLbEndpoints( LocalityLbEndpoints.create(
ImmutableList.of( ImmutableList.of(
new LbEndpoint("172.44.2.2", 8000, 3, true)), 2, 0)); LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0));
verify(watcher2).onChanged(edsUpdateCaptor.capture()); verify(watcher2).onChanged(edsUpdateCaptor.capture());
edsUpdate = edsUpdateCaptor.getValue(); edsUpdate = edsUpdateCaptor.getValue();
assertThat(edsUpdate.clusterName).isEqualTo(edsResource); assertThat(edsUpdate.clusterName).isEqualTo(edsResource);
assertThat(edsUpdate.dropPolicies).isEmpty(); assertThat(edsUpdate.dropPolicies).isEmpty();
assertThat(edsUpdate.localityLbEndpointsMap) assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly( .containsExactly(
new Locality("region2", "zone2", "subzone2"), Locality.create("region2", "zone2", "subzone2"),
new LocalityLbEndpoints( LocalityLbEndpoints.create(
ImmutableList.of( ImmutableList.of(
new LbEndpoint("172.44.2.2", 8000, 3, true)), 2, 0)); LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0));
verifyNoMoreInteractions(edsResourceWatcher); verifyNoMoreInteractions(edsResourceWatcher);
} }

View File

@ -45,14 +45,13 @@ import io.grpc.internal.FakeClock;
import io.grpc.internal.ObjectPool; import io.grpc.internal.ObjectPool;
import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.EnvoyProtoData.ClusterStats; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyProtoData.DropOverload;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
import io.grpc.xds.Stats.ClusterStats;
import io.grpc.xds.Stats.UpstreamLocalityStats;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection; import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig; import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
@ -97,7 +96,7 @@ public class ClusterImplLoadBalancerTest {
}); });
private final FakeClock fakeClock = new FakeClock(); private final FakeClock fakeClock = new FakeClock();
private final Locality locality = private final Locality locality =
new Locality("test-region", "test-zone", "test-subzone"); Locality.create("test-region", "test-zone", "test-subzone");
private final PolicySelection roundRobin = private final PolicySelection roundRobin =
new PolicySelection(new FakeLoadBalancerProvider("round_robin"), null); new PolicySelection(new FakeLoadBalancerProvider("round_robin"), null);
private final List<FakeLoadBalancer> downstreamBalancers = new ArrayList<>(); private final List<FakeLoadBalancer> downstreamBalancers = new ArrayList<>();
@ -219,27 +218,27 @@ public class ClusterImplLoadBalancerTest {
ClusterStats clusterStats = ClusterStats clusterStats =
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
UpstreamLocalityStats localityStats = UpstreamLocalityStats localityStats =
Iterables.getOnlyElement(clusterStats.getUpstreamLocalityStatsList()); Iterables.getOnlyElement(clusterStats.upstreamLocalityStatsList());
assertThat(localityStats.getLocality()).isEqualTo(locality); assertThat(localityStats.locality()).isEqualTo(locality);
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(3L); assertThat(localityStats.totalIssuedRequests()).isEqualTo(3L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L); assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L); assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(1L); assertThat(localityStats.totalRequestsInProgress()).isEqualTo(1L);
streamTracer3.streamClosed(Status.OK); streamTracer3.streamClosed(Status.OK);
subchannel.shutdown(); // stats recorder released subchannel.shutdown(); // stats recorder released
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
// Locality load is reported for one last time in case of loads occurred since the previous // Locality load is reported for one last time in case of loads occurred since the previous
// load report. // load report.
localityStats = Iterables.getOnlyElement(clusterStats.getUpstreamLocalityStatsList()); localityStats = Iterables.getOnlyElement(clusterStats.upstreamLocalityStatsList());
assertThat(localityStats.getLocality()).isEqualTo(locality); assertThat(localityStats.locality()).isEqualTo(locality);
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L); assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L); assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L); assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(0L); assertThat(localityStats.totalRequestsInProgress()).isEqualTo(0L);
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getUpstreamLocalityStatsList()).isEmpty(); // no longer reported assertThat(clusterStats.upstreamLocalityStatsList()).isEmpty(); // no longer reported
} }
@Test @Test
@ -248,7 +247,7 @@ public class ClusterImplLoadBalancerTest {
WeightedTargetConfig weightedTargetConfig = WeightedTargetConfig weightedTargetConfig =
buildWeightedTargetConfig(ImmutableMap.of(locality, 10)); buildWeightedTargetConfig(ImmutableMap.of(locality, 10));
ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME,
null, Collections.singletonList(new DropOverload("throttle", 500_000)), null, Collections.singletonList(DropOverload.create("throttle", 500_000)),
new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); new PolicySelection(weightedTargetProvider, weightedTargetConfig), null);
EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality); EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality);
deliverAddressesAndConfig(Collections.singletonList(endpoint), config); deliverAddressesAndConfig(Collections.singletonList(endpoint), config);
@ -268,16 +267,16 @@ public class ClusterImplLoadBalancerTest {
assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: throttle"); assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: throttle");
ClusterStats clusterStats = ClusterStats clusterStats =
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getCategory()) assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).category())
.isEqualTo("throttle"); .isEqualTo("throttle");
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getDroppedCount()) assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).droppedCount())
.isEqualTo(1L); .isEqualTo(1L);
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
// Config update updates drop policies. // Config update updates drop policies.
config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, null, config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, null,
Collections.singletonList(new DropOverload("lb", 1_000_000)), Collections.singletonList(DropOverload.create("lb", 1_000_000)),
new PolicySelection(weightedTargetProvider, weightedTargetConfig), null); new PolicySelection(weightedTargetProvider, weightedTargetConfig), null);
loadBalancer.handleResolvedAddresses( loadBalancer.handleResolvedAddresses(
ResolvedAddresses.newBuilder() ResolvedAddresses.newBuilder()
@ -294,12 +293,12 @@ public class ClusterImplLoadBalancerTest {
assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: lb"); assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: lb");
clusterStats = clusterStats =
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getCategory()) assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).category())
.isEqualTo("lb"); .isEqualTo("lb");
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getDroppedCount()) assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).droppedCount())
.isEqualTo(1L); .isEqualTo(1L);
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class)); result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getStatus().isOk()).isTrue();
@ -346,21 +345,21 @@ public class ClusterImplLoadBalancerTest {
} }
ClusterStats clusterStats = ClusterStats clusterStats =
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class)); PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
if (enableCircuitBreaking) { if (enableCircuitBreaking) {
assertThat(result.getStatus().isOk()).isFalse(); assertThat(result.getStatus().isOk()).isFalse();
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE); assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
assertThat(result.getStatus().getDescription()) assertThat(result.getStatus().getDescription())
.isEqualTo("Cluster max concurrent requests limit exceeded"); .isEqualTo("Cluster max concurrent requests limit exceeded");
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
} else { } else {
assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getStatus().isOk()).isTrue();
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
} }
// Config update increments circuit breakers max_concurrent_requests threshold. // Config update increments circuit breakers max_concurrent_requests threshold.
@ -375,21 +374,21 @@ public class ClusterImplLoadBalancerTest {
result.getStreamTracerFactory().newClientStreamTracer( result.getStreamTracerFactory().newClientStreamTracer(
ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // 101th request ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // 101th request
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class)); // 102th request result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class)); // 102th request
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
if (enableCircuitBreaking) { if (enableCircuitBreaking) {
assertThat(result.getStatus().isOk()).isFalse(); assertThat(result.getStatus().isOk()).isFalse();
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE); assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
assertThat(result.getStatus().getDescription()) assertThat(result.getStatus().getDescription())
.isEqualTo("Cluster max concurrent requests limit exceeded"); .isEqualTo("Cluster max concurrent requests limit exceeded");
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
} else { } else {
assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getStatus().isOk()).isTrue();
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
} }
} }
@ -434,21 +433,21 @@ public class ClusterImplLoadBalancerTest {
} }
ClusterStats clusterStats = ClusterStats clusterStats =
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class)); PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER)); clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME); assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
if (enableCircuitBreaking) { if (enableCircuitBreaking) {
assertThat(result.getStatus().isOk()).isFalse(); assertThat(result.getStatus().isOk()).isFalse();
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE); assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
assertThat(result.getStatus().getDescription()) assertThat(result.getStatus().getDescription())
.isEqualTo("Cluster max concurrent requests limit exceeded"); .isEqualTo("Cluster max concurrent requests limit exceeded");
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
} else { } else {
assertThat(result.getStatus().isOk()).isTrue(); assertThat(result.getStatus().isOk()).isTrue();
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L); assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
} }
} }

View File

@ -59,10 +59,9 @@ import io.grpc.internal.ServiceConfigUtil.PolicySelection;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig; import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
import io.grpc.xds.EnvoyProtoData.DropOverload; import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyProtoData.LbEndpoint; import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.EnvoyProtoData.Locality; import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
@ -104,11 +103,11 @@ public class ClusterResolverLoadBalancerTest {
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com"; private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
private static final String LRS_SERVER_NAME = "lrs.googleapis.com"; private static final String LRS_SERVER_NAME = "lrs.googleapis.com";
private final Locality locality1 = private final Locality locality1 =
new Locality("test-region-1", "test-zone-1", "test-subzone-1"); Locality.create("test-region-1", "test-zone-1", "test-subzone-1");
private final Locality locality2 = private final Locality locality2 =
new Locality("test-region-2", "test-zone-2", "test-subzone-2"); Locality.create("test-region-2", "test-zone-2", "test-subzone-2");
private final Locality locality3 = private final Locality locality3 =
new Locality("test-region-3", "test-zone-3", "test-subzone-3"); Locality.create("test-region-3", "test-zone-3", "test-subzone-3");
private final UpstreamTlsContext tlsContext = private final UpstreamTlsContext tlsContext =
CommonTlsContextTestsUtil.buildUpstreamTlsContextFromFilenames( CommonTlsContextTestsUtil.buildUpstreamTlsContextFromFilenames(
CommonTlsContextTestsUtil.CLIENT_KEY_FILE, CommonTlsContextTestsUtil.CLIENT_KEY_FILE,
@ -580,7 +579,7 @@ public class ClusterResolverLoadBalancerTest {
Collections.singletonList(endpoint3)); Collections.singletonList(endpoint3));
assertAddressesEqual(AddressFilter.filter(AddressFilter.filter( assertAddressesEqual(AddressFilter.filter(AddressFilter.filter(
childBalancer.addresses, CLUSTER_DNS + "[priority0]"), childBalancer.addresses, CLUSTER_DNS + "[priority0]"),
new Locality("", "", "").toString()), Locality.create("", "", "").toString()),
Arrays.asList(endpoint1, endpoint2)); Arrays.asList(endpoint1, endpoint2));
} }
@ -769,9 +768,9 @@ public class ClusterResolverLoadBalancerTest {
List<LbEndpoint> endpoints = new ArrayList<>(); List<LbEndpoint> endpoints = new ArrayList<>();
for (EquivalentAddressGroup addr : managedEndpoints.keySet()) { for (EquivalentAddressGroup addr : managedEndpoints.keySet()) {
boolean status = managedEndpoints.get(addr); boolean status = managedEndpoints.get(addr);
endpoints.add(new LbEndpoint(addr, 100 /* unused */, status)); endpoints.add(LbEndpoint.create(addr, 100 /* unused */, status));
} }
return new LocalityLbEndpoints(endpoints, localityWeight, priority); return LocalityLbEndpoints.create(endpoints, localityWeight, priority);
} }
private static EquivalentAddressGroup makeAddress(final String name) { private static EquivalentAddressGroup makeAddress(final String name) {

View File

@ -19,38 +19,10 @@ package io.grpc.xds;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.testing.EqualsTester;
import com.google.protobuf.BoolValue;
import com.google.protobuf.Struct; import com.google.protobuf.Struct;
import com.google.protobuf.UInt32Value;
import com.google.protobuf.Value; import com.google.protobuf.Value;
import com.google.protobuf.util.Durations;
import com.google.re2j.Pattern;
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher;
import io.envoyproxy.envoy.config.route.v3.RedirectAction;
import io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration;
import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.v3.FractionalPercent;
import io.envoyproxy.envoy.type.v3.Int64Range;
import io.grpc.xds.EnvoyProtoData.Address; import io.grpc.xds.EnvoyProtoData.Address;
import io.grpc.xds.EnvoyProtoData.ClusterStats;
import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests;
import io.grpc.xds.EnvoyProtoData.ClusterWeight;
import io.grpc.xds.EnvoyProtoData.EndpointLoadMetricStats;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.EnvoyProtoData.Route;
import io.grpc.xds.EnvoyProtoData.RouteAction;
import io.grpc.xds.EnvoyProtoData.StructOrError;
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
import io.grpc.xds.RouteMatch.FractionMatcher;
import io.grpc.xds.RouteMatch.HeaderMatcher;
import io.grpc.xds.RouteMatch.PathMatcher;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
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;
@ -61,41 +33,6 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
public class EnvoyProtoDataTest { public class EnvoyProtoDataTest {
@Test
public void locality_convertToAndFromLocalityProto() {
io.envoyproxy.envoy.config.core.v3.Locality locality =
io.envoyproxy.envoy.config.core.v3.Locality.newBuilder()
.setRegion("test_region")
.setZone("test_zone")
.setSubZone("test_subzone")
.build();
Locality xdsLocality = Locality.fromEnvoyProtoLocality(locality);
assertThat(xdsLocality.getRegion()).isEqualTo("test_region");
assertThat(xdsLocality.getZone()).isEqualTo("test_zone");
assertThat(xdsLocality.getSubZone()).isEqualTo("test_subzone");
io.envoyproxy.envoy.api.v2.core.Locality convertedLocality =
xdsLocality.toEnvoyProtoLocalityV2();
assertThat(convertedLocality.getRegion()).isEqualTo("test_region");
assertThat(convertedLocality.getZone()).isEqualTo("test_zone");
assertThat(convertedLocality.getSubZone()).isEqualTo("test_subzone");
}
@Test
public void locality_equal() {
new EqualsTester()
.addEqualityGroup(
new Locality("region-a", "zone-a", "subzone-a"),
new Locality("region-a", "zone-a", "subzone-a"))
.addEqualityGroup(
new Locality("region", "zone", "subzone")
)
.addEqualityGroup(
new Locality("", "", ""),
new Locality("", "", ""))
.testEquals();
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Test @Test
public void convertNode() { public void convertNode() {
@ -108,7 +45,7 @@ public class EnvoyProtoDataTest {
"ENVOY_PORT", "ENVOY_PORT",
"TRAFFICDIRECTOR_NETWORK_NAME", "TRAFFICDIRECTOR_NETWORK_NAME",
"VPC_NETWORK_NAME")) "VPC_NETWORK_NAME"))
.setLocality(new Locality("region", "zone", "subzone")) .setLocality(Locality.create("region", "zone", "subzone"))
.addListeningAddresses(new Address("www.foo.com", 8080)) .addListeningAddresses(new Address("www.foo.com", 8080))
.addListeningAddresses(new Address("www.bar.com", 8088)) .addListeningAddresses(new Address("www.bar.com", 8088))
.setBuildVersion("v1") .setBuildVersion("v1")
@ -185,448 +122,4 @@ public class EnvoyProtoDataTest {
.build(); .build();
assertThat(node.toEnvoyProtoNodeV2()).isEqualTo(nodeProtoV2); assertThat(node.toEnvoyProtoNodeV2()).isEqualTo(nodeProtoV2);
} }
@Test
public void locality_hash() {
assertThat(new Locality("region", "zone", "subzone").hashCode())
.isEqualTo(new Locality("region", "zone","subzone").hashCode());
}
// TODO(chengyuanzhang): add test for other data types.
@Test
public void convertRoute() {
io.envoyproxy.envoy.config.route.v3.Route proto1 =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method"))
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo"))
.build();
StructOrError<Route> struct1 = Route.fromEnvoyProtoRoute(proto1);
assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct())
.isEqualTo(
new Route(
new RouteMatch(PathMatcher.fromPath("/service/method", false),
Collections.<HeaderMatcher>emptyList(), null),
new RouteAction(null, "cluster-foo", null)));
io.envoyproxy.envoy.config.route.v3.Route unsupportedProto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("route-blade")
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
.setRedirect(RedirectAction.getDefaultInstance())
.build();
StructOrError<Route> unsupportedStruct = Route.fromEnvoyProtoRoute(unsupportedProto);
assertThat(unsupportedStruct.getErrorDetail()).isNotNull();
assertThat(unsupportedStruct.getStruct()).isNull();
}
@Test
public void convertRoute_skipWithUnsupportedMatcher() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method")
.addQueryParameters(
io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher
.getDefaultInstance()))
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo"))
.build();
assertThat(Route.fromEnvoyProtoRoute(proto)).isNull();
}
@Test
public void convertRoute_skipWithUnsupportedAction() {
io.envoyproxy.envoy.config.route.v3.Route proto =
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
.setName("ignore me")
.setMatch(
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method"))
.setRoute(
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setClusterHeader("some cluster header"))
.build();
assertThat(Route.fromEnvoyProtoRoute(proto)).isNull();
}
@Test
public void convertRouteMatch_pathMatching() {
// path_specifier = prefix
io.envoyproxy.envoy.config.route.v3.RouteMatch proto1 =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix("/").build();
StructOrError<RouteMatch> struct1 = Route.convertEnvoyProtoRouteMatch(proto1);
assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct()).isEqualTo(
new RouteMatch(
PathMatcher.fromPrefix("/", false), Collections.<HeaderMatcher>emptyList(), null));
proto1 = proto1.toBuilder().setCaseSensitive(BoolValue.newBuilder().setValue(true)).build();
struct1 = Route.convertEnvoyProtoRouteMatch(proto1);
assertThat(struct1.getStruct()).isEqualTo(
new RouteMatch(
PathMatcher.fromPrefix("/", true), Collections.<HeaderMatcher>emptyList(), null));
// path_specifier = path
io.envoyproxy.envoy.config.route.v3.RouteMatch proto2 =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPath("/service/method")
.build();
StructOrError<RouteMatch> struct2 = Route.convertEnvoyProtoRouteMatch(proto2);
assertThat(struct2.getErrorDetail()).isNull();
assertThat(struct2.getStruct()).isEqualTo(
new RouteMatch(
PathMatcher.fromPath("/service/method", false),
Collections.<HeaderMatcher>emptyList(), null));
proto2 = proto2.toBuilder().setCaseSensitive(BoolValue.newBuilder().setValue(true)).build();
struct2 = Route.convertEnvoyProtoRouteMatch(proto2);
assertThat(struct2.getStruct()).isEqualTo(
new RouteMatch(
PathMatcher.fromPath("/service/method", true),
Collections.<HeaderMatcher>emptyList(), null));
// path_specifier = safe_regex
io.envoyproxy.envoy.config.route.v3.RouteMatch proto4 =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setSafeRegex(RegexMatcher.newBuilder().setRegex("."))
.build();
StructOrError<RouteMatch> struct4 = Route.convertEnvoyProtoRouteMatch(proto4);
assertThat(struct4.getErrorDetail()).isNull();
assertThat(struct4.getStruct()).isEqualTo(
new RouteMatch(
PathMatcher.fromRegEx(Pattern.compile(".")),
Collections.<HeaderMatcher>emptyList(), null));
// query_parameters is set
io.envoyproxy.envoy.config.route.v3.RouteMatch proto6 =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.addQueryParameters(QueryParameterMatcher.getDefaultInstance())
.build();
StructOrError<RouteMatch> struct6 = Route.convertEnvoyProtoRouteMatch(proto6);
assertThat(struct6).isNull();
// path_specifier unset
io.envoyproxy.envoy.config.route.v3.RouteMatch unsetProto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.getDefaultInstance();
StructOrError<RouteMatch> unsetStruct = Route.convertEnvoyProtoRouteMatch(unsetProto);
assertThat(unsetStruct.getErrorDetail()).isNotNull();
assertThat(unsetStruct.getStruct()).isNull();
}
@Test
public void convertRouteMatch_withHeaderMatching() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPrefix("")
.addHeaders(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":scheme")
.setPrefixMatch("http"))
.addHeaders(
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setExactMatch("PUT"))
.build();
StructOrError<RouteMatch> struct = Route.convertEnvoyProtoRouteMatch(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
new RouteMatch(
PathMatcher.fromPrefix("", false),
Arrays.asList(
new HeaderMatcher(":scheme", null, null, null, null, "http", null, false),
new HeaderMatcher(":method", "PUT", null, null, null, null, null, false)),
null));
}
@Test
public void convertRouteMatch_withRuntimeFraction() {
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
.setPrefix("")
.setRuntimeFraction(
RuntimeFractionalPercent.newBuilder()
.setDefaultValue(
FractionalPercent.newBuilder()
.setNumerator(30)
.setDenominator(FractionalPercent.DenominatorType.HUNDRED)))
.build();
StructOrError<RouteMatch> struct = Route.convertEnvoyProtoRouteMatch(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct())
.isEqualTo(
new RouteMatch(
PathMatcher.fromPrefix( "", false), Collections.<HeaderMatcher>emptyList(),
new FractionMatcher(30, 100)));
}
@Test
public void convertRouteAction_cluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().getCluster()).isEqualTo("cluster-foo");
assertThat(struct.getStruct().getWeightedCluster()).isNull();
}
@Test
public void convertRouteAction_weightedCluster() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setWeightedClusters(
WeightedCluster.newBuilder()
.addClusters(
WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-foo")
.setWeight(UInt32Value.newBuilder().setValue(30)))
.addClusters(WeightedCluster.ClusterWeight
.newBuilder()
.setName("cluster-bar")
.setWeight(UInt32Value.newBuilder().setValue(70))))
.build();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getErrorDetail()).isNull();
assertThat(struct.getStruct().getCluster()).isNull();
assertThat(struct.getStruct().getWeightedCluster()).containsExactly(
new ClusterWeight("cluster-foo", 30, null), new ClusterWeight("cluster-bar", 70, null));
}
@Test
public void convertRouteAction_unspecifiedClusterError() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.getDefaultInstance();
StructOrError<RouteAction> unsetStruct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(unsetStruct.getStruct()).isNull();
assertThat(unsetStruct.getErrorDetail()).isNotNull();
}
@Test
public void convertRouteAction_timeoutByGrpcTimeoutHeaderMax() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setGrpcTimeoutHeaderMax(Durations.fromSeconds(5L))
.setMaxStreamDuration(Durations.fromMillis(20L)))
.build();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getStruct().getTimeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}
@Test
public void convertRouteAction_timeoutByMaxStreamDuration() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.setMaxStreamDuration(
MaxStreamDuration.newBuilder()
.setMaxStreamDuration(Durations.fromSeconds(5L)))
.build();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getStruct().getTimeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
}
@Test
public void convertRouteAction_timeoutUnset() {
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
.setCluster("cluster-foo")
.build();
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
assertThat(struct.getStruct().getTimeoutNano()).isNull();
}
@Test
public void convertHeaderMatcher() {
// header_match_specifier = exact_match
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto1 =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setExactMatch("PUT")
.build();
StructOrError<HeaderMatcher> struct1 = Route.convertEnvoyProtoHeaderMatcher(proto1);
assertThat(struct1.getErrorDetail()).isNull();
assertThat(struct1.getStruct()).isEqualTo(
new HeaderMatcher(":method", "PUT", null, null, null, null, null, false));
// header_match_specifier = safe_regex_match
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto3 =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("P*"))
.build();
StructOrError<HeaderMatcher> struct3 = Route.convertEnvoyProtoHeaderMatcher(proto3);
assertThat(struct3.getErrorDetail()).isNull();
assertThat(struct3.getStruct()).isEqualTo(
new HeaderMatcher(":method", null, Pattern.compile("P*"), null, null, null, null, false));
// header_match_specifier = range_match
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto4 =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("timeout")
.setRangeMatch(Int64Range.newBuilder().setStart(10L).setEnd(20L))
.build();
StructOrError<HeaderMatcher> struct4 = Route.convertEnvoyProtoHeaderMatcher(proto4);
assertThat(struct4.getErrorDetail()).isNull();
assertThat(struct4.getStruct()).isEqualTo(
new HeaderMatcher(
"timeout", null, null, new HeaderMatcher.Range(10L, 20L), null, null, null, false));
// header_match_specifier = present_match
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto5 =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("user-agent")
.setPresentMatch(true)
.build();
StructOrError<HeaderMatcher> struct5 = Route.convertEnvoyProtoHeaderMatcher(proto5);
assertThat(struct5.getErrorDetail()).isNull();
assertThat(struct5.getStruct()).isEqualTo(
new HeaderMatcher("user-agent", null, null, null, true, null, null, false));
// header_match_specifier = prefix_match
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto6 =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("authority")
.setPrefixMatch("service-foo")
.build();
StructOrError<HeaderMatcher> struct6 = Route.convertEnvoyProtoHeaderMatcher(proto6);
assertThat(struct6.getErrorDetail()).isNull();
assertThat(struct6.getStruct()).isEqualTo(
new HeaderMatcher("authority", null, null, null, null, "service-foo", null, false));
// header_match_specifier = suffix_match
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto7 =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName("authority")
.setSuffixMatch("googleapis.com")
.build();
StructOrError<HeaderMatcher> struct7 = Route.convertEnvoyProtoHeaderMatcher(proto7);
assertThat(struct7.getErrorDetail()).isNull();
assertThat(struct7.getStruct()).isEqualTo(
new HeaderMatcher(
"authority", null, null, null, null, null, "googleapis.com", false));
// header_match_specifier unset
io.envoyproxy.envoy.config.route.v3.HeaderMatcher unsetProto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.getDefaultInstance();
StructOrError<HeaderMatcher> unsetStruct = Route.convertEnvoyProtoHeaderMatcher(unsetProto);
assertThat(unsetStruct.getErrorDetail()).isNotNull();
assertThat(unsetStruct.getStruct()).isNull();
}
@Test
public void convertHeaderMatcher_malformedRegExPattern() {
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
.setName(":method")
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("["))
.build();
StructOrError<HeaderMatcher> struct = Route.convertEnvoyProtoHeaderMatcher(proto);
assertThat(struct.getErrorDetail()).isNotNull();
assertThat(struct.getStruct()).isNull();
}
@Test
public void convertClusterWeight() {
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto =
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight.newBuilder()
.setName("cluster-foo")
.setWeight(UInt32Value.newBuilder().setValue(30)).build();
ClusterWeight struct = ClusterWeight.fromEnvoyProtoClusterWeight(proto).getStruct();
assertThat(struct.getName()).isEqualTo("cluster-foo");
assertThat(struct.getWeight()).isEqualTo(30);
}
@Test
public void clusterStats_convertToEnvoyProto() {
ClusterStats clusterStats =
ClusterStats.newBuilder()
.setClusterName("cluster1")
.setClusterServiceName("backend-service1")
.setLoadReportIntervalNanos(1234)
.setTotalDroppedRequests(123)
.addUpstreamLocalityStats(UpstreamLocalityStats.newBuilder()
.setLocality(new Locality("region1", "zone1", "subzone1"))
.setTotalErrorRequests(1)
.setTotalRequestsInProgress(2)
.setTotalSuccessfulRequests(100)
.setTotalIssuedRequests(103)
.addLoadMetricStats(EndpointLoadMetricStats.newBuilder()
.setMetricName("metric1")
.setNumRequestsFinishedWithMetric(1000)
.setTotalMetricValue(0.5D)
.build())
.build())
.addDroppedRequests(new DroppedRequests("category1", 100))
.build();
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats clusterStatsProto =
clusterStats.toEnvoyProtoClusterStats();
assertThat(clusterStatsProto).isEqualTo(
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.newBuilder()
.setClusterName("cluster1")
.setClusterServiceName("backend-service1")
.setLoadReportInterval(Durations.fromNanos(1234))
.setTotalDroppedRequests(123)
.addUpstreamLocalityStats(
io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.newBuilder()
.setLocality(
new Locality("region1", "zone1", "subzone1").toEnvoyProtoLocality())
.setTotalErrorRequests(1)
.setTotalRequestsInProgress(2)
.setTotalSuccessfulRequests(100)
.setTotalIssuedRequests(103)
.addLoadMetricStats(
io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats.newBuilder()
.setMetricName("metric1")
.setNumRequestsFinishedWithMetric(1000)
.setTotalMetricValue(0.5D)))
.addDroppedRequests(
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests.newBuilder()
.setCategory("category1")
.setDroppedCount(100))
.build());
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats clusterStatsProtoV2 =
clusterStats.toEnvoyProtoClusterStatsV2();
assertThat(clusterStatsProtoV2).isEqualTo(
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.newBuilder()
.setClusterName("cluster1")
.setClusterServiceName("backend-service1")
.setLoadReportInterval(Durations.fromNanos(1234))
.setTotalDroppedRequests(123)
.addUpstreamLocalityStats(
io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.newBuilder()
.setLocality(
new Locality("region1", "zone1", "subzone1").toEnvoyProtoLocalityV2())
.setTotalErrorRequests(1)
.setTotalRequestsInProgress(2)
.setTotalSuccessfulRequests(100)
.setTotalIssuedRequests(103)
.addLoadMetricStats(
io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats.newBuilder()
.setMetricName("metric1")
.setNumRequestsFinishedWithMetric(1000)
.setTotalMetricValue(0.5D)))
.addDroppedRequests(
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests.newBuilder()
.setCategory("category1")
.setDroppedCount(100))
.build());
}
} }

View File

@ -50,7 +50,6 @@ import io.grpc.internal.BackoffPolicy;
import io.grpc.internal.FakeClock; import io.grpc.internal.FakeClock;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
import io.grpc.testing.GrpcCleanupRule; import io.grpc.testing.GrpcCleanupRule;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -85,8 +84,8 @@ public class LoadReportClientTest {
private static final String CLUSTER2 = "cluster-bar.googleapis.com"; private static final String CLUSTER2 = "cluster-bar.googleapis.com";
private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com"; private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com";
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com"; private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
private static final Locality LOCALITY1 = new Locality("region1", "zone1", "subZone1"); private static final Locality LOCALITY1 = Locality.create("region1", "zone1", "subZone1");
private static final Locality LOCALITY2 = new Locality("region2", "zone2", "subZone2"); private static final Locality LOCALITY2 = Locality.create("region2", "zone2", "subZone2");
private static final FakeClock.TaskFilter LOAD_REPORTING_TASK_FILTER = private static final FakeClock.TaskFilter LOAD_REPORTING_TASK_FILTER =
new FakeClock.TaskFilter() { new FakeClock.TaskFilter() {
@Override @Override

View File

@ -21,12 +21,11 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import io.grpc.Status; import io.grpc.Status;
import io.grpc.internal.FakeClock; import io.grpc.internal.FakeClock;
import io.grpc.xds.EnvoyProtoData.ClusterStats;
import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
import io.grpc.xds.Stats.ClusterStats;
import io.grpc.xds.Stats.DroppedRequests;
import io.grpc.xds.Stats.UpstreamLocalityStats;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -45,11 +44,11 @@ public class LoadStatsManager2Test {
private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com"; private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com";
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com"; private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
private static final Locality LOCALITY1 = private static final Locality LOCALITY1 =
new Locality("test_region1", "test_zone1", "test_subzone1"); Locality.create("test_region1", "test_zone1", "test_subzone1");
private static final Locality LOCALITY2 = private static final Locality LOCALITY2 =
new Locality("test_region2", "test_zone2", "test_subzone2"); Locality.create("test_region2", "test_zone2", "test_subzone2");
private static final Locality LOCALITY3 = private static final Locality LOCALITY3 =
new Locality("test_region3", "test_zone3", "test_subzone3"); Locality.create("test_region3", "test_zone3", "test_subzone3");
private final FakeClock fakeClock = new FakeClock(); private final FakeClock fakeClock = new FakeClock();
private final LoadStatsManager2 loadStatsManager = private final LoadStatsManager2 loadStatsManager =
@ -85,68 +84,68 @@ public class LoadStatsManager2Test {
assertThat(allStats).hasSize(3); // three cluster:edsServiceName assertThat(allStats).hasSize(3); // three cluster:edsServiceName
ClusterStats stats1 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME1); ClusterStats stats1 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME1);
assertThat(stats1.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L)); assertThat(stats1.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats1.getDroppedRequestsList()).hasSize(2); assertThat(stats1.droppedRequestsList()).hasSize(2);
assertThat(findDroppedRequestCount(stats1.getDroppedRequestsList(), "lb")).isEqualTo(1L); assertThat(findDroppedRequestCount(stats1.droppedRequestsList(), "lb")).isEqualTo(1L);
assertThat(findDroppedRequestCount(stats1.getDroppedRequestsList(), "throttle")).isEqualTo(1L); assertThat(findDroppedRequestCount(stats1.droppedRequestsList(), "throttle")).isEqualTo(1L);
assertThat(stats1.getTotalDroppedRequests()).isEqualTo(1L + 1L); assertThat(stats1.totalDroppedRequests()).isEqualTo(1L + 1L);
assertThat(stats1.getUpstreamLocalityStatsList()).hasSize(2); // two localities assertThat(stats1.upstreamLocalityStatsList()).hasSize(2); // two localities
UpstreamLocalityStats loadStats1 = UpstreamLocalityStats loadStats1 =
findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY1); findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY1);
assertThat(loadStats1.getTotalIssuedRequests()).isEqualTo(19L); assertThat(loadStats1.totalIssuedRequests()).isEqualTo(19L);
assertThat(loadStats1.getTotalSuccessfulRequests()).isEqualTo(1L); assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(loadStats1.getTotalErrorRequests()).isEqualTo(0L); assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalRequestsInProgress()).isEqualTo(19L - 1L); assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(19L - 1L);
UpstreamLocalityStats loadStats2 = UpstreamLocalityStats loadStats2 =
findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY2); findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
assertThat(loadStats2.getTotalIssuedRequests()).isEqualTo(9L); assertThat(loadStats2.totalIssuedRequests()).isEqualTo(9L);
assertThat(loadStats2.getTotalSuccessfulRequests()).isEqualTo(0L); assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalErrorRequests()).isEqualTo(1L); assertThat(loadStats2.totalErrorRequests()).isEqualTo(1L);
assertThat(loadStats2.getTotalRequestsInProgress()).isEqualTo(9L - 1L); assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(9L - 1L);
ClusterStats stats2 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME2); ClusterStats stats2 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME2);
assertThat(stats2.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L)); assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats2.getDroppedRequestsList()).isEmpty(); // no categorized drops assertThat(stats2.droppedRequestsList()).isEmpty(); // no categorized drops
assertThat(stats2.getTotalDroppedRequests()).isEqualTo(1L); assertThat(stats2.totalDroppedRequests()).isEqualTo(1L);
assertThat(stats2.getUpstreamLocalityStatsList()).isEmpty(); // no per-locality stats assertThat(stats2.upstreamLocalityStatsList()).isEmpty(); // no per-locality stats
ClusterStats stats3 = findClusterStats(allStats, CLUSTER_NAME2, null); ClusterStats stats3 = findClusterStats(allStats, CLUSTER_NAME2, null);
assertThat(stats3.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L)); assertThat(stats3.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats3.getDroppedRequestsList()).isEmpty(); assertThat(stats3.droppedRequestsList()).isEmpty();
assertThat(stats3.getTotalDroppedRequests()).isEqualTo(0L); // no drops recorded assertThat(stats3.totalDroppedRequests()).isEqualTo(0L); // no drops recorded
assertThat(stats3.getUpstreamLocalityStatsList()).hasSize(1); // one localities assertThat(stats3.upstreamLocalityStatsList()).hasSize(1); // one localities
UpstreamLocalityStats loadStats3 = UpstreamLocalityStats loadStats3 =
Iterables.getOnlyElement(stats3.getUpstreamLocalityStatsList()); Iterables.getOnlyElement(stats3.upstreamLocalityStatsList());
assertThat(loadStats3.getTotalIssuedRequests()).isEqualTo(1L); assertThat(loadStats3.totalIssuedRequests()).isEqualTo(1L);
assertThat(loadStats3.getTotalSuccessfulRequests()).isEqualTo(0L); assertThat(loadStats3.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats3.getTotalErrorRequests()).isEqualTo(0L); assertThat(loadStats3.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats3.getTotalRequestsInProgress()).isEqualTo(1L); assertThat(loadStats3.totalRequestsInProgress()).isEqualTo(1L);
fakeClock.forwardTime(3L, TimeUnit.SECONDS); fakeClock.forwardTime(3L, TimeUnit.SECONDS);
List<ClusterStats> clusterStatsList = loadStatsManager.getClusterStatsReports(CLUSTER_NAME1); List<ClusterStats> clusterStatsList = loadStatsManager.getClusterStatsReports(CLUSTER_NAME1);
assertThat(clusterStatsList).hasSize(2); assertThat(clusterStatsList).hasSize(2);
stats1 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME1); stats1 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME1);
assertThat(stats1.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(3L)); assertThat(stats1.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
assertThat(stats1.getDroppedRequestsList()).isEmpty(); assertThat(stats1.droppedRequestsList()).isEmpty();
assertThat(stats1.getTotalDroppedRequests()).isEqualTo(0L); // no new drops recorded assertThat(stats1.totalDroppedRequests()).isEqualTo(0L); // no new drops recorded
assertThat(stats1.getUpstreamLocalityStatsList()).hasSize(2); // two localities assertThat(stats1.upstreamLocalityStatsList()).hasSize(2); // two localities
loadStats1 = findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY1); loadStats1 = findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY1);
assertThat(loadStats1.getTotalIssuedRequests()).isEqualTo(0L); assertThat(loadStats1.totalIssuedRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalSuccessfulRequests()).isEqualTo(0L); assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalErrorRequests()).isEqualTo(0L); assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalRequestsInProgress()).isEqualTo(18L); // still in-progress assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(18L); // still in-progress
loadStats2 = findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY2); loadStats2 = findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
assertThat(loadStats2.getTotalIssuedRequests()).isEqualTo(0L); assertThat(loadStats2.totalIssuedRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalSuccessfulRequests()).isEqualTo(0L); assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalErrorRequests()).isEqualTo(0L); assertThat(loadStats2.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalRequestsInProgress()).isEqualTo(8L); // still in-progress assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(8L); // still in-progress
stats2 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME2); stats2 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME2);
assertThat(stats2.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(3L)); assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
assertThat(stats2.getDroppedRequestsList()).isEmpty(); assertThat(stats2.droppedRequestsList()).isEmpty();
assertThat(stats2.getTotalDroppedRequests()).isEqualTo(0L); // no new drops recorded assertThat(stats2.totalDroppedRequests()).isEqualTo(0L); // no new drops recorded
assertThat(stats2.getUpstreamLocalityStatsList()).isEmpty(); // no per-locality stats assertThat(stats2.upstreamLocalityStatsList()).isEmpty(); // no per-locality stats
} }
@Test @Test
@ -162,10 +161,10 @@ public class LoadStatsManager2Test {
ClusterStats stats = Iterables.getOnlyElement( ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)); loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
assertThat(stats.getDroppedRequestsList()).hasSize(2); assertThat(stats.droppedRequestsList()).hasSize(2);
assertThat(findDroppedRequestCount(stats.getDroppedRequestsList(), "lb")).isEqualTo(1L); assertThat(findDroppedRequestCount(stats.droppedRequestsList(), "lb")).isEqualTo(1L);
assertThat(findDroppedRequestCount(stats.getDroppedRequestsList(), "throttle")).isEqualTo(1L); assertThat(findDroppedRequestCount(stats.droppedRequestsList(), "throttle")).isEqualTo(1L);
assertThat(stats.getTotalDroppedRequests()).isEqualTo(4L); // 2 cagetorized + 2 uncategoized assertThat(stats.totalDroppedRequests()).isEqualTo(4L); // 2 cagetorized + 2 uncategoized
} }
@Test @Test
@ -175,15 +174,15 @@ public class LoadStatsManager2Test {
counter.recordDroppedRequest("lb"); counter.recordDroppedRequest("lb");
ClusterStats stats = Iterables.getOnlyElement( ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)); loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
assertThat(stats.getDroppedRequestsList()).hasSize(1); assertThat(stats.droppedRequestsList()).hasSize(1);
assertThat(Iterables.getOnlyElement(stats.getDroppedRequestsList()).getDroppedCount()) assertThat(Iterables.getOnlyElement(stats.droppedRequestsList()).droppedCount())
.isEqualTo(1L); .isEqualTo(1L);
assertThat(stats.getTotalDroppedRequests()).isEqualTo(1L); assertThat(stats.totalDroppedRequests()).isEqualTo(1L);
counter.release(); counter.release();
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)); stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
assertThat(stats.getDroppedRequestsList()).isEmpty(); assertThat(stats.droppedRequestsList()).isEmpty();
assertThat(stats.getTotalDroppedRequests()).isEqualTo(0L); assertThat(stats.totalDroppedRequests()).isEqualTo(0L);
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty(); assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
} }
@ -203,11 +202,11 @@ public class LoadStatsManager2Test {
ClusterStats stats = Iterables.getOnlyElement( ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)); loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
UpstreamLocalityStats localityStats = UpstreamLocalityStats localityStats =
Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList()); Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(1L + 2L); assertThat(localityStats.totalIssuedRequests()).isEqualTo(1L + 2L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L); assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L); assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(1L + 2L - 1L - 1L); assertThat(localityStats.totalRequestsInProgress()).isEqualTo(1L + 2L - 1L - 1L);
} }
@Test @Test
@ -220,30 +219,30 @@ public class LoadStatsManager2Test {
ClusterStats stats = Iterables.getOnlyElement( ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)); loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
UpstreamLocalityStats localityStats = UpstreamLocalityStats localityStats =
Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList()); Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(2L); assertThat(localityStats.totalIssuedRequests()).isEqualTo(2L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L); assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L); assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(2L); assertThat(localityStats.totalRequestsInProgress()).isEqualTo(2L);
// release the counter, but requests still in-flight // release the counter, but requests still in-flight
counter.release(); counter.release();
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)); stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
localityStats = Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList()); localityStats = Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L); assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L); assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L); assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()) assertThat(localityStats.totalRequestsInProgress())
.isEqualTo(2L); // retained by in-flight calls .isEqualTo(2L); // retained by in-flight calls
counter.recordCallFinished(Status.OK); counter.recordCallFinished(Status.OK);
counter.recordCallFinished(Status.UNAVAILABLE); counter.recordCallFinished(Status.UNAVAILABLE);
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)); stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
localityStats = Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList()); localityStats = Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L); assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L); assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L); assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(0L); assertThat(localityStats.totalRequestsInProgress()).isEqualTo(0L);
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty(); assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
} }
@ -252,8 +251,8 @@ public class LoadStatsManager2Test {
private static ClusterStats findClusterStats( private static ClusterStats findClusterStats(
List<ClusterStats> statsList, String cluster, @Nullable String edsServiceName) { List<ClusterStats> statsList, String cluster, @Nullable String edsServiceName) {
for (ClusterStats stats : statsList) { for (ClusterStats stats : statsList) {
if (stats.getClusterName().equals(cluster) if (stats.clusterName().equals(cluster)
&& Objects.equals(stats.getClusterServiceName(), edsServiceName)) { && Objects.equals(stats.clusterServiceName(), edsServiceName)) {
return stats; return stats;
} }
} }
@ -264,7 +263,7 @@ public class LoadStatsManager2Test {
private static UpstreamLocalityStats findLocalityStats( private static UpstreamLocalityStats findLocalityStats(
List<UpstreamLocalityStats> localityStatsList, Locality locality) { List<UpstreamLocalityStats> localityStatsList, Locality locality) {
for (UpstreamLocalityStats stats : localityStatsList) { for (UpstreamLocalityStats stats : localityStatsList) {
if (stats.getLocality().equals(locality)) { if (stats.locality().equals(locality)) {
return stats; return stats;
} }
} }
@ -275,11 +274,11 @@ public class LoadStatsManager2Test {
List<DroppedRequests> droppedRequestsLists, String category) { List<DroppedRequests> droppedRequestsLists, String category) {
DroppedRequests drop = null; DroppedRequests drop = null;
for (DroppedRequests stats : droppedRequestsLists) { for (DroppedRequests stats : droppedRequestsLists) {
if (stats.getCategory().equals(category)) { if (stats.category().equals(category)) {
drop = stats; drop = stats;
} }
} }
assertThat(drop).isNotNull(); assertThat(drop).isNotNull();
return drop.getDroppedCount(); return drop.droppedCount();
} }
} }

View File

@ -1,212 +0,0 @@
/*
* Copyright 2020 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 com.google.re2j.Pattern;
import io.grpc.xds.RouteMatch.FractionMatcher;
import io.grpc.xds.RouteMatch.HeaderMatcher;
import io.grpc.xds.RouteMatch.HeaderMatcher.Range;
import io.grpc.xds.RouteMatch.PathMatcher;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link RouteMatch}. */
@RunWith(JUnit4.class)
public class RouteMatchTest {
private final Map<String, Iterable<String>> headers = new HashMap<>();
@Before
public void setUp() {
headers.put("authority", Collections.singletonList("foo.googleapis.com"));
headers.put("grpc-encoding", Collections.singletonList("gzip"));
headers.put("user-agent", Collections.singletonList("gRPC-Java"));
headers.put("content-length", Collections.singletonList("1000"));
headers.put("custom-key", Arrays.asList("custom-value1", "custom-value2"));
}
@Test
public void routeMatching_pathOnly() {
RouteMatch routeMatch1 =
new RouteMatch(
PathMatcher.fromPath("/FooService/barMethod", true),
Collections.<HeaderMatcher>emptyList(), null);
assertThat(routeMatch1.matches("/FooService/barMethod", headers)).isTrue();
assertThat(routeMatch1.matches("/FooService/bazMethod", headers)).isFalse();
RouteMatch routeMatch2 =
new RouteMatch(
PathMatcher.fromPrefix("/FooService/", true),
Collections.<HeaderMatcher>emptyList(), null);
assertThat(routeMatch2.matches("/FooService/barMethod", headers)).isTrue();
assertThat(routeMatch2.matches("/FooService/bazMethod", headers)).isTrue();
assertThat(routeMatch2.matches("/BarService/bazMethod", headers)).isFalse();
RouteMatch routeMatch3 =
new RouteMatch(
PathMatcher.fromRegEx(Pattern.compile(".*Foo.*")),
Collections.<HeaderMatcher>emptyList(), null);
assertThat(routeMatch3.matches("/FooService/barMethod", headers)).isTrue();
}
@Test
public void pathMatching_caseInsensitive() {
PathMatcher pathMatcher1 = PathMatcher.fromPath("/FooService/barMethod", false);
assertThat(pathMatcher1.matches("/fooservice/barmethod")).isTrue();
PathMatcher pathMatcher2 = PathMatcher.fromPrefix("/FooService", false);
assertThat(pathMatcher2.matches("/fooservice/barmethod")).isTrue();
}
@Test
public void routeMatching_withHeaders() {
PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true);
RouteMatch routeMatch1 = new RouteMatch(
pathMatcher,
Arrays.asList(
new HeaderMatcher(
"grpc-encoding", "gzip", null, null, null, null, null, false),
new HeaderMatcher(
"authority", null, Pattern.compile(".*googleapis.*"), null, null, null,
null, false),
new HeaderMatcher(
"content-length", null, null, new Range(100, 10000), null, null, null, false),
new HeaderMatcher("user-agent", null, null, null, true, null, null, false),
new HeaderMatcher("custom-key", null, null, null, null, "custom-", null, false),
new HeaderMatcher("custom-key", null, null, null, null, null, "value2", false)),
null);
assertThat(routeMatch1.matches("/FooService/barMethod", headers)).isTrue();
RouteMatch routeMatch2 = new RouteMatch(
pathMatcher,
Collections.singletonList(
new HeaderMatcher(
"authority", null, Pattern.compile(".*googleapis.*"), null, null, null,
null, true)),
null);
assertThat(routeMatch2.matches("/FooService/barMethod", headers)).isFalse();
RouteMatch routeMatch3 = new RouteMatch(
pathMatcher,
Collections.singletonList(
new HeaderMatcher(
"user-agent", "gRPC-Go", null, null, null, null,
null, false)),
null);
assertThat(routeMatch3.matches("/FooService/barMethod", headers)).isFalse();
RouteMatch routeMatch4 = new RouteMatch(
pathMatcher,
Collections.singletonList(
new HeaderMatcher(
"user-agent", null, null, null, false, null,
null, false)),
null);
assertThat(routeMatch4.matches("/FooService/barMethod", headers)).isFalse();
RouteMatch routeMatch5 = new RouteMatch(
pathMatcher,
Collections.singletonList(
new HeaderMatcher(
"user-agent", null, null, null, false, null,
null, true)),
null);
assertThat(routeMatch5.matches("/FooService/barMethod", headers)).isTrue();
RouteMatch routeMatch6 = new RouteMatch(
pathMatcher,
Collections.singletonList(
new HeaderMatcher(
"user-agent", null, null, null, true, null,
null, true)),
null);
assertThat(routeMatch6.matches("/FooService/barMethod", headers)).isFalse();
RouteMatch routeMatch7 = new RouteMatch(
pathMatcher,
Collections.singletonList(
new HeaderMatcher(
"custom-key", "custom-value1,custom-value2", null, null, null, null,
null, false)),
null);
assertThat(routeMatch7.matches("/FooService/barMethod", headers)).isTrue();
}
@Test
public void routeMatching_withRuntimeFraction() {
PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true);
RouteMatch routeMatch1 =
new RouteMatch(
pathMatcher,
Collections.<HeaderMatcher>emptyList(),
new FractionMatcher(100, 1000, new FakeRandom(50)));
assertThat(routeMatch1.matches("/FooService/barMethod", headers)).isTrue();
RouteMatch routeMatch2 =
new RouteMatch(
pathMatcher,
Collections.<HeaderMatcher>emptyList(),
new FractionMatcher(100, 1000, new FakeRandom(100)));
assertThat(routeMatch2.matches("/FooService/barMethod", headers)).isFalse();
}
@Test
public void headerMatching_specialCaseGrpcHeaders() {
PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true);
Map<String, Iterable<String>> headers = new HashMap<>();
headers.put("grpc-previous-rpc-attempts", Collections.singletonList("0"));
RouteMatch routeMatch1 =
new RouteMatch(pathMatcher,
Arrays.asList(
new HeaderMatcher(
"grpc-previous-rpc-attempts", "0", null, null, null, null,
null, false)),
null);
assertThat(routeMatch1.matches("/FooService/barMethod", headers)).isFalse();
RouteMatch routeMatch2 =
new RouteMatch(pathMatcher,
Arrays.asList(
new HeaderMatcher(
"content-type", "application/grpc", null, null, null, null,
null, false)),
null);
assertThat(routeMatch2.matches("/FooService/barMethod", headers)).isTrue();
}
private static final class FakeRandom implements ThreadSafeRandom {
private final int value;
FakeRandom(int value) {
this.value = value;
}
@Override
public int nextInt(int bound) {
return value;
}
}
}

View File

@ -18,6 +18,7 @@ package io.grpc.xds;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -26,6 +27,7 @@ import static org.mockito.Mockito.when;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.re2j.Pattern;
import io.grpc.CallOptions; import io.grpc.CallOptions;
import io.grpc.Channel; import io.grpc.Channel;
import io.grpc.ClientCall; import io.grpc.ClientCall;
@ -49,15 +51,18 @@ import io.grpc.internal.NoopClientCall.NoopClientCallListener;
import io.grpc.internal.ObjectPool; import io.grpc.internal.ObjectPool;
import io.grpc.internal.PickSubchannelArgsImpl; import io.grpc.internal.PickSubchannelArgsImpl;
import io.grpc.testing.TestMethodDescriptors; import io.grpc.testing.TestMethodDescriptors;
import io.grpc.xds.EnvoyProtoData.ClusterWeight; import io.grpc.xds.Matchers.HeaderMatcher;
import io.grpc.xds.EnvoyProtoData.Route; import io.grpc.xds.Matchers.PathMatcher;
import io.grpc.xds.EnvoyProtoData.RouteAction; import io.grpc.xds.VirtualHost.Route;
import io.grpc.xds.EnvoyProtoData.VirtualHost; import io.grpc.xds.VirtualHost.Route.RouteAction;
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
import io.grpc.xds.VirtualHost.Route.RouteMatch;
import io.grpc.xds.XdsClient.RdsResourceWatcher; import io.grpc.xds.XdsClient.RdsResourceWatcher;
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -232,25 +237,25 @@ public class XdsNameResolverTest {
} }
private List<VirtualHost> buildUnmatchedVirtualHosts() { private List<VirtualHost> buildUnmatchedVirtualHosts() {
Route route1 = new Route(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), Route route1 = Route.create(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null);
Route route2 = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), Route route2 = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)); RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null);
return Arrays.asList( return Arrays.asList(
new VirtualHost("virtualhost-foo", Collections.singletonList("hello.googleapis.com"), VirtualHost.create("virtualhost-foo", Collections.singletonList("hello.googleapis.com"),
Collections.singletonList(route1)), Collections.singletonList(route1), null),
new VirtualHost("virtualhost-bar", Collections.singletonList("hi.googleapis.com"), VirtualHost.create("virtualhost-bar", Collections.singletonList("hi.googleapis.com"),
Collections.singletonList(route2))); Collections.singletonList(route2), null));
} }
@Test @Test
public void resolved_noTimeout() { public void resolved_noTimeout() {
resolver.start(mockListener); resolver.start(mockListener);
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
Route route = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), Route route = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(null, cluster1, null)); // per-route timeout unset RouteAction.forCluster(cluster1, null), null); // per-route timeout unset
VirtualHost virtualHost = new VirtualHost("does not matter", VirtualHost virtualHost = VirtualHost.create("does not matter",
Collections.singletonList(AUTHORITY), Collections.singletonList(route)); Collections.singletonList(AUTHORITY), Collections.singletonList(route), null);
xdsClient.deliverLdsUpdate(AUTHORITY, 0L, Collections.singletonList(virtualHost)); xdsClient.deliverLdsUpdate(AUTHORITY, 0L, Collections.singletonList(virtualHost));
verify(mockListener).onResult(resolutionResultCaptor.capture()); verify(mockListener).onResult(resolutionResultCaptor.capture());
ResolutionResult result = resolutionResultCaptor.getValue(); ResolutionResult result = resolutionResultCaptor.getValue();
@ -262,10 +267,10 @@ public class XdsNameResolverTest {
public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() { public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() {
resolver.start(mockListener); resolver.start(mockListener);
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
Route route = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), Route route = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(null, cluster1, null)); // per-route timeout unset RouteAction.forCluster(cluster1, null), null); // per-route timeout unset
VirtualHost virtualHost = new VirtualHost("does not matter", VirtualHost virtualHost = VirtualHost.create("does not matter",
Collections.singletonList(AUTHORITY), Collections.singletonList(route)); Collections.singletonList(AUTHORITY), Collections.singletonList(route), null);
xdsClient.deliverLdsUpdate(AUTHORITY, TimeUnit.SECONDS.toNanos(5L), xdsClient.deliverLdsUpdate(AUTHORITY, TimeUnit.SECONDS.toNanos(5L),
Collections.singletonList(virtualHost)); Collections.singletonList(virtualHost));
verify(mockListener).onResult(resolutionResultCaptor.capture()); verify(mockListener).onResult(resolutionResultCaptor.capture());
@ -307,12 +312,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Arrays.asList( Arrays.asList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)), RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
new Route( Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
verify(mockListener).onResult(resolutionResultCaptor.capture()); verify(mockListener).onResult(resolutionResultCaptor.capture());
ResolutionResult result = resolutionResultCaptor.getValue(); ResolutionResult result = resolutionResultCaptor.getValue();
// Updated service config still contains cluster1 while it is removed resource. New calls no // Updated service config still contains cluster1 while it is removed resource. New calls no
@ -342,12 +347,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Arrays.asList( Arrays.asList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)), RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
new Route( Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
// Two consecutive service config updates: one for removing clcuster1, // Two consecutive service config updates: one for removing clcuster1,
// one for adding "another=cluster". // one for adding "another=cluster".
verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture()); verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture());
@ -373,12 +378,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Arrays.asList( Arrays.asList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)), RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
new Route( Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
verify(mockListener).onResult(resolutionResultCaptor.capture()); verify(mockListener).onResult(resolutionResultCaptor.capture());
ResolutionResult result = resolutionResultCaptor.getValue(); ResolutionResult result = resolutionResultCaptor.getValue();
@ -389,12 +394,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Arrays.asList( Arrays.asList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), "another-cluster", null)), RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(15L)), null),
new Route( Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
verifyNoMoreInteractions(mockListener); // no cluster added/deleted verifyNoMoreInteractions(mockListener); // no cluster added/deleted
assertCallSelectResult(call1, configSelector, "another-cluster", 15.0); assertCallSelectResult(call1, configSelector, "another-cluster", 15.0);
} }
@ -407,18 +412,18 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Collections.singletonList( Collections.singletonList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Arrays.asList( Arrays.asList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)), RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null),
new Route( Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
testCall.deliverErrorStatus(); testCall.deliverErrorStatus();
verifyNoMoreInteractions(mockListener); verifyNoMoreInteractions(mockListener);
} }
@ -431,14 +436,14 @@ public class XdsNameResolverTest {
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient(); FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Arrays.asList( Collections.singletonList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction( RouteAction.forWeightedClusters(
TimeUnit.SECONDS.toNanos(20L), null,
Arrays.asList( Arrays.asList(
new ClusterWeight(cluster1, 20, null), ClusterWeight.create(cluster1, 20, null),
new ClusterWeight(cluster2, 80, null)))))); ClusterWeight.create(cluster2, 80, null)),
TimeUnit.SECONDS.toNanos(20L)), null)));
verify(mockListener).onResult(resolutionResultCaptor.capture()); verify(mockListener).onResult(resolutionResultCaptor.capture());
ResolutionResult result = resolutionResultCaptor.getValue(); ResolutionResult result = resolutionResultCaptor.getValue();
assertThat(result.getAddresses()).isEmpty(); assertThat(result.getAddresses()).isEmpty();
@ -493,12 +498,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate( xdsClient.deliverLdsUpdate(
AUTHORITY, AUTHORITY,
Arrays.asList( Arrays.asList(
new Route( Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)), RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null),
new Route( Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()), RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null)))); RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
verify(mockListener).onResult(resolutionResultCaptor.capture()); verify(mockListener).onResult(resolutionResultCaptor.capture());
ResolutionResult result = resolutionResultCaptor.getValue(); ResolutionResult result = resolutionResultCaptor.getValue();
assertThat(result.getAddresses()).isEmpty(); assertThat(result.getAddresses()).isEmpty();
@ -636,12 +641,12 @@ public class XdsNameResolverTest {
public void findVirtualHostForHostName_exactMatchFirst() { public void findVirtualHostForHostName_exactMatchFirst() {
String hostname = "a.googleapis.com"; String hostname = "a.googleapis.com";
List<Route> routes = Collections.emptyList(); List<Route> routes = Collections.emptyList();
VirtualHost vHost1 = new VirtualHost("virtualhost01.googleapis.com", VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes); Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes, null);
VirtualHost vHost2 = new VirtualHost("virtualhost02.googleapis.com", VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
Collections.singletonList("*.googleapis.com"), routes); Collections.singletonList("*.googleapis.com"), routes, null);
VirtualHost vHost3 = VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com",
new VirtualHost("virtualhost03.googleapis.com", Collections.singletonList("*"), routes); Collections.singletonList("*"), routes, null);
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
.isEqualTo(vHost1); .isEqualTo(vHost1);
@ -651,14 +656,12 @@ public class XdsNameResolverTest {
public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() { public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() {
String hostname = "a.googleapis.com"; String hostname = "a.googleapis.com";
List<Route> routes = Collections.emptyList(); List<Route> routes = Collections.emptyList();
VirtualHost vHost1 = VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
new VirtualHost("virtualhost01.googleapis.com", Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes, null);
Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes); VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
VirtualHost vHost2 = Collections.singletonList("a.googleapis.*"), routes, null);
new VirtualHost("virtualhost02.googleapis.com", VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com",
Collections.singletonList("a.googleapis.*"), routes); Collections.singletonList("*"), routes, null);
VirtualHost vHost3 =
new VirtualHost("virtualhost03.googleapis.com", Collections.singletonList("*"), routes);
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3); List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
.isEqualTo(vHost1); .isEqualTo(vHost1);
@ -668,16 +671,127 @@ public class XdsNameResolverTest {
public void findVirtualHostForHostName_asteriskMatchAnyDomain() { public void findVirtualHostForHostName_asteriskMatchAnyDomain() {
String hostname = "a.googleapis.com"; String hostname = "a.googleapis.com";
List<Route> routes = Collections.emptyList(); List<Route> routes = Collections.emptyList();
VirtualHost vHost1 = VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
new VirtualHost("virtualhost01.googleapis.com", Collections.singletonList("*"), routes); Collections.singletonList("*"), routes, null);
VirtualHost vHost2 = VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
new VirtualHost("virtualhost02.googleapis.com", Collections.singletonList("b.googleapis.com"), routes, null);
Collections.singletonList("b.googleapis.com"), routes);
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2); List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2);
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname)) assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
.isEqualTo(vHost1);; .isEqualTo(vHost1);;
} }
@Test
public void routeMatching_pathOnly() {
Map<String, Iterable<String>> headers = Collections.emptyMap();
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
RouteMatch routeMatch1 =
RouteMatch.create(
PathMatcher.fromPath("/FooService/barMethod", true),
Collections.<HeaderMatcher>emptyList(), null);
assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/barMethod", headers, random))
.isTrue();
assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/bazMethod", headers, random))
.isFalse();
RouteMatch routeMatch2 =
RouteMatch.create(
PathMatcher.fromPrefix("/FooService/", true),
Collections.<HeaderMatcher>emptyList(), null);
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/barMethod", headers, random))
.isTrue();
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/bazMethod", headers, random))
.isTrue();
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/BarService/bazMethod", headers, random))
.isFalse();
RouteMatch routeMatch3 =
RouteMatch.create(
PathMatcher.fromRegEx(Pattern.compile(".*Foo.*")),
Collections.<HeaderMatcher>emptyList(), null);
assertThat(XdsNameResolver.matchRoute(routeMatch3, "/FooService/barMethod", headers, random))
.isTrue();
}
@Test
public void routeMatching_withHeaders() {
Map<String, Iterable<String>> headers = new HashMap<>();
headers.put("authority", Collections.singletonList("foo.googleapis.com"));
headers.put("grpc-encoding", Collections.singletonList("gzip"));
headers.put("user-agent", Collections.singletonList("gRPC-Java"));
headers.put("content-length", Collections.singletonList("1000"));
headers.put("custom-key", Arrays.asList("custom-value1", "custom-value2"));
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true);
RouteMatch routeMatch1 = RouteMatch.create(
pathMatcher,
Arrays.asList(
HeaderMatcher.forExactValue("grpc-encoding", "gzip", false),
HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), false),
HeaderMatcher.forRange(
"content-length", HeaderMatcher.Range.create(100, 10000), false),
HeaderMatcher.forPresent("user-agent", true, false),
HeaderMatcher.forPrefix("custom-key", "custom-", false),
HeaderMatcher.forSuffix("custom-key", "value2", false)),
null);
assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/barMethod", headers, random))
.isTrue();
RouteMatch routeMatch2 = RouteMatch.create(
pathMatcher,
Collections.singletonList(
HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), true)),
null);
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/barMethod", headers, random))
.isFalse();
RouteMatch routeMatch3 = RouteMatch.create(
pathMatcher,
Collections.singletonList(
HeaderMatcher.forExactValue("user-agent", "gRPC-Go", false)), null);
assertThat(XdsNameResolver.matchRoute(routeMatch3, "/FooService/barMethod", headers, random))
.isFalse();
RouteMatch routeMatch4 = RouteMatch.create(
pathMatcher,
Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, false)),
null);
assertThat(XdsNameResolver.matchRoute(routeMatch4, "/FooService/barMethod", headers, random))
.isFalse();
RouteMatch routeMatch5 = RouteMatch.create(
pathMatcher,
Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, true)), // inverted
null);
assertThat(XdsNameResolver.matchRoute(routeMatch5, "/FooService/barMethod", headers, random))
.isTrue();
RouteMatch routeMatch6 = RouteMatch.create(
pathMatcher,
Collections.singletonList(HeaderMatcher.forPresent("user-agent", true, true)),
null);
assertThat(XdsNameResolver.matchRoute(routeMatch6, "/FooService/barMethod", headers, random))
.isFalse();
RouteMatch routeMatch7 = RouteMatch.create(
pathMatcher,
Collections.singletonList(
HeaderMatcher.forExactValue("custom-key", "custom-value1,custom-value2", false)),
null);
assertThat(XdsNameResolver.matchRoute(routeMatch7, "/FooService/barMethod", headers, random))
.isTrue();
}
@Test
public void pathMatching_caseInsensitive() {
PathMatcher pathMatcher1 = PathMatcher.fromPath("/FooService/barMethod", false);
assertThat(XdsNameResolver.matchPath(pathMatcher1, "/fooservice/barmethod")).isTrue();
PathMatcher pathMatcher2 = PathMatcher.fromPrefix("/FooService", false);
assertThat(XdsNameResolver.matchPath(pathMatcher2, "/fooservice/barmethod")).isTrue();
}
private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory { private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory {
@Override @Override
@ -757,8 +871,8 @@ public class XdsNameResolverTest {
if (!resourceName.equals(ldsResource)) { if (!resourceName.equals(ldsResource)) {
return; return;
} }
VirtualHost virtualHost = VirtualHost virtualHost = VirtualHost.create("virtual-host",
new VirtualHost("virtual-host", Collections.singletonList(AUTHORITY), routes); Collections.singletonList(AUTHORITY), routes, null);
ldsWatcher.onChanged( ldsWatcher.onChanged(
new LdsUpdate(0, Collections.singletonList(virtualHost), false, null)); new LdsUpdate(0, Collections.singletonList(virtualHost), false, null));
} }