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'
protocVersion = protobufVersion
opencensusVersion = '0.28.0'
autovalueVersion = '1.7.4'
configureProtoCompilation = {
String generatedSourcePath = "${projectDir}/src/generated"
@ -142,6 +143,8 @@ subprojects {
libraries = [
android_annotations: "com.google.android:annotations:4.1.1.4",
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",
cronet_api: 'org.chromium.net:cronet-api:76.3809.111',
cronet_embedded: 'org.chromium.net:cronet-embedded:66.3359.158',

View File

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

View File

@ -27,7 +27,6 @@ import io.grpc.internal.GrpcUtil;
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
import io.grpc.internal.JsonParser;
import io.grpc.internal.JsonUtil;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.XdsLogger.XdsLogLevel;
import java.io.IOException;
@ -214,7 +213,7 @@ public class BootstrapperImpl implements Bootstrapper {
if (rawLocality.containsKey("sub_zone")) {
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);
}
}

View File

@ -17,15 +17,18 @@
package io.grpc.xds;
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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
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.Cluster;
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.RoutingPriority;
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.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.HttpFilter;
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.Status;
import io.grpc.SynchronizationContext.ScheduledHandle;
import io.grpc.internal.BackoffPolicy;
import io.grpc.xds.EnvoyProtoData.DropOverload;
import io.grpc.xds.EnvoyProtoData.HttpFault;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.EnvoyProtoData.StructOrError;
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.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.ClusterType;
import io.grpc.xds.XdsClient.CdsUpdate.EdsClusterConfig;
import io.grpc.xds.XdsClient.CdsUpdate.LogicalDnsClusterConfig;
import io.grpc.xds.XdsLogger.XdsLogLevel;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -82,6 +95,7 @@ final class ClientXdsClient extends AbstractXdsClient {
static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15;
@VisibleForTesting
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 =
"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2"
+ ".HttpConnectionManager";
@ -176,7 +190,7 @@ final class ClientXdsClient extends AbstractXdsClient {
hasFaultInjection = true;
if (httpFilter.hasTypedConfig()) {
StructOrError<HttpFault> httpFaultOrError =
HttpFault.decodeFaultFilterConfig(httpFilter.getTypedConfig());
decodeFaultFilterConfig(httpFilter.getTypedConfig());
if (httpFaultOrError.getErrorDetail() != null) {
nackResponse(ResourceType.LDS, nonce,
"Listener " + listenerName + " contains invalid HttpFault filter: "
@ -189,10 +203,10 @@ final class ClientXdsClient extends AbstractXdsClient {
}
}
if (hcm.hasRouteConfig()) {
List<EnvoyProtoData.VirtualHost> virtualHosts = new ArrayList<>();
for (VirtualHost virtualHostProto : hcm.getRouteConfig().getVirtualHostsList()) {
StructOrError<EnvoyProtoData.VirtualHost> virtualHost =
EnvoyProtoData.VirtualHost.fromEnvoyProtoVirtualHost(virtualHostProto);
List<VirtualHost> virtualHosts = new ArrayList<>();
for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto
: hcm.getRouteConfig().getVirtualHostsList()) {
StructOrError<VirtualHost> virtualHost = parseVirtualHost(virtualHostProto);
if (virtualHost.getErrorDetail() != null) {
nackResponse(ResourceType.LDS, nonce,
"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
protected void handleRdsResponse(String versionInfo, List<Any> resources, String nonce) {
// Unpack RouteConfiguration messages.
@ -262,11 +656,11 @@ final class ClientXdsClient extends AbstractXdsClient {
for (Map.Entry<String, RouteConfiguration> entry : routeConfigs.entrySet()) {
String routeConfigName = entry.getKey();
RouteConfiguration routeConfig = entry.getValue();
List<EnvoyProtoData.VirtualHost> virtualHosts =
List<VirtualHost> virtualHosts =
new ArrayList<>(routeConfig.getVirtualHostsCount());
for (VirtualHost virtualHostProto : routeConfig.getVirtualHostsList()) {
StructOrError<EnvoyProtoData.VirtualHost> virtualHost =
EnvoyProtoData.VirtualHost.fromEnvoyProtoVirtualHost(virtualHostProto);
for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto
: routeConfig.getVirtualHostsList()) {
StructOrError<VirtualHost> virtualHost = parseVirtualHost(virtualHostProto);
if (virtualHost.getErrorDetail() != null) {
nackResponse(ResourceType.RDS, nonce, "RouteConfiguration " + routeConfigName
+ " contains invalid virtual host: " + virtualHost.getErrorDetail());
@ -508,45 +902,35 @@ final class ClientXdsClient extends AbstractXdsClient {
Map<Locality, LocalityLbEndpoints> localityLbEndpointsMap = new LinkedHashMap<>();
List<DropOverload> dropOverloads = new ArrayList<>();
int maxPriority = -1;
for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpoints
for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto
: assignment.getEndpointsList()) {
// Filter out localities without or with 0 weight.
if (!localityLbEndpoints.hasLoadBalancingWeight()
|| localityLbEndpoints.getLoadBalancingWeight().getValue() < 1) {
StructOrError<LocalityLbEndpoints> localityLbEndpoints =
parseLocalityLbEndpoints(localityLbEndpointsProto);
if (localityLbEndpoints == null) {
continue;
}
int localityPriority = localityLbEndpoints.getPriority();
if (localityPriority < 0) {
nackResponse(ResourceType.EDS, nonce,
"ClusterLoadAssignment " + clusterName + " : locality with negative priority.");
if (localityLbEndpoints.getErrorDetail() != null) {
nackResponse(ResourceType.EDS, nonce, "ClusterLoadAssignment " + clusterName + ": "
+ localityLbEndpoints.getErrorDetail());
return;
}
maxPriority = Math.max(maxPriority, localityPriority);
priorities.add(localityPriority);
// The endpoint field of each lb_endpoints must be set.
// 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
maxPriority = Math.max(maxPriority, localityLbEndpoints.getStruct().priority());
priorities.add(localityLbEndpoints.getStruct().priority());
// Note endpoints with health status other than HEALTHY and UNKNOWN are still
// handed over to watching parties. It is watching parties' responsibility to
// filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy().
localityLbEndpointsMap.put(
Locality.fromEnvoyProtoLocality(localityLbEndpoints.getLocality()),
LocalityLbEndpoints.fromEnvoyProtoLocalityLbEndpoints(localityLbEndpoints));
parseLocality(localityLbEndpointsProto.getLocality()),
localityLbEndpoints.getStruct());
}
if (priorities.size() != maxPriority + 1) {
nackResponse(ResourceType.EDS, nonce,
"ClusterLoadAssignment " + clusterName + " : sparse priorities.");
return;
}
for (ClusterLoadAssignment.Policy.DropOverload dropOverload
for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto
: assignment.getPolicy().getDropOverloadsList()) {
dropOverloads.add(DropOverload.fromEnvoyProtoDropOverload(dropOverload));
dropOverloads.add(parseDropOverload(dropOverloadProto));
}
EdsUpdate update = new EdsUpdate(clusterName, localityLbEndpointsMap, dropOverloads);
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
protected void handleStreamClosed(Status error) {
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.ForwardingSubchannel;
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
import io.grpc.xds.EnvoyProtoData.DropOverload;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
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.
// In case of not (which really shouldn't), loads are aggregated under an empty locality.
if (locality == null) {
locality = new Locality("", "", "");
locality = Locality.create("", "", "");
}
final ClusterLocalityStats localityStats = xdsClient.addClusterLocalityStats(
cluster, edsServiceName, locality);
@ -292,14 +291,14 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
public PickResult pickSubchannel(PickSubchannelArgs args) {
for (DropOverload dropOverload : dropPolicies) {
int rand = random.nextInt(1_000_000);
if (rand < dropOverload.getDropsPerMillion()) {
if (rand < dropOverload.dropsPerMillion()) {
logger.log(XdsLogLevel.INFO, "Drop request with category: {0}",
dropOverload.getCategory());
dropOverload.category());
if (dropStats != null) {
dropStats.recordDroppedRequest(dropOverload.getCategory());
dropStats.recordDroppedRequest(dropOverload.category());
}
return PickResult.withDrop(
Status.UNAVAILABLE.withDescription("Dropped: " + dropOverload.getCategory()));
Status.UNAVAILABLE.withDescription("Dropped: " + dropOverload.category()));
}
}
final PickResult result = delegate.pickSubchannel(args);

View File

@ -26,7 +26,7 @@ import io.grpc.LoadBalancerProvider;
import io.grpc.LoadBalancerRegistry;
import io.grpc.NameResolver.ConfigOrError;
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 java.util.ArrayList;
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.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
import io.grpc.xds.EnvoyProtoData.DropOverload;
import io.grpc.xds.EnvoyProtoData.LbEndpoint;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
@ -78,7 +77,7 @@ import javax.annotation.Nullable;
*/
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 String authority;
private final SynchronizationContext syncContext;
@ -385,16 +384,16 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>();
for (Locality locality : localityLbEndpoints.keySet()) {
LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
int priority = localityLbInfo.getPriority();
int priority = localityLbInfo.priority();
String priorityName = priorityName(name, priority);
boolean discard = true;
for (LbEndpoint endpoint : localityLbInfo.getEndpoints()) {
for (LbEndpoint endpoint : localityLbInfo.endpoints()) {
if (endpoint.isHealthy()) {
discard = false;
Attributes attr = endpoint.getAddress().getAttributes().toBuilder()
Attributes attr = endpoint.eag().getAttributes().toBuilder()
.set(InternalXdsAttributes.ATTR_LOCALITY, locality).build();
EquivalentAddressGroup eag =
new EquivalentAddressGroup(endpoint.getAddress().getAddresses(), attr);
new EquivalentAddressGroup(endpoint.eag().getAddresses(), attr);
eag = AddressFilter.setPathFilter(
eag, Arrays.asList(priorityName, localityName(locality)));
addresses.add(eag);
@ -409,7 +408,7 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>());
}
prioritizedLocalityWeights.get(priorityName).put(
locality, localityLbInfo.getLocalityWeight());
locality, localityLbInfo.localityWeight());
}
if (prioritizedLocalityWeights.isEmpty()) {
// 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.NameResolver;
import io.grpc.internal.ObjectPool;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
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.internal.BackoffPolicy;
import io.grpc.stub.StreamObserver;
import io.grpc.xds.EnvoyProtoData.ClusterStats;
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 java.util.ArrayList;
import java.util.Collections;
@ -346,7 +348,7 @@ final class LoadReportClient {
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest.newBuilder()
.setNode(node.toEnvoyProtoNodeV2());
for (ClusterStats stats : clusterStatsList) {
requestBuilder.addClusterStats(stats.toEnvoyProtoClusterStatsV2());
requestBuilder.addClusterStats(buildClusterStats(stats));
}
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest request = requestBuilder.build();
lrsRequestWriterV2.onNext(requestBuilder.build());
@ -357,6 +359,37 @@ final class LoadReportClient {
void sendError(Exception 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 {
@ -410,7 +443,7 @@ final class LoadReportClient {
LoadStatsRequest.Builder requestBuilder =
LoadStatsRequest.newBuilder().setNode(node.toEnvoyProtoNode());
for (ClusterStats stats : clusterStatsList) {
requestBuilder.addClusterStats(stats.toEnvoyProtoClusterStats());
requestBuilder.addClusterStats(buildClusterStats(stats));
}
LoadStatsRequest request = requestBuilder.build();
lrsRequestWriterV3.onNext(request);
@ -421,5 +454,38 @@ final class LoadReportClient {
void sendError(Exception 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.collect.Sets;
import io.grpc.Status;
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.Stats.ClusterStats;
import io.grpc.xds.Stats.DroppedRequests;
import io.grpc.xds.Stats.UpstreamLocalityStats;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -153,9 +152,9 @@ final class LoadStatsManager2 {
if (clusterDropStats != null) {
Set<String> toDiscard = new HashSet<>();
for (String edsServiceName : clusterDropStats.keySet()) {
ClusterStats.Builder builder = ClusterStats.newBuilder().setClusterName(cluster);
ClusterStats.Builder builder = ClusterStats.newBuilder().clusterName(cluster);
if (edsServiceName != null) {
builder.setClusterServiceName(edsServiceName);
builder.clusterServiceName(edsServiceName);
}
ReferenceCounted<ClusterDropStats> ref = clusterDropStats.get(edsServiceName);
if (ref.getReferenceCount() == 0) { // stats object no longer needed after snapshot
@ -164,12 +163,12 @@ final class LoadStatsManager2 {
ClusterDropStatsSnapshot dropStatsSnapshot = ref.get().snapshot();
long totalCategorizedDrops = 0L;
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();
}
builder.setTotalDroppedRequests(
builder.totalDroppedRequests(
totalCategorizedDrops + dropStatsSnapshot.uncategorizedDrops);
builder.setLoadReportIntervalNanos(dropStatsSnapshot.durationNano);
builder.loadReportIntervalNano(dropStatsSnapshot.durationNano);
statsReportBuilders.put(edsServiceName, builder);
}
clusterDropStats.keySet().removeAll(toDiscard);
@ -180,9 +179,9 @@ final class LoadStatsManager2 {
for (String edsServiceName : clusterLoadStats.keySet()) {
ClusterStats.Builder builder = statsReportBuilders.get(edsServiceName);
if (builder == null) {
builder = ClusterStats.newBuilder().setClusterName(cluster);
builder = ClusterStats.newBuilder().clusterName(cluster);
if (edsServiceName != null) {
builder.setClusterServiceName(edsServiceName);
builder.clusterServiceName(edsServiceName);
}
statsReportBuilders.put(edsServiceName, builder);
}
@ -196,17 +195,14 @@ final class LoadStatsManager2 {
if (ref.getReferenceCount() == 0 && snapshot.callsInProgress == 0) {
localitiesToDiscard.add(locality);
}
UpstreamLocalityStats.Builder localityStatsBuilder = UpstreamLocalityStats.newBuilder();
localityStatsBuilder.setLocality(locality);
localityStatsBuilder.setTotalIssuedRequests(snapshot.callsIssued);
localityStatsBuilder.setTotalSuccessfulRequests(snapshot.callsSucceeded);
localityStatsBuilder.setTotalErrorRequests(snapshot.callsFailed);
localityStatsBuilder.setTotalRequestsInProgress(snapshot.callsInProgress);
builder.addUpstreamLocalityStats(localityStatsBuilder.build());
UpstreamLocalityStats upstreamLocalityStats = UpstreamLocalityStats.create(
locality, snapshot.callsIssued, snapshot.callsSucceeded, snapshot.callsFailed,
snapshot.callsInProgress);
builder.addUpstreamLocalityStats(upstreamLocalityStats);
// Use the max (drops/loads) recording interval as the overall interval for the
// cluster's stats. In general, they should be mostly identical.
builder.setLoadReportIntervalNanos(
Math.max(builder.getLoadReportIntervalNanos(), snapshot.durationNano));
builder.loadReportIntervalNano(
Math.max(builder.loadReportIntervalNano(), snapshot.durationNano));
}
localityStats.keySet().removeAll(localitiesToDiscard);
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.ToStringHelper;
import io.grpc.Status;
import io.grpc.xds.EnvoyProtoData.DropOverload;
import io.grpc.xds.EnvoyProtoData.HttpFault;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.VirtualHost;
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyServerProtoData.Listener;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import io.grpc.Attributes;
@ -39,11 +40,14 @@ import io.grpc.Status;
import io.grpc.SynchronizationContext;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.ObjectPool;
import io.grpc.xds.EnvoyProtoData.ClusterWeight;
import io.grpc.xds.EnvoyProtoData.Route;
import io.grpc.xds.EnvoyProtoData.RouteAction;
import io.grpc.xds.EnvoyProtoData.VirtualHost;
import io.grpc.xds.Matchers.FractionMatcher;
import io.grpc.xds.Matchers.HeaderMatcher;
import io.grpc.xds.Matchers.PathMatcher;
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.LdsUpdate;
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
VirtualHost targetVirtualHost = null; // target VirtualHost with longest matched domain
for (VirtualHost vHost : virtualHosts) {
for (String domain : vHost.getDomains()) {
for (String domain : vHost.domains()) {
boolean selected = false;
if (matchHostName(hostName, domain)) { // matching
if (!domain.contains("*")) { // exact matching
@ -320,8 +324,8 @@ final class XdsNameResolver extends NameResolver {
Route selectedRoute = null;
do {
for (Route route : routingConfig.routes) {
if (route.getRouteMatch().matches(
"/" + args.getMethodDescriptor().getFullMethodName(), asciiHeaders)) {
if (matchRoute(route.routeMatch(), "/" + args.getMethodDescriptor().getFullMethodName(),
asciiHeaders, random)) {
selectedRoute = route;
break;
}
@ -330,20 +334,20 @@ final class XdsNameResolver extends NameResolver {
return Result.forError(
Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"));
}
RouteAction action = selectedRoute.getRouteAction();
if (action.getCluster() != null) {
cluster = action.getCluster();
} else if (action.getWeightedCluster() != null) {
RouteAction action = selectedRoute.routeAction();
if (action.cluster() != null) {
cluster = action.cluster();
} else if (action.weightedClusters() != null) {
int totalWeight = 0;
for (ClusterWeight weightedCluster : action.getWeightedCluster()) {
totalWeight += weightedCluster.getWeight();
for (ClusterWeight weightedCluster : action.weightedClusters()) {
totalWeight += weightedCluster.weight();
}
int select = random.nextInt(totalWeight);
int accumulator = 0;
for (ClusterWeight weightedCluster : action.getWeightedCluster()) {
accumulator += weightedCluster.getWeight();
for (ClusterWeight weightedCluster : action.weightedClusters()) {
accumulator += weightedCluster.weight();
if (select < accumulator) {
cluster = weightedCluster.getName();
cluster = weightedCluster.name();
break;
}
}
@ -352,7 +356,7 @@ final class XdsNameResolver extends NameResolver {
// TODO(chengyuanzhang): avoid service config generation and parsing for each call.
Map<String, ?> rawServiceConfig = Collections.emptyMap();
if (enableTimeout) {
Long timeoutNano = selectedRoute.getRouteAction().getTimeoutNano();
Long timeoutNano = selectedRoute.routeAction().timeoutNano();
if (timeoutNano == null) {
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 final ConfigOrError emptyServiceConfig =
serviceConfigParser.parseServiceConfig(Collections.<String, Object>emptyMap());
@ -537,15 +611,15 @@ final class XdsNameResolver extends NameResolver {
listener.onResult(emptyResult);
return;
}
List<Route> routes = virtualHost.getRoutes();
List<Route> routes = virtualHost.routes();
Set<String> clusters = new HashSet<>();
for (Route route : routes) {
RouteAction action = route.getRouteAction();
if (action.getCluster() != null) {
clusters.add(action.getCluster());
} else if (action.getWeightedCluster() != null) {
for (ClusterWeight weighedCluster : action.getWeightedCluster()) {
clusters.add(weighedCluster.getName());
RouteAction action = route.routeAction();
if (action.cluster() != null) {
clusters.add(action.cluster());
} else if (action.weightedClusters() != null) {
for (ClusterWeight weighedCluster : action.weightedClusters()) {
clusters.add(weighedCluster.name());
}
}
}

View File

@ -29,7 +29,6 @@ import io.grpc.internal.GrpcUtil;
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
import io.grpc.xds.Bootstrapper.BootstrapInfo;
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.Node;
import java.io.IOException;
import java.util.List;
@ -118,7 +117,7 @@ public class BootstrapperImplTest {
getNodeBuilder()
.setId("ENVOY_NODE_ID")
.setCluster("ENVOY_CLUSTER")
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setMetadata(
ImmutableMap.of(
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
@ -176,7 +175,7 @@ public class BootstrapperImplTest {
getNodeBuilder()
.setId("ENVOY_NODE_ID")
.setCluster("ENVOY_CLUSTER")
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setMetadata(
ImmutableMap.of(
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
@ -224,7 +223,7 @@ public class BootstrapperImplTest {
getNodeBuilder()
.setId("ENVOY_NODE_ID")
.setCluster("ENVOY_CLUSTER")
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
.setMetadata(
ImmutableMap.of(
"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.testing.GrpcCleanupRule;
import io.grpc.xds.AbstractXdsClient.ResourceType;
import io.grpc.xds.EnvoyProtoData.DropOverload;
import io.grpc.xds.EnvoyProtoData.HttpFault;
import io.grpc.xds.EnvoyProtoData.LbEndpoint;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
import io.grpc.xds.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LbEndpoint;
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
@ -374,18 +372,18 @@ public abstract class ClientXdsClientTestBase {
assertThat(ldsUpdate.virtualHosts).hasSize(2);
assertThat(ldsUpdate.hasFaultInjection).isTrue();
assertThat(ldsUpdate.httpFault).isNull();
HttpFault httpFault = ldsUpdate.virtualHosts.get(0).getHttpFault();
assertThat(httpFault.faultDelay.delayNanos).isEqualTo(300);
assertThat(httpFault.faultDelay.ratePerMillion).isEqualTo(1000);
assertThat(httpFault.faultAbort).isNull();
assertThat(httpFault.upstreamCluster).isEqualTo("cluster1");
assertThat(httpFault.maxActiveFaults).isEqualTo(100);
httpFault = ldsUpdate.virtualHosts.get(1).getHttpFault();
assertThat(httpFault.faultDelay).isNull();
assertThat(httpFault.faultAbort.status.getCode()).isEqualTo(Status.Code.UNAVAILABLE);
assertThat(httpFault.faultAbort.ratePerMillion).isEqualTo(2000);
assertThat(httpFault.upstreamCluster).isEqualTo("cluster2");
assertThat(httpFault.maxActiveFaults).isEqualTo(101);
HttpFault httpFault = ldsUpdate.virtualHosts.get(0).httpFault();
assertThat(httpFault.faultDelay().delayNanos()).isEqualTo(300);
assertThat(httpFault.faultDelay().ratePerMillion()).isEqualTo(1000);
assertThat(httpFault.faultAbort()).isNull();
assertThat(httpFault.upstreamCluster()).isEqualTo("cluster1");
assertThat(httpFault.maxActiveFaults()).isEqualTo(100);
httpFault = ldsUpdate.virtualHosts.get(1).httpFault();
assertThat(httpFault.faultDelay()).isNull();
assertThat(httpFault.faultAbort().status().getCode()).isEqualTo(Status.Code.UNAVAILABLE);
assertThat(httpFault.faultAbort().ratePerMillion()).isEqualTo(2000);
assertThat(httpFault.upstreamCluster()).isEqualTo("cluster2");
assertThat(httpFault.maxActiveFaults()).isEqualTo(101);
}
@Test
@ -942,17 +940,17 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies)
.containsExactly(
new DropOverload("lb", 200),
new DropOverload("throttle", 1000));
DropOverload.create("lb", 200),
DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly(
new Locality("region1", "zone1", "subzone1"),
new LocalityLbEndpoints(
Locality.create("region1", "zone1", "subzone1"),
LocalityLbEndpoints.create(
ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080,
LbEndpoint.create("192.168.0.1", 8080,
2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
Locality.create("region3", "zone3", "subzone3"),
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
}
@Test
@ -991,17 +989,17 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies)
.containsExactly(
new DropOverload("lb", 200),
new DropOverload("throttle", 1000));
DropOverload.create("lb", 200),
DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly(
new Locality("region1", "zone1", "subzone1"),
new LocalityLbEndpoints(
Locality.create("region1", "zone1", "subzone1"),
LocalityLbEndpoints.create(
ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080,
LbEndpoint.create("192.168.0.1", 8080,
2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
Locality.create("region3", "zone3", "subzone3"),
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
call.verifyNoMoreRequest();
}
@ -1050,16 +1048,16 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies)
.containsExactly(
new DropOverload("lb", 200),
new DropOverload("throttle", 1000));
DropOverload.create("lb", 200),
DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly(
new Locality("region1", "zone1", "subzone1"),
new LocalityLbEndpoints(
Locality.create("region1", "zone1", "subzone1"),
LocalityLbEndpoints.create(
ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
LbEndpoint.create("192.168.0.1", 8080, 2, true)), 1, 0),
Locality.create("region3", "zone3", "subzone3"),
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
clusterLoadAssignments =
ImmutableList.of(
@ -1079,10 +1077,10 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.dropPolicies).isEmpty();
assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly(
new Locality("region2", "zone2", "subzone2"),
new LocalityLbEndpoints(
Locality.create("region2", "zone2", "subzone2"),
LocalityLbEndpoints.create(
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
@ -1187,16 +1185,16 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
assertThat(edsUpdate.dropPolicies)
.containsExactly(
new DropOverload("lb", 200),
new DropOverload("throttle", 1000));
DropOverload.create("lb", 200),
DropOverload.create("throttle", 1000));
assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly(
new Locality("region1", "zone1", "subzone1"),
new LocalityLbEndpoints(
Locality.create("region1", "zone1", "subzone1"),
LocalityLbEndpoints.create(
ImmutableList.of(
new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0),
new Locality("region3", "zone3", "subzone3"),
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
LbEndpoint.create("192.168.0.1", 8080, 2, true)), 1, 0),
Locality.create("region3", "zone3", "subzone3"),
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
verifyNoMoreInteractions(watcher1, watcher2);
clusterLoadAssignments =
@ -1217,20 +1215,20 @@ public abstract class ClientXdsClientTestBase {
assertThat(edsUpdate.dropPolicies).isEmpty();
assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly(
new Locality("region2", "zone2", "subzone2"),
new LocalityLbEndpoints(
Locality.create("region2", "zone2", "subzone2"),
LocalityLbEndpoints.create(
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());
edsUpdate = edsUpdateCaptor.getValue();
assertThat(edsUpdate.clusterName).isEqualTo(edsResource);
assertThat(edsUpdate.dropPolicies).isEmpty();
assertThat(edsUpdate.localityLbEndpointsMap)
.containsExactly(
new Locality("region2", "zone2", "subzone2"),
new LocalityLbEndpoints(
Locality.create("region2", "zone2", "subzone2"),
LocalityLbEndpoints.create(
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);
}

View File

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

View File

@ -19,38 +19,10 @@ package io.grpc.xds;
import static com.google.common.truth.Truth.assertThat;
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.UInt32Value;
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.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.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.runner.RunWith;
import org.junit.runners.JUnit4;
@ -61,41 +33,6 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
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")
@Test
public void convertNode() {
@ -108,7 +45,7 @@ public class EnvoyProtoDataTest {
"ENVOY_PORT",
"TRAFFICDIRECTOR_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.bar.com", 8088))
.setBuildVersion("v1")
@ -185,448 +122,4 @@ public class EnvoyProtoDataTest {
.build();
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.stub.StreamObserver;
import io.grpc.testing.GrpcCleanupRule;
import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
import java.util.ArrayDeque;
@ -85,8 +84,8 @@ public class LoadReportClientTest {
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_NAME2 = "backend-service-bar.googleapis.com";
private static final Locality LOCALITY1 = new Locality("region1", "zone1", "subZone1");
private static final Locality LOCALITY2 = new Locality("region2", "zone2", "subZone2");
private static final Locality LOCALITY1 = Locality.create("region1", "zone1", "subZone1");
private static final Locality LOCALITY2 = Locality.create("region2", "zone2", "subZone2");
private static final FakeClock.TaskFilter LOAD_REPORTING_TASK_FILTER =
new FakeClock.TaskFilter() {
@Override

View File

@ -21,12 +21,11 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Iterables;
import io.grpc.Status;
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.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.Objects;
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_NAME2 = "backend-service-bar.googleapis.com";
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 =
new Locality("test_region2", "test_zone2", "test_subzone2");
Locality.create("test_region2", "test_zone2", "test_subzone2");
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 LoadStatsManager2 loadStatsManager =
@ -85,68 +84,68 @@ public class LoadStatsManager2Test {
assertThat(allStats).hasSize(3); // three cluster:edsServiceName
ClusterStats stats1 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME1);
assertThat(stats1.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats1.getDroppedRequestsList()).hasSize(2);
assertThat(findDroppedRequestCount(stats1.getDroppedRequestsList(), "lb")).isEqualTo(1L);
assertThat(findDroppedRequestCount(stats1.getDroppedRequestsList(), "throttle")).isEqualTo(1L);
assertThat(stats1.getTotalDroppedRequests()).isEqualTo(1L + 1L);
assertThat(stats1.getUpstreamLocalityStatsList()).hasSize(2); // two localities
assertThat(stats1.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats1.droppedRequestsList()).hasSize(2);
assertThat(findDroppedRequestCount(stats1.droppedRequestsList(), "lb")).isEqualTo(1L);
assertThat(findDroppedRequestCount(stats1.droppedRequestsList(), "throttle")).isEqualTo(1L);
assertThat(stats1.totalDroppedRequests()).isEqualTo(1L + 1L);
assertThat(stats1.upstreamLocalityStatsList()).hasSize(2); // two localities
UpstreamLocalityStats loadStats1 =
findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY1);
assertThat(loadStats1.getTotalIssuedRequests()).isEqualTo(19L);
assertThat(loadStats1.getTotalSuccessfulRequests()).isEqualTo(1L);
assertThat(loadStats1.getTotalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalRequestsInProgress()).isEqualTo(19L - 1L);
findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY1);
assertThat(loadStats1.totalIssuedRequests()).isEqualTo(19L);
assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(19L - 1L);
UpstreamLocalityStats loadStats2 =
findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY2);
assertThat(loadStats2.getTotalIssuedRequests()).isEqualTo(9L);
assertThat(loadStats2.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalErrorRequests()).isEqualTo(1L);
assertThat(loadStats2.getTotalRequestsInProgress()).isEqualTo(9L - 1L);
findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
assertThat(loadStats2.totalIssuedRequests()).isEqualTo(9L);
assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.totalErrorRequests()).isEqualTo(1L);
assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(9L - 1L);
ClusterStats stats2 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME2);
assertThat(stats2.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats2.getDroppedRequestsList()).isEmpty(); // no categorized drops
assertThat(stats2.getTotalDroppedRequests()).isEqualTo(1L);
assertThat(stats2.getUpstreamLocalityStatsList()).isEmpty(); // no per-locality stats
assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats2.droppedRequestsList()).isEmpty(); // no categorized drops
assertThat(stats2.totalDroppedRequests()).isEqualTo(1L);
assertThat(stats2.upstreamLocalityStatsList()).isEmpty(); // no per-locality stats
ClusterStats stats3 = findClusterStats(allStats, CLUSTER_NAME2, null);
assertThat(stats3.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats3.getDroppedRequestsList()).isEmpty();
assertThat(stats3.getTotalDroppedRequests()).isEqualTo(0L); // no drops recorded
assertThat(stats3.getUpstreamLocalityStatsList()).hasSize(1); // one localities
assertThat(stats3.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
assertThat(stats3.droppedRequestsList()).isEmpty();
assertThat(stats3.totalDroppedRequests()).isEqualTo(0L); // no drops recorded
assertThat(stats3.upstreamLocalityStatsList()).hasSize(1); // one localities
UpstreamLocalityStats loadStats3 =
Iterables.getOnlyElement(stats3.getUpstreamLocalityStatsList());
assertThat(loadStats3.getTotalIssuedRequests()).isEqualTo(1L);
assertThat(loadStats3.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats3.getTotalErrorRequests()).isEqualTo(0L);
assertThat(loadStats3.getTotalRequestsInProgress()).isEqualTo(1L);
Iterables.getOnlyElement(stats3.upstreamLocalityStatsList());
assertThat(loadStats3.totalIssuedRequests()).isEqualTo(1L);
assertThat(loadStats3.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats3.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats3.totalRequestsInProgress()).isEqualTo(1L);
fakeClock.forwardTime(3L, TimeUnit.SECONDS);
List<ClusterStats> clusterStatsList = loadStatsManager.getClusterStatsReports(CLUSTER_NAME1);
assertThat(clusterStatsList).hasSize(2);
stats1 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME1);
assertThat(stats1.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
assertThat(stats1.getDroppedRequestsList()).isEmpty();
assertThat(stats1.getTotalDroppedRequests()).isEqualTo(0L); // no new drops recorded
assertThat(stats1.getUpstreamLocalityStatsList()).hasSize(2); // two localities
loadStats1 = findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY1);
assertThat(loadStats1.getTotalIssuedRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.getTotalRequestsInProgress()).isEqualTo(18L); // still in-progress
loadStats2 = findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY2);
assertThat(loadStats2.getTotalIssuedRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalErrorRequests()).isEqualTo(0L);
assertThat(loadStats2.getTotalRequestsInProgress()).isEqualTo(8L); // still in-progress
assertThat(stats1.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
assertThat(stats1.droppedRequestsList()).isEmpty();
assertThat(stats1.totalDroppedRequests()).isEqualTo(0L); // no new drops recorded
assertThat(stats1.upstreamLocalityStatsList()).hasSize(2); // two localities
loadStats1 = findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY1);
assertThat(loadStats1.totalIssuedRequests()).isEqualTo(0L);
assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(18L); // still in-progress
loadStats2 = findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
assertThat(loadStats2.totalIssuedRequests()).isEqualTo(0L);
assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(loadStats2.totalErrorRequests()).isEqualTo(0L);
assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(8L); // still in-progress
stats2 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME2);
assertThat(stats2.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
assertThat(stats2.getDroppedRequestsList()).isEmpty();
assertThat(stats2.getTotalDroppedRequests()).isEqualTo(0L); // no new drops recorded
assertThat(stats2.getUpstreamLocalityStatsList()).isEmpty(); // no per-locality stats
assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
assertThat(stats2.droppedRequestsList()).isEmpty();
assertThat(stats2.totalDroppedRequests()).isEqualTo(0L); // no new drops recorded
assertThat(stats2.upstreamLocalityStatsList()).isEmpty(); // no per-locality stats
}
@Test
@ -162,10 +161,10 @@ public class LoadStatsManager2Test {
ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
assertThat(stats.getDroppedRequestsList()).hasSize(2);
assertThat(findDroppedRequestCount(stats.getDroppedRequestsList(), "lb")).isEqualTo(1L);
assertThat(findDroppedRequestCount(stats.getDroppedRequestsList(), "throttle")).isEqualTo(1L);
assertThat(stats.getTotalDroppedRequests()).isEqualTo(4L); // 2 cagetorized + 2 uncategoized
assertThat(stats.droppedRequestsList()).hasSize(2);
assertThat(findDroppedRequestCount(stats.droppedRequestsList(), "lb")).isEqualTo(1L);
assertThat(findDroppedRequestCount(stats.droppedRequestsList(), "throttle")).isEqualTo(1L);
assertThat(stats.totalDroppedRequests()).isEqualTo(4L); // 2 cagetorized + 2 uncategoized
}
@Test
@ -175,15 +174,15 @@ public class LoadStatsManager2Test {
counter.recordDroppedRequest("lb");
ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
assertThat(stats.getDroppedRequestsList()).hasSize(1);
assertThat(Iterables.getOnlyElement(stats.getDroppedRequestsList()).getDroppedCount())
assertThat(stats.droppedRequestsList()).hasSize(1);
assertThat(Iterables.getOnlyElement(stats.droppedRequestsList()).droppedCount())
.isEqualTo(1L);
assertThat(stats.getTotalDroppedRequests()).isEqualTo(1L);
assertThat(stats.totalDroppedRequests()).isEqualTo(1L);
counter.release();
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
assertThat(stats.getDroppedRequestsList()).isEmpty();
assertThat(stats.getTotalDroppedRequests()).isEqualTo(0L);
assertThat(stats.droppedRequestsList()).isEmpty();
assertThat(stats.totalDroppedRequests()).isEqualTo(0L);
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
}
@ -203,11 +202,11 @@ public class LoadStatsManager2Test {
ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
UpstreamLocalityStats localityStats =
Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(1L + 2L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(1L + 2L - 1L - 1L);
Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.totalIssuedRequests()).isEqualTo(1L + 2L);
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(1L + 2L - 1L - 1L);
}
@Test
@ -220,30 +219,30 @@ public class LoadStatsManager2Test {
ClusterStats stats = Iterables.getOnlyElement(
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
UpstreamLocalityStats localityStats =
Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(2L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(2L);
Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.totalIssuedRequests()).isEqualTo(2L);
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(2L);
// release the counter, but requests still in-flight
counter.release();
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
localityStats = Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalRequestsInProgress())
localityStats = Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(0L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
assertThat(localityStats.totalRequestsInProgress())
.isEqualTo(2L); // retained by in-flight calls
counter.recordCallFinished(Status.OK);
counter.recordCallFinished(Status.UNAVAILABLE);
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
localityStats = Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(0L);
localityStats = Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(0L);
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
}
@ -252,8 +251,8 @@ public class LoadStatsManager2Test {
private static ClusterStats findClusterStats(
List<ClusterStats> statsList, String cluster, @Nullable String edsServiceName) {
for (ClusterStats stats : statsList) {
if (stats.getClusterName().equals(cluster)
&& Objects.equals(stats.getClusterServiceName(), edsServiceName)) {
if (stats.clusterName().equals(cluster)
&& Objects.equals(stats.clusterServiceName(), edsServiceName)) {
return stats;
}
}
@ -264,7 +263,7 @@ public class LoadStatsManager2Test {
private static UpstreamLocalityStats findLocalityStats(
List<UpstreamLocalityStats> localityStatsList, Locality locality) {
for (UpstreamLocalityStats stats : localityStatsList) {
if (stats.getLocality().equals(locality)) {
if (stats.locality().equals(locality)) {
return stats;
}
}
@ -275,11 +274,11 @@ public class LoadStatsManager2Test {
List<DroppedRequests> droppedRequestsLists, String category) {
DroppedRequests drop = null;
for (DroppedRequests stats : droppedRequestsLists) {
if (stats.getCategory().equals(category)) {
if (stats.category().equals(category)) {
drop = stats;
}
}
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 org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
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.collect.Iterables;
import com.google.re2j.Pattern;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
@ -49,15 +51,18 @@ import io.grpc.internal.NoopClientCall.NoopClientCallListener;
import io.grpc.internal.ObjectPool;
import io.grpc.internal.PickSubchannelArgsImpl;
import io.grpc.testing.TestMethodDescriptors;
import io.grpc.xds.EnvoyProtoData.ClusterWeight;
import io.grpc.xds.EnvoyProtoData.Route;
import io.grpc.xds.EnvoyProtoData.RouteAction;
import io.grpc.xds.EnvoyProtoData.VirtualHost;
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.RdsResourceWatcher;
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -232,25 +237,25 @@ public class XdsNameResolverTest {
}
private List<VirtualHost> buildUnmatchedVirtualHosts() {
Route route1 = new Route(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null));
Route route2 = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null));
Route route1 = Route.create(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null);
Route route2 = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null);
return Arrays.asList(
new VirtualHost("virtualhost-foo", Collections.singletonList("hello.googleapis.com"),
Collections.singletonList(route1)),
new VirtualHost("virtualhost-bar", Collections.singletonList("hi.googleapis.com"),
Collections.singletonList(route2)));
VirtualHost.create("virtualhost-foo", Collections.singletonList("hello.googleapis.com"),
Collections.singletonList(route1), null),
VirtualHost.create("virtualhost-bar", Collections.singletonList("hi.googleapis.com"),
Collections.singletonList(route2), null));
}
@Test
public void resolved_noTimeout() {
resolver.start(mockListener);
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
Route route = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(null, cluster1, null)); // per-route timeout unset
VirtualHost virtualHost = new VirtualHost("does not matter",
Collections.singletonList(AUTHORITY), Collections.singletonList(route));
Route route = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
RouteAction.forCluster(cluster1, null), null); // per-route timeout unset
VirtualHost virtualHost = VirtualHost.create("does not matter",
Collections.singletonList(AUTHORITY), Collections.singletonList(route), null);
xdsClient.deliverLdsUpdate(AUTHORITY, 0L, Collections.singletonList(virtualHost));
verify(mockListener).onResult(resolutionResultCaptor.capture());
ResolutionResult result = resolutionResultCaptor.getValue();
@ -262,10 +267,10 @@ public class XdsNameResolverTest {
public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() {
resolver.start(mockListener);
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
Route route = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(null, cluster1, null)); // per-route timeout unset
VirtualHost virtualHost = new VirtualHost("does not matter",
Collections.singletonList(AUTHORITY), Collections.singletonList(route));
Route route = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
RouteAction.forCluster(cluster1, null), null); // per-route timeout unset
VirtualHost virtualHost = VirtualHost.create("does not matter",
Collections.singletonList(AUTHORITY), Collections.singletonList(route), null);
xdsClient.deliverLdsUpdate(AUTHORITY, TimeUnit.SECONDS.toNanos(5L),
Collections.singletonList(virtualHost));
verify(mockListener).onResult(resolutionResultCaptor.capture());
@ -307,12 +312,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate(
AUTHORITY,
Arrays.asList(
new Route(
Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)),
new Route(
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
Route.create(
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());
ResolutionResult result = resolutionResultCaptor.getValue();
// Updated service config still contains cluster1 while it is removed resource. New calls no
@ -342,12 +347,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate(
AUTHORITY,
Arrays.asList(
new Route(
Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)),
new Route(
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
Route.create(
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,
// one for adding "another=cluster".
verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture());
@ -373,12 +378,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate(
AUTHORITY,
Arrays.asList(
new Route(
Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)),
new Route(
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
Route.create(
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());
ResolutionResult result = resolutionResultCaptor.getValue();
@ -389,12 +394,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate(
AUTHORITY,
Arrays.asList(
new Route(
Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), "another-cluster", null)),
new Route(
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(15L)), null),
Route.create(
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
assertCallSelectResult(call1, configSelector, "another-cluster", 15.0);
}
@ -407,18 +412,18 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate(
AUTHORITY,
Collections.singletonList(
new Route(
Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
xdsClient.deliverLdsUpdate(
AUTHORITY,
Arrays.asList(
new Route(
Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)),
new Route(
RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null),
Route.create(
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
testCall.deliverErrorStatus();
verifyNoMoreInteractions(mockListener);
}
@ -431,14 +436,14 @@ public class XdsNameResolverTest {
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
xdsClient.deliverLdsUpdate(
AUTHORITY,
Arrays.asList(
new Route(
Collections.singletonList(
Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(
TimeUnit.SECONDS.toNanos(20L), null,
RouteAction.forWeightedClusters(
Arrays.asList(
new ClusterWeight(cluster1, 20, null),
new ClusterWeight(cluster2, 80, null))))));
ClusterWeight.create(cluster1, 20, null),
ClusterWeight.create(cluster2, 80, null)),
TimeUnit.SECONDS.toNanos(20L)), null)));
verify(mockListener).onResult(resolutionResultCaptor.capture());
ResolutionResult result = resolutionResultCaptor.getValue();
assertThat(result.getAddresses()).isEmpty();
@ -493,12 +498,12 @@ public class XdsNameResolverTest {
xdsClient.deliverLdsUpdate(
AUTHORITY,
Arrays.asList(
new Route(
Route.create(
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)),
new Route(
RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null),
Route.create(
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());
ResolutionResult result = resolutionResultCaptor.getValue();
assertThat(result.getAddresses()).isEmpty();
@ -636,12 +641,12 @@ public class XdsNameResolverTest {
public void findVirtualHostForHostName_exactMatchFirst() {
String hostname = "a.googleapis.com";
List<Route> routes = Collections.emptyList();
VirtualHost vHost1 = new VirtualHost("virtualhost01.googleapis.com",
Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes);
VirtualHost vHost2 = new VirtualHost("virtualhost02.googleapis.com",
Collections.singletonList("*.googleapis.com"), routes);
VirtualHost vHost3 =
new VirtualHost("virtualhost03.googleapis.com", Collections.singletonList("*"), routes);
VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes, null);
VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
Collections.singletonList("*.googleapis.com"), routes, null);
VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com",
Collections.singletonList("*"), routes, null);
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
.isEqualTo(vHost1);
@ -651,14 +656,12 @@ public class XdsNameResolverTest {
public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() {
String hostname = "a.googleapis.com";
List<Route> routes = Collections.emptyList();
VirtualHost vHost1 =
new VirtualHost("virtualhost01.googleapis.com",
Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes);
VirtualHost vHost2 =
new VirtualHost("virtualhost02.googleapis.com",
Collections.singletonList("a.googleapis.*"), routes);
VirtualHost vHost3 =
new VirtualHost("virtualhost03.googleapis.com", Collections.singletonList("*"), routes);
VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes, null);
VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
Collections.singletonList("a.googleapis.*"), routes, null);
VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com",
Collections.singletonList("*"), routes, null);
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
.isEqualTo(vHost1);
@ -668,16 +671,127 @@ public class XdsNameResolverTest {
public void findVirtualHostForHostName_asteriskMatchAnyDomain() {
String hostname = "a.googleapis.com";
List<Route> routes = Collections.emptyList();
VirtualHost vHost1 =
new VirtualHost("virtualhost01.googleapis.com", Collections.singletonList("*"), routes);
VirtualHost vHost2 =
new VirtualHost("virtualhost02.googleapis.com",
Collections.singletonList("b.googleapis.com"), routes);
VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
Collections.singletonList("*"), routes, null);
VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
Collections.singletonList("b.googleapis.com"), routes, null);
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2);
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
.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 {
@Override
@ -757,8 +871,8 @@ public class XdsNameResolverTest {
if (!resourceName.equals(ldsResource)) {
return;
}
VirtualHost virtualHost =
new VirtualHost("virtual-host", Collections.singletonList(AUTHORITY), routes);
VirtualHost virtualHost = VirtualHost.create("virtual-host",
Collections.singletonList(AUTHORITY), routes, null);
ldsWatcher.onChanged(
new LdsUpdate(0, Collections.singletonList(virtualHost), false, null));
}