Skip to content

Commit 27d1508

Browse files
authored
xds,googleapis: Allow wrapping NameResolver to inject XdsClient (#12450)
Since there is no longer a single global XdsClient, it makes more sense to allow things like the c2p name resolver to inject its own bootstrap even if there is one defined in an environment variable. GoogleCloudToProdNameResolver can now pass an XdsClient instance to XdsNameResolver, and SharedXdsClientPoolProvider allows GoogleCloudToProdNameResolver to choose the bootstrap for that one specific target. Since XdsNameResolver is no longer in control of the XdsClient pool the XdsClient instance is now passed to ClusterImplLb. A channel will now only access the global XdsClient pool exactly once: in the name resolver. BootstrapInfo is purposefully being shared across channels, as we really want to share things like credentials which can have significant memory use and may have caches which reduce I/O when shared. That is why SharedXdsClientPoolProvider receives BootstrapInfo instead of Map<String,?>. Verifying BootstrapInfo.server() is not empty was moved from SharedXdsClientPoolProvider to GrpcBootstrapperImpl so avoid getOrCreate() throwing an exception in only that one case. It might make sense to move that to BootstrapperImpl, but that will need more investigation. A lot of tests needed updating because XdsClientPoolProvider is no longer responsible for parsing the bootstrap, so we now need bootstraps even if XdsClientPoolProvider will ignore it. This also fixes a bug in GoogleCloudToProdNameResolver where it would initialize the delegate even when it failed to create the bootstrap. That would certainly cause all RPCs on the channel to fail because of the missing bootstrap and it defeated the point of `succeeded == false` and `refresh()` which was supposed to retry contacting the metadata server. The server tests were enhanced to give a useful error when server.start() throws an exception, as otherwise the real error is lost. b/442819521
1 parent acbbf86 commit 27d1508

27 files changed

+526
-351
lines changed

googleapis/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ java_library(
1313
"//core:internal",
1414
"//xds",
1515
artifact("com.google.guava:guava"),
16+
artifact("com.google.errorprone:error_prone_annotations"),
1617
],
1718
)

googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@
2020

2121
import com.google.common.annotations.VisibleForTesting;
2222
import com.google.common.base.Preconditions;
23-
import com.google.common.base.Strings;
2423
import com.google.common.collect.ImmutableList;
2524
import com.google.common.collect.ImmutableMap;
2625
import com.google.common.io.CharStreams;
26+
import com.google.errorprone.annotations.concurrent.GuardedBy;
27+
import io.grpc.MetricRecorder;
2728
import io.grpc.NameResolver;
2829
import io.grpc.NameResolverRegistry;
2930
import io.grpc.Status;
@@ -32,6 +33,13 @@
3233
import io.grpc.internal.GrpcUtil;
3334
import io.grpc.internal.SharedResourceHolder;
3435
import io.grpc.internal.SharedResourceHolder.Resource;
36+
import io.grpc.xds.InternalGrpcBootstrapperImpl;
37+
import io.grpc.xds.InternalSharedXdsClientPoolProvider;
38+
import io.grpc.xds.InternalSharedXdsClientPoolProvider.XdsClientResult;
39+
import io.grpc.xds.XdsNameResolverProvider;
40+
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
41+
import io.grpc.xds.client.XdsClient;
42+
import io.grpc.xds.client.XdsInitializationException;
3543
import java.io.IOException;
3644
import java.io.InputStream;
3745
import java.io.InputStreamReader;
@@ -41,7 +49,6 @@
4149
import java.net.URISyntaxException;
4250
import java.net.URL;
4351
import java.nio.charset.StandardCharsets;
44-
import java.util.Map;
4552
import java.util.Random;
4653
import java.util.concurrent.Executor;
4754
import java.util.logging.Level;
@@ -63,52 +70,54 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
6370
static final String C2P_AUTHORITY = "traffic-director-c2p.xds.googleapis.com";
6471
@VisibleForTesting
6572
static boolean isOnGcp = InternalCheckGcpEnvironment.isOnGcp();
66-
@VisibleForTesting
67-
static boolean xdsBootstrapProvided =
68-
System.getenv("GRPC_XDS_BOOTSTRAP") != null
69-
|| System.getProperty("io.grpc.xds.bootstrap") != null
70-
|| System.getenv("GRPC_XDS_BOOTSTRAP_CONFIG") != null
71-
|| System.getProperty("io.grpc.xds.bootstrapConfig") != null;
72-
@VisibleForTesting
73-
static boolean enableFederation =
74-
Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_FEDERATION"))
75-
|| Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_FEDERATION"));
7673

7774
private static final String serverUriOverride =
7875
System.getenv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI");
7976

80-
private HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE;
77+
@GuardedBy("GoogleCloudToProdNameResolver.class")
78+
private static BootstrapInfo bootstrapInfo;
79+
private static HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE;
80+
private static int c2pId = new Random().nextInt();
81+
82+
private static synchronized BootstrapInfo getBootstrapInfo()
83+
throws XdsInitializationException, IOException {
84+
if (bootstrapInfo != null) {
85+
return bootstrapInfo;
86+
}
87+
BootstrapInfo bootstrapInfoTmp =
88+
InternalGrpcBootstrapperImpl.parseBootstrap(generateBootstrap());
89+
// Avoid setting global when testing
90+
if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) {
91+
bootstrapInfo = bootstrapInfoTmp;
92+
}
93+
return bootstrapInfoTmp;
94+
}
95+
8196
private final String authority;
8297
private final SynchronizationContext syncContext;
8398
private final Resource<Executor> executorResource;
84-
private final BootstrapSetter bootstrapSetter;
99+
private final String target;
100+
private final MetricRecorder metricRecorder;
85101
private final NameResolver delegate;
86-
private final Random rand;
87102
private final boolean usingExecutorResource;
88-
// It's not possible to use both PSM and DirectPath C2P in the same application.
89-
// Delegate to DNS if user-provided bootstrap is found.
90-
private final String schemeOverride =
91-
!isOnGcp
92-
|| (xdsBootstrapProvided && !enableFederation)
93-
? "dns" : "xds";
103+
private final String schemeOverride = !isOnGcp ? "dns" : "xds";
104+
private XdsClientResult xdsClientPool;
105+
private XdsClient xdsClient;
94106
private Executor executor;
95107
private Listener2 listener;
96108
private boolean succeeded;
97109
private boolean resolving;
98110
private boolean shutdown;
99111

100-
GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource,
101-
BootstrapSetter bootstrapSetter) {
102-
this(targetUri, args, executorResource, new Random(), bootstrapSetter,
112+
GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource) {
113+
this(targetUri, args, executorResource,
103114
NameResolverRegistry.getDefaultRegistry().asFactory());
104115
}
105116

106117
@VisibleForTesting
107118
GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource,
108-
Random rand, BootstrapSetter bootstrapSetter, NameResolver.Factory nameResolverFactory) {
119+
NameResolver.Factory nameResolverFactory) {
109120
this.executorResource = checkNotNull(executorResource, "executorResource");
110-
this.bootstrapSetter = checkNotNull(bootstrapSetter, "bootstrapSetter");
111-
this.rand = checkNotNull(rand, "rand");
112121
String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath");
113122
Preconditions.checkArgument(
114123
targetPath.startsWith("/"),
@@ -118,9 +127,14 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
118127
authority = GrpcUtil.checkAuthority(targetPath.substring(1));
119128
syncContext = checkNotNull(args, "args").getSynchronizationContext();
120129
targetUri = overrideUriScheme(targetUri, schemeOverride);
121-
if (schemeOverride.equals("xds") && enableFederation) {
130+
if (schemeOverride.equals("xds")) {
122131
targetUri = overrideUriAuthority(targetUri, C2P_AUTHORITY);
132+
args = args.toBuilder()
133+
.setArg(XdsNameResolverProvider.XDS_CLIENT_SUPPLIER, () -> xdsClient)
134+
.build();
123135
}
136+
target = targetUri.toString();
137+
metricRecorder = args.getMetricRecorder();
124138
delegate = checkNotNull(nameResolverFactory, "nameResolverFactory").newNameResolver(
125139
targetUri, args);
126140
executor = args.getOffloadExecutor();
@@ -150,7 +164,7 @@ private void resolve() {
150164

151165
resolving = true;
152166
if (logger.isLoggable(Level.FINE)) {
153-
logger.fine("resolve with schemaOverride = " + schemeOverride);
167+
logger.log(Level.FINE, "start with schemaOverride = {0}", schemeOverride);
154168
}
155169

156170
if (schemeOverride.equals("dns")) {
@@ -168,28 +182,28 @@ private void resolve() {
168182
class Resolve implements Runnable {
169183
@Override
170184
public void run() {
171-
ImmutableMap<String, ?> rawBootstrap = null;
185+
BootstrapInfo bootstrapInfo = null;
172186
try {
173-
// User provided bootstrap configs are only supported with federation. If federation is
174-
// not enabled or there is no user provided config, we set a custom bootstrap override.
175-
// Otherwise, we don't set the override, which will allow a user provided bootstrap config
176-
// to take effect.
177-
if (!enableFederation || !xdsBootstrapProvided) {
178-
rawBootstrap = generateBootstrap(queryZoneMetadata(METADATA_URL_ZONE),
179-
queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6));
180-
}
187+
bootstrapInfo = getBootstrapInfo();
181188
} catch (IOException e) {
182189
listener.onError(
183190
Status.INTERNAL.withDescription("Unable to get metadata").withCause(e));
191+
} catch (XdsInitializationException e) {
192+
listener.onError(
193+
Status.INTERNAL.withDescription("Unable to create c2p bootstrap").withCause(e));
194+
} catch (Throwable t) {
195+
listener.onError(
196+
Status.INTERNAL.withDescription("Unexpected error creating c2p bootstrap")
197+
.withCause(t));
184198
} finally {
185-
final ImmutableMap<String, ?> finalRawBootstrap = rawBootstrap;
199+
final BootstrapInfo finalBootstrapInfo = bootstrapInfo;
186200
syncContext.execute(new Runnable() {
187201
@Override
188202
public void run() {
189-
if (!shutdown) {
190-
if (finalRawBootstrap != null) {
191-
bootstrapSetter.setBootstrap(finalRawBootstrap);
192-
}
203+
if (!shutdown && finalBootstrapInfo != null) {
204+
xdsClientPool = InternalSharedXdsClientPoolProvider.getOrCreate(
205+
target, finalBootstrapInfo, metricRecorder, null);
206+
xdsClient = xdsClientPool.getObject();
193207
delegate.start(listener);
194208
succeeded = true;
195209
}
@@ -203,9 +217,16 @@ public void run() {
203217
executor.execute(new Resolve());
204218
}
205219

206-
private ImmutableMap<String, ?> generateBootstrap(String zone, boolean supportIpv6) {
220+
@VisibleForTesting
221+
static ImmutableMap<String, ?> generateBootstrap() throws IOException {
222+
return generateBootstrap(
223+
queryZoneMetadata(METADATA_URL_ZONE),
224+
queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6));
225+
}
226+
227+
private static ImmutableMap<String, ?> generateBootstrap(String zone, boolean supportIpv6) {
207228
ImmutableMap.Builder<String, Object> nodeBuilder = ImmutableMap.builder();
208-
nodeBuilder.put("id", "C2P-" + (rand.nextInt() & Integer.MAX_VALUE));
229+
nodeBuilder.put("id", "C2P-" + (c2pId & Integer.MAX_VALUE));
209230
if (!zone.isEmpty()) {
210231
nodeBuilder.put("locality", ImmutableMap.of("zone", zone));
211232
}
@@ -250,12 +271,15 @@ public void shutdown() {
250271
if (delegate != null) {
251272
delegate.shutdown();
252273
}
274+
if (xdsClient != null) {
275+
xdsClient = xdsClientPool.returnObject(xdsClient);
276+
}
253277
if (executor != null && usingExecutorResource) {
254278
executor = SharedResourceHolder.release(executorResource, executor);
255279
}
256280
}
257281

258-
private String queryZoneMetadata(String url) throws IOException {
282+
private static String queryZoneMetadata(String url) throws IOException {
259283
HttpURLConnection con = null;
260284
String respBody;
261285
try {
@@ -275,7 +299,7 @@ private String queryZoneMetadata(String url) throws IOException {
275299
return index == -1 ? "" : respBody.substring(index + 1);
276300
}
277301

278-
private boolean queryIpv6SupportMetadata(String url) throws IOException {
302+
private static boolean queryIpv6SupportMetadata(String url) throws IOException {
279303
HttpURLConnection con = null;
280304
try {
281305
con = httpConnectionProvider.createConnection(url);
@@ -294,8 +318,17 @@ private boolean queryIpv6SupportMetadata(String url) throws IOException {
294318
}
295319

296320
@VisibleForTesting
297-
void setHttpConnectionProvider(HttpConnectionProvider httpConnectionProvider) {
298-
this.httpConnectionProvider = httpConnectionProvider;
321+
static void setHttpConnectionProvider(HttpConnectionProvider httpConnectionProvider) {
322+
if (httpConnectionProvider == null) {
323+
GoogleCloudToProdNameResolver.httpConnectionProvider = HttpConnectionFactory.INSTANCE;
324+
} else {
325+
GoogleCloudToProdNameResolver.httpConnectionProvider = httpConnectionProvider;
326+
}
327+
}
328+
329+
@VisibleForTesting
330+
static void setC2pId(int c2pId) {
331+
GoogleCloudToProdNameResolver.c2pId = c2pId;
299332
}
300333

301334
private static URI overrideUriScheme(URI uri, String scheme) {
@@ -335,8 +368,4 @@ public HttpURLConnection createConnection(String url) throws IOException {
335368
interface HttpConnectionProvider {
336369
HttpURLConnection createConnection(String url) throws IOException;
337370
}
338-
339-
public interface BootstrapSetter {
340-
void setBootstrap(Map<String, ?> bootstrap);
341-
}
342371
}

googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolverProvider.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@
2222
import io.grpc.NameResolver.Args;
2323
import io.grpc.NameResolverProvider;
2424
import io.grpc.internal.GrpcUtil;
25-
import io.grpc.xds.InternalSharedXdsClientPoolProvider;
2625
import java.net.InetSocketAddress;
2726
import java.net.SocketAddress;
2827
import java.net.URI;
2928
import java.util.Collection;
3029
import java.util.Collections;
31-
import java.util.Map;
3230

3331
/**
3432
* A provider for {@link GoogleCloudToProdNameResolver}.
@@ -52,8 +50,7 @@ public GoogleCloudToProdNameResolverProvider() {
5250
public NameResolver newNameResolver(URI targetUri, Args args) {
5351
if (scheme.equals(targetUri.getScheme())) {
5452
return new GoogleCloudToProdNameResolver(
55-
targetUri, args, GrpcUtil.SHARED_CHANNEL_EXECUTOR,
56-
new SharedXdsClientPoolProviderBootstrapSetter());
53+
targetUri, args, GrpcUtil.SHARED_CHANNEL_EXECUTOR);
5754
}
5855
return null;
5956
}
@@ -77,12 +74,4 @@ protected int priority() {
7774
public Collection<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
7875
return Collections.singleton(InetSocketAddress.class);
7976
}
80-
81-
private static final class SharedXdsClientPoolProviderBootstrapSetter
82-
implements GoogleCloudToProdNameResolver.BootstrapSetter {
83-
@Override
84-
public void setBootstrap(Map<String, ?> bootstrap) {
85-
InternalSharedXdsClientPoolProvider.setDefaultProviderBootstrapOverride(bootstrap);
86-
}
87-
}
8877
}

0 commit comments

Comments
 (0)