From 6d96e6588e17d5035a951ac0402278cbc2e85869 Mon Sep 17 00:00:00 2001 From: Anirudh Ramachandra Date: Thu, 25 Jan 2024 09:18:52 +0530 Subject: [PATCH] Allow users outside of io.grpc.xds package to create custom xDS resources (#10834) Currently few of the interfaces needed to define and start a watch for a xDS resource are package private, which can't be used externally outside of io.grpc.xds. Exposing them outside allows users to define their own custom resources and start a watch along with the default supported resources. Also as part of this change, move an Exception defined in the XdsClientImpl into XdsResourceType. As XdsClientImpl is an implementation package, it makes more sense to expose it via the XdsResourceType class. --- .../grpc/xds/LoadBalancerConfigFactory.java | 2 +- xds/src/main/java/io/grpc/xds/XdsClient.java | 5 ++- .../main/java/io/grpc/xds/XdsClientImpl.java | 12 ------- .../java/io/grpc/xds/XdsClusterResource.java | 15 ++++---- .../java/io/grpc/xds/XdsEndpointResource.java | 15 ++++---- .../java/io/grpc/xds/XdsListenerResource.java | 14 ++++---- .../java/io/grpc/xds/XdsResourceType.java | 35 +++++++++++++------ .../grpc/xds/XdsRouteConfigureResource.java | 14 ++++---- .../xds/LoadBalancerConfigFactoryTest.java | 2 +- .../io/grpc/xds/XdsClientImplDataTest.java | 2 +- .../io/grpc/xds/XdsClientImplTestBase.java | 6 ++-- 11 files changed, 61 insertions(+), 61 deletions(-) diff --git a/xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java b/xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java index 9b1dc722400..81ae3454045 100644 --- a/xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java +++ b/xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java @@ -39,8 +39,8 @@ import io.grpc.LoadBalancerRegistry; import io.grpc.internal.JsonParser; import io.grpc.xds.LoadBalancerConfigFactory.LoadBalancingPolicyConverter.MaxRecursionReachedException; -import io.grpc.xds.XdsClientImpl.ResourceInvalidException; import io.grpc.xds.XdsLogger.XdsLogLevel; +import io.grpc.xds.XdsResourceType.ResourceInvalidException; import java.io.IOException; import java.util.Map; diff --git a/xds/src/main/java/io/grpc/xds/XdsClient.java b/xds/src/main/java/io/grpc/xds/XdsClient.java index aa222a98d5c..267d5468b62 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClient.java +++ b/xds/src/main/java/io/grpc/xds/XdsClient.java @@ -47,7 +47,7 @@ * protocols (e.g., LDS, RDS, VHDS, CDS and EDS) over a single channel. Watch-based interfaces * are provided for each set of data needed by gRPC. */ -abstract class XdsClient { +public abstract class XdsClient { static boolean isResourceNameValid(String resourceName, String typeUrl) { checkNotNull(resourceName, "resourceName"); @@ -110,8 +110,7 @@ static String percentEncodePath(String input) { return Joiner.on('/').join(encodedSegs); } - interface ResourceUpdate { - } + public interface ResourceUpdate {} /** * Watcher interface for a single requested xDS resource. diff --git a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java index 7389d7ebef1..0e5e7106c17 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClientImpl.java +++ b/xds/src/main/java/io/grpc/xds/XdsClientImpl.java @@ -748,18 +748,6 @@ private void notifyWatcher(ResourceWatcher watcher, T update) { } } - static final class ResourceInvalidException extends Exception { - private static final long serialVersionUID = 0L; - - ResourceInvalidException(String message) { - super(message, null, false, false); - } - - ResourceInvalidException(String message, Throwable cause) { - super(cause != null ? message + ": " + cause.getMessage() : message, cause, false, false); - } - } - abstract static class XdsChannelFactory { static final XdsChannelFactory DEFAULT_XDS_CHANNEL_FACTORY = new XdsChannelFactory() { @Override diff --git a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java index 73b0a975ff4..00285453aab 100644 --- a/xds/src/main/java/io/grpc/xds/XdsClusterResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsClusterResource.java @@ -42,8 +42,8 @@ import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.XdsClient.ResourceUpdate; -import io.grpc.xds.XdsClientImpl.ResourceInvalidException; import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsResourceType.ResourceInvalidException; import java.util.List; import java.util.Locale; import java.util.Set; @@ -65,7 +65,7 @@ public static XdsClusterResource getInstance() { @Override @Nullable - String extractResourceName(Message unpackedResource) { + protected String extractResourceName(Message unpackedResource) { if (!(unpackedResource instanceof Cluster)) { return null; } @@ -73,29 +73,28 @@ String extractResourceName(Message unpackedResource) { } @Override - String typeName() { + protected String typeName() { return "CDS"; } @Override - String typeUrl() { + protected String typeUrl() { return ADS_TYPE_URL_CDS; } @Override - boolean isFullStateOfTheWorld() { + protected boolean isFullStateOfTheWorld() { return true; } @Override @SuppressWarnings("unchecked") - Class unpackedClassName() { + protected Class unpackedClassName() { return Cluster.class; } @Override - CdsUpdate doParse(Args args, Message unpackedMessage) - throws ResourceInvalidException { + protected CdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException { if (!(unpackedMessage instanceof Cluster)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); } diff --git a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java index 39caa9a8597..129c664ad25 100644 --- a/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsEndpointResource.java @@ -28,8 +28,8 @@ import io.grpc.xds.Endpoints.DropOverload; import io.grpc.xds.Endpoints.LocalityLbEndpoints; import io.grpc.xds.XdsClient.ResourceUpdate; -import io.grpc.xds.XdsClientImpl.ResourceInvalidException; import io.grpc.xds.XdsEndpointResource.EdsUpdate; +import io.grpc.xds.XdsResourceType.ResourceInvalidException; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; @@ -54,7 +54,7 @@ public static XdsEndpointResource getInstance() { @Override @Nullable - String extractResourceName(Message unpackedResource) { + protected String extractResourceName(Message unpackedResource) { if (!(unpackedResource instanceof ClusterLoadAssignment)) { return null; } @@ -62,28 +62,27 @@ String extractResourceName(Message unpackedResource) { } @Override - String typeName() { + protected String typeName() { return "EDS"; } @Override - String typeUrl() { + protected String typeUrl() { return ADS_TYPE_URL_EDS; } @Override - boolean isFullStateOfTheWorld() { + protected boolean isFullStateOfTheWorld() { return false; } @Override - Class unpackedClassName() { + protected Class unpackedClassName() { return ClusterLoadAssignment.class; } @Override - EdsUpdate doParse(Args args, Message unpackedMessage) - throws ResourceInvalidException { + protected EdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException { if (!(unpackedMessage instanceof ClusterLoadAssignment)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); } diff --git a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java index 62e71a1cbd8..642193c4377 100644 --- a/xds/src/main/java/io/grpc/xds/XdsListenerResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsListenerResource.java @@ -18,8 +18,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import static io.grpc.xds.XdsClient.ResourceUpdate; -import static io.grpc.xds.XdsClientImpl.ResourceInvalidException; import static io.grpc.xds.XdsClusterResource.validateCommonTlsContext; +import static io.grpc.xds.XdsResourceType.ResourceInvalidException; import static io.grpc.xds.XdsRouteConfigureResource.extractVirtualHosts; import com.github.udpa.udpa.type.v1.TypedStruct; @@ -66,7 +66,7 @@ public static XdsListenerResource getInstance() { @Override @Nullable - String extractResourceName(Message unpackedResource) { + protected String extractResourceName(Message unpackedResource) { if (!(unpackedResource instanceof Listener)) { return null; } @@ -74,27 +74,27 @@ String extractResourceName(Message unpackedResource) { } @Override - String typeName() { + protected String typeName() { return "LDS"; } @Override - Class unpackedClassName() { + protected Class unpackedClassName() { return Listener.class; } @Override - String typeUrl() { + protected String typeUrl() { return ADS_TYPE_URL_LDS; } @Override - boolean isFullStateOfTheWorld() { + protected boolean isFullStateOfTheWorld() { return true; } @Override - LdsUpdate doParse(Args args, Message unpackedMessage) + protected LdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException { if (!(unpackedMessage instanceof Listener)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); diff --git a/xds/src/main/java/io/grpc/xds/XdsResourceType.java b/xds/src/main/java/io/grpc/xds/XdsResourceType.java index 1bec72d492c..ce2bb7301a7 100644 --- a/xds/src/main/java/io/grpc/xds/XdsResourceType.java +++ b/xds/src/main/java/io/grpc/xds/XdsResourceType.java @@ -21,7 +21,6 @@ import static io.grpc.xds.XdsClient.ResourceUpdate; import static io.grpc.xds.XdsClient.canonifyResourceName; import static io.grpc.xds.XdsClient.isResourceNameValid; -import static io.grpc.xds.XdsClientImpl.ResourceInvalidException; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; @@ -29,7 +28,10 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; import io.envoyproxy.envoy.service.discovery.v3.Resource; +import io.grpc.ExperimentalApi; import io.grpc.LoadBalancerRegistry; +import io.grpc.xds.Bootstrapper.ServerInfo; +import io.grpc.xds.XdsClient.ResourceUpdate; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -38,7 +40,8 @@ import java.util.Set; import javax.annotation.Nullable; -abstract class XdsResourceType { +@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10847") +public abstract class XdsResourceType { static final String TYPE_URL_RESOURCE = "type.googleapis.com/envoy.service.discovery.v3.Resource"; static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls"; @@ -68,22 +71,22 @@ abstract class XdsResourceType { "type.googleapis.com/xds.type.v3.TypedStruct"; @Nullable - abstract String extractResourceName(Message unpackedResource); + protected abstract String extractResourceName(Message unpackedResource); - abstract Class unpackedClassName(); + protected abstract Class unpackedClassName(); - abstract String typeName(); + protected abstract String typeName(); - abstract String typeUrl(); + protected abstract String typeUrl(); // Do not confuse with the SotW approach: it is the mechanism in which the client must specify all // resource names it is interested in with each request. Different resource types may behave // differently in this approach. For LDS and CDS resources, the server must return all resources // that the client has subscribed to in each request. For RDS and EDS, the server may only return // the resources that need an update. - abstract boolean isFullStateOfTheWorld(); + protected abstract boolean isFullStateOfTheWorld(); - static class Args { + public static class Args { final ServerInfo serverInfo; final String versionInfo; final String nonce; @@ -114,6 +117,18 @@ public Args(ServerInfo serverInfo, String versionInfo, String nonce, } } + public static final class ResourceInvalidException extends Exception { + private static final long serialVersionUID = 0L; + + public ResourceInvalidException(String message) { + super(message, null, false, false); + } + + public ResourceInvalidException(String message, Throwable cause) { + super(cause != null ? message + ": " + cause.getMessage() : message, cause, false, false); + } + } + ValidatedResourceUpdate parse(Args args, List resources) { Map> parsedResources = new HashMap<>(resources.size()); Set unpackedResources = new HashSet<>(resources.size()); @@ -147,7 +162,7 @@ ValidatedResourceUpdate parse(Args args, List resources) { T resourceUpdate; try { resourceUpdate = doParse(args, unpackedMessage); - } catch (XdsClientImpl.ResourceInvalidException e) { + } catch (ResourceInvalidException e) { errors.add(String.format("%s response %s '%s' validation error: %s", typeName(), unpackedClassName().getSimpleName(), cname, e.getMessage())); invalidResources.add(cname); @@ -162,7 +177,7 @@ ValidatedResourceUpdate parse(Args args, List resources) { } - abstract T doParse(Args args, Message unpackedMessage) throws ResourceInvalidException; + protected abstract T doParse(Args args, Message unpackedMessage) throws ResourceInvalidException; /** * Helper method to unpack serialized {@link com.google.protobuf.Any} message, while replacing diff --git a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java index 47f6f6beeea..7adac68df1e 100644 --- a/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java +++ b/xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java @@ -49,7 +49,7 @@ 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.XdsClientImpl.ResourceInvalidException; +import io.grpc.xds.XdsResourceType.ResourceInvalidException; import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.MatcherParser; import io.grpc.xds.internal.Matchers; @@ -85,7 +85,7 @@ public static XdsRouteConfigureResource getInstance() { @Override @Nullable - String extractResourceName(Message unpackedResource) { + protected String extractResourceName(Message unpackedResource) { if (!(unpackedResource instanceof RouteConfiguration)) { return null; } @@ -93,27 +93,27 @@ String extractResourceName(Message unpackedResource) { } @Override - String typeName() { + protected String typeName() { return "RDS"; } @Override - String typeUrl() { + protected String typeUrl() { return ADS_TYPE_URL_RDS; } @Override - boolean isFullStateOfTheWorld() { + protected boolean isFullStateOfTheWorld() { return false; } @Override - Class unpackedClassName() { + protected Class unpackedClassName() { return RouteConfiguration.class; } @Override - RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage) + protected RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage) throws ResourceInvalidException { if (!(unpackedMessage instanceof RouteConfiguration)) { throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass()); diff --git a/xds/src/test/java/io/grpc/xds/LoadBalancerConfigFactoryTest.java b/xds/src/test/java/io/grpc/xds/LoadBalancerConfigFactoryTest.java index fe500105bc6..00e1ab686a6 100644 --- a/xds/src/test/java/io/grpc/xds/LoadBalancerConfigFactoryTest.java +++ b/xds/src/test/java/io/grpc/xds/LoadBalancerConfigFactoryTest.java @@ -52,7 +52,7 @@ import io.grpc.internal.JsonUtil; import io.grpc.internal.ServiceConfigUtil; import io.grpc.internal.ServiceConfigUtil.LbConfig; -import io.grpc.xds.XdsClientImpl.ResourceInvalidException; +import io.grpc.xds.XdsResourceType.ResourceInvalidException; import java.util.List; import org.junit.After; import org.junit.Test; diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java b/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java index b73240490c5..66fbb7d1ed5 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java @@ -134,8 +134,8 @@ import io.grpc.xds.VirtualHost.Route.RouteMatch; import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher; import io.grpc.xds.WeightedRoundRobinLoadBalancer.WeightedRoundRobinLoadBalancerConfig; -import io.grpc.xds.XdsClientImpl.ResourceInvalidException; import io.grpc.xds.XdsClusterResource.CdsUpdate; +import io.grpc.xds.XdsResourceType.ResourceInvalidException; import io.grpc.xds.XdsResourceType.StructOrError; import io.grpc.xds.internal.Matchers; import io.grpc.xds.internal.Matchers.FractionMatcher; diff --git a/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java b/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java index 4c3e05d8f1b..06f4dd9a625 100644 --- a/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java +++ b/xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java @@ -91,12 +91,12 @@ import io.grpc.xds.XdsClient.ResourceMetadata.UpdateFailureState; import io.grpc.xds.XdsClient.ResourceUpdate; import io.grpc.xds.XdsClient.ResourceWatcher; -import io.grpc.xds.XdsClientImpl.ResourceInvalidException; import io.grpc.xds.XdsClientImpl.XdsChannelFactory; 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.XdsResourceType.ResourceInvalidException; import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate; import io.grpc.xds.internal.security.CommonTlsContextTestsUtil; import java.io.IOException; @@ -2242,7 +2242,7 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() { // The response NACKed with errors indicating indices of the failed resources. String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: " + "Cluster cluster.googleapis.com: malformed UpstreamTlsContext: " - + "io.grpc.xds.XdsClientImpl$ResourceInvalidException: " + + "io.grpc.xds.XdsResourceType$ResourceInvalidException: " + "ca_certificate_provider_instance is required in upstream-tls-context"; call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg)); verify(cdsResourceWatcher).onError(errorCaptor.capture()); @@ -2349,7 +2349,7 @@ public void cdsResponseWithInvalidOutlierDetectionNacks() { String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: " + "Cluster cluster.googleapis.com: malformed outlier_detection: " - + "io.grpc.xds.XdsClientImpl$ResourceInvalidException: outlier_detection " + + "io.grpc.xds.XdsResourceType$ResourceInvalidException: outlier_detection " + "max_ejection_percent is > 100"; call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg)); verify(cdsResourceWatcher).onError(errorCaptor.capture());