Skip to content

Commit 6d96e65

Browse files
authoredJan 25, 2024
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.
1 parent 3e8e56f commit 6d96e65

11 files changed

+61
-61
lines changed
 

‎xds/src/main/java/io/grpc/xds/LoadBalancerConfigFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@
3939
import io.grpc.LoadBalancerRegistry;
4040
import io.grpc.internal.JsonParser;
4141
import io.grpc.xds.LoadBalancerConfigFactory.LoadBalancingPolicyConverter.MaxRecursionReachedException;
42-
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
4342
import io.grpc.xds.XdsLogger.XdsLogLevel;
43+
import io.grpc.xds.XdsResourceType.ResourceInvalidException;
4444
import java.io.IOException;
4545
import java.util.Map;
4646

‎xds/src/main/java/io/grpc/xds/XdsClient.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
* protocols (e.g., LDS, RDS, VHDS, CDS and EDS) over a single channel. Watch-based interfaces
4848
* are provided for each set of data needed by gRPC.
4949
*/
50-
abstract class XdsClient {
50+
public abstract class XdsClient {
5151

5252
static boolean isResourceNameValid(String resourceName, String typeUrl) {
5353
checkNotNull(resourceName, "resourceName");
@@ -110,8 +110,7 @@ static String percentEncodePath(String input) {
110110
return Joiner.on('/').join(encodedSegs);
111111
}
112112

113-
interface ResourceUpdate {
114-
}
113+
public interface ResourceUpdate {}
115114

116115
/**
117116
* Watcher interface for a single requested xDS resource.

‎xds/src/main/java/io/grpc/xds/XdsClientImpl.java

-12
Original file line numberDiff line numberDiff line change
@@ -748,18 +748,6 @@ private void notifyWatcher(ResourceWatcher<T> watcher, T update) {
748748
}
749749
}
750750

751-
static final class ResourceInvalidException extends Exception {
752-
private static final long serialVersionUID = 0L;
753-
754-
ResourceInvalidException(String message) {
755-
super(message, null, false, false);
756-
}
757-
758-
ResourceInvalidException(String message, Throwable cause) {
759-
super(cause != null ? message + ": " + cause.getMessage() : message, cause, false, false);
760-
}
761-
}
762-
763751
abstract static class XdsChannelFactory {
764752
static final XdsChannelFactory DEFAULT_XDS_CHANNEL_FACTORY = new XdsChannelFactory() {
765753
@Override

‎xds/src/main/java/io/grpc/xds/XdsClusterResource.java

+7-8
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
4343
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
4444
import io.grpc.xds.XdsClient.ResourceUpdate;
45-
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
4645
import io.grpc.xds.XdsClusterResource.CdsUpdate;
46+
import io.grpc.xds.XdsResourceType.ResourceInvalidException;
4747
import java.util.List;
4848
import java.util.Locale;
4949
import java.util.Set;
@@ -65,37 +65,36 @@ public static XdsClusterResource getInstance() {
6565

6666
@Override
6767
@Nullable
68-
String extractResourceName(Message unpackedResource) {
68+
protected String extractResourceName(Message unpackedResource) {
6969
if (!(unpackedResource instanceof Cluster)) {
7070
return null;
7171
}
7272
return ((Cluster) unpackedResource).getName();
7373
}
7474

7575
@Override
76-
String typeName() {
76+
protected String typeName() {
7777
return "CDS";
7878
}
7979

8080
@Override
81-
String typeUrl() {
81+
protected String typeUrl() {
8282
return ADS_TYPE_URL_CDS;
8383
}
8484

8585
@Override
86-
boolean isFullStateOfTheWorld() {
86+
protected boolean isFullStateOfTheWorld() {
8787
return true;
8888
}
8989

9090
@Override
9191
@SuppressWarnings("unchecked")
92-
Class<Cluster> unpackedClassName() {
92+
protected Class<Cluster> unpackedClassName() {
9393
return Cluster.class;
9494
}
9595

9696
@Override
97-
CdsUpdate doParse(Args args, Message unpackedMessage)
98-
throws ResourceInvalidException {
97+
protected CdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException {
9998
if (!(unpackedMessage instanceof Cluster)) {
10099
throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
101100
}

‎xds/src/main/java/io/grpc/xds/XdsEndpointResource.java

+7-8
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import io.grpc.xds.Endpoints.DropOverload;
2929
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
3030
import io.grpc.xds.XdsClient.ResourceUpdate;
31-
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
3231
import io.grpc.xds.XdsEndpointResource.EdsUpdate;
32+
import io.grpc.xds.XdsResourceType.ResourceInvalidException;
3333
import java.net.InetSocketAddress;
3434
import java.util.ArrayList;
3535
import java.util.Collections;
@@ -54,36 +54,35 @@ public static XdsEndpointResource getInstance() {
5454

5555
@Override
5656
@Nullable
57-
String extractResourceName(Message unpackedResource) {
57+
protected String extractResourceName(Message unpackedResource) {
5858
if (!(unpackedResource instanceof ClusterLoadAssignment)) {
5959
return null;
6060
}
6161
return ((ClusterLoadAssignment) unpackedResource).getClusterName();
6262
}
6363

6464
@Override
65-
String typeName() {
65+
protected String typeName() {
6666
return "EDS";
6767
}
6868

6969
@Override
70-
String typeUrl() {
70+
protected String typeUrl() {
7171
return ADS_TYPE_URL_EDS;
7272
}
7373

7474
@Override
75-
boolean isFullStateOfTheWorld() {
75+
protected boolean isFullStateOfTheWorld() {
7676
return false;
7777
}
7878

7979
@Override
80-
Class<ClusterLoadAssignment> unpackedClassName() {
80+
protected Class<ClusterLoadAssignment> unpackedClassName() {
8181
return ClusterLoadAssignment.class;
8282
}
8383

8484
@Override
85-
EdsUpdate doParse(Args args, Message unpackedMessage)
86-
throws ResourceInvalidException {
85+
protected EdsUpdate doParse(Args args, Message unpackedMessage) throws ResourceInvalidException {
8786
if (!(unpackedMessage instanceof ClusterLoadAssignment)) {
8887
throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());
8988
}

‎xds/src/main/java/io/grpc/xds/XdsListenerResource.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818

1919
import static com.google.common.base.Preconditions.checkNotNull;
2020
import static io.grpc.xds.XdsClient.ResourceUpdate;
21-
import static io.grpc.xds.XdsClientImpl.ResourceInvalidException;
2221
import static io.grpc.xds.XdsClusterResource.validateCommonTlsContext;
22+
import static io.grpc.xds.XdsResourceType.ResourceInvalidException;
2323
import static io.grpc.xds.XdsRouteConfigureResource.extractVirtualHosts;
2424

2525
import com.github.udpa.udpa.type.v1.TypedStruct;
@@ -66,35 +66,35 @@ public static XdsListenerResource getInstance() {
6666

6767
@Override
6868
@Nullable
69-
String extractResourceName(Message unpackedResource) {
69+
protected String extractResourceName(Message unpackedResource) {
7070
if (!(unpackedResource instanceof Listener)) {
7171
return null;
7272
}
7373
return ((Listener) unpackedResource).getName();
7474
}
7575

7676
@Override
77-
String typeName() {
77+
protected String typeName() {
7878
return "LDS";
7979
}
8080

8181
@Override
82-
Class<Listener> unpackedClassName() {
82+
protected Class<Listener> unpackedClassName() {
8383
return Listener.class;
8484
}
8585

8686
@Override
87-
String typeUrl() {
87+
protected String typeUrl() {
8888
return ADS_TYPE_URL_LDS;
8989
}
9090

9191
@Override
92-
boolean isFullStateOfTheWorld() {
92+
protected boolean isFullStateOfTheWorld() {
9393
return true;
9494
}
9595

9696
@Override
97-
LdsUpdate doParse(Args args, Message unpackedMessage)
97+
protected LdsUpdate doParse(Args args, Message unpackedMessage)
9898
throws ResourceInvalidException {
9999
if (!(unpackedMessage instanceof Listener)) {
100100
throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());

‎xds/src/main/java/io/grpc/xds/XdsResourceType.java

+25-10
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@
2121
import static io.grpc.xds.XdsClient.ResourceUpdate;
2222
import static io.grpc.xds.XdsClient.canonifyResourceName;
2323
import static io.grpc.xds.XdsClient.isResourceNameValid;
24-
import static io.grpc.xds.XdsClientImpl.ResourceInvalidException;
2524

2625
import com.google.common.annotations.VisibleForTesting;
2726
import com.google.common.base.Strings;
2827
import com.google.protobuf.Any;
2928
import com.google.protobuf.InvalidProtocolBufferException;
3029
import com.google.protobuf.Message;
3130
import io.envoyproxy.envoy.service.discovery.v3.Resource;
31+
import io.grpc.ExperimentalApi;
3232
import io.grpc.LoadBalancerRegistry;
33+
import io.grpc.xds.Bootstrapper.ServerInfo;
34+
import io.grpc.xds.XdsClient.ResourceUpdate;
3335
import java.util.ArrayList;
3436
import java.util.HashMap;
3537
import java.util.HashSet;
@@ -38,7 +40,8 @@
3840
import java.util.Set;
3941
import javax.annotation.Nullable;
4042

41-
abstract class XdsResourceType<T extends ResourceUpdate> {
43+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/10847")
44+
public abstract class XdsResourceType<T extends ResourceUpdate> {
4245
static final String TYPE_URL_RESOURCE =
4346
"type.googleapis.com/envoy.service.discovery.v3.Resource";
4447
static final String TRANSPORT_SOCKET_NAME_TLS = "envoy.transport_sockets.tls";
@@ -68,22 +71,22 @@ abstract class XdsResourceType<T extends ResourceUpdate> {
6871
"type.googleapis.com/xds.type.v3.TypedStruct";
6972

7073
@Nullable
71-
abstract String extractResourceName(Message unpackedResource);
74+
protected abstract String extractResourceName(Message unpackedResource);
7275

73-
abstract Class<? extends com.google.protobuf.Message> unpackedClassName();
76+
protected abstract Class<? extends com.google.protobuf.Message> unpackedClassName();
7477

75-
abstract String typeName();
78+
protected abstract String typeName();
7679

77-
abstract String typeUrl();
80+
protected abstract String typeUrl();
7881

7982
// Do not confuse with the SotW approach: it is the mechanism in which the client must specify all
8083
// resource names it is interested in with each request. Different resource types may behave
8184
// differently in this approach. For LDS and CDS resources, the server must return all resources
8285
// that the client has subscribed to in each request. For RDS and EDS, the server may only return
8386
// the resources that need an update.
84-
abstract boolean isFullStateOfTheWorld();
87+
protected abstract boolean isFullStateOfTheWorld();
8588

86-
static class Args {
89+
public static class Args {
8790
final ServerInfo serverInfo;
8891
final String versionInfo;
8992
final String nonce;
@@ -114,6 +117,18 @@ public Args(ServerInfo serverInfo, String versionInfo, String nonce,
114117
}
115118
}
116119

120+
public static final class ResourceInvalidException extends Exception {
121+
private static final long serialVersionUID = 0L;
122+
123+
public ResourceInvalidException(String message) {
124+
super(message, null, false, false);
125+
}
126+
127+
public ResourceInvalidException(String message, Throwable cause) {
128+
super(cause != null ? message + ": " + cause.getMessage() : message, cause, false, false);
129+
}
130+
}
131+
117132
ValidatedResourceUpdate<T> parse(Args args, List<Any> resources) {
118133
Map<String, ParsedResource<T>> parsedResources = new HashMap<>(resources.size());
119134
Set<String> unpackedResources = new HashSet<>(resources.size());
@@ -147,7 +162,7 @@ ValidatedResourceUpdate<T> parse(Args args, List<Any> resources) {
147162
T resourceUpdate;
148163
try {
149164
resourceUpdate = doParse(args, unpackedMessage);
150-
} catch (XdsClientImpl.ResourceInvalidException e) {
165+
} catch (ResourceInvalidException e) {
151166
errors.add(String.format("%s response %s '%s' validation error: %s",
152167
typeName(), unpackedClassName().getSimpleName(), cname, e.getMessage()));
153168
invalidResources.add(cname);
@@ -162,7 +177,7 @@ ValidatedResourceUpdate<T> parse(Args args, List<Any> resources) {
162177

163178
}
164179

165-
abstract T doParse(Args args, Message unpackedMessage) throws ResourceInvalidException;
180+
protected abstract T doParse(Args args, Message unpackedMessage) throws ResourceInvalidException;
166181

167182
/**
168183
* Helper method to unpack serialized {@link com.google.protobuf.Any} message, while replacing

‎xds/src/main/java/io/grpc/xds/XdsRouteConfigureResource.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
import io.grpc.xds.VirtualHost.Route.RouteMatch;
5050
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
5151
import io.grpc.xds.XdsClient.ResourceUpdate;
52-
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
52+
import io.grpc.xds.XdsResourceType.ResourceInvalidException;
5353
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
5454
import io.grpc.xds.internal.MatcherParser;
5555
import io.grpc.xds.internal.Matchers;
@@ -85,35 +85,35 @@ public static XdsRouteConfigureResource getInstance() {
8585

8686
@Override
8787
@Nullable
88-
String extractResourceName(Message unpackedResource) {
88+
protected String extractResourceName(Message unpackedResource) {
8989
if (!(unpackedResource instanceof RouteConfiguration)) {
9090
return null;
9191
}
9292
return ((RouteConfiguration) unpackedResource).getName();
9393
}
9494

9595
@Override
96-
String typeName() {
96+
protected String typeName() {
9797
return "RDS";
9898
}
9999

100100
@Override
101-
String typeUrl() {
101+
protected String typeUrl() {
102102
return ADS_TYPE_URL_RDS;
103103
}
104104

105105
@Override
106-
boolean isFullStateOfTheWorld() {
106+
protected boolean isFullStateOfTheWorld() {
107107
return false;
108108
}
109109

110110
@Override
111-
Class<RouteConfiguration> unpackedClassName() {
111+
protected Class<RouteConfiguration> unpackedClassName() {
112112
return RouteConfiguration.class;
113113
}
114114

115115
@Override
116-
RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage)
116+
protected RdsUpdate doParse(XdsResourceType.Args args, Message unpackedMessage)
117117
throws ResourceInvalidException {
118118
if (!(unpackedMessage instanceof RouteConfiguration)) {
119119
throw new ResourceInvalidException("Invalid message type: " + unpackedMessage.getClass());

‎xds/src/test/java/io/grpc/xds/LoadBalancerConfigFactoryTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
import io.grpc.internal.JsonUtil;
5353
import io.grpc.internal.ServiceConfigUtil;
5454
import io.grpc.internal.ServiceConfigUtil.LbConfig;
55-
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
55+
import io.grpc.xds.XdsResourceType.ResourceInvalidException;
5656
import java.util.List;
5757
import org.junit.After;
5858
import org.junit.Test;

‎xds/src/test/java/io/grpc/xds/XdsClientImplDataTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,8 @@
134134
import io.grpc.xds.VirtualHost.Route.RouteMatch;
135135
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
136136
import io.grpc.xds.WeightedRoundRobinLoadBalancer.WeightedRoundRobinLoadBalancerConfig;
137-
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
138137
import io.grpc.xds.XdsClusterResource.CdsUpdate;
138+
import io.grpc.xds.XdsResourceType.ResourceInvalidException;
139139
import io.grpc.xds.XdsResourceType.StructOrError;
140140
import io.grpc.xds.internal.Matchers;
141141
import io.grpc.xds.internal.Matchers.FractionMatcher;

‎xds/src/test/java/io/grpc/xds/XdsClientImplTestBase.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@
9191
import io.grpc.xds.XdsClient.ResourceMetadata.UpdateFailureState;
9292
import io.grpc.xds.XdsClient.ResourceUpdate;
9393
import io.grpc.xds.XdsClient.ResourceWatcher;
94-
import io.grpc.xds.XdsClientImpl.ResourceInvalidException;
9594
import io.grpc.xds.XdsClientImpl.XdsChannelFactory;
9695
import io.grpc.xds.XdsClusterResource.CdsUpdate;
9796
import io.grpc.xds.XdsClusterResource.CdsUpdate.ClusterType;
9897
import io.grpc.xds.XdsEndpointResource.EdsUpdate;
9998
import io.grpc.xds.XdsListenerResource.LdsUpdate;
99+
import io.grpc.xds.XdsResourceType.ResourceInvalidException;
100100
import io.grpc.xds.XdsRouteConfigureResource.RdsUpdate;
101101
import io.grpc.xds.internal.security.CommonTlsContextTestsUtil;
102102
import java.io.IOException;
@@ -2242,7 +2242,7 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() {
22422242
// The response NACKed with errors indicating indices of the failed resources.
22432243
String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: "
22442244
+ "Cluster cluster.googleapis.com: malformed UpstreamTlsContext: "
2245-
+ "io.grpc.xds.XdsClientImpl$ResourceInvalidException: "
2245+
+ "io.grpc.xds.XdsResourceType$ResourceInvalidException: "
22462246
+ "ca_certificate_provider_instance is required in upstream-tls-context";
22472247
call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg));
22482248
verify(cdsResourceWatcher).onError(errorCaptor.capture());
@@ -2349,7 +2349,7 @@ public void cdsResponseWithInvalidOutlierDetectionNacks() {
23492349

23502350
String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: "
23512351
+ "Cluster cluster.googleapis.com: malformed outlier_detection: "
2352-
+ "io.grpc.xds.XdsClientImpl$ResourceInvalidException: outlier_detection "
2352+
+ "io.grpc.xds.XdsResourceType$ResourceInvalidException: outlier_detection "
23532353
+ "max_ejection_percent is > 100";
23542354
call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg));
23552355
verify(cdsResourceWatcher).onError(errorCaptor.capture());

0 commit comments

Comments
 (0)
Please sign in to comment.