mirror of https://github.com/grpc/grpc-java.git
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:
parent
f0cf435b85
commit
01ed082281
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue