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'
|
protobufVersion = '3.12.0'
|
||||||
protocVersion = protobufVersion
|
protocVersion = protobufVersion
|
||||||
opencensusVersion = '0.28.0'
|
opencensusVersion = '0.28.0'
|
||||||
|
autovalueVersion = '1.7.4'
|
||||||
|
|
||||||
configureProtoCompilation = {
|
configureProtoCompilation = {
|
||||||
String generatedSourcePath = "${projectDir}/src/generated"
|
String generatedSourcePath = "${projectDir}/src/generated"
|
||||||
|
|
@ -142,6 +143,8 @@ subprojects {
|
||||||
libraries = [
|
libraries = [
|
||||||
android_annotations: "com.google.android:annotations:4.1.1.4",
|
android_annotations: "com.google.android:annotations:4.1.1.4",
|
||||||
animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.19",
|
animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.19",
|
||||||
|
autovalue: "com.google.auto.value:auto-value:${autovalueVersion}",
|
||||||
|
autovalue_annotation: "com.google.auto.value:auto-value-annotations:${autovalueVersion}",
|
||||||
errorprone: "com.google.errorprone:error_prone_annotations:2.4.0",
|
errorprone: "com.google.errorprone:error_prone_annotations:2.4.0",
|
||||||
cronet_api: 'org.chromium.net:cronet-api:76.3809.111',
|
cronet_api: 'org.chromium.net:cronet-api:76.3809.111',
|
||||||
cronet_embedded: 'org.chromium.net:cronet-embedded:66.3359.158',
|
cronet_embedded: 'org.chromium.net:cronet-embedded:66.3359.158',
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,13 @@ description = "gRPC: XDS plugin"
|
||||||
it.options.compilerArgs += [
|
it.options.compilerArgs += [
|
||||||
// valueOf(int) in RoutingPriority has been deprecated
|
// valueOf(int) in RoutingPriority has been deprecated
|
||||||
"-Xlint:-deprecation",
|
"-Xlint:-deprecation",
|
||||||
|
// only has AutoValue annotation processor
|
||||||
|
"-Xlint:-processing",
|
||||||
]
|
]
|
||||||
|
appendToProperty(
|
||||||
|
it.options.errorprone.excludedPaths,
|
||||||
|
".*/build/generated/sources/annotationProcessor/java/.*",
|
||||||
|
"|")
|
||||||
}
|
}
|
||||||
|
|
||||||
evaluationDependsOn(project(':grpc-core').path)
|
evaluationDependsOn(project(':grpc-core').path)
|
||||||
|
|
@ -27,7 +33,8 @@ dependencies {
|
||||||
project(path: ':grpc-alts', configuration: 'shadow'),
|
project(path: ':grpc-alts', configuration: 'shadow'),
|
||||||
libraries.gson,
|
libraries.gson,
|
||||||
libraries.re2j,
|
libraries.re2j,
|
||||||
libraries.bouncycastle
|
libraries.bouncycastle,
|
||||||
|
libraries.autovalue_annotation
|
||||||
def nettyDependency = implementation project(':grpc-netty')
|
def nettyDependency = implementation project(':grpc-netty')
|
||||||
|
|
||||||
implementation (libraries.opencensus_proto) {
|
implementation (libraries.opencensus_proto) {
|
||||||
|
|
@ -44,6 +51,7 @@ dependencies {
|
||||||
|
|
||||||
testImplementation project(':grpc-core').sourceSets.test.output
|
testImplementation project(':grpc-core').sourceSets.test.output
|
||||||
|
|
||||||
|
annotationProcessor libraries.autovalue
|
||||||
compileOnly libraries.javax_annotation,
|
compileOnly libraries.javax_annotation,
|
||||||
// At runtime use the epoll included in grpc-netty-shaded
|
// At runtime use the epoll included in grpc-netty-shaded
|
||||||
libraries.netty_epoll
|
libraries.netty_epoll
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import io.grpc.internal.GrpcUtil;
|
||||||
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
|
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
|
||||||
import io.grpc.internal.JsonParser;
|
import io.grpc.internal.JsonParser;
|
||||||
import io.grpc.internal.JsonUtil;
|
import io.grpc.internal.JsonUtil;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Node;
|
import io.grpc.xds.EnvoyProtoData.Node;
|
||||||
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -214,7 +213,7 @@ public class BootstrapperImpl implements Bootstrapper {
|
||||||
if (rawLocality.containsKey("sub_zone")) {
|
if (rawLocality.containsKey("sub_zone")) {
|
||||||
logger.log(XdsLogLevel.INFO, "Locality sub_zone: {0}", subZone);
|
logger.log(XdsLogLevel.INFO, "Locality sub_zone: {0}", subZone);
|
||||||
}
|
}
|
||||||
Locality locality = new Locality(region, zone, subZone);
|
Locality locality = Locality.create(region, zone, subZone);
|
||||||
nodeBuilder.setLocality(locality);
|
nodeBuilder.setLocality(locality);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,18 @@
|
||||||
package io.grpc.xds;
|
package io.grpc.xds;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static io.grpc.xds.EnvoyProtoData.HTTP_FAULT_FILTER_NAME;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static io.grpc.xds.EnvoyProtoData.TRANSPORT_SOCKET_NAME_TLS;
|
import static io.grpc.xds.EnvoyProtoData.TRANSPORT_SOCKET_NAME_TLS;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Stopwatch;
|
import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.protobuf.Any;
|
import com.google.protobuf.Any;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import com.google.protobuf.util.Durations;
|
import com.google.protobuf.util.Durations;
|
||||||
|
import com.google.re2j.Pattern;
|
||||||
|
import com.google.re2j.PatternSyntaxException;
|
||||||
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
|
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
|
||||||
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
|
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
|
||||||
import io.envoyproxy.envoy.config.cluster.v3.Cluster.CustomClusterType;
|
import io.envoyproxy.envoy.config.cluster.v3.Cluster.CustomClusterType;
|
||||||
|
|
@ -34,31 +37,41 @@ import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy;
|
||||||
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
|
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
|
||||||
import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
|
import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
|
||||||
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
|
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
|
||||||
import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint;
|
|
||||||
import io.envoyproxy.envoy.config.listener.v3.Listener;
|
import io.envoyproxy.envoy.config.listener.v3.Listener;
|
||||||
import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
|
import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
|
||||||
import io.envoyproxy.envoy.config.route.v3.VirtualHost;
|
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault;
|
||||||
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
|
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
|
||||||
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter;
|
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter;
|
||||||
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
|
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
|
||||||
|
import io.envoyproxy.envoy.type.v3.FractionalPercent;
|
||||||
|
import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType;
|
||||||
|
import io.grpc.EquivalentAddressGroup;
|
||||||
import io.grpc.ManagedChannel;
|
import io.grpc.ManagedChannel;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.SynchronizationContext.ScheduledHandle;
|
import io.grpc.SynchronizationContext.ScheduledHandle;
|
||||||
import io.grpc.internal.BackoffPolicy;
|
import io.grpc.internal.BackoffPolicy;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyProtoData.HttpFault;
|
import io.grpc.xds.Endpoints.LbEndpoint;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
||||||
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Node;
|
import io.grpc.xds.EnvoyProtoData.Node;
|
||||||
import io.grpc.xds.EnvoyProtoData.StructOrError;
|
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
|
import io.grpc.xds.HttpFault.FaultAbort;
|
||||||
|
import io.grpc.xds.HttpFault.FaultDelay;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
||||||
|
import io.grpc.xds.Matchers.FractionMatcher;
|
||||||
|
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||||
|
import io.grpc.xds.Matchers.PathMatcher;
|
||||||
|
import io.grpc.xds.VirtualHost.Route;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||||
import io.grpc.xds.XdsClient.CdsUpdate.AggregateClusterConfig;
|
import io.grpc.xds.XdsClient.CdsUpdate.AggregateClusterConfig;
|
||||||
import io.grpc.xds.XdsClient.CdsUpdate.ClusterType;
|
import io.grpc.xds.XdsClient.CdsUpdate.ClusterType;
|
||||||
import io.grpc.xds.XdsClient.CdsUpdate.EdsClusterConfig;
|
import io.grpc.xds.XdsClient.CdsUpdate.EdsClusterConfig;
|
||||||
import io.grpc.xds.XdsClient.CdsUpdate.LogicalDnsClusterConfig;
|
import io.grpc.xds.XdsClient.CdsUpdate.LogicalDnsClusterConfig;
|
||||||
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -82,6 +95,7 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15;
|
static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15;
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
|
static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
|
||||||
|
private static final String HTTP_FAULT_FILTER_NAME = "envoy.fault";
|
||||||
private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 =
|
private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 =
|
||||||
"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2"
|
"type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2"
|
||||||
+ ".HttpConnectionManager";
|
+ ".HttpConnectionManager";
|
||||||
|
|
@ -176,7 +190,7 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
hasFaultInjection = true;
|
hasFaultInjection = true;
|
||||||
if (httpFilter.hasTypedConfig()) {
|
if (httpFilter.hasTypedConfig()) {
|
||||||
StructOrError<HttpFault> httpFaultOrError =
|
StructOrError<HttpFault> httpFaultOrError =
|
||||||
HttpFault.decodeFaultFilterConfig(httpFilter.getTypedConfig());
|
decodeFaultFilterConfig(httpFilter.getTypedConfig());
|
||||||
if (httpFaultOrError.getErrorDetail() != null) {
|
if (httpFaultOrError.getErrorDetail() != null) {
|
||||||
nackResponse(ResourceType.LDS, nonce,
|
nackResponse(ResourceType.LDS, nonce,
|
||||||
"Listener " + listenerName + " contains invalid HttpFault filter: "
|
"Listener " + listenerName + " contains invalid HttpFault filter: "
|
||||||
|
|
@ -189,10 +203,10 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hcm.hasRouteConfig()) {
|
if (hcm.hasRouteConfig()) {
|
||||||
List<EnvoyProtoData.VirtualHost> virtualHosts = new ArrayList<>();
|
List<VirtualHost> virtualHosts = new ArrayList<>();
|
||||||
for (VirtualHost virtualHostProto : hcm.getRouteConfig().getVirtualHostsList()) {
|
for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto
|
||||||
StructOrError<EnvoyProtoData.VirtualHost> virtualHost =
|
: hcm.getRouteConfig().getVirtualHostsList()) {
|
||||||
EnvoyProtoData.VirtualHost.fromEnvoyProtoVirtualHost(virtualHostProto);
|
StructOrError<VirtualHost> virtualHost = parseVirtualHost(virtualHostProto);
|
||||||
if (virtualHost.getErrorDetail() != null) {
|
if (virtualHost.getErrorDetail() != null) {
|
||||||
nackResponse(ResourceType.LDS, nonce,
|
nackResponse(ResourceType.LDS, nonce,
|
||||||
"Listener " + listenerName + " contains invalid virtual host: "
|
"Listener " + listenerName + " contains invalid virtual host: "
|
||||||
|
|
@ -237,6 +251,386 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static StructOrError<VirtualHost> parseVirtualHost(
|
||||||
|
io.envoyproxy.envoy.config.route.v3.VirtualHost proto) {
|
||||||
|
String name = proto.getName();
|
||||||
|
List<Route> routes = new ArrayList<>(proto.getRoutesCount());
|
||||||
|
for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) {
|
||||||
|
StructOrError<Route> route = parseRoute(routeProto);
|
||||||
|
if (route == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (route.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Virtual host [" + name + "] contains invalid route : " + route.getErrorDetail());
|
||||||
|
}
|
||||||
|
routes.add(route.getStruct());
|
||||||
|
}
|
||||||
|
HttpFault httpFault = null;
|
||||||
|
Map<String, Any> filterConfigMap = proto.getTypedPerFilterConfigMap();
|
||||||
|
if (filterConfigMap.containsKey(HTTP_FAULT_FILTER_NAME)) {
|
||||||
|
Any rawFaultFilterConfig = filterConfigMap.get(HTTP_FAULT_FILTER_NAME);
|
||||||
|
StructOrError<HttpFault> httpFaultOrError = decodeFaultFilterConfig(rawFaultFilterConfig);
|
||||||
|
if (httpFaultOrError.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Virtual host [" + name + "] contains invalid HttpFault filter : "
|
||||||
|
+ httpFaultOrError.getErrorDetail());
|
||||||
|
}
|
||||||
|
httpFault = httpFaultOrError.getStruct();
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(VirtualHost.create(
|
||||||
|
name, proto.getDomainsList(), routes, httpFault));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
static StructOrError<Route> parseRoute(io.envoyproxy.envoy.config.route.v3.Route proto) {
|
||||||
|
StructOrError<RouteMatch> routeMatch = parseRouteMatch(proto.getMatch());
|
||||||
|
if (routeMatch == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (routeMatch.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Invalid route [" + proto.getName() + "]: " + routeMatch.getErrorDetail());
|
||||||
|
}
|
||||||
|
|
||||||
|
StructOrError<RouteAction> routeAction;
|
||||||
|
switch (proto.getActionCase()) {
|
||||||
|
case ROUTE:
|
||||||
|
routeAction = parseRouteAction(proto.getRoute());
|
||||||
|
break;
|
||||||
|
case REDIRECT:
|
||||||
|
return StructOrError.fromError("Unsupported action type: redirect");
|
||||||
|
case DIRECT_RESPONSE:
|
||||||
|
return StructOrError.fromError("Unsupported action type: direct_response");
|
||||||
|
case FILTER_ACTION:
|
||||||
|
return StructOrError.fromError("Unsupported action type: filter_action");
|
||||||
|
case ACTION_NOT_SET:
|
||||||
|
default:
|
||||||
|
return StructOrError.fromError("Unknown action type: " + proto.getActionCase());
|
||||||
|
}
|
||||||
|
if (routeAction == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (routeAction.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Invalid route [" + proto.getName() + "]: " + routeAction.getErrorDetail());
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpFault httpFault = null;
|
||||||
|
Map<String, Any> filterConfigMap = proto.getTypedPerFilterConfigMap();
|
||||||
|
if (filterConfigMap.containsKey(HTTP_FAULT_FILTER_NAME)) {
|
||||||
|
Any rawFaultFilterConfig = filterConfigMap.get(HTTP_FAULT_FILTER_NAME);
|
||||||
|
StructOrError<HttpFault> httpFaultOrError = decodeFaultFilterConfig(rawFaultFilterConfig);
|
||||||
|
if (httpFaultOrError.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Route [" + proto.getName() + "] contains invalid HttpFault filter: "
|
||||||
|
+ httpFaultOrError.getErrorDetail());
|
||||||
|
}
|
||||||
|
httpFault = httpFaultOrError.getStruct();
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(Route.create(
|
||||||
|
routeMatch.getStruct(), routeAction.getStruct(), httpFault));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
static StructOrError<RouteMatch> parseRouteMatch(
|
||||||
|
io.envoyproxy.envoy.config.route.v3.RouteMatch proto) {
|
||||||
|
if (proto.getQueryParametersCount() != 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
StructOrError<PathMatcher> pathMatch = parsePathMatcher(proto);
|
||||||
|
if (pathMatch.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(pathMatch.getErrorDetail());
|
||||||
|
}
|
||||||
|
|
||||||
|
FractionMatcher fractionMatch = null;
|
||||||
|
if (proto.hasRuntimeFraction()) {
|
||||||
|
StructOrError<FractionMatcher> parsedFraction =
|
||||||
|
parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue());
|
||||||
|
if (parsedFraction.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(parsedFraction.getErrorDetail());
|
||||||
|
}
|
||||||
|
fractionMatch = parsedFraction.getStruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<HeaderMatcher> headerMatchers = new ArrayList<>();
|
||||||
|
for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher hmProto : proto.getHeadersList()) {
|
||||||
|
StructOrError<HeaderMatcher> headerMatcher = parseHeaderMatcher(hmProto);
|
||||||
|
if (headerMatcher.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(headerMatcher.getErrorDetail());
|
||||||
|
}
|
||||||
|
headerMatchers.add(headerMatcher.getStruct());
|
||||||
|
}
|
||||||
|
|
||||||
|
return StructOrError.fromStruct(RouteMatch.create(
|
||||||
|
pathMatch.getStruct(), headerMatchers, fractionMatch));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static StructOrError<PathMatcher> parsePathMatcher(
|
||||||
|
io.envoyproxy.envoy.config.route.v3.RouteMatch proto) {
|
||||||
|
boolean caseSensitive = proto.getCaseSensitive().getValue();
|
||||||
|
switch (proto.getPathSpecifierCase()) {
|
||||||
|
case PREFIX:
|
||||||
|
return StructOrError.fromStruct(
|
||||||
|
PathMatcher.fromPrefix(proto.getPrefix(), caseSensitive));
|
||||||
|
case PATH:
|
||||||
|
return StructOrError.fromStruct(PathMatcher.fromPath(proto.getPath(), caseSensitive));
|
||||||
|
case SAFE_REGEX:
|
||||||
|
String rawPattern = proto.getSafeRegex().getRegex();
|
||||||
|
Pattern safeRegEx;
|
||||||
|
try {
|
||||||
|
safeRegEx = Pattern.compile(rawPattern);
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
return StructOrError.fromError("Malformed safe regex pattern: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(PathMatcher.fromRegEx(safeRegEx));
|
||||||
|
case PATHSPECIFIER_NOT_SET:
|
||||||
|
default:
|
||||||
|
return StructOrError.fromError("Unknown path match type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StructOrError<FractionMatcher> parseFractionMatcher(
|
||||||
|
io.envoyproxy.envoy.type.v3.FractionalPercent proto) {
|
||||||
|
int numerator = proto.getNumerator();
|
||||||
|
int denominator = 0;
|
||||||
|
switch (proto.getDenominator()) {
|
||||||
|
case HUNDRED:
|
||||||
|
denominator = 100;
|
||||||
|
break;
|
||||||
|
case TEN_THOUSAND:
|
||||||
|
denominator = 10_000;
|
||||||
|
break;
|
||||||
|
case MILLION:
|
||||||
|
denominator = 1_000_000;
|
||||||
|
break;
|
||||||
|
case UNRECOGNIZED:
|
||||||
|
default:
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Unrecognized fractional percent denominator: " + proto.getDenominator());
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(FractionMatcher.create(numerator, denominator));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static StructOrError<HeaderMatcher> parseHeaderMatcher(
|
||||||
|
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto) {
|
||||||
|
switch (proto.getHeaderMatchSpecifierCase()) {
|
||||||
|
case EXACT_MATCH:
|
||||||
|
return StructOrError.fromStruct(HeaderMatcher.forExactValue(
|
||||||
|
proto.getName(), proto.getExactMatch(), proto.getInvertMatch()));
|
||||||
|
case SAFE_REGEX_MATCH:
|
||||||
|
String rawPattern = proto.getSafeRegexMatch().getRegex();
|
||||||
|
Pattern safeRegExMatch;
|
||||||
|
try {
|
||||||
|
safeRegExMatch = Pattern.compile(rawPattern);
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"HeaderMatcher [" + proto.getName() + "] contains malformed safe regex pattern: "
|
||||||
|
+ e.getMessage());
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(HeaderMatcher.forSafeRegEx(
|
||||||
|
proto.getName(), safeRegExMatch, proto.getInvertMatch()));
|
||||||
|
case RANGE_MATCH:
|
||||||
|
HeaderMatcher.Range rangeMatch = HeaderMatcher.Range.create(
|
||||||
|
proto.getRangeMatch().getStart(), proto.getRangeMatch().getEnd());
|
||||||
|
return StructOrError.fromStruct(HeaderMatcher.forRange(
|
||||||
|
proto.getName(), rangeMatch, proto.getInvertMatch()));
|
||||||
|
case PRESENT_MATCH:
|
||||||
|
return StructOrError.fromStruct(HeaderMatcher.forPresent(
|
||||||
|
proto.getName(), proto.getPresentMatch(), proto.getInvertMatch()));
|
||||||
|
case PREFIX_MATCH:
|
||||||
|
return StructOrError.fromStruct(HeaderMatcher.forPrefix(
|
||||||
|
proto.getName(), proto.getPrefixMatch(), proto.getInvertMatch()));
|
||||||
|
case SUFFIX_MATCH:
|
||||||
|
return StructOrError.fromStruct(HeaderMatcher.forSuffix(
|
||||||
|
proto.getName(), proto.getSuffixMatch(), proto.getInvertMatch()));
|
||||||
|
case HEADERMATCHSPECIFIER_NOT_SET:
|
||||||
|
default:
|
||||||
|
return StructOrError.fromError("Unknown header matcher type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
static StructOrError<RouteAction> parseRouteAction(
|
||||||
|
io.envoyproxy.envoy.config.route.v3.RouteAction proto) {
|
||||||
|
Long timeoutNano = null;
|
||||||
|
if (proto.hasMaxStreamDuration()) {
|
||||||
|
io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration maxStreamDuration
|
||||||
|
= proto.getMaxStreamDuration();
|
||||||
|
if (maxStreamDuration.hasGrpcTimeoutHeaderMax()) {
|
||||||
|
timeoutNano = Durations.toNanos(maxStreamDuration.getGrpcTimeoutHeaderMax());
|
||||||
|
} else if (maxStreamDuration.hasMaxStreamDuration()) {
|
||||||
|
timeoutNano = Durations.toNanos(maxStreamDuration.getMaxStreamDuration());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<ClusterWeight> weightedClusters;
|
||||||
|
switch (proto.getClusterSpecifierCase()) {
|
||||||
|
case CLUSTER:
|
||||||
|
return StructOrError.fromStruct(RouteAction.forCluster(proto.getCluster(), timeoutNano));
|
||||||
|
case CLUSTER_HEADER:
|
||||||
|
return null;
|
||||||
|
case WEIGHTED_CLUSTERS:
|
||||||
|
List<io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight> clusterWeights
|
||||||
|
= proto.getWeightedClusters().getClustersList();
|
||||||
|
if (clusterWeights.isEmpty()) {
|
||||||
|
return StructOrError.fromError("No cluster found in weighted cluster list");
|
||||||
|
}
|
||||||
|
weightedClusters = new ArrayList<>();
|
||||||
|
for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight
|
||||||
|
: clusterWeights) {
|
||||||
|
StructOrError<ClusterWeight> clusterWeightOrError = parseClusterWeight(clusterWeight);
|
||||||
|
if (clusterWeightOrError.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError("RouteAction contains invalid ClusterWeight: "
|
||||||
|
+ clusterWeightOrError.getErrorDetail());
|
||||||
|
}
|
||||||
|
weightedClusters.add(clusterWeightOrError.getStruct());
|
||||||
|
}
|
||||||
|
// TODO(chengyuanzhang): validate if the sum of weights equals to total weight.
|
||||||
|
return StructOrError.fromStruct(RouteAction.forWeightedClusters(
|
||||||
|
weightedClusters, timeoutNano));
|
||||||
|
case CLUSTERSPECIFIER_NOT_SET:
|
||||||
|
default:
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Unknown cluster specifier: " + proto.getClusterSpecifierCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static StructOrError<ClusterWeight> parseClusterWeight(
|
||||||
|
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto) {
|
||||||
|
HttpFault httpFault = null;
|
||||||
|
Map<String, Any> filterConfigMap = proto.getTypedPerFilterConfigMap();
|
||||||
|
if (filterConfigMap.containsKey(HTTP_FAULT_FILTER_NAME)) {
|
||||||
|
Any rawFaultFilterConfig = filterConfigMap.get(HTTP_FAULT_FILTER_NAME);
|
||||||
|
StructOrError<HttpFault> httpFaultOrError = decodeFaultFilterConfig(rawFaultFilterConfig);
|
||||||
|
if (httpFaultOrError.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"ClusterWeight [" + proto.getName() + "] contains invalid HttpFault filter: "
|
||||||
|
+ httpFaultOrError.getErrorDetail());
|
||||||
|
}
|
||||||
|
httpFault = httpFaultOrError.getStruct();
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(
|
||||||
|
ClusterWeight.create(proto.getName(), proto.getWeight().getValue(), httpFault));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StructOrError<HttpFault> decodeFaultFilterConfig(Any rawFaultFilterConfig) {
|
||||||
|
if (rawFaultFilterConfig.getTypeUrl().equals(
|
||||||
|
"type.googleapis.com/envoy.config.filter.http.fault.v2.HTTPFault")) {
|
||||||
|
rawFaultFilterConfig = rawFaultFilterConfig.toBuilder().setTypeUrl(
|
||||||
|
"type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault").build();
|
||||||
|
}
|
||||||
|
HTTPFault httpFaultProto;
|
||||||
|
try {
|
||||||
|
httpFaultProto = rawFaultFilterConfig.unpack(HTTPFault.class);
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
return StructOrError.fromError("Invalid proto: " + e);
|
||||||
|
}
|
||||||
|
return parseHttpFault(httpFaultProto);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StructOrError<HttpFault> parseHttpFault(HTTPFault httpFault) {
|
||||||
|
FaultDelay faultDelay = null;
|
||||||
|
FaultAbort faultAbort = null;
|
||||||
|
if (httpFault.hasDelay()) {
|
||||||
|
faultDelay = parseFaultDelay(httpFault.getDelay());
|
||||||
|
}
|
||||||
|
if (httpFault.hasAbort()) {
|
||||||
|
StructOrError<FaultAbort> faultAbortOrError = parseFaultAbort(httpFault.getAbort());
|
||||||
|
if (faultAbortOrError.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"HttpFault contains invalid FaultAbort: " + faultAbortOrError.getErrorDetail());
|
||||||
|
}
|
||||||
|
faultAbort = faultAbortOrError.getStruct();
|
||||||
|
}
|
||||||
|
if (faultDelay == null && faultAbort == null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Invalid HttpFault: neither fault_delay nor fault_abort is specified");
|
||||||
|
}
|
||||||
|
String upstreamCluster = httpFault.getUpstreamCluster();
|
||||||
|
List<String> downstreamNodes = httpFault.getDownstreamNodesList();
|
||||||
|
List<HeaderMatcher> headers = new ArrayList<>();
|
||||||
|
for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto : httpFault.getHeadersList()) {
|
||||||
|
StructOrError<HeaderMatcher> headerMatcherOrError = parseHeaderMatcher(proto);
|
||||||
|
if (headerMatcherOrError.getErrorDetail() != null) {
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"HttpFault contains invalid header matcher: "
|
||||||
|
+ headerMatcherOrError.getErrorDetail());
|
||||||
|
}
|
||||||
|
headers.add(headerMatcherOrError.getStruct());
|
||||||
|
}
|
||||||
|
Integer maxActiveFaults = null;
|
||||||
|
if (httpFault.hasMaxActiveFaults()) {
|
||||||
|
maxActiveFaults = httpFault.getMaxActiveFaults().getValue();
|
||||||
|
if (maxActiveFaults < 0) {
|
||||||
|
maxActiveFaults = Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(HttpFault.create(
|
||||||
|
faultDelay, faultAbort, upstreamCluster, downstreamNodes, headers, maxActiveFaults));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FaultDelay parseFaultDelay(
|
||||||
|
io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay faultDelay) {
|
||||||
|
int rate = getRatePerMillion(faultDelay.getPercentage());
|
||||||
|
if (faultDelay.hasHeaderDelay()) {
|
||||||
|
return FaultDelay.forHeader(rate);
|
||||||
|
}
|
||||||
|
return FaultDelay.forFixedDelay(Durations.toNanos(faultDelay.getFixedDelay()), rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static StructOrError<FaultAbort> parseFaultAbort(
|
||||||
|
io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort faultAbort) {
|
||||||
|
int rate = getRatePerMillion(faultAbort.getPercentage());
|
||||||
|
switch (faultAbort.getErrorTypeCase()) {
|
||||||
|
case HEADER_ABORT:
|
||||||
|
return StructOrError.fromStruct(FaultAbort.forHeader(rate));
|
||||||
|
case HTTP_STATUS:
|
||||||
|
return StructOrError.fromStruct(FaultAbort.forStatus(
|
||||||
|
convertHttpStatus(faultAbort.getHttpStatus()), rate));
|
||||||
|
case GRPC_STATUS:
|
||||||
|
return StructOrError.fromStruct(FaultAbort.forStatus(
|
||||||
|
Status.fromCodeValue(faultAbort.getGrpcStatus()), rate));
|
||||||
|
case ERRORTYPE_NOT_SET:
|
||||||
|
default:
|
||||||
|
return StructOrError.fromError(
|
||||||
|
"Unknown error type case: " + faultAbort.getErrorTypeCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Status convertHttpStatus(int httpCode) {
|
||||||
|
Status status;
|
||||||
|
switch (httpCode) {
|
||||||
|
case 400:
|
||||||
|
status = Status.INTERNAL;
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
status = Status.UNAUTHENTICATED;
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
status = Status.PERMISSION_DENIED;
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
status = Status.UNIMPLEMENTED;
|
||||||
|
break;
|
||||||
|
case 429:
|
||||||
|
case 502:
|
||||||
|
case 503:
|
||||||
|
case 504:
|
||||||
|
status = Status.UNAVAILABLE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
status = Status.UNKNOWN;
|
||||||
|
}
|
||||||
|
return status.withDescription("HTTP code: " + httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleRdsResponse(String versionInfo, List<Any> resources, String nonce) {
|
protected void handleRdsResponse(String versionInfo, List<Any> resources, String nonce) {
|
||||||
// Unpack RouteConfiguration messages.
|
// Unpack RouteConfiguration messages.
|
||||||
|
|
@ -262,11 +656,11 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
for (Map.Entry<String, RouteConfiguration> entry : routeConfigs.entrySet()) {
|
for (Map.Entry<String, RouteConfiguration> entry : routeConfigs.entrySet()) {
|
||||||
String routeConfigName = entry.getKey();
|
String routeConfigName = entry.getKey();
|
||||||
RouteConfiguration routeConfig = entry.getValue();
|
RouteConfiguration routeConfig = entry.getValue();
|
||||||
List<EnvoyProtoData.VirtualHost> virtualHosts =
|
List<VirtualHost> virtualHosts =
|
||||||
new ArrayList<>(routeConfig.getVirtualHostsCount());
|
new ArrayList<>(routeConfig.getVirtualHostsCount());
|
||||||
for (VirtualHost virtualHostProto : routeConfig.getVirtualHostsList()) {
|
for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto
|
||||||
StructOrError<EnvoyProtoData.VirtualHost> virtualHost =
|
: routeConfig.getVirtualHostsList()) {
|
||||||
EnvoyProtoData.VirtualHost.fromEnvoyProtoVirtualHost(virtualHostProto);
|
StructOrError<VirtualHost> virtualHost = parseVirtualHost(virtualHostProto);
|
||||||
if (virtualHost.getErrorDetail() != null) {
|
if (virtualHost.getErrorDetail() != null) {
|
||||||
nackResponse(ResourceType.RDS, nonce, "RouteConfiguration " + routeConfigName
|
nackResponse(ResourceType.RDS, nonce, "RouteConfiguration " + routeConfigName
|
||||||
+ " contains invalid virtual host: " + virtualHost.getErrorDetail());
|
+ " contains invalid virtual host: " + virtualHost.getErrorDetail());
|
||||||
|
|
@ -508,45 +902,35 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
Map<Locality, LocalityLbEndpoints> localityLbEndpointsMap = new LinkedHashMap<>();
|
Map<Locality, LocalityLbEndpoints> localityLbEndpointsMap = new LinkedHashMap<>();
|
||||||
List<DropOverload> dropOverloads = new ArrayList<>();
|
List<DropOverload> dropOverloads = new ArrayList<>();
|
||||||
int maxPriority = -1;
|
int maxPriority = -1;
|
||||||
for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpoints
|
for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto
|
||||||
: assignment.getEndpointsList()) {
|
: assignment.getEndpointsList()) {
|
||||||
// Filter out localities without or with 0 weight.
|
StructOrError<LocalityLbEndpoints> localityLbEndpoints =
|
||||||
if (!localityLbEndpoints.hasLoadBalancingWeight()
|
parseLocalityLbEndpoints(localityLbEndpointsProto);
|
||||||
|| localityLbEndpoints.getLoadBalancingWeight().getValue() < 1) {
|
if (localityLbEndpoints == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int localityPriority = localityLbEndpoints.getPriority();
|
if (localityLbEndpoints.getErrorDetail() != null) {
|
||||||
if (localityPriority < 0) {
|
nackResponse(ResourceType.EDS, nonce, "ClusterLoadAssignment " + clusterName + ": "
|
||||||
nackResponse(ResourceType.EDS, nonce,
|
+ localityLbEndpoints.getErrorDetail());
|
||||||
"ClusterLoadAssignment " + clusterName + " : locality with negative priority.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
maxPriority = Math.max(maxPriority, localityPriority);
|
maxPriority = Math.max(maxPriority, localityLbEndpoints.getStruct().priority());
|
||||||
priorities.add(localityPriority);
|
priorities.add(localityLbEndpoints.getStruct().priority());
|
||||||
// The endpoint field of each lb_endpoints must be set.
|
// Note endpoints with health status other than HEALTHY and UNKNOWN are still
|
||||||
// Inside of it: the address field must be set.
|
|
||||||
for (LbEndpoint lbEndpoint : localityLbEndpoints.getLbEndpointsList()) {
|
|
||||||
if (!lbEndpoint.getEndpoint().hasAddress()) {
|
|
||||||
nackResponse(ResourceType.EDS, nonce,
|
|
||||||
"ClusterLoadAssignment " + clusterName + " : endpoint with no address.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Note endpoints with health status other than UNHEALTHY and UNKNOWN are still
|
|
||||||
// handed over to watching parties. It is watching parties' responsibility to
|
// handed over to watching parties. It is watching parties' responsibility to
|
||||||
// filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy().
|
// filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy().
|
||||||
localityLbEndpointsMap.put(
|
localityLbEndpointsMap.put(
|
||||||
Locality.fromEnvoyProtoLocality(localityLbEndpoints.getLocality()),
|
parseLocality(localityLbEndpointsProto.getLocality()),
|
||||||
LocalityLbEndpoints.fromEnvoyProtoLocalityLbEndpoints(localityLbEndpoints));
|
localityLbEndpoints.getStruct());
|
||||||
}
|
}
|
||||||
if (priorities.size() != maxPriority + 1) {
|
if (priorities.size() != maxPriority + 1) {
|
||||||
nackResponse(ResourceType.EDS, nonce,
|
nackResponse(ResourceType.EDS, nonce,
|
||||||
"ClusterLoadAssignment " + clusterName + " : sparse priorities.");
|
"ClusterLoadAssignment " + clusterName + " : sparse priorities.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (ClusterLoadAssignment.Policy.DropOverload dropOverload
|
for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto
|
||||||
: assignment.getPolicy().getDropOverloadsList()) {
|
: assignment.getPolicy().getDropOverloadsList()) {
|
||||||
dropOverloads.add(DropOverload.fromEnvoyProtoDropOverload(dropOverload));
|
dropOverloads.add(parseDropOverload(dropOverloadProto));
|
||||||
}
|
}
|
||||||
EdsUpdate update = new EdsUpdate(clusterName, localityLbEndpointsMap, dropOverloads);
|
EdsUpdate update = new EdsUpdate(clusterName, localityLbEndpointsMap, dropOverloads);
|
||||||
edsUpdates.put(clusterName, update);
|
edsUpdates.put(clusterName, update);
|
||||||
|
|
@ -561,6 +945,72 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Locality parseLocality(io.envoyproxy.envoy.config.core.v3.Locality proto) {
|
||||||
|
return Locality.create(proto.getRegion(), proto.getZone(), proto.getSubZone());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DropOverload parseDropOverload(
|
||||||
|
io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment.Policy.DropOverload proto) {
|
||||||
|
return DropOverload.create(proto.getCategory(), getRatePerMillion(proto.getDropPercentage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
static StructOrError<LocalityLbEndpoints> parseLocalityLbEndpoints(
|
||||||
|
io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints proto) {
|
||||||
|
// Filter out localities without or with 0 weight.
|
||||||
|
if (!proto.hasLoadBalancingWeight() || proto.getLoadBalancingWeight().getValue() < 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (proto.getPriority() < 0) {
|
||||||
|
return StructOrError.fromError("negative priority");
|
||||||
|
}
|
||||||
|
List<LbEndpoint> endpoints = new ArrayList<>(proto.getLbEndpointsCount());
|
||||||
|
for (io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint endpoint : proto.getLbEndpointsList()) {
|
||||||
|
// The endpoint field of each lb_endpoints must be set.
|
||||||
|
// Inside of it: the address field must be set.
|
||||||
|
if (!endpoint.hasEndpoint() || !endpoint.getEndpoint().hasAddress()) {
|
||||||
|
return StructOrError.fromError("LbEndpoint with no endpoint/address");
|
||||||
|
}
|
||||||
|
io.envoyproxy.envoy.config.core.v3.SocketAddress socketAddress =
|
||||||
|
endpoint.getEndpoint().getAddress().getSocketAddress();
|
||||||
|
InetSocketAddress addr =
|
||||||
|
new InetSocketAddress(socketAddress.getAddress(), socketAddress.getPortValue());
|
||||||
|
boolean isHealthy =
|
||||||
|
endpoint.getHealthStatus() == io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY
|
||||||
|
|| endpoint.getHealthStatus()
|
||||||
|
== io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN;
|
||||||
|
endpoints.add(LbEndpoint.create(
|
||||||
|
new EquivalentAddressGroup(ImmutableList.<java.net.SocketAddress>of(addr)),
|
||||||
|
endpoint.getLoadBalancingWeight().getValue(), isHealthy));
|
||||||
|
}
|
||||||
|
return StructOrError.fromStruct(LocalityLbEndpoints.create(
|
||||||
|
endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getRatePerMillion(FractionalPercent percent) {
|
||||||
|
int numerator = percent.getNumerator();
|
||||||
|
DenominatorType type = percent.getDenominator();
|
||||||
|
switch (type) {
|
||||||
|
case TEN_THOUSAND:
|
||||||
|
numerator *= 100;
|
||||||
|
break;
|
||||||
|
case HUNDRED:
|
||||||
|
numerator *= 10_000;
|
||||||
|
break;
|
||||||
|
case MILLION:
|
||||||
|
break;
|
||||||
|
case UNRECOGNIZED:
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown denominator type of " + percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numerator > 1_000_000 || numerator < 0) {
|
||||||
|
numerator = 1_000_000;
|
||||||
|
}
|
||||||
|
return numerator;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleStreamClosed(Status error) {
|
protected void handleStreamClosed(Status error) {
|
||||||
cleanUpResourceTimers();
|
cleanUpResourceTimers();
|
||||||
|
|
@ -933,4 +1383,53 @@ final class ClientXdsClient extends AbstractXdsClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final class StructOrError<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link StructOrError} for the successfully converted data object.
|
||||||
|
*/
|
||||||
|
private static <T> StructOrError<T> fromStruct(T struct) {
|
||||||
|
return new StructOrError<>(struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link StructOrError} for the failure to convert the data object.
|
||||||
|
*/
|
||||||
|
private static <T> StructOrError<T> fromError(String errorDetail) {
|
||||||
|
return new StructOrError<>(errorDetail);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String errorDetail;
|
||||||
|
private final T struct;
|
||||||
|
|
||||||
|
private StructOrError(T struct) {
|
||||||
|
this.struct = checkNotNull(struct, "struct");
|
||||||
|
this.errorDetail = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StructOrError(String errorDetail) {
|
||||||
|
this.struct = null;
|
||||||
|
this.errorDetail = checkNotNull(errorDetail, "errorDetail");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns struct if exists, otherwise null.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
T getStruct() {
|
||||||
|
return struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns error detail if exists, otherwise null.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
@Nullable
|
||||||
|
String getErrorDetail() {
|
||||||
|
return errorDetail;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,7 @@ import io.grpc.util.ForwardingClientStreamTracer;
|
||||||
import io.grpc.util.ForwardingLoadBalancerHelper;
|
import io.grpc.util.ForwardingLoadBalancerHelper;
|
||||||
import io.grpc.util.ForwardingSubchannel;
|
import io.grpc.util.ForwardingSubchannel;
|
||||||
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
||||||
|
|
@ -213,7 +212,7 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
|
||||||
// attributes with its locality, including endpoints in LOGICAL_DNS clusters.
|
// attributes with its locality, including endpoints in LOGICAL_DNS clusters.
|
||||||
// In case of not (which really shouldn't), loads are aggregated under an empty locality.
|
// In case of not (which really shouldn't), loads are aggregated under an empty locality.
|
||||||
if (locality == null) {
|
if (locality == null) {
|
||||||
locality = new Locality("", "", "");
|
locality = Locality.create("", "", "");
|
||||||
}
|
}
|
||||||
final ClusterLocalityStats localityStats = xdsClient.addClusterLocalityStats(
|
final ClusterLocalityStats localityStats = xdsClient.addClusterLocalityStats(
|
||||||
cluster, edsServiceName, locality);
|
cluster, edsServiceName, locality);
|
||||||
|
|
@ -292,14 +291,14 @@ final class ClusterImplLoadBalancer extends LoadBalancer {
|
||||||
public PickResult pickSubchannel(PickSubchannelArgs args) {
|
public PickResult pickSubchannel(PickSubchannelArgs args) {
|
||||||
for (DropOverload dropOverload : dropPolicies) {
|
for (DropOverload dropOverload : dropPolicies) {
|
||||||
int rand = random.nextInt(1_000_000);
|
int rand = random.nextInt(1_000_000);
|
||||||
if (rand < dropOverload.getDropsPerMillion()) {
|
if (rand < dropOverload.dropsPerMillion()) {
|
||||||
logger.log(XdsLogLevel.INFO, "Drop request with category: {0}",
|
logger.log(XdsLogLevel.INFO, "Drop request with category: {0}",
|
||||||
dropOverload.getCategory());
|
dropOverload.category());
|
||||||
if (dropStats != null) {
|
if (dropStats != null) {
|
||||||
dropStats.recordDroppedRequest(dropOverload.getCategory());
|
dropStats.recordDroppedRequest(dropOverload.category());
|
||||||
}
|
}
|
||||||
return PickResult.withDrop(
|
return PickResult.withDrop(
|
||||||
Status.UNAVAILABLE.withDescription("Dropped: " + dropOverload.getCategory()));
|
Status.UNAVAILABLE.withDescription("Dropped: " + dropOverload.category()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final PickResult result = delegate.pickSubchannel(args);
|
final PickResult result = delegate.pickSubchannel(args);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import io.grpc.LoadBalancerProvider;
|
||||||
import io.grpc.LoadBalancerRegistry;
|
import io.grpc.LoadBalancerRegistry;
|
||||||
import io.grpc.NameResolver.ConfigOrError;
|
import io.grpc.NameResolver.ConfigOrError;
|
||||||
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
|
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,9 @@ import io.grpc.util.GracefulSwitchLoadBalancer;
|
||||||
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
||||||
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
|
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
|
||||||
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
|
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyProtoData.LbEndpoint;
|
import io.grpc.xds.Endpoints.LbEndpoint;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
||||||
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
|
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
|
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
|
||||||
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
|
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
|
||||||
|
|
@ -78,7 +77,7 @@ import javax.annotation.Nullable;
|
||||||
*/
|
*/
|
||||||
final class ClusterResolverLoadBalancer extends LoadBalancer {
|
final class ClusterResolverLoadBalancer extends LoadBalancer {
|
||||||
|
|
||||||
private static final Locality LOGICAL_DNS_CLUSTER_LOCALITY = new Locality("", "", "");
|
private static final Locality LOGICAL_DNS_CLUSTER_LOCALITY = Locality.create("", "", "");
|
||||||
private final XdsLogger logger;
|
private final XdsLogger logger;
|
||||||
private final String authority;
|
private final String authority;
|
||||||
private final SynchronizationContext syncContext;
|
private final SynchronizationContext syncContext;
|
||||||
|
|
@ -385,16 +384,16 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
|
||||||
Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>();
|
Map<String, Map<Locality, Integer>> prioritizedLocalityWeights = new HashMap<>();
|
||||||
for (Locality locality : localityLbEndpoints.keySet()) {
|
for (Locality locality : localityLbEndpoints.keySet()) {
|
||||||
LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
|
LocalityLbEndpoints localityLbInfo = localityLbEndpoints.get(locality);
|
||||||
int priority = localityLbInfo.getPriority();
|
int priority = localityLbInfo.priority();
|
||||||
String priorityName = priorityName(name, priority);
|
String priorityName = priorityName(name, priority);
|
||||||
boolean discard = true;
|
boolean discard = true;
|
||||||
for (LbEndpoint endpoint : localityLbInfo.getEndpoints()) {
|
for (LbEndpoint endpoint : localityLbInfo.endpoints()) {
|
||||||
if (endpoint.isHealthy()) {
|
if (endpoint.isHealthy()) {
|
||||||
discard = false;
|
discard = false;
|
||||||
Attributes attr = endpoint.getAddress().getAttributes().toBuilder()
|
Attributes attr = endpoint.eag().getAttributes().toBuilder()
|
||||||
.set(InternalXdsAttributes.ATTR_LOCALITY, locality).build();
|
.set(InternalXdsAttributes.ATTR_LOCALITY, locality).build();
|
||||||
EquivalentAddressGroup eag =
|
EquivalentAddressGroup eag =
|
||||||
new EquivalentAddressGroup(endpoint.getAddress().getAddresses(), attr);
|
new EquivalentAddressGroup(endpoint.eag().getAddresses(), attr);
|
||||||
eag = AddressFilter.setPathFilter(
|
eag = AddressFilter.setPathFilter(
|
||||||
eag, Arrays.asList(priorityName, localityName(locality)));
|
eag, Arrays.asList(priorityName, localityName(locality)));
|
||||||
addresses.add(eag);
|
addresses.add(eag);
|
||||||
|
|
@ -409,7 +408,7 @@ final class ClusterResolverLoadBalancer extends LoadBalancer {
|
||||||
prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>());
|
prioritizedLocalityWeights.put(priorityName, new HashMap<Locality, Integer>());
|
||||||
}
|
}
|
||||||
prioritizedLocalityWeights.get(priorityName).put(
|
prioritizedLocalityWeights.get(priorityName).put(
|
||||||
locality, localityLbInfo.getLocalityWeight());
|
locality, localityLbInfo.localityWeight());
|
||||||
}
|
}
|
||||||
if (prioritizedLocalityWeights.isEmpty()) {
|
if (prioritizedLocalityWeights.isEmpty()) {
|
||||||
// Will still update the result, as if the cluster resource is revoked.
|
// Will still update the result, as if the cluster resource is revoked.
|
||||||
|
|
|
||||||
|
|
@ -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.Internal;
|
||||||
import io.grpc.NameResolver;
|
import io.grpc.NameResolver;
|
||||||
import io.grpc.internal.ObjectPool;
|
import io.grpc.internal.ObjectPool;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
|
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
|
||||||
import io.grpc.xds.internal.sds.SslContextProviderSupplier;
|
import io.grpc.xds.internal.sds.SslContextProviderSupplier;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,10 @@ import io.grpc.SynchronizationContext;
|
||||||
import io.grpc.SynchronizationContext.ScheduledHandle;
|
import io.grpc.SynchronizationContext.ScheduledHandle;
|
||||||
import io.grpc.internal.BackoffPolicy;
|
import io.grpc.internal.BackoffPolicy;
|
||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Node;
|
import io.grpc.xds.EnvoyProtoData.Node;
|
||||||
|
import io.grpc.xds.Stats.ClusterStats;
|
||||||
|
import io.grpc.xds.Stats.DroppedRequests;
|
||||||
|
import io.grpc.xds.Stats.UpstreamLocalityStats;
|
||||||
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
import io.grpc.xds.XdsLogger.XdsLogLevel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -346,7 +348,7 @@ final class LoadReportClient {
|
||||||
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest.newBuilder()
|
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest.newBuilder()
|
||||||
.setNode(node.toEnvoyProtoNodeV2());
|
.setNode(node.toEnvoyProtoNodeV2());
|
||||||
for (ClusterStats stats : clusterStatsList) {
|
for (ClusterStats stats : clusterStatsList) {
|
||||||
requestBuilder.addClusterStats(stats.toEnvoyProtoClusterStatsV2());
|
requestBuilder.addClusterStats(buildClusterStats(stats));
|
||||||
}
|
}
|
||||||
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest request = requestBuilder.build();
|
io.envoyproxy.envoy.service.load_stats.v2.LoadStatsRequest request = requestBuilder.build();
|
||||||
lrsRequestWriterV2.onNext(requestBuilder.build());
|
lrsRequestWriterV2.onNext(requestBuilder.build());
|
||||||
|
|
@ -357,6 +359,37 @@ final class LoadReportClient {
|
||||||
void sendError(Exception error) {
|
void sendError(Exception error) {
|
||||||
lrsRequestWriterV2.onError(error);
|
lrsRequestWriterV2.onError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private io.envoyproxy.envoy.api.v2.endpoint.ClusterStats buildClusterStats(
|
||||||
|
ClusterStats stats) {
|
||||||
|
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.Builder builder =
|
||||||
|
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.newBuilder()
|
||||||
|
.setClusterName(stats.clusterName());
|
||||||
|
if (stats.clusterServiceName() != null) {
|
||||||
|
builder.setClusterServiceName(stats.clusterServiceName());
|
||||||
|
}
|
||||||
|
for (UpstreamLocalityStats upstreamLocalityStats : stats.upstreamLocalityStatsList()) {
|
||||||
|
builder.addUpstreamLocalityStats(
|
||||||
|
io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.newBuilder()
|
||||||
|
.setLocality(
|
||||||
|
io.envoyproxy.envoy.api.v2.core.Locality.newBuilder()
|
||||||
|
.setRegion(upstreamLocalityStats.locality().region())
|
||||||
|
.setZone(upstreamLocalityStats.locality().zone())
|
||||||
|
.setSubZone(upstreamLocalityStats.locality().subZone()))
|
||||||
|
.setTotalSuccessfulRequests(upstreamLocalityStats.totalSuccessfulRequests())
|
||||||
|
.setTotalErrorRequests(upstreamLocalityStats.totalErrorRequests())
|
||||||
|
.setTotalRequestsInProgress(upstreamLocalityStats.totalRequestsInProgress())
|
||||||
|
.setTotalIssuedRequests(upstreamLocalityStats.totalIssuedRequests()));
|
||||||
|
}
|
||||||
|
for (DroppedRequests droppedRequests : stats.droppedRequestsList()) {
|
||||||
|
builder.addDroppedRequests(
|
||||||
|
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests.newBuilder()
|
||||||
|
.setCategory(droppedRequests.category())
|
||||||
|
.setDroppedCount(droppedRequests.droppedCount()));
|
||||||
|
}
|
||||||
|
return builder.setTotalDroppedRequests(stats.totalDroppedRequests())
|
||||||
|
.setLoadReportInterval(Durations.fromNanos(stats.loadReportIntervalNano())).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class LrsStreamV3 extends LrsStream {
|
private final class LrsStreamV3 extends LrsStream {
|
||||||
|
|
@ -410,7 +443,7 @@ final class LoadReportClient {
|
||||||
LoadStatsRequest.Builder requestBuilder =
|
LoadStatsRequest.Builder requestBuilder =
|
||||||
LoadStatsRequest.newBuilder().setNode(node.toEnvoyProtoNode());
|
LoadStatsRequest.newBuilder().setNode(node.toEnvoyProtoNode());
|
||||||
for (ClusterStats stats : clusterStatsList) {
|
for (ClusterStats stats : clusterStatsList) {
|
||||||
requestBuilder.addClusterStats(stats.toEnvoyProtoClusterStats());
|
requestBuilder.addClusterStats(buildClusterStats(stats));
|
||||||
}
|
}
|
||||||
LoadStatsRequest request = requestBuilder.build();
|
LoadStatsRequest request = requestBuilder.build();
|
||||||
lrsRequestWriterV3.onNext(request);
|
lrsRequestWriterV3.onNext(request);
|
||||||
|
|
@ -421,5 +454,38 @@ final class LoadReportClient {
|
||||||
void sendError(Exception error) {
|
void sendError(Exception error) {
|
||||||
lrsRequestWriterV3.onError(error);
|
lrsRequestWriterV3.onError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private io.envoyproxy.envoy.config.endpoint.v3.ClusterStats buildClusterStats(
|
||||||
|
ClusterStats stats) {
|
||||||
|
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.Builder builder =
|
||||||
|
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.newBuilder()
|
||||||
|
.setClusterName(stats.clusterName());
|
||||||
|
if (stats.clusterServiceName() != null) {
|
||||||
|
builder.setClusterServiceName(stats.clusterServiceName());
|
||||||
|
}
|
||||||
|
for (UpstreamLocalityStats upstreamLocalityStats : stats.upstreamLocalityStatsList()) {
|
||||||
|
builder.addUpstreamLocalityStats(
|
||||||
|
io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.newBuilder()
|
||||||
|
.setLocality(
|
||||||
|
io.envoyproxy.envoy.config.core.v3.Locality.newBuilder()
|
||||||
|
.setRegion(upstreamLocalityStats.locality().region())
|
||||||
|
.setZone(upstreamLocalityStats.locality().zone())
|
||||||
|
.setSubZone(upstreamLocalityStats.locality().subZone()))
|
||||||
|
.setTotalSuccessfulRequests(upstreamLocalityStats.totalSuccessfulRequests())
|
||||||
|
.setTotalErrorRequests(upstreamLocalityStats.totalErrorRequests())
|
||||||
|
.setTotalRequestsInProgress(upstreamLocalityStats.totalRequestsInProgress())
|
||||||
|
.setTotalIssuedRequests(upstreamLocalityStats.totalIssuedRequests()));
|
||||||
|
}
|
||||||
|
for (DroppedRequests droppedRequests : stats.droppedRequestsList()) {
|
||||||
|
builder.addDroppedRequests(
|
||||||
|
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests.newBuilder()
|
||||||
|
.setCategory(droppedRequests.category())
|
||||||
|
.setDroppedCount(droppedRequests.droppedCount()));
|
||||||
|
}
|
||||||
|
return builder
|
||||||
|
.setTotalDroppedRequests(stats.totalDroppedRequests())
|
||||||
|
.setLoadReportInterval(Durations.fromNanos(stats.loadReportIntervalNano()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,9 @@ import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats;
|
import io.grpc.xds.Stats.ClusterStats;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests;
|
import io.grpc.xds.Stats.DroppedRequests;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
import io.grpc.xds.Stats.UpstreamLocalityStats;
|
||||||
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -153,9 +152,9 @@ final class LoadStatsManager2 {
|
||||||
if (clusterDropStats != null) {
|
if (clusterDropStats != null) {
|
||||||
Set<String> toDiscard = new HashSet<>();
|
Set<String> toDiscard = new HashSet<>();
|
||||||
for (String edsServiceName : clusterDropStats.keySet()) {
|
for (String edsServiceName : clusterDropStats.keySet()) {
|
||||||
ClusterStats.Builder builder = ClusterStats.newBuilder().setClusterName(cluster);
|
ClusterStats.Builder builder = ClusterStats.newBuilder().clusterName(cluster);
|
||||||
if (edsServiceName != null) {
|
if (edsServiceName != null) {
|
||||||
builder.setClusterServiceName(edsServiceName);
|
builder.clusterServiceName(edsServiceName);
|
||||||
}
|
}
|
||||||
ReferenceCounted<ClusterDropStats> ref = clusterDropStats.get(edsServiceName);
|
ReferenceCounted<ClusterDropStats> ref = clusterDropStats.get(edsServiceName);
|
||||||
if (ref.getReferenceCount() == 0) { // stats object no longer needed after snapshot
|
if (ref.getReferenceCount() == 0) { // stats object no longer needed after snapshot
|
||||||
|
|
@ -164,12 +163,12 @@ final class LoadStatsManager2 {
|
||||||
ClusterDropStatsSnapshot dropStatsSnapshot = ref.get().snapshot();
|
ClusterDropStatsSnapshot dropStatsSnapshot = ref.get().snapshot();
|
||||||
long totalCategorizedDrops = 0L;
|
long totalCategorizedDrops = 0L;
|
||||||
for (Map.Entry<String, Long> entry : dropStatsSnapshot.categorizedDrops.entrySet()) {
|
for (Map.Entry<String, Long> entry : dropStatsSnapshot.categorizedDrops.entrySet()) {
|
||||||
builder.addDroppedRequests(new DroppedRequests(entry.getKey(), entry.getValue()));
|
builder.addDroppedRequests(DroppedRequests.create(entry.getKey(), entry.getValue()));
|
||||||
totalCategorizedDrops += entry.getValue();
|
totalCategorizedDrops += entry.getValue();
|
||||||
}
|
}
|
||||||
builder.setTotalDroppedRequests(
|
builder.totalDroppedRequests(
|
||||||
totalCategorizedDrops + dropStatsSnapshot.uncategorizedDrops);
|
totalCategorizedDrops + dropStatsSnapshot.uncategorizedDrops);
|
||||||
builder.setLoadReportIntervalNanos(dropStatsSnapshot.durationNano);
|
builder.loadReportIntervalNano(dropStatsSnapshot.durationNano);
|
||||||
statsReportBuilders.put(edsServiceName, builder);
|
statsReportBuilders.put(edsServiceName, builder);
|
||||||
}
|
}
|
||||||
clusterDropStats.keySet().removeAll(toDiscard);
|
clusterDropStats.keySet().removeAll(toDiscard);
|
||||||
|
|
@ -180,9 +179,9 @@ final class LoadStatsManager2 {
|
||||||
for (String edsServiceName : clusterLoadStats.keySet()) {
|
for (String edsServiceName : clusterLoadStats.keySet()) {
|
||||||
ClusterStats.Builder builder = statsReportBuilders.get(edsServiceName);
|
ClusterStats.Builder builder = statsReportBuilders.get(edsServiceName);
|
||||||
if (builder == null) {
|
if (builder == null) {
|
||||||
builder = ClusterStats.newBuilder().setClusterName(cluster);
|
builder = ClusterStats.newBuilder().clusterName(cluster);
|
||||||
if (edsServiceName != null) {
|
if (edsServiceName != null) {
|
||||||
builder.setClusterServiceName(edsServiceName);
|
builder.clusterServiceName(edsServiceName);
|
||||||
}
|
}
|
||||||
statsReportBuilders.put(edsServiceName, builder);
|
statsReportBuilders.put(edsServiceName, builder);
|
||||||
}
|
}
|
||||||
|
|
@ -196,17 +195,14 @@ final class LoadStatsManager2 {
|
||||||
if (ref.getReferenceCount() == 0 && snapshot.callsInProgress == 0) {
|
if (ref.getReferenceCount() == 0 && snapshot.callsInProgress == 0) {
|
||||||
localitiesToDiscard.add(locality);
|
localitiesToDiscard.add(locality);
|
||||||
}
|
}
|
||||||
UpstreamLocalityStats.Builder localityStatsBuilder = UpstreamLocalityStats.newBuilder();
|
UpstreamLocalityStats upstreamLocalityStats = UpstreamLocalityStats.create(
|
||||||
localityStatsBuilder.setLocality(locality);
|
locality, snapshot.callsIssued, snapshot.callsSucceeded, snapshot.callsFailed,
|
||||||
localityStatsBuilder.setTotalIssuedRequests(snapshot.callsIssued);
|
snapshot.callsInProgress);
|
||||||
localityStatsBuilder.setTotalSuccessfulRequests(snapshot.callsSucceeded);
|
builder.addUpstreamLocalityStats(upstreamLocalityStats);
|
||||||
localityStatsBuilder.setTotalErrorRequests(snapshot.callsFailed);
|
|
||||||
localityStatsBuilder.setTotalRequestsInProgress(snapshot.callsInProgress);
|
|
||||||
builder.addUpstreamLocalityStats(localityStatsBuilder.build());
|
|
||||||
// Use the max (drops/loads) recording interval as the overall interval for the
|
// Use the max (drops/loads) recording interval as the overall interval for the
|
||||||
// cluster's stats. In general, they should be mostly identical.
|
// cluster's stats. In general, they should be mostly identical.
|
||||||
builder.setLoadReportIntervalNanos(
|
builder.loadReportIntervalNano(
|
||||||
Math.max(builder.getLoadReportIntervalNanos(), snapshot.durationNano));
|
Math.max(builder.loadReportIntervalNano(), snapshot.durationNano));
|
||||||
}
|
}
|
||||||
localityStats.keySet().removeAll(localitiesToDiscard);
|
localityStats.keySet().removeAll(localitiesToDiscard);
|
||||||
if (localityStats.isEmpty()) {
|
if (localityStats.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
import com.google.common.base.MoreObjects.ToStringHelper;
|
import com.google.common.base.MoreObjects.ToStringHelper;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyProtoData.HttpFault;
|
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.VirtualHost;
|
|
||||||
import io.grpc.xds.EnvoyServerProtoData.Listener;
|
import io.grpc.xds.EnvoyServerProtoData.Listener;
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import io.grpc.Attributes;
|
import io.grpc.Attributes;
|
||||||
|
|
@ -39,11 +40,14 @@ import io.grpc.Status;
|
||||||
import io.grpc.SynchronizationContext;
|
import io.grpc.SynchronizationContext;
|
||||||
import io.grpc.internal.GrpcUtil;
|
import io.grpc.internal.GrpcUtil;
|
||||||
import io.grpc.internal.ObjectPool;
|
import io.grpc.internal.ObjectPool;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterWeight;
|
import io.grpc.xds.Matchers.FractionMatcher;
|
||||||
import io.grpc.xds.EnvoyProtoData.Route;
|
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||||
import io.grpc.xds.EnvoyProtoData.RouteAction;
|
import io.grpc.xds.Matchers.PathMatcher;
|
||||||
import io.grpc.xds.EnvoyProtoData.VirtualHost;
|
|
||||||
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
|
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
|
||||||
|
import io.grpc.xds.VirtualHost.Route;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||||
import io.grpc.xds.XdsClient.LdsResourceWatcher;
|
import io.grpc.xds.XdsClient.LdsResourceWatcher;
|
||||||
import io.grpc.xds.XdsClient.LdsUpdate;
|
import io.grpc.xds.XdsClient.LdsUpdate;
|
||||||
import io.grpc.xds.XdsClient.RdsResourceWatcher;
|
import io.grpc.xds.XdsClient.RdsResourceWatcher;
|
||||||
|
|
@ -218,7 +222,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
boolean exactMatchFound = false; // true if a virtual host with exactly matched domain found
|
boolean exactMatchFound = false; // true if a virtual host with exactly matched domain found
|
||||||
VirtualHost targetVirtualHost = null; // target VirtualHost with longest matched domain
|
VirtualHost targetVirtualHost = null; // target VirtualHost with longest matched domain
|
||||||
for (VirtualHost vHost : virtualHosts) {
|
for (VirtualHost vHost : virtualHosts) {
|
||||||
for (String domain : vHost.getDomains()) {
|
for (String domain : vHost.domains()) {
|
||||||
boolean selected = false;
|
boolean selected = false;
|
||||||
if (matchHostName(hostName, domain)) { // matching
|
if (matchHostName(hostName, domain)) { // matching
|
||||||
if (!domain.contains("*")) { // exact matching
|
if (!domain.contains("*")) { // exact matching
|
||||||
|
|
@ -320,8 +324,8 @@ final class XdsNameResolver extends NameResolver {
|
||||||
Route selectedRoute = null;
|
Route selectedRoute = null;
|
||||||
do {
|
do {
|
||||||
for (Route route : routingConfig.routes) {
|
for (Route route : routingConfig.routes) {
|
||||||
if (route.getRouteMatch().matches(
|
if (matchRoute(route.routeMatch(), "/" + args.getMethodDescriptor().getFullMethodName(),
|
||||||
"/" + args.getMethodDescriptor().getFullMethodName(), asciiHeaders)) {
|
asciiHeaders, random)) {
|
||||||
selectedRoute = route;
|
selectedRoute = route;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -330,20 +334,20 @@ final class XdsNameResolver extends NameResolver {
|
||||||
return Result.forError(
|
return Result.forError(
|
||||||
Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"));
|
Status.UNAVAILABLE.withDescription("Could not find xDS route matching RPC"));
|
||||||
}
|
}
|
||||||
RouteAction action = selectedRoute.getRouteAction();
|
RouteAction action = selectedRoute.routeAction();
|
||||||
if (action.getCluster() != null) {
|
if (action.cluster() != null) {
|
||||||
cluster = action.getCluster();
|
cluster = action.cluster();
|
||||||
} else if (action.getWeightedCluster() != null) {
|
} else if (action.weightedClusters() != null) {
|
||||||
int totalWeight = 0;
|
int totalWeight = 0;
|
||||||
for (ClusterWeight weightedCluster : action.getWeightedCluster()) {
|
for (ClusterWeight weightedCluster : action.weightedClusters()) {
|
||||||
totalWeight += weightedCluster.getWeight();
|
totalWeight += weightedCluster.weight();
|
||||||
}
|
}
|
||||||
int select = random.nextInt(totalWeight);
|
int select = random.nextInt(totalWeight);
|
||||||
int accumulator = 0;
|
int accumulator = 0;
|
||||||
for (ClusterWeight weightedCluster : action.getWeightedCluster()) {
|
for (ClusterWeight weightedCluster : action.weightedClusters()) {
|
||||||
accumulator += weightedCluster.getWeight();
|
accumulator += weightedCluster.weight();
|
||||||
if (select < accumulator) {
|
if (select < accumulator) {
|
||||||
cluster = weightedCluster.getName();
|
cluster = weightedCluster.name();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +356,7 @@ final class XdsNameResolver extends NameResolver {
|
||||||
// TODO(chengyuanzhang): avoid service config generation and parsing for each call.
|
// TODO(chengyuanzhang): avoid service config generation and parsing for each call.
|
||||||
Map<String, ?> rawServiceConfig = Collections.emptyMap();
|
Map<String, ?> rawServiceConfig = Collections.emptyMap();
|
||||||
if (enableTimeout) {
|
if (enableTimeout) {
|
||||||
Long timeoutNano = selectedRoute.getRouteAction().getTimeoutNano();
|
Long timeoutNano = selectedRoute.routeAction().timeoutNano();
|
||||||
if (timeoutNano == null) {
|
if (timeoutNano == null) {
|
||||||
timeoutNano = routingConfig.fallbackTimeoutNano;
|
timeoutNano = routingConfig.fallbackTimeoutNano;
|
||||||
}
|
}
|
||||||
|
|
@ -442,6 +446,76 @@ final class XdsNameResolver extends NameResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static boolean matchRoute(RouteMatch routeMatch, String fullMethodName,
|
||||||
|
Map<String, Iterable<String>> headers, ThreadSafeRandom random) {
|
||||||
|
if (!matchPath(routeMatch.pathMatcher(), fullMethodName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (HeaderMatcher headerMatcher : routeMatch.headerMatchers()) {
|
||||||
|
Iterable<String> headerValues = headers.get(headerMatcher.name());
|
||||||
|
// Special cases for hiding headers: "grpc-previous-rpc-attempts".
|
||||||
|
if (headerMatcher.name().equals("grpc-previous-rpc-attempts")) {
|
||||||
|
headerValues = null;
|
||||||
|
}
|
||||||
|
// Special case for exposing headers: "content-type".
|
||||||
|
if (headerMatcher.name().equals("content-type")) {
|
||||||
|
headerValues = Collections.singletonList("application/grpc");
|
||||||
|
}
|
||||||
|
if (!matchHeader(headerMatcher, headerValues)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FractionMatcher fraction = routeMatch.fractionMatcher();
|
||||||
|
return fraction == null || random.nextInt(fraction.denominator()) < fraction.numerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static boolean matchPath(PathMatcher pathMatcher, String fullMethodName) {
|
||||||
|
if (pathMatcher.path() != null) {
|
||||||
|
return pathMatcher.caseSensitive()
|
||||||
|
? pathMatcher.path().equals(fullMethodName)
|
||||||
|
: pathMatcher.path().equalsIgnoreCase(fullMethodName);
|
||||||
|
} else if (pathMatcher.prefix() != null) {
|
||||||
|
return pathMatcher.caseSensitive()
|
||||||
|
? fullMethodName.startsWith(pathMatcher.prefix())
|
||||||
|
: fullMethodName.toLowerCase().startsWith(pathMatcher.prefix().toLowerCase());
|
||||||
|
}
|
||||||
|
return pathMatcher.regEx().matches(fullMethodName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static boolean matchHeader(HeaderMatcher headerMatcher,
|
||||||
|
@Nullable Iterable<String> headerValues) {
|
||||||
|
if (headerMatcher.present() != null) {
|
||||||
|
return (headerValues == null) == headerMatcher.present().equals(headerMatcher.inverted());
|
||||||
|
}
|
||||||
|
if (headerValues == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String valueStr = Joiner.on(",").join(headerValues);
|
||||||
|
boolean baseMatch;
|
||||||
|
if (headerMatcher.exactValue() != null) {
|
||||||
|
baseMatch = headerMatcher.exactValue().equals(valueStr);
|
||||||
|
} else if (headerMatcher.safeRegEx() != null) {
|
||||||
|
baseMatch = headerMatcher.safeRegEx().matches(valueStr);
|
||||||
|
} else if (headerMatcher.range() != null) {
|
||||||
|
long numValue;
|
||||||
|
try {
|
||||||
|
numValue = Long.parseLong(valueStr);
|
||||||
|
baseMatch = numValue >= headerMatcher.range().start()
|
||||||
|
&& numValue <= headerMatcher.range().end();
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
baseMatch = false;
|
||||||
|
}
|
||||||
|
} else if (headerMatcher.prefix() != null) {
|
||||||
|
baseMatch = valueStr.startsWith(headerMatcher.prefix());
|
||||||
|
} else {
|
||||||
|
baseMatch = valueStr.endsWith(headerMatcher.suffix());
|
||||||
|
}
|
||||||
|
return baseMatch != headerMatcher.inverted();
|
||||||
|
}
|
||||||
|
|
||||||
private class ResolveState implements LdsResourceWatcher {
|
private class ResolveState implements LdsResourceWatcher {
|
||||||
private final ConfigOrError emptyServiceConfig =
|
private final ConfigOrError emptyServiceConfig =
|
||||||
serviceConfigParser.parseServiceConfig(Collections.<String, Object>emptyMap());
|
serviceConfigParser.parseServiceConfig(Collections.<String, Object>emptyMap());
|
||||||
|
|
@ -537,15 +611,15 @@ final class XdsNameResolver extends NameResolver {
|
||||||
listener.onResult(emptyResult);
|
listener.onResult(emptyResult);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<Route> routes = virtualHost.getRoutes();
|
List<Route> routes = virtualHost.routes();
|
||||||
Set<String> clusters = new HashSet<>();
|
Set<String> clusters = new HashSet<>();
|
||||||
for (Route route : routes) {
|
for (Route route : routes) {
|
||||||
RouteAction action = route.getRouteAction();
|
RouteAction action = route.routeAction();
|
||||||
if (action.getCluster() != null) {
|
if (action.cluster() != null) {
|
||||||
clusters.add(action.getCluster());
|
clusters.add(action.cluster());
|
||||||
} else if (action.getWeightedCluster() != null) {
|
} else if (action.weightedClusters() != null) {
|
||||||
for (ClusterWeight weighedCluster : action.getWeightedCluster()) {
|
for (ClusterWeight weighedCluster : action.weightedClusters()) {
|
||||||
clusters.add(weighedCluster.getName());
|
clusters.add(weighedCluster.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ import io.grpc.internal.GrpcUtil;
|
||||||
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
|
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
|
||||||
import io.grpc.xds.Bootstrapper.BootstrapInfo;
|
import io.grpc.xds.Bootstrapper.BootstrapInfo;
|
||||||
import io.grpc.xds.Bootstrapper.ServerInfo;
|
import io.grpc.xds.Bootstrapper.ServerInfo;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Node;
|
import io.grpc.xds.EnvoyProtoData.Node;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -118,7 +117,7 @@ public class BootstrapperImplTest {
|
||||||
getNodeBuilder()
|
getNodeBuilder()
|
||||||
.setId("ENVOY_NODE_ID")
|
.setId("ENVOY_NODE_ID")
|
||||||
.setCluster("ENVOY_CLUSTER")
|
.setCluster("ENVOY_CLUSTER")
|
||||||
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
|
.setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
|
||||||
.setMetadata(
|
.setMetadata(
|
||||||
ImmutableMap.of(
|
ImmutableMap.of(
|
||||||
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
||||||
|
|
@ -176,7 +175,7 @@ public class BootstrapperImplTest {
|
||||||
getNodeBuilder()
|
getNodeBuilder()
|
||||||
.setId("ENVOY_NODE_ID")
|
.setId("ENVOY_NODE_ID")
|
||||||
.setCluster("ENVOY_CLUSTER")
|
.setCluster("ENVOY_CLUSTER")
|
||||||
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
|
.setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
|
||||||
.setMetadata(
|
.setMetadata(
|
||||||
ImmutableMap.of(
|
ImmutableMap.of(
|
||||||
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
||||||
|
|
@ -224,7 +223,7 @@ public class BootstrapperImplTest {
|
||||||
getNodeBuilder()
|
getNodeBuilder()
|
||||||
.setId("ENVOY_NODE_ID")
|
.setId("ENVOY_NODE_ID")
|
||||||
.setCluster("ENVOY_CLUSTER")
|
.setCluster("ENVOY_CLUSTER")
|
||||||
.setLocality(new Locality("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
|
.setLocality(Locality.create("ENVOY_REGION", "ENVOY_ZONE", "ENVOY_SUBZONE"))
|
||||||
.setMetadata(
|
.setMetadata(
|
||||||
ImmutableMap.of(
|
ImmutableMap.of(
|
||||||
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
"TRAFFICDIRECTOR_INTERCEPTION_PORT",
|
||||||
|
|
|
||||||
|
|
@ -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.internal.FakeClock.TaskFilter;
|
||||||
import io.grpc.testing.GrpcCleanupRule;
|
import io.grpc.testing.GrpcCleanupRule;
|
||||||
import io.grpc.xds.AbstractXdsClient.ResourceType;
|
import io.grpc.xds.AbstractXdsClient.ResourceType;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyProtoData.HttpFault;
|
import io.grpc.xds.Endpoints.LbEndpoint;
|
||||||
import io.grpc.xds.EnvoyProtoData.LbEndpoint;
|
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Node;
|
import io.grpc.xds.EnvoyProtoData.Node;
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||||
|
|
@ -374,18 +372,18 @@ public abstract class ClientXdsClientTestBase {
|
||||||
assertThat(ldsUpdate.virtualHosts).hasSize(2);
|
assertThat(ldsUpdate.virtualHosts).hasSize(2);
|
||||||
assertThat(ldsUpdate.hasFaultInjection).isTrue();
|
assertThat(ldsUpdate.hasFaultInjection).isTrue();
|
||||||
assertThat(ldsUpdate.httpFault).isNull();
|
assertThat(ldsUpdate.httpFault).isNull();
|
||||||
HttpFault httpFault = ldsUpdate.virtualHosts.get(0).getHttpFault();
|
HttpFault httpFault = ldsUpdate.virtualHosts.get(0).httpFault();
|
||||||
assertThat(httpFault.faultDelay.delayNanos).isEqualTo(300);
|
assertThat(httpFault.faultDelay().delayNanos()).isEqualTo(300);
|
||||||
assertThat(httpFault.faultDelay.ratePerMillion).isEqualTo(1000);
|
assertThat(httpFault.faultDelay().ratePerMillion()).isEqualTo(1000);
|
||||||
assertThat(httpFault.faultAbort).isNull();
|
assertThat(httpFault.faultAbort()).isNull();
|
||||||
assertThat(httpFault.upstreamCluster).isEqualTo("cluster1");
|
assertThat(httpFault.upstreamCluster()).isEqualTo("cluster1");
|
||||||
assertThat(httpFault.maxActiveFaults).isEqualTo(100);
|
assertThat(httpFault.maxActiveFaults()).isEqualTo(100);
|
||||||
httpFault = ldsUpdate.virtualHosts.get(1).getHttpFault();
|
httpFault = ldsUpdate.virtualHosts.get(1).httpFault();
|
||||||
assertThat(httpFault.faultDelay).isNull();
|
assertThat(httpFault.faultDelay()).isNull();
|
||||||
assertThat(httpFault.faultAbort.status.getCode()).isEqualTo(Status.Code.UNAVAILABLE);
|
assertThat(httpFault.faultAbort().status().getCode()).isEqualTo(Status.Code.UNAVAILABLE);
|
||||||
assertThat(httpFault.faultAbort.ratePerMillion).isEqualTo(2000);
|
assertThat(httpFault.faultAbort().ratePerMillion()).isEqualTo(2000);
|
||||||
assertThat(httpFault.upstreamCluster).isEqualTo("cluster2");
|
assertThat(httpFault.upstreamCluster()).isEqualTo("cluster2");
|
||||||
assertThat(httpFault.maxActiveFaults).isEqualTo(101);
|
assertThat(httpFault.maxActiveFaults()).isEqualTo(101);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -942,17 +940,17 @@ public abstract class ClientXdsClientTestBase {
|
||||||
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
||||||
assertThat(edsUpdate.dropPolicies)
|
assertThat(edsUpdate.dropPolicies)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new DropOverload("lb", 200),
|
DropOverload.create("lb", 200),
|
||||||
new DropOverload("throttle", 1000));
|
DropOverload.create("throttle", 1000));
|
||||||
assertThat(edsUpdate.localityLbEndpointsMap)
|
assertThat(edsUpdate.localityLbEndpointsMap)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new Locality("region1", "zone1", "subzone1"),
|
Locality.create("region1", "zone1", "subzone1"),
|
||||||
new LocalityLbEndpoints(
|
LocalityLbEndpoints.create(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new LbEndpoint("192.168.0.1", 8080,
|
LbEndpoint.create("192.168.0.1", 8080,
|
||||||
2, true)), 1, 0),
|
2, true)), 1, 0),
|
||||||
new Locality("region3", "zone3", "subzone3"),
|
Locality.create("region3", "zone3", "subzone3"),
|
||||||
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
|
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -991,17 +989,17 @@ public abstract class ClientXdsClientTestBase {
|
||||||
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
||||||
assertThat(edsUpdate.dropPolicies)
|
assertThat(edsUpdate.dropPolicies)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new DropOverload("lb", 200),
|
DropOverload.create("lb", 200),
|
||||||
new DropOverload("throttle", 1000));
|
DropOverload.create("throttle", 1000));
|
||||||
assertThat(edsUpdate.localityLbEndpointsMap)
|
assertThat(edsUpdate.localityLbEndpointsMap)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new Locality("region1", "zone1", "subzone1"),
|
Locality.create("region1", "zone1", "subzone1"),
|
||||||
new LocalityLbEndpoints(
|
LocalityLbEndpoints.create(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new LbEndpoint("192.168.0.1", 8080,
|
LbEndpoint.create("192.168.0.1", 8080,
|
||||||
2, true)), 1, 0),
|
2, true)), 1, 0),
|
||||||
new Locality("region3", "zone3", "subzone3"),
|
Locality.create("region3", "zone3", "subzone3"),
|
||||||
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
|
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
|
||||||
call.verifyNoMoreRequest();
|
call.verifyNoMoreRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1050,16 +1048,16 @@ public abstract class ClientXdsClientTestBase {
|
||||||
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
||||||
assertThat(edsUpdate.dropPolicies)
|
assertThat(edsUpdate.dropPolicies)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new DropOverload("lb", 200),
|
DropOverload.create("lb", 200),
|
||||||
new DropOverload("throttle", 1000));
|
DropOverload.create("throttle", 1000));
|
||||||
assertThat(edsUpdate.localityLbEndpointsMap)
|
assertThat(edsUpdate.localityLbEndpointsMap)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new Locality("region1", "zone1", "subzone1"),
|
Locality.create("region1", "zone1", "subzone1"),
|
||||||
new LocalityLbEndpoints(
|
LocalityLbEndpoints.create(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0),
|
LbEndpoint.create("192.168.0.1", 8080, 2, true)), 1, 0),
|
||||||
new Locality("region3", "zone3", "subzone3"),
|
Locality.create("region3", "zone3", "subzone3"),
|
||||||
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
|
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
|
||||||
|
|
||||||
clusterLoadAssignments =
|
clusterLoadAssignments =
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
|
|
@ -1079,10 +1077,10 @@ public abstract class ClientXdsClientTestBase {
|
||||||
assertThat(edsUpdate.dropPolicies).isEmpty();
|
assertThat(edsUpdate.dropPolicies).isEmpty();
|
||||||
assertThat(edsUpdate.localityLbEndpointsMap)
|
assertThat(edsUpdate.localityLbEndpointsMap)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new Locality("region2", "zone2", "subzone2"),
|
Locality.create("region2", "zone2", "subzone2"),
|
||||||
new LocalityLbEndpoints(
|
LocalityLbEndpoints.create(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new LbEndpoint("172.44.2.2", 8000, 3, true)), 2, 0));
|
LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -1187,16 +1185,16 @@ public abstract class ClientXdsClientTestBase {
|
||||||
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
assertThat(edsUpdate.clusterName).isEqualTo(EDS_RESOURCE);
|
||||||
assertThat(edsUpdate.dropPolicies)
|
assertThat(edsUpdate.dropPolicies)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new DropOverload("lb", 200),
|
DropOverload.create("lb", 200),
|
||||||
new DropOverload("throttle", 1000));
|
DropOverload.create("throttle", 1000));
|
||||||
assertThat(edsUpdate.localityLbEndpointsMap)
|
assertThat(edsUpdate.localityLbEndpointsMap)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new Locality("region1", "zone1", "subzone1"),
|
Locality.create("region1", "zone1", "subzone1"),
|
||||||
new LocalityLbEndpoints(
|
LocalityLbEndpoints.create(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new LbEndpoint("192.168.0.1", 8080, 2, true)), 1, 0),
|
LbEndpoint.create("192.168.0.1", 8080, 2, true)), 1, 0),
|
||||||
new Locality("region3", "zone3", "subzone3"),
|
Locality.create("region3", "zone3", "subzone3"),
|
||||||
new LocalityLbEndpoints(ImmutableList.<LbEndpoint>of(), 2, 1));
|
LocalityLbEndpoints.create(ImmutableList.<LbEndpoint>of(), 2, 1));
|
||||||
verifyNoMoreInteractions(watcher1, watcher2);
|
verifyNoMoreInteractions(watcher1, watcher2);
|
||||||
|
|
||||||
clusterLoadAssignments =
|
clusterLoadAssignments =
|
||||||
|
|
@ -1217,20 +1215,20 @@ public abstract class ClientXdsClientTestBase {
|
||||||
assertThat(edsUpdate.dropPolicies).isEmpty();
|
assertThat(edsUpdate.dropPolicies).isEmpty();
|
||||||
assertThat(edsUpdate.localityLbEndpointsMap)
|
assertThat(edsUpdate.localityLbEndpointsMap)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new Locality("region2", "zone2", "subzone2"),
|
Locality.create("region2", "zone2", "subzone2"),
|
||||||
new LocalityLbEndpoints(
|
LocalityLbEndpoints.create(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new LbEndpoint("172.44.2.2", 8000, 3, true)), 2, 0));
|
LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0));
|
||||||
verify(watcher2).onChanged(edsUpdateCaptor.capture());
|
verify(watcher2).onChanged(edsUpdateCaptor.capture());
|
||||||
edsUpdate = edsUpdateCaptor.getValue();
|
edsUpdate = edsUpdateCaptor.getValue();
|
||||||
assertThat(edsUpdate.clusterName).isEqualTo(edsResource);
|
assertThat(edsUpdate.clusterName).isEqualTo(edsResource);
|
||||||
assertThat(edsUpdate.dropPolicies).isEmpty();
|
assertThat(edsUpdate.dropPolicies).isEmpty();
|
||||||
assertThat(edsUpdate.localityLbEndpointsMap)
|
assertThat(edsUpdate.localityLbEndpointsMap)
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
new Locality("region2", "zone2", "subzone2"),
|
Locality.create("region2", "zone2", "subzone2"),
|
||||||
new LocalityLbEndpoints(
|
LocalityLbEndpoints.create(
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new LbEndpoint("172.44.2.2", 8000, 3, true)), 2, 0));
|
LbEndpoint.create("172.44.2.2", 8000, 3, true)), 2, 0));
|
||||||
verifyNoMoreInteractions(edsResourceWatcher);
|
verifyNoMoreInteractions(edsResourceWatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,14 +45,13 @@ import io.grpc.internal.FakeClock;
|
||||||
import io.grpc.internal.ObjectPool;
|
import io.grpc.internal.ObjectPool;
|
||||||
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
|
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
|
||||||
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
|
|
||||||
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
||||||
|
import io.grpc.xds.Stats.ClusterStats;
|
||||||
|
import io.grpc.xds.Stats.UpstreamLocalityStats;
|
||||||
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
|
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedPolicySelection;
|
||||||
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
|
import io.grpc.xds.WeightedTargetLoadBalancerProvider.WeightedTargetConfig;
|
||||||
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
|
import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider;
|
||||||
|
|
@ -97,7 +96,7 @@ public class ClusterImplLoadBalancerTest {
|
||||||
});
|
});
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
private final Locality locality =
|
private final Locality locality =
|
||||||
new Locality("test-region", "test-zone", "test-subzone");
|
Locality.create("test-region", "test-zone", "test-subzone");
|
||||||
private final PolicySelection roundRobin =
|
private final PolicySelection roundRobin =
|
||||||
new PolicySelection(new FakeLoadBalancerProvider("round_robin"), null);
|
new PolicySelection(new FakeLoadBalancerProvider("round_robin"), null);
|
||||||
private final List<FakeLoadBalancer> downstreamBalancers = new ArrayList<>();
|
private final List<FakeLoadBalancer> downstreamBalancers = new ArrayList<>();
|
||||||
|
|
@ -219,27 +218,27 @@ public class ClusterImplLoadBalancerTest {
|
||||||
ClusterStats clusterStats =
|
ClusterStats clusterStats =
|
||||||
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
UpstreamLocalityStats localityStats =
|
UpstreamLocalityStats localityStats =
|
||||||
Iterables.getOnlyElement(clusterStats.getUpstreamLocalityStatsList());
|
Iterables.getOnlyElement(clusterStats.upstreamLocalityStatsList());
|
||||||
assertThat(localityStats.getLocality()).isEqualTo(locality);
|
assertThat(localityStats.locality()).isEqualTo(locality);
|
||||||
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(3L);
|
assertThat(localityStats.totalIssuedRequests()).isEqualTo(3L);
|
||||||
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L);
|
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
|
||||||
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L);
|
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
|
||||||
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(1L);
|
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(1L);
|
||||||
|
|
||||||
streamTracer3.streamClosed(Status.OK);
|
streamTracer3.streamClosed(Status.OK);
|
||||||
subchannel.shutdown(); // stats recorder released
|
subchannel.shutdown(); // stats recorder released
|
||||||
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
// Locality load is reported for one last time in case of loads occurred since the previous
|
// Locality load is reported for one last time in case of loads occurred since the previous
|
||||||
// load report.
|
// load report.
|
||||||
localityStats = Iterables.getOnlyElement(clusterStats.getUpstreamLocalityStatsList());
|
localityStats = Iterables.getOnlyElement(clusterStats.upstreamLocalityStatsList());
|
||||||
assertThat(localityStats.getLocality()).isEqualTo(locality);
|
assertThat(localityStats.locality()).isEqualTo(locality);
|
||||||
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L);
|
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
|
||||||
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(0L);
|
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(0L);
|
||||||
|
|
||||||
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getUpstreamLocalityStatsList()).isEmpty(); // no longer reported
|
assertThat(clusterStats.upstreamLocalityStatsList()).isEmpty(); // no longer reported
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -248,7 +247,7 @@ public class ClusterImplLoadBalancerTest {
|
||||||
WeightedTargetConfig weightedTargetConfig =
|
WeightedTargetConfig weightedTargetConfig =
|
||||||
buildWeightedTargetConfig(ImmutableMap.of(locality, 10));
|
buildWeightedTargetConfig(ImmutableMap.of(locality, 10));
|
||||||
ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME,
|
ClusterImplConfig config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME,
|
||||||
null, Collections.singletonList(new DropOverload("throttle", 500_000)),
|
null, Collections.singletonList(DropOverload.create("throttle", 500_000)),
|
||||||
new PolicySelection(weightedTargetProvider, weightedTargetConfig), null);
|
new PolicySelection(weightedTargetProvider, weightedTargetConfig), null);
|
||||||
EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality);
|
EquivalentAddressGroup endpoint = makeAddress("endpoint-addr", locality);
|
||||||
deliverAddressesAndConfig(Collections.singletonList(endpoint), config);
|
deliverAddressesAndConfig(Collections.singletonList(endpoint), config);
|
||||||
|
|
@ -268,16 +267,16 @@ public class ClusterImplLoadBalancerTest {
|
||||||
assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: throttle");
|
assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: throttle");
|
||||||
ClusterStats clusterStats =
|
ClusterStats clusterStats =
|
||||||
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getCategory())
|
assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).category())
|
||||||
.isEqualTo("throttle");
|
.isEqualTo("throttle");
|
||||||
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getDroppedCount())
|
assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).droppedCount())
|
||||||
.isEqualTo(1L);
|
.isEqualTo(1L);
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
|
||||||
|
|
||||||
// Config update updates drop policies.
|
// Config update updates drop policies.
|
||||||
config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, null,
|
config = new ClusterImplConfig(CLUSTER, EDS_SERVICE_NAME, LRS_SERVER_NAME, null,
|
||||||
Collections.singletonList(new DropOverload("lb", 1_000_000)),
|
Collections.singletonList(DropOverload.create("lb", 1_000_000)),
|
||||||
new PolicySelection(weightedTargetProvider, weightedTargetConfig), null);
|
new PolicySelection(weightedTargetProvider, weightedTargetConfig), null);
|
||||||
loadBalancer.handleResolvedAddresses(
|
loadBalancer.handleResolvedAddresses(
|
||||||
ResolvedAddresses.newBuilder()
|
ResolvedAddresses.newBuilder()
|
||||||
|
|
@ -294,12 +293,12 @@ public class ClusterImplLoadBalancerTest {
|
||||||
assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: lb");
|
assertThat(result.getStatus().getDescription()).isEqualTo("Dropped: lb");
|
||||||
clusterStats =
|
clusterStats =
|
||||||
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getCategory())
|
assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).category())
|
||||||
.isEqualTo("lb");
|
.isEqualTo("lb");
|
||||||
assertThat(Iterables.getOnlyElement(clusterStats.getDroppedRequestsList()).getDroppedCount())
|
assertThat(Iterables.getOnlyElement(clusterStats.droppedRequestsList()).droppedCount())
|
||||||
.isEqualTo(1L);
|
.isEqualTo(1L);
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
|
||||||
|
|
||||||
result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
|
result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
|
||||||
assertThat(result.getStatus().isOk()).isTrue();
|
assertThat(result.getStatus().isOk()).isTrue();
|
||||||
|
|
@ -346,21 +345,21 @@ public class ClusterImplLoadBalancerTest {
|
||||||
}
|
}
|
||||||
ClusterStats clusterStats =
|
ClusterStats clusterStats =
|
||||||
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
|
||||||
|
|
||||||
PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
|
PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
|
||||||
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
if (enableCircuitBreaking) {
|
if (enableCircuitBreaking) {
|
||||||
assertThat(result.getStatus().isOk()).isFalse();
|
assertThat(result.getStatus().isOk()).isFalse();
|
||||||
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
|
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
|
||||||
assertThat(result.getStatus().getDescription())
|
assertThat(result.getStatus().getDescription())
|
||||||
.isEqualTo("Cluster max concurrent requests limit exceeded");
|
.isEqualTo("Cluster max concurrent requests limit exceeded");
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
|
||||||
} else {
|
} else {
|
||||||
assertThat(result.getStatus().isOk()).isTrue();
|
assertThat(result.getStatus().isOk()).isTrue();
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config update increments circuit breakers max_concurrent_requests threshold.
|
// Config update increments circuit breakers max_concurrent_requests threshold.
|
||||||
|
|
@ -375,21 +374,21 @@ public class ClusterImplLoadBalancerTest {
|
||||||
result.getStreamTracerFactory().newClientStreamTracer(
|
result.getStreamTracerFactory().newClientStreamTracer(
|
||||||
ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // 101th request
|
ClientStreamTracer.StreamInfo.newBuilder().build(), new Metadata()); // 101th request
|
||||||
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
|
||||||
|
|
||||||
result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class)); // 102th request
|
result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class)); // 102th request
|
||||||
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
if (enableCircuitBreaking) {
|
if (enableCircuitBreaking) {
|
||||||
assertThat(result.getStatus().isOk()).isFalse();
|
assertThat(result.getStatus().isOk()).isFalse();
|
||||||
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
|
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
|
||||||
assertThat(result.getStatus().getDescription())
|
assertThat(result.getStatus().getDescription())
|
||||||
.isEqualTo("Cluster max concurrent requests limit exceeded");
|
.isEqualTo("Cluster max concurrent requests limit exceeded");
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
|
||||||
} else {
|
} else {
|
||||||
assertThat(result.getStatus().isOk()).isTrue();
|
assertThat(result.getStatus().isOk()).isTrue();
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -434,21 +433,21 @@ public class ClusterImplLoadBalancerTest {
|
||||||
}
|
}
|
||||||
ClusterStats clusterStats =
|
ClusterStats clusterStats =
|
||||||
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
|
||||||
|
|
||||||
PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
|
PickResult result = currentPicker.pickSubchannel(mock(PickSubchannelArgs.class));
|
||||||
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
clusterStats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER));
|
||||||
assertThat(clusterStats.getClusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
assertThat(clusterStats.clusterServiceName()).isEqualTo(EDS_SERVICE_NAME);
|
||||||
if (enableCircuitBreaking) {
|
if (enableCircuitBreaking) {
|
||||||
assertThat(result.getStatus().isOk()).isFalse();
|
assertThat(result.getStatus().isOk()).isFalse();
|
||||||
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
|
assertThat(result.getStatus().getCode()).isEqualTo(Code.UNAVAILABLE);
|
||||||
assertThat(result.getStatus().getDescription())
|
assertThat(result.getStatus().getDescription())
|
||||||
.isEqualTo("Cluster max concurrent requests limit exceeded");
|
.isEqualTo("Cluster max concurrent requests limit exceeded");
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(1L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(1L);
|
||||||
} else {
|
} else {
|
||||||
assertThat(result.getStatus().isOk()).isTrue();
|
assertThat(result.getStatus().isOk()).isTrue();
|
||||||
assertThat(clusterStats.getTotalDroppedRequests()).isEqualTo(0L);
|
assertThat(clusterStats.totalDroppedRequests()).isEqualTo(0L);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,9 @@ import io.grpc.internal.ServiceConfigUtil.PolicySelection;
|
||||||
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
import io.grpc.xds.ClusterImplLoadBalancerProvider.ClusterImplConfig;
|
||||||
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
|
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig;
|
||||||
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
|
import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism;
|
||||||
import io.grpc.xds.EnvoyProtoData.DropOverload;
|
import io.grpc.xds.Endpoints.DropOverload;
|
||||||
import io.grpc.xds.EnvoyProtoData.LbEndpoint;
|
import io.grpc.xds.Endpoints.LbEndpoint;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
||||||
import io.grpc.xds.EnvoyProtoData.LocalityLbEndpoints;
|
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
|
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig;
|
||||||
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
|
import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig;
|
||||||
|
|
@ -104,11 +103,11 @@ public class ClusterResolverLoadBalancerTest {
|
||||||
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
|
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
|
||||||
private static final String LRS_SERVER_NAME = "lrs.googleapis.com";
|
private static final String LRS_SERVER_NAME = "lrs.googleapis.com";
|
||||||
private final Locality locality1 =
|
private final Locality locality1 =
|
||||||
new Locality("test-region-1", "test-zone-1", "test-subzone-1");
|
Locality.create("test-region-1", "test-zone-1", "test-subzone-1");
|
||||||
private final Locality locality2 =
|
private final Locality locality2 =
|
||||||
new Locality("test-region-2", "test-zone-2", "test-subzone-2");
|
Locality.create("test-region-2", "test-zone-2", "test-subzone-2");
|
||||||
private final Locality locality3 =
|
private final Locality locality3 =
|
||||||
new Locality("test-region-3", "test-zone-3", "test-subzone-3");
|
Locality.create("test-region-3", "test-zone-3", "test-subzone-3");
|
||||||
private final UpstreamTlsContext tlsContext =
|
private final UpstreamTlsContext tlsContext =
|
||||||
CommonTlsContextTestsUtil.buildUpstreamTlsContextFromFilenames(
|
CommonTlsContextTestsUtil.buildUpstreamTlsContextFromFilenames(
|
||||||
CommonTlsContextTestsUtil.CLIENT_KEY_FILE,
|
CommonTlsContextTestsUtil.CLIENT_KEY_FILE,
|
||||||
|
|
@ -580,7 +579,7 @@ public class ClusterResolverLoadBalancerTest {
|
||||||
Collections.singletonList(endpoint3));
|
Collections.singletonList(endpoint3));
|
||||||
assertAddressesEqual(AddressFilter.filter(AddressFilter.filter(
|
assertAddressesEqual(AddressFilter.filter(AddressFilter.filter(
|
||||||
childBalancer.addresses, CLUSTER_DNS + "[priority0]"),
|
childBalancer.addresses, CLUSTER_DNS + "[priority0]"),
|
||||||
new Locality("", "", "").toString()),
|
Locality.create("", "", "").toString()),
|
||||||
Arrays.asList(endpoint1, endpoint2));
|
Arrays.asList(endpoint1, endpoint2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -769,9 +768,9 @@ public class ClusterResolverLoadBalancerTest {
|
||||||
List<LbEndpoint> endpoints = new ArrayList<>();
|
List<LbEndpoint> endpoints = new ArrayList<>();
|
||||||
for (EquivalentAddressGroup addr : managedEndpoints.keySet()) {
|
for (EquivalentAddressGroup addr : managedEndpoints.keySet()) {
|
||||||
boolean status = managedEndpoints.get(addr);
|
boolean status = managedEndpoints.get(addr);
|
||||||
endpoints.add(new LbEndpoint(addr, 100 /* unused */, status));
|
endpoints.add(LbEndpoint.create(addr, 100 /* unused */, status));
|
||||||
}
|
}
|
||||||
return new LocalityLbEndpoints(endpoints, localityWeight, priority);
|
return LocalityLbEndpoints.create(endpoints, localityWeight, priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EquivalentAddressGroup makeAddress(final String name) {
|
private static EquivalentAddressGroup makeAddress(final String name) {
|
||||||
|
|
|
||||||
|
|
@ -19,38 +19,10 @@ package io.grpc.xds;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.testing.EqualsTester;
|
|
||||||
import com.google.protobuf.BoolValue;
|
|
||||||
import com.google.protobuf.Struct;
|
import com.google.protobuf.Struct;
|
||||||
import com.google.protobuf.UInt32Value;
|
|
||||||
import com.google.protobuf.Value;
|
import com.google.protobuf.Value;
|
||||||
import com.google.protobuf.util.Durations;
|
|
||||||
import com.google.re2j.Pattern;
|
|
||||||
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
|
|
||||||
import io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher;
|
|
||||||
import io.envoyproxy.envoy.config.route.v3.RedirectAction;
|
|
||||||
import io.envoyproxy.envoy.config.route.v3.RouteAction.MaxStreamDuration;
|
|
||||||
import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
|
|
||||||
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
|
|
||||||
import io.envoyproxy.envoy.type.v3.FractionalPercent;
|
|
||||||
import io.envoyproxy.envoy.type.v3.Int64Range;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Address;
|
import io.grpc.xds.EnvoyProtoData.Address;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterWeight;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.EndpointLoadMetricStats;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Node;
|
import io.grpc.xds.EnvoyProtoData.Node;
|
||||||
import io.grpc.xds.EnvoyProtoData.Route;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.RouteAction;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.StructOrError;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
|
|
||||||
import io.grpc.xds.RouteMatch.FractionMatcher;
|
|
||||||
import io.grpc.xds.RouteMatch.HeaderMatcher;
|
|
||||||
import io.grpc.xds.RouteMatch.PathMatcher;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.JUnit4;
|
import org.junit.runners.JUnit4;
|
||||||
|
|
@ -61,41 +33,6 @@ import org.junit.runners.JUnit4;
|
||||||
@RunWith(JUnit4.class)
|
@RunWith(JUnit4.class)
|
||||||
public class EnvoyProtoDataTest {
|
public class EnvoyProtoDataTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
public void locality_convertToAndFromLocalityProto() {
|
|
||||||
io.envoyproxy.envoy.config.core.v3.Locality locality =
|
|
||||||
io.envoyproxy.envoy.config.core.v3.Locality.newBuilder()
|
|
||||||
.setRegion("test_region")
|
|
||||||
.setZone("test_zone")
|
|
||||||
.setSubZone("test_subzone")
|
|
||||||
.build();
|
|
||||||
Locality xdsLocality = Locality.fromEnvoyProtoLocality(locality);
|
|
||||||
assertThat(xdsLocality.getRegion()).isEqualTo("test_region");
|
|
||||||
assertThat(xdsLocality.getZone()).isEqualTo("test_zone");
|
|
||||||
assertThat(xdsLocality.getSubZone()).isEqualTo("test_subzone");
|
|
||||||
|
|
||||||
io.envoyproxy.envoy.api.v2.core.Locality convertedLocality =
|
|
||||||
xdsLocality.toEnvoyProtoLocalityV2();
|
|
||||||
assertThat(convertedLocality.getRegion()).isEqualTo("test_region");
|
|
||||||
assertThat(convertedLocality.getZone()).isEqualTo("test_zone");
|
|
||||||
assertThat(convertedLocality.getSubZone()).isEqualTo("test_subzone");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void locality_equal() {
|
|
||||||
new EqualsTester()
|
|
||||||
.addEqualityGroup(
|
|
||||||
new Locality("region-a", "zone-a", "subzone-a"),
|
|
||||||
new Locality("region-a", "zone-a", "subzone-a"))
|
|
||||||
.addEqualityGroup(
|
|
||||||
new Locality("region", "zone", "subzone")
|
|
||||||
)
|
|
||||||
.addEqualityGroup(
|
|
||||||
new Locality("", "", ""),
|
|
||||||
new Locality("", "", ""))
|
|
||||||
.testEquals();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Test
|
@Test
|
||||||
public void convertNode() {
|
public void convertNode() {
|
||||||
|
|
@ -108,7 +45,7 @@ public class EnvoyProtoDataTest {
|
||||||
"ENVOY_PORT",
|
"ENVOY_PORT",
|
||||||
"TRAFFICDIRECTOR_NETWORK_NAME",
|
"TRAFFICDIRECTOR_NETWORK_NAME",
|
||||||
"VPC_NETWORK_NAME"))
|
"VPC_NETWORK_NAME"))
|
||||||
.setLocality(new Locality("region", "zone", "subzone"))
|
.setLocality(Locality.create("region", "zone", "subzone"))
|
||||||
.addListeningAddresses(new Address("www.foo.com", 8080))
|
.addListeningAddresses(new Address("www.foo.com", 8080))
|
||||||
.addListeningAddresses(new Address("www.bar.com", 8088))
|
.addListeningAddresses(new Address("www.bar.com", 8088))
|
||||||
.setBuildVersion("v1")
|
.setBuildVersion("v1")
|
||||||
|
|
@ -185,448 +122,4 @@ public class EnvoyProtoDataTest {
|
||||||
.build();
|
.build();
|
||||||
assertThat(node.toEnvoyProtoNodeV2()).isEqualTo(nodeProtoV2);
|
assertThat(node.toEnvoyProtoNodeV2()).isEqualTo(nodeProtoV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void locality_hash() {
|
|
||||||
assertThat(new Locality("region", "zone", "subzone").hashCode())
|
|
||||||
.isEqualTo(new Locality("region", "zone","subzone").hashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(chengyuanzhang): add test for other data types.
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRoute() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route proto1 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
|
|
||||||
.setName("route-blade")
|
|
||||||
.setMatch(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.setPath("/service/method"))
|
|
||||||
.setRoute(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setCluster("cluster-foo"))
|
|
||||||
.build();
|
|
||||||
StructOrError<Route> struct1 = Route.fromEnvoyProtoRoute(proto1);
|
|
||||||
assertThat(struct1.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct1.getStruct())
|
|
||||||
.isEqualTo(
|
|
||||||
new Route(
|
|
||||||
new RouteMatch(PathMatcher.fromPath("/service/method", false),
|
|
||||||
Collections.<HeaderMatcher>emptyList(), null),
|
|
||||||
new RouteAction(null, "cluster-foo", null)));
|
|
||||||
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route unsupportedProto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
|
|
||||||
.setName("route-blade")
|
|
||||||
.setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath(""))
|
|
||||||
.setRedirect(RedirectAction.getDefaultInstance())
|
|
||||||
.build();
|
|
||||||
StructOrError<Route> unsupportedStruct = Route.fromEnvoyProtoRoute(unsupportedProto);
|
|
||||||
assertThat(unsupportedStruct.getErrorDetail()).isNotNull();
|
|
||||||
assertThat(unsupportedStruct.getStruct()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRoute_skipWithUnsupportedMatcher() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
|
|
||||||
.setName("ignore me")
|
|
||||||
.setMatch(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.setPath("/service/method")
|
|
||||||
.addQueryParameters(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.QueryParameterMatcher
|
|
||||||
.getDefaultInstance()))
|
|
||||||
.setRoute(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setCluster("cluster-foo"))
|
|
||||||
.build();
|
|
||||||
assertThat(Route.fromEnvoyProtoRoute(proto)).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRoute_skipWithUnsupportedAction() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.Route.newBuilder()
|
|
||||||
.setName("ignore me")
|
|
||||||
.setMatch(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.setPath("/service/method"))
|
|
||||||
.setRoute(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setClusterHeader("some cluster header"))
|
|
||||||
.build();
|
|
||||||
assertThat(Route.fromEnvoyProtoRoute(proto)).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteMatch_pathMatching() {
|
|
||||||
// path_specifier = prefix
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch proto1 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix("/").build();
|
|
||||||
StructOrError<RouteMatch> struct1 = Route.convertEnvoyProtoRouteMatch(proto1);
|
|
||||||
assertThat(struct1.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct1.getStruct()).isEqualTo(
|
|
||||||
new RouteMatch(
|
|
||||||
PathMatcher.fromPrefix("/", false), Collections.<HeaderMatcher>emptyList(), null));
|
|
||||||
|
|
||||||
proto1 = proto1.toBuilder().setCaseSensitive(BoolValue.newBuilder().setValue(true)).build();
|
|
||||||
struct1 = Route.convertEnvoyProtoRouteMatch(proto1);
|
|
||||||
assertThat(struct1.getStruct()).isEqualTo(
|
|
||||||
new RouteMatch(
|
|
||||||
PathMatcher.fromPrefix("/", true), Collections.<HeaderMatcher>emptyList(), null));
|
|
||||||
|
|
||||||
// path_specifier = path
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch proto2 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.setPath("/service/method")
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteMatch> struct2 = Route.convertEnvoyProtoRouteMatch(proto2);
|
|
||||||
assertThat(struct2.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct2.getStruct()).isEqualTo(
|
|
||||||
new RouteMatch(
|
|
||||||
PathMatcher.fromPath("/service/method", false),
|
|
||||||
Collections.<HeaderMatcher>emptyList(), null));
|
|
||||||
|
|
||||||
proto2 = proto2.toBuilder().setCaseSensitive(BoolValue.newBuilder().setValue(true)).build();
|
|
||||||
struct2 = Route.convertEnvoyProtoRouteMatch(proto2);
|
|
||||||
assertThat(struct2.getStruct()).isEqualTo(
|
|
||||||
new RouteMatch(
|
|
||||||
PathMatcher.fromPath("/service/method", true),
|
|
||||||
Collections.<HeaderMatcher>emptyList(), null));
|
|
||||||
|
|
||||||
// path_specifier = safe_regex
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch proto4 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.setSafeRegex(RegexMatcher.newBuilder().setRegex("."))
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteMatch> struct4 = Route.convertEnvoyProtoRouteMatch(proto4);
|
|
||||||
assertThat(struct4.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct4.getStruct()).isEqualTo(
|
|
||||||
new RouteMatch(
|
|
||||||
PathMatcher.fromRegEx(Pattern.compile(".")),
|
|
||||||
Collections.<HeaderMatcher>emptyList(), null));
|
|
||||||
|
|
||||||
// query_parameters is set
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch proto6 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.addQueryParameters(QueryParameterMatcher.getDefaultInstance())
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteMatch> struct6 = Route.convertEnvoyProtoRouteMatch(proto6);
|
|
||||||
assertThat(struct6).isNull();
|
|
||||||
|
|
||||||
// path_specifier unset
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch unsetProto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.getDefaultInstance();
|
|
||||||
StructOrError<RouteMatch> unsetStruct = Route.convertEnvoyProtoRouteMatch(unsetProto);
|
|
||||||
assertThat(unsetStruct.getErrorDetail()).isNotNull();
|
|
||||||
assertThat(unsetStruct.getStruct()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteMatch_withHeaderMatching() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.setPrefix("")
|
|
||||||
.addHeaders(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName(":scheme")
|
|
||||||
.setPrefixMatch("http"))
|
|
||||||
.addHeaders(
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName(":method")
|
|
||||||
.setExactMatch("PUT"))
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteMatch> struct = Route.convertEnvoyProtoRouteMatch(proto);
|
|
||||||
assertThat(struct.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct.getStruct())
|
|
||||||
.isEqualTo(
|
|
||||||
new RouteMatch(
|
|
||||||
PathMatcher.fromPrefix("", false),
|
|
||||||
Arrays.asList(
|
|
||||||
new HeaderMatcher(":scheme", null, null, null, null, "http", null, false),
|
|
||||||
new HeaderMatcher(":method", "PUT", null, null, null, null, null, false)),
|
|
||||||
null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteMatch_withRuntimeFraction() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder()
|
|
||||||
.setPrefix("")
|
|
||||||
.setRuntimeFraction(
|
|
||||||
RuntimeFractionalPercent.newBuilder()
|
|
||||||
.setDefaultValue(
|
|
||||||
FractionalPercent.newBuilder()
|
|
||||||
.setNumerator(30)
|
|
||||||
.setDenominator(FractionalPercent.DenominatorType.HUNDRED)))
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteMatch> struct = Route.convertEnvoyProtoRouteMatch(proto);
|
|
||||||
assertThat(struct.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct.getStruct())
|
|
||||||
.isEqualTo(
|
|
||||||
new RouteMatch(
|
|
||||||
PathMatcher.fromPrefix( "", false), Collections.<HeaderMatcher>emptyList(),
|
|
||||||
new FractionMatcher(30, 100)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteAction_cluster() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setCluster("cluster-foo")
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
|
|
||||||
assertThat(struct.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct.getStruct().getCluster()).isEqualTo("cluster-foo");
|
|
||||||
assertThat(struct.getStruct().getWeightedCluster()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteAction_weightedCluster() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setWeightedClusters(
|
|
||||||
WeightedCluster.newBuilder()
|
|
||||||
.addClusters(
|
|
||||||
WeightedCluster.ClusterWeight
|
|
||||||
.newBuilder()
|
|
||||||
.setName("cluster-foo")
|
|
||||||
.setWeight(UInt32Value.newBuilder().setValue(30)))
|
|
||||||
.addClusters(WeightedCluster.ClusterWeight
|
|
||||||
.newBuilder()
|
|
||||||
.setName("cluster-bar")
|
|
||||||
.setWeight(UInt32Value.newBuilder().setValue(70))))
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
|
|
||||||
assertThat(struct.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct.getStruct().getCluster()).isNull();
|
|
||||||
assertThat(struct.getStruct().getWeightedCluster()).containsExactly(
|
|
||||||
new ClusterWeight("cluster-foo", 30, null), new ClusterWeight("cluster-bar", 70, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteAction_unspecifiedClusterError() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.getDefaultInstance();
|
|
||||||
StructOrError<RouteAction> unsetStruct = RouteAction.fromEnvoyProtoRouteAction(proto);
|
|
||||||
assertThat(unsetStruct.getStruct()).isNull();
|
|
||||||
assertThat(unsetStruct.getErrorDetail()).isNotNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteAction_timeoutByGrpcTimeoutHeaderMax() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setCluster("cluster-foo")
|
|
||||||
.setMaxStreamDuration(
|
|
||||||
MaxStreamDuration.newBuilder()
|
|
||||||
.setGrpcTimeoutHeaderMax(Durations.fromSeconds(5L))
|
|
||||||
.setMaxStreamDuration(Durations.fromMillis(20L)))
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
|
|
||||||
assertThat(struct.getStruct().getTimeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteAction_timeoutByMaxStreamDuration() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setCluster("cluster-foo")
|
|
||||||
.setMaxStreamDuration(
|
|
||||||
MaxStreamDuration.newBuilder()
|
|
||||||
.setMaxStreamDuration(Durations.fromSeconds(5L)))
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
|
|
||||||
assertThat(struct.getStruct().getTimeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertRouteAction_timeoutUnset() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder()
|
|
||||||
.setCluster("cluster-foo")
|
|
||||||
.build();
|
|
||||||
StructOrError<RouteAction> struct = RouteAction.fromEnvoyProtoRouteAction(proto);
|
|
||||||
assertThat(struct.getStruct().getTimeoutNano()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertHeaderMatcher() {
|
|
||||||
// header_match_specifier = exact_match
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto1 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName(":method")
|
|
||||||
.setExactMatch("PUT")
|
|
||||||
.build();
|
|
||||||
StructOrError<HeaderMatcher> struct1 = Route.convertEnvoyProtoHeaderMatcher(proto1);
|
|
||||||
assertThat(struct1.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct1.getStruct()).isEqualTo(
|
|
||||||
new HeaderMatcher(":method", "PUT", null, null, null, null, null, false));
|
|
||||||
|
|
||||||
// header_match_specifier = safe_regex_match
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto3 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName(":method")
|
|
||||||
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("P*"))
|
|
||||||
.build();
|
|
||||||
StructOrError<HeaderMatcher> struct3 = Route.convertEnvoyProtoHeaderMatcher(proto3);
|
|
||||||
assertThat(struct3.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct3.getStruct()).isEqualTo(
|
|
||||||
new HeaderMatcher(":method", null, Pattern.compile("P*"), null, null, null, null, false));
|
|
||||||
|
|
||||||
// header_match_specifier = range_match
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto4 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName("timeout")
|
|
||||||
.setRangeMatch(Int64Range.newBuilder().setStart(10L).setEnd(20L))
|
|
||||||
.build();
|
|
||||||
StructOrError<HeaderMatcher> struct4 = Route.convertEnvoyProtoHeaderMatcher(proto4);
|
|
||||||
assertThat(struct4.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct4.getStruct()).isEqualTo(
|
|
||||||
new HeaderMatcher(
|
|
||||||
"timeout", null, null, new HeaderMatcher.Range(10L, 20L), null, null, null, false));
|
|
||||||
|
|
||||||
// header_match_specifier = present_match
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto5 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName("user-agent")
|
|
||||||
.setPresentMatch(true)
|
|
||||||
.build();
|
|
||||||
StructOrError<HeaderMatcher> struct5 = Route.convertEnvoyProtoHeaderMatcher(proto5);
|
|
||||||
assertThat(struct5.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct5.getStruct()).isEqualTo(
|
|
||||||
new HeaderMatcher("user-agent", null, null, null, true, null, null, false));
|
|
||||||
|
|
||||||
// header_match_specifier = prefix_match
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto6 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName("authority")
|
|
||||||
.setPrefixMatch("service-foo")
|
|
||||||
.build();
|
|
||||||
StructOrError<HeaderMatcher> struct6 = Route.convertEnvoyProtoHeaderMatcher(proto6);
|
|
||||||
assertThat(struct6.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct6.getStruct()).isEqualTo(
|
|
||||||
new HeaderMatcher("authority", null, null, null, null, "service-foo", null, false));
|
|
||||||
|
|
||||||
// header_match_specifier = suffix_match
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto7 =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName("authority")
|
|
||||||
.setSuffixMatch("googleapis.com")
|
|
||||||
.build();
|
|
||||||
StructOrError<HeaderMatcher> struct7 = Route.convertEnvoyProtoHeaderMatcher(proto7);
|
|
||||||
assertThat(struct7.getErrorDetail()).isNull();
|
|
||||||
assertThat(struct7.getStruct()).isEqualTo(
|
|
||||||
new HeaderMatcher(
|
|
||||||
"authority", null, null, null, null, null, "googleapis.com", false));
|
|
||||||
|
|
||||||
// header_match_specifier unset
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher unsetProto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.getDefaultInstance();
|
|
||||||
StructOrError<HeaderMatcher> unsetStruct = Route.convertEnvoyProtoHeaderMatcher(unsetProto);
|
|
||||||
assertThat(unsetStruct.getErrorDetail()).isNotNull();
|
|
||||||
assertThat(unsetStruct.getStruct()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertHeaderMatcher_malformedRegExPattern() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.HeaderMatcher.newBuilder()
|
|
||||||
.setName(":method")
|
|
||||||
.setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("["))
|
|
||||||
.build();
|
|
||||||
StructOrError<HeaderMatcher> struct = Route.convertEnvoyProtoHeaderMatcher(proto);
|
|
||||||
assertThat(struct.getErrorDetail()).isNotNull();
|
|
||||||
assertThat(struct.getStruct()).isNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void convertClusterWeight() {
|
|
||||||
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto =
|
|
||||||
io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight.newBuilder()
|
|
||||||
.setName("cluster-foo")
|
|
||||||
.setWeight(UInt32Value.newBuilder().setValue(30)).build();
|
|
||||||
ClusterWeight struct = ClusterWeight.fromEnvoyProtoClusterWeight(proto).getStruct();
|
|
||||||
assertThat(struct.getName()).isEqualTo("cluster-foo");
|
|
||||||
assertThat(struct.getWeight()).isEqualTo(30);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clusterStats_convertToEnvoyProto() {
|
|
||||||
ClusterStats clusterStats =
|
|
||||||
ClusterStats.newBuilder()
|
|
||||||
.setClusterName("cluster1")
|
|
||||||
.setClusterServiceName("backend-service1")
|
|
||||||
.setLoadReportIntervalNanos(1234)
|
|
||||||
.setTotalDroppedRequests(123)
|
|
||||||
.addUpstreamLocalityStats(UpstreamLocalityStats.newBuilder()
|
|
||||||
.setLocality(new Locality("region1", "zone1", "subzone1"))
|
|
||||||
.setTotalErrorRequests(1)
|
|
||||||
.setTotalRequestsInProgress(2)
|
|
||||||
.setTotalSuccessfulRequests(100)
|
|
||||||
.setTotalIssuedRequests(103)
|
|
||||||
.addLoadMetricStats(EndpointLoadMetricStats.newBuilder()
|
|
||||||
.setMetricName("metric1")
|
|
||||||
.setNumRequestsFinishedWithMetric(1000)
|
|
||||||
.setTotalMetricValue(0.5D)
|
|
||||||
.build())
|
|
||||||
.build())
|
|
||||||
.addDroppedRequests(new DroppedRequests("category1", 100))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats clusterStatsProto =
|
|
||||||
clusterStats.toEnvoyProtoClusterStats();
|
|
||||||
assertThat(clusterStatsProto).isEqualTo(
|
|
||||||
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.newBuilder()
|
|
||||||
.setClusterName("cluster1")
|
|
||||||
.setClusterServiceName("backend-service1")
|
|
||||||
.setLoadReportInterval(Durations.fromNanos(1234))
|
|
||||||
.setTotalDroppedRequests(123)
|
|
||||||
.addUpstreamLocalityStats(
|
|
||||||
io.envoyproxy.envoy.config.endpoint.v3.UpstreamLocalityStats.newBuilder()
|
|
||||||
.setLocality(
|
|
||||||
new Locality("region1", "zone1", "subzone1").toEnvoyProtoLocality())
|
|
||||||
.setTotalErrorRequests(1)
|
|
||||||
.setTotalRequestsInProgress(2)
|
|
||||||
.setTotalSuccessfulRequests(100)
|
|
||||||
.setTotalIssuedRequests(103)
|
|
||||||
.addLoadMetricStats(
|
|
||||||
io.envoyproxy.envoy.config.endpoint.v3.EndpointLoadMetricStats.newBuilder()
|
|
||||||
.setMetricName("metric1")
|
|
||||||
.setNumRequestsFinishedWithMetric(1000)
|
|
||||||
.setTotalMetricValue(0.5D)))
|
|
||||||
.addDroppedRequests(
|
|
||||||
io.envoyproxy.envoy.config.endpoint.v3.ClusterStats.DroppedRequests.newBuilder()
|
|
||||||
.setCategory("category1")
|
|
||||||
.setDroppedCount(100))
|
|
||||||
.build());
|
|
||||||
|
|
||||||
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats clusterStatsProtoV2 =
|
|
||||||
clusterStats.toEnvoyProtoClusterStatsV2();
|
|
||||||
assertThat(clusterStatsProtoV2).isEqualTo(
|
|
||||||
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.newBuilder()
|
|
||||||
.setClusterName("cluster1")
|
|
||||||
.setClusterServiceName("backend-service1")
|
|
||||||
.setLoadReportInterval(Durations.fromNanos(1234))
|
|
||||||
.setTotalDroppedRequests(123)
|
|
||||||
.addUpstreamLocalityStats(
|
|
||||||
io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats.newBuilder()
|
|
||||||
.setLocality(
|
|
||||||
new Locality("region1", "zone1", "subzone1").toEnvoyProtoLocalityV2())
|
|
||||||
.setTotalErrorRequests(1)
|
|
||||||
.setTotalRequestsInProgress(2)
|
|
||||||
.setTotalSuccessfulRequests(100)
|
|
||||||
.setTotalIssuedRequests(103)
|
|
||||||
.addLoadMetricStats(
|
|
||||||
io.envoyproxy.envoy.api.v2.endpoint.EndpointLoadMetricStats.newBuilder()
|
|
||||||
.setMetricName("metric1")
|
|
||||||
.setNumRequestsFinishedWithMetric(1000)
|
|
||||||
.setTotalMetricValue(0.5D)))
|
|
||||||
.addDroppedRequests(
|
|
||||||
io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests.newBuilder()
|
|
||||||
.setCategory("category1")
|
|
||||||
.setDroppedCount(100))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ import io.grpc.internal.BackoffPolicy;
|
||||||
import io.grpc.internal.FakeClock;
|
import io.grpc.internal.FakeClock;
|
||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import io.grpc.testing.GrpcCleanupRule;
|
import io.grpc.testing.GrpcCleanupRule;
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
|
@ -85,8 +84,8 @@ public class LoadReportClientTest {
|
||||||
private static final String CLUSTER2 = "cluster-bar.googleapis.com";
|
private static final String CLUSTER2 = "cluster-bar.googleapis.com";
|
||||||
private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com";
|
private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com";
|
||||||
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
|
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
|
||||||
private static final Locality LOCALITY1 = new Locality("region1", "zone1", "subZone1");
|
private static final Locality LOCALITY1 = Locality.create("region1", "zone1", "subZone1");
|
||||||
private static final Locality LOCALITY2 = new Locality("region2", "zone2", "subZone2");
|
private static final Locality LOCALITY2 = Locality.create("region2", "zone2", "subZone2");
|
||||||
private static final FakeClock.TaskFilter LOAD_REPORTING_TASK_FILTER =
|
private static final FakeClock.TaskFilter LOAD_REPORTING_TASK_FILTER =
|
||||||
new FakeClock.TaskFilter() {
|
new FakeClock.TaskFilter() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,11 @@ import static com.google.common.truth.Truth.assertThat;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.internal.FakeClock;
|
import io.grpc.internal.FakeClock;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterStats.DroppedRequests;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.Locality;
|
|
||||||
import io.grpc.xds.EnvoyProtoData.UpstreamLocalityStats;
|
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterDropStats;
|
||||||
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats;
|
||||||
|
import io.grpc.xds.Stats.ClusterStats;
|
||||||
|
import io.grpc.xds.Stats.DroppedRequests;
|
||||||
|
import io.grpc.xds.Stats.UpstreamLocalityStats;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
@ -45,11 +44,11 @@ public class LoadStatsManager2Test {
|
||||||
private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com";
|
private static final String EDS_SERVICE_NAME1 = "backend-service-foo.googleapis.com";
|
||||||
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
|
private static final String EDS_SERVICE_NAME2 = "backend-service-bar.googleapis.com";
|
||||||
private static final Locality LOCALITY1 =
|
private static final Locality LOCALITY1 =
|
||||||
new Locality("test_region1", "test_zone1", "test_subzone1");
|
Locality.create("test_region1", "test_zone1", "test_subzone1");
|
||||||
private static final Locality LOCALITY2 =
|
private static final Locality LOCALITY2 =
|
||||||
new Locality("test_region2", "test_zone2", "test_subzone2");
|
Locality.create("test_region2", "test_zone2", "test_subzone2");
|
||||||
private static final Locality LOCALITY3 =
|
private static final Locality LOCALITY3 =
|
||||||
new Locality("test_region3", "test_zone3", "test_subzone3");
|
Locality.create("test_region3", "test_zone3", "test_subzone3");
|
||||||
|
|
||||||
private final FakeClock fakeClock = new FakeClock();
|
private final FakeClock fakeClock = new FakeClock();
|
||||||
private final LoadStatsManager2 loadStatsManager =
|
private final LoadStatsManager2 loadStatsManager =
|
||||||
|
|
@ -85,68 +84,68 @@ public class LoadStatsManager2Test {
|
||||||
assertThat(allStats).hasSize(3); // three cluster:edsServiceName
|
assertThat(allStats).hasSize(3); // three cluster:edsServiceName
|
||||||
|
|
||||||
ClusterStats stats1 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME1);
|
ClusterStats stats1 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME1);
|
||||||
assertThat(stats1.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
|
assertThat(stats1.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
|
||||||
assertThat(stats1.getDroppedRequestsList()).hasSize(2);
|
assertThat(stats1.droppedRequestsList()).hasSize(2);
|
||||||
assertThat(findDroppedRequestCount(stats1.getDroppedRequestsList(), "lb")).isEqualTo(1L);
|
assertThat(findDroppedRequestCount(stats1.droppedRequestsList(), "lb")).isEqualTo(1L);
|
||||||
assertThat(findDroppedRequestCount(stats1.getDroppedRequestsList(), "throttle")).isEqualTo(1L);
|
assertThat(findDroppedRequestCount(stats1.droppedRequestsList(), "throttle")).isEqualTo(1L);
|
||||||
assertThat(stats1.getTotalDroppedRequests()).isEqualTo(1L + 1L);
|
assertThat(stats1.totalDroppedRequests()).isEqualTo(1L + 1L);
|
||||||
assertThat(stats1.getUpstreamLocalityStatsList()).hasSize(2); // two localities
|
assertThat(stats1.upstreamLocalityStatsList()).hasSize(2); // two localities
|
||||||
UpstreamLocalityStats loadStats1 =
|
UpstreamLocalityStats loadStats1 =
|
||||||
findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY1);
|
findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY1);
|
||||||
assertThat(loadStats1.getTotalIssuedRequests()).isEqualTo(19L);
|
assertThat(loadStats1.totalIssuedRequests()).isEqualTo(19L);
|
||||||
assertThat(loadStats1.getTotalSuccessfulRequests()).isEqualTo(1L);
|
assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(1L);
|
||||||
assertThat(loadStats1.getTotalErrorRequests()).isEqualTo(0L);
|
assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats1.getTotalRequestsInProgress()).isEqualTo(19L - 1L);
|
assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(19L - 1L);
|
||||||
|
|
||||||
UpstreamLocalityStats loadStats2 =
|
UpstreamLocalityStats loadStats2 =
|
||||||
findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY2);
|
findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
|
||||||
assertThat(loadStats2.getTotalIssuedRequests()).isEqualTo(9L);
|
assertThat(loadStats2.totalIssuedRequests()).isEqualTo(9L);
|
||||||
assertThat(loadStats2.getTotalSuccessfulRequests()).isEqualTo(0L);
|
assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats2.getTotalErrorRequests()).isEqualTo(1L);
|
assertThat(loadStats2.totalErrorRequests()).isEqualTo(1L);
|
||||||
assertThat(loadStats2.getTotalRequestsInProgress()).isEqualTo(9L - 1L);
|
assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(9L - 1L);
|
||||||
|
|
||||||
ClusterStats stats2 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME2);
|
ClusterStats stats2 = findClusterStats(allStats, CLUSTER_NAME1, EDS_SERVICE_NAME2);
|
||||||
assertThat(stats2.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
|
assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
|
||||||
assertThat(stats2.getDroppedRequestsList()).isEmpty(); // no categorized drops
|
assertThat(stats2.droppedRequestsList()).isEmpty(); // no categorized drops
|
||||||
assertThat(stats2.getTotalDroppedRequests()).isEqualTo(1L);
|
assertThat(stats2.totalDroppedRequests()).isEqualTo(1L);
|
||||||
assertThat(stats2.getUpstreamLocalityStatsList()).isEmpty(); // no per-locality stats
|
assertThat(stats2.upstreamLocalityStatsList()).isEmpty(); // no per-locality stats
|
||||||
|
|
||||||
ClusterStats stats3 = findClusterStats(allStats, CLUSTER_NAME2, null);
|
ClusterStats stats3 = findClusterStats(allStats, CLUSTER_NAME2, null);
|
||||||
assertThat(stats3.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
|
assertThat(stats3.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L + 10L));
|
||||||
assertThat(stats3.getDroppedRequestsList()).isEmpty();
|
assertThat(stats3.droppedRequestsList()).isEmpty();
|
||||||
assertThat(stats3.getTotalDroppedRequests()).isEqualTo(0L); // no drops recorded
|
assertThat(stats3.totalDroppedRequests()).isEqualTo(0L); // no drops recorded
|
||||||
assertThat(stats3.getUpstreamLocalityStatsList()).hasSize(1); // one localities
|
assertThat(stats3.upstreamLocalityStatsList()).hasSize(1); // one localities
|
||||||
UpstreamLocalityStats loadStats3 =
|
UpstreamLocalityStats loadStats3 =
|
||||||
Iterables.getOnlyElement(stats3.getUpstreamLocalityStatsList());
|
Iterables.getOnlyElement(stats3.upstreamLocalityStatsList());
|
||||||
assertThat(loadStats3.getTotalIssuedRequests()).isEqualTo(1L);
|
assertThat(loadStats3.totalIssuedRequests()).isEqualTo(1L);
|
||||||
assertThat(loadStats3.getTotalSuccessfulRequests()).isEqualTo(0L);
|
assertThat(loadStats3.totalSuccessfulRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats3.getTotalErrorRequests()).isEqualTo(0L);
|
assertThat(loadStats3.totalErrorRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats3.getTotalRequestsInProgress()).isEqualTo(1L);
|
assertThat(loadStats3.totalRequestsInProgress()).isEqualTo(1L);
|
||||||
|
|
||||||
fakeClock.forwardTime(3L, TimeUnit.SECONDS);
|
fakeClock.forwardTime(3L, TimeUnit.SECONDS);
|
||||||
List<ClusterStats> clusterStatsList = loadStatsManager.getClusterStatsReports(CLUSTER_NAME1);
|
List<ClusterStats> clusterStatsList = loadStatsManager.getClusterStatsReports(CLUSTER_NAME1);
|
||||||
assertThat(clusterStatsList).hasSize(2);
|
assertThat(clusterStatsList).hasSize(2);
|
||||||
stats1 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME1);
|
stats1 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME1);
|
||||||
assertThat(stats1.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
|
assertThat(stats1.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
|
||||||
assertThat(stats1.getDroppedRequestsList()).isEmpty();
|
assertThat(stats1.droppedRequestsList()).isEmpty();
|
||||||
assertThat(stats1.getTotalDroppedRequests()).isEqualTo(0L); // no new drops recorded
|
assertThat(stats1.totalDroppedRequests()).isEqualTo(0L); // no new drops recorded
|
||||||
assertThat(stats1.getUpstreamLocalityStatsList()).hasSize(2); // two localities
|
assertThat(stats1.upstreamLocalityStatsList()).hasSize(2); // two localities
|
||||||
loadStats1 = findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY1);
|
loadStats1 = findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY1);
|
||||||
assertThat(loadStats1.getTotalIssuedRequests()).isEqualTo(0L);
|
assertThat(loadStats1.totalIssuedRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats1.getTotalSuccessfulRequests()).isEqualTo(0L);
|
assertThat(loadStats1.totalSuccessfulRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats1.getTotalErrorRequests()).isEqualTo(0L);
|
assertThat(loadStats1.totalErrorRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats1.getTotalRequestsInProgress()).isEqualTo(18L); // still in-progress
|
assertThat(loadStats1.totalRequestsInProgress()).isEqualTo(18L); // still in-progress
|
||||||
loadStats2 = findLocalityStats(stats1.getUpstreamLocalityStatsList(), LOCALITY2);
|
loadStats2 = findLocalityStats(stats1.upstreamLocalityStatsList(), LOCALITY2);
|
||||||
assertThat(loadStats2.getTotalIssuedRequests()).isEqualTo(0L);
|
assertThat(loadStats2.totalIssuedRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats2.getTotalSuccessfulRequests()).isEqualTo(0L);
|
assertThat(loadStats2.totalSuccessfulRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats2.getTotalErrorRequests()).isEqualTo(0L);
|
assertThat(loadStats2.totalErrorRequests()).isEqualTo(0L);
|
||||||
assertThat(loadStats2.getTotalRequestsInProgress()).isEqualTo(8L); // still in-progress
|
assertThat(loadStats2.totalRequestsInProgress()).isEqualTo(8L); // still in-progress
|
||||||
|
|
||||||
stats2 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME2);
|
stats2 = findClusterStats(clusterStatsList, CLUSTER_NAME1, EDS_SERVICE_NAME2);
|
||||||
assertThat(stats2.getLoadReportIntervalNanos()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
|
assertThat(stats2.loadReportIntervalNano()).isEqualTo(TimeUnit.SECONDS.toNanos(3L));
|
||||||
assertThat(stats2.getDroppedRequestsList()).isEmpty();
|
assertThat(stats2.droppedRequestsList()).isEmpty();
|
||||||
assertThat(stats2.getTotalDroppedRequests()).isEqualTo(0L); // no new drops recorded
|
assertThat(stats2.totalDroppedRequests()).isEqualTo(0L); // no new drops recorded
|
||||||
assertThat(stats2.getUpstreamLocalityStatsList()).isEmpty(); // no per-locality stats
|
assertThat(stats2.upstreamLocalityStatsList()).isEmpty(); // no per-locality stats
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -162,10 +161,10 @@ public class LoadStatsManager2Test {
|
||||||
|
|
||||||
ClusterStats stats = Iterables.getOnlyElement(
|
ClusterStats stats = Iterables.getOnlyElement(
|
||||||
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
||||||
assertThat(stats.getDroppedRequestsList()).hasSize(2);
|
assertThat(stats.droppedRequestsList()).hasSize(2);
|
||||||
assertThat(findDroppedRequestCount(stats.getDroppedRequestsList(), "lb")).isEqualTo(1L);
|
assertThat(findDroppedRequestCount(stats.droppedRequestsList(), "lb")).isEqualTo(1L);
|
||||||
assertThat(findDroppedRequestCount(stats.getDroppedRequestsList(), "throttle")).isEqualTo(1L);
|
assertThat(findDroppedRequestCount(stats.droppedRequestsList(), "throttle")).isEqualTo(1L);
|
||||||
assertThat(stats.getTotalDroppedRequests()).isEqualTo(4L); // 2 cagetorized + 2 uncategoized
|
assertThat(stats.totalDroppedRequests()).isEqualTo(4L); // 2 cagetorized + 2 uncategoized
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -175,15 +174,15 @@ public class LoadStatsManager2Test {
|
||||||
counter.recordDroppedRequest("lb");
|
counter.recordDroppedRequest("lb");
|
||||||
ClusterStats stats = Iterables.getOnlyElement(
|
ClusterStats stats = Iterables.getOnlyElement(
|
||||||
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
||||||
assertThat(stats.getDroppedRequestsList()).hasSize(1);
|
assertThat(stats.droppedRequestsList()).hasSize(1);
|
||||||
assertThat(Iterables.getOnlyElement(stats.getDroppedRequestsList()).getDroppedCount())
|
assertThat(Iterables.getOnlyElement(stats.droppedRequestsList()).droppedCount())
|
||||||
.isEqualTo(1L);
|
.isEqualTo(1L);
|
||||||
assertThat(stats.getTotalDroppedRequests()).isEqualTo(1L);
|
assertThat(stats.totalDroppedRequests()).isEqualTo(1L);
|
||||||
|
|
||||||
counter.release();
|
counter.release();
|
||||||
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
||||||
assertThat(stats.getDroppedRequestsList()).isEmpty();
|
assertThat(stats.droppedRequestsList()).isEmpty();
|
||||||
assertThat(stats.getTotalDroppedRequests()).isEqualTo(0L);
|
assertThat(stats.totalDroppedRequests()).isEqualTo(0L);
|
||||||
|
|
||||||
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
|
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
@ -203,11 +202,11 @@ public class LoadStatsManager2Test {
|
||||||
ClusterStats stats = Iterables.getOnlyElement(
|
ClusterStats stats = Iterables.getOnlyElement(
|
||||||
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
||||||
UpstreamLocalityStats localityStats =
|
UpstreamLocalityStats localityStats =
|
||||||
Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
|
Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
|
||||||
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(1L + 2L);
|
assertThat(localityStats.totalIssuedRequests()).isEqualTo(1L + 2L);
|
||||||
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L);
|
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
|
||||||
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L);
|
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
|
||||||
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(1L + 2L - 1L - 1L);
|
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(1L + 2L - 1L - 1L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -220,30 +219,30 @@ public class LoadStatsManager2Test {
|
||||||
ClusterStats stats = Iterables.getOnlyElement(
|
ClusterStats stats = Iterables.getOnlyElement(
|
||||||
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
||||||
UpstreamLocalityStats localityStats =
|
UpstreamLocalityStats localityStats =
|
||||||
Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
|
Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
|
||||||
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(2L);
|
assertThat(localityStats.totalIssuedRequests()).isEqualTo(2L);
|
||||||
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(2L);
|
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(2L);
|
||||||
|
|
||||||
// release the counter, but requests still in-flight
|
// release the counter, but requests still in-flight
|
||||||
counter.release();
|
counter.release();
|
||||||
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
||||||
localityStats = Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
|
localityStats = Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
|
||||||
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalErrorRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalRequestsInProgress())
|
assertThat(localityStats.totalRequestsInProgress())
|
||||||
.isEqualTo(2L); // retained by in-flight calls
|
.isEqualTo(2L); // retained by in-flight calls
|
||||||
|
|
||||||
counter.recordCallFinished(Status.OK);
|
counter.recordCallFinished(Status.OK);
|
||||||
counter.recordCallFinished(Status.UNAVAILABLE);
|
counter.recordCallFinished(Status.UNAVAILABLE);
|
||||||
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
stats = Iterables.getOnlyElement(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1));
|
||||||
localityStats = Iterables.getOnlyElement(stats.getUpstreamLocalityStatsList());
|
localityStats = Iterables.getOnlyElement(stats.upstreamLocalityStatsList());
|
||||||
assertThat(localityStats.getTotalIssuedRequests()).isEqualTo(0L);
|
assertThat(localityStats.totalIssuedRequests()).isEqualTo(0L);
|
||||||
assertThat(localityStats.getTotalSuccessfulRequests()).isEqualTo(1L);
|
assertThat(localityStats.totalSuccessfulRequests()).isEqualTo(1L);
|
||||||
assertThat(localityStats.getTotalErrorRequests()).isEqualTo(1L);
|
assertThat(localityStats.totalErrorRequests()).isEqualTo(1L);
|
||||||
assertThat(localityStats.getTotalRequestsInProgress()).isEqualTo(0L);
|
assertThat(localityStats.totalRequestsInProgress()).isEqualTo(0L);
|
||||||
|
|
||||||
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
|
assertThat(loadStatsManager.getClusterStatsReports(CLUSTER_NAME1)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
@ -252,8 +251,8 @@ public class LoadStatsManager2Test {
|
||||||
private static ClusterStats findClusterStats(
|
private static ClusterStats findClusterStats(
|
||||||
List<ClusterStats> statsList, String cluster, @Nullable String edsServiceName) {
|
List<ClusterStats> statsList, String cluster, @Nullable String edsServiceName) {
|
||||||
for (ClusterStats stats : statsList) {
|
for (ClusterStats stats : statsList) {
|
||||||
if (stats.getClusterName().equals(cluster)
|
if (stats.clusterName().equals(cluster)
|
||||||
&& Objects.equals(stats.getClusterServiceName(), edsServiceName)) {
|
&& Objects.equals(stats.clusterServiceName(), edsServiceName)) {
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -264,7 +263,7 @@ public class LoadStatsManager2Test {
|
||||||
private static UpstreamLocalityStats findLocalityStats(
|
private static UpstreamLocalityStats findLocalityStats(
|
||||||
List<UpstreamLocalityStats> localityStatsList, Locality locality) {
|
List<UpstreamLocalityStats> localityStatsList, Locality locality) {
|
||||||
for (UpstreamLocalityStats stats : localityStatsList) {
|
for (UpstreamLocalityStats stats : localityStatsList) {
|
||||||
if (stats.getLocality().equals(locality)) {
|
if (stats.locality().equals(locality)) {
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -275,11 +274,11 @@ public class LoadStatsManager2Test {
|
||||||
List<DroppedRequests> droppedRequestsLists, String category) {
|
List<DroppedRequests> droppedRequestsLists, String category) {
|
||||||
DroppedRequests drop = null;
|
DroppedRequests drop = null;
|
||||||
for (DroppedRequests stats : droppedRequestsLists) {
|
for (DroppedRequests stats : droppedRequestsLists) {
|
||||||
if (stats.getCategory().equals(category)) {
|
if (stats.category().equals(category)) {
|
||||||
drop = stats;
|
drop = stats;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertThat(drop).isNotNull();
|
assertThat(drop).isNotNull();
|
||||||
return drop.getDroppedCount();
|
return drop.droppedCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
@ -26,6 +27,7 @@ import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.re2j.Pattern;
|
||||||
import io.grpc.CallOptions;
|
import io.grpc.CallOptions;
|
||||||
import io.grpc.Channel;
|
import io.grpc.Channel;
|
||||||
import io.grpc.ClientCall;
|
import io.grpc.ClientCall;
|
||||||
|
|
@ -49,15 +51,18 @@ import io.grpc.internal.NoopClientCall.NoopClientCallListener;
|
||||||
import io.grpc.internal.ObjectPool;
|
import io.grpc.internal.ObjectPool;
|
||||||
import io.grpc.internal.PickSubchannelArgsImpl;
|
import io.grpc.internal.PickSubchannelArgsImpl;
|
||||||
import io.grpc.testing.TestMethodDescriptors;
|
import io.grpc.testing.TestMethodDescriptors;
|
||||||
import io.grpc.xds.EnvoyProtoData.ClusterWeight;
|
import io.grpc.xds.Matchers.HeaderMatcher;
|
||||||
import io.grpc.xds.EnvoyProtoData.Route;
|
import io.grpc.xds.Matchers.PathMatcher;
|
||||||
import io.grpc.xds.EnvoyProtoData.RouteAction;
|
import io.grpc.xds.VirtualHost.Route;
|
||||||
import io.grpc.xds.EnvoyProtoData.VirtualHost;
|
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight;
|
||||||
|
import io.grpc.xds.VirtualHost.Route.RouteMatch;
|
||||||
import io.grpc.xds.XdsClient.RdsResourceWatcher;
|
import io.grpc.xds.XdsClient.RdsResourceWatcher;
|
||||||
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
|
import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
@ -232,25 +237,25 @@ public class XdsNameResolverTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<VirtualHost> buildUnmatchedVirtualHosts() {
|
private List<VirtualHost> buildUnmatchedVirtualHosts() {
|
||||||
Route route1 = new Route(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
Route route1 = Route.create(RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null);
|
||||||
Route route2 = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
Route route2 = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null));
|
RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null);
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new VirtualHost("virtualhost-foo", Collections.singletonList("hello.googleapis.com"),
|
VirtualHost.create("virtualhost-foo", Collections.singletonList("hello.googleapis.com"),
|
||||||
Collections.singletonList(route1)),
|
Collections.singletonList(route1), null),
|
||||||
new VirtualHost("virtualhost-bar", Collections.singletonList("hi.googleapis.com"),
|
VirtualHost.create("virtualhost-bar", Collections.singletonList("hi.googleapis.com"),
|
||||||
Collections.singletonList(route2)));
|
Collections.singletonList(route2), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void resolved_noTimeout() {
|
public void resolved_noTimeout() {
|
||||||
resolver.start(mockListener);
|
resolver.start(mockListener);
|
||||||
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
||||||
Route route = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
Route route = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(null, cluster1, null)); // per-route timeout unset
|
RouteAction.forCluster(cluster1, null), null); // per-route timeout unset
|
||||||
VirtualHost virtualHost = new VirtualHost("does not matter",
|
VirtualHost virtualHost = VirtualHost.create("does not matter",
|
||||||
Collections.singletonList(AUTHORITY), Collections.singletonList(route));
|
Collections.singletonList(AUTHORITY), Collections.singletonList(route), null);
|
||||||
xdsClient.deliverLdsUpdate(AUTHORITY, 0L, Collections.singletonList(virtualHost));
|
xdsClient.deliverLdsUpdate(AUTHORITY, 0L, Collections.singletonList(virtualHost));
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
|
|
@ -262,10 +267,10 @@ public class XdsNameResolverTest {
|
||||||
public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() {
|
public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() {
|
||||||
resolver.start(mockListener);
|
resolver.start(mockListener);
|
||||||
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
||||||
Route route = new Route(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
Route route = Route.create(RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(null, cluster1, null)); // per-route timeout unset
|
RouteAction.forCluster(cluster1, null), null); // per-route timeout unset
|
||||||
VirtualHost virtualHost = new VirtualHost("does not matter",
|
VirtualHost virtualHost = VirtualHost.create("does not matter",
|
||||||
Collections.singletonList(AUTHORITY), Collections.singletonList(route));
|
Collections.singletonList(AUTHORITY), Collections.singletonList(route), null);
|
||||||
xdsClient.deliverLdsUpdate(AUTHORITY, TimeUnit.SECONDS.toNanos(5L),
|
xdsClient.deliverLdsUpdate(AUTHORITY, TimeUnit.SECONDS.toNanos(5L),
|
||||||
Collections.singletonList(virtualHost));
|
Collections.singletonList(virtualHost));
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
|
|
@ -307,12 +312,12 @@ public class XdsNameResolverTest {
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)),
|
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
// Updated service config still contains cluster1 while it is removed resource. New calls no
|
// Updated service config still contains cluster1 while it is removed resource. New calls no
|
||||||
|
|
@ -342,12 +347,12 @@ public class XdsNameResolverTest {
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)),
|
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
|
||||||
// Two consecutive service config updates: one for removing clcuster1,
|
// Two consecutive service config updates: one for removing clcuster1,
|
||||||
// one for adding "another=cluster".
|
// one for adding "another=cluster".
|
||||||
verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture());
|
verify(mockListener, times(2)).onResult(resolutionResultCaptor.capture());
|
||||||
|
|
@ -373,12 +378,12 @@ public class XdsNameResolverTest {
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(20L), "another-cluster", null)),
|
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(20L)), null),
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
|
||||||
|
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
|
|
@ -389,12 +394,12 @@ public class XdsNameResolverTest {
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), "another-cluster", null)),
|
RouteAction.forCluster("another-cluster", TimeUnit.SECONDS.toNanos(15L)), null),
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
|
||||||
verifyNoMoreInteractions(mockListener); // no cluster added/deleted
|
verifyNoMoreInteractions(mockListener); // no cluster added/deleted
|
||||||
assertCallSelectResult(call1, configSelector, "another-cluster", 15.0);
|
assertCallSelectResult(call1, configSelector, "another-cluster", 15.0);
|
||||||
}
|
}
|
||||||
|
|
@ -407,18 +412,18 @@ public class XdsNameResolverTest {
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Collections.singletonList(
|
Collections.singletonList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)),
|
RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null),
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
|
||||||
testCall.deliverErrorStatus();
|
testCall.deliverErrorStatus();
|
||||||
verifyNoMoreInteractions(mockListener);
|
verifyNoMoreInteractions(mockListener);
|
||||||
}
|
}
|
||||||
|
|
@ -431,14 +436,14 @@ public class XdsNameResolverTest {
|
||||||
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Arrays.asList(
|
Collections.singletonList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(
|
RouteAction.forWeightedClusters(
|
||||||
TimeUnit.SECONDS.toNanos(20L), null,
|
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new ClusterWeight(cluster1, 20, null),
|
ClusterWeight.create(cluster1, 20, null),
|
||||||
new ClusterWeight(cluster2, 80, null))))));
|
ClusterWeight.create(cluster2, 80, null)),
|
||||||
|
TimeUnit.SECONDS.toNanos(20L)), null)));
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
assertThat(result.getAddresses()).isEmpty();
|
assertThat(result.getAddresses()).isEmpty();
|
||||||
|
|
@ -493,12 +498,12 @@ public class XdsNameResolverTest {
|
||||||
xdsClient.deliverLdsUpdate(
|
xdsClient.deliverLdsUpdate(
|
||||||
AUTHORITY,
|
AUTHORITY,
|
||||||
Arrays.asList(
|
Arrays.asList(
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call1.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster1, null)),
|
RouteAction.forCluster(cluster1, TimeUnit.SECONDS.toNanos(15L)), null),
|
||||||
new Route(
|
Route.create(
|
||||||
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
RouteMatch.withPathExactOnly(call2.getFullMethodNameForPath()),
|
||||||
new RouteAction(TimeUnit.SECONDS.toNanos(15L), cluster2, null))));
|
RouteAction.forCluster(cluster2, TimeUnit.SECONDS.toNanos(15L)), null)));
|
||||||
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
verify(mockListener).onResult(resolutionResultCaptor.capture());
|
||||||
ResolutionResult result = resolutionResultCaptor.getValue();
|
ResolutionResult result = resolutionResultCaptor.getValue();
|
||||||
assertThat(result.getAddresses()).isEmpty();
|
assertThat(result.getAddresses()).isEmpty();
|
||||||
|
|
@ -636,12 +641,12 @@ public class XdsNameResolverTest {
|
||||||
public void findVirtualHostForHostName_exactMatchFirst() {
|
public void findVirtualHostForHostName_exactMatchFirst() {
|
||||||
String hostname = "a.googleapis.com";
|
String hostname = "a.googleapis.com";
|
||||||
List<Route> routes = Collections.emptyList();
|
List<Route> routes = Collections.emptyList();
|
||||||
VirtualHost vHost1 = new VirtualHost("virtualhost01.googleapis.com",
|
VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
|
||||||
Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes);
|
Arrays.asList("a.googleapis.com", "b.googleapis.com"), routes, null);
|
||||||
VirtualHost vHost2 = new VirtualHost("virtualhost02.googleapis.com",
|
VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
|
||||||
Collections.singletonList("*.googleapis.com"), routes);
|
Collections.singletonList("*.googleapis.com"), routes, null);
|
||||||
VirtualHost vHost3 =
|
VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com",
|
||||||
new VirtualHost("virtualhost03.googleapis.com", Collections.singletonList("*"), routes);
|
Collections.singletonList("*"), routes, null);
|
||||||
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
|
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
|
||||||
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
|
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
|
||||||
.isEqualTo(vHost1);
|
.isEqualTo(vHost1);
|
||||||
|
|
@ -651,14 +656,12 @@ public class XdsNameResolverTest {
|
||||||
public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() {
|
public void findVirtualHostForHostName_preferSuffixDomainOverPrefixDomain() {
|
||||||
String hostname = "a.googleapis.com";
|
String hostname = "a.googleapis.com";
|
||||||
List<Route> routes = Collections.emptyList();
|
List<Route> routes = Collections.emptyList();
|
||||||
VirtualHost vHost1 =
|
VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
|
||||||
new VirtualHost("virtualhost01.googleapis.com",
|
Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes, null);
|
||||||
Arrays.asList("*.googleapis.com", "b.googleapis.com"), routes);
|
VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
|
||||||
VirtualHost vHost2 =
|
Collections.singletonList("a.googleapis.*"), routes, null);
|
||||||
new VirtualHost("virtualhost02.googleapis.com",
|
VirtualHost vHost3 = VirtualHost.create("virtualhost03.googleapis.com",
|
||||||
Collections.singletonList("a.googleapis.*"), routes);
|
Collections.singletonList("*"), routes, null);
|
||||||
VirtualHost vHost3 =
|
|
||||||
new VirtualHost("virtualhost03.googleapis.com", Collections.singletonList("*"), routes);
|
|
||||||
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
|
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2, vHost3);
|
||||||
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
|
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
|
||||||
.isEqualTo(vHost1);
|
.isEqualTo(vHost1);
|
||||||
|
|
@ -668,16 +671,127 @@ public class XdsNameResolverTest {
|
||||||
public void findVirtualHostForHostName_asteriskMatchAnyDomain() {
|
public void findVirtualHostForHostName_asteriskMatchAnyDomain() {
|
||||||
String hostname = "a.googleapis.com";
|
String hostname = "a.googleapis.com";
|
||||||
List<Route> routes = Collections.emptyList();
|
List<Route> routes = Collections.emptyList();
|
||||||
VirtualHost vHost1 =
|
VirtualHost vHost1 = VirtualHost.create("virtualhost01.googleapis.com",
|
||||||
new VirtualHost("virtualhost01.googleapis.com", Collections.singletonList("*"), routes);
|
Collections.singletonList("*"), routes, null);
|
||||||
VirtualHost vHost2 =
|
VirtualHost vHost2 = VirtualHost.create("virtualhost02.googleapis.com",
|
||||||
new VirtualHost("virtualhost02.googleapis.com",
|
Collections.singletonList("b.googleapis.com"), routes, null);
|
||||||
Collections.singletonList("b.googleapis.com"), routes);
|
|
||||||
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2);
|
List<VirtualHost> virtualHosts = Arrays.asList(vHost1, vHost2);
|
||||||
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
|
assertThat(XdsNameResolver.findVirtualHostForHostName(virtualHosts, hostname))
|
||||||
.isEqualTo(vHost1);;
|
.isEqualTo(vHost1);;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void routeMatching_pathOnly() {
|
||||||
|
Map<String, Iterable<String>> headers = Collections.emptyMap();
|
||||||
|
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
|
||||||
|
|
||||||
|
RouteMatch routeMatch1 =
|
||||||
|
RouteMatch.create(
|
||||||
|
PathMatcher.fromPath("/FooService/barMethod", true),
|
||||||
|
Collections.<HeaderMatcher>emptyList(), null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/barMethod", headers, random))
|
||||||
|
.isTrue();
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/bazMethod", headers, random))
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
RouteMatch routeMatch2 =
|
||||||
|
RouteMatch.create(
|
||||||
|
PathMatcher.fromPrefix("/FooService/", true),
|
||||||
|
Collections.<HeaderMatcher>emptyList(), null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/barMethod", headers, random))
|
||||||
|
.isTrue();
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/bazMethod", headers, random))
|
||||||
|
.isTrue();
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/BarService/bazMethod", headers, random))
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
RouteMatch routeMatch3 =
|
||||||
|
RouteMatch.create(
|
||||||
|
PathMatcher.fromRegEx(Pattern.compile(".*Foo.*")),
|
||||||
|
Collections.<HeaderMatcher>emptyList(), null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch3, "/FooService/barMethod", headers, random))
|
||||||
|
.isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void routeMatching_withHeaders() {
|
||||||
|
Map<String, Iterable<String>> headers = new HashMap<>();
|
||||||
|
headers.put("authority", Collections.singletonList("foo.googleapis.com"));
|
||||||
|
headers.put("grpc-encoding", Collections.singletonList("gzip"));
|
||||||
|
headers.put("user-agent", Collections.singletonList("gRPC-Java"));
|
||||||
|
headers.put("content-length", Collections.singletonList("1000"));
|
||||||
|
headers.put("custom-key", Arrays.asList("custom-value1", "custom-value2"));
|
||||||
|
ThreadSafeRandom random = mock(ThreadSafeRandom.class);
|
||||||
|
|
||||||
|
PathMatcher pathMatcher = PathMatcher.fromPath("/FooService/barMethod", true);
|
||||||
|
RouteMatch routeMatch1 = RouteMatch.create(
|
||||||
|
pathMatcher,
|
||||||
|
Arrays.asList(
|
||||||
|
HeaderMatcher.forExactValue("grpc-encoding", "gzip", false),
|
||||||
|
HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), false),
|
||||||
|
HeaderMatcher.forRange(
|
||||||
|
"content-length", HeaderMatcher.Range.create(100, 10000), false),
|
||||||
|
HeaderMatcher.forPresent("user-agent", true, false),
|
||||||
|
HeaderMatcher.forPrefix("custom-key", "custom-", false),
|
||||||
|
HeaderMatcher.forSuffix("custom-key", "value2", false)),
|
||||||
|
null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch1, "/FooService/barMethod", headers, random))
|
||||||
|
.isTrue();
|
||||||
|
|
||||||
|
RouteMatch routeMatch2 = RouteMatch.create(
|
||||||
|
pathMatcher,
|
||||||
|
Collections.singletonList(
|
||||||
|
HeaderMatcher.forSafeRegEx("authority", Pattern.compile(".*googleapis.*"), true)),
|
||||||
|
null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch2, "/FooService/barMethod", headers, random))
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
RouteMatch routeMatch3 = RouteMatch.create(
|
||||||
|
pathMatcher,
|
||||||
|
Collections.singletonList(
|
||||||
|
HeaderMatcher.forExactValue("user-agent", "gRPC-Go", false)), null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch3, "/FooService/barMethod", headers, random))
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
RouteMatch routeMatch4 = RouteMatch.create(
|
||||||
|
pathMatcher,
|
||||||
|
Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, false)),
|
||||||
|
null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch4, "/FooService/barMethod", headers, random))
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
RouteMatch routeMatch5 = RouteMatch.create(
|
||||||
|
pathMatcher,
|
||||||
|
Collections.singletonList(HeaderMatcher.forPresent("user-agent", false, true)), // inverted
|
||||||
|
null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch5, "/FooService/barMethod", headers, random))
|
||||||
|
.isTrue();
|
||||||
|
|
||||||
|
RouteMatch routeMatch6 = RouteMatch.create(
|
||||||
|
pathMatcher,
|
||||||
|
Collections.singletonList(HeaderMatcher.forPresent("user-agent", true, true)),
|
||||||
|
null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch6, "/FooService/barMethod", headers, random))
|
||||||
|
.isFalse();
|
||||||
|
|
||||||
|
RouteMatch routeMatch7 = RouteMatch.create(
|
||||||
|
pathMatcher,
|
||||||
|
Collections.singletonList(
|
||||||
|
HeaderMatcher.forExactValue("custom-key", "custom-value1,custom-value2", false)),
|
||||||
|
null);
|
||||||
|
assertThat(XdsNameResolver.matchRoute(routeMatch7, "/FooService/barMethod", headers, random))
|
||||||
|
.isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void pathMatching_caseInsensitive() {
|
||||||
|
PathMatcher pathMatcher1 = PathMatcher.fromPath("/FooService/barMethod", false);
|
||||||
|
assertThat(XdsNameResolver.matchPath(pathMatcher1, "/fooservice/barmethod")).isTrue();
|
||||||
|
|
||||||
|
PathMatcher pathMatcher2 = PathMatcher.fromPrefix("/FooService", false);
|
||||||
|
assertThat(XdsNameResolver.matchPath(pathMatcher2, "/fooservice/barmethod")).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory {
|
private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -757,8 +871,8 @@ public class XdsNameResolverTest {
|
||||||
if (!resourceName.equals(ldsResource)) {
|
if (!resourceName.equals(ldsResource)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
VirtualHost virtualHost =
|
VirtualHost virtualHost = VirtualHost.create("virtual-host",
|
||||||
new VirtualHost("virtual-host", Collections.singletonList(AUTHORITY), routes);
|
Collections.singletonList(AUTHORITY), routes, null);
|
||||||
ldsWatcher.onChanged(
|
ldsWatcher.onChanged(
|
||||||
new LdsUpdate(0, Collections.singletonList(virtualHost), false, null));
|
new LdsUpdate(0, Collections.singletonList(virtualHost), false, null));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue