diff --git a/xds/src/main/java/io/grpc/xds/Bootstrapper.java b/xds/src/main/java/io/grpc/xds/Bootstrapper.java
index 862f8691080..57cdc6f324b 100644
--- a/xds/src/main/java/io/grpc/xds/Bootstrapper.java
+++ b/xds/src/main/java/io/grpc/xds/Bootstrapper.java
@@ -16,6 +16,8 @@
package io.grpc.xds;
+import static com.google.common.base.Preconditions.checkArgument;
+
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
@@ -33,6 +35,8 @@
@Internal
public abstract class Bootstrapper {
+ static final String XDSTP_SCHEME = "xdstp:";
+
/**
* Returns system-loaded bootstrap configuration.
*/
@@ -104,12 +108,13 @@ abstract static class AuthorityInfo {
*
If the same server is listed in multiple authorities, the entries will be de-duped (i.e.,
* resources for both authorities will be fetched on the same ADS stream).
*
- *
If empty, the top-level server list {@link BootstrapInfo#servers()} will be used.
+ *
Defaults to the top-level server list {@link BootstrapInfo#servers()}. Must not be empty.
*/
abstract ImmutableList xdsServers();
static AuthorityInfo create(
String clientListenerResourceNameTemplate, List xdsServers) {
+ checkArgument(!xdsServers.isEmpty(), "xdsServers must not be empty");
return new AutoValue_Bootstrapper_AuthorityInfo(
clientListenerResourceNameTemplate, ImmutableList.copyOf(xdsServers));
}
@@ -121,7 +126,7 @@ static AuthorityInfo create(
@AutoValue
@Internal
public abstract static class BootstrapInfo {
- /** Returns the list of xDS servers to be connected to. */
+ /** Returns the list of xDS servers to be connected to. Must not be empty. */
abstract ImmutableList servers();
/** Returns the node identifier to be included in xDS requests. */
diff --git a/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java b/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java
index 35ff5e55a42..23b14357096 100644
--- a/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java
+++ b/xds/src/main/java/io/grpc/xds/BootstrapperImpl.java
@@ -229,7 +229,7 @@ BootstrapInfo bootstrap(Map rawData) throws XdsInitializationExceptio
JsonUtil.getString(rawAuthority, "client_listener_resource_name_template");
logger.log(
XdsLogLevel.INFO, "client_listener_resource_name_template: {0}", clientListnerTemplate);
- String prefix = "xdstp://" + authorityName + "/";
+ String prefix = XDSTP_SCHEME + "//" + authorityName + "/";
if (clientListnerTemplate == null) {
clientListnerTemplate = prefix + "envoy.config.listener.v3.Listener/%s";
} else if (!clientListnerTemplate.startsWith(prefix)) {
diff --git a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java
index 0a11ad47288..2b676553838 100644
--- a/xds/src/main/java/io/grpc/xds/ClientXdsClient.java
+++ b/xds/src/main/java/io/grpc/xds/ClientXdsClient.java
@@ -18,6 +18,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.xds.Bootstrapper.XDSTP_SCHEME;
import com.github.udpa.udpa.type.v1.TypedStruct;
import com.google.common.annotations.VisibleForTesting;
@@ -70,6 +71,7 @@
import io.grpc.internal.BackoffPolicy;
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.Endpoints.DropOverload;
import io.grpc.xds.Endpoints.LbEndpoint;
@@ -98,6 +100,7 @@
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.Arrays;
@@ -2267,8 +2270,12 @@ private final class ResourceSubscriber {
ResourceSubscriber(ResourceType type, String resource) {
syncContext.throwIfNotInThisSynchronizationContext();
this.type = type;
+ // TODO(zdapeng): Validate authority in resource URI for new-style resource name
+ // when parsing XDS response.
+ // TODO(zdapeng): Canonicalize the resource name by sorting the context params in normal
+ // lexicographic order.
this.resource = resource;
- this.serverInfo = getServerInfo();
+ this.serverInfo = getServerInfo(resource);
// Initialize metadata in UNKNOWN state to cover the case when resource subscriber,
// is created but not yet requested because the client is in backoff.
this.metadata = ResourceMetadata.newResourceMetadataUnknown();
@@ -2280,8 +2287,16 @@ private final class ResourceSubscriber {
restartTimer();
}
- // TODO(zdapeng): add resourceName arg and support xdstp:// resources
- private ServerInfo getServerInfo() {
+ private ServerInfo getServerInfo(String resource) {
+ if (resource.startsWith(XDSTP_SCHEME)) {
+ URI uri = URI.create(resource);
+ String authority = uri.getAuthority();
+ if (authority == null) {
+ authority = "";
+ }
+ AuthorityInfo authorityInfo = bootstrapInfo.authorities().get(authority);
+ return authorityInfo.xdsServers().get(0);
+ }
return bootstrapInfo.servers().get(0); // use first server
}
diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java
index 4cd52c8b3f9..b6b66327525 100644
--- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java
+++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java
@@ -18,12 +18,14 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.xds.Bootstrapper.XDSTP_SCHEME;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
+import com.google.common.net.UrlEscapers;
import com.google.gson.Gson;
import com.google.protobuf.util.Durations;
import io.grpc.Attributes;
@@ -45,6 +47,8 @@
import io.grpc.SynchronizationContext;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.ObjectPool;
+import io.grpc.xds.Bootstrapper.AuthorityInfo;
+import io.grpc.xds.Bootstrapper.BootstrapInfo;
import io.grpc.xds.Filter.ClientInterceptorBuilder;
import io.grpc.xds.Filter.FilterConfig;
import io.grpc.xds.Filter.NamedFilterConfig;
@@ -101,7 +105,9 @@ final class XdsNameResolver extends NameResolver {
private final InternalLogId logId;
private final XdsLogger logger;
- private final String authority;
+ @Nullable
+ private final String targetAuthority;
+ private final String serviceAuthority;
private final ServiceConfigParser serviceConfigParser;
private final SynchronizationContext syncContext;
private final ScheduledExecutorService scheduler;
@@ -120,20 +126,23 @@ final class XdsNameResolver extends NameResolver {
private CallCounterProvider callCounterProvider;
private ResolveState resolveState;
- XdsNameResolver(String name, ServiceConfigParser serviceConfigParser,
+ XdsNameResolver(
+ @Nullable String targetAuthority, String name, ServiceConfigParser serviceConfigParser,
SynchronizationContext syncContext, ScheduledExecutorService scheduler,
@Nullable Map bootstrapOverride) {
- this(name, serviceConfigParser, syncContext, scheduler,
+ this(targetAuthority, name, serviceConfigParser, syncContext, scheduler,
SharedXdsClientPoolProvider.getDefaultProvider(), ThreadSafeRandomImpl.instance,
FilterRegistry.getDefaultRegistry(), bootstrapOverride);
}
@VisibleForTesting
- XdsNameResolver(String name, ServiceConfigParser serviceConfigParser,
+ XdsNameResolver(
+ @Nullable String targetAuthority, String name, ServiceConfigParser serviceConfigParser,
SynchronizationContext syncContext, ScheduledExecutorService scheduler,
XdsClientPoolFactory xdsClientPoolFactory, ThreadSafeRandom random,
FilterRegistry filterRegistry, @Nullable Map bootstrapOverride) {
- authority = GrpcUtil.checkAuthority(checkNotNull(name, "name"));
+ this.targetAuthority = targetAuthority;
+ serviceAuthority = GrpcUtil.checkAuthority(checkNotNull(name, "name"));
this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser");
this.syncContext = checkNotNull(syncContext, "syncContext");
this.scheduler = checkNotNull(scheduler, "scheduler");
@@ -149,7 +158,7 @@ final class XdsNameResolver extends NameResolver {
@Override
public String getServiceAuthority() {
- return authority;
+ return serviceAuthority;
}
@Override
@@ -163,11 +172,33 @@ public void start(Listener2 listener) {
return;
}
xdsClient = xdsClientPool.getObject();
+ BootstrapInfo bootstrapInfo = xdsClient.getBootstrapInfo();
+ String listenerNameTemplate;
+ if (targetAuthority == null) {
+ listenerNameTemplate = bootstrapInfo.clientDefaultListenerResourceNameTemplate();
+ } else {
+ AuthorityInfo authorityInfo = bootstrapInfo.authorities().get(targetAuthority);
+ if (authorityInfo == null) {
+ listener.onError(Status.INVALID_ARGUMENT.withDescription(
+ "invalid target URI: target authority not found in the bootstrap"));
+ return;
+ }
+ listenerNameTemplate = authorityInfo.clientListenerResourceNameTemplate();
+ }
+ String replacement = serviceAuthority;
+ if (listenerNameTemplate.startsWith(XDSTP_SCHEME)) {
+ replacement = UrlEscapers.urlFragmentEscaper().escape(replacement);
+ }
+ String ldsResourceName = expandPercentS(listenerNameTemplate, replacement);
callCounterProvider = SharedCallCounterMap.getInstance();
- resolveState = new ResolveState();
+ resolveState = new ResolveState(ldsResourceName);
resolveState.start();
}
+ private static String expandPercentS(String template, String replacement) {
+ return template.replace("%s", replacement);
+ }
+
@Override
public void shutdown() {
logger.log(XdsLogLevel.INFO, "Shutdown");
@@ -624,12 +655,17 @@ private class ResolveState implements LdsResourceWatcher {
.setServiceConfig(emptyServiceConfig)
// let channel take action for no config selector
.build();
+ private final String ldsResourceName;
private boolean stopped;
@Nullable
private Set existingClusters; // clusters to which new requests can be routed
@Nullable
private RouteDiscoveryState routeDiscoveryState;
+ ResolveState(String ldsResourceName) {
+ this.ldsResourceName = ldsResourceName;
+ }
+
@Override
public void onChanged(final LdsUpdate update) {
syncContext.execute(new Runnable() {
@@ -686,23 +722,23 @@ public void run() {
}
private void start() {
- logger.log(XdsLogLevel.INFO, "Start watching LDS resource {0}", authority);
- xdsClient.watchLdsResource(authority, this);
+ logger.log(XdsLogLevel.INFO, "Start watching LDS resource {0}", ldsResourceName);
+ xdsClient.watchLdsResource(ldsResourceName, this);
}
private void stop() {
- logger.log(XdsLogLevel.INFO, "Stop watching LDS resource {0}", authority);
+ logger.log(XdsLogLevel.INFO, "Stop watching LDS resource {0}", ldsResourceName);
stopped = true;
cleanUpRouteDiscoveryState();
- xdsClient.cancelLdsResourceWatch(authority, this);
+ xdsClient.cancelLdsResourceWatch(ldsResourceName, this);
}
private void updateRoutes(List virtualHosts, long httpMaxStreamDurationNano,
@Nullable List filterConfigs) {
- VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, authority);
+ VirtualHost virtualHost = findVirtualHostForHostName(virtualHosts, ldsResourceName);
if (virtualHost == null) {
logger.log(XdsLogLevel.WARNING,
- "Failed to find virtual host matching hostname {0}", authority);
+ "Failed to find virtual host matching hostname {0}", ldsResourceName);
cleanUpRoutes();
return;
}
diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java
index 03d88a9752e..a02e27c37c7 100644
--- a/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java
+++ b/xds/src/main/java/io/grpc/xds/XdsNameResolverProvider.java
@@ -74,9 +74,10 @@ public XdsNameResolver newNameResolver(URI targetUri, Args args) {
targetPath,
targetUri);
String name = targetPath.substring(1);
- return new XdsNameResolver(name, args.getServiceConfigParser(),
- args.getSynchronizationContext(), args.getScheduledExecutorService(),
- bootstrapOverride);
+ return new XdsNameResolver(
+ targetUri.getAuthority(), name, args.getServiceConfigParser(),
+ args.getSynchronizationContext(), args.getScheduledExecutorService(),
+ bootstrapOverride);
}
return null;
}
diff --git a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java
index 6eefcf63411..0946f621487 100644
--- a/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java
+++ b/xds/src/main/java/io/grpc/xds/XdsServerWrapper.java
@@ -18,11 +18,13 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.xds.Bootstrapper.XDSTP_SCHEME;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.SettableFuture;
import io.grpc.Attributes;
import io.grpc.InternalServerInterceptors;
@@ -192,7 +194,11 @@ private void internalStart() {
xdsClient = xdsClientPool.returnObject(xdsClient);
return;
}
- discoveryState = new DiscoveryState(listenerTemplate.replaceAll("%s", listenerAddress));
+ String replacement = listenerAddress;
+ if (listenerTemplate.startsWith(XDSTP_SCHEME)) {
+ replacement = UrlEscapers.urlFragmentEscaper().escape(replacement);
+ }
+ discoveryState = new DiscoveryState(listenerTemplate.replaceAll("%s", replacement));
}
@Override
diff --git a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java
index 9d0c735ba95..6fdf5e1b811 100644
--- a/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java
+++ b/xds/src/test/java/io/grpc/xds/ClientXdsClientTestBase.java
@@ -59,6 +59,7 @@
import io.grpc.internal.TimeProvider;
import io.grpc.testing.GrpcCleanupRule;
import io.grpc.xds.AbstractXdsClient.ResourceType;
+import io.grpc.xds.Bootstrapper.AuthorityInfo;
import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
import io.grpc.xds.Bootstrapper.ServerInfo;
import io.grpc.xds.ClientXdsClient.XdsChannelFactory;
@@ -114,6 +115,8 @@
@RunWith(JUnit4.class)
public abstract class ClientXdsClientTestBase {
private static final String SERVER_URI = "trafficdirector.googleapis.com";
+ private static final String SERVER_URI_CUSTOME_AUTHORITY = "trafficdirector2.googleapis.com";
+ private static final String SERVER_URI_EMPTY_AUTHORITY = "trafficdirector3.googleapis.com";
private static final String LDS_RESOURCE = "listener.googleapis.com";
private static final String RDS_RESOURCE = "route-configuration.googleapis.com";
private static final String CDS_RESOURCE = "cluster.googleapis.com";
@@ -250,6 +253,8 @@ public long currentTimeNanos() {
private TlsContextManager tlsContextManager;
private ManagedChannel channel;
+ private ManagedChannel channelForCustomAuthority;
+ private ManagedChannel channelForEmptyAuthority;
private ClientXdsClient xdsClient;
private boolean originalEnableFaultInjection;
private boolean originalEnableRbac;
@@ -281,7 +286,24 @@ public void setUp() throws IOException {
XdsChannelFactory xdsChannelFactory = new XdsChannelFactory() {
@Override
ManagedChannel create(ServerInfo serverInfo) {
- return channel;
+ if (serverInfo.target().equals(SERVER_URI)) {
+ return channel;
+ }
+ if (serverInfo.target().equals(SERVER_URI_CUSTOME_AUTHORITY)) {
+ if (channelForCustomAuthority == null) {
+ channelForCustomAuthority = cleanupRule.register(
+ InProcessChannelBuilder.forName(serverName).directExecutor().build());
+ }
+ return channelForCustomAuthority;
+ }
+ if (serverInfo.target().equals(SERVER_URI_EMPTY_AUTHORITY)) {
+ if (channelForEmptyAuthority == null) {
+ channelForEmptyAuthority = cleanupRule.register(
+ InProcessChannelBuilder.forName(serverName).directExecutor().build());
+ }
+ return channelForEmptyAuthority;
+ }
+ throw new IllegalArgumentException("Can not create channel for " + serverInfo);
}
};
@@ -290,6 +312,17 @@ ManagedChannel create(ServerInfo serverInfo) {
.servers(Arrays.asList(
Bootstrapper.ServerInfo.create(SERVER_URI, CHANNEL_CREDENTIALS, useProtocolV3())))
.node(EnvoyProtoData.Node.newBuilder().build())
+ .authorities(ImmutableMap.of(
+ "authority.xds.com",
+ AuthorityInfo.create(
+ "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/%s",
+ ImmutableList.of(Bootstrapper.ServerInfo.create(
+ SERVER_URI_CUSTOME_AUTHORITY, CHANNEL_CREDENTIALS, useProtocolV3()))),
+ "",
+ AuthorityInfo.create(
+ "xdstp:///envoy.config.listener.v3.Listener/%s",
+ ImmutableList.of(Bootstrapper.ServerInfo.create(
+ SERVER_URI_EMPTY_AUTHORITY, CHANNEL_CREDENTIALS, useProtocolV3())))))
.certProviders(ImmutableMap.of("cert-instance-name",
CertificateProviderInfo.create("file-watcher", ImmutableMap.of())))
.build();
@@ -706,6 +739,46 @@ public void ldsResourceUpdated() {
.isEqualTo(RDS_RESOURCE);
verifyResourceMetadataAcked(LDS, LDS_RESOURCE, testListenerRds, VERSION_2, TIME_INCREMENT * 2);
verifySubscribedResourcesMetadataSizes(1, 0, 0, 0);
+ assertThat(channelForCustomAuthority).isNull();
+ assertThat(channelForEmptyAuthority).isNull();
+ }
+
+ @Test
+ public void ldsResourceUpdated_withXdstpResourceName() {
+ String ldsResourceName =
+ "xdstp://authority.xds.com/envoy.config.listener.v3.Listener/listener1";
+ DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher);
+ assertThat(channelForCustomAuthority).isNotNull();
+ verifyResourceMetadataRequested(LDS, ldsResourceName);
+
+ Any testListenerVhosts = Any.pack(mf.buildListenerWithApiListener(ldsResourceName,
+ mf.buildRouteConfiguration("do not care", mf.buildOpaqueVirtualHosts(VHOST_SIZE))));
+ call.sendResponse(LDS, testListenerVhosts, VERSION_1, "0000");
+ call.verifyRequest(LDS, ldsResourceName, VERSION_1, "0000", NODE);
+ verify(ldsResourceWatcher).onChanged(ldsUpdateCaptor.capture());
+ assertThat(ldsUpdateCaptor.getValue().httpConnectionManager().virtualHosts())
+ .hasSize(VHOST_SIZE);
+ verifyResourceMetadataAcked(
+ LDS, ldsResourceName, testListenerVhosts, VERSION_1, TIME_INCREMENT);
+ }
+
+ @Test
+ public void ldsResourceUpdated_withXdstpResourceName_withEmptyAuthority() {
+ String ldsResourceName =
+ "xdstp:///envoy.config.listener.v3.Listener/listener1";
+ DiscoveryRpcCall call = startResourceWatcher(LDS, ldsResourceName, ldsResourceWatcher);
+ assertThat(channelForEmptyAuthority).isNotNull();
+ verifyResourceMetadataRequested(LDS, ldsResourceName);
+
+ Any testListenerVhosts = Any.pack(mf.buildListenerWithApiListener(ldsResourceName,
+ mf.buildRouteConfiguration("do not care", mf.buildOpaqueVirtualHosts(VHOST_SIZE))));
+ call.sendResponse(LDS, testListenerVhosts, VERSION_1, "0000");
+ call.verifyRequest(LDS, ldsResourceName, VERSION_1, "0000", NODE);
+ verify(ldsResourceWatcher).onChanged(ldsUpdateCaptor.capture());
+ assertThat(ldsUpdateCaptor.getValue().httpConnectionManager().virtualHosts())
+ .hasSize(VHOST_SIZE);
+ verifyResourceMetadataAcked(
+ LDS, ldsResourceName, testListenerVhosts, VERSION_1, TIME_INCREMENT);
}
@Test
diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java
index babaa2b3034..7968b7fb366 100644
--- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java
+++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -45,6 +46,7 @@
import io.grpc.ClientInterceptor;
import io.grpc.ClientInterceptors;
import io.grpc.Deadline;
+import io.grpc.InsecureChannelCredentials;
import io.grpc.InternalConfigSelector;
import io.grpc.InternalConfigSelector.Result;
import io.grpc.Metadata;
@@ -67,6 +69,10 @@
import io.grpc.internal.PickSubchannelArgsImpl;
import io.grpc.internal.ScParser;
import io.grpc.testing.TestMethodDescriptors;
+import io.grpc.xds.Bootstrapper.AuthorityInfo;
+import io.grpc.xds.Bootstrapper.BootstrapInfo;
+import io.grpc.xds.Bootstrapper.ServerInfo;
+import io.grpc.xds.EnvoyProtoData.Node;
import io.grpc.xds.FaultConfig.FaultAbort;
import io.grpc.xds.FaultConfig.FaultDelay;
import io.grpc.xds.Filter.FilterConfig;
@@ -132,6 +138,12 @@ public ConfigOrError parseServiceConfig(Map rawServiceConfig) {
private final CallInfo call1 = new CallInfo("HelloService", "hi");
private final CallInfo call2 = new CallInfo("GreetService", "bye");
private final TestChannel channel = new TestChannel();
+ private BootstrapInfo bootstrapInfo = BootstrapInfo.builder()
+ .servers(ImmutableList.of(ServerInfo.create(
+ "td.googleapis.com", InsecureChannelCredentials.create(), true)))
+ .node(Node.newBuilder().build())
+ .build();
+ private String expectedLdsResourceName = AUTHORITY;
@Mock
private ThreadSafeRandom mockRandom;
@@ -152,7 +164,7 @@ public void setUp() {
FilterRegistry filterRegistry = FilterRegistry.newRegistry().register(
new FaultFilter(mockRandom, new AtomicLong()),
RouterFilter.INSTANCE);
- resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, scheduler,
+ resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, filterRegistry, null);
}
@@ -185,7 +197,7 @@ public ObjectPool getOrCreate() throws XdsInitializationException {
throw new XdsInitializationException("Fail to read bootstrap file");
}
};
- resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, scheduler,
+ resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener);
verify(mockListener).onError(errorCaptor.capture());
@@ -195,6 +207,79 @@ public ObjectPool getOrCreate() throws XdsInitializationException {
assertThat(error.getCause()).hasMessageThat().isEqualTo("Fail to read bootstrap file");
}
+ @Test
+ public void resolving_withTargetAuthorityNotFound() {
+ resolver = new XdsNameResolver(
+ "notfound.google.com", AUTHORITY, serviceConfigParser, syncContext, scheduler,
+ xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
+ resolver.start(mockListener);
+ verify(mockListener).onError(errorCaptor.capture());
+ Status error = errorCaptor.getValue();
+ assertThat(error.getCode()).isEqualTo(Code.INVALID_ARGUMENT);
+ assertThat(error.getDescription()).isEqualTo(
+ "invalid target URI: target authority not found in the bootstrap");
+ }
+
+ @Test
+ public void resolving_noTargetAuthority_templateWithoutXdstp() {
+ bootstrapInfo = BootstrapInfo.builder()
+ .servers(ImmutableList.of(ServerInfo.create(
+ "td.googleapis.com", InsecureChannelCredentials.create(), true)))
+ .node(Node.newBuilder().build())
+ .clientDefaultListenerResourceNameTemplate("%s/id=1")
+ .build();
+ String serviceAuthority = "[::FFFF:129.144.52.38]:80";
+ expectedLdsResourceName = "[::FFFF:129.144.52.38]:80/id=1";
+ resolver = new XdsNameResolver(
+ null, serviceAuthority, serviceConfigParser, syncContext, scheduler, xdsClientPoolFactory,
+ mockRandom, FilterRegistry.getDefaultRegistry(), null);
+ resolver.start(mockListener);
+ verify(mockListener, never()).onError(any(Status.class));
+ }
+
+ @Test
+ public void resolving_noTargetAuthority_templateWithXdstp() {
+ bootstrapInfo = BootstrapInfo.builder()
+ .servers(ImmutableList.of(ServerInfo.create(
+ "td.googleapis.com", InsecureChannelCredentials.create(), true)))
+ .node(Node.newBuilder().build())
+ .clientDefaultListenerResourceNameTemplate(
+ "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%s?id=1")
+ .build();
+ String serviceAuthority = "[::FFFF:129.144.52.38]:80";
+ expectedLdsResourceName =
+ "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/"
+ + "%5B::FFFF:129.144.52.38%5D:80?id=1";
+ resolver = new XdsNameResolver(
+ null, serviceAuthority, serviceConfigParser, syncContext, scheduler,
+ xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
+ resolver.start(mockListener);
+ verify(mockListener, never()).onError(any(Status.class));
+ }
+
+ @Test
+ public void resolving_targetAuthorityInAuthoritiesMap() {
+ String targetAuthority = "xds.authority.com";
+ String serviceAuthority = "[::FFFF:129.144.52.38]:80";
+ bootstrapInfo = BootstrapInfo.builder()
+ .servers(ImmutableList.of(ServerInfo.create(
+ "td.googleapis.com", InsecureChannelCredentials.create(), true)))
+ .node(Node.newBuilder().build())
+ .authorities(
+ ImmutableMap.of(targetAuthority, AuthorityInfo.create(
+ "xdstp://" + targetAuthority + "/envoy.config.listener.v3.Listener/%s",
+ ImmutableList.of(ServerInfo.create(
+ "td.googleapis.com", InsecureChannelCredentials.create(), true)))))
+ .build();
+ expectedLdsResourceName =
+ "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%5B::FFFF:129.144.52.38%5D:80";
+ resolver = new XdsNameResolver(
+ "xds.authority.com", serviceAuthority, serviceConfigParser, syncContext, scheduler,
+ xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
+ resolver.start(mockListener);
+ verify(mockListener, never()).onError(any(Status.class));
+ }
+
@Test
public void resolving_ldsResourceNotFound() {
resolver.start(mockListener);
@@ -435,7 +520,7 @@ public void resolved_fallbackToHttpMaxStreamDurationAsTimeout() {
public void retryPolicyInPerMethodConfigGeneratedByResolverIsValid() {
ServiceConfigParser realParser = new ScParser(
true, 5, 5, new AutoConfiguredLoadBalancerFactory("pick-first"));
- resolver = new XdsNameResolver(AUTHORITY, realParser, syncContext, scheduler,
+ resolver = new XdsNameResolver(null, AUTHORITY, realParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener);
FakeXdsClient xdsClient = (FakeXdsClient) resolver.getXdsClient();
@@ -638,7 +723,7 @@ public void resolved_rpcHashingByChannelId() {
// A different resolver/Channel.
resolver.shutdown();
reset(mockListener);
- resolver = new XdsNameResolver(AUTHORITY, serviceConfigParser, syncContext, scheduler,
+ resolver = new XdsNameResolver(null, AUTHORITY, serviceConfigParser, syncContext, scheduler,
xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null);
resolver.start(mockListener);
xdsClient = (FakeXdsClient) resolver.getXdsClient();
@@ -1698,8 +1783,11 @@ public void routeMatching_withHeaders() {
}
private final class FakeXdsClientPoolFactory implements XdsClientPoolFactory {
+ Map bootstrap;
+
@Override
public void setBootstrapOverride(Map bootstrap) {
+ this.bootstrap = bootstrap;
}
@Override
@@ -1731,11 +1819,16 @@ private class FakeXdsClient extends XdsClient {
private LdsResourceWatcher ldsWatcher;
private RdsResourceWatcher rdsWatcher;
+ @Override
+ BootstrapInfo getBootstrapInfo() {
+ return bootstrapInfo;
+ }
+
@Override
void watchLdsResource(String resourceName, LdsResourceWatcher watcher) {
assertThat(ldsResource).isNull();
assertThat(ldsWatcher).isNull();
- assertThat(resourceName).isEqualTo(AUTHORITY);
+ assertThat(resourceName).isEqualTo(expectedLdsResourceName);
ldsResource = resourceName;
ldsWatcher = watcher;
}
@@ -1744,7 +1837,7 @@ void watchLdsResource(String resourceName, LdsResourceWatcher watcher) {
void cancelLdsResourceWatch(String resourceName, LdsResourceWatcher watcher) {
assertThat(ldsResource).isNotNull();
assertThat(ldsWatcher).isNotNull();
- assertThat(resourceName).isEqualTo(AUTHORITY);
+ assertThat(resourceName).isEqualTo(expectedLdsResourceName);
ldsResource = null;
ldsWatcher = null;
}
@@ -1773,7 +1866,7 @@ void deliverLdsUpdate(long httpMaxStreamDurationNano, List virtualH
void deliverLdsUpdate(final List routes) {
VirtualHost virtualHost =
VirtualHost.create(
- "virtual-host", Collections.singletonList(AUTHORITY), routes,
+ "virtual-host", Collections.singletonList(expectedLdsResourceName), routes,
ImmutableMap.of());
ldsWatcher.onChanged(LdsUpdate.forApiListener(HttpConnectionManager.forVirtualHosts(
0L, Collections.singletonList(virtualHost), null)));
@@ -1817,7 +1910,7 @@ void deliverLdsUpdateWithFaultInjection(
FAULT_FILTER_INSTANCE_NAME, virtualHostFaultConfig);
VirtualHost virtualHost = VirtualHost.create(
"virtual-host",
- Collections.singletonList(AUTHORITY),
+ Collections.singletonList(expectedLdsResourceName),
Collections.singletonList(route),
overrideConfig);
ldsWatcher.onChanged(LdsUpdate.forApiListener(HttpConnectionManager.forVirtualHosts(
@@ -1843,7 +1936,7 @@ void deliverLdsUpdateForRdsName(String rdsName) {
}
void deliverLdsResourceNotFound() {
- ldsWatcher.onResourceDoesNotExist(AUTHORITY);
+ ldsWatcher.onResourceDoesNotExist(expectedLdsResourceName);
}
void deliverRdsUpdateWithFaultInjection(
@@ -1876,7 +1969,7 @@ void deliverRdsUpdateWithFaultInjection(
FAULT_FILTER_INSTANCE_NAME, virtualHostFaultConfig);
VirtualHost virtualHost = VirtualHost.create(
"virtual-host",
- Collections.singletonList(AUTHORITY),
+ Collections.singletonList(expectedLdsResourceName),
Collections.singletonList(route),
overrideConfig);
rdsWatcher.onChanged(new RdsUpdate(Collections.singletonList(virtualHost)));
diff --git a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java
index bfb16c9745e..f011b789da9 100644
--- a/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java
+++ b/xds/src/test/java/io/grpc/xds/XdsServerWrapperTest.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -55,6 +56,7 @@
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.XdsServerBuilder.XdsServingStatusListener;
@@ -173,6 +175,36 @@ public void run() {
}
}
+ @Test
+ public void testBootstrap_templateWithXdstp() throws Exception {
+ Bootstrapper.BootstrapInfo b = Bootstrapper.BootstrapInfo.builder()
+ .servers(Arrays.asList(
+ Bootstrapper.ServerInfo.create(
+ "uri", InsecureChannelCredentials.create(), true)))
+ .node(EnvoyProtoData.Node.newBuilder().setId("id").build())
+ .serverListenerResourceNameTemplate(
+ "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/server/%s")
+ .build();
+ XdsClient xdsClient = mock(XdsClient.class);
+ when(xdsClient.getBootstrapInfo()).thenReturn(b);
+ xdsServerWrapper = new XdsServerWrapper("[::FFFF:129.144.52.38]:80", mockBuilder, listener,
+ selectorManager, new FakeXdsClientPoolFactory(xdsClient), filterRegistry);
+ Executors.newSingleThreadExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ xdsServerWrapper.start();
+ } catch (IOException ex) {
+ // ignore
+ }
+ }
+ });
+ verify(xdsClient, timeout(5000)).watchLdsResource(
+ eq("xdstp://xds.authority.com/envoy.config.listener.v3.Listener/grpc/server/"
+ + "%5B::FFFF:129.144.52.38%5D:80"),
+ any(LdsResourceWatcher.class));
+ }
+
@Test
public void shutdown() throws Exception {
final SettableFuture start = SettableFuture.create();