From e1ad984db3ca735b1196e248214171a1e1506215 Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 16 Sep 2022 10:08:16 -0700 Subject: [PATCH] xds: refactor xds client to make it resource agnostic (#9444) Mainly refactor work to make type specific xds resources generic, e.g. 1. Define abstract class XdsResourceType to be extended by pluggable new resources. It mainly contains abstract method doParse() to parse unpacked proto messges and produce a ResourceUpdate. The common unpacking proto logic is in XdsResourceType default method parse() 2. Move the parsing/processing logics to specific XdsResourceType. Implementing: XdsListenerResource for LDS XdsRouteConfigureResource for RDS XdsClusterResource for CDS XdsEndpointResource for EDS 3. The XdsResourceTypes are singleton. To process for each XdsClient, context is passed in parameters, defined by XdsResourceType.Args. 4. Watchers will use generic APIs to subscribe to resource watchXdsResource(XdsResourceType, resourceName, watcher). Watcher and ResourceSubscribers becomes java generic class. --- .../java/io/grpc/xds/AbstractXdsClient.java | 163 +- .../java/io/grpc/xds/CdsLoadBalancer2.java | 13 +- .../java/io/grpc/xds/ClientXdsClient.java | 2348 +---------------- .../grpc/xds/ClusterResolverLoadBalancer.java | 10 +- xds/src/main/java/io/grpc/xds/XdsClient.java | 386 +-- .../java/io/grpc/xds/XdsClusterResource.java | 686 +++++ .../java/io/grpc/xds/XdsEndpointResource.java | 259 ++ .../java/io/grpc/xds/XdsListenerResource.java | 633 +++++ .../java/io/grpc/xds/XdsNameResolver.java | 24 +- .../java/io/grpc/xds/XdsResourceType.java | 308 +++ .../grpc/xds/XdsRouteConfigureResource.java | 707 +++++ .../java/io/grpc/xds/XdsServerWrapper.java | 24 +- .../io/grpc/xds/CdsLoadBalancer2Test.java | 24 +- .../io/grpc/xds/ClientXdsClientDataTest.java | 299 +-- .../io/grpc/xds/ClientXdsClientTestBase.java | 503 ++-- .../io/grpc/xds/ClientXdsClientV2Test.java | 2 +- .../io/grpc/xds/ClientXdsClientV3Test.java | 2 +- .../xds/ClusterResolverLoadBalancerTest.java | 23 +- .../XdsClientWrapperForServerSdsTestMisc.java | 2 +- .../java/io/grpc/xds/XdsNameResolverTest.java | 74 +- .../io/grpc/xds/XdsSdsClientServerTest.java | 2 +- .../java/io/grpc/xds/XdsServerTestHelper.java | 59 +- .../io/grpc/xds/XdsServerWrapperTest.java | 14 +- 23 files changed, 3392 insertions(+), 3173 deletions(-) create mode 100644 xds/src/main/java/io/grpc/xds/XdsClusterResource.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsEndpointResource.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsListenerResource.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsResourceType.java create mode 100644 xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java diff --git a/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java b/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java index d559c05440..9fa25ba2cf 100644 --- a/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/AbstractXdsClient.java @@ -19,6 +19,14 @@ package io.grpc.xds; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import static io.grpc.xds.XdsClusterResource.ADS_TYPE_URL_CDS; +import static io.grpc.xds.XdsClusterResource.ADS_TYPE_URL_CDS_V2; +import static io.grpc.xds.XdsEndpointResource.ADS_TYPE_URL_EDS; +import static io.grpc.xds.XdsEndpointResource.ADS_TYPE_URL_EDS_V2; +import static io.grpc.xds.XdsListenerResource.ADS_TYPE_URL_LDS; +import static io.grpc.xds.XdsListenerResource.ADS_TYPE_URL_LDS_V2; +import static io.grpc.xds.XdsRouteConfigureResource.ADS_TYPE_URL_RDS; +import static io.grpc.xds.XdsRouteConfigureResource.ADS_TYPE_URL_RDS_V2; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; @@ -41,11 +49,14 @@ import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClientXdsClient.XdsChannelFactory; import io.grpc.xds.EnvoyProtoData.Node; import io.grpc.xds.XdsClient.ResourceStore; +import io.grpc.xds.XdsClient.ResourceUpdate; import io.grpc.xds.XdsClient.XdsResponseHandler; import io.grpc.xds.XdsLogger.XdsLogLevel; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -55,23 +66,6 @@ import javax.annotation.Nullable; * the xDS RPC stream. */ final class AbstractXdsClient { - - private static final String ADS_TYPE_URL_LDS_V2 = "type.googleapis.com/envoy.api.v2.Listener"; - private static final String ADS_TYPE_URL_LDS = - "type.googleapis.com/envoy.config.listener.v3.Listener"; - private static final String ADS_TYPE_URL_RDS_V2 = - "type.googleapis.com/envoy.api.v2.RouteConfiguration"; - private static final String ADS_TYPE_URL_RDS = - "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"; - @VisibleForTesting - static final String ADS_TYPE_URL_CDS_V2 = "type.googleapis.com/envoy.api.v2.Cluster"; - private static final String ADS_TYPE_URL_CDS = - "type.googleapis.com/envoy.config.cluster.v3.Cluster"; - private static final String ADS_TYPE_URL_EDS_V2 = - "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"; - private static final String ADS_TYPE_URL_EDS = - "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; - private final SynchronizationContext syncContext; private final InternalLogId logId; private final XdsLogger logger; @@ -88,10 +82,7 @@ final class AbstractXdsClient { // Last successfully applied version_info for each resource type. Starts with empty string. // A version_info is used to update management server with client's most recent knowledge of // resources. - private String ldsVersion = ""; - private String rdsVersion = ""; - private String cdsVersion = ""; - private String edsVersion = ""; + private final Map versions = new HashMap<>(); private boolean shutdown; @Nullable @@ -162,16 +153,17 @@ final class AbstractXdsClient { * Updates the resource subscription for the given resource type. */ // Must be synchronized. - void adjustResourceSubscription(ResourceType type) { + void adjustResourceSubscription(XdsResourceType resourceType) { if (isInBackoff()) { return; } if (adsStream == null) { startRpcStream(); } - Collection resources = resourceStore.getSubscribedResources(serverInfo, type); + Collection resources = resourceStore.getSubscribedResources(serverInfo, + resourceType.typeName()); if (resources != null) { - adsStream.sendDiscoveryRequest(type, resources); + adsStream.sendDiscoveryRequest(resourceType, resources); } } @@ -180,24 +172,9 @@ final class AbstractXdsClient { * and sends an ACK request to the management server. */ // Must be synchronized. - void ackResponse(ResourceType type, String versionInfo, String nonce) { - switch (type) { - case LDS: - ldsVersion = versionInfo; - break; - case RDS: - rdsVersion = versionInfo; - break; - case CDS: - cdsVersion = versionInfo; - break; - case EDS: - edsVersion = versionInfo; - break; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type: " + type); - } + void ackResponse(XdsResourceType xdsResourceType, String versionInfo, String nonce) { + ResourceType type = xdsResourceType.typeName(); + versions.put(type, versionInfo); logger.log(XdsLogLevel.INFO, "Sending ACK for {0} update, nonce: {1}, current version: {2}", type, nonce, versionInfo); Collection resources = resourceStore.getSubscribedResources(serverInfo, type); @@ -212,8 +189,9 @@ final class AbstractXdsClient { * accepted version) to the management server. */ // Must be synchronized. - void nackResponse(ResourceType type, String nonce, String errorDetail) { - String versionInfo = getCurrentVersion(type); + void nackResponse(XdsResourceType xdsResourceType, String nonce, String errorDetail) { + ResourceType type = xdsResourceType.typeName(); + String versionInfo = versions.getOrDefault(type, ""); logger.log(XdsLogLevel.INFO, "Sending NACK for {0} update, nonce: {1}, current version: {2}", type, nonce, versionInfo); Collection resources = resourceStore.getSubscribedResources(serverInfo, type); @@ -253,30 +231,6 @@ final class AbstractXdsClient { stopwatch.reset().start(); } - /** Returns the latest accepted version of the given resource type. */ - // Must be synchronized. - String getCurrentVersion(ResourceType type) { - String version; - switch (type) { - case LDS: - version = ldsVersion; - break; - case RDS: - version = rdsVersion; - break; - case CDS: - version = cdsVersion; - break; - case EDS: - version = edsVersion; - break; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type: " + type); - } - return version; - } - @VisibleForTesting final class RpcRetryTask implements Runnable { @Override @@ -291,13 +245,14 @@ final class AbstractXdsClient { } Collection resources = resourceStore.getSubscribedResources(serverInfo, type); if (resources != null) { - adsStream.sendDiscoveryRequest(type, resources); + adsStream.sendDiscoveryRequest(resourceStore.getXdsResourceType(type), resources); } } xdsResponseHandler.handleStreamRestarted(serverInfo); } } + // TODO(zivy) : remove and replace with XdsResourceType enum ResourceType { UNKNOWN, LDS, RDS, CDS, EDS; @@ -361,17 +316,13 @@ final class AbstractXdsClient { private abstract class AbstractAdsStream { private boolean responseReceived; private boolean closed; - // Response nonce for the most recently received discovery responses of each resource type. // Client initiated requests start response nonce with empty string. - // A nonce is used to indicate the specific DiscoveryResponse each DiscoveryRequest - // corresponds to. - // A nonce becomes stale following a newer nonce being presented to the client in a - // DiscoveryResponse. - private String ldsRespNonce = ""; - private String rdsRespNonce = ""; - private String cdsRespNonce = ""; - private String edsRespNonce = ""; + // Nonce in each response is echoed back in the following ACK/NACK request. It is + // used for management server to identify which response the client is ACKing/NACking. + // To avoid confusion, client-initiated requests will always use the nonce in + // most recently received responses of each resource type. + private final Map respNonces = new HashMap<>(); abstract void start(); @@ -381,35 +332,20 @@ final class AbstractXdsClient { * Sends a discovery request with the given {@code versionInfo}, {@code nonce} and * {@code errorDetail}. Used for reacting to a specific discovery response. For * client-initiated discovery requests, use {@link - * #sendDiscoveryRequest(ResourceType, Collection)}. + * #sendDiscoveryRequest(XdsResourceType, Collection)}. */ - abstract void sendDiscoveryRequest(ResourceType type, String versionInfo, + abstract void sendDiscoveryRequest(ResourceType type, String version, Collection resources, String nonce, @Nullable String errorDetail); /** * Sends a client-initiated discovery request. */ - final void sendDiscoveryRequest(ResourceType type, Collection resources) { - String nonce; - switch (type) { - case LDS: - nonce = ldsRespNonce; - break; - case RDS: - nonce = rdsRespNonce; - break; - case CDS: - nonce = cdsRespNonce; - break; - case EDS: - nonce = edsRespNonce; - break; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type: " + type); - } + final void sendDiscoveryRequest(XdsResourceType xdsResourceType, + Collection resources) { + ResourceType type = xdsResourceType.typeName(); logger.log(XdsLogLevel.INFO, "Sending {0} request for resources: {1}", type, resources); - sendDiscoveryRequest(type, getCurrentVersion(type), resources, nonce, null); + sendDiscoveryRequest(type, versions.getOrDefault(type, ""), resources, + respNonces.getOrDefault(type, ""), null); } final void handleRpcResponse( @@ -418,31 +354,8 @@ final class AbstractXdsClient { return; } responseReceived = true; - // Nonce in each response is echoed back in the following ACK/NACK request. It is - // used for management server to identify which response the client is ACKing/NACking. - // To avoid confusion, client-initiated requests will always use the nonce in - // most recently received responses of each resource type. - switch (type) { - case LDS: - ldsRespNonce = nonce; - xdsResponseHandler.handleLdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case RDS: - rdsRespNonce = nonce; - xdsResponseHandler.handleRdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case CDS: - cdsRespNonce = nonce; - xdsResponseHandler.handleCdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case EDS: - edsRespNonce = nonce; - xdsResponseHandler.handleEdsResponse(serverInfo, versionInfo, resources, nonce); - break; - case UNKNOWN: - default: - logger.log(XdsLogLevel.WARNING, "Ignore an unknown type of DiscoveryResponse"); - } + respNonces.put(type, nonce); + xdsResponseHandler.handleResourceResponse(type, serverInfo, versionInfo, resources, nonce); } final void handleRpcError(Throwable t) { diff --git a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java index 4fa701c3c4..afbb21008e 100644 --- a/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java +++ b/xds/src/main/java/io/grpc/xds/CdsLoadBalancer2.java @@ -35,9 +35,9 @@ import io.grpc.internal.ServiceConfigUtil.PolicySelection; import io.grpc.xds.CdsLoadBalancerProvider.CdsConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig; import io.grpc.xds.ClusterResolverLoadBalancerProvider.ClusterResolverConfig.DiscoveryMechanism; -import io.grpc.xds.XdsClient.CdsResourceWatcher; -import io.grpc.xds.XdsClient.CdsUpdate; -import io.grpc.xds.XdsClient.CdsUpdate.ClusterType; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; import java.util.ArrayDeque; @@ -221,7 +221,7 @@ final class CdsLoadBalancer2 extends LoadBalancer { } } - private final class ClusterState implements CdsResourceWatcher { + private final class ClusterState implements ResourceWatcher { private final String name; @Nullable private Map childClusterStates; @@ -237,12 +237,12 @@ final class CdsLoadBalancer2 extends LoadBalancer { } private void start() { - xdsClient.watchCdsResource(name, this); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), name, this); } void shutdown() { shutdown = true; - xdsClient.cancelCdsResourceWatch(name, this); + xdsClient.cancelXdsResourceWatch(XdsClusterResource.getInstance(), name, this); if (childClusterStates != null) { // recursively shut down all descendants for (ClusterState state : childClusterStates.values()) { state.shutdown(); @@ -300,6 +300,7 @@ final class CdsLoadBalancer2 extends LoadBalancer { if (shutdown) { return; } + logger.log(XdsLogLevel.DEBUG, "Received cluster update {0}", update); discovered = true; result = update; diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java index 55593eb99a..80e70d6d1c 100644 --- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java +++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java @@ -18,110 +18,50 @@ package io.grpc.xds; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static io.grpc.xds.AbstractXdsClient.ResourceType.CDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.LDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.RDS; import static io.grpc.xds.Bootstrapper.XDSTP_SCHEME; +import static io.grpc.xds.XdsResourceType.ParsedResource; +import static io.grpc.xds.XdsResourceType.ValidatedResourceUpdate; -import com.github.udpa.udpa.type.v1.TypedStruct; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; -import com.google.common.base.Splitter; import com.google.common.base.Stopwatch; -import com.google.common.base.Strings; import com.google.common.base.Supplier; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.protobuf.Any; -import com.google.protobuf.Duration; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.protobuf.util.Durations; -import com.google.re2j.Pattern; -import com.google.re2j.PatternSyntaxException; -import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds; -import io.envoyproxy.envoy.config.cluster.v3.Cluster; -import io.envoyproxy.envoy.config.cluster.v3.Cluster.CustomClusterType; -import io.envoyproxy.envoy.config.cluster.v3.Cluster.DiscoveryType; -import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions; -import io.envoyproxy.envoy.config.core.v3.RoutingPriority; -import io.envoyproxy.envoy.config.core.v3.SocketAddress; -import io.envoyproxy.envoy.config.core.v3.SocketAddress.PortSpecifierCase; -import io.envoyproxy.envoy.config.core.v3.TrafficDirection; -import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; -import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; -import io.envoyproxy.envoy.config.listener.v3.Listener; -import io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin; -import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff; -import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; -import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; -import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; -import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext; -import io.envoyproxy.envoy.service.discovery.v3.Resource; -import io.envoyproxy.envoy.type.v3.FractionalPercent; -import io.envoyproxy.envoy.type.v3.FractionalPercent.DenominatorType; import io.grpc.ChannelCredentials; import io.grpc.Context; -import io.grpc.EquivalentAddressGroup; import io.grpc.Grpc; import io.grpc.InternalLogId; import io.grpc.LoadBalancerRegistry; import io.grpc.ManagedChannel; -import io.grpc.NameResolver; import io.grpc.Status; -import io.grpc.Status.Code; import io.grpc.SynchronizationContext; import io.grpc.SynchronizationContext.ScheduledHandle; import io.grpc.internal.BackoffPolicy; -import io.grpc.internal.ServiceConfigUtil; -import io.grpc.internal.ServiceConfigUtil.LbConfig; import io.grpc.internal.TimeProvider; import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.AuthorityInfo; import io.grpc.xds.Bootstrapper.ServerInfo; -import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; -import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; -import io.grpc.xds.Endpoints.DropOverload; -import io.grpc.xds.Endpoints.LbEndpoint; -import io.grpc.xds.Endpoints.LocalityLbEndpoints; -import io.grpc.xds.EnvoyServerProtoData.CidrRange; -import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; -import io.grpc.xds.EnvoyServerProtoData.FilterChain; -import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; -import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; -import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; -import io.grpc.xds.Filter.ClientInterceptorBuilder; -import io.grpc.xds.Filter.FilterConfig; -import io.grpc.xds.Filter.NamedFilterConfig; -import io.grpc.xds.Filter.ServerInterceptorBuilder; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; -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.RouteAction.HashPolicy; -import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; -import io.grpc.xds.VirtualHost.Route.RouteMatch; -import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; import io.grpc.xds.XdsClient.ResourceStore; import io.grpc.xds.XdsClient.XdsResponseHandler; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; -import io.grpc.xds.internal.Matchers.FractionMatcher; -import io.grpc.xds.internal.Matchers.HeaderMatcher; -import java.net.InetSocketAddress; import java.net.URI; -import java.net.UnknownHostException; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -137,69 +77,6 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res // Longest time to wait, since the subscription to some resource, for concluding its absence. @VisibleForTesting static final int INITIAL_RESOURCE_FETCH_TIMEOUT_SEC = 15; - private static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; - @VisibleForTesting - static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate"; - @VisibleForTesting - static final String HASH_POLICY_FILTER_STATE_KEY = "io.grpc.channel_id"; - @VisibleForTesting - static boolean enableFaultInjection = - Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION")) - || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION")); - @VisibleForTesting - static boolean enableRetry = - Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY")) - || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY")); - @VisibleForTesting - static boolean enableRbac = - Strings.isNullOrEmpty(System.getenv("GRPC_XDS_EXPERIMENTAL_RBAC")) - || Boolean.parseBoolean(System.getenv("GRPC_XDS_EXPERIMENTAL_RBAC")); - @VisibleForTesting - static boolean enableRouteLookup = - !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_RLS_LB")) - && Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_RLS_LB")); - @VisibleForTesting - static boolean enableLeastRequest = - !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) - ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) - : Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest")); - @VisibleForTesting - static boolean enableCustomLbConfig = - Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")) - || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG")); - @VisibleForTesting - static boolean enableOutlierDetection = - Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")) - || Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION")); - private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = - "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" - + ".HttpConnectionManager"; - static final String TYPE_URL_HTTP_CONNECTION_MANAGER = - "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3" - + ".HttpConnectionManager"; - private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT = - "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"; - private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 = - "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext"; - private static final String TYPE_URL_CLUSTER_CONFIG_V2 = - "type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig"; - private static final String TYPE_URL_CLUSTER_CONFIG = - "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig"; - private static final String TYPE_URL_TYPED_STRUCT_UDPA = - "type.googleapis.com/udpa.type.v1.TypedStruct"; - private static final String TYPE_URL_TYPED_STRUCT = - "type.googleapis.com/xds.type.v3.TypedStruct"; - private static final String TYPE_URL_FILTER_CONFIG = - "type.googleapis.com/envoy.config.route.v3.FilterConfig"; - private static final String TYPE_URL_RESOURCE_V2 = "type.googleapis.com/envoy.api.v2.Resource"; - private static final String TYPE_URL_RESOURCE_V3 = - "type.googleapis.com/envoy.service.discovery.v3.Resource"; - // TODO(zdapeng): need to discuss how to handle unsupported values. - private static final Set SUPPORTED_RETRYABLE_CODES = - Collections.unmodifiableSet(EnumSet.of( - Code.CANCELLED, Code.DEADLINE_EXCEEDED, Code.INTERNAL, Code.RESOURCE_EXHAUSTED, - Code.UNAVAILABLE)); - private final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @Override @@ -216,10 +93,15 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res private final LoadBalancerRegistry loadBalancerRegistry = LoadBalancerRegistry.getDefaultRegistry(); private final Map serverChannelMap = new HashMap<>(); - private final Map ldsResourceSubscribers = new HashMap<>(); - private final Map rdsResourceSubscribers = new HashMap<>(); - private final Map cdsResourceSubscribers = new HashMap<>(); - private final Map edsResourceSubscribers = new HashMap<>(); + private final Map, + Map>> + resourceSubscribers = new HashMap<>(); + private final Map> xdsResourceTypeMap = + ImmutableMap.of( + LDS, XdsListenerResource.getInstance(), + RDS, XdsRouteConfigureResource.getInstance(), + CDS, XdsClusterResource.getInstance(), + EDS, XdsEndpointResource.getInstance()); private final LoadStatsManager2 loadStatsManager; private final Map serverLrsClientMap = new HashMap<>(); private final XdsChannelFactory xdsChannelFactory; @@ -282,1834 +164,46 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res serverLrsClientMap.put(serverInfo, lrsClient); } - private Any maybeUnwrapResources(Any resource) - throws InvalidProtocolBufferException { - if (resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V2) - || resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V3)) { - return unpackCompatibleType(resource, Resource.class, TYPE_URL_RESOURCE_V3, - TYPE_URL_RESOURCE_V2).getResource(); - } else { - return resource; - } - } - @Override - public void handleLdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { + public void handleResourceResponse( + ResourceType resourceType, ServerInfo serverInfo, String versionInfo, List resources, + String nonce) { syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - Set retainedRdsResources = new HashSet<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - boolean isResourceV3; - Listener listener; - try { - resource = maybeUnwrapResources(resource); - // Unpack the Listener. - isResourceV3 = resource.getTypeUrl().equals(ResourceType.LDS.typeUrl()); - listener = unpackCompatibleType(resource, Listener.class, ResourceType.LDS.typeUrl(), - ResourceType.LDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add("LDS response Resource index " + i + " - can't decode Listener: " + e); - continue; - } - if (!isResourceNameValid(listener.getName(), resource.getTypeUrl())) { - errors.add( - "Unsupported resource name: " + listener.getName() + " for type: " + ResourceType.LDS); - continue; - } - String listenerName = canonifyResourceName(listener.getName()); - unpackedResources.add(listenerName); - - // Process Listener into LdsUpdate. - LdsUpdate ldsUpdate; - try { - if (listener.hasApiListener()) { - ldsUpdate = processClientSideListener( - listener, retainedRdsResources, enableFaultInjection && isResourceV3); - } else { - ldsUpdate = processServerSideListener( - listener, retainedRdsResources, enableRbac && isResourceV3); - } - } catch (ResourceInvalidException e) { - errors.add( - "LDS response Listener '" + listenerName + "' validation error: " + e.getMessage()); - invalidResources.add(listenerName); - continue; - } - - // LdsUpdate parsed successfully. - parsedResources.put(listenerName, new ParsedResource(ldsUpdate, resource)); - } - logger.log(XdsLogLevel.INFO, - "Received LDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.LDS, parsedResources, invalidResources, retainedRdsResources, - versionInfo, nonce, errors); - } - - private LdsUpdate processClientSideListener( - Listener listener, Set rdsResources, boolean parseHttpFilter) - throws ResourceInvalidException { - // Unpack HttpConnectionManager from the Listener. - HttpConnectionManager hcm; - try { - hcm = unpackCompatibleType( - listener.getApiListener().getApiListener(), HttpConnectionManager.class, - TYPE_URL_HTTP_CONNECTION_MANAGER, TYPE_URL_HTTP_CONNECTION_MANAGER_V2); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException( - "Could not parse HttpConnectionManager config from ApiListener", e); - } - return LdsUpdate.forApiListener(parseHttpConnectionManager( - hcm, rdsResources, filterRegistry, parseHttpFilter, true /* isForClient */)); - } - - private LdsUpdate processServerSideListener( - Listener proto, Set rdsResources, boolean parseHttpFilter) - throws ResourceInvalidException { - Set certProviderInstances = null; - if (getBootstrapInfo() != null && getBootstrapInfo().certProviders() != null) { - certProviderInstances = getBootstrapInfo().certProviders().keySet(); - } - return LdsUpdate.forTcpListener(parseServerSideListener( - proto, rdsResources, tlsContextManager, filterRegistry, certProviderInstances, - parseHttpFilter)); - } - - @VisibleForTesting - static EnvoyServerProtoData.Listener parseServerSideListener( - Listener proto, Set rdsResources, TlsContextManager tlsContextManager, - FilterRegistry filterRegistry, Set certProviderInstances, boolean parseHttpFilter) - throws ResourceInvalidException { - if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND) - && !proto.getTrafficDirection().equals(TrafficDirection.UNSPECIFIED)) { - throw new ResourceInvalidException( - "Listener " + proto.getName() + " with invalid traffic direction: " - + proto.getTrafficDirection()); - } - if (!proto.getListenerFiltersList().isEmpty()) { - throw new ResourceInvalidException( - "Listener " + proto.getName() + " cannot have listener_filters"); - } - if (proto.hasUseOriginalDst()) { - throw new ResourceInvalidException( - "Listener " + proto.getName() + " cannot have use_original_dst set to true"); - } - - String address = null; - if (proto.getAddress().hasSocketAddress()) { - SocketAddress socketAddress = proto.getAddress().getSocketAddress(); - address = socketAddress.getAddress(); - switch (socketAddress.getPortSpecifierCase()) { - case NAMED_PORT: - address = address + ":" + socketAddress.getNamedPort(); - break; - case PORT_VALUE: - address = address + ":" + socketAddress.getPortValue(); - break; - default: - // noop - } - } - - ImmutableList.Builder filterChains = ImmutableList.builder(); - Set uniqueSet = new HashSet<>(); - for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) { - filterChains.add( - parseFilterChain(fc, rdsResources, tlsContextManager, filterRegistry, uniqueSet, - certProviderInstances, parseHttpFilter)); - } - FilterChain defaultFilterChain = null; - if (proto.hasDefaultFilterChain()) { - defaultFilterChain = parseFilterChain( - proto.getDefaultFilterChain(), rdsResources, tlsContextManager, filterRegistry, - null, certProviderInstances, parseHttpFilter); - } - - return EnvoyServerProtoData.Listener.create( - proto.getName(), address, filterChains.build(), defaultFilterChain); - } - - @VisibleForTesting - static FilterChain parseFilterChain( - io.envoyproxy.envoy.config.listener.v3.FilterChain proto, Set rdsResources, - TlsContextManager tlsContextManager, FilterRegistry filterRegistry, - Set uniqueSet, Set certProviderInstances, boolean parseHttpFilters) - throws ResourceInvalidException { - if (proto.getFiltersCount() != 1) { - throw new ResourceInvalidException("FilterChain " + proto.getName() - + " should contain exact one HttpConnectionManager filter"); - } - io.envoyproxy.envoy.config.listener.v3.Filter filter = proto.getFiltersList().get(0); - if (!filter.hasTypedConfig()) { - throw new ResourceInvalidException( - "FilterChain " + proto.getName() + " contains filter " + filter.getName() - + " without typed_config"); - } - Any any = filter.getTypedConfig(); - // HttpConnectionManager is the only supported network filter at the moment. - if (!any.getTypeUrl().equals(TYPE_URL_HTTP_CONNECTION_MANAGER)) { - throw new ResourceInvalidException( - "FilterChain " + proto.getName() + " contains filter " + filter.getName() - + " with unsupported typed_config type " + any.getTypeUrl()); - } - HttpConnectionManager hcmProto; - try { - hcmProto = any.unpack(HttpConnectionManager.class); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException("FilterChain " + proto.getName() + " with filter " - + filter.getName() + " failed to unpack message", e); - } - io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager( - hcmProto, rdsResources, filterRegistry, parseHttpFilters, false /* isForClient */); - - EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null; - if (proto.hasTransportSocket()) { - if (!TRANSPORT_SOCKET_NAME_TLS.equals(proto.getTransportSocket().getName())) { - throw new ResourceInvalidException("transport-socket with name " - + proto.getTransportSocket().getName() + " not supported."); - } - DownstreamTlsContext downstreamTlsContextProto; - try { - downstreamTlsContextProto = - proto.getTransportSocket().getTypedConfig().unpack(DownstreamTlsContext.class); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException("FilterChain " + proto.getName() - + " failed to unpack message", e); - } - downstreamTlsContext = - EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext( - validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances)); - } - - FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch()); - checkForUniqueness(uniqueSet, filterChainMatch); - return FilterChain.create( - proto.getName(), - filterChainMatch, - httpConnectionManager, - downstreamTlsContext, - tlsContextManager - ); - } - - @VisibleForTesting - static DownstreamTlsContext validateDownstreamTlsContext( - DownstreamTlsContext downstreamTlsContext, Set certProviderInstances) - throws ResourceInvalidException { - if (downstreamTlsContext.hasCommonTlsContext()) { - validateCommonTlsContext(downstreamTlsContext.getCommonTlsContext(), certProviderInstances, - true); - } else { - throw new ResourceInvalidException( - "common-tls-context is required in downstream-tls-context"); - } - if (downstreamTlsContext.hasRequireSni()) { - throw new ResourceInvalidException( - "downstream-tls-context with require-sni is not supported"); - } - DownstreamTlsContext.OcspStaplePolicy ocspStaplePolicy = downstreamTlsContext - .getOcspStaplePolicy(); - if (ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.UNRECOGNIZED - && ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.LENIENT_STAPLING) { - throw new ResourceInvalidException( - "downstream-tls-context with ocsp_staple_policy value " + ocspStaplePolicy.name() - + " is not supported"); - } - return downstreamTlsContext; - } - - @VisibleForTesting - static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext - validateUpstreamTlsContext( - io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext, - Set certProviderInstances) - throws ResourceInvalidException { - if (upstreamTlsContext.hasCommonTlsContext()) { - validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances, - false); - } else { - throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context"); - } - return upstreamTlsContext; - } - - @VisibleForTesting - static void validateCommonTlsContext( - CommonTlsContext commonTlsContext, Set certProviderInstances, boolean server) - throws ResourceInvalidException { - if (commonTlsContext.hasCustomHandshaker()) { - throw new ResourceInvalidException( - "common-tls-context with custom_handshaker is not supported"); - } - if (commonTlsContext.hasTlsParams()) { - throw new ResourceInvalidException("common-tls-context with tls_params is not supported"); - } - if (commonTlsContext.hasValidationContextSdsSecretConfig()) { - throw new ResourceInvalidException( - "common-tls-context with validation_context_sds_secret_config is not supported"); - } - if (commonTlsContext.hasValidationContextCertificateProvider()) { - throw new ResourceInvalidException( - "common-tls-context with validation_context_certificate_provider is not supported"); - } - if (commonTlsContext.hasValidationContextCertificateProviderInstance()) { - throw new ResourceInvalidException( - "common-tls-context with validation_context_certificate_provider_instance is not" - + " supported"); - } - String certInstanceName = getIdentityCertInstanceName(commonTlsContext); - if (certInstanceName == null) { - if (server) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is required in downstream-tls-context"); - } - if (commonTlsContext.getTlsCertificatesCount() > 0) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is unset"); - } - if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is unset"); - } - if (commonTlsContext.hasTlsCertificateCertificateProvider()) { - throw new ResourceInvalidException( - "tls_certificate_provider_instance is unset"); - } - } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) { - throw new ResourceInvalidException( - "CertificateProvider instance name '" + certInstanceName - + "' not defined in the bootstrap file."); - } - String rootCaInstanceName = getRootCertInstanceName(commonTlsContext); - if (rootCaInstanceName == null) { - if (!server) { - throw new ResourceInvalidException( - "ca_certificate_provider_instance is required in upstream-tls-context"); - } - } else { - if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) { - throw new ResourceInvalidException( - "ca_certificate_provider_instance name '" + rootCaInstanceName - + "' not defined in the bootstrap file."); - } - CertificateValidationContext certificateValidationContext = null; - if (commonTlsContext.hasValidationContext()) { - certificateValidationContext = commonTlsContext.getValidationContext(); - } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext - .getCombinedValidationContext().hasDefaultValidationContext()) { - certificateValidationContext = commonTlsContext.getCombinedValidationContext() - .getDefaultValidationContext(); - } - if (certificateValidationContext != null) { - if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) { - throw new ResourceInvalidException( - "match_subject_alt_names only allowed in upstream_tls_context"); - } - if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) { - throw new ResourceInvalidException( - "verify_certificate_spki in default_validation_context is not supported"); - } - if (certificateValidationContext.getVerifyCertificateHashCount() > 0) { - throw new ResourceInvalidException( - "verify_certificate_hash in default_validation_context is not supported"); - } - if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) { - throw new ResourceInvalidException( - "require_signed_certificate_timestamp in default_validation_context is not " - + "supported"); - } - if (certificateValidationContext.hasCrl()) { - throw new ResourceInvalidException("crl in default_validation_context is not supported"); - } - if (certificateValidationContext.hasCustomValidatorConfig()) { - throw new ResourceInvalidException( - "custom_validator_config in default_validation_context is not supported"); - } - } - } - } - - static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection( - io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection) - throws ResourceInvalidException { - if (outlierDetection.hasInterval()) { - if (!Durations.isValid(outlierDetection.getInterval())) { - throw new ResourceInvalidException("outlier_detection interval is not a valid Duration"); - } - if (hasNegativeValues(outlierDetection.getInterval())) { - throw new ResourceInvalidException("outlier_detection interval has a negative value"); - } - } - if (outlierDetection.hasBaseEjectionTime()) { - if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection base_ejection_time is not a valid Duration"); - } - if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection base_ejection_time has a negative value"); - } - } - if (outlierDetection.hasMaxEjectionTime()) { - if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection max_ejection_time is not a valid Duration"); - } - if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) { - throw new ResourceInvalidException( - "outlier_detection max_ejection_time has a negative value"); - } - } - if (outlierDetection.hasMaxEjectionPercent() - && outlierDetection.getMaxEjectionPercent().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection max_ejection_percent is > 100"); - } - if (outlierDetection.hasEnforcingSuccessRate() - && outlierDetection.getEnforcingSuccessRate().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection enforcing_success_rate is > 100"); - } - if (outlierDetection.hasFailurePercentageThreshold() - && outlierDetection.getFailurePercentageThreshold().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection failure_percentage_threshold is > 100"); - } - if (outlierDetection.hasEnforcingFailurePercentage() - && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) { - throw new ResourceInvalidException( - "outlier_detection enforcing_failure_percentage is > 100"); - } - - return outlierDetection; - } - - static boolean hasNegativeValues(Duration duration) { - return duration.getSeconds() < 0 || duration.getNanos() < 0; - } - - private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) { - if (commonTlsContext.hasTlsCertificateProviderInstance()) { - return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName(); - } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) { - return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName(); - } - return null; - } - - private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) { - if (commonTlsContext.hasValidationContext()) { - if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) { - return commonTlsContext.getValidationContext().getCaCertificateProviderInstance() - .getInstanceName(); - } - } else if (commonTlsContext.hasCombinedValidationContext()) { - CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext - = commonTlsContext.getCombinedValidationContext(); - if (combinedCertificateValidationContext.hasDefaultValidationContext() - && combinedCertificateValidationContext.getDefaultValidationContext() - .hasCaCertificateProviderInstance()) { - return combinedCertificateValidationContext.getDefaultValidationContext() - .getCaCertificateProviderInstance().getInstanceName(); - } else if (combinedCertificateValidationContext - .hasValidationContextCertificateProviderInstance()) { - return combinedCertificateValidationContext - .getValidationContextCertificateProviderInstance().getInstanceName(); - } - } - return null; - } - - private static void checkForUniqueness(Set uniqueSet, - FilterChainMatch filterChainMatch) throws ResourceInvalidException { - if (uniqueSet != null) { - List crossProduct = getCrossProduct(filterChainMatch); - for (FilterChainMatch cur : crossProduct) { - if (!uniqueSet.add(cur)) { - throw new ResourceInvalidException("FilterChainMatch must be unique. " - + "Found duplicate: " + cur); - } - } - } - } - - private static List getCrossProduct(FilterChainMatch filterChainMatch) { - // repeating fields to process: - // prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames - List expandedList = expandOnPrefixRange(filterChainMatch); - expandedList = expandOnApplicationProtocols(expandedList); - expandedList = expandOnSourcePrefixRange(expandedList); - expandedList = expandOnSourcePorts(expandedList); - return expandOnServerNames(expandedList); - } - - private static List expandOnPrefixRange(FilterChainMatch filterChainMatch) { - ArrayList expandedList = new ArrayList<>(); - if (filterChainMatch.prefixRanges().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.prefixRanges()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - ImmutableList.of(cidrRange), - filterChainMatch.applicationProtocols(), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - return expandedList; - } - - private static List expandOnApplicationProtocols( - Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.applicationProtocols().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (String applicationProtocol : filterChainMatch.applicationProtocols()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - ImmutableList.of(applicationProtocol), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static List expandOnSourcePrefixRange( - Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.sourcePrefixRanges().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.sourcePrefixRanges()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - filterChainMatch.applicationProtocols(), - ImmutableList.of(cidrRange), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static List expandOnSourcePorts(Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.sourcePorts().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (Integer sourcePort : filterChainMatch.sourcePorts()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - filterChainMatch.applicationProtocols(), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - ImmutableList.of(sourcePort), - filterChainMatch.serverNames(), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static List expandOnServerNames(Collection set) { - ArrayList expandedList = new ArrayList<>(); - for (FilterChainMatch filterChainMatch : set) { - if (filterChainMatch.serverNames().isEmpty()) { - expandedList.add(filterChainMatch); - } else { - for (String serverName : filterChainMatch.serverNames()) { - expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), - filterChainMatch.prefixRanges(), - filterChainMatch.applicationProtocols(), - filterChainMatch.sourcePrefixRanges(), - filterChainMatch.connectionSourceType(), - filterChainMatch.sourcePorts(), - ImmutableList.of(serverName), - filterChainMatch.transportProtocol())); - } - } - } - return expandedList; - } - - private static FilterChainMatch parseFilterChainMatch( - io.envoyproxy.envoy.config.listener.v3.FilterChainMatch proto) - throws ResourceInvalidException { - ImmutableList.Builder prefixRanges = ImmutableList.builder(); - ImmutableList.Builder sourcePrefixRanges = ImmutableList.builder(); - try { - for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getPrefixRangesList()) { - prefixRanges.add( - CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); - } - for (io.envoyproxy.envoy.config.core.v3.CidrRange range - : proto.getSourcePrefixRangesList()) { - sourcePrefixRanges.add( - CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); - } - } catch (UnknownHostException e) { - throw new ResourceInvalidException("Failed to create CidrRange", e); - } - ConnectionSourceType sourceType; - switch (proto.getSourceType()) { - case ANY: - sourceType = ConnectionSourceType.ANY; - break; - case EXTERNAL: - sourceType = ConnectionSourceType.EXTERNAL; - break; - case SAME_IP_OR_LOOPBACK: - sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK; - break; - default: - throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType()); - } - return FilterChainMatch.create( - proto.getDestinationPort().getValue(), - prefixRanges.build(), - ImmutableList.copyOf(proto.getApplicationProtocolsList()), - sourcePrefixRanges.build(), - sourceType, - ImmutableList.copyOf(proto.getSourcePortsList()), - ImmutableList.copyOf(proto.getServerNamesList()), - proto.getTransportProtocol()); - } - - @VisibleForTesting - static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( - HttpConnectionManager proto, Set rdsResources, FilterRegistry filterRegistry, - boolean parseHttpFilter, boolean isForClient) throws ResourceInvalidException { - if (enableRbac && proto.getXffNumTrustedHops() != 0) { - throw new ResourceInvalidException( - "HttpConnectionManager with xff_num_trusted_hops unsupported"); - } - if (enableRbac && !proto.getOriginalIpDetectionExtensionsList().isEmpty()) { - throw new ResourceInvalidException("HttpConnectionManager with " - + "original_ip_detection_extensions unsupported"); - } - // Obtain max_stream_duration from Http Protocol Options. - long maxStreamDuration = 0; - if (proto.hasCommonHttpProtocolOptions()) { - HttpProtocolOptions options = proto.getCommonHttpProtocolOptions(); - if (options.hasMaxStreamDuration()) { - maxStreamDuration = Durations.toNanos(options.getMaxStreamDuration()); - } - } - - // Parse http filters. - List filterConfigs = null; - if (parseHttpFilter) { - if (proto.getHttpFiltersList().isEmpty()) { - throw new ResourceInvalidException("Missing HttpFilter in HttpConnectionManager."); - } - filterConfigs = new ArrayList<>(); - Set names = new HashSet<>(); - for (int i = 0; i < proto.getHttpFiltersCount(); i++) { - io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter - httpFilter = proto.getHttpFiltersList().get(i); - String filterName = httpFilter.getName(); - if (!names.add(filterName)) { - throw new ResourceInvalidException( - "HttpConnectionManager contains duplicate HttpFilter: " + filterName); - } - StructOrError filterConfig = - parseHttpFilter(httpFilter, filterRegistry, isForClient); - if ((i == proto.getHttpFiltersCount() - 1) - && (filterConfig == null || !isTerminalFilter(filterConfig.struct))) { - throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: " - + filterName); - } - if (filterConfig == null) { - continue; - } - if (filterConfig.getErrorDetail() != null) { - throw new ResourceInvalidException( - "HttpConnectionManager contains invalid HttpFilter: " - + filterConfig.getErrorDetail()); - } - if ((i < proto.getHttpFiltersCount() - 1) && isTerminalFilter(filterConfig.getStruct())) { - throw new ResourceInvalidException("A terminal HttpFilter must be the last filter: " - + filterName); - } - filterConfigs.add(new NamedFilterConfig(filterName, filterConfig.struct)); - } - } - - // Parse inlined RouteConfiguration or RDS. - if (proto.hasRouteConfig()) { - List virtualHosts = extractVirtualHosts( - proto.getRouteConfig(), filterRegistry, parseHttpFilter); - return io.grpc.xds.HttpConnectionManager.forVirtualHosts( - maxStreamDuration, virtualHosts, filterConfigs); - } - if (proto.hasRds()) { - Rds rds = proto.getRds(); - if (!rds.hasConfigSource()) { - throw new ResourceInvalidException( - "HttpConnectionManager contains invalid RDS: missing config_source"); - } - if (!rds.getConfigSource().hasAds() && !rds.getConfigSource().hasSelf()) { - throw new ResourceInvalidException( - "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); - } - // Collect the RDS resource referenced by this HttpConnectionManager. - rdsResources.add(rds.getRouteConfigName()); - return io.grpc.xds.HttpConnectionManager.forRdsName( - maxStreamDuration, rds.getRouteConfigName(), filterConfigs); - } - throw new ResourceInvalidException( - "HttpConnectionManager neither has inlined route_config nor RDS"); - } - - // hard-coded: currently router config is the only terminal filter. - private static boolean isTerminalFilter(FilterConfig filterConfig) { - return RouterFilter.ROUTER_CONFIG.equals(filterConfig); - } - - @VisibleForTesting - @Nullable // Returns null if the filter is optional but not supported. - static StructOrError parseHttpFilter( - io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter - httpFilter, FilterRegistry filterRegistry, boolean isForClient) { - String filterName = httpFilter.getName(); - boolean isOptional = httpFilter.getIsOptional(); - if (!httpFilter.hasTypedConfig()) { - if (isOptional) { - return null; - } else { - return StructOrError.fromError( - "HttpFilter [" + filterName + "] is not optional and has no typed config"); - } - } - Message rawConfig = httpFilter.getTypedConfig(); - String typeUrl = httpFilter.getTypedConfig().getTypeUrl(); - - try { - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { - TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class); - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); - } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - com.github.xds.type.v3.TypedStruct newTypedStruct = - httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class); - typeUrl = newTypedStruct.getTypeUrl(); - rawConfig = newTypedStruct.getValue(); - } - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "HttpFilter [" + filterName + "] contains invalid proto: " + e); - } - Filter filter = filterRegistry.get(typeUrl); - if ((isForClient && !(filter instanceof ClientInterceptorBuilder)) - || (!isForClient && !(filter instanceof ServerInterceptorBuilder))) { - if (isOptional) { - return null; - } else { - return StructOrError.fromError( - "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " - + (isForClient ? "client" : "server")); - } - } - ConfigOrError filterConfig = filter.parseFilterConfig(rawConfig); - if (filterConfig.errorDetail != null) { - return StructOrError.fromError( - "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail); - } - return StructOrError.fromStruct(filterConfig.config); - } - - private static StructOrError parseVirtualHost( - io.envoyproxy.envoy.config.route.v3.VirtualHost proto, FilterRegistry filterRegistry, - boolean parseHttpFilter, Map pluginConfigMap, - Set optionalPlugins) { - String name = proto.getName(); - List routes = new ArrayList<>(proto.getRoutesCount()); - for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) { - StructOrError route = parseRoute( - routeProto, filterRegistry, parseHttpFilter, pluginConfigMap, optionalPlugins); - if (route == null) { - continue; - } - if (route.getErrorDetail() != null) { - return StructOrError.fromError( - "Virtual host [" + name + "] contains invalid route : " + route.getErrorDetail()); - } - routes.add(route.getStruct()); - } - if (!parseHttpFilter) { - return StructOrError.fromStruct(VirtualHost.create( - name, proto.getDomainsList(), routes, new HashMap())); - } - StructOrError> overrideConfigs = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); - if (overrideConfigs.errorDetail != null) { - return StructOrError.fromError( - "VirtualHost [" + proto.getName() + "] contains invalid HttpFilter config: " - + overrideConfigs.errorDetail); - } - return StructOrError.fromStruct(VirtualHost.create( - name, proto.getDomainsList(), routes, overrideConfigs.struct)); - } - - @VisibleForTesting - static StructOrError> parseOverrideFilterConfigs( - Map rawFilterConfigMap, FilterRegistry filterRegistry) { - Map overrideConfigs = new HashMap<>(); - for (String name : rawFilterConfigMap.keySet()) { - Any anyConfig = rawFilterConfigMap.get(name); - String typeUrl = anyConfig.getTypeUrl(); - boolean isOptional = false; - if (typeUrl.equals(TYPE_URL_FILTER_CONFIG)) { - io.envoyproxy.envoy.config.route.v3.FilterConfig filterConfig; - try { - filterConfig = - anyConfig.unpack(io.envoyproxy.envoy.config.route.v3.FilterConfig.class); - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "FilterConfig [" + name + "] contains invalid proto: " + e); - } - isOptional = filterConfig.getIsOptional(); - anyConfig = filterConfig.getConfig(); - typeUrl = anyConfig.getTypeUrl(); - } - Message rawConfig = anyConfig; - try { - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { - TypedStruct typedStruct = anyConfig.unpack(TypedStruct.class); - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); - } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - com.github.xds.type.v3.TypedStruct newTypedStruct = - anyConfig.unpack(com.github.xds.type.v3.TypedStruct.class); - typeUrl = newTypedStruct.getTypeUrl(); - rawConfig = newTypedStruct.getValue(); - } - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError( - "FilterConfig [" + name + "] contains invalid proto: " + e); - } - Filter filter = filterRegistry.get(typeUrl); - if (filter == null) { - if (isOptional) { - continue; - } - return StructOrError.fromError( - "HttpFilter [" + name + "](" + typeUrl + ") is required but unsupported"); - } - ConfigOrError filterConfig = - filter.parseFilterConfigOverride(rawConfig); - if (filterConfig.errorDetail != null) { - return StructOrError.fromError( - "Invalid filter config for HttpFilter [" + name + "]: " + filterConfig.errorDetail); - } - overrideConfigs.put(name, filterConfig.config); - } - return StructOrError.fromStruct(overrideConfigs); - } - - @VisibleForTesting - @Nullable - static StructOrError parseRoute( - io.envoyproxy.envoy.config.route.v3.Route proto, FilterRegistry filterRegistry, - boolean parseHttpFilter, Map pluginConfigMap, - Set optionalPlugins) { - StructOrError routeMatch = parseRouteMatch(proto.getMatch()); - if (routeMatch == null) { - return null; - } - if (routeMatch.getErrorDetail() != null) { - return StructOrError.fromError( - "Route [" + proto.getName() + "] contains invalid RouteMatch: " - + routeMatch.getErrorDetail()); - } - - Map overrideConfigs = Collections.emptyMap(); - if (parseHttpFilter) { - StructOrError> overrideConfigsOrError = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); - if (overrideConfigsOrError.errorDetail != null) { - return StructOrError.fromError( - "Route [" + proto.getName() + "] contains invalid HttpFilter config: " - + overrideConfigsOrError.errorDetail); - } - overrideConfigs = overrideConfigsOrError.struct; - } - - switch (proto.getActionCase()) { - case ROUTE: - StructOrError routeAction = - parseRouteAction(proto.getRoute(), filterRegistry, parseHttpFilter, pluginConfigMap, - optionalPlugins); - if (routeAction == null) { - return null; - } - if (routeAction.errorDetail != null) { - return StructOrError.fromError( - "Route [" + proto.getName() + "] contains invalid RouteAction: " - + routeAction.getErrorDetail()); - } - return StructOrError.fromStruct( - Route.forAction(routeMatch.struct, routeAction.struct, overrideConfigs)); - case NON_FORWARDING_ACTION: - return StructOrError.fromStruct( - Route.forNonForwardingAction(routeMatch.struct, overrideConfigs)); - case REDIRECT: - case DIRECT_RESPONSE: - case FILTER_ACTION: - case ACTION_NOT_SET: - default: - return StructOrError.fromError( - "Route [" + proto.getName() + "] with unknown action type: " + proto.getActionCase()); - } - } - - @VisibleForTesting - @Nullable - static StructOrError parseRouteMatch( - io.envoyproxy.envoy.config.route.v3.RouteMatch proto) { - if (proto.getQueryParametersCount() != 0) { - return null; - } - StructOrError pathMatch = parsePathMatcher(proto); - if (pathMatch.getErrorDetail() != null) { - return StructOrError.fromError(pathMatch.getErrorDetail()); - } - - FractionMatcher fractionMatch = null; - if (proto.hasRuntimeFraction()) { - StructOrError parsedFraction = - parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue()); - if (parsedFraction.getErrorDetail() != null) { - return StructOrError.fromError(parsedFraction.getErrorDetail()); - } - fractionMatch = parsedFraction.getStruct(); - } - - List headerMatchers = new ArrayList<>(); - for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher hmProto : proto.getHeadersList()) { - StructOrError 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 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 parseFractionMatcher(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 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"); - } - } - - /** - * Parses the RouteAction config. The returned result may contain a (parsed form) - * {@link RouteAction} or an error message. Returns {@code null} if the RouteAction - * should be ignored. - */ - @VisibleForTesting - @Nullable - static StructOrError parseRouteAction( - io.envoyproxy.envoy.config.route.v3.RouteAction proto, FilterRegistry filterRegistry, - boolean parseHttpFilter, Map pluginConfigMap, - Set optionalPlugins) { - 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()); - } - } - RetryPolicy retryPolicy = null; - if (enableRetry && proto.hasRetryPolicy()) { - StructOrError retryPolicyOrError = parseRetryPolicy(proto.getRetryPolicy()); - if (retryPolicyOrError != null) { - if (retryPolicyOrError.errorDetail != null) { - return StructOrError.fromError(retryPolicyOrError.errorDetail); - } - retryPolicy = retryPolicyOrError.struct; - } - } - List hashPolicies = new ArrayList<>(); - for (io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy config - : proto.getHashPolicyList()) { - HashPolicy policy = null; - boolean terminal = config.getTerminal(); - switch (config.getPolicySpecifierCase()) { - case HEADER: - io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.Header headerCfg = - config.getHeader(); - Pattern regEx = null; - String regExSubstitute = null; - if (headerCfg.hasRegexRewrite() && headerCfg.getRegexRewrite().hasPattern() - && headerCfg.getRegexRewrite().getPattern().hasGoogleRe2()) { - regEx = Pattern.compile(headerCfg.getRegexRewrite().getPattern().getRegex()); - regExSubstitute = headerCfg.getRegexRewrite().getSubstitution(); - } - policy = HashPolicy.forHeader( - terminal, headerCfg.getHeaderName(), regEx, regExSubstitute); - break; - case FILTER_STATE: - if (config.getFilterState().getKey().equals(HASH_POLICY_FILTER_STATE_KEY)) { - policy = HashPolicy.forChannelId(terminal); - } - break; - default: - // Ignore - } - if (policy != null) { - hashPolicies.add(policy); - } - } - - switch (proto.getClusterSpecifierCase()) { - case CLUSTER: - return StructOrError.fromStruct(RouteAction.forCluster( - proto.getCluster(), hashPolicies, timeoutNano, retryPolicy)); - case CLUSTER_HEADER: - return null; - case WEIGHTED_CLUSTERS: - List clusterWeights - = proto.getWeightedClusters().getClustersList(); - if (clusterWeights.isEmpty()) { - return StructOrError.fromError("No cluster found in weighted cluster list"); - } - List weightedClusters = new ArrayList<>(); - for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight - : clusterWeights) { - StructOrError clusterWeightOrError = - parseClusterWeight(clusterWeight, filterRegistry, parseHttpFilter); - 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, hashPolicies, timeoutNano, retryPolicy)); - case CLUSTER_SPECIFIER_PLUGIN: - if (enableRouteLookup) { - String pluginName = proto.getClusterSpecifierPlugin(); - PluginConfig pluginConfig = pluginConfigMap.get(pluginName); - if (pluginConfig == null) { - // Skip route if the plugin is not registered, but it's optional. - if (optionalPlugins.contains(pluginName)) { - return null; - } - return StructOrError.fromError( - "ClusterSpecifierPlugin for [" + pluginName + "] not found"); - } - NamedPluginConfig namedPluginConfig = NamedPluginConfig.create(pluginName, pluginConfig); - return StructOrError.fromStruct(RouteAction.forClusterSpecifierPlugin( - namedPluginConfig, hashPolicies, timeoutNano, retryPolicy)); - } else { - return null; - } - case CLUSTERSPECIFIER_NOT_SET: - default: - return null; - } - } - - @Nullable // Return null if we ignore the given policy. - private static StructOrError parseRetryPolicy( - io.envoyproxy.envoy.config.route.v3.RetryPolicy retryPolicyProto) { - int maxAttempts = 2; - if (retryPolicyProto.hasNumRetries()) { - maxAttempts = retryPolicyProto.getNumRetries().getValue() + 1; - } - Duration initialBackoff = Durations.fromMillis(25); - Duration maxBackoff = Durations.fromMillis(250); - if (retryPolicyProto.hasRetryBackOff()) { - RetryBackOff retryBackOff = retryPolicyProto.getRetryBackOff(); - if (!retryBackOff.hasBaseInterval()) { - return StructOrError.fromError("No base_interval specified in retry_backoff"); - } - Duration originalInitialBackoff = initialBackoff = retryBackOff.getBaseInterval(); - if (Durations.compare(initialBackoff, Durations.ZERO) <= 0) { - return StructOrError.fromError("base_interval in retry_backoff must be positive"); - } - if (Durations.compare(initialBackoff, Durations.fromMillis(1)) < 0) { - initialBackoff = Durations.fromMillis(1); - } - if (retryBackOff.hasMaxInterval()) { - maxBackoff = retryPolicyProto.getRetryBackOff().getMaxInterval(); - if (Durations.compare(maxBackoff, originalInitialBackoff) < 0) { - return StructOrError.fromError( - "max_interval in retry_backoff cannot be less than base_interval"); - } - if (Durations.compare(maxBackoff, Durations.fromMillis(1)) < 0) { - maxBackoff = Durations.fromMillis(1); - } - } else { - maxBackoff = Durations.fromNanos(Durations.toNanos(initialBackoff) * 10); - } - } - Iterable retryOns = - Splitter.on(',').omitEmptyStrings().trimResults().split(retryPolicyProto.getRetryOn()); - ImmutableList.Builder retryableStatusCodesBuilder = ImmutableList.builder(); - for (String retryOn : retryOns) { - Code code; - try { - code = Code.valueOf(retryOn.toUpperCase(Locale.US).replace('-', '_')); - } catch (IllegalArgumentException e) { - // unsupported value, such as "5xx" - continue; - } - if (!SUPPORTED_RETRYABLE_CODES.contains(code)) { - // unsupported value - continue; - } - retryableStatusCodesBuilder.add(code); - } - List retryableStatusCodes = retryableStatusCodesBuilder.build(); - return StructOrError.fromStruct( - RetryPolicy.create( - maxAttempts, retryableStatusCodes, initialBackoff, maxBackoff, - /* perAttemptRecvTimeout= */ null)); - } - - @VisibleForTesting - static StructOrError parseClusterWeight( - io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto, - FilterRegistry filterRegistry, boolean parseHttpFilter) { - if (!parseHttpFilter) { - return StructOrError.fromStruct(ClusterWeight.create( - proto.getName(), proto.getWeight().getValue(), new HashMap())); - } - StructOrError> overrideConfigs = - parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); - if (overrideConfigs.errorDetail != null) { - return StructOrError.fromError( - "ClusterWeight [" + proto.getName() + "] contains invalid HttpFilter config: " - + overrideConfigs.errorDetail); - } - return StructOrError.fromStruct(ClusterWeight.create( - proto.getName(), proto.getWeight().getValue(), overrideConfigs.struct)); - } - - @Override - public void handleRdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { - syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - // Unpack the RouteConfiguration. - RouteConfiguration routeConfig; - try { - resource = maybeUnwrapResources(resource); - routeConfig = unpackCompatibleType(resource, RouteConfiguration.class, - ResourceType.RDS.typeUrl(), ResourceType.RDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add("RDS response Resource index " + i + " - can't decode RouteConfiguration: " + e); - continue; - } - if (!isResourceNameValid(routeConfig.getName(), resource.getTypeUrl())) { - errors.add( - "Unsupported resource name: " + routeConfig.getName() + " for type: " - + ResourceType.RDS); - continue; - } - String routeConfigName = canonifyResourceName(routeConfig.getName()); - unpackedResources.add(routeConfigName); - - // Process RouteConfiguration into RdsUpdate. - RdsUpdate rdsUpdate; - boolean isResourceV3 = resource.getTypeUrl().equals(ResourceType.RDS.typeUrl()); - try { - rdsUpdate = processRouteConfiguration( - routeConfig, filterRegistry, enableFaultInjection && isResourceV3); - } catch (ResourceInvalidException e) { - errors.add( - "RDS response RouteConfiguration '" + routeConfigName + "' validation error: " + e - .getMessage()); - invalidResources.add(routeConfigName); - continue; - } - - parsedResources.put(routeConfigName, new ParsedResource(rdsUpdate, resource)); - } - logger.log(XdsLogLevel.INFO, - "Received RDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.RDS, parsedResources, invalidResources, - Collections.emptySet(), versionInfo, nonce, errors); - } - - private static RdsUpdate processRouteConfiguration( - RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) - throws ResourceInvalidException { - return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry, parseHttpFilter)); - } - - private static List extractVirtualHosts( - RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) - throws ResourceInvalidException { - Map pluginConfigMap = new HashMap<>(); - ImmutableSet.Builder optionalPlugins = ImmutableSet.builder(); - - if (enableRouteLookup) { - List plugins = routeConfig.getClusterSpecifierPluginsList(); - for (ClusterSpecifierPlugin plugin : plugins) { - String pluginName = plugin.getExtension().getName(); - PluginConfig pluginConfig = parseClusterSpecifierPlugin(plugin); - if (pluginConfig != null) { - if (pluginConfigMap.put(pluginName, pluginConfig) != null) { - throw new ResourceInvalidException( - "Multiple ClusterSpecifierPlugins with the same name: " + pluginName); - } - } else { - // The plugin parsed successfully, and it's not supported, but it's marked as optional. - optionalPlugins.add(pluginName); - } - } - } - List virtualHosts = new ArrayList<>(routeConfig.getVirtualHostsCount()); - for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto - : routeConfig.getVirtualHostsList()) { - StructOrError virtualHost = - parseVirtualHost(virtualHostProto, filterRegistry, parseHttpFilter, pluginConfigMap, - optionalPlugins.build()); - if (virtualHost.getErrorDetail() != null) { - throw new ResourceInvalidException( - "RouteConfiguration contains invalid virtual host: " + virtualHost.getErrorDetail()); - } - virtualHosts.add(virtualHost.getStruct()); - } - return virtualHosts; - } - - @Nullable // null if the plugin is not supported, but it's marked as optional. - private static PluginConfig parseClusterSpecifierPlugin(ClusterSpecifierPlugin pluginProto) - throws ResourceInvalidException { - return parseClusterSpecifierPlugin( - pluginProto, ClusterSpecifierPluginRegistry.getDefaultRegistry()); - } - - @Nullable // null if the plugin is not supported, but it's marked as optional. - @VisibleForTesting - static PluginConfig parseClusterSpecifierPlugin( - ClusterSpecifierPlugin pluginProto, ClusterSpecifierPluginRegistry registry) - throws ResourceInvalidException { - TypedExtensionConfig extension = pluginProto.getExtension(); - String pluginName = extension.getName(); - Any anyConfig = extension.getTypedConfig(); - String typeUrl = anyConfig.getTypeUrl(); - Message rawConfig = anyConfig; - if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA) || typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { - try { - TypedStruct typedStruct = unpackCompatibleType( - anyConfig, TypedStruct.class, TYPE_URL_TYPED_STRUCT_UDPA, TYPE_URL_TYPED_STRUCT); - typeUrl = typedStruct.getTypeUrl(); - rawConfig = typedStruct.getValue(); - } catch (InvalidProtocolBufferException e) { - throw new ResourceInvalidException( - "ClusterSpecifierPlugin [" + pluginName + "] contains invalid proto", e); - } - } - io.grpc.xds.ClusterSpecifierPlugin plugin = registry.get(typeUrl); - if (plugin == null) { - if (!pluginProto.getIsOptional()) { - throw new ResourceInvalidException("Unsupported ClusterSpecifierPlugin type: " + typeUrl); - } - return null; - } - ConfigOrError pluginConfigOrError = plugin.parsePlugin(rawConfig); - if (pluginConfigOrError.errorDetail != null) { - throw new ResourceInvalidException(pluginConfigOrError.errorDetail); - } - return pluginConfigOrError.config; - } - - @Override - public void handleCdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { - syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - Set retainedEdsResources = new HashSet<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - // Unpack the Cluster. - Cluster cluster; - try { - resource = maybeUnwrapResources(resource); - cluster = unpackCompatibleType( - resource, Cluster.class, ResourceType.CDS.typeUrl(), ResourceType.CDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add("CDS response Resource index " + i + " - can't decode Cluster: " + e); - continue; - } - if (!isResourceNameValid(cluster.getName(), resource.getTypeUrl())) { - errors.add( - "Unsupported resource name: " + cluster.getName() + " for type: " + ResourceType.CDS); - continue; - } - String clusterName = canonifyResourceName(cluster.getName()); - - // Management server is required to always send newly requested resources, even if they - // may have been sent previously (proactively). Thus, client does not need to cache - // unrequested resources. - if (!cdsResourceSubscribers.containsKey(clusterName)) { - continue; - } - unpackedResources.add(clusterName); - - // Process Cluster into CdsUpdate. - CdsUpdate cdsUpdate; - try { - Set certProviderInstances = null; - if (getBootstrapInfo() != null && getBootstrapInfo().certProviders() != null) { - certProviderInstances = getBootstrapInfo().certProviders().keySet(); - } - cdsUpdate = processCluster(cluster, retainedEdsResources, certProviderInstances, serverInfo, - loadBalancerRegistry); - } catch (ResourceInvalidException e) { - errors.add( - "CDS response Cluster '" + clusterName + "' validation error: " + e.getMessage()); - invalidResources.add(clusterName); - continue; - } - parsedResources.put(clusterName, new ParsedResource(cdsUpdate, resource)); - } - logger.log(XdsLogLevel.INFO, - "Received CDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.CDS, parsedResources, invalidResources, retainedEdsResources, - versionInfo, nonce, errors); - } - - @VisibleForTesting - static CdsUpdate processCluster(Cluster cluster, Set retainedEdsResources, - Set certProviderInstances, ServerInfo serverInfo, - LoadBalancerRegistry loadBalancerRegistry) - throws ResourceInvalidException { - StructOrError structOrError; - switch (cluster.getClusterDiscoveryTypeCase()) { - case TYPE: - structOrError = parseNonAggregateCluster(cluster, retainedEdsResources, - certProviderInstances, serverInfo); - break; - case CLUSTER_TYPE: - structOrError = parseAggregateCluster(cluster); - break; - case CLUSTERDISCOVERYTYPE_NOT_SET: - default: - throw new ResourceInvalidException( - "Cluster " + cluster.getName() + ": unspecified cluster discovery type"); - } - if (structOrError.getErrorDetail() != null) { - throw new ResourceInvalidException(structOrError.getErrorDetail()); - } - CdsUpdate.Builder updateBuilder = structOrError.getStruct(); - - ImmutableMap lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster, - enableLeastRequest, enableCustomLbConfig); - - // Validate the LB config by trying to parse it with the corresponding LB provider. - LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig); - NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider( - lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig( - lbConfig.getRawConfigValue()); - if (configOrError.getError() != null) { - throw new ResourceInvalidException(structOrError.getErrorDetail()); - } - - updateBuilder.lbPolicyConfig(lbPolicyConfig); - - return updateBuilder.build(); - } - - private static StructOrError parseAggregateCluster(Cluster cluster) { - String clusterName = cluster.getName(); - CustomClusterType customType = cluster.getClusterType(); - String typeName = customType.getName(); - if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) { - return StructOrError.fromError( - "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName); - } - io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig; - try { - clusterConfig = unpackCompatibleType(customType.getTypedConfig(), - io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class, - TYPE_URL_CLUSTER_CONFIG, TYPE_URL_CLUSTER_CONFIG_V2); - } catch (InvalidProtocolBufferException e) { - return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e); - } - return StructOrError.fromStruct(CdsUpdate.forAggregate( - clusterName, clusterConfig.getClustersList())); - } - - private static StructOrError parseNonAggregateCluster( - Cluster cluster, Set edsResources, Set certProviderInstances, - ServerInfo serverInfo) { - String clusterName = cluster.getName(); - ServerInfo lrsServerInfo = null; - Long maxConcurrentRequests = null; - UpstreamTlsContext upstreamTlsContext = null; - OutlierDetection outlierDetection = null; - if (cluster.hasLrsServer()) { - if (!cluster.getLrsServer().hasSelf()) { - return StructOrError.fromError( - "Cluster " + clusterName + ": only support LRS for the same management server"); - } - lrsServerInfo = serverInfo; - } - if (cluster.hasCircuitBreakers()) { - List thresholds = cluster.getCircuitBreakers().getThresholdsList(); - for (Thresholds threshold : thresholds) { - if (threshold.getPriority() != RoutingPriority.DEFAULT) { - continue; - } - if (threshold.hasMaxRequests()) { - maxConcurrentRequests = (long) threshold.getMaxRequests().getValue(); - } - } - } - if (cluster.getTransportSocketMatchesCount() > 0) { - return StructOrError.fromError("Cluster " + clusterName - + ": transport-socket-matches not supported."); - } - if (cluster.hasTransportSocket()) { - if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) { - return StructOrError.fromError("transport-socket with name " - + cluster.getTransportSocket().getName() + " not supported."); - } - try { - upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext( - validateUpstreamTlsContext( - unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(), - io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext.class, - TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2), - certProviderInstances)); - } catch (InvalidProtocolBufferException | ResourceInvalidException e) { - return StructOrError.fromError( - "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e); - } - } - if (cluster.hasOutlierDetection() && enableOutlierDetection) { - try { - outlierDetection = OutlierDetection.fromEnvoyOutlierDetection( - validateOutlierDetection(cluster.getOutlierDetection())); - } catch (ResourceInvalidException e) { - return StructOrError.fromError( - "Cluster " + clusterName + ": malformed outlier_detection: " + e); - } - } - - - DiscoveryType type = cluster.getType(); - if (type == DiscoveryType.EDS) { - String edsServiceName = null; - io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig = - cluster.getEdsClusterConfig(); - if (!edsClusterConfig.getEdsConfig().hasAds() - && ! edsClusterConfig.getEdsConfig().hasSelf()) { - return StructOrError.fromError( - "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use" - + " EDS over ADS or self ConfigSource"); - } - // If the service_name field is set, that value will be used for the EDS request. - if (!edsClusterConfig.getServiceName().isEmpty()) { - edsServiceName = edsClusterConfig.getServiceName(); - edsResources.add(edsServiceName); - } else { - edsResources.add(clusterName); - } - return StructOrError.fromStruct(CdsUpdate.forEds( - clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext, - outlierDetection)); - } else if (type.equals(DiscoveryType.LOGICAL_DNS)) { - if (!cluster.hasLoadAssignment()) { - return StructOrError.fromError( - "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host"); - } - ClusterLoadAssignment assignment = cluster.getLoadAssignment(); - if (assignment.getEndpointsCount() != 1 - || assignment.getEndpoints(0).getLbEndpointsCount() != 1) { - return StructOrError.fromError( - "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single " - + "locality_lb_endpoint and a single lb_endpoint"); - } - io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint = - assignment.getEndpoints(0).getLbEndpoints(0); - if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress() - || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) { - return StructOrError.fromError( - "Cluster " + clusterName - + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address"); - } - SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress(); - if (!socketAddress.getResolverName().isEmpty()) { - return StructOrError.fromError( - "Cluster " + clusterName - + ": LOGICAL DNS clusters must NOT have a custom resolver name set"); - } - if (socketAddress.getPortSpecifierCase() != PortSpecifierCase.PORT_VALUE) { - return StructOrError.fromError( - "Cluster " + clusterName - + ": LOGICAL DNS clusters socket_address must have port_value"); - } - String dnsHostName = String.format( - Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue()); - return StructOrError.fromStruct(CdsUpdate.forLogicalDns( - clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext)); - } - return StructOrError.fromError( - "Cluster " + clusterName + ": unsupported built-in discovery type: " + type); - } - - @Override - public void handleEdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce) { - syncContext.throwIfNotInThisSynchronizationContext(); - Map parsedResources = new HashMap<>(resources.size()); - Set unpackedResources = new HashSet<>(resources.size()); - Set invalidResources = new HashSet<>(); - List errors = new ArrayList<>(); - - for (int i = 0; i < resources.size(); i++) { - Any resource = resources.get(i); - - // Unpack the ClusterLoadAssignment. - ClusterLoadAssignment assignment; - try { - resource = maybeUnwrapResources(resource); - assignment = - unpackCompatibleType(resource, ClusterLoadAssignment.class, ResourceType.EDS.typeUrl(), - ResourceType.EDS.typeUrlV2()); - } catch (InvalidProtocolBufferException e) { - errors.add( - "EDS response Resource index " + i + " - can't decode ClusterLoadAssignment: " + e); - continue; - } - if (!isResourceNameValid(assignment.getClusterName(), resource.getTypeUrl())) { - errors.add("Unsupported resource name: " + assignment.getClusterName() + " for type: " - + ResourceType.EDS); - continue; - } - String clusterName = canonifyResourceName(assignment.getClusterName()); - - // Skip information for clusters not requested. - // Management server is required to always send newly requested resources, even if they - // may have been sent previously (proactively). Thus, client does not need to cache - // unrequested resources. - if (!edsResourceSubscribers.containsKey(clusterName)) { - continue; - } - unpackedResources.add(clusterName); - - // Process ClusterLoadAssignment into EdsUpdate. - EdsUpdate edsUpdate; - try { - edsUpdate = processClusterLoadAssignment(assignment); - } catch (ResourceInvalidException e) { - errors.add("EDS response ClusterLoadAssignment '" + clusterName - + "' validation error: " + e.getMessage()); - invalidResources.add(clusterName); - continue; - } - parsedResources.put(clusterName, new ParsedResource(edsUpdate, resource)); - } - logger.log( - XdsLogLevel.INFO, "Received EDS Response version {0} nonce {1}. Parsed resources: {2}", - versionInfo, nonce, unpackedResources); - handleResourceUpdate( - serverInfo, ResourceType.EDS, parsedResources, invalidResources, - Collections.emptySet(), versionInfo, nonce, errors); - } - - private static EdsUpdate processClusterLoadAssignment(ClusterLoadAssignment assignment) - throws ResourceInvalidException { - Map> priorities = new HashMap<>(); - Map localityLbEndpointsMap = new LinkedHashMap<>(); - List dropOverloads = new ArrayList<>(); - int maxPriority = -1; - for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto - : assignment.getEndpointsList()) { - StructOrError structOrError = - parseLocalityLbEndpoints(localityLbEndpointsProto); - if (structOrError == null) { - continue; - } - if (structOrError.getErrorDetail() != null) { - throw new ResourceInvalidException(structOrError.getErrorDetail()); - } - - LocalityLbEndpoints localityLbEndpoints = structOrError.getStruct(); - int priority = localityLbEndpoints.priority(); - maxPriority = Math.max(maxPriority, priority); - // Note endpoints with health status other than HEALTHY and UNKNOWN are still - // handed over to watching parties. It is watching parties' responsibility to - // filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy(). - Locality locality = parseLocality(localityLbEndpointsProto.getLocality()); - localityLbEndpointsMap.put(locality, localityLbEndpoints); - if (!priorities.containsKey(priority)) { - priorities.put(priority, new HashSet<>()); - } - if (!priorities.get(priority).add(locality)) { - throw new ResourceInvalidException("ClusterLoadAssignment has duplicate locality:" - + locality + " for priority:" + priority); - } - } - if (priorities.size() != maxPriority + 1) { - throw new ResourceInvalidException("ClusterLoadAssignment has sparse priorities"); - } - - for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto - : assignment.getPolicy().getDropOverloadsList()) { - dropOverloads.add(parseDropOverload(dropOverloadProto)); - } - return new EdsUpdate(assignment.getClusterName(), localityLbEndpointsMap, dropOverloads); - } - - 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 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 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.of(addr)), - endpoint.getLoadBalancingWeight().getValue(), isHealthy)); - } - return StructOrError.fromStruct(LocalityLbEndpoints.create( - endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority())); - } - - /** - * Helper method to unpack serialized {@link com.google.protobuf.Any} message, while replacing - * Type URL {@code compatibleTypeUrl} with {@code typeUrl}. - * - * @param The type of unpacked message - * @param any serialized message to unpack - * @param clazz the class to unpack the message to - * @param typeUrl type URL to replace message Type URL, when it's compatible - * @param compatibleTypeUrl compatible Type URL to be replaced with {@code typeUrl} - * @return Unpacked message - * @throws InvalidProtocolBufferException if the message couldn't be unpacked - */ - private static T unpackCompatibleType( - Any any, Class clazz, String typeUrl, String compatibleTypeUrl) - throws InvalidProtocolBufferException { - if (any.getTypeUrl().equals(compatibleTypeUrl)) { - any = any.toBuilder().setTypeUrl(typeUrl).build(); - } - return any.unpack(clazz); - } - - 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; + XdsResourceType xdsResourceType = + xdsResourceTypeMap.get(resourceType); + if (xdsResourceType == null) { + logger.log(XdsLogLevel.WARNING, "Ignore an unknown type of DiscoveryResponse"); + return; + } + Set toParseResourceNames = (resourceType == LDS || resourceType == RDS ) ? null : + resourceSubscribers.get(xdsResourceType).keySet(); + XdsResourceType.Args args = new XdsResourceType.Args(serverInfo, versionInfo, nonce, + bootstrapInfo, filterRegistry, loadBalancerRegistry, tlsContextManager, + toParseResourceNames); + handleResourceUpdate(args, resources, xdsResourceType); } @Override public void handleStreamClosed(Status error) { syncContext.throwIfNotInThisSynchronizationContext(); cleanUpResourceTimers(); - for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { - subscriber.onError(error); - } - for (ResourceSubscriber subscriber : rdsResourceSubscribers.values()) { - subscriber.onError(error); - } - for (ResourceSubscriber subscriber : cdsResourceSubscribers.values()) { - subscriber.onError(error); - } - for (ResourceSubscriber subscriber : edsResourceSubscribers.values()) { - subscriber.onError(error); + for (Map> subscriberMap : + resourceSubscribers.values()) { + for (ResourceSubscriber subscriber : subscriberMap.values()) { + subscriber.onError(error); + } } } @Override public void handleStreamRestarted(ServerInfo serverInfo) { syncContext.throwIfNotInThisSynchronizationContext(); - for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); - } - } - for (ResourceSubscriber subscriber : rdsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); - } - } - for (ResourceSubscriber subscriber : cdsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); - } - } - for (ResourceSubscriber subscriber : edsResourceSubscribers.values()) { - if (subscriber.serverInfo.equals(serverInfo)) { - subscriber.restartTimer(); + for (Map> subscriberMap : + resourceSubscribers.values()) { + for (ResourceSubscriber subscriber : subscriberMap.values()) { + if (subscriber.serverInfo.equals(serverInfo)) { + subscriber.restartTimer(); + } } } } @@ -2142,26 +236,23 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res return isShutdown; } - private Map getSubscribedResourcesMap(ResourceType type) { - switch (type) { - case LDS: - return ldsResourceSubscribers; - case RDS: - return rdsResourceSubscribers; - case CDS: - return cdsResourceSubscribers; - case EDS: - return edsResourceSubscribers; - case UNKNOWN: - default: - throw new AssertionError("Unknown resource type"); - } + private Map> getSubscribedResourcesMap( + ResourceType type) { + return resourceSubscribers.getOrDefault(xdsResourceTypeMap.get(type), Collections.emptyMap()); } @Nullable @Override - public Collection getSubscribedResources(ServerInfo serverInfo, ResourceType type) { - Map resources = getSubscribedResourcesMap(type); + public XdsResourceType getXdsResourceType(ResourceType type) { + return xdsResourceTypeMap.get(type); + } + + @Nullable + @Override + public Collection getSubscribedResources(ServerInfo serverInfo, + ResourceType type) { + Map> resources = + getSubscribedResourcesMap(type); ImmutableSet.Builder builder = ImmutableSet.builder(); for (String key : resources.keySet()) { if (resources.get(key).serverInfo.equals(serverInfo)) { @@ -2183,16 +274,15 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res // A map from a "resource type" to a map ("resource name": "resource metadata") ImmutableMap.Builder> metadataSnapshot = ImmutableMap.builder(); - for (ResourceType type : ResourceType.values()) { - if (type == ResourceType.UNKNOWN) { - continue; - } + for (XdsResourceType resourceType: xdsResourceTypeMap.values()) { ImmutableMap.Builder metadataMap = ImmutableMap.builder(); - for (Map.Entry resourceEntry - : getSubscribedResourcesMap(type).entrySet()) { + Map> resourceSubscriberMap = + resourceSubscribers.getOrDefault(resourceType, Collections.emptyMap()); + for (Map.Entry> resourceEntry + : resourceSubscriberMap.entrySet()) { metadataMap.put(resourceEntry.getKey(), resourceEntry.getValue().metadata); } - metadataSnapshot.put(type, metadataMap.buildOrThrow()); + metadataSnapshot.put(resourceType.typeName(), metadataMap.buildOrThrow()); } future.set(metadataSnapshot.buildOrThrow()); } @@ -2206,17 +296,23 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res } @Override - void watchLdsResource(final String resourceName, final LdsResourceWatcher watcher) { + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { syncContext.execute(new Runnable() { @Override + @SuppressWarnings("unchecked") public void run() { - ResourceSubscriber subscriber = ldsResourceSubscribers.get(resourceName); + if (!resourceSubscribers.containsKey(type)) { + resourceSubscribers.put(type, new HashMap<>()); + } + ResourceSubscriber subscriber = + (ResourceSubscriber) resourceSubscribers.get(type).get(resourceName);; if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe LDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.LDS, resourceName); - ldsResourceSubscribers.put(resourceName, subscriber); + logger.log(XdsLogLevel.INFO, "Subscribe {0} resource {1}", type, resourceName); + subscriber = new ResourceSubscriber<>(type.typeName(), resourceName); + resourceSubscribers.get(type).put(resourceName, subscriber); if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.LDS); + subscriber.xdsChannel.adjustResourceSubscription(type); } } subscriber.addWatcher(watcher); @@ -2225,128 +321,24 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res } @Override - void cancelLdsResourceWatch(final String resourceName, final LdsResourceWatcher watcher) { + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { syncContext.execute(new Runnable() { @Override + @SuppressWarnings("unchecked") public void run() { - ResourceSubscriber subscriber = ldsResourceSubscribers.get(resourceName); + ResourceSubscriber subscriber = + (ResourceSubscriber) resourceSubscribers.get(type).get(resourceName);; subscriber.removeWatcher(watcher); if (!subscriber.isWatched()) { subscriber.cancelResourceWatch(); - ldsResourceSubscribers.remove(resourceName); + resourceSubscribers.get(type).remove(resourceName); if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.LDS); + subscriber.xdsChannel.adjustResourceSubscription(type); } - } - } - }); - } - - @Override - void watchRdsResource(final String resourceName, final RdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = rdsResourceSubscribers.get(resourceName); - if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe RDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.RDS, resourceName); - rdsResourceSubscribers.put(resourceName, subscriber); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.RDS); - } - } - subscriber.addWatcher(watcher); - } - }); - } - - @Override - void cancelRdsResourceWatch(final String resourceName, final RdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = rdsResourceSubscribers.get(resourceName); - subscriber.removeWatcher(watcher); - if (!subscriber.isWatched()) { - subscriber.cancelResourceWatch(); - rdsResourceSubscribers.remove(resourceName); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.RDS); - } - } - } - }); - } - - @Override - void watchCdsResource(final String resourceName, final CdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = cdsResourceSubscribers.get(resourceName); - if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe CDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.CDS, resourceName); - cdsResourceSubscribers.put(resourceName, subscriber); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.CDS); - } - } - subscriber.addWatcher(watcher); - } - }); - } - - @Override - void cancelCdsResourceWatch(final String resourceName, final CdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = cdsResourceSubscribers.get(resourceName); - subscriber.removeWatcher(watcher); - if (!subscriber.isWatched()) { - subscriber.cancelResourceWatch(); - cdsResourceSubscribers.remove(resourceName); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.CDS); - } - } - } - }); - } - - @Override - void watchEdsResource(final String resourceName, final EdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = edsResourceSubscribers.get(resourceName); - if (subscriber == null) { - logger.log(XdsLogLevel.INFO, "Subscribe EDS resource {0}", resourceName); - subscriber = new ResourceSubscriber(ResourceType.EDS, resourceName); - edsResourceSubscribers.put(resourceName, subscriber); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.EDS); - } - } - subscriber.addWatcher(watcher); - } - }); - } - - @Override - void cancelEdsResourceWatch(final String resourceName, final EdsResourceWatcher watcher) { - syncContext.execute(new Runnable() { - @Override - public void run() { - ResourceSubscriber subscriber = edsResourceSubscribers.get(resourceName); - subscriber.removeWatcher(watcher); - if (!subscriber.isWatched()) { - subscriber.cancelResourceWatch(); - edsResourceSubscribers.remove(resourceName); - if (subscriber.xdsChannel != null) { - subscriber.xdsChannel.adjustResourceSubscription(ResourceType.EDS); + if (resourceSubscribers.get(type).isEmpty()) { + resourceSubscribers.remove(type); } } } @@ -2399,54 +391,57 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res } private void cleanUpResourceTimers() { - for (ResourceSubscriber subscriber : ldsResourceSubscribers.values()) { - subscriber.stopTimer(); - } - for (ResourceSubscriber subscriber : rdsResourceSubscribers.values()) { - subscriber.stopTimer(); - } - for (ResourceSubscriber subscriber : cdsResourceSubscribers.values()) { - subscriber.stopTimer(); - } - for (ResourceSubscriber subscriber : edsResourceSubscribers.values()) { - subscriber.stopTimer(); + for (Map> subscriberMap : resourceSubscribers.values()) { + for (ResourceSubscriber subscriber : subscriberMap.values()) { + subscriber.stopTimer(); + } } } - private void handleResourceUpdate( - ServerInfo serverInfo, ResourceType type, Map parsedResources, - Set invalidResources, Set retainedResources, String version, String nonce, - List errors) { + @SuppressWarnings("unchecked") + private void handleResourceUpdate(XdsResourceType.Args args, + List resources, + XdsResourceType xdsResourceType) { + ValidatedResourceUpdate result = xdsResourceType.parse(args, resources); + logger.log(XdsLogger.XdsLogLevel.INFO, + "Received {0} Response version {1} nonce {2}. Parsed resources: {3}", + xdsResourceType.typeName(), args.versionInfo, args.nonce, result.unpackedResources); + Map> parsedResources = result.parsedResources; + Set invalidResources = result.invalidResources; + Set retainedResources = result.retainedResources; + List errors = result.errors; String errorDetail = null; if (errors.isEmpty()) { checkArgument(invalidResources.isEmpty(), "found invalid resources but missing errors"); - serverChannelMap.get(serverInfo).ackResponse(type, version, nonce); + serverChannelMap.get(args.serverInfo).ackResponse(xdsResourceType, args.versionInfo, + args.nonce); } else { errorDetail = Joiner.on('\n').join(errors); logger.log(XdsLogLevel.WARNING, "Failed processing {0} Response version {1} nonce {2}. Errors:\n{3}", - type, version, nonce, errorDetail); - serverChannelMap.get(serverInfo).nackResponse(type, nonce, errorDetail); + xdsResourceType.typeName(), args.versionInfo, args.nonce, errorDetail); + serverChannelMap.get(args.serverInfo).nackResponse(xdsResourceType, args.nonce, errorDetail); } long updateTime = timeProvider.currentTimeNanos(); - for (Map.Entry entry : getSubscribedResourcesMap(type).entrySet()) { + for (Map.Entry> entry : + getSubscribedResourcesMap(xdsResourceType.typeName()).entrySet()) { String resourceName = entry.getKey(); - ResourceSubscriber subscriber = entry.getValue(); + ResourceSubscriber subscriber = (ResourceSubscriber) entry.getValue(); if (parsedResources.containsKey(resourceName)) { // Happy path: the resource updated successfully. Notify the watchers of the update. - subscriber.onData(parsedResources.get(resourceName), version, updateTime); + subscriber.onData(parsedResources.get(resourceName), args.versionInfo, updateTime); continue; } if (invalidResources.contains(resourceName)) { // The resource update is invalid. Capture the error without notifying the watchers. - subscriber.onRejected(version, updateTime, errorDetail); + subscriber.onRejected(args.versionInfo, updateTime, errorDetail); } // Nothing else to do for incremental ADS resources. - if (type != ResourceType.LDS && type != ResourceType.CDS) { + if (xdsResourceType.dependentResource() == null) { continue; } @@ -2474,9 +469,13 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res // LDS/CDS responses represents the state of the world, RDS/EDS resources not referenced in // LDS/CDS resources should be deleted. - if (type == ResourceType.LDS || type == ResourceType.CDS) { - Map dependentSubscribers = - type == ResourceType.LDS ? rdsResourceSubscribers : edsResourceSubscribers; + if (xdsResourceType.dependentResource() != null) { + XdsResourceType dependency = xdsResourceTypeMap.get(xdsResourceType.dependentResource()); + Map> dependentSubscribers = + resourceSubscribers.get(dependency); + if (dependentSubscribers == null) { + return; + } for (String resource : dependentSubscribers.keySet()) { if (!retainedResources.contains(resource)) { dependentSubscribers.get(resource).onAbsent(); @@ -2486,18 +485,18 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res } private void retainDependentResource( - ResourceSubscriber subscriber, Set retainedResources) { + ResourceSubscriber subscriber, Set retainedResources) { if (subscriber.data == null) { return; } String resourceName = null; - if (subscriber.type == ResourceType.LDS) { + if (subscriber.type == LDS) { LdsUpdate ldsUpdate = (LdsUpdate) subscriber.data; io.grpc.xds.HttpConnectionManager hcm = ldsUpdate.httpConnectionManager(); if (hcm != null) { resourceName = hcm.rdsName(); } - } else if (subscriber.type == ResourceType.CDS) { + } else if (subscriber.type == CDS) { CdsUpdate cdsUpdate = (CdsUpdate) subscriber.data; resourceName = cdsUpdate.edsServiceName(); } @@ -2507,34 +506,16 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res } } - private static final class ParsedResource { - private final ResourceUpdate resourceUpdate; - private final Any rawResource; - - private ParsedResource(ResourceUpdate resourceUpdate, Any rawResource) { - this.resourceUpdate = checkNotNull(resourceUpdate, "resourceUpdate"); - this.rawResource = checkNotNull(rawResource, "rawResource"); - } - - private ResourceUpdate getResourceUpdate() { - return resourceUpdate; - } - - private Any getRawResource() { - return rawResource; - } - } - /** * Tracks a single subscribed resource. */ - private final class ResourceSubscriber { + private final class ResourceSubscriber { @Nullable private final ServerInfo serverInfo; @Nullable private final AbstractXdsClient xdsChannel; private final ResourceType type; private final String resource; - private final Set watchers = new HashSet<>(); - @Nullable private ResourceUpdate data; + private final Set> watchers = new HashSet<>(); + @Nullable private T data; private boolean absent; // Tracks whether the deletion has been ignored per bootstrap server feature. // See https://github.com/grpc/proposal/blob/master/A53-xds-ignore-resource-deletion.md @@ -2582,7 +563,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res return bootstrapInfo.servers().get(0); // use first server } - void addWatcher(ResourceWatcher watcher) { + void addWatcher(ResourceWatcher watcher) { checkArgument(!watchers.contains(watcher), "watcher %s already registered", watcher); watchers.add(watcher); if (errorDescription != null) { @@ -2596,7 +577,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res } } - void removeWatcher(ResourceWatcher watcher) { + void removeWatcher(ResourceWatcher watcher) { checkArgument(watchers.contains(watcher), "watcher %s not registered", watcher); watchers.remove(watcher); } @@ -2653,7 +634,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res return !watchers.isEmpty(); } - void onData(ParsedResource parsedResource, String version, long updateTime) { + void onData(ParsedResource parsedResource, String version, long updateTime) { if (respTimer != null && respTimer.isPending()) { respTimer.cancel(); respTimer = null; @@ -2670,7 +651,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res resourceDeletionIgnored = false; } if (!Objects.equals(oldData, data)) { - for (ResourceWatcher watcher : watchers) { + for (ResourceWatcher watcher : watchers) { notifyWatcher(watcher, data); } } @@ -2685,7 +666,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res // and the resource is reusable. boolean ignoreResourceDeletionEnabled = serverInfo != null && serverInfo.ignoreResourceDeletion(); - boolean isStateOfTheWorld = (type == ResourceType.LDS || type == ResourceType.CDS); + boolean isStateOfTheWorld = (type == LDS || type == CDS); if (ignoreResourceDeletionEnabled && isStateOfTheWorld && data != null) { if (!resourceDeletionIgnored) { logger.log(XdsLogLevel.FORCE_WARNING, @@ -2701,7 +682,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res data = null; absent = true; metadata = ResourceMetadata.newResourceMetadataDoesNotExist(); - for (ResourceWatcher watcher : watchers) { + for (ResourceWatcher watcher : watchers) { watcher.onResourceDoesNotExist(resource); } } @@ -2720,7 +701,7 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res .withDescription(description + "nodeID: " + bootstrapInfo.node().getId()) .withCause(error.getCause()); - for (ResourceWatcher watcher : watchers) { + for (ResourceWatcher watcher : watchers) { watcher.onError(errorAugmented); } } @@ -2730,24 +711,8 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res .newResourceMetadataNacked(metadata, rejectedVersion, rejectedTime, rejectedDetails); } - private void notifyWatcher(ResourceWatcher watcher, ResourceUpdate update) { - switch (type) { - case LDS: - ((LdsResourceWatcher) watcher).onChanged((LdsUpdate) update); - break; - case RDS: - ((RdsResourceWatcher) watcher).onChanged((RdsUpdate) update); - break; - case CDS: - ((CdsResourceWatcher) watcher).onChanged((CdsUpdate) update); - break; - case EDS: - ((EdsResourceWatcher) watcher).onChanged((EdsUpdate) update); - break; - case UNKNOWN: - default: - throw new AssertionError("should never be here"); - } + private void notifyWatcher(ResourceWatcher watcher, T update) { + watcher.onChanged(update); } } @@ -2764,55 +729,6 @@ final class ClientXdsClient extends XdsClient implements XdsResponseHandler, Res } } - @VisibleForTesting - static final class StructOrError { - - /** - * Returns a {@link StructOrError} for the successfully converted data object. - */ - private static StructOrError fromStruct(T struct) { - return new StructOrError<>(struct); - } - - /** - * Returns a {@link StructOrError} for the failure to convert the data object. - */ - private static StructOrError 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; - } - } - abstract static class XdsChannelFactory { static final XdsChannelFactory DEFAULT_XDS_CHANNEL_FACTORY = new XdsChannelFactory() { @Override diff --git a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java index 96e2cf441a..f4aaf9426b 100644 --- a/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java +++ b/xds/src/main/java/io/grpc/xds/ClusterResolverLoadBalancer.java @@ -52,8 +52,8 @@ import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; -import io.grpc.xds.XdsClient.EdsResourceWatcher; -import io.grpc.xds.XdsClient.EdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsSubchannelPickers.ErrorPicker; import java.net.URI; @@ -350,7 +350,7 @@ final class ClusterResolverLoadBalancer extends LoadBalancer { } } - private final class EdsClusterState extends ClusterState implements EdsResourceWatcher { + private final class EdsClusterState extends ClusterState implements ResourceWatcher { @Nullable private final String edsServiceName; private Map localityPriorityNames = Collections.emptyMap(); @@ -367,7 +367,7 @@ final class ClusterResolverLoadBalancer extends LoadBalancer { void start() { String resourceName = edsServiceName != null ? edsServiceName : name; logger.log(XdsLogLevel.INFO, "Start watching EDS resource {0}", resourceName); - xdsClient.watchEdsResource(resourceName, this); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), resourceName, this); } @Override @@ -375,7 +375,7 @@ final class ClusterResolverLoadBalancer extends LoadBalancer { super.shutdown(); String resourceName = edsServiceName != null ? edsServiceName : name; logger.log(XdsLogLevel.INFO, "Stop watching EDS resource {0}", resourceName); - xdsClient.cancelEdsResourceWatch(resourceName, this); + xdsClient.cancelXdsResourceWatch(XdsEndpointResource.getInstance(), resourceName, this); } @Override diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index 028ae155ba..feb3afa3e9 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -19,23 +19,14 @@ package io.grpc.xds; import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.xds.Bootstrapper.XDSTP_SCHEME; -import com.google.auto.value.AutoValue; import com.google.common.base.Joiner; -import com.google.common.base.MoreObjects; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.net.UrlEscapers; import com.google.common.util.concurrent.ListenableFuture; import com.google.protobuf.Any; import io.grpc.Status; import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.ServerInfo; -import io.grpc.xds.Endpoints.DropOverload; -import io.grpc.xds.Endpoints.LocalityLbEndpoints; -import io.grpc.xds.EnvoyServerProtoData.Listener; -import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; -import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; import io.grpc.xds.LoadStatsManager2.ClusterLocalityStats; import java.net.URI; @@ -43,10 +34,8 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import javax.annotation.Nullable; /** @@ -118,295 +107,13 @@ abstract class XdsClient { return Joiner.on('/').join(encodedSegs); } - @AutoValue - abstract static class LdsUpdate implements ResourceUpdate { - // Http level api listener configuration. - @Nullable - abstract HttpConnectionManager httpConnectionManager(); - - // Tcp level listener configuration. - @Nullable - abstract Listener listener(); - - static LdsUpdate forApiListener(HttpConnectionManager httpConnectionManager) { - checkNotNull(httpConnectionManager, "httpConnectionManager"); - return new AutoValue_XdsClient_LdsUpdate(httpConnectionManager, null); - } - - static LdsUpdate forTcpListener(Listener listener) { - checkNotNull(listener, "listener"); - return new AutoValue_XdsClient_LdsUpdate(null, listener); - } - } - - static final class RdsUpdate implements ResourceUpdate { - // The list virtual hosts that make up the route table. - final List virtualHosts; - - RdsUpdate(List virtualHosts) { - this.virtualHosts = Collections.unmodifiableList( - new ArrayList<>(checkNotNull(virtualHosts, "virtualHosts"))); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("virtualHosts", virtualHosts) - .toString(); - } - - @Override - public int hashCode() { - return Objects.hash(virtualHosts); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RdsUpdate that = (RdsUpdate) o; - return Objects.equals(virtualHosts, that.virtualHosts); - } - } - - /** xDS resource update for cluster-level configuration. */ - @AutoValue - abstract static class CdsUpdate implements ResourceUpdate { - abstract String clusterName(); - - abstract ClusterType clusterType(); - - abstract ImmutableMap lbPolicyConfig(); - - // Only valid if lbPolicy is "ring_hash_experimental". - abstract long minRingSize(); - - // Only valid if lbPolicy is "ring_hash_experimental". - abstract long maxRingSize(); - - // Only valid if lbPolicy is "least_request_experimental". - abstract int choiceCount(); - - // Alternative resource name to be used in EDS requests. - /// Only valid for EDS cluster. - @Nullable - abstract String edsServiceName(); - - // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable - // via DNS. - // Only valid for LOGICAL_DNS cluster. - @Nullable - abstract String dnsHostName(); - - // Load report server info for reporting loads via LRS. - // Only valid for EDS or LOGICAL_DNS cluster. - @Nullable - abstract ServerInfo lrsServerInfo(); - - // Max number of concurrent requests can be sent to this cluster. - // Only valid for EDS or LOGICAL_DNS cluster. - @Nullable - abstract Long maxConcurrentRequests(); - - // TLS context used to connect to connect to this cluster. - // Only valid for EDS or LOGICAL_DNS cluster. - @Nullable - abstract UpstreamTlsContext upstreamTlsContext(); - - // List of underlying clusters making of this aggregate cluster. - // Only valid for AGGREGATE cluster. - @Nullable - abstract ImmutableList prioritizedClusterNames(); - - // Outlier detection configuration. - @Nullable - abstract OutlierDetection outlierDetection(); - - static Builder forAggregate(String clusterName, List prioritizedClusterNames) { - checkNotNull(prioritizedClusterNames, "prioritizedClusterNames"); - return new AutoValue_XdsClient_CdsUpdate.Builder() - .clusterName(clusterName) - .clusterType(ClusterType.AGGREGATE) - .minRingSize(0) - .maxRingSize(0) - .choiceCount(0) - .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames)); - } - - static Builder forEds(String clusterName, @Nullable String edsServiceName, - @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext upstreamTlsContext, - @Nullable OutlierDetection outlierDetection) { - return new AutoValue_XdsClient_CdsUpdate.Builder() - .clusterName(clusterName) - .clusterType(ClusterType.EDS) - .minRingSize(0) - .maxRingSize(0) - .choiceCount(0) - .edsServiceName(edsServiceName) - .lrsServerInfo(lrsServerInfo) - .maxConcurrentRequests(maxConcurrentRequests) - .upstreamTlsContext(upstreamTlsContext) - .outlierDetection(outlierDetection); - } - - static Builder forLogicalDns(String clusterName, String dnsHostName, - @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, - @Nullable UpstreamTlsContext upstreamTlsContext) { - return new AutoValue_XdsClient_CdsUpdate.Builder() - .clusterName(clusterName) - .clusterType(ClusterType.LOGICAL_DNS) - .minRingSize(0) - .maxRingSize(0) - .choiceCount(0) - .dnsHostName(dnsHostName) - .lrsServerInfo(lrsServerInfo) - .maxConcurrentRequests(maxConcurrentRequests) - .upstreamTlsContext(upstreamTlsContext); - } - - enum ClusterType { - EDS, LOGICAL_DNS, AGGREGATE - } - - enum LbPolicy { - ROUND_ROBIN, RING_HASH, LEAST_REQUEST - } - - // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed. - @Override - public final String toString() { - return MoreObjects.toStringHelper(this) - .add("clusterName", clusterName()) - .add("clusterType", clusterType()) - .add("lbPolicyConfig", lbPolicyConfig()) - .add("minRingSize", minRingSize()) - .add("maxRingSize", maxRingSize()) - .add("choiceCount", choiceCount()) - .add("edsServiceName", edsServiceName()) - .add("dnsHostName", dnsHostName()) - .add("lrsServerInfo", lrsServerInfo()) - .add("maxConcurrentRequests", maxConcurrentRequests()) - .add("prioritizedClusterNames", prioritizedClusterNames()) - // Exclude upstreamTlsContext and outlierDetection as their string representations are - // cumbersome. - .toString(); - } - - @AutoValue.Builder - abstract static class Builder { - // Private, use one of the static factory methods instead. - protected abstract Builder clusterName(String clusterName); - - // Private, use one of the static factory methods instead. - protected abstract Builder clusterType(ClusterType clusterType); - - protected abstract Builder lbPolicyConfig(ImmutableMap lbPolicyConfig); - - Builder roundRobinLbPolicy() { - return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of())); - } - - Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) { - return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental", - ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize", - maxRingSize.doubleValue()))); - } - - Builder leastRequestLbPolicy(Integer choiceCount) { - return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental", - ImmutableMap.of("choiceCount", choiceCount.doubleValue()))); - } - - // Private, use leastRequestLbPolicy(int). - protected abstract Builder choiceCount(int choiceCount); - - // Private, use ringHashLbPolicy(long, long). - protected abstract Builder minRingSize(long minRingSize); - - // Private, use ringHashLbPolicy(long, long). - protected abstract Builder maxRingSize(long maxRingSize); - - // Private, use CdsUpdate.forEds() instead. - protected abstract Builder edsServiceName(String edsServiceName); - - // Private, use CdsUpdate.forLogicalDns() instead. - protected abstract Builder dnsHostName(String dnsHostName); - - // Private, use one of the static factory methods instead. - protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo); - - // Private, use one of the static factory methods instead. - protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests); - - // Private, use one of the static factory methods instead. - protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext); - - // Private, use CdsUpdate.forAggregate() instead. - protected abstract Builder prioritizedClusterNames(List prioritizedClusterNames); - - protected abstract Builder outlierDetection(OutlierDetection outlierDetection); - - abstract CdsUpdate build(); - } - } - - static final class EdsUpdate implements ResourceUpdate { - final String clusterName; - final Map localityLbEndpointsMap; - final List dropPolicies; - - EdsUpdate(String clusterName, Map localityLbEndpoints, - List dropPolicies) { - this.clusterName = checkNotNull(clusterName, "clusterName"); - this.localityLbEndpointsMap = Collections.unmodifiableMap( - new LinkedHashMap<>(checkNotNull(localityLbEndpoints, "localityLbEndpoints"))); - this.dropPolicies = Collections.unmodifiableList( - new ArrayList<>(checkNotNull(dropPolicies, "dropPolicies"))); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - EdsUpdate that = (EdsUpdate) o; - return Objects.equals(clusterName, that.clusterName) - && Objects.equals(localityLbEndpointsMap, that.localityLbEndpointsMap) - && Objects.equals(dropPolicies, that.dropPolicies); - } - - @Override - public int hashCode() { - return Objects.hash(clusterName, localityLbEndpointsMap, dropPolicies); - } - - @Override - public String toString() { - return - MoreObjects - .toStringHelper(this) - .add("clusterName", clusterName) - .add("localityLbEndpointsMap", localityLbEndpointsMap) - .add("dropPolicies", dropPolicies) - .toString(); - } - } - interface ResourceUpdate { } /** * Watcher interface for a single requested xDS resource. */ - interface ResourceWatcher { + interface ResourceWatcher { /** * Called when the resource discovery RPC encounters some transient error. @@ -426,22 +133,8 @@ abstract class XdsClient { * @param resourceName name of the resource requested in discovery request. */ void onResourceDoesNotExist(String resourceName); - } - interface LdsResourceWatcher extends ResourceWatcher { - void onChanged(LdsUpdate update); - } - - interface RdsResourceWatcher extends ResourceWatcher { - void onChanged(RdsUpdate update); - } - - interface CdsResourceWatcher extends ResourceWatcher { - void onChanged(CdsUpdate update); - } - - interface EdsResourceWatcher extends ResourceWatcher { - void onChanged(EdsUpdate update); + void onChanged(T update); } /** @@ -609,58 +302,19 @@ abstract class XdsClient { } /** - * Registers a data watcher for the given LDS resource. + * Registers a data watcher for the given Xds resource. */ - void watchLdsResource(String resourceName, LdsResourceWatcher watcher) { + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { throw new UnsupportedOperationException(); } /** - * Unregisters the given LDS resource watcher. + * Unregisters the given resource watcher. */ - void cancelLdsResourceWatch(String resourceName, LdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Registers a data watcher for the given RDS resource. - */ - void watchRdsResource(String resourceName, RdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Unregisters the given RDS resource watcher. - */ - void cancelRdsResourceWatch(String resourceName, RdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Registers a data watcher for the given CDS resource. - */ - void watchCdsResource(String resourceName, CdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Unregisters the given CDS resource watcher. - */ - void cancelCdsResourceWatch(String resourceName, CdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Registers a data watcher for the given EDS resource. - */ - void watchEdsResource(String resourceName, EdsResourceWatcher watcher) { - throw new UnsupportedOperationException(); - } - - /** - * Unregisters the given EDS resource watcher. - */ - void cancelEdsResourceWatch(String resourceName, EdsResourceWatcher watcher) { + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { throw new UnsupportedOperationException(); } @@ -691,21 +345,10 @@ abstract class XdsClient { } interface XdsResponseHandler { - /** Called when an LDS response is received. */ - void handleLdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); - - /** Called when an RDS response is received. */ - void handleRdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); - - /** Called when an CDS response is received. */ - void handleCdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); - - /** Called when an EDS response is received. */ - void handleEdsResponse( - ServerInfo serverInfo, String versionInfo, List resources, String nonce); + /** Called when a xds response is received. */ + void handleResourceResponse( + ResourceType resourceType, ServerInfo serverInfo, String versionInfo, List resources, + String nonce); /** Called when the ADS stream is closed passively. */ // Must be synchronized. @@ -727,5 +370,8 @@ abstract class XdsClient { // Must be synchronized. @Nullable Collection getSubscribedResources(ServerInfo serverInfo, ResourceType type); + + @Nullable + XdsResourceType getXdsResourceType(ResourceType type); } } diff --git a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java new file mode 100644 index 0000000000..4dc3095efa --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java @@ -0,0 +1,686 @@ +/* + * Copyright 2022 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 static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.AbstractXdsClient.ResourceType.CDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; +import static io.grpc.xds.Bootstrapper.ServerInfo; + +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.protobuf.Duration; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.Durations; +import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds; +import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.core.v3.RoutingPriority; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; +import io.grpc.LoadBalancerRegistry; +import io.grpc.NameResolver; +import io.grpc.internal.ServiceConfigUtil; +import io.grpc.internal.ServiceConfigUtil.LbConfig; +import io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; +import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; +import io.grpc.xds.XdsClient.ResourceUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsClusterResource extends XdsResourceType { + static final String ADS_TYPE_URL_CDS_V2 = "type.googleapis.com/envoy.api.v2.Cluster"; + static final String ADS_TYPE_URL_CDS = + "type.googleapis.com/envoy.config.cluster.v3.Cluster"; + private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT = + "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext"; + private static final String TYPE_URL_UPSTREAM_TLS_CONTEXT_V2 = + "type.googleapis.com/envoy.api.v2.auth.UpstreamTlsContext"; + + private static final XdsClusterResource instance = new XdsClusterResource(); + + public static XdsClusterResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof Cluster)) { + return null; + } + return ((Cluster) unpackedResource).getName(); + } + + @Override + ResourceType typeName() { + return CDS; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_CDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_CDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return EDS; + } + + @Override + @SuppressWarnings("unchecked") + Class unpackedClassName() { + return Cluster.class; + } + + @Override + CdsUpdate doParse(Args args, Message unpackedMessage, + Set retainedResources, boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof Cluster)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + Set certProviderInstances = null; + if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) { + certProviderInstances = args.bootstrapInfo.certProviders().keySet(); + } + return processCluster((Cluster) unpackedMessage, retainedResources, certProviderInstances, + args.serverInfo, args.loadBalancerRegistry); + } + + @VisibleForTesting + static CdsUpdate processCluster(Cluster cluster, Set retainedEdsResources, + Set certProviderInstances, + Bootstrapper.ServerInfo serverInfo, + LoadBalancerRegistry loadBalancerRegistry) + throws ResourceInvalidException { + StructOrError structOrError; + switch (cluster.getClusterDiscoveryTypeCase()) { + case TYPE: + structOrError = parseNonAggregateCluster(cluster, retainedEdsResources, + certProviderInstances, serverInfo); + break; + case CLUSTER_TYPE: + structOrError = parseAggregateCluster(cluster); + break; + case CLUSTERDISCOVERYTYPE_NOT_SET: + default: + throw new ResourceInvalidException( + "Cluster " + cluster.getName() + ": unspecified cluster discovery type"); + } + if (structOrError.getErrorDetail() != null) { + throw new ResourceInvalidException(structOrError.getErrorDetail()); + } + CdsUpdate.Builder updateBuilder = structOrError.getStruct(); + + ImmutableMap lbPolicyConfig = LoadBalancerConfigFactory.newConfig(cluster, + enableLeastRequest, enableCustomLbConfig); + + // Validate the LB config by trying to parse it with the corresponding LB provider. + LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(lbPolicyConfig); + NameResolver.ConfigOrError configOrError = loadBalancerRegistry.getProvider( + lbConfig.getPolicyName()).parseLoadBalancingPolicyConfig( + lbConfig.getRawConfigValue()); + if (configOrError.getError() != null) { + throw new ResourceInvalidException(structOrError.getErrorDetail()); + } + + updateBuilder.lbPolicyConfig(lbPolicyConfig); + + return updateBuilder.build(); + } + + private static StructOrError parseAggregateCluster(Cluster cluster) { + String clusterName = cluster.getName(); + Cluster.CustomClusterType customType = cluster.getClusterType(); + String typeName = customType.getName(); + if (!typeName.equals(AGGREGATE_CLUSTER_TYPE_NAME)) { + return StructOrError.fromError( + "Cluster " + clusterName + ": unsupported custom cluster type: " + typeName); + } + io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig clusterConfig; + try { + clusterConfig = unpackCompatibleType(customType.getTypedConfig(), + io.envoyproxy.envoy.extensions.clusters.aggregate.v3.ClusterConfig.class, + TYPE_URL_CLUSTER_CONFIG, TYPE_URL_CLUSTER_CONFIG_V2); + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError("Cluster " + clusterName + ": malformed ClusterConfig: " + e); + } + return StructOrError.fromStruct(CdsUpdate.forAggregate( + clusterName, clusterConfig.getClustersList())); + } + + private static StructOrError parseNonAggregateCluster( + Cluster cluster, Set edsResources, Set certProviderInstances, + Bootstrapper.ServerInfo serverInfo) { + String clusterName = cluster.getName(); + Bootstrapper.ServerInfo lrsServerInfo = null; + Long maxConcurrentRequests = null; + EnvoyServerProtoData.UpstreamTlsContext upstreamTlsContext = null; + OutlierDetection outlierDetection = null; + if (cluster.hasLrsServer()) { + if (!cluster.getLrsServer().hasSelf()) { + return StructOrError.fromError( + "Cluster " + clusterName + ": only support LRS for the same management server"); + } + lrsServerInfo = serverInfo; + } + if (cluster.hasCircuitBreakers()) { + List thresholds = cluster.getCircuitBreakers().getThresholdsList(); + for (Thresholds threshold : thresholds) { + if (threshold.getPriority() != RoutingPriority.DEFAULT) { + continue; + } + if (threshold.hasMaxRequests()) { + maxConcurrentRequests = (long) threshold.getMaxRequests().getValue(); + } + } + } + if (cluster.getTransportSocketMatchesCount() > 0) { + return StructOrError.fromError("Cluster " + clusterName + + ": transport-socket-matches not supported."); + } + if (cluster.hasTransportSocket()) { + if (!TRANSPORT_SOCKET_NAME_TLS.equals(cluster.getTransportSocket().getName())) { + return StructOrError.fromError("transport-socket with name " + + cluster.getTransportSocket().getName() + " not supported."); + } + try { + upstreamTlsContext = UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext( + validateUpstreamTlsContext( + unpackCompatibleType(cluster.getTransportSocket().getTypedConfig(), + io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext.class, + TYPE_URL_UPSTREAM_TLS_CONTEXT, TYPE_URL_UPSTREAM_TLS_CONTEXT_V2), + certProviderInstances)); + } catch (InvalidProtocolBufferException | ResourceInvalidException e) { + return StructOrError.fromError( + "Cluster " + clusterName + ": malformed UpstreamTlsContext: " + e); + } + } + + if (cluster.hasOutlierDetection() && enableOutlierDetection) { + try { + outlierDetection = OutlierDetection.fromEnvoyOutlierDetection( + validateOutlierDetection(cluster.getOutlierDetection())); + } catch (ResourceInvalidException e) { + return StructOrError.fromError( + "Cluster " + clusterName + ": malformed outlier_detection: " + e); + } + } + + Cluster.DiscoveryType type = cluster.getType(); + if (type == Cluster.DiscoveryType.EDS) { + String edsServiceName = null; + io.envoyproxy.envoy.config.cluster.v3.Cluster.EdsClusterConfig edsClusterConfig = + cluster.getEdsClusterConfig(); + if (!edsClusterConfig.getEdsConfig().hasAds() + && ! edsClusterConfig.getEdsConfig().hasSelf()) { + return StructOrError.fromError( + "Cluster " + clusterName + ": field eds_cluster_config must be set to indicate to use" + + " EDS over ADS or self ConfigSource"); + } + // If the service_name field is set, that value will be used for the EDS request. + if (!edsClusterConfig.getServiceName().isEmpty()) { + edsServiceName = edsClusterConfig.getServiceName(); + edsResources.add(edsServiceName); + } else { + edsResources.add(clusterName); + } + return StructOrError.fromStruct(CdsUpdate.forEds( + clusterName, edsServiceName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext, + outlierDetection)); + } else if (type.equals(Cluster.DiscoveryType.LOGICAL_DNS)) { + if (!cluster.hasLoadAssignment()) { + return StructOrError.fromError( + "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single host"); + } + ClusterLoadAssignment assignment = cluster.getLoadAssignment(); + if (assignment.getEndpointsCount() != 1 + || assignment.getEndpoints(0).getLbEndpointsCount() != 1) { + return StructOrError.fromError( + "Cluster " + clusterName + ": LOGICAL_DNS clusters must have a single " + + "locality_lb_endpoint and a single lb_endpoint"); + } + io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint lbEndpoint = + assignment.getEndpoints(0).getLbEndpoints(0); + if (!lbEndpoint.hasEndpoint() || !lbEndpoint.getEndpoint().hasAddress() + || !lbEndpoint.getEndpoint().getAddress().hasSocketAddress()) { + return StructOrError.fromError( + "Cluster " + clusterName + + ": LOGICAL_DNS clusters must have an endpoint with address and socket_address"); + } + SocketAddress socketAddress = lbEndpoint.getEndpoint().getAddress().getSocketAddress(); + if (!socketAddress.getResolverName().isEmpty()) { + return StructOrError.fromError( + "Cluster " + clusterName + + ": LOGICAL DNS clusters must NOT have a custom resolver name set"); + } + if (socketAddress.getPortSpecifierCase() != SocketAddress.PortSpecifierCase.PORT_VALUE) { + return StructOrError.fromError( + "Cluster " + clusterName + + ": LOGICAL DNS clusters socket_address must have port_value"); + } + String dnsHostName = String.format( + Locale.US, "%s:%d", socketAddress.getAddress(), socketAddress.getPortValue()); + return StructOrError.fromStruct(CdsUpdate.forLogicalDns( + clusterName, dnsHostName, lrsServerInfo, maxConcurrentRequests, upstreamTlsContext)); + } + return StructOrError.fromError( + "Cluster " + clusterName + ": unsupported built-in discovery type: " + type); + } + + static io.envoyproxy.envoy.config.cluster.v3.OutlierDetection validateOutlierDetection( + io.envoyproxy.envoy.config.cluster.v3.OutlierDetection outlierDetection) + throws ResourceInvalidException { + if (outlierDetection.hasInterval()) { + if (!Durations.isValid(outlierDetection.getInterval())) { + throw new ResourceInvalidException("outlier_detection interval is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getInterval())) { + throw new ResourceInvalidException("outlier_detection interval has a negative value"); + } + } + if (outlierDetection.hasBaseEjectionTime()) { + if (!Durations.isValid(outlierDetection.getBaseEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection base_ejection_time is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getBaseEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection base_ejection_time has a negative value"); + } + } + if (outlierDetection.hasMaxEjectionTime()) { + if (!Durations.isValid(outlierDetection.getMaxEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_time is not a valid Duration"); + } + if (hasNegativeValues(outlierDetection.getMaxEjectionTime())) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_time has a negative value"); + } + } + if (outlierDetection.hasMaxEjectionPercent() + && outlierDetection.getMaxEjectionPercent().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection max_ejection_percent is > 100"); + } + if (outlierDetection.hasEnforcingSuccessRate() + && outlierDetection.getEnforcingSuccessRate().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection enforcing_success_rate is > 100"); + } + if (outlierDetection.hasFailurePercentageThreshold() + && outlierDetection.getFailurePercentageThreshold().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection failure_percentage_threshold is > 100"); + } + if (outlierDetection.hasEnforcingFailurePercentage() + && outlierDetection.getEnforcingFailurePercentage().getValue() > 100) { + throw new ResourceInvalidException( + "outlier_detection enforcing_failure_percentage is > 100"); + } + + return outlierDetection; + } + + static boolean hasNegativeValues(Duration duration) { + return duration.getSeconds() < 0 || duration.getNanos() < 0; + } + + @VisibleForTesting + static io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + validateUpstreamTlsContext( + io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext upstreamTlsContext, + Set certProviderInstances) + throws ResourceInvalidException { + if (upstreamTlsContext.hasCommonTlsContext()) { + validateCommonTlsContext(upstreamTlsContext.getCommonTlsContext(), certProviderInstances, + false); + } else { + throw new ResourceInvalidException("common-tls-context is required in upstream-tls-context"); + } + return upstreamTlsContext; + } + + @VisibleForTesting + static void validateCommonTlsContext( + CommonTlsContext commonTlsContext, Set certProviderInstances, boolean server) + throws ResourceInvalidException { + if (commonTlsContext.hasCustomHandshaker()) { + throw new ResourceInvalidException( + "common-tls-context with custom_handshaker is not supported"); + } + if (commonTlsContext.hasTlsParams()) { + throw new ResourceInvalidException("common-tls-context with tls_params is not supported"); + } + if (commonTlsContext.hasValidationContextSdsSecretConfig()) { + throw new ResourceInvalidException( + "common-tls-context with validation_context_sds_secret_config is not supported"); + } + if (commonTlsContext.hasValidationContextCertificateProvider()) { + throw new ResourceInvalidException( + "common-tls-context with validation_context_certificate_provider is not supported"); + } + if (commonTlsContext.hasValidationContextCertificateProviderInstance()) { + throw new ResourceInvalidException( + "common-tls-context with validation_context_certificate_provider_instance is not" + + " supported"); + } + String certInstanceName = getIdentityCertInstanceName(commonTlsContext); + if (certInstanceName == null) { + if (server) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is required in downstream-tls-context"); + } + if (commonTlsContext.getTlsCertificatesCount() > 0) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is unset"); + } + if (commonTlsContext.getTlsCertificateSdsSecretConfigsCount() > 0) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is unset"); + } + if (commonTlsContext.hasTlsCertificateCertificateProvider()) { + throw new ResourceInvalidException( + "tls_certificate_provider_instance is unset"); + } + } else if (certProviderInstances == null || !certProviderInstances.contains(certInstanceName)) { + throw new ResourceInvalidException( + "CertificateProvider instance name '" + certInstanceName + + "' not defined in the bootstrap file."); + } + String rootCaInstanceName = getRootCertInstanceName(commonTlsContext); + if (rootCaInstanceName == null) { + if (!server) { + throw new ResourceInvalidException( + "ca_certificate_provider_instance is required in upstream-tls-context"); + } + } else { + if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) { + throw new ResourceInvalidException( + "ca_certificate_provider_instance name '" + rootCaInstanceName + + "' not defined in the bootstrap file."); + } + CertificateValidationContext certificateValidationContext = null; + if (commonTlsContext.hasValidationContext()) { + certificateValidationContext = commonTlsContext.getValidationContext(); + } else if (commonTlsContext.hasCombinedValidationContext() && commonTlsContext + .getCombinedValidationContext().hasDefaultValidationContext()) { + certificateValidationContext = commonTlsContext.getCombinedValidationContext() + .getDefaultValidationContext(); + } + if (certificateValidationContext != null) { + if (certificateValidationContext.getMatchSubjectAltNamesCount() > 0 && server) { + throw new ResourceInvalidException( + "match_subject_alt_names only allowed in upstream_tls_context"); + } + if (certificateValidationContext.getVerifyCertificateSpkiCount() > 0) { + throw new ResourceInvalidException( + "verify_certificate_spki in default_validation_context is not supported"); + } + if (certificateValidationContext.getVerifyCertificateHashCount() > 0) { + throw new ResourceInvalidException( + "verify_certificate_hash in default_validation_context is not supported"); + } + if (certificateValidationContext.hasRequireSignedCertificateTimestamp()) { + throw new ResourceInvalidException( + "require_signed_certificate_timestamp in default_validation_context is not " + + "supported"); + } + if (certificateValidationContext.hasCrl()) { + throw new ResourceInvalidException("crl in default_validation_context is not supported"); + } + if (certificateValidationContext.hasCustomValidatorConfig()) { + throw new ResourceInvalidException( + "custom_validator_config in default_validation_context is not supported"); + } + } + } + } + + private static String getIdentityCertInstanceName(CommonTlsContext commonTlsContext) { + if (commonTlsContext.hasTlsCertificateProviderInstance()) { + return commonTlsContext.getTlsCertificateProviderInstance().getInstanceName(); + } else if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) { + return commonTlsContext.getTlsCertificateCertificateProviderInstance().getInstanceName(); + } + return null; + } + + private static String getRootCertInstanceName(CommonTlsContext commonTlsContext) { + if (commonTlsContext.hasValidationContext()) { + if (commonTlsContext.getValidationContext().hasCaCertificateProviderInstance()) { + return commonTlsContext.getValidationContext().getCaCertificateProviderInstance() + .getInstanceName(); + } + } else if (commonTlsContext.hasCombinedValidationContext()) { + CommonTlsContext.CombinedCertificateValidationContext combinedCertificateValidationContext + = commonTlsContext.getCombinedValidationContext(); + if (combinedCertificateValidationContext.hasDefaultValidationContext() + && combinedCertificateValidationContext.getDefaultValidationContext() + .hasCaCertificateProviderInstance()) { + return combinedCertificateValidationContext.getDefaultValidationContext() + .getCaCertificateProviderInstance().getInstanceName(); + } else if (combinedCertificateValidationContext + .hasValidationContextCertificateProviderInstance()) { + return combinedCertificateValidationContext + .getValidationContextCertificateProviderInstance().getInstanceName(); + } + } + return null; + } + + /** xDS resource update for cluster-level configuration. */ + @AutoValue + abstract static class CdsUpdate implements ResourceUpdate { + abstract String clusterName(); + + abstract ClusterType clusterType(); + + abstract ImmutableMap lbPolicyConfig(); + + // Only valid if lbPolicy is "ring_hash_experimental". + abstract long minRingSize(); + + // Only valid if lbPolicy is "ring_hash_experimental". + abstract long maxRingSize(); + + // Only valid if lbPolicy is "least_request_experimental". + abstract int choiceCount(); + + // Alternative resource name to be used in EDS requests. + /// Only valid for EDS cluster. + @Nullable + abstract String edsServiceName(); + + // Corresponding DNS name to be used if upstream endpoints of the cluster is resolvable + // via DNS. + // Only valid for LOGICAL_DNS cluster. + @Nullable + abstract String dnsHostName(); + + // Load report server info for reporting loads via LRS. + // Only valid for EDS or LOGICAL_DNS cluster. + @Nullable + abstract ServerInfo lrsServerInfo(); + + // Max number of concurrent requests can be sent to this cluster. + // Only valid for EDS or LOGICAL_DNS cluster. + @Nullable + abstract Long maxConcurrentRequests(); + + // TLS context used to connect to connect to this cluster. + // Only valid for EDS or LOGICAL_DNS cluster. + @Nullable + abstract UpstreamTlsContext upstreamTlsContext(); + + // List of underlying clusters making of this aggregate cluster. + // Only valid for AGGREGATE cluster. + @Nullable + abstract ImmutableList prioritizedClusterNames(); + + // Outlier detection configuration. + @Nullable + abstract OutlierDetection outlierDetection(); + + static Builder forAggregate(String clusterName, List prioritizedClusterNames) { + checkNotNull(prioritizedClusterNames, "prioritizedClusterNames"); + return new AutoValue_XdsClusterResource_CdsUpdate.Builder() + .clusterName(clusterName) + .clusterType(ClusterType.AGGREGATE) + .minRingSize(0) + .maxRingSize(0) + .choiceCount(0) + .prioritizedClusterNames(ImmutableList.copyOf(prioritizedClusterNames)); + } + + static Builder forEds(String clusterName, @Nullable String edsServiceName, + @Nullable ServerInfo lrsServerInfo, @Nullable Long maxConcurrentRequests, + @Nullable UpstreamTlsContext upstreamTlsContext, + @Nullable OutlierDetection outlierDetection) { + return new AutoValue_XdsClusterResource_CdsUpdate.Builder() + .clusterName(clusterName) + .clusterType(ClusterType.EDS) + .minRingSize(0) + .maxRingSize(0) + .choiceCount(0) + .edsServiceName(edsServiceName) + .lrsServerInfo(lrsServerInfo) + .maxConcurrentRequests(maxConcurrentRequests) + .upstreamTlsContext(upstreamTlsContext) + .outlierDetection(outlierDetection); + } + + static Builder forLogicalDns(String clusterName, String dnsHostName, + @Nullable ServerInfo lrsServerInfo, + @Nullable Long maxConcurrentRequests, + @Nullable UpstreamTlsContext upstreamTlsContext) { + return new AutoValue_XdsClusterResource_CdsUpdate.Builder() + .clusterName(clusterName) + .clusterType(ClusterType.LOGICAL_DNS) + .minRingSize(0) + .maxRingSize(0) + .choiceCount(0) + .dnsHostName(dnsHostName) + .lrsServerInfo(lrsServerInfo) + .maxConcurrentRequests(maxConcurrentRequests) + .upstreamTlsContext(upstreamTlsContext); + } + + enum ClusterType { + EDS, LOGICAL_DNS, AGGREGATE + } + + enum LbPolicy { + ROUND_ROBIN, RING_HASH, LEAST_REQUEST + } + + // FIXME(chengyuanzhang): delete this after UpstreamTlsContext's toString() is fixed. + @Override + public final String toString() { + return MoreObjects.toStringHelper(this) + .add("clusterName", clusterName()) + .add("clusterType", clusterType()) + .add("lbPolicyConfig", lbPolicyConfig()) + .add("minRingSize", minRingSize()) + .add("maxRingSize", maxRingSize()) + .add("choiceCount", choiceCount()) + .add("edsServiceName", edsServiceName()) + .add("dnsHostName", dnsHostName()) + .add("lrsServerInfo", lrsServerInfo()) + .add("maxConcurrentRequests", maxConcurrentRequests()) + // Exclude upstreamTlsContext and outlierDetection as their string representations are + // cumbersome. + .add("prioritizedClusterNames", prioritizedClusterNames()) + .toString(); + } + + @AutoValue.Builder + abstract static class Builder { + // Private, use one of the static factory methods instead. + protected abstract Builder clusterName(String clusterName); + + // Private, use one of the static factory methods instead. + protected abstract Builder clusterType(ClusterType clusterType); + + protected abstract Builder lbPolicyConfig(ImmutableMap lbPolicyConfig); + + Builder roundRobinLbPolicy() { + return this.lbPolicyConfig(ImmutableMap.of("round_robin", ImmutableMap.of())); + } + + Builder ringHashLbPolicy(Long minRingSize, Long maxRingSize) { + return this.lbPolicyConfig(ImmutableMap.of("ring_hash_experimental", + ImmutableMap.of("minRingSize", minRingSize.doubleValue(), "maxRingSize", + maxRingSize.doubleValue()))); + } + + Builder leastRequestLbPolicy(Integer choiceCount) { + return this.lbPolicyConfig(ImmutableMap.of("least_request_experimental", + ImmutableMap.of("choiceCount", choiceCount.doubleValue()))); + } + + // Private, use leastRequestLbPolicy(int). + protected abstract Builder choiceCount(int choiceCount); + + // Private, use ringHashLbPolicy(long, long). + protected abstract Builder minRingSize(long minRingSize); + + // Private, use ringHashLbPolicy(long, long). + protected abstract Builder maxRingSize(long maxRingSize); + + // Private, use CdsUpdate.forEds() instead. + protected abstract Builder edsServiceName(String edsServiceName); + + // Private, use CdsUpdate.forLogicalDns() instead. + protected abstract Builder dnsHostName(String dnsHostName); + + // Private, use one of the static factory methods instead. + protected abstract Builder lrsServerInfo(ServerInfo lrsServerInfo); + + // Private, use one of the static factory methods instead. + protected abstract Builder maxConcurrentRequests(Long maxConcurrentRequests); + + // Private, use one of the static factory methods instead. + protected abstract Builder upstreamTlsContext(UpstreamTlsContext upstreamTlsContext); + + // Private, use CdsUpdate.forAggregate() instead. + protected abstract Builder prioritizedClusterNames(List prioritizedClusterNames); + + protected abstract Builder outlierDetection(OutlierDetection outlierDetection); + + abstract CdsUpdate build(); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java new file mode 100644 index 0000000000..db1e93d13f --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java @@ -0,0 +1,259 @@ +/* + * Copyright 2022 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 static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Message; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.type.v3.FractionalPercent; +import io.grpc.EquivalentAddressGroup; +import io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import io.grpc.xds.Endpoints.DropOverload; +import io.grpc.xds.Endpoints.LocalityLbEndpoints; +import io.grpc.xds.XdsClient.ResourceUpdate; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsEndpointResource extends XdsResourceType { + static final String ADS_TYPE_URL_EDS_V2 = + "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"; + static final String ADS_TYPE_URL_EDS = + "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment"; + + private static final XdsEndpointResource instance = new XdsEndpointResource(); + + public static XdsEndpointResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof ClusterLoadAssignment)) { + return null; + } + return ((ClusterLoadAssignment) unpackedResource).getClusterName(); + } + + @Override + ResourceType typeName() { + return EDS; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_EDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_EDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return null; + } + + @Override + Class unpackedClassName() { + return ClusterLoadAssignment.class; + } + + @Override + EdsUpdate doParse(Args args, Message unpackedMessage, + Set retainedResources, boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof ClusterLoadAssignment)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + return processClusterLoadAssignment((ClusterLoadAssignment) unpackedMessage); + } + + private static EdsUpdate processClusterLoadAssignment(ClusterLoadAssignment assignment) + throws ResourceInvalidException { + Map> priorities = new HashMap<>(); + Map localityLbEndpointsMap = new LinkedHashMap<>(); + List dropOverloads = new ArrayList<>(); + int maxPriority = -1; + for (io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints localityLbEndpointsProto + : assignment.getEndpointsList()) { + StructOrError structOrError = + parseLocalityLbEndpoints(localityLbEndpointsProto); + if (structOrError == null) { + continue; + } + if (structOrError.getErrorDetail() != null) { + throw new ResourceInvalidException(structOrError.getErrorDetail()); + } + + LocalityLbEndpoints localityLbEndpoints = structOrError.getStruct(); + int priority = localityLbEndpoints.priority(); + maxPriority = Math.max(maxPriority, priority); + // Note endpoints with health status other than HEALTHY and UNKNOWN are still + // handed over to watching parties. It is watching parties' responsibility to + // filter out unhealthy endpoints. See EnvoyProtoData.LbEndpoint#isHealthy(). + Locality locality = parseLocality(localityLbEndpointsProto.getLocality()); + localityLbEndpointsMap.put(locality, localityLbEndpoints); + if (!priorities.containsKey(priority)) { + priorities.put(priority, new HashSet<>()); + } + if (!priorities.get(priority).add(locality)) { + throw new ResourceInvalidException("ClusterLoadAssignment has duplicate locality:" + + locality + " for priority:" + priority); + } + } + if (priorities.size() != maxPriority + 1) { + throw new ResourceInvalidException("ClusterLoadAssignment has sparse priorities"); + } + + for (ClusterLoadAssignment.Policy.DropOverload dropOverloadProto + : assignment.getPolicy().getDropOverloadsList()) { + dropOverloads.add(parseDropOverload(dropOverloadProto)); + } + return new EdsUpdate(assignment.getClusterName(), localityLbEndpointsMap, dropOverloads); + } + + 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())); + } + + private static int getRatePerMillion(FractionalPercent percent) { + int numerator = percent.getNumerator(); + FractionalPercent.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; + } + + + @VisibleForTesting + @Nullable + static StructOrError 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 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(Endpoints.LbEndpoint.create( + new EquivalentAddressGroup(ImmutableList.of(addr)), + endpoint.getLoadBalancingWeight().getValue(), isHealthy)); + } + return StructOrError.fromStruct(Endpoints.LocalityLbEndpoints.create( + endpoints, proto.getLoadBalancingWeight().getValue(), proto.getPriority())); + } + + static final class EdsUpdate implements ResourceUpdate { + final String clusterName; + final Map localityLbEndpointsMap; + final List dropPolicies; + + EdsUpdate(String clusterName, Map localityLbEndpoints, + List dropPolicies) { + this.clusterName = checkNotNull(clusterName, "clusterName"); + this.localityLbEndpointsMap = Collections.unmodifiableMap( + new LinkedHashMap<>(checkNotNull(localityLbEndpoints, "localityLbEndpoints"))); + this.dropPolicies = Collections.unmodifiableList( + new ArrayList<>(checkNotNull(dropPolicies, "dropPolicies"))); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + EdsUpdate that = (EdsUpdate) o; + return Objects.equals(clusterName, that.clusterName) + && Objects.equals(localityLbEndpointsMap, that.localityLbEndpointsMap) + && Objects.equals(dropPolicies, that.dropPolicies); + } + + @Override + public int hashCode() { + return Objects.hash(clusterName, localityLbEndpointsMap, dropPolicies); + } + + @Override + public String toString() { + return + MoreObjects + .toStringHelper(this) + .add("clusterName", clusterName) + .add("localityLbEndpointsMap", localityLbEndpointsMap) + .add("dropPolicies", dropPolicies) + .toString(); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java new file mode 100644 index 0000000000..397ba32dc6 --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java @@ -0,0 +1,633 @@ +/* + * Copyright 2022 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 static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.AbstractXdsClient.ResourceType.LDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType.RDS; +import static io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import static io.grpc.xds.XdsClient.ResourceUpdate; +import static io.grpc.xds.XdsClusterResource.validateCommonTlsContext; +import static io.grpc.xds.XdsRouteConfigureResource.extractVirtualHosts; + +import com.github.udpa.udpa.type.v1.TypedStruct; +import com.google.auto.value.AutoValue; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.Durations; +import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.core.v3.TrafficDirection; +import io.envoyproxy.envoy.config.listener.v3.Listener; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; +import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext; +import io.grpc.xds.EnvoyServerProtoData.CidrRange; +import io.grpc.xds.EnvoyServerProtoData.ConnectionSourceType; +import io.grpc.xds.EnvoyServerProtoData.FilterChain; +import io.grpc.xds.EnvoyServerProtoData.FilterChainMatch; +import io.grpc.xds.Filter.FilterConfig; +import io.grpc.xds.XdsListenerResource.LdsUpdate; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsListenerResource extends XdsResourceType { + static final String ADS_TYPE_URL_LDS_V2 = "type.googleapis.com/envoy.api.v2.Listener"; + static final String ADS_TYPE_URL_LDS = + "type.googleapis.com/envoy.config.listener.v3.Listener"; + private static final String TYPE_URL_HTTP_CONNECTION_MANAGER_V2 = + "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2" + + ".HttpConnectionManager"; + static final String TYPE_URL_HTTP_CONNECTION_MANAGER = + "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3" + + ".HttpConnectionManager"; + private static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; + private static final XdsListenerResource instance = new XdsListenerResource(); + + public static XdsListenerResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof Listener)) { + return null; + } + return ((Listener) unpackedResource).getName(); + } + + @Override + ResourceType typeName() { + return LDS; + } + + @Override + Class unpackedClassName() { + return Listener.class; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_LDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_LDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return RDS; + } + + @Override + LdsUpdate doParse(Args args, Message unpackedMessage, Set retainedResources, + boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof Listener)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + Listener listener = (Listener) unpackedMessage; + + if (listener.hasApiListener()) { + return processClientSideListener( + listener, retainedResources, args, enableFaultInjection && isResourceV3); + } else { + return processServerSideListener( + listener, retainedResources, args, enableRbac && isResourceV3); + } + } + + private LdsUpdate processClientSideListener( + Listener listener, Set rdsResources, Args args, boolean parseHttpFilter) + throws ResourceInvalidException { + // Unpack HttpConnectionManager from the Listener. + HttpConnectionManager hcm; + try { + hcm = unpackCompatibleType( + listener.getApiListener().getApiListener(), HttpConnectionManager.class, + TYPE_URL_HTTP_CONNECTION_MANAGER, TYPE_URL_HTTP_CONNECTION_MANAGER_V2); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException( + "Could not parse HttpConnectionManager config from ApiListener", e); + } + return LdsUpdate.forApiListener(parseHttpConnectionManager( + hcm, rdsResources, args.filterRegistry, parseHttpFilter, true /* isForClient */)); + } + + private LdsUpdate processServerSideListener( + Listener proto, Set rdsResources, Args args, boolean parseHttpFilter) + throws ResourceInvalidException { + Set certProviderInstances = null; + if (args.bootstrapInfo != null && args.bootstrapInfo.certProviders() != null) { + certProviderInstances = args.bootstrapInfo.certProviders().keySet(); + } + return LdsUpdate.forTcpListener(parseServerSideListener( + proto, rdsResources, args.tlsContextManager, args.filterRegistry, certProviderInstances, + parseHttpFilter)); + } + + @VisibleForTesting + static EnvoyServerProtoData.Listener parseServerSideListener( + Listener proto, Set rdsResources, TlsContextManager tlsContextManager, + FilterRegistry filterRegistry, Set certProviderInstances, boolean parseHttpFilter) + throws ResourceInvalidException { + if (!proto.getTrafficDirection().equals(TrafficDirection.INBOUND) + && !proto.getTrafficDirection().equals(TrafficDirection.UNSPECIFIED)) { + throw new ResourceInvalidException( + "Listener " + proto.getName() + " with invalid traffic direction: " + + proto.getTrafficDirection()); + } + if (!proto.getListenerFiltersList().isEmpty()) { + throw new ResourceInvalidException( + "Listener " + proto.getName() + " cannot have listener_filters"); + } + if (proto.hasUseOriginalDst()) { + throw new ResourceInvalidException( + "Listener " + proto.getName() + " cannot have use_original_dst set to true"); + } + + String address = null; + if (proto.getAddress().hasSocketAddress()) { + SocketAddress socketAddress = proto.getAddress().getSocketAddress(); + address = socketAddress.getAddress(); + switch (socketAddress.getPortSpecifierCase()) { + case NAMED_PORT: + address = address + ":" + socketAddress.getNamedPort(); + break; + case PORT_VALUE: + address = address + ":" + socketAddress.getPortValue(); + break; + default: + // noop + } + } + + ImmutableList.Builder filterChains = ImmutableList.builder(); + Set uniqueSet = new HashSet<>(); + for (io.envoyproxy.envoy.config.listener.v3.FilterChain fc : proto.getFilterChainsList()) { + filterChains.add( + parseFilterChain(fc, rdsResources, tlsContextManager, filterRegistry, uniqueSet, + certProviderInstances, parseHttpFilter)); + } + FilterChain defaultFilterChain = null; + if (proto.hasDefaultFilterChain()) { + defaultFilterChain = parseFilterChain( + proto.getDefaultFilterChain(), rdsResources, tlsContextManager, filterRegistry, + null, certProviderInstances, parseHttpFilter); + } + + return EnvoyServerProtoData.Listener.create( + proto.getName(), address, filterChains.build(), defaultFilterChain); + } + + @VisibleForTesting + static FilterChain parseFilterChain( + io.envoyproxy.envoy.config.listener.v3.FilterChain proto, Set rdsResources, + TlsContextManager tlsContextManager, FilterRegistry filterRegistry, + Set uniqueSet, Set certProviderInstances, boolean parseHttpFilters) + throws ResourceInvalidException { + if (proto.getFiltersCount() != 1) { + throw new ResourceInvalidException("FilterChain " + proto.getName() + + " should contain exact one HttpConnectionManager filter"); + } + io.envoyproxy.envoy.config.listener.v3.Filter filter = proto.getFiltersList().get(0); + if (!filter.hasTypedConfig()) { + throw new ResourceInvalidException( + "FilterChain " + proto.getName() + " contains filter " + filter.getName() + + " without typed_config"); + } + Any any = filter.getTypedConfig(); + // HttpConnectionManager is the only supported network filter at the moment. + if (!any.getTypeUrl().equals(TYPE_URL_HTTP_CONNECTION_MANAGER)) { + throw new ResourceInvalidException( + "FilterChain " + proto.getName() + " contains filter " + filter.getName() + + " with unsupported typed_config type " + any.getTypeUrl()); + } + HttpConnectionManager hcmProto; + try { + hcmProto = any.unpack(HttpConnectionManager.class); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException("FilterChain " + proto.getName() + " with filter " + + filter.getName() + " failed to unpack message", e); + } + io.grpc.xds.HttpConnectionManager httpConnectionManager = parseHttpConnectionManager( + hcmProto, rdsResources, filterRegistry, parseHttpFilters, false /* isForClient */); + + EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext = null; + if (proto.hasTransportSocket()) { + if (!TRANSPORT_SOCKET_NAME_TLS.equals(proto.getTransportSocket().getName())) { + throw new ResourceInvalidException("transport-socket with name " + + proto.getTransportSocket().getName() + " not supported."); + } + DownstreamTlsContext downstreamTlsContextProto; + try { + downstreamTlsContextProto = + proto.getTransportSocket().getTypedConfig().unpack(DownstreamTlsContext.class); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException("FilterChain " + proto.getName() + + " failed to unpack message", e); + } + downstreamTlsContext = + EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext( + validateDownstreamTlsContext(downstreamTlsContextProto, certProviderInstances)); + } + + FilterChainMatch filterChainMatch = parseFilterChainMatch(proto.getFilterChainMatch()); + checkForUniqueness(uniqueSet, filterChainMatch); + return FilterChain.create( + proto.getName(), + filterChainMatch, + httpConnectionManager, + downstreamTlsContext, + tlsContextManager + ); + } + + @VisibleForTesting + static DownstreamTlsContext validateDownstreamTlsContext( + DownstreamTlsContext downstreamTlsContext, Set certProviderInstances) + throws ResourceInvalidException { + if (downstreamTlsContext.hasCommonTlsContext()) { + validateCommonTlsContext(downstreamTlsContext.getCommonTlsContext(), certProviderInstances, + true); + } else { + throw new ResourceInvalidException( + "common-tls-context is required in downstream-tls-context"); + } + if (downstreamTlsContext.hasRequireSni()) { + throw new ResourceInvalidException( + "downstream-tls-context with require-sni is not supported"); + } + DownstreamTlsContext.OcspStaplePolicy ocspStaplePolicy = downstreamTlsContext + .getOcspStaplePolicy(); + if (ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.UNRECOGNIZED + && ocspStaplePolicy != DownstreamTlsContext.OcspStaplePolicy.LENIENT_STAPLING) { + throw new ResourceInvalidException( + "downstream-tls-context with ocsp_staple_policy value " + ocspStaplePolicy.name() + + " is not supported"); + } + return downstreamTlsContext; + } + + private static void checkForUniqueness(Set uniqueSet, + FilterChainMatch filterChainMatch) throws ResourceInvalidException { + if (uniqueSet != null) { + List crossProduct = getCrossProduct(filterChainMatch); + for (FilterChainMatch cur : crossProduct) { + if (!uniqueSet.add(cur)) { + throw new ResourceInvalidException("FilterChainMatch must be unique. " + + "Found duplicate: " + cur); + } + } + } + } + + private static List getCrossProduct(FilterChainMatch filterChainMatch) { + // repeating fields to process: + // prefixRanges, applicationProtocols, sourcePrefixRanges, sourcePorts, serverNames + List expandedList = expandOnPrefixRange(filterChainMatch); + expandedList = expandOnApplicationProtocols(expandedList); + expandedList = expandOnSourcePrefixRange(expandedList); + expandedList = expandOnSourcePorts(expandedList); + return expandOnServerNames(expandedList); + } + + private static List expandOnPrefixRange(FilterChainMatch filterChainMatch) { + ArrayList expandedList = new ArrayList<>(); + if (filterChainMatch.prefixRanges().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (CidrRange cidrRange : filterChainMatch.prefixRanges()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + ImmutableList.of(cidrRange), + filterChainMatch.applicationProtocols(), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + return expandedList; + } + + private static List expandOnApplicationProtocols( + Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.applicationProtocols().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (String applicationProtocol : filterChainMatch.applicationProtocols()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + ImmutableList.of(applicationProtocol), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static List expandOnSourcePrefixRange( + Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.sourcePrefixRanges().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (EnvoyServerProtoData.CidrRange cidrRange : filterChainMatch.sourcePrefixRanges()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + filterChainMatch.applicationProtocols(), + ImmutableList.of(cidrRange), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static List expandOnSourcePorts(Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.sourcePorts().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (Integer sourcePort : filterChainMatch.sourcePorts()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + filterChainMatch.applicationProtocols(), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + ImmutableList.of(sourcePort), + filterChainMatch.serverNames(), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static List expandOnServerNames(Collection set) { + ArrayList expandedList = new ArrayList<>(); + for (FilterChainMatch filterChainMatch : set) { + if (filterChainMatch.serverNames().isEmpty()) { + expandedList.add(filterChainMatch); + } else { + for (String serverName : filterChainMatch.serverNames()) { + expandedList.add(FilterChainMatch.create(filterChainMatch.destinationPort(), + filterChainMatch.prefixRanges(), + filterChainMatch.applicationProtocols(), + filterChainMatch.sourcePrefixRanges(), + filterChainMatch.connectionSourceType(), + filterChainMatch.sourcePorts(), + ImmutableList.of(serverName), + filterChainMatch.transportProtocol())); + } + } + } + return expandedList; + } + + private static FilterChainMatch parseFilterChainMatch( + io.envoyproxy.envoy.config.listener.v3.FilterChainMatch proto) + throws ResourceInvalidException { + ImmutableList.Builder prefixRanges = ImmutableList.builder(); + ImmutableList.Builder sourcePrefixRanges = ImmutableList.builder(); + try { + for (io.envoyproxy.envoy.config.core.v3.CidrRange range : proto.getPrefixRangesList()) { + prefixRanges.add( + CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); + } + for (io.envoyproxy.envoy.config.core.v3.CidrRange range + : proto.getSourcePrefixRangesList()) { + sourcePrefixRanges.add( + CidrRange.create(range.getAddressPrefix(), range.getPrefixLen().getValue())); + } + } catch (UnknownHostException e) { + throw new ResourceInvalidException("Failed to create CidrRange", e); + } + ConnectionSourceType sourceType; + switch (proto.getSourceType()) { + case ANY: + sourceType = ConnectionSourceType.ANY; + break; + case EXTERNAL: + sourceType = ConnectionSourceType.EXTERNAL; + break; + case SAME_IP_OR_LOOPBACK: + sourceType = ConnectionSourceType.SAME_IP_OR_LOOPBACK; + break; + default: + throw new ResourceInvalidException("Unknown source-type: " + proto.getSourceType()); + } + return FilterChainMatch.create( + proto.getDestinationPort().getValue(), + prefixRanges.build(), + ImmutableList.copyOf(proto.getApplicationProtocolsList()), + sourcePrefixRanges.build(), + sourceType, + ImmutableList.copyOf(proto.getSourcePortsList()), + ImmutableList.copyOf(proto.getServerNamesList()), + proto.getTransportProtocol()); + } + + @VisibleForTesting + static io.grpc.xds.HttpConnectionManager parseHttpConnectionManager( + HttpConnectionManager proto, Set rdsResources, FilterRegistry filterRegistry, + boolean parseHttpFilter, boolean isForClient) throws ResourceInvalidException { + if (enableRbac && proto.getXffNumTrustedHops() != 0) { + throw new ResourceInvalidException( + "HttpConnectionManager with xff_num_trusted_hops unsupported"); + } + if (enableRbac && !proto.getOriginalIpDetectionExtensionsList().isEmpty()) { + throw new ResourceInvalidException("HttpConnectionManager with " + + "original_ip_detection_extensions unsupported"); + } + // Obtain max_stream_duration from Http Protocol Options. + long maxStreamDuration = 0; + if (proto.hasCommonHttpProtocolOptions()) { + HttpProtocolOptions options = proto.getCommonHttpProtocolOptions(); + if (options.hasMaxStreamDuration()) { + maxStreamDuration = Durations.toNanos(options.getMaxStreamDuration()); + } + } + + // Parse http filters. + List filterConfigs = null; + if (parseHttpFilter) { + if (proto.getHttpFiltersList().isEmpty()) { + throw new ResourceInvalidException("Missing HttpFilter in HttpConnectionManager."); + } + filterConfigs = new ArrayList<>(); + Set names = new HashSet<>(); + for (int i = 0; i < proto.getHttpFiltersCount(); i++) { + io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter + httpFilter = proto.getHttpFiltersList().get(i); + String filterName = httpFilter.getName(); + if (!names.add(filterName)) { + throw new ResourceInvalidException( + "HttpConnectionManager contains duplicate HttpFilter: " + filterName); + } + StructOrError filterConfig = + parseHttpFilter(httpFilter, filterRegistry, isForClient); + if ((i == proto.getHttpFiltersCount() - 1) + && (filterConfig == null || !isTerminalFilter(filterConfig.getStruct()))) { + throw new ResourceInvalidException("The last HttpFilter must be a terminal filter: " + + filterName); + } + if (filterConfig == null) { + continue; + } + if (filterConfig.getErrorDetail() != null) { + throw new ResourceInvalidException( + "HttpConnectionManager contains invalid HttpFilter: " + + filterConfig.getErrorDetail()); + } + if ((i < proto.getHttpFiltersCount() - 1) && isTerminalFilter(filterConfig.getStruct())) { + throw new ResourceInvalidException("A terminal HttpFilter must be the last filter: " + + filterName); + } + filterConfigs.add(new Filter.NamedFilterConfig(filterName, filterConfig.getStruct())); + } + } + + // Parse inlined RouteConfiguration or RDS. + if (proto.hasRouteConfig()) { + List virtualHosts = extractVirtualHosts( + proto.getRouteConfig(), filterRegistry, parseHttpFilter); + return io.grpc.xds.HttpConnectionManager.forVirtualHosts( + maxStreamDuration, virtualHosts, filterConfigs); + } + if (proto.hasRds()) { + Rds rds = proto.getRds(); + if (!rds.hasConfigSource()) { + throw new ResourceInvalidException( + "HttpConnectionManager contains invalid RDS: missing config_source"); + } + if (!rds.getConfigSource().hasAds() && !rds.getConfigSource().hasSelf()) { + throw new ResourceInvalidException( + "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); + } + // Collect the RDS resource referenced by this HttpConnectionManager. + rdsResources.add(rds.getRouteConfigName()); + return io.grpc.xds.HttpConnectionManager.forRdsName( + maxStreamDuration, rds.getRouteConfigName(), filterConfigs); + } + throw new ResourceInvalidException( + "HttpConnectionManager neither has inlined route_config nor RDS"); + } + + // hard-coded: currently router config is the only terminal filter. + private static boolean isTerminalFilter(Filter.FilterConfig filterConfig) { + return RouterFilter.ROUTER_CONFIG.equals(filterConfig); + } + + @VisibleForTesting + @Nullable // Returns null if the filter is optional but not supported. + static StructOrError parseHttpFilter( + io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter + httpFilter, FilterRegistry filterRegistry, boolean isForClient) { + String filterName = httpFilter.getName(); + boolean isOptional = httpFilter.getIsOptional(); + if (!httpFilter.hasTypedConfig()) { + if (isOptional) { + return null; + } else { + return StructOrError.fromError( + "HttpFilter [" + filterName + "] is not optional and has no typed config"); + } + } + Message rawConfig = httpFilter.getTypedConfig(); + String typeUrl = httpFilter.getTypedConfig().getTypeUrl(); + + try { + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { + TypedStruct typedStruct = httpFilter.getTypedConfig().unpack(TypedStruct.class); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + com.github.xds.type.v3.TypedStruct newTypedStruct = + httpFilter.getTypedConfig().unpack(com.github.xds.type.v3.TypedStruct.class); + typeUrl = newTypedStruct.getTypeUrl(); + rawConfig = newTypedStruct.getValue(); + } + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "HttpFilter [" + filterName + "] contains invalid proto: " + e); + } + Filter filter = filterRegistry.get(typeUrl); + if ((isForClient && !(filter instanceof Filter.ClientInterceptorBuilder)) + || (!isForClient && !(filter instanceof Filter.ServerInterceptorBuilder))) { + if (isOptional) { + return null; + } else { + return StructOrError.fromError( + "HttpFilter [" + filterName + "](" + typeUrl + ") is required but unsupported for " + + (isForClient ? "client" : "server")); + } + } + ConfigOrError filterConfig = filter.parseFilterConfig(rawConfig); + if (filterConfig.errorDetail != null) { + return StructOrError.fromError( + "Invalid filter config for HttpFilter [" + filterName + "]: " + filterConfig.errorDetail); + } + return StructOrError.fromStruct(filterConfig.config); + } + + @AutoValue + abstract static class LdsUpdate implements ResourceUpdate { + // Http level api listener configuration. + @Nullable + abstract io.grpc.xds.HttpConnectionManager httpConnectionManager(); + + // Tcp level listener configuration. + @Nullable + abstract EnvoyServerProtoData.Listener listener(); + + static LdsUpdate forApiListener(io.grpc.xds.HttpConnectionManager httpConnectionManager) { + checkNotNull(httpConnectionManager, "httpConnectionManager"); + return new AutoValue_XdsListenerResource_LdsUpdate(httpConnectionManager, null); + } + + static LdsUpdate forTcpListener(EnvoyServerProtoData.Listener listener) { + checkNotNull(listener, "listener"); + return new AutoValue_XdsListenerResource_LdsUpdate(null, listener); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 0d51d3afa8..702c217a3e 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -63,13 +63,12 @@ import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy; import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.LdsUpdate; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsLogger.XdsLogLevel; import io.grpc.xds.XdsNameResolverProvider.CallCounterProvider; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.Matchers.FractionMatcher; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.util.ArrayList; @@ -688,7 +687,7 @@ final class XdsNameResolver extends NameResolver { } } - private class ResolveState implements LdsResourceWatcher { + private class ResolveState implements ResourceWatcher { private final ConfigOrError emptyServiceConfig = serviceConfigParser.parseServiceConfig(Collections.emptyMap()); private final String ldsResourceName; @@ -723,7 +722,8 @@ final class XdsNameResolver extends NameResolver { rdsName, httpConnectionManager.httpMaxStreamDurationNano(), httpConnectionManager.httpFilterConfigs()); logger.log(XdsLogLevel.INFO, "Start watching RDS resource {0}", rdsName); - xdsClient.watchRdsResource(rdsName, routeDiscoveryState); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), + rdsName, routeDiscoveryState); } } }); @@ -762,14 +762,14 @@ final class XdsNameResolver extends NameResolver { private void start() { logger.log(XdsLogLevel.INFO, "Start watching LDS resource {0}", ldsResourceName); - xdsClient.watchLdsResource(ldsResourceName, this); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), ldsResourceName, this); } private void stop() { logger.log(XdsLogLevel.INFO, "Stop watching LDS resource {0}", ldsResourceName); stopped = true; cleanUpRouteDiscoveryState(); - xdsClient.cancelLdsResourceWatch(ldsResourceName, this); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), ldsResourceName, this); } // called in syncContext @@ -905,7 +905,8 @@ final class XdsNameResolver extends NameResolver { if (routeDiscoveryState != null) { String rdsName = routeDiscoveryState.resourceName; logger.log(XdsLogLevel.INFO, "Stop watching RDS resource {0}", rdsName); - xdsClient.cancelRdsResourceWatch(rdsName, routeDiscoveryState); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), rdsName, + routeDiscoveryState); routeDiscoveryState = null; } } @@ -914,7 +915,7 @@ final class XdsNameResolver extends NameResolver { * Discovery state for RouteConfiguration resource. One instance for each Listener resource * update. */ - private class RouteDiscoveryState implements RdsResourceWatcher { + private class RouteDiscoveryState implements ResourceWatcher { private final String resourceName; private final long httpMaxStreamDurationNano; @Nullable @@ -936,7 +937,8 @@ final class XdsNameResolver extends NameResolver { return; } logger.log(XdsLogLevel.INFO, "Received RDS resource update: {0}", update); - updateRoutes(update.virtualHosts, httpMaxStreamDurationNano, filterConfigs); + updateRoutes(update.virtualHosts, httpMaxStreamDurationNano, + filterConfigs); } }); } diff --git a/xds/src/main/java/io/grpc/xds/XdsResourceType.java b/xds/src/main/java/io/grpc/xds/XdsResourceType.java new file mode 100644 index 0000000000..52c143934e --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsResourceType.java @@ -0,0 +1,308 @@ +/* + * Copyright 2022 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 static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.Bootstrapper.ServerInfo; +import static io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import static io.grpc.xds.XdsClient.ResourceUpdate; +import static io.grpc.xds.XdsClient.canonifyResourceName; +import static io.grpc.xds.XdsClient.isResourceNameValid; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import io.envoyproxy.envoy.service.discovery.v3.Resource; +import io.grpc.LoadBalancerRegistry; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; + +abstract class XdsResourceType { + static final String TYPE_URL_RESOURCE_V2 = "type.googleapis.com/envoy.api.v2.Resource"; + static final String TYPE_URL_RESOURCE_V3 = + "type.googleapis.com/envoy.service.discovery.v3.Resource"; + static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; + @VisibleForTesting + static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate"; + @VisibleForTesting + static final String HASH_POLICY_FILTER_STATE_KEY = "io.grpc.channel_id"; + @VisibleForTesting + static boolean enableFaultInjection = getFlag("GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION", true); + @VisibleForTesting + static boolean enableRetry = getFlag("GRPC_XDS_EXPERIMENTAL_ENABLE_RETRY", true); + @VisibleForTesting + static boolean enableRbac = getFlag("GRPC_XDS_EXPERIMENTAL_RBAC", true); + @VisibleForTesting + static boolean enableRouteLookup = getFlag("GRPC_EXPERIMENTAL_XDS_RLS_LB", false); + @VisibleForTesting + static boolean enableLeastRequest = + !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) + ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) + : Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest")); + @VisibleForTesting + static boolean enableCustomLbConfig = getFlag("GRPC_EXPERIMENTAL_XDS_CUSTOM_LB_CONFIG", true); + @VisibleForTesting + static boolean enableOutlierDetection = getFlag("GRPC_EXPERIMENTAL_ENABLE_OUTLIER_DETECTION", + true); + static final String TYPE_URL_CLUSTER_CONFIG_V2 = + "type.googleapis.com/envoy.config.cluster.aggregate.v2alpha.ClusterConfig"; + static final String TYPE_URL_CLUSTER_CONFIG = + "type.googleapis.com/envoy.extensions.clusters.aggregate.v3.ClusterConfig"; + static final String TYPE_URL_TYPED_STRUCT_UDPA = + "type.googleapis.com/udpa.type.v1.TypedStruct"; + static final String TYPE_URL_TYPED_STRUCT = + "type.googleapis.com/xds.type.v3.TypedStruct"; + + @Nullable + abstract String extractResourceName(Message unpackedResource); + + abstract Class unpackedClassName(); + + abstract ResourceType typeName(); + + abstract String typeUrl(); + + abstract String typeUrlV2(); + + // Non-null for State of the World resources. + @Nullable + abstract ResourceType dependentResource(); + + static class Args { + final ServerInfo serverInfo; + final String versionInfo; + final String nonce; + final Bootstrapper.BootstrapInfo bootstrapInfo; + final FilterRegistry filterRegistry; + final LoadBalancerRegistry loadBalancerRegistry; + final TlsContextManager tlsContextManager; + // Management server is required to always send newly requested resources, even if they + // may have been sent previously (proactively). Thus, client does not need to cache + // unrequested resources. + // Only resources in the set needs to be parsed. Null means parse everything. + final @Nullable Set subscribedResources; + + public Args(ServerInfo serverInfo, String versionInfo, String nonce, + Bootstrapper.BootstrapInfo bootstrapInfo, + FilterRegistry filterRegistry, + LoadBalancerRegistry loadBalancerRegistry, + TlsContextManager tlsContextManager, + @Nullable Set subscribedResources) { + this.serverInfo = serverInfo; + this.versionInfo = versionInfo; + this.nonce = nonce; + this.bootstrapInfo = bootstrapInfo; + this.filterRegistry = filterRegistry; + this.loadBalancerRegistry = loadBalancerRegistry; + this.tlsContextManager = tlsContextManager; + this.subscribedResources = subscribedResources; + } + } + + ValidatedResourceUpdate parse(Args args, List resources) { + Map> parsedResources = new HashMap<>(resources.size()); + Set unpackedResources = new HashSet<>(resources.size()); + Set invalidResources = new HashSet<>(); + List errors = new ArrayList<>(); + Set retainedResources = new HashSet<>(); + + for (int i = 0; i < resources.size(); i++) { + Any resource = resources.get(i); + + boolean isResourceV3; + Message unpackedMessage; + try { + resource = maybeUnwrapResources(resource); + isResourceV3 = resource.getTypeUrl().equals(typeUrl()); + unpackedMessage = unpackCompatibleType(resource, unpackedClassName(), + typeUrl(), typeUrlV2()); + } catch (InvalidProtocolBufferException e) { + errors.add(String.format("%s response Resource index %d - can't decode %s: %s", + typeName(), i, unpackedClassName().getSimpleName(), e.getMessage())); + continue; + } + String name = extractResourceName(unpackedMessage); + if (name == null || !isResourceNameValid(name, resource.getTypeUrl())) { + errors.add( + "Unsupported resource name: " + name + " for type: " + typeName()); + continue; + } + String cname = canonifyResourceName(name); + if (args.subscribedResources != null && !args.subscribedResources.contains(name)) { + continue; + } + unpackedResources.add(cname); + + T resourceUpdate; + try { + resourceUpdate = doParse(args, unpackedMessage, retainedResources, isResourceV3); + } catch (ClientXdsClient.ResourceInvalidException e) { + errors.add(String.format("%s response %s '%s' validation error: %s", + typeName(), unpackedClassName().getSimpleName(), cname, e.getMessage())); + invalidResources.add(cname); + continue; + } + + // Resource parsed successfully. + parsedResources.put(cname, new ParsedResource(resourceUpdate, resource)); + } + return new ValidatedResourceUpdate(parsedResources, unpackedResources, invalidResources, + errors, retainedResources); + + } + + abstract T doParse(Args args, Message unpackedMessage, Set retainedResources, + boolean isResourceV3) + throws ResourceInvalidException; + + /** + * Helper method to unpack serialized {@link com.google.protobuf.Any} message, while replacing + * Type URL {@code compatibleTypeUrl} with {@code typeUrl}. + * + * @param The type of unpacked message + * @param any serialized message to unpack + * @param clazz the class to unpack the message to + * @param typeUrl type URL to replace message Type URL, when it's compatible + * @param compatibleTypeUrl compatible Type URL to be replaced with {@code typeUrl} + * @return Unpacked message + * @throws InvalidProtocolBufferException if the message couldn't be unpacked + */ + static T unpackCompatibleType( + Any any, Class clazz, String typeUrl, String compatibleTypeUrl) + throws InvalidProtocolBufferException { + if (any.getTypeUrl().equals(compatibleTypeUrl)) { + any = any.toBuilder().setTypeUrl(typeUrl).build(); + } + return any.unpack(clazz); + } + + private Any maybeUnwrapResources(Any resource) + throws InvalidProtocolBufferException { + if (resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V2) + || resource.getTypeUrl().equals(TYPE_URL_RESOURCE_V3)) { + return unpackCompatibleType(resource, Resource.class, TYPE_URL_RESOURCE_V3, + TYPE_URL_RESOURCE_V2).getResource(); + } else { + return resource; + } + } + + static final class ParsedResource { + private final T resourceUpdate; + private final Any rawResource; + + public ParsedResource(T resourceUpdate, Any rawResource) { + this.resourceUpdate = checkNotNull(resourceUpdate, "resourceUpdate"); + this.rawResource = checkNotNull(rawResource, "rawResource"); + } + + T getResourceUpdate() { + return resourceUpdate; + } + + Any getRawResource() { + return rawResource; + } + } + + static final class ValidatedResourceUpdate { + Map> parsedResources; + Set unpackedResources; + Set invalidResources; + List errors; + Set retainedResources; + + // validated resource update + public ValidatedResourceUpdate(Map> parsedResources, + Set unpackedResources, + Set invalidResources, + List errors, + Set retainedResources) { + this.parsedResources = parsedResources; + this.unpackedResources = unpackedResources; + this.invalidResources = invalidResources; + this.errors = errors; + this.retainedResources = retainedResources; + } + } + + private static boolean getFlag(String envVarName, boolean enableByDefault) { + String envVar = System.getenv(envVarName); + if (enableByDefault) { + return Strings.isNullOrEmpty(envVar) || Boolean.parseBoolean(envVar); + } else { + return !Strings.isNullOrEmpty(envVar) && Boolean.parseBoolean(envVar); + } + } + + @VisibleForTesting + static final class StructOrError { + + /** + * Returns a {@link StructOrError} for the successfully converted data object. + */ + static StructOrError fromStruct(T struct) { + return new StructOrError<>(struct); + } + + /** + * Returns a {@link StructOrError} for the failure to convert the data object. + */ + static StructOrError 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; + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java new file mode 100644 index 0000000000..f2fca0b1bf --- /dev/null +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -0,0 +1,707 @@ +/* + * Copyright 2022 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 static io.grpc.xds.AbstractXdsClient.ResourceType.RDS; +import static io.grpc.xds.AbstractXdsClient.ResourceType; +import static io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; + +import com.github.udpa.udpa.type.v1.TypedStruct; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.protobuf.Any; +import com.google.protobuf.Duration; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Message; +import com.google.protobuf.util.Durations; +import com.google.re2j.Pattern; +import com.google.re2j.PatternSyntaxException; +import io.envoyproxy.envoy.config.core.v3.TypedExtensionConfig; +import io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin; +import io.envoyproxy.envoy.config.route.v3.RetryPolicy.RetryBackOff; +import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; +import io.envoyproxy.envoy.type.v3.FractionalPercent; +import io.grpc.Status; +import io.grpc.xds.ClientXdsClient.ResourceInvalidException; +import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; +import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; +import io.grpc.xds.Filter.FilterConfig; +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.RouteAction.HashPolicy; +import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; +import io.grpc.xds.VirtualHost.Route.RouteMatch; +import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; +import io.grpc.xds.XdsClient.ResourceUpdate; +import io.grpc.xds.internal.Matchers.FractionMatcher; +import io.grpc.xds.internal.Matchers.HeaderMatcher; +import io.grpc.xds.internal.Matchers; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.annotation.Nullable; + +class XdsRouteConfigureResource extends XdsResourceType { + static final String ADS_TYPE_URL_RDS_V2 = + "type.googleapis.com/envoy.api.v2.RouteConfiguration"; + static final String ADS_TYPE_URL_RDS = + "type.googleapis.com/envoy.config.route.v3.RouteConfiguration"; + private static final String TYPE_URL_FILTER_CONFIG = + "type.googleapis.com/envoy.config.route.v3.FilterConfig"; + // TODO(zdapeng): need to discuss how to handle unsupported values. + private static final Set SUPPORTED_RETRYABLE_CODES = + Collections.unmodifiableSet(EnumSet.of( + Status.Code.CANCELLED, Status.Code.DEADLINE_EXCEEDED, Status.Code.INTERNAL, + Status.Code.RESOURCE_EXHAUSTED, Status.Code.UNAVAILABLE)); + + private static final XdsRouteConfigureResource instance = new XdsRouteConfigureResource(); + + public static XdsRouteConfigureResource getInstance() { + return instance; + } + + @Override + @Nullable + String extractResourceName(Message unpackedResource) { + if (!(unpackedResource instanceof RouteConfiguration)) { + return null; + } + return ((RouteConfiguration) unpackedResource).getName(); + } + + @Override + ResourceType typeName() { + return RDS; + } + + @Override + String typeUrl() { + return ADS_TYPE_URL_RDS; + } + + @Override + String typeUrlV2() { + return ADS_TYPE_URL_RDS_V2; + } + + @Nullable + @Override + ResourceType dependentResource() { + return null; + } + + @Override + Class unpackedClassName() { + return RouteConfiguration.class; + } + + @Override + RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage, + Set retainedResources, boolean isResourceV3) + throws ResourceInvalidException { + if (!(unpackedMessage instanceof RouteConfiguration)) { + throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); + } + return processRouteConfiguration((RouteConfiguration) unpackedMessage, + args.filterRegistry, enableFaultInjection && isResourceV3); + } + + private static RdsUpdate processRouteConfiguration( + RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) + throws ResourceInvalidException { + return new RdsUpdate(extractVirtualHosts(routeConfig, filterRegistry, parseHttpFilter)); + } + + static List extractVirtualHosts( + RouteConfiguration routeConfig, FilterRegistry filterRegistry, boolean parseHttpFilter) + throws ResourceInvalidException { + Map pluginConfigMap = new HashMap<>(); + ImmutableSet.Builder optionalPlugins = ImmutableSet.builder(); + + if (enableRouteLookup) { + List plugins = routeConfig.getClusterSpecifierPluginsList(); + for (ClusterSpecifierPlugin plugin : plugins) { + String pluginName = plugin.getExtension().getName(); + PluginConfig pluginConfig = parseClusterSpecifierPlugin(plugin); + if (pluginConfig != null) { + if (pluginConfigMap.put(pluginName, pluginConfig) != null) { + throw new ResourceInvalidException( + "Multiple ClusterSpecifierPlugins with the same name: " + pluginName); + } + } else { + // The plugin parsed successfully, and it's not supported, but it's marked as optional. + optionalPlugins.add(pluginName); + } + } + } + List virtualHosts = new ArrayList<>(routeConfig.getVirtualHostsCount()); + for (io.envoyproxy.envoy.config.route.v3.VirtualHost virtualHostProto + : routeConfig.getVirtualHostsList()) { + StructOrError virtualHost = + parseVirtualHost(virtualHostProto, filterRegistry, parseHttpFilter, pluginConfigMap, + optionalPlugins.build()); + if (virtualHost.getErrorDetail() != null) { + throw new ResourceInvalidException( + "RouteConfiguration contains invalid virtual host: " + virtualHost.getErrorDetail()); + } + virtualHosts.add(virtualHost.getStruct()); + } + return virtualHosts; + } + + private static StructOrError parseVirtualHost( + io.envoyproxy.envoy.config.route.v3.VirtualHost proto, FilterRegistry filterRegistry, + boolean parseHttpFilter, Map pluginConfigMap, + Set optionalPlugins) { + String name = proto.getName(); + List routes = new ArrayList<>(proto.getRoutesCount()); + for (io.envoyproxy.envoy.config.route.v3.Route routeProto : proto.getRoutesList()) { + StructOrError route = parseRoute( + routeProto, filterRegistry, parseHttpFilter, pluginConfigMap, optionalPlugins); + if (route == null) { + continue; + } + if (route.getErrorDetail() != null) { + return StructOrError.fromError( + "Virtual host [" + name + "] contains invalid route : " + route.getErrorDetail()); + } + routes.add(route.getStruct()); + } + if (!parseHttpFilter) { + return StructOrError.fromStruct(VirtualHost.create( + name, proto.getDomainsList(), routes, new HashMap())); + } + StructOrError> overrideConfigs = + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + if (overrideConfigs.getErrorDetail() != null) { + return StructOrError.fromError( + "VirtualHost [" + proto.getName() + "] contains invalid HttpFilter config: " + + overrideConfigs.getErrorDetail()); + } + return StructOrError.fromStruct(VirtualHost.create( + name, proto.getDomainsList(), routes, overrideConfigs.getStruct())); + } + + @VisibleForTesting + static StructOrError> parseOverrideFilterConfigs( + Map rawFilterConfigMap, FilterRegistry filterRegistry) { + Map overrideConfigs = new HashMap<>(); + for (String name : rawFilterConfigMap.keySet()) { + Any anyConfig = rawFilterConfigMap.get(name); + String typeUrl = anyConfig.getTypeUrl(); + boolean isOptional = false; + if (typeUrl.equals(TYPE_URL_FILTER_CONFIG)) { + io.envoyproxy.envoy.config.route.v3.FilterConfig filterConfig; + try { + filterConfig = + anyConfig.unpack(io.envoyproxy.envoy.config.route.v3.FilterConfig.class); + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "FilterConfig [" + name + "] contains invalid proto: " + e); + } + isOptional = filterConfig.getIsOptional(); + anyConfig = filterConfig.getConfig(); + typeUrl = anyConfig.getTypeUrl(); + } + Message rawConfig = anyConfig; + try { + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA)) { + TypedStruct typedStruct = anyConfig.unpack(TypedStruct.class); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } else if (typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + com.github.xds.type.v3.TypedStruct newTypedStruct = + anyConfig.unpack(com.github.xds.type.v3.TypedStruct.class); + typeUrl = newTypedStruct.getTypeUrl(); + rawConfig = newTypedStruct.getValue(); + } + } catch (InvalidProtocolBufferException e) { + return StructOrError.fromError( + "FilterConfig [" + name + "] contains invalid proto: " + e); + } + Filter filter = filterRegistry.get(typeUrl); + if (filter == null) { + if (isOptional) { + continue; + } + return StructOrError.fromError( + "HttpFilter [" + name + "](" + typeUrl + ") is required but unsupported"); + } + ConfigOrError filterConfig = + filter.parseFilterConfigOverride(rawConfig); + if (filterConfig.errorDetail != null) { + return StructOrError.fromError( + "Invalid filter config for HttpFilter [" + name + "]: " + filterConfig.errorDetail); + } + overrideConfigs.put(name, filterConfig.config); + } + return StructOrError.fromStruct(overrideConfigs); + } + + @VisibleForTesting + @Nullable + static StructOrError parseRoute( + io.envoyproxy.envoy.config.route.v3.Route proto, FilterRegistry filterRegistry, + boolean parseHttpFilter, Map pluginConfigMap, + Set optionalPlugins) { + StructOrError routeMatch = parseRouteMatch(proto.getMatch()); + if (routeMatch == null) { + return null; + } + if (routeMatch.getErrorDetail() != null) { + return StructOrError.fromError( + "Route [" + proto.getName() + "] contains invalid RouteMatch: " + + routeMatch.getErrorDetail()); + } + + Map overrideConfigs = Collections.emptyMap(); + if (parseHttpFilter) { + StructOrError> overrideConfigsOrError = + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + if (overrideConfigsOrError.getErrorDetail() != null) { + return StructOrError.fromError( + "Route [" + proto.getName() + "] contains invalid HttpFilter config: " + + overrideConfigsOrError.getErrorDetail()); + } + overrideConfigs = overrideConfigsOrError.getStruct(); + } + + switch (proto.getActionCase()) { + case ROUTE: + StructOrError routeAction = + parseRouteAction(proto.getRoute(), filterRegistry, parseHttpFilter, pluginConfigMap, + optionalPlugins); + if (routeAction == null) { + return null; + } + if (routeAction.getErrorDetail() != null) { + return StructOrError.fromError( + "Route [" + proto.getName() + "] contains invalid RouteAction: " + + routeAction.getErrorDetail()); + } + return StructOrError.fromStruct( + Route.forAction(routeMatch.getStruct(), routeAction.getStruct(), overrideConfigs)); + case NON_FORWARDING_ACTION: + return StructOrError.fromStruct( + Route.forNonForwardingAction(routeMatch.getStruct(), overrideConfigs)); + case REDIRECT: + case DIRECT_RESPONSE: + case FILTER_ACTION: + case ACTION_NOT_SET: + default: + return StructOrError.fromError( + "Route [" + proto.getName() + "] with unknown action type: " + proto.getActionCase()); + } + } + + @VisibleForTesting + @Nullable + static StructOrError parseRouteMatch( + io.envoyproxy.envoy.config.route.v3.RouteMatch proto) { + if (proto.getQueryParametersCount() != 0) { + return null; + } + StructOrError pathMatch = parsePathMatcher(proto); + if (pathMatch.getErrorDetail() != null) { + return StructOrError.fromError(pathMatch.getErrorDetail()); + } + + FractionMatcher fractionMatch = null; + if (proto.hasRuntimeFraction()) { + StructOrError parsedFraction = + parseFractionMatcher(proto.getRuntimeFraction().getDefaultValue()); + if (parsedFraction.getErrorDetail() != null) { + return StructOrError.fromError(parsedFraction.getErrorDetail()); + } + fractionMatch = parsedFraction.getStruct(); + } + + List headerMatchers = new ArrayList<>(); + for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher hmProto : proto.getHeadersList()) { + StructOrError 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 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 parseFractionMatcher(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 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(Matchers.HeaderMatcher.forSafeRegEx( + proto.getName(), safeRegExMatch, proto.getInvertMatch())); + case RANGE_MATCH: + Matchers.HeaderMatcher.Range rangeMatch = Matchers.HeaderMatcher.Range.create( + proto.getRangeMatch().getStart(), proto.getRangeMatch().getEnd()); + return StructOrError.fromStruct(Matchers.HeaderMatcher.forRange( + proto.getName(), rangeMatch, proto.getInvertMatch())); + case PRESENT_MATCH: + return StructOrError.fromStruct(Matchers.HeaderMatcher.forPresent( + proto.getName(), proto.getPresentMatch(), proto.getInvertMatch())); + case PREFIX_MATCH: + return StructOrError.fromStruct(Matchers.HeaderMatcher.forPrefix( + proto.getName(), proto.getPrefixMatch(), proto.getInvertMatch())); + case SUFFIX_MATCH: + return StructOrError.fromStruct(Matchers.HeaderMatcher.forSuffix( + proto.getName(), proto.getSuffixMatch(), proto.getInvertMatch())); + case HEADERMATCHSPECIFIER_NOT_SET: + default: + return StructOrError.fromError("Unknown header matcher type"); + } + } + + /** + * Parses the RouteAction config. The returned result may contain a (parsed form) + * {@link RouteAction} or an error message. Returns {@code null} if the RouteAction + * should be ignored. + */ + @VisibleForTesting + @Nullable + static StructOrError parseRouteAction( + io.envoyproxy.envoy.config.route.v3.RouteAction proto, FilterRegistry filterRegistry, + boolean parseHttpFilter, Map pluginConfigMap, + Set optionalPlugins) { + 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()); + } + } + RetryPolicy retryPolicy = null; + if (enableRetry && proto.hasRetryPolicy()) { + StructOrError retryPolicyOrError = parseRetryPolicy(proto.getRetryPolicy()); + if (retryPolicyOrError != null) { + if (retryPolicyOrError.getErrorDetail() != null) { + return StructOrError.fromError(retryPolicyOrError.getErrorDetail()); + } + retryPolicy = retryPolicyOrError.getStruct(); + } + } + List hashPolicies = new ArrayList<>(); + for (io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy config + : proto.getHashPolicyList()) { + HashPolicy policy = null; + boolean terminal = config.getTerminal(); + switch (config.getPolicySpecifierCase()) { + case HEADER: + io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.Header headerCfg = + config.getHeader(); + Pattern regEx = null; + String regExSubstitute = null; + if (headerCfg.hasRegexRewrite() && headerCfg.getRegexRewrite().hasPattern() + && headerCfg.getRegexRewrite().getPattern().hasGoogleRe2()) { + regEx = Pattern.compile(headerCfg.getRegexRewrite().getPattern().getRegex()); + regExSubstitute = headerCfg.getRegexRewrite().getSubstitution(); + } + policy = HashPolicy.forHeader( + terminal, headerCfg.getHeaderName(), regEx, regExSubstitute); + break; + case FILTER_STATE: + if (config.getFilterState().getKey().equals(HASH_POLICY_FILTER_STATE_KEY)) { + policy = HashPolicy.forChannelId(terminal); + } + break; + default: + // Ignore + } + if (policy != null) { + hashPolicies.add(policy); + } + } + + switch (proto.getClusterSpecifierCase()) { + case CLUSTER: + return StructOrError.fromStruct(RouteAction.forCluster( + proto.getCluster(), hashPolicies, timeoutNano, retryPolicy)); + case CLUSTER_HEADER: + return null; + case WEIGHTED_CLUSTERS: + List clusterWeights + = proto.getWeightedClusters().getClustersList(); + if (clusterWeights.isEmpty()) { + return StructOrError.fromError("No cluster found in weighted cluster list"); + } + List weightedClusters = new ArrayList<>(); + for (io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight clusterWeight + : clusterWeights) { + StructOrError clusterWeightOrError = + parseClusterWeight(clusterWeight, filterRegistry, parseHttpFilter); + 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(VirtualHost.Route.RouteAction.forWeightedClusters( + weightedClusters, hashPolicies, timeoutNano, retryPolicy)); + case CLUSTER_SPECIFIER_PLUGIN: + if (enableRouteLookup) { + String pluginName = proto.getClusterSpecifierPlugin(); + PluginConfig pluginConfig = pluginConfigMap.get(pluginName); + if (pluginConfig == null) { + // Skip route if the plugin is not registered, but it's optional. + if (optionalPlugins.contains(pluginName)) { + return null; + } + return StructOrError.fromError( + "ClusterSpecifierPlugin for [" + pluginName + "] not found"); + } + NamedPluginConfig namedPluginConfig = NamedPluginConfig.create(pluginName, pluginConfig); + return StructOrError.fromStruct(VirtualHost.Route.RouteAction.forClusterSpecifierPlugin( + namedPluginConfig, hashPolicies, timeoutNano, retryPolicy)); + } else { + return null; + } + case CLUSTERSPECIFIER_NOT_SET: + default: + return null; + } + } + + @Nullable // Return null if we ignore the given policy. + private static StructOrError parseRetryPolicy( + io.envoyproxy.envoy.config.route.v3.RetryPolicy retryPolicyProto) { + int maxAttempts = 2; + if (retryPolicyProto.hasNumRetries()) { + maxAttempts = retryPolicyProto.getNumRetries().getValue() + 1; + } + Duration initialBackoff = Durations.fromMillis(25); + Duration maxBackoff = Durations.fromMillis(250); + if (retryPolicyProto.hasRetryBackOff()) { + RetryBackOff retryBackOff = retryPolicyProto.getRetryBackOff(); + if (!retryBackOff.hasBaseInterval()) { + return StructOrError.fromError("No base_interval specified in retry_backoff"); + } + Duration originalInitialBackoff = initialBackoff = retryBackOff.getBaseInterval(); + if (Durations.compare(initialBackoff, Durations.ZERO) <= 0) { + return StructOrError.fromError("base_interval in retry_backoff must be positive"); + } + if (Durations.compare(initialBackoff, Durations.fromMillis(1)) < 0) { + initialBackoff = Durations.fromMillis(1); + } + if (retryBackOff.hasMaxInterval()) { + maxBackoff = retryPolicyProto.getRetryBackOff().getMaxInterval(); + if (Durations.compare(maxBackoff, originalInitialBackoff) < 0) { + return StructOrError.fromError( + "max_interval in retry_backoff cannot be less than base_interval"); + } + if (Durations.compare(maxBackoff, Durations.fromMillis(1)) < 0) { + maxBackoff = Durations.fromMillis(1); + } + } else { + maxBackoff = Durations.fromNanos(Durations.toNanos(initialBackoff) * 10); + } + } + Iterable retryOns = + Splitter.on(',').omitEmptyStrings().trimResults().split(retryPolicyProto.getRetryOn()); + ImmutableList.Builder retryableStatusCodesBuilder = ImmutableList.builder(); + for (String retryOn : retryOns) { + Status.Code code; + try { + code = Status.Code.valueOf(retryOn.toUpperCase(Locale.US).replace('-', '_')); + } catch (IllegalArgumentException e) { + // unsupported value, such as "5xx" + continue; + } + if (!SUPPORTED_RETRYABLE_CODES.contains(code)) { + // unsupported value + continue; + } + retryableStatusCodesBuilder.add(code); + } + List retryableStatusCodes = retryableStatusCodesBuilder.build(); + return StructOrError.fromStruct( + VirtualHost.Route.RouteAction.RetryPolicy.create( + maxAttempts, retryableStatusCodes, initialBackoff, maxBackoff, + /* perAttemptRecvTimeout= */ null)); + } + + @VisibleForTesting + static StructOrError parseClusterWeight( + io.envoyproxy.envoy.config.route.v3.WeightedCluster.ClusterWeight proto, + FilterRegistry filterRegistry, boolean parseHttpFilter) { + if (!parseHttpFilter) { + return StructOrError.fromStruct(ClusterWeight.create(proto.getName(), + proto.getWeight().getValue(), new HashMap())); + } + StructOrError> overrideConfigs = + parseOverrideFilterConfigs(proto.getTypedPerFilterConfigMap(), filterRegistry); + if (overrideConfigs.getErrorDetail() != null) { + return StructOrError.fromError( + "ClusterWeight [" + proto.getName() + "] contains invalid HttpFilter config: " + + overrideConfigs.getErrorDetail()); + } + return StructOrError.fromStruct(VirtualHost.Route.RouteAction.ClusterWeight.create( + proto.getName(), proto.getWeight().getValue(), overrideConfigs.getStruct())); + } + + @Nullable // null if the plugin is not supported, but it's marked as optional. + private static PluginConfig parseClusterSpecifierPlugin(ClusterSpecifierPlugin pluginProto) + throws ResourceInvalidException { + return parseClusterSpecifierPlugin( + pluginProto, ClusterSpecifierPluginRegistry.getDefaultRegistry()); + } + + @Nullable // null if the plugin is not supported, but it's marked as optional. + @VisibleForTesting + static PluginConfig parseClusterSpecifierPlugin( + ClusterSpecifierPlugin pluginProto, ClusterSpecifierPluginRegistry registry) + throws ResourceInvalidException { + TypedExtensionConfig extension = pluginProto.getExtension(); + String pluginName = extension.getName(); + Any anyConfig = extension.getTypedConfig(); + String typeUrl = anyConfig.getTypeUrl(); + Message rawConfig = anyConfig; + if (typeUrl.equals(TYPE_URL_TYPED_STRUCT_UDPA) || typeUrl.equals(TYPE_URL_TYPED_STRUCT)) { + try { + TypedStruct typedStruct = unpackCompatibleType( + anyConfig, TypedStruct.class, TYPE_URL_TYPED_STRUCT_UDPA, TYPE_URL_TYPED_STRUCT); + typeUrl = typedStruct.getTypeUrl(); + rawConfig = typedStruct.getValue(); + } catch (InvalidProtocolBufferException e) { + throw new ResourceInvalidException( + "ClusterSpecifierPlugin [" + pluginName + "] contains invalid proto", e); + } + } + io.grpc.xds.ClusterSpecifierPlugin plugin = registry.get(typeUrl); + if (plugin == null) { + if (!pluginProto.getIsOptional()) { + throw new ResourceInvalidException("Unsupported ClusterSpecifierPlugin type: " + typeUrl); + } + return null; + } + ConfigOrError pluginConfigOrError = plugin.parsePlugin(rawConfig); + if (pluginConfigOrError.errorDetail != null) { + throw new ResourceInvalidException(pluginConfigOrError.errorDetail); + } + return pluginConfigOrError.config; + } + + static final class RdsUpdate implements ResourceUpdate { + // The list virtual hosts that make up the route table. + final List virtualHosts; + + RdsUpdate(List virtualHosts) { + this.virtualHosts = Collections.unmodifiableList( + new ArrayList<>(checkNotNull(virtualHosts, "virtualHosts"))); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("virtualHosts", virtualHosts) + .toString(); + } + + @Override + public int hashCode() { + return Objects.hash(virtualHosts); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RdsUpdate that = (RdsUpdate) o; + return Objects.equals(virtualHosts, that.virtualHosts); + } + } +} diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java index b269e7f9bc..a58a3f6bc2 100644 --- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java +++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java @@ -50,11 +50,10 @@ import io.grpc.xds.Filter.ServerInterceptorBuilder; import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector; import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl; import io.grpc.xds.VirtualHost.Route; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.LdsUpdate; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; import io.grpc.xds.internal.security.SslContextProviderSupplier; import java.io.IOException; @@ -344,7 +343,7 @@ final class XdsServerWrapper extends Server { } } - private final class DiscoveryState implements LdsResourceWatcher { + private final class DiscoveryState implements ResourceWatcher { private final String resourceName; // RDS resource name is the key. private final Map routeDiscoveryStates = new HashMap<>(); @@ -368,7 +367,7 @@ final class XdsServerWrapper extends Server { private DiscoveryState(String resourceName) { this.resourceName = checkNotNull(resourceName, "resourceName"); - xdsClient.watchLdsResource(resourceName, this); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), resourceName, this); } @Override @@ -402,7 +401,8 @@ final class XdsServerWrapper extends Server { if (rdsState == null) { rdsState = new RouteDiscoveryState(hcm.rdsName()); routeDiscoveryStates.put(hcm.rdsName(), rdsState); - xdsClient.watchRdsResource(hcm.rdsName(), rdsState); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), + hcm.rdsName(), rdsState); } if (rdsState.isPending) { pendingRds.add(hcm.rdsName()); @@ -412,7 +412,8 @@ final class XdsServerWrapper extends Server { } for (Map.Entry entry: routeDiscoveryStates.entrySet()) { if (!allRds.contains(entry.getKey())) { - xdsClient.cancelRdsResourceWatch(entry.getKey(), entry.getValue()); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), + entry.getKey(), entry.getValue()); } } routeDiscoveryStates.keySet().retainAll(allRds); @@ -458,7 +459,7 @@ final class XdsServerWrapper extends Server { stopped = true; cleanUpRouteDiscoveryStates(); logger.log(Level.FINE, "Stop watching LDS resource {0}", resourceName); - xdsClient.cancelLdsResourceWatch(resourceName, this); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), resourceName, this); List toRelease = getSuppliersInUse(); filterChainSelectorManager.updateSelector(FilterChainSelector.NO_FILTER_CHAIN); for (SslContextProviderSupplier s: toRelease) { @@ -588,7 +589,8 @@ final class XdsServerWrapper extends Server { for (RouteDiscoveryState rdsState : routeDiscoveryStates.values()) { String rdsName = rdsState.resourceName; logger.log(Level.FINE, "Stop watching RDS resource {0}", rdsName); - xdsClient.cancelRdsResourceWatch(rdsName, rdsState); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), rdsName, + rdsState); } routeDiscoveryStates.clear(); savedRdsRoutingConfigRef.clear(); @@ -626,7 +628,7 @@ final class XdsServerWrapper extends Server { } } - private final class RouteDiscoveryState implements RdsResourceWatcher { + private final class RouteDiscoveryState implements ResourceWatcher { private final String resourceName; private ImmutableList savedVirtualHosts; private boolean isPending = true; diff --git a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java index c211551f1f..1fd81e606a 100644 --- a/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java +++ b/xds/src/test/java/io/grpc/xds/CdsLoadBalancer2Test.java @@ -17,6 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.AbstractXdsClient.ResourceType.CDS; import static io.grpc.xds.XdsLbPolicies.CLUSTER_RESOLVER_POLICY_NAME; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; @@ -55,7 +56,7 @@ import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.LeastRequestLoadBalancer.LeastRequestConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; -import io.grpc.xds.XdsClient.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.util.ArrayList; import java.util.Arrays; @@ -91,7 +92,7 @@ public class CdsLoadBalancer2Test { null, null, null, null, SuccessRateEjection.create(null, null, null, null), null); - private final SynchronizationContext syncContext = new SynchronizationContext( + private static final SynchronizationContext syncContext = new SynchronizationContext( new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { @@ -649,17 +650,24 @@ public class CdsLoadBalancer2Test { } } - private static final class FakeXdsClient extends XdsClient { - private final Map watchers = new HashMap<>(); + private final class FakeXdsClient extends XdsClient { + private final Map> watchers = new HashMap<>(); @Override - void watchCdsResource(String resourceName, CdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(CDS); assertThat(watchers).doesNotContainKey(resourceName); - watchers.put(resourceName, watcher); + watchers.put(resourceName, (ResourceWatcher)watcher); } @Override - void cancelCdsResourceWatch(String resourceName, CdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(CDS); assertThat(watchers).containsKey(resourceName); watchers.remove(resourceName); } @@ -677,7 +685,7 @@ public class CdsLoadBalancer2Test { } private void deliverError(Status error) { - for (CdsResourceWatcher watcher : watchers.values()) { + for (ResourceWatcher watcher : watchers.values()) { watcher.onError(error); } } diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java index fdc39d6f6f..dae17bfb89 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientDataTest.java @@ -118,7 +118,6 @@ import io.grpc.lookup.v1.RouteLookupConfig; import io.grpc.xds.AbstractXdsClient.ResourceType; import io.grpc.xds.Bootstrapper.ServerInfo; import io.grpc.xds.ClientXdsClient.ResourceInvalidException; -import io.grpc.xds.ClientXdsClient.StructOrError; import io.grpc.xds.ClusterSpecifierPlugin.NamedPluginConfig; import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig; import io.grpc.xds.Endpoints.LbEndpoint; @@ -131,7 +130,8 @@ import io.grpc.xds.VirtualHost.Route.RouteAction.ClusterWeight; import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsResourceType.StructOrError; import io.grpc.xds.internal.Matchers.FractionMatcher; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.util.Arrays; @@ -168,22 +168,22 @@ public class ClientXdsClientDataTest { @Before public void setUp() { - originalEnableRetry = ClientXdsClient.enableRetry; + originalEnableRetry = XdsResourceType.enableRetry; assertThat(originalEnableRetry).isTrue(); - originalEnableRbac = ClientXdsClient.enableRbac; + originalEnableRbac = XdsResourceType.enableRbac; assertThat(originalEnableRbac).isTrue(); - originalEnableRouteLookup = ClientXdsClient.enableRouteLookup; + originalEnableRouteLookup = XdsResourceType.enableRouteLookup; assertThat(originalEnableRouteLookup).isFalse(); - originalEnableLeastRequest = ClientXdsClient.enableLeastRequest; + originalEnableLeastRequest = XdsResourceType.enableLeastRequest; assertThat(originalEnableLeastRequest).isFalse(); } @After public void tearDown() { - ClientXdsClient.enableRetry = originalEnableRetry; - ClientXdsClient.enableRbac = originalEnableRbac; - ClientXdsClient.enableRouteLookup = originalEnableRouteLookup; - ClientXdsClient.enableLeastRequest = originalEnableLeastRequest; + XdsResourceType.enableRetry = originalEnableRetry; + XdsResourceType.enableRbac = originalEnableRbac; + XdsResourceType.enableRouteLookup = originalEnableRouteLookup; + XdsResourceType.enableLeastRequest = originalEnableLeastRequest; } @Test @@ -198,7 +198,7 @@ public class ClientXdsClientDataTest { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setCluster("cluster-foo")) .build(); - StructOrError struct = ClientXdsClient.parseRoute( + StructOrError struct = XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()) @@ -221,7 +221,7 @@ public class ClientXdsClientDataTest { .setPath("/service/method")) .setNonForwardingAction(NonForwardingAction.getDefaultInstance()) .build(); - StructOrError struct = ClientXdsClient.parseRoute( + StructOrError struct = XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct()) .isEqualTo( @@ -240,7 +240,7 @@ public class ClientXdsClientDataTest { .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath("")) .setRedirect(RedirectAction.getDefaultInstance()) .build(); - res = ClientXdsClient.parseRoute( + res = XdsRouteConfigureResource.parseRoute( redirectRoute, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) @@ -252,7 +252,7 @@ public class ClientXdsClientDataTest { .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath("")) .setDirectResponse(DirectResponseAction.getDefaultInstance()) .build(); - res = ClientXdsClient.parseRoute( + res = XdsRouteConfigureResource.parseRoute( directResponseRoute, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) @@ -264,7 +264,7 @@ public class ClientXdsClientDataTest { .setMatch(io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPath("")) .setFilterAction(FilterAction.getDefaultInstance()) .build(); - res = ClientXdsClient.parseRoute( + res = XdsRouteConfigureResource.parseRoute( filterRoute, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(res.getStruct()).isNull(); assertThat(res.getErrorDetail()) @@ -286,7 +286,7 @@ public class ClientXdsClientDataTest { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setCluster("cluster-foo")) .build(); - assertThat(ClientXdsClient.parseRoute( + assertThat(XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of())) .isNull(); } @@ -303,7 +303,7 @@ public class ClientXdsClientDataTest { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setClusterHeader("cluster header")) // cluster_header action not supported .build(); - assertThat(ClientXdsClient.parseRoute( + assertThat(XdsRouteConfigureResource.parseRoute( proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of())) .isNull(); } @@ -323,7 +323,7 @@ public class ClientXdsClientDataTest { .setName(":method") .setExactMatch("PUT")) .build(); - StructOrError struct = ClientXdsClient.parseRouteMatch(proto); + StructOrError struct = XdsRouteConfigureResource.parseRouteMatch(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()) .isEqualTo( @@ -347,7 +347,7 @@ public class ClientXdsClientDataTest { .setNumerator(30) .setDenominator(FractionalPercent.DenominatorType.HUNDRED))) .build(); - StructOrError struct = ClientXdsClient.parseRouteMatch(proto); + StructOrError struct = XdsRouteConfigureResource.parseRouteMatch(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()) .isEqualTo( @@ -362,7 +362,7 @@ public class ClientXdsClientDataTest { io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder() .setPath("/service/method") .build(); - StructOrError struct = ClientXdsClient.parsePathMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parsePathMatcher(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( PathMatcher.fromPath("/service/method", false)); @@ -372,7 +372,7 @@ public class ClientXdsClientDataTest { public void parsePathMatcher_withPrefix() { io.envoyproxy.envoy.config.route.v3.RouteMatch proto = io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder().setPrefix("/").build(); - StructOrError struct = ClientXdsClient.parsePathMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parsePathMatcher(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( PathMatcher.fromPrefix("/", false)); @@ -384,7 +384,7 @@ public class ClientXdsClientDataTest { io.envoyproxy.envoy.config.route.v3.RouteMatch.newBuilder() .setSafeRegex(RegexMatcher.newBuilder().setRegex(".")) .build(); - StructOrError struct = ClientXdsClient.parsePathMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parsePathMatcher(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo(PathMatcher.fromRegEx(Pattern.compile("."))); } @@ -397,7 +397,7 @@ public class ClientXdsClientDataTest { .setName(":method") .setExactMatch("PUT") .build(); - StructOrError struct1 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct1 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct1.getErrorDetail()).isNull(); assertThat(struct1.getStruct()).isEqualTo( HeaderMatcher.forExactValue(":method", "PUT", false)); @@ -411,7 +411,7 @@ public class ClientXdsClientDataTest { .setName(":method") .setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("P*")) .build(); - StructOrError struct3 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct3 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct3.getErrorDetail()).isNull(); assertThat(struct3.getStruct()).isEqualTo( HeaderMatcher.forSafeRegEx(":method", Pattern.compile("P*"), false)); @@ -424,7 +424,7 @@ public class ClientXdsClientDataTest { .setName("timeout") .setRangeMatch(Int64Range.newBuilder().setStart(10L).setEnd(20L)) .build(); - StructOrError struct4 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct4 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct4.getErrorDetail()).isNull(); assertThat(struct4.getStruct()).isEqualTo( HeaderMatcher.forRange("timeout", HeaderMatcher.Range.create(10L, 20L), false)); @@ -437,7 +437,7 @@ public class ClientXdsClientDataTest { .setName("user-agent") .setPresentMatch(true) .build(); - StructOrError struct5 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct5 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct5.getErrorDetail()).isNull(); assertThat(struct5.getStruct()).isEqualTo( HeaderMatcher.forPresent("user-agent", true, false)); @@ -451,7 +451,7 @@ public class ClientXdsClientDataTest { .setName("authority") .setPrefixMatch("service-foo") .build(); - StructOrError struct6 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct6 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct6.getErrorDetail()).isNull(); assertThat(struct6.getStruct()).isEqualTo( HeaderMatcher.forPrefix("authority", "service-foo", false)); @@ -465,7 +465,7 @@ public class ClientXdsClientDataTest { .setName("authority") .setSuffixMatch("googleapis.com") .build(); - StructOrError struct7 = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct7 = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct7.getErrorDetail()).isNull(); assertThat(struct7.getStruct()).isEqualTo( HeaderMatcher.forSuffix("authority", "googleapis.com", false)); @@ -479,7 +479,7 @@ public class ClientXdsClientDataTest { .setName(":method") .setSafeRegexMatch(RegexMatcher.newBuilder().setRegex("[")) .build(); - StructOrError struct = ClientXdsClient.parseHeaderMatcher(proto); + StructOrError struct = XdsRouteConfigureResource.parseHeaderMatcher(proto); assertThat(struct.getErrorDetail()).isNotNull(); assertThat(struct.getStruct()).isNull(); } @@ -491,7 +491,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct().cluster()).isEqualTo("cluster-foo"); @@ -515,7 +515,7 @@ public class ClientXdsClientDataTest { .setWeight(UInt32Value.newBuilder().setValue(70)))) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct().cluster()).isNull(); @@ -535,7 +535,7 @@ public class ClientXdsClientDataTest { .setMaxStreamDuration(Durations.fromMillis(20L))) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L)); } @@ -550,7 +550,7 @@ public class ClientXdsClientDataTest { .setMaxStreamDuration(Durations.fromSeconds(5L))) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().timeoutNano()).isEqualTo(TimeUnit.SECONDS.toNanos(5L)); } @@ -562,14 +562,14 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().timeoutNano()).isNull(); } @Test public void parseRouteAction_withRetryPolicy() { - ClientXdsClient.enableRetry = true; + XdsResourceType.enableRetry = true; RetryPolicy.Builder builder = RetryPolicy.newBuilder() .setNumRetries(UInt32Value.of(3)) .setRetryBackOff( @@ -585,7 +585,7 @@ public class ClientXdsClientDataTest { .setRetryPolicy(builder.build()) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); RouteAction.RetryPolicy retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.maxAttempts()).isEqualTo(4); @@ -609,7 +609,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder.build()) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy()).isNotNull(); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()).isEmpty(); @@ -622,7 +622,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()).isEqualTo("No base_interval specified in retry_backoff"); @@ -632,7 +632,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.maxBackoff()).isEqualTo(Durations.fromMillis(500 * 10)); @@ -643,7 +643,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()) .isEqualTo("base_interval in retry_backoff must be positive"); @@ -656,7 +656,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()) .isEqualTo("max_interval in retry_backoff cannot be less than base_interval"); @@ -669,7 +669,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getErrorDetail()) .isEqualTo("max_interval in retry_backoff cannot be less than base_interval"); @@ -682,7 +682,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().initialBackoff()) .isEqualTo(Durations.fromMillis(1)); @@ -698,7 +698,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); retryPolicy = struct.getStruct().retryPolicy(); assertThat(retryPolicy.initialBackoff()).isEqualTo(Durations.fromMillis(25)); @@ -717,7 +717,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -735,7 +735,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -753,7 +753,7 @@ public class ClientXdsClientDataTest { .setCluster("cluster-foo") .setRetryPolicy(builder) .build(); - struct = ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + struct = XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct.getStruct().retryPolicy().retryableStatusCodes()) .containsExactly(Code.CANCELLED); @@ -784,14 +784,14 @@ public class ClientXdsClientDataTest { io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder() .setFilterState( FilterState.newBuilder() - .setKey(ClientXdsClient.HASH_POLICY_FILTER_STATE_KEY))) + .setKey(XdsResourceType.HASH_POLICY_FILTER_STATE_KEY))) .addHashPolicy( io.envoyproxy.envoy.config.route.v3.RouteAction.HashPolicy.newBuilder() .setQueryParameter( QueryParameter.newBuilder().setName("param"))) // unsupported .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); List policies = struct.getStruct().hashPolicies(); assertThat(policies).hasSize(2); @@ -811,20 +811,20 @@ public class ClientXdsClientDataTest { io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct).isNull(); } @Test public void parseRouteAction_clusterSpecifier_routeLookupDisabled() { - ClientXdsClient.enableRouteLookup = false; + XdsResourceType.enableRouteLookup = false; io.envoyproxy.envoy.config.route.v3.RouteAction proto = io.envoyproxy.envoy.config.route.v3.RouteAction.newBuilder() .setClusterSpecifierPlugin(CLUSTER_SPECIFIER_PLUGIN.name()) .build(); StructOrError struct = - ClientXdsClient.parseRouteAction(proto, filterRegistry, false, + XdsRouteConfigureResource.parseRouteAction(proto, filterRegistry, false, ImmutableMap.of(), ImmutableSet.of()); assertThat(struct).isNull(); } @@ -837,7 +837,7 @@ public class ClientXdsClientDataTest { .setWeight(UInt32Value.newBuilder().setValue(30)) .build(); ClusterWeight clusterWeight = - ClientXdsClient.parseClusterWeight(proto, filterRegistry, false).getStruct(); + XdsRouteConfigureResource.parseClusterWeight(proto, filterRegistry, false).getStruct(); assertThat(clusterWeight.name()).isEqualTo("cluster-foo"); assertThat(clusterWeight.weight()).isEqualTo(30); } @@ -859,7 +859,7 @@ public class ClientXdsClientDataTest { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.HEALTHY) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( @@ -883,7 +883,7 @@ public class ClientXdsClientDataTest { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( @@ -907,7 +907,7 @@ public class ClientXdsClientDataTest { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNHEALTHY) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isNull(); assertThat(struct.getStruct()).isEqualTo( LocalityLbEndpoints.create( @@ -931,7 +931,7 @@ public class ClientXdsClientDataTest { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - assertThat(ClientXdsClient.parseLocalityLbEndpoints(proto)).isNull(); + assertThat(XdsEndpointResource.parseLocalityLbEndpoints(proto)).isNull(); } @Test @@ -951,7 +951,7 @@ public class ClientXdsClientDataTest { .setHealthStatus(io.envoyproxy.envoy.config.core.v3.HealthStatus.UNKNOWN) .setLoadBalancingWeight(UInt32Value.newBuilder().setValue(20))) // endpoint weight .build(); - StructOrError struct = ClientXdsClient.parseLocalityLbEndpoints(proto); + StructOrError struct = XdsEndpointResource.parseLocalityLbEndpoints(proto); assertThat(struct.getErrorDetail()).isEqualTo("negative priority"); } @@ -961,7 +961,7 @@ public class ClientXdsClientDataTest { .setIsOptional(true) .setTypedConfig(Any.pack(StringValue.of("unsupported"))) .build(); - assertThat(ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true)).isNull(); + assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true)).isNull(); } private static class SimpleFilterConfig implements FilterConfig { @@ -1022,7 +1022,7 @@ public class ClientXdsClientDataTest { .setTypeUrl("test-url") .setValue(rawStruct) .build())).build(); - FilterConfig config = ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, + FilterConfig config = XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true).getStruct(); assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); @@ -1033,7 +1033,7 @@ public class ClientXdsClientDataTest { .setTypeUrl("test-url") .setValue(rawStruct) .build())).build(); - config = ClientXdsClient.parseHttpFilter(httpFilterNewTypeStruct, filterRegistry, + config = XdsListenerResource.parseHttpFilter(httpFilterNewTypeStruct, filterRegistry, true).getStruct(); assertThat(((SimpleFilterConfig)config).getConfig()).isEqualTo(rawStruct); } @@ -1059,8 +1059,8 @@ public class ClientXdsClientDataTest { .setValue(rawStruct1) .build()) ); - Map map = ClientXdsClient.parseOverrideFilterConfigs(rawFilterMap, - filterRegistry).getStruct(); + Map map = XdsRouteConfigureResource.parseOverrideFilterConfigs( + rawFilterMap, filterRegistry).getStruct(); assertThat(((SimpleFilterConfig)map.get("struct-0")).getConfig()).isEqualTo(rawStruct0); assertThat(((SimpleFilterConfig)map.get("struct-1")).getConfig()).isEqualTo(rawStruct1); } @@ -1072,7 +1072,7 @@ public class ClientXdsClientDataTest { .setName("unsupported.filter") .setTypedConfig(Any.pack(StringValue.of("string value"))) .build(); - assertThat(ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true) + assertThat(XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported " @@ -1088,7 +1088,7 @@ public class ClientXdsClientDataTest { .setName("envoy.router") .setTypedConfig(Any.pack(Router.getDefaultInstance())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, true /* isForClient */).getStruct(); assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL); } @@ -1102,7 +1102,7 @@ public class ClientXdsClientDataTest { .setName("envoy.router") .setTypedConfig(Any.pack(Router.getDefaultInstance())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, false /* isForClient */).getStruct(); assertThat(config.typeUrl()).isEqualTo(RouterFilter.TYPE_URL); } @@ -1129,7 +1129,7 @@ public class ClientXdsClientDataTest { .setDenominator(DenominatorType.HUNDRED))) .build())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, true /* isForClient */).getStruct(); assertThat(config).isInstanceOf(FaultConfig.class); } @@ -1157,7 +1157,7 @@ public class ClientXdsClientDataTest { .build())) .build(); StructOrError config = - ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */); + XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, false /* isForClient */); assertThat(config.getErrorDetail()).isEqualTo( "HttpFilter [envoy.fault](" + FaultFilter.TYPE_URL + ") is required but " + "unsupported for server"); @@ -1185,7 +1185,7 @@ public class ClientXdsClientDataTest { .build()) .build())) .build(); - FilterConfig config = ClientXdsClient.parseHttpFilter( + FilterConfig config = XdsListenerResource.parseHttpFilter( httpFilter, filterRegistry, false /* isForClient */).getStruct(); assertThat(config).isInstanceOf(RbacConfig.class); } @@ -1213,7 +1213,7 @@ public class ClientXdsClientDataTest { .build())) .build(); StructOrError config = - ClientXdsClient.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */); + XdsListenerResource.parseHttpFilter(httpFilter, filterRegistry, true /* isForClient */); assertThat(config.getErrorDetail()).isEqualTo( "HttpFilter [envoy.auth](" + RbacFilter.TYPE_URL + ") is required but " + "unsupported for client"); @@ -1238,7 +1238,8 @@ public class ClientXdsClientDataTest { .build(); Map configOverrides = ImmutableMap.of("envoy.auth", Any.pack(rbacPerRoute)); Map parsedConfigs = - ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry).getStruct(); + XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + .getStruct(); assertThat(parsedConfigs).hasSize(1); assertThat(parsedConfigs).containsKey("envoy.auth"); assertThat(parsedConfigs.get("envoy.auth")).isInstanceOf(RbacConfig.class); @@ -1258,7 +1259,8 @@ public class ClientXdsClientDataTest { .setIsOptional(true).setConfig(Any.pack(StringValue.of("string value"))) .build())); Map parsedConfigs = - ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry).getStruct(); + XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) + .getStruct(); assertThat(parsedConfigs).hasSize(1); assertThat(parsedConfigs).containsKey("envoy.fault"); } @@ -1276,7 +1278,7 @@ public class ClientXdsClientDataTest { Any.pack(io.envoyproxy.envoy.config.route.v3.FilterConfig.newBuilder() .setIsOptional(false).setConfig(Any.pack(StringValue.of("string value"))) .build())); - assertThat(ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry) + assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported"); @@ -1286,7 +1288,7 @@ public class ClientXdsClientDataTest { Any.pack(httpFault), "unsupported.filter", Any.pack(StringValue.of("string value"))); - assertThat(ClientXdsClient.parseOverrideFilterConfigs(configOverrides, filterRegistry) + assertThat(XdsRouteConfigureResource.parseOverrideFilterConfigs(configOverrides, filterRegistry) .getErrorDetail()).isEqualTo( "HttpFilter [unsupported.filter]" + "(type.googleapis.com/google.protobuf.StringValue) is required but unsupported"); @@ -1299,7 +1301,7 @@ public class ClientXdsClientDataTest { HttpConnectionManager hcm = HttpConnectionManager.newBuilder().setXffNumTrustedHops(2).build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager with xff_num_trusted_hops unsupported"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* does not matter */, true /* does not matter */); } @@ -1313,7 +1315,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager with original_ip_detection_extensions unsupported"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* does not matter */, false); } @@ -1328,7 +1330,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager neither has inlined route_config nor RDS"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* does not matter */, true /* does not matter */); } @@ -1347,7 +1349,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("HttpConnectionManager contains duplicate HttpFilter: envoy.filter.foo"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1365,7 +1367,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1383,7 +1385,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("A terminal HttpFilter must be the last filter: terminal"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true); } @@ -1399,7 +1401,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("The last HttpFilter must be a terminal filter: envoy.filter.bar"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @@ -1411,14 +1413,14 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Missing HttpFilter in HttpConnectionManager."); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, true /* parseHttpFilter */, true /* does not matter */); } @Test public void parseHttpConnectionManager_clusterSpecifierPlugin() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder() .addGrpcKeybuilders( GrpcKeyBuilder.newBuilder() @@ -1457,7 +1459,7 @@ public class ClientXdsClientDataTest { .addRoutes(route))) .build(); - io.grpc.xds.HttpConnectionManager parsedHcm = ClientXdsClient.parseHttpConnectionManager( + io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1471,7 +1473,7 @@ public class ClientXdsClientDataTest { @Test public void parseHttpConnectionManager_duplicatePluginName() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; RouteLookupConfig routeLookupConfig1 = RouteLookupConfig.newBuilder() .addGrpcKeybuilders( GrpcKeyBuilder.newBuilder() @@ -1534,14 +1536,14 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Multiple ClusterSpecifierPlugins with the same name: rls-plugin-1"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @Test public void parseHttpConnectionManager_pluginNameNotFound() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder() .addGrpcKeybuilders( GrpcKeyBuilder.newBuilder() @@ -1583,7 +1585,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage("ClusterSpecifierPlugin for [invalid-plugin-name] not found"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm, new HashSet(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @@ -1591,7 +1593,7 @@ public class ClientXdsClientDataTest { @Test public void parseHttpConnectionManager_optionalPlugin() throws ResourceInvalidException { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; // RLS Plugin, and a route to it. RouteLookupConfig routeLookupConfig = RouteLookupConfig.newBuilder() @@ -1653,7 +1655,7 @@ public class ClientXdsClientDataTest { .addRoutes(rlsRoute) .addRoutes(optionalRoute)) .build(); - io.grpc.xds.HttpConnectionManager parsedHcm = ClientXdsClient.parseHttpConnectionManager( + io.grpc.xds.HttpConnectionManager parsedHcm = XdsListenerResource.parseHttpConnectionManager( HttpConnectionManager.newBuilder().setRouteConfig(routeConfig).build(), new HashSet<>(), filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1669,7 +1671,7 @@ public class ClientXdsClientDataTest { @Test public void parseHttpConnectionManager_validateRdsConfigSource() throws Exception { - ClientXdsClient.enableRouteLookup = true; + XdsResourceType.enableRouteLookup = true; Set rdsResources = new HashSet<>(); HttpConnectionManager hcm1 = @@ -1679,7 +1681,7 @@ public class ClientXdsClientDataTest { .setConfigSource( ConfigSource.newBuilder().setAds(AggregatedConfigSource.getDefaultInstance()))) .build(); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm1, rdsResources, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1690,7 +1692,7 @@ public class ClientXdsClientDataTest { .setConfigSource( ConfigSource.newBuilder().setSelf(SelfConfigSource.getDefaultInstance()))) .build(); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm2, rdsResources, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); @@ -1705,7 +1707,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "HttpConnectionManager contains invalid RDS: must specify ADS or self ConfigSource"); - ClientXdsClient.parseHttpConnectionManager( + XdsListenerResource.parseHttpConnectionManager( hcm3, rdsResources, filterRegistry, false /* parseHttpFilter */, true /* does not matter */); } @@ -1744,7 +1746,8 @@ public class ClientXdsClientDataTest { .setTypedConfig(Any.pack(typedStruct))) .build(); - PluginConfig pluginConfig = ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + PluginConfig pluginConfig = XdsRouteConfigureResource + .parseClusterSpecifierPlugin(pluginProto, registry); assertThat(pluginConfig).isInstanceOf(TestPluginConfig.class); } @@ -1782,7 +1785,8 @@ public class ClientXdsClientDataTest { .setTypedConfig(Any.pack(typedStruct))) .build(); - PluginConfig pluginConfig = ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + PluginConfig pluginConfig = XdsRouteConfigureResource + .parseClusterSpecifierPlugin(pluginProto, registry); assertThat(pluginConfig).isInstanceOf(TestPluginConfig.class); } @@ -1799,7 +1803,7 @@ public class ClientXdsClientDataTest { thrown.expectMessage( "Unsupported ClusterSpecifierPlugin type: type.googleapis.com/google.protobuf.StringValue"); - ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + XdsRouteConfigureResource.parseClusterSpecifierPlugin(pluginProto, registry); } @Test @@ -1813,7 +1817,8 @@ public class ClientXdsClientDataTest { .setIsOptional(true) .build(); - PluginConfig pluginConfig = ClientXdsClient.parseClusterSpecifierPlugin(pluginProto, registry); + PluginConfig pluginConfig = XdsRouteConfigureResource + .parseClusterSpecifierPlugin(pluginProto, registry); assertThat(pluginConfig).isNull(); } @@ -1832,7 +1837,7 @@ public class ClientXdsClientDataTest { .build(); try { - ClientXdsClient.parseClusterSpecifierPlugin( + XdsRouteConfigureResource.parseClusterSpecifierPlugin( io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder() .setExtension(brokenPlugin) .build(), @@ -1860,7 +1865,7 @@ public class ClientXdsClientDataTest { // Despite being optional, still should fail. try { - ClientXdsClient.parseClusterSpecifierPlugin( + XdsRouteConfigureResource.parseClusterSpecifierPlugin( io.envoyproxy.envoy.config.route.v3.ClusterSpecifierPlugin.newBuilder() .setIsOptional(true) .setExtension(brokenPlugin) @@ -1887,7 +1892,7 @@ public class ClientXdsClientDataTest { .setLbPolicy(LbPolicy.RING_HASH) .build(); - CdsUpdate update = ClientXdsClient.processCluster( + CdsUpdate update = XdsClusterResource.processCluster( cluster, new HashSet(), null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig()); @@ -1896,7 +1901,7 @@ public class ClientXdsClientDataTest { @Test public void parseCluster_leastRequestLbPolicy_defaultLbConfig() throws ResourceInvalidException { - ClientXdsClient.enableLeastRequest = true; + XdsResourceType.enableLeastRequest = true; Cluster cluster = Cluster.newBuilder() .setName("cluster-foo.googleapis.com") .setType(DiscoveryType.EDS) @@ -1909,7 +1914,7 @@ public class ClientXdsClientDataTest { .setLbPolicy(LbPolicy.LEAST_REQUEST) .build(); - CdsUpdate update = ClientXdsClient.processCluster( + CdsUpdate update = XdsClusterResource.processCluster( cluster, new HashSet(), null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); LbConfig lbConfig = ServiceConfigUtil.unwrapLoadBalancingConfig(update.lbPolicyConfig()); @@ -1938,7 +1943,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "Cluster cluster-foo.googleapis.com: transport-socket-matches not supported."); - ClientXdsClient.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster, new HashSet(), null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); } @@ -1956,7 +1961,7 @@ public class ClientXdsClientDataTest { .setServiceName("service-foo.googleapis.com")) .setLbPolicy(LbPolicy.ROUND_ROBIN) .build(); - ClientXdsClient.processCluster(cluster1, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster1, retainedEdsResources, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); Cluster cluster2 = Cluster.newBuilder() @@ -1970,7 +1975,7 @@ public class ClientXdsClientDataTest { .setServiceName("service-foo.googleapis.com")) .setLbPolicy(LbPolicy.ROUND_ROBIN) .build(); - ClientXdsClient.processCluster(cluster2, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster2, retainedEdsResources, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); Cluster cluster3 = Cluster.newBuilder() @@ -1989,7 +1994,7 @@ public class ClientXdsClientDataTest { thrown.expectMessage( "Cluster cluster-foo.googleapis.com: field eds_cluster_config must be set to indicate to" + " use EDS over ADS or self ConfigSource"); - ClientXdsClient.processCluster(cluster3, retainedEdsResources, null, LRS_SERVER_INFO, + XdsClusterResource.processCluster(cluster3, retainedEdsResources, null, LRS_SERVER_INFO, LoadBalancerRegistry.getDefaultRegistry()); } @@ -2002,7 +2007,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 with invalid traffic direction: OUTBOUND"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2012,7 +2017,7 @@ public class ClientXdsClientDataTest { Listener.newBuilder() .setName("listener1") .build(); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2026,7 +2031,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have listener_filters"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2040,7 +2045,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("Listener listener1 cannot have use_original_dst set to true"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2089,7 +2094,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2138,7 +2143,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("FilterChainMatch must be unique. Found duplicate:"); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2187,7 +2192,7 @@ public class ClientXdsClientDataTest { .setTrafficDirection(TrafficDirection.INBOUND) .addAllFilterChains(Arrays.asList(filterChain1, filterChain2)) .build(); - ClientXdsClient.parseServerSideListener( + XdsListenerResource.parseServerSideListener( listener, new HashSet(), null, filterRegistry, null, true /* does not matter */); } @@ -2202,7 +2207,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2221,7 +2226,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "FilterChain filter-chain-foo should contain exact one HttpConnectionManager filter"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2240,7 +2245,7 @@ public class ClientXdsClientDataTest { thrown.expectMessage( "FilterChain filter-chain-foo contains filter envoy.http_connection_manager " + "without typed_config"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2263,7 +2268,7 @@ public class ClientXdsClientDataTest { thrown.expectMessage( "FilterChain filter-chain-foo contains filter unsupported with unsupported " + "typed_config type unsupported-type-url"); - ClientXdsClient.parseFilterChain( + XdsListenerResource.parseFilterChain( filterChain, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); } @@ -2291,10 +2296,10 @@ public class ClientXdsClientDataTest { .build())) .build(); - EnvoyServerProtoData.FilterChain parsedFilterChain1 = ClientXdsClient.parseFilterChain( + EnvoyServerProtoData.FilterChain parsedFilterChain1 = XdsListenerResource.parseFilterChain( filterChain1, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); - EnvoyServerProtoData.FilterChain parsedFilterChain2 = ClientXdsClient.parseFilterChain( + EnvoyServerProtoData.FilterChain parsedFilterChain2 = XdsListenerResource.parseFilterChain( filterChain2, new HashSet(), null, filterRegistry, null, null, true /* does not matter */); assertThat(parsedFilterChain1.name()).isEqualTo(parsedFilterChain2.name()); @@ -2307,7 +2312,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context with tls_params is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2317,7 +2322,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context with custom_handshaker is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2327,7 +2332,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2339,7 +2344,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "common-tls-context with validation_context_sds_secret_config is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2353,7 +2358,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "common-tls-context with validation_context_certificate_provider is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2368,7 +2373,7 @@ public class ClientXdsClientDataTest { thrown.expectMessage( "common-tls-context with validation_context_certificate_provider_instance is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2379,7 +2384,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "tls_certificate_provider_instance is required in downstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, true); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, true); } @Test @@ -2390,7 +2395,7 @@ public class ClientXdsClientDataTest { .setTlsCertificateProviderInstance( CertificateProviderPluginInstance.newBuilder().setInstanceName("name1").build()) .build(); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true); } @@ -2402,7 +2407,7 @@ public class ClientXdsClientDataTest { .setTlsCertificateCertificateProviderInstance( CertificateProviderInstance.newBuilder().setInstanceName("name1").build()) .build(); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true); } @@ -2417,7 +2422,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "CertificateProvider instance name 'bad-name' not defined in the bootstrap file."); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), true); } @@ -2432,7 +2437,7 @@ public class ClientXdsClientDataTest { CertificateProviderInstance.newBuilder().setInstanceName("name1").build()) .build()) .build(); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false); } @@ -2450,7 +2455,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "ca_certificate_provider_instance name 'bad-name' not defined in the bootstrap file."); - ClientXdsClient + XdsClusterResource .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false); } @@ -2462,7 +2467,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("tls_certificate_provider_instance is unset"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2474,7 +2479,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "tls_certificate_provider_instance is unset"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2488,7 +2493,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "tls_certificate_provider_instance is unset"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2498,7 +2503,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2511,7 +2516,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "ca_certificate_provider_instance is required in upstream-tls-context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, null, false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); } @Test @@ -2531,7 +2536,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("match_subject_alt_names only allowed in upstream_tls_context"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), true); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), true); } @Test @@ -2551,7 +2556,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage("verify_certificate_spki in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2571,7 +2576,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage("verify_certificate_hash in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2592,7 +2597,7 @@ public class ClientXdsClientDataTest { thrown.expectMessage( "require_signed_certificate_timestamp in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2611,7 +2616,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("crl in default_validation_context is not supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2631,7 +2636,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage("custom_validator_config in default_validation_context is not " + "supported"); - ClientXdsClient.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); + XdsClusterResource.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(""), false); } @Test @@ -2639,7 +2644,7 @@ public class ClientXdsClientDataTest { DownstreamTlsContext downstreamTlsContext = DownstreamTlsContext.getDefaultInstance(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context is required in downstream-tls-context"); - ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, null); + XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, null); } @Test @@ -2659,7 +2664,7 @@ public class ClientXdsClientDataTest { .build(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("downstream-tls-context with require-sni is not supported"); - ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); + XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); } @Test @@ -2680,7 +2685,7 @@ public class ClientXdsClientDataTest { thrown.expect(ResourceInvalidException.class); thrown.expectMessage( "downstream-tls-context with ocsp_staple_policy value STRICT_STAPLING is not supported"); - ClientXdsClient.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); + XdsListenerResource.validateDownstreamTlsContext(downstreamTlsContext, ImmutableSet.of("")); } @Test @@ -2688,7 +2693,7 @@ public class ClientXdsClientDataTest { UpstreamTlsContext upstreamTlsContext = UpstreamTlsContext.getDefaultInstance(); thrown.expect(ResourceInvalidException.class); thrown.expectMessage("common-tls-context is required in upstream-tls-context"); - ClientXdsClient.validateUpstreamTlsContext(upstreamTlsContext, null); + XdsClusterResource.validateUpstreamTlsContext(upstreamTlsContext, null); } @Test diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java index cd8766024c..788110e1c2 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java @@ -81,19 +81,16 @@ import io.grpc.xds.EnvoyServerProtoData.FilterChain; import io.grpc.xds.EnvoyServerProtoData.SuccessRateEjection; import io.grpc.xds.FaultConfig.FractionalPercent.DenominatorType; import io.grpc.xds.LoadStatsManager2.ClusterDropStats; -import io.grpc.xds.XdsClient.CdsResourceWatcher; -import io.grpc.xds.XdsClient.CdsUpdate; -import io.grpc.xds.XdsClient.CdsUpdate.ClusterType; -import io.grpc.xds.XdsClient.EdsResourceWatcher; -import io.grpc.xds.XdsClient.EdsUpdate; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.LdsUpdate; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; import io.grpc.xds.XdsClient.ResourceMetadata; import io.grpc.xds.XdsClient.ResourceMetadata.ResourceMetadataStatus; import io.grpc.xds.XdsClient.ResourceMetadata.UpdateFailureState; +import io.grpc.xds.XdsClient.ResourceUpdate; import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.io.IOException; import java.util.ArrayDeque; @@ -254,13 +251,13 @@ public abstract class ClientXdsClientTestBase { @Mock private BackoffPolicy backoffPolicy2; @Mock - private LdsResourceWatcher ldsResourceWatcher; + private ResourceWatcher ldsResourceWatcher; @Mock - private RdsResourceWatcher rdsResourceWatcher; + private ResourceWatcher rdsResourceWatcher; @Mock - private CdsResourceWatcher cdsResourceWatcher; + private ResourceWatcher cdsResourceWatcher; @Mock - private EdsResourceWatcher edsResourceWatcher; + private ResourceWatcher edsResourceWatcher; @Mock private TlsContextManager tlsContextManager; @@ -282,12 +279,12 @@ public abstract class ClientXdsClientTestBase { when(backoffPolicy2.nextBackoffNanos()).thenReturn(20L, 200L); // Start the server and the client. - originalEnableFaultInjection = ClientXdsClient.enableFaultInjection; - ClientXdsClient.enableFaultInjection = true; - originalEnableRbac = ClientXdsClient.enableRbac; + originalEnableFaultInjection = XdsResourceType.enableFaultInjection; + XdsResourceType.enableFaultInjection = true; + originalEnableRbac = XdsResourceType.enableRbac; assertThat(originalEnableRbac).isTrue(); - originalEnableLeastRequest = ClientXdsClient.enableLeastRequest; - ClientXdsClient.enableLeastRequest = true; + originalEnableLeastRequest = XdsResourceType.enableLeastRequest; + XdsResourceType.enableLeastRequest = true; originalEnableFederation = BootstrapperImpl.enableFederation; final String serverName = InProcessServerBuilder.generateName(); cleanupRule.register( @@ -361,9 +358,9 @@ public abstract class ClientXdsClientTestBase { @After public void tearDown() { - ClientXdsClient.enableFaultInjection = originalEnableFaultInjection; - ClientXdsClient.enableRbac = originalEnableRbac; - ClientXdsClient.enableLeastRequest = originalEnableLeastRequest; + XdsResourceType.enableFaultInjection = originalEnableFaultInjection; + XdsResourceType.enableRbac = originalEnableRbac; + XdsResourceType.enableLeastRequest = originalEnableLeastRequest; BootstrapperImpl.enableFederation = originalEnableFederation; xdsClient.shutdown(); channel.shutdown(); // channel not owned by XdsClient @@ -579,7 +576,8 @@ public abstract class ClientXdsClientTestBase { @Test public void ldsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); Any listener = Any.pack(mf.buildListenerWithApiListener("bar.googleapis.com", mf.buildRouteConfiguration("route-bar.googleapis.com", mf.buildOpaqueVirtualHosts(1)))); @@ -604,20 +602,23 @@ public abstract class ClientXdsClientTestBase { String ldsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.listener.v3.Listener/listener1" : "xdstp://unknown.example.com/envoy.api.v2.Listener/listener1"; - xdsClient.watchLdsResource(ldsResourceName, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),ldsResourceName, + ldsResourceWatcher); verify(ldsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + ldsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelLdsResourceWatch(ldsResourceName, ldsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(),ldsResourceName, + ldsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @Test public void ldsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); call.sendResponse(LDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -633,7 +634,8 @@ public abstract class ClientXdsClientTestBase { @Test public void ldsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -660,9 +662,9 @@ public abstract class ClientXdsClientTestBase { @Test public void ldsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchLdsResource("A", ldsResourceWatcher); - xdsClient.watchLdsResource("B", ldsResourceWatcher); - xdsClient.watchLdsResource("C", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"A", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"B", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"C", ldsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(LDS, "A"); @@ -725,12 +727,12 @@ public abstract class ClientXdsClientTestBase { @Test public void ldsResponseErrorHandling_subscribedResourceInvalid_withRdsSubscription() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchLdsResource("A", ldsResourceWatcher); - xdsClient.watchRdsResource("A.1", rdsResourceWatcher); - xdsClient.watchLdsResource("B", ldsResourceWatcher); - xdsClient.watchRdsResource("B.1", rdsResourceWatcher); - xdsClient.watchLdsResource("C", ldsResourceWatcher); - xdsClient.watchRdsResource("C.1", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"A", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"A.1", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"B", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"B.1", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),"C", ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"C.1", rdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(LDS, "A"); @@ -803,7 +805,8 @@ public abstract class ClientXdsClientTestBase { @Test public void ldsResourceFound_containsVirtualHosts() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // Client sends an ACK LDS request. call.sendResponse(LDS, testListenerVhosts, VERSION_1, "0000"); @@ -817,7 +820,8 @@ public abstract class ClientXdsClientTestBase { @Test public void wrappedLdsResource() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // Client sends an ACK LDS request. call.sendResponse(LDS, mf.buildWrappedResource(testListenerVhosts), VERSION_1, "0000"); @@ -831,7 +835,8 @@ public abstract class ClientXdsClientTestBase { @Test public void ldsResourceFound_containsRdsName() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); call.sendResponse(LDS, testListenerRds, VERSION_1, "0000"); // Client sends an ACK LDS request. @@ -844,15 +849,17 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedLdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // Client sends an ACK LDS request. call.sendResponse(LDS, testListenerRds, VERSION_1, "0000"); call.verifyRequest(LDS, LDS_RESOURCE, VERSION_1, "0000", NODE); - LdsResourceWatcher watcher = mock(LdsResourceWatcher.class); - xdsClient.watchLdsResource(LDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, watcher); verify(watcher).onChanged(ldsUpdateCaptor.capture()); verifyGoldenListenerRds(ldsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -861,13 +868,15 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedLdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(ldsResourceWatcher).onResourceDoesNotExist(LDS_RESOURCE); // Add another watcher. - LdsResourceWatcher watcher = mock(LdsResourceWatcher.class); - xdsClient.watchLdsResource(LDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(LDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(LDS, LDS_RESOURCE); @@ -876,7 +885,8 @@ public abstract class ClientXdsClientTestBase { @Test public void ldsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Initial LDS response. @@ -903,7 +913,8 @@ public abstract class ClientXdsClientTestBase { String ldsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1" : "xdstp://authority.xds.com/envoy.api.v2.Listener/listener1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); verifyResourceMetadataRequested(LDS, ldsResourceName); @@ -923,7 +934,8 @@ public abstract class ClientXdsClientTestBase { String ldsResourceName = useProtocolV3() ? "xdstp:///envoy.config.listener.v3.Listener/listener1" : "xdstp:///envoy.api.v2.Listener/listener1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForEmptyAuthority).isNotNull(); verifyResourceMetadataRequested(LDS, ldsResourceName); @@ -943,7 +955,8 @@ public abstract class ClientXdsClientTestBase { String ldsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1/a?bar=2&foo=1" : "xdstp://authority.xds.com/envoy.api.v2.Listener/listener1/a?bar=2&foo=1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String ldsResourceNameWithUnorderedContextParams = useProtocolV3() @@ -963,7 +976,8 @@ public abstract class ClientXdsClientTestBase { String ldsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1" : "xdstp://authority.xds.com/envoy.api.v2.Listener/listener1"; - DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), ldsResourceName, + ldsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String ldsResourceNameWithWrongType = @@ -984,7 +998,8 @@ public abstract class ClientXdsClientTestBase { String rdsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.route.v3.RouteConfiguration/route1" : "xdstp://authority.xds.com/envoy.api.v2.RouteConfiguration/route1"; - DiscoveryRpcCall call = startResourceWatcher(RDS, rdsResourceName, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + rdsResourceName, rdsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String rdsResourceNameWithWrongType = @@ -1004,14 +1019,16 @@ public abstract class ClientXdsClientTestBase { String rdsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.route.v3.RouteConfiguration/route1" : "xdstp://unknown.example.com/envoy.api.v2.RouteConfiguration/route1"; - xdsClient.watchRdsResource(rdsResourceName, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),rdsResourceName, + rdsResourceWatcher); verify(rdsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + rdsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelRdsResourceWatch(rdsResourceName, rdsResourceWatcher); + xdsClient.cancelXdsResourceWatch( + XdsRouteConfigureResource.getInstance(),rdsResourceName, rdsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @@ -1021,7 +1038,8 @@ public abstract class ClientXdsClientTestBase { String cdsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.cluster.v3.Cluster/cluster1" : "xdstp://authority.xds.com/envoy.api.v2.Cluster/cluster1"; - DiscoveryRpcCall call = startResourceWatcher(CDS, cdsResourceName, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), cdsResourceName, + cdsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String cdsResourceNameWithWrongType = @@ -1042,14 +1060,16 @@ public abstract class ClientXdsClientTestBase { String cdsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.cluster.v3.Cluster/cluster1" : "xdstp://unknown.example.com/envoy.api.v2.Cluster/cluster1"; - xdsClient.watchCdsResource(cdsResourceName, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),cdsResourceName, + cdsResourceWatcher); verify(cdsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + cdsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelCdsResourceWatch(cdsResourceName, cdsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsClusterResource.getInstance(),cdsResourceName, + cdsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @@ -1059,7 +1079,8 @@ public abstract class ClientXdsClientTestBase { String edsResourceName = useProtocolV3() ? "xdstp://authority.xds.com/envoy.config.endpoint.v3.ClusterLoadAssignment/cluster1" : "xdstp://authority.xds.com/envoy.api.v2.ClusterLoadAssignment/cluster1"; - DiscoveryRpcCall call = startResourceWatcher(EDS, edsResourceName, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), edsResourceName, + edsResourceWatcher); assertThat(channelForCustomAuthority).isNotNull(); String edsResourceNameWithWrongType = @@ -1083,21 +1104,24 @@ public abstract class ClientXdsClientTestBase { String edsResourceName = useProtocolV3() ? "xdstp://unknown.example.com/envoy.config.endpoint.v3.ClusterLoadAssignment/cluster1" : "xdstp://unknown.example.com/envoy.api.v2.ClusterLoadAssignment/cluster1"; - xdsClient.watchEdsResource(edsResourceName, edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), + edsResourceName, edsResourceWatcher); verify(edsResourceWatcher).onError(errorCaptor.capture()); Status error = errorCaptor.getValue(); assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT); assertThat(error.getDescription()).isEqualTo( "Wrong configuration: xds server does not exist for resource " + edsResourceName); assertThat(resourceDiscoveryCalls.poll()).isNull(); - xdsClient.cancelEdsResourceWatch(edsResourceName, edsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsEndpointResource.getInstance(), + edsResourceName, edsResourceWatcher); assertThat(resourceDiscoveryCalls.poll()).isNull(); } @Test public void ldsResourceUpdate_withFaultInjection() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); Any listener = Any.pack( mf.buildListenerWithApiListener( LDS_RESOURCE, @@ -1163,7 +1187,8 @@ public abstract class ClientXdsClientTestBase { public void ldsResourceDeleted() { Assume.assumeFalse(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Initial LDS response. @@ -1190,7 +1215,8 @@ public abstract class ClientXdsClientTestBase { public void ldsResourceDeleted_ignoreResourceDeletion() { Assume.assumeTrue(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); // Initial LDS response. @@ -1223,13 +1249,14 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void multipleLdsWatchers() { String ldsResourceTwo = "bar.googleapis.com"; - LdsResourceWatcher watcher1 = mock(LdsResourceWatcher.class); - LdsResourceWatcher watcher2 = mock(LdsResourceWatcher.class); - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchLdsResource(ldsResourceTwo, watcher1); - xdsClient.watchLdsResource(ldsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),ldsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),ldsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(LDS, ImmutableList.of(LDS_RESOURCE, ldsResourceTwo), "", "", NODE); // Both LDS resources were requested. @@ -1247,7 +1274,7 @@ public abstract class ClientXdsClientTestBase { Any listenerTwo = Any.pack(mf.buildListenerWithApiListenerForRds(ldsResourceTwo, RDS_RESOURCE)); call.sendResponse(LDS, ImmutableList.of(testListenerVhosts, listenerTwo), VERSION_1, "0000"); - // ldsResourceWatcher called with listenerVhosts. + // ResourceWatcher called with listenerVhosts. verify(ldsResourceWatcher).onChanged(ldsUpdateCaptor.capture()); verifyGoldenListenerVhosts(ldsUpdateCaptor.getValue()); // watcher1 called with listenerTwo. @@ -1266,7 +1293,8 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); Any routeConfig = Any.pack(mf.buildRouteConfiguration("route-bar.googleapis.com", mf.buildOpaqueVirtualHosts(2))); call.sendResponse(ResourceType.RDS, routeConfig, VERSION_1, "0000"); @@ -1286,7 +1314,8 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); call.sendResponse(RDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -1302,7 +1331,8 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -1329,9 +1359,9 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchRdsResource("A", rdsResourceWatcher); - xdsClient.watchRdsResource("B", rdsResourceWatcher); - xdsClient.watchRdsResource("C", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"A", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"B", rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),"C", rdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(RDS, "A"); @@ -1386,7 +1416,8 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); call.sendResponse(RDS, testRouteConfig, VERSION_1, "0000"); // Client sends an ACK RDS request. @@ -1400,7 +1431,8 @@ public abstract class ClientXdsClientTestBase { @Test public void wrappedRdsResource() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); call.sendResponse(RDS, mf.buildWrappedResource(testRouteConfig), VERSION_1, "0000"); // Client sends an ACK RDS request. @@ -1413,15 +1445,17 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedRdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); call.sendResponse(RDS, testRouteConfig, VERSION_1, "0000"); // Client sends an ACK RDS request. call.verifyRequest(RDS, RDS_RESOURCE, VERSION_1, "0000", NODE); - RdsResourceWatcher watcher = mock(RdsResourceWatcher.class); - xdsClient.watchRdsResource(RDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, watcher); verify(watcher).onChanged(rdsUpdateCaptor.capture()); verifyGoldenRouteConfig(rdsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -1430,13 +1464,15 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedRdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(rdsResourceWatcher).onResourceDoesNotExist(RDS_RESOURCE); // Add another watcher. - RdsResourceWatcher watcher = mock(RdsResourceWatcher.class); - xdsClient.watchRdsResource(RDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(RDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(RDS, RDS_RESOURCE); @@ -1445,7 +1481,8 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(RDS, RDS_RESOURCE, rdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); // Initial RDS response. @@ -1471,8 +1508,10 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResourceDeletedByLdsApiListener() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, + ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, + rdsResourceWatcher); verifyResourceMetadataRequested(LDS, LDS_RESOURCE); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); verifySubscribedResourcesMetadataSizes(1, 0, 1, 0); @@ -1509,8 +1548,10 @@ public abstract class ClientXdsClientTestBase { @Test public void rdsResourcesDeletedByLdsTcpListener() { Assume.assumeTrue(useProtocolV3()); - xdsClient.watchLdsResource(LISTENER_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), RDS_RESOURCE, + rdsResourceWatcher); verifyResourceMetadataRequested(LDS, LISTENER_RESOURCE); verifyResourceMetadataRequested(RDS, RDS_RESOURCE); verifySubscribedResourcesMetadataSizes(1, 0, 1, 0); @@ -1573,13 +1614,15 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void multipleRdsWatchers() { String rdsResourceTwo = "route-bar.googleapis.com"; - RdsResourceWatcher watcher1 = mock(RdsResourceWatcher.class); - RdsResourceWatcher watcher2 = mock(RdsResourceWatcher.class); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchRdsResource(rdsResourceTwo, watcher1); - xdsClient.watchRdsResource(rdsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, + rdsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),rdsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),rdsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(RDS, Arrays.asList(RDS_RESOURCE, rdsResourceTwo), "", "", NODE); // Both RDS resources were requested. @@ -1618,7 +1661,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); List clusters = ImmutableList.of( Any.pack(mf.buildEdsCluster("cluster-bar.googleapis.com", null, "round_robin", null, @@ -1629,7 +1673,7 @@ public abstract class ClientXdsClientTestBase { // Client sent an ACK CDS request. call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); - verifyNoInteractions(cdsResourceWatcher); + verifyNoInteractions(ldsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); // Server failed to return subscribed resource within expected time window. @@ -1642,7 +1686,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); call.sendResponse(CDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -1653,12 +1698,13 @@ public abstract class ClientXdsClientTestBase { call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of( "CDS response Resource index 0 - can't decode Cluster: ", "CDS response Resource index 1 - can't decode Cluster: ")); - verifyNoInteractions(cdsResourceWatcher); + verifyNoInteractions(ldsResourceWatcher); } @Test public void cdsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -1686,9 +1732,9 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchCdsResource("A", cdsResourceWatcher); - xdsClient.watchCdsResource("B", cdsResourceWatcher); - xdsClient.watchCdsResource("C", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"A", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"B", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"C", cdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(CDS, "A"); @@ -1762,12 +1808,12 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResponseErrorHandling_subscribedResourceInvalid_withEdsSubscription() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchCdsResource("A", cdsResourceWatcher); - xdsClient.watchEdsResource("A.1", edsResourceWatcher); - xdsClient.watchCdsResource("B", cdsResourceWatcher); - xdsClient.watchEdsResource("B.1", edsResourceWatcher); - xdsClient.watchCdsResource("C", cdsResourceWatcher); - xdsClient.watchEdsResource("C.1", edsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"A", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"A.1", edsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"B", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"B.1", edsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),"C", cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"C.1", edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(CDS, "A"); @@ -1849,7 +1895,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), + CDS_RESOURCE, cdsResourceWatcher); call.sendResponse(CDS, testClusterRoundRobin, VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1864,7 +1911,8 @@ public abstract class ClientXdsClientTestBase { @Test public void wrappedCdsResource() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); call.sendResponse(CDS, mf.buildWrappedResource(testClusterRoundRobin), VERSION_1, "0000"); // Client sent an ACK CDS request. @@ -1879,7 +1927,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResourceFound_leastRequestLbPolicy() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); Message leastRequestConfig = mf.buildLeastRequestLbConfig(3); Any clusterRingHash = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "least_request_experimental", null, @@ -1910,7 +1959,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResourceFound_ringHashLbPolicy() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); Message ringHashConfig = mf.buildRingHashLbConfig("xx_hash", 10L, 100L); Any clusterRingHash = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "ring_hash_experimental", ringHashConfig, null, @@ -1941,7 +1991,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResponseWithAggregateCluster() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); List candidates = Arrays.asList( "cluster1.googleapis.com", "cluster2.googleapis.com", "cluster3.googleapis.com"); Any clusterAggregate = @@ -1966,7 +2017,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResponseWithCircuitBreakers() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); Any clusterCircuitBreakers = Any.pack( mf.buildEdsCluster(CDS_RESOURCE, null, "round_robin", null, null, false, null, "envoy.transport_sockets.tls", mf.buildCircuitBreakers(50, 200), null)); @@ -1999,7 +2051,8 @@ public abstract class ClientXdsClientTestBase { @SuppressWarnings("deprecation") public void cdsResponseWithUpstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. Any clusterEds = @@ -2017,7 +2070,8 @@ public abstract class ClientXdsClientTestBase { // Client sent an ACK CDS request. call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); - verify(cdsResourceWatcher, times(1)).onChanged(cdsUpdateCaptor.capture()); + verify(cdsResourceWatcher, times(1)) + .onChanged(cdsUpdateCaptor.capture()); CdsUpdate cdsUpdate = cdsUpdateCaptor.getValue(); CommonTlsContext.CertificateProviderInstance certificateProviderInstance = cdsUpdate.upstreamTlsContext().getCommonTlsContext().getCombinedValidationContext() @@ -2035,7 +2089,8 @@ public abstract class ClientXdsClientTestBase { @SuppressWarnings("deprecation") public void cdsResponseWithNewUpstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. Any clusterEds = @@ -2070,7 +2125,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResponseErrorHandling_badUpstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. List clusters = ImmutableList.of(Any @@ -2096,9 +2152,10 @@ public abstract class ClientXdsClientTestBase { @SuppressWarnings("deprecation") public void cdsResponseWithOutlierDetection() { Assume.assumeTrue(useProtocolV3()); - ClientXdsClient.enableOutlierDetection = true; + XdsClusterResource.enableOutlierDetection = true; - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() .setInterval(Durations.fromNanos(100)) @@ -2167,9 +2224,10 @@ public abstract class ClientXdsClientTestBase { @SuppressWarnings("deprecation") public void cdsResponseWithOutlierDetection_supportDisabled() { Assume.assumeTrue(useProtocolV3()); - ClientXdsClient.enableOutlierDetection = false; + XdsClusterResource.enableOutlierDetection = false; - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() .setInterval(Durations.fromNanos(100)).build(); @@ -2206,9 +2264,10 @@ public abstract class ClientXdsClientTestBase { @SuppressWarnings("deprecation") public void cdsResponseWithInvalidOutlierDetectionNacks() { Assume.assumeTrue(useProtocolV3()); - ClientXdsClient.enableOutlierDetection = true; + XdsClusterResource.enableOutlierDetection = true; - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); OutlierDetection outlierDetectionXds = OutlierDetection.newBuilder() .setMaxEjectionPercent(UInt32Value.of(101)).build(); @@ -2238,21 +2297,21 @@ public abstract class ClientXdsClientTestBase { @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_invalidInterval() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_negativeInterval() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setInterval(Duration.newBuilder().setSeconds(-1)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_invalidBaseEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder() .setBaseEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) .build()); @@ -2260,14 +2319,14 @@ public abstract class ClientXdsClientTestBase { @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_negativeBaseEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setBaseEjectionTime(Duration.newBuilder().setSeconds(-1)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_invalidMaxEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder() .setMaxEjectionTime(Duration.newBuilder().setSeconds(Long.MAX_VALUE)) .build()); @@ -2275,35 +2334,35 @@ public abstract class ClientXdsClientTestBase { @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_negativeMaxEjectionTime() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setMaxEjectionTime(Duration.newBuilder().setSeconds(-1)) .build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_maxEjectionPercentTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setMaxEjectionPercent(UInt32Value.of(101)).build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_enforcingSuccessRateTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setEnforcingSuccessRate(UInt32Value.of(101)).build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_failurePercentageThresholdTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setFailurePercentageThreshold(UInt32Value.of(101)).build()); } @Test(expected = ResourceInvalidException.class) public void validateOutlierDetection_enforcingFailurePercentageTooHigh() throws ResourceInvalidException { - ClientXdsClient.validateOutlierDetection( + XdsClusterResource.validateOutlierDetection( OutlierDetection.newBuilder().setEnforcingFailurePercentage(UInt32Value.of(101)).build()); } @@ -2313,7 +2372,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResponseErrorHandling_badTransportSocketName() { Assume.assumeTrue(useProtocolV3()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); // Management server sends back CDS response with UpstreamTlsContext. List clusters = ImmutableList.of(Any @@ -2332,15 +2392,17 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedCdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); call.sendResponse(CDS, testClusterRoundRobin, VERSION_1, "0000"); // Client sends an ACK CDS request. call.verifyRequest(CDS, CDS_RESOURCE, VERSION_1, "0000", NODE); - CdsResourceWatcher watcher = mock(CdsResourceWatcher.class); - xdsClient.watchCdsResource(CDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), CDS_RESOURCE, watcher); verify(watcher).onChanged(cdsUpdateCaptor.capture()); verifyGoldenClusterRoundRobin(cdsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -2351,12 +2413,14 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedCdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(cdsResourceWatcher).onResourceDoesNotExist(CDS_RESOURCE); - CdsResourceWatcher watcher = mock(CdsResourceWatcher.class); - xdsClient.watchCdsResource(CDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(CDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(CDS, CDS_RESOURCE); @@ -2365,7 +2429,8 @@ public abstract class ClientXdsClientTestBase { @Test public void cdsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Initial CDS response. @@ -2419,7 +2484,8 @@ public abstract class ClientXdsClientTestBase { // Assures that CDS updates identical to the current config are ignored. @Test public void cdsResourceUpdatedWithDuplicate() { - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); String edsService = "eds-service-bar.googleapis.com"; String transportSocketName = "envoy.transport_sockets.tls"; @@ -2467,7 +2533,8 @@ public abstract class ClientXdsClientTestBase { public void cdsResourceDeleted() { Assume.assumeFalse(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Initial CDS response. @@ -2495,7 +2562,8 @@ public abstract class ClientXdsClientTestBase { public void cdsResourceDeleted_ignoreResourceDeletion() { Assume.assumeTrue(ignoreResourceDeletion()); - DiscoveryRpcCall call = startResourceWatcher(CDS, CDS_RESOURCE, cdsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsClusterResource.getInstance(), CDS_RESOURCE, + cdsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); // Initial CDS response. @@ -2516,7 +2584,7 @@ public abstract class ClientXdsClientTestBase { TIME_INCREMENT); verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); // onResourceDoesNotExist must not be called. - verify(cdsResourceWatcher, never()).onResourceDoesNotExist(CDS_RESOURCE); + verify(ldsResourceWatcher, never()).onResourceDoesNotExist(CDS_RESOURCE); // Next update is correct, and contains the cluster again. call.sendResponse(CDS, testClusterRoundRobin, VERSION_3, "0003"); @@ -2526,17 +2594,18 @@ public abstract class ClientXdsClientTestBase { verifyResourceMetadataAcked(CDS, CDS_RESOURCE, testClusterRoundRobin, VERSION_3, TIME_INCREMENT * 3); verifySubscribedResourcesMetadataSizes(0, 1, 0, 0); - verifyNoMoreInteractions(cdsResourceWatcher); + verifyNoMoreInteractions(ldsResourceWatcher); } @Test + @SuppressWarnings("unchecked") public void multipleCdsWatchers() { String cdsResourceTwo = "cluster-bar.googleapis.com"; - CdsResourceWatcher watcher1 = mock(CdsResourceWatcher.class); - CdsResourceWatcher watcher2 = mock(CdsResourceWatcher.class); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchCdsResource(cdsResourceTwo, watcher1); - xdsClient.watchCdsResource(cdsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),cdsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),cdsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(CDS, Arrays.asList(CDS_RESOURCE, cdsResourceTwo), "", "", NODE); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); @@ -2607,7 +2676,8 @@ public abstract class ClientXdsClientTestBase { @Test public void edsResourceNotFound() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); Any clusterLoadAssignment = Any.pack(mf.buildClusterLoadAssignment( "cluster-bar.googleapis.com", ImmutableList.of(lbEndpointHealthy), @@ -2629,7 +2699,8 @@ public abstract class ClientXdsClientTestBase { @Test public void edsResponseErrorHandling_allResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); call.sendResponse(EDS, ImmutableList.of(FAILING_ANY, FAILING_ANY), VERSION_1, "0000"); @@ -2645,7 +2716,8 @@ public abstract class ClientXdsClientTestBase { @Test public void edsResponseErrorHandling_someResourcesFailedUnpack() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); // Correct resource is in the middle to ensure processing continues on errors. @@ -2675,9 +2747,9 @@ public abstract class ClientXdsClientTestBase { @Test public void edsResponseErrorHandling_subscribedResourceInvalid() { List subscribedResourceNames = ImmutableList.of("A", "B", "C"); - xdsClient.watchEdsResource("A", edsResourceWatcher); - xdsClient.watchEdsResource("B", edsResourceWatcher); - xdsClient.watchEdsResource("C", edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"A", edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"B", edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),"C", edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); assertThat(call).isNotNull(); verifyResourceMetadataRequested(EDS, "A"); @@ -2735,7 +2807,8 @@ public abstract class ClientXdsClientTestBase { @Test public void edsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); call.sendResponse(EDS, testClusterLoadAssignment, VERSION_1, "0000"); // Client sent an ACK EDS request. @@ -2749,7 +2822,8 @@ public abstract class ClientXdsClientTestBase { @Test public void wrappedEdsResourceFound() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); call.sendResponse(EDS, mf.buildWrappedResource(testClusterLoadAssignment), VERSION_1, "0000"); // Client sent an ACK EDS request. @@ -2762,15 +2836,17 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedEdsResource_data() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); call.sendResponse(EDS, testClusterLoadAssignment, VERSION_1, "0000"); // Client sent an ACK EDS request. call.verifyRequest(EDS, EDS_RESOURCE, VERSION_1, "0000", NODE); // Add another watcher. - EdsResourceWatcher watcher = mock(EdsResourceWatcher.class); - xdsClient.watchEdsResource(EDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, watcher); verify(watcher).onChanged(edsUpdateCaptor.capture()); validateGoldenClusterLoadAssignment(edsUpdateCaptor.getValue()); call.verifyNoMoreRequest(); @@ -2780,12 +2856,14 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void cachedEdsResource_absent() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); fakeClock.forwardTime(ClientXdsClient.INITIAL_RESOURCE_FETCH_TIMEOUT_SEC, TimeUnit.SECONDS); verify(edsResourceWatcher).onResourceDoesNotExist(EDS_RESOURCE); - EdsResourceWatcher watcher = mock(EdsResourceWatcher.class); - xdsClient.watchEdsResource(EDS_RESOURCE, watcher); + ResourceWatcher watcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, watcher); verify(watcher).onResourceDoesNotExist(EDS_RESOURCE); call.verifyNoMoreRequest(); verifyResourceMetadataDoesNotExist(EDS, EDS_RESOURCE); @@ -2794,7 +2872,8 @@ public abstract class ClientXdsClientTestBase { @Test public void edsResourceUpdated() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); // Initial EDS response. @@ -2830,7 +2909,8 @@ public abstract class ClientXdsClientTestBase { @Test public void edsDuplicateLocalityInTheSamePriority() { - DiscoveryRpcCall call = startResourceWatcher(EDS, EDS_RESOURCE, edsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsEndpointResource.getInstance(), EDS_RESOURCE, + edsResourceWatcher); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); // Updated EDS response. @@ -2852,14 +2932,15 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void edsResourceDeletedByCds() { String resource = "backend-service.googleapis.com"; - CdsResourceWatcher cdsWatcher = mock(CdsResourceWatcher.class); - EdsResourceWatcher edsWatcher = mock(EdsResourceWatcher.class); - xdsClient.watchCdsResource(resource, cdsWatcher); - xdsClient.watchEdsResource(resource, edsWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + ResourceWatcher cdsWatcher = mock(ResourceWatcher.class); + ResourceWatcher edsWatcher = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),resource, cdsWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),resource, edsWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, edsResourceWatcher); verifyResourceMetadataRequested(CDS, CDS_RESOURCE); verifyResourceMetadataRequested(CDS, resource); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); @@ -2939,13 +3020,14 @@ public abstract class ClientXdsClientTestBase { } @Test + @SuppressWarnings("unchecked") public void multipleEdsWatchers() { String edsResourceTwo = "cluster-load-assignment-bar.googleapis.com"; - EdsResourceWatcher watcher1 = mock(EdsResourceWatcher.class); - EdsResourceWatcher watcher2 = mock(EdsResourceWatcher.class); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); - xdsClient.watchEdsResource(edsResourceTwo, watcher1); - xdsClient.watchEdsResource(edsResourceTwo, watcher2); + ResourceWatcher watcher1 = mock(ResourceWatcher.class); + ResourceWatcher watcher2 = mock(ResourceWatcher.class); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, edsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),edsResourceTwo, watcher1); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),edsResourceTwo, watcher2); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(EDS, Arrays.asList(EDS_RESOURCE, edsResourceTwo), "", "", NODE); verifyResourceMetadataRequested(EDS, EDS_RESOURCE); @@ -3012,7 +3094,8 @@ public abstract class ClientXdsClientTestBase { CancellableContext cancellableContext = Context.current().withCancellation(); Context prevContext = cancellableContext.attach(); try { - DiscoveryRpcCall call = startResourceWatcher(LDS, LDS_RESOURCE, ldsResourceWatcher); + DiscoveryRpcCall call = startResourceWatcher(XdsListenerResource.getInstance(), LDS_RESOURCE, + ldsResourceWatcher); // The inbound RPC finishes and closes its context. The outbound RPC's control plane RPC // should not be impacted. @@ -3029,10 +3112,11 @@ public abstract class ClientXdsClientTestBase { @Test public void streamClosedAndRetryWithBackoff() { InOrder inOrder = Mockito.inOrder(backoffPolicyProvider, backoffPolicy1, backoffPolicy2); - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(),LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(),RDS_RESOURCE, + rdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(),CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(),EDS_RESOURCE, edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.verifyRequest(LDS, LDS_RESOURCE, "", "", NODE); call.verifyRequest(RDS, RDS_RESOURCE, "", "", NODE); @@ -3149,8 +3233,10 @@ public abstract class ClientXdsClientTestBase { @Test public void streamClosedAndRetryRaceWithAddRemoveWatchers() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), + LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); call.sendError(Status.UNAVAILABLE.asException()); verify(ldsResourceWatcher).onError(errorCaptor.capture()); @@ -3161,10 +3247,14 @@ public abstract class ClientXdsClientTestBase { Iterables.getOnlyElement(fakeClock.getPendingTasks(RPC_RETRY_TASK_FILTER)); assertThat(retryTask.getDelay(TimeUnit.NANOSECONDS)).isEqualTo(10L); - xdsClient.cancelLdsResourceWatch(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.cancelRdsResourceWatch(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsListenerResource.getInstance(), + LDS_RESOURCE, ldsResourceWatcher); + xdsClient.cancelXdsResourceWatch(XdsRouteConfigureResource.getInstance(), + RDS_RESOURCE, rdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), + CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), + EDS_RESOURCE, edsResourceWatcher); fakeClock.forwardNanos(10L); call = resourceDiscoveryCalls.poll(); call.verifyRequest(CDS, CDS_RESOURCE, "", "", NODE); @@ -3181,10 +3271,11 @@ public abstract class ClientXdsClientTestBase { @Test public void streamClosedAndRetryRestartsResourceInitialFetchTimerForUnresolvedResources() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); - xdsClient.watchRdsResource(RDS_RESOURCE, rdsResourceWatcher); - xdsClient.watchCdsResource(CDS_RESOURCE, cdsResourceWatcher); - xdsClient.watchEdsResource(EDS_RESOURCE, edsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsRouteConfigureResource.getInstance(), RDS_RESOURCE, + rdsResourceWatcher); + xdsClient.watchXdsResource(XdsClusterResource.getInstance(), CDS_RESOURCE, cdsResourceWatcher); + xdsClient.watchXdsResource(XdsEndpointResource.getInstance(), EDS_RESOURCE, edsResourceWatcher); DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); ScheduledTask ldsResourceTimeout = Iterables.getOnlyElement(fakeClock.getPendingTasks(LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER)); @@ -3221,7 +3312,7 @@ public abstract class ClientXdsClientTestBase { @Test public void reportLoadStatsToServer() { - xdsClient.watchLdsResource(LDS_RESOURCE, ldsResourceWatcher); + xdsClient.watchXdsResource(XdsListenerResource.getInstance(), LDS_RESOURCE, ldsResourceWatcher); String clusterName = "cluster-foo.googleapis.com"; ClusterDropStats dropStats = xdsClient.addClusterDropStats(xdsServerInfo, clusterName, null); LrsRpcCall lrsCall = loadReportCalls.poll(); @@ -3249,7 +3340,8 @@ public abstract class ClientXdsClientTestBase { public void serverSideListenerFound() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3285,7 +3377,8 @@ public abstract class ClientXdsClientTestBase { public void serverSideListenerNotFound() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3312,7 +3405,8 @@ public abstract class ClientXdsClientTestBase { public void serverSideListenerResponseErrorHandling_badDownstreamTlsContext() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3338,7 +3432,8 @@ public abstract class ClientXdsClientTestBase { public void serverSideListenerResponseErrorHandling_badTransportSocketName() { Assume.assumeTrue(useProtocolV3()); ClientXdsClientTestBase.DiscoveryRpcCall call = - startResourceWatcher(LDS, LISTENER_RESOURCE, ldsResourceWatcher); + startResourceWatcher(XdsListenerResource.getInstance(), LISTENER_RESOURCE, + ldsResourceWatcher); Message hcmFilter = mf.buildHttpConnectionManagerFilter( "route-foo.googleapis.com", null, Collections.singletonList(mf.buildTerminalFilter())); @@ -3361,32 +3456,32 @@ public abstract class ClientXdsClientTestBase { verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg); } - private DiscoveryRpcCall startResourceWatcher( - ResourceType type, String name, ResourceWatcher watcher) { + private DiscoveryRpcCall startResourceWatcher( + XdsResourceType type, String name, ResourceWatcher watcher) { FakeClock.TaskFilter timeoutTaskFilter; - switch (type) { + switch (type.typeName()) { case LDS: timeoutTaskFilter = LDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchLdsResource(name, (LdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case RDS: timeoutTaskFilter = RDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchRdsResource(name, (RdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case CDS: timeoutTaskFilter = CDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchCdsResource(name, (CdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case EDS: timeoutTaskFilter = EDS_RESOURCE_FETCH_TIMEOUT_TASK_FILTER; - xdsClient.watchEdsResource(name, (EdsResourceWatcher) watcher); + xdsClient.watchXdsResource(type, name, watcher); break; case UNKNOWN: default: throw new AssertionError("should never be here"); } DiscoveryRpcCall call = resourceDiscoveryCalls.poll(); - call.verifyRequest(type, Collections.singletonList(name), "", "", NODE); + call.verifyRequest(type.typeName(), Collections.singletonList(name), "", "", NODE); ScheduledTask timeoutTask = Iterables.getOnlyElement(fakeClock.getPendingTasks(timeoutTaskFilter)); assertThat(timeoutTask.getDelay(TimeUnit.SECONDS)) diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java index 3e2cafd58f..3bb6e42138 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV2Test.java @@ -463,7 +463,7 @@ public class ClientXdsClientV2Test extends ClientXdsClientTestBase { ClusterConfig clusterConfig = ClusterConfig.newBuilder().addAllClusters(clusters).build(); CustomClusterType type = CustomClusterType.newBuilder() - .setName(ClientXdsClient.AGGREGATE_CLUSTER_TYPE_NAME) + .setName(XdsResourceType.AGGREGATE_CLUSTER_TYPE_NAME) .setTypedConfig(Any.pack(clusterConfig)) .build(); Cluster.Builder builder = Cluster.newBuilder().setName(clusterName).setClusterType(type); diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java index b051643c0b..6eb48e5bb0 100644 --- a/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java +++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientV3Test.java @@ -519,7 +519,7 @@ public class ClientXdsClientV3Test extends ClientXdsClientTestBase { ClusterConfig clusterConfig = ClusterConfig.newBuilder().addAllClusters(clusters).build(); CustomClusterType type = CustomClusterType.newBuilder() - .setName(ClientXdsClient.AGGREGATE_CLUSTER_TYPE_NAME) + .setName(XdsResourceType.AGGREGATE_CLUSTER_TYPE_NAME) .setTypedConfig(Any.pack(clusterConfig)) .build(); Cluster.Builder builder = Cluster.newBuilder().setName(clusterName).setClusterType(type); diff --git a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java index 22a4985aaf..741c2ba8cd 100644 --- a/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java +++ b/xds/src/test/java/io/grpc/xds/ClusterResolverLoadBalancerTest.java @@ -17,6 +17,7 @@ package io.grpc.xds; import static com.google.common.truth.Truth.assertThat; +import static io.grpc.xds.AbstractXdsClient.ResourceType.EDS; import static io.grpc.xds.XdsLbPolicies.CLUSTER_IMPL_POLICY_NAME; import static io.grpc.xds.XdsLbPolicies.PRIORITY_POLICY_NAME; import static io.grpc.xds.XdsLbPolicies.WEIGHTED_TARGET_POLICY_NAME; @@ -76,6 +77,7 @@ import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig; import io.grpc.xds.PriorityLoadBalancerProvider.PriorityLbConfig.PriorityChildConfig; import io.grpc.xds.RingHashLoadBalancer.RingHashConfig; import io.grpc.xds.WrrLocalityLoadBalancer.WrrLocalityConfig; +import io.grpc.xds.XdsEndpointResource.EdsUpdate; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.net.SocketAddress; import java.net.URI; @@ -185,7 +187,6 @@ public class ClusterResolverLoadBalancerTest { private int xdsClientRefs; private ClusterResolverLoadBalancer loadBalancer; - @Before public void setUp() throws URISyntaxException { MockitoAnnotations.initMocks(this); @@ -1168,16 +1169,24 @@ public class ClusterResolverLoadBalancerTest { } private static final class FakeXdsClient extends XdsClient { - private final Map watchers = new HashMap<>(); + private final Map> watchers = new HashMap<>(); + @Override - void watchEdsResource(String resourceName, EdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType type, String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(EDS); assertThat(watchers).doesNotContainKey(resourceName); - watchers.put(resourceName, watcher); + watchers.put(resourceName, (ResourceWatcher) watcher); } @Override - void cancelEdsResourceWatch(String resourceName, EdsResourceWatcher watcher) { + @SuppressWarnings("unchecked") + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + assertThat(type.typeName()).isEqualTo(EDS); assertThat(watchers).containsKey(resourceName); watchers.remove(resourceName); } @@ -1192,7 +1201,7 @@ public class ClusterResolverLoadBalancerTest { Map localityLbEndpointsMap) { if (watchers.containsKey(resource)) { watchers.get(resource).onChanged( - new EdsUpdate(resource, localityLbEndpointsMap, dropOverloads)); + new XdsEndpointResource.EdsUpdate(resource, localityLbEndpointsMap, dropOverloads)); } } @@ -1203,7 +1212,7 @@ public class ClusterResolverLoadBalancerTest { } void deliverError(Status error) { - for (EdsResourceWatcher watcher : watchers.values()) { + for (ResourceWatcher watcher : watchers.values()) { watcher.onError(error); } } diff --git a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java index cec4688c06..5099178118 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientWrapperForServerSdsTestMisc.java @@ -44,7 +44,7 @@ import io.grpc.netty.ProtocolNegotiationEvent; import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext; import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler; import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHandler.FilterChainSelector; -import io.grpc.xds.XdsClient.LdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index d96fa2b30a..f2eddf00fe 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -86,7 +86,9 @@ import io.grpc.xds.VirtualHost.Route.RouteAction.HashPolicy; import io.grpc.xds.VirtualHost.Route.RouteAction.RetryPolicy; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsNameResolverProvider.XdsClientPoolFactory; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.Matchers.HeaderMatcher; import java.io.IOException; import java.util.ArrayList; @@ -2072,8 +2074,8 @@ public class XdsNameResolverTest { // Should never be subscribing to more than one LDS and RDS resource at any point of time. private String ldsResource; // should always be AUTHORITY private String rdsResource; - private LdsResourceWatcher ldsWatcher; - private RdsResourceWatcher rdsWatcher; + private ResourceWatcher ldsWatcher; + private ResourceWatcher rdsWatcher; @Override BootstrapInfo getBootstrapInfo() { @@ -2081,37 +2083,49 @@ public class XdsNameResolverTest { } @Override - void watchLdsResource(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsResource).isNull(); - assertThat(ldsWatcher).isNull(); - assertThat(resourceName).isEqualTo(expectedLdsResourceName); - ldsResource = resourceName; - ldsWatcher = watcher; + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType resourceType, + String resourceName, + ResourceWatcher watcher) { + + switch (resourceType.typeName()) { + case LDS: + assertThat(ldsResource).isNull(); + assertThat(ldsWatcher).isNull(); + assertThat(resourceName).isEqualTo(expectedLdsResourceName); + ldsResource = resourceName; + ldsWatcher = (ResourceWatcher) watcher; + break; + case RDS: + assertThat(rdsResource).isNull(); + assertThat(rdsWatcher).isNull(); + rdsResource = resourceName; + rdsWatcher = (ResourceWatcher) watcher; + break; + default: + } } @Override - void cancelLdsResourceWatch(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsResource).isNotNull(); - assertThat(ldsWatcher).isNotNull(); - assertThat(resourceName).isEqualTo(expectedLdsResourceName); - ldsResource = null; - ldsWatcher = null; - } - - @Override - void watchRdsResource(String resourceName, RdsResourceWatcher watcher) { - assertThat(rdsResource).isNull(); - assertThat(rdsWatcher).isNull(); - rdsResource = resourceName; - rdsWatcher = watcher; - } - - @Override - void cancelRdsResourceWatch(String resourceName, RdsResourceWatcher watcher) { - assertThat(rdsResource).isNotNull(); - assertThat(rdsWatcher).isNotNull(); - rdsResource = null; - rdsWatcher = null; + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + switch (type.typeName()) { + case LDS: + assertThat(ldsResource).isNotNull(); + assertThat(ldsWatcher).isNotNull(); + assertThat(resourceName).isEqualTo(expectedLdsResourceName); + ldsResource = null; + ldsWatcher = null; + break; + case RDS: + assertThat(rdsResource).isNotNull(); + assertThat(rdsWatcher).isNotNull(); + rdsResource = null; + rdsWatcher = null; + break; + default: + } } void deliverLdsUpdate(long httpMaxStreamDurationNano, List virtualHosts) { diff --git a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java index fe28da8bff..3ef23c1137 100644 --- a/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsSdsClientServerTest.java @@ -57,7 +57,7 @@ import io.grpc.xds.Filter.NamedFilterConfig; import io.grpc.xds.VirtualHost.Route; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.LdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; import io.grpc.xds.internal.Matchers.HeaderMatcher; diff --git a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java index 15868ba414..c13be0361d 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerTestHelper.java @@ -30,7 +30,8 @@ import io.grpc.xds.EnvoyServerProtoData.Listener; import io.grpc.xds.Filter.FilterConfig; import io.grpc.xds.Filter.NamedFilterConfig; import io.grpc.xds.VirtualHost.Route; -import io.grpc.xds.XdsClient.LdsUpdate; +import io.grpc.xds.XdsListenerResource.LdsUpdate; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -163,9 +164,9 @@ public class XdsServerTestHelper { static final class FakeXdsClient extends XdsClient { boolean shutdown; SettableFuture ldsResource = SettableFuture.create(); - LdsResourceWatcher ldsWatcher; + ResourceWatcher ldsWatcher; CountDownLatch rdsCount = new CountDownLatch(1); - final Map rdsWatchers = new HashMap<>(); + final Map> rdsWatchers = new HashMap<>(); @Override public TlsContextManager getTlsContextManager() { @@ -178,28 +179,40 @@ public class XdsServerTestHelper { } @Override - void watchLdsResource(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsWatcher).isNull(); - ldsWatcher = watcher; - ldsResource.set(resourceName); + @SuppressWarnings("unchecked") + void watchXdsResource(XdsResourceType resourceType, + String resourceName, + ResourceWatcher watcher) { + switch (resourceType.typeName()) { + case LDS: + assertThat(ldsWatcher).isNull(); + ldsWatcher = (ResourceWatcher) watcher; + ldsResource.set(resourceName); + break; + case RDS: + //re-register is not allowed. + assertThat(rdsWatchers.put(resourceName, (ResourceWatcher)watcher)).isNull(); + rdsCount.countDown(); + break; + default: + } } @Override - void cancelLdsResourceWatch(String resourceName, LdsResourceWatcher watcher) { - assertThat(ldsWatcher).isNotNull(); - ldsResource = null; - ldsWatcher = null; - } - - @Override - void watchRdsResource(String resourceName, RdsResourceWatcher watcher) { - assertThat(rdsWatchers.put(resourceName, watcher)).isNull(); //re-register is not allowed. - rdsCount.countDown(); - } - - @Override - void cancelRdsResourceWatch(String resourceName, RdsResourceWatcher watcher) { - rdsWatchers.remove(resourceName); + void cancelXdsResourceWatch(XdsResourceType type, + String resourceName, + ResourceWatcher watcher) { + switch (type.typeName()) { + case LDS: + assertThat(ldsWatcher).isNotNull(); + ldsResource = null; + ldsWatcher = null; + break; + case RDS: + rdsWatchers.remove(resourceName); + break; + default: + } } @Override @@ -213,7 +226,7 @@ public class XdsServerTestHelper { } void deliverLdsUpdate(List filterChains, - FilterChain defaultFilterChain) { + FilterChain defaultFilterChain) { ldsWatcher.onChanged(LdsUpdate.forTcpListener(Listener.create( "listener", "0.0.0.0:1", ImmutableList.copyOf(filterChains), defaultFilterChain))); } diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java index d0b05112ba..ef2f606f3f 100644 --- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java @@ -57,9 +57,8 @@ import io.grpc.xds.FilterChainMatchingProtocolNegotiators.FilterChainMatchingHan import io.grpc.xds.VirtualHost.Route; import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; -import io.grpc.xds.XdsClient.LdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsResourceWatcher; -import io.grpc.xds.XdsClient.RdsUpdate; +import io.grpc.xds.XdsClient.ResourceWatcher; +import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.XdsServerBuilder.XdsServingStatusListener; import io.grpc.xds.XdsServerTestHelper.FakeXdsClient; import io.grpc.xds.XdsServerTestHelper.FakeXdsClientPoolFactory; @@ -177,6 +176,7 @@ public class XdsServerWrapperTest { } @Test + @SuppressWarnings("unchecked") public void testBootstrap_templateWithXdstp() throws Exception { Bootstrapper.BootstrapInfo b = Bootstrapper.BootstrapInfo.builder() .servers(Arrays.asList( @@ -187,6 +187,7 @@ public class XdsServerWrapperTest { "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/server/%s") .build(); XdsClient xdsClient = mock(XdsClient.class); + XdsListenerResource listenerResource = XdsListenerResource.getInstance(); when(xdsClient.getBootstrapInfo()).thenReturn(b); xdsServerWrapper = new XdsServerWrapper("[::FFFF:129.144.52.38]:80", mockBuilder, listener, selectorManager, new FakeXdsClientPoolFactory(xdsClient), filterRegistry); @@ -200,10 +201,11 @@ public class XdsServerWrapperTest { } } }); - verify(xdsClient, timeout(5000)).watchLdsResource( + verify(xdsClient, timeout(5000)).watchXdsResource( + eq(listenerResource), eq("xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/server/" + "%5B::FFFF:129.144.52.38%5D:80"), - any(LdsResourceWatcher.class)); + any(ResourceWatcher.class)); } @Test @@ -727,7 +729,7 @@ public class XdsServerWrapperTest { xdsClient.ldsWatcher.onError(Status.INTERNAL); assertThat(selectorManager.getSelectorToUpdateSelector()) .isSameInstanceAs(FilterChainSelector.NO_FILTER_CHAIN); - RdsResourceWatcher saveRdsWatcher = xdsClient.rdsWatchers.get("rds"); + ResourceWatcher saveRdsWatcher = xdsClient.rdsWatchers.get("rds"); verify(mockBuilder, times(1)).build(); verify(listener, times(2)).onNotServing(any(StatusException.class)); assertThat(sslSupplier0.isShutdown()).isFalse();