Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions googleapis/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ java_library(
"//core:internal",
"//xds",
artifact("com.google.guava:guava"),
artifact("com.google.errorprone:error_prone_annotations"),
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.CharStreams;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import io.grpc.MetricRecorder;
import io.grpc.NameResolver;
import io.grpc.NameResolverRegistry;
import io.grpc.Status;
Expand All @@ -32,6 +33,13 @@
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.SharedResourceHolder;
import io.grpc.internal.SharedResourceHolder.Resource;
import io.grpc.xds.InternalGrpcBootstrapperImpl;
import io.grpc.xds.InternalSharedXdsClientPoolProvider;
import io.grpc.xds.InternalSharedXdsClientPoolProvider.XdsClientResult;
import io.grpc.xds.XdsNameResolverProvider;
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
import io.grpc.xds.client.XdsClient;
import io.grpc.xds.client.XdsInitializationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
Expand All @@ -41,7 +49,6 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.logging.Level;
Expand All @@ -63,52 +70,54 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
static final String C2P_AUTHORITY = "traffic-director-c2p.xds.googleapis.com";
@VisibleForTesting
static boolean isOnGcp = InternalCheckGcpEnvironment.isOnGcp();
@VisibleForTesting
static boolean xdsBootstrapProvided =
System.getenv("GRPC_XDS_BOOTSTRAP") != null
|| System.getProperty("io.grpc.xds.bootstrap") != null
|| System.getenv("GRPC_XDS_BOOTSTRAP_CONFIG") != null
|| System.getProperty("io.grpc.xds.bootstrapConfig") != null;
@VisibleForTesting
static boolean enableFederation =
Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_XDS_FEDERATION"))
|| Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_XDS_FEDERATION"));

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

private HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE;
@GuardedBy("GoogleCloudToProdNameResolver.class")
private static BootstrapInfo bootstrapInfo;
private static HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE;
private static int c2pId = new Random().nextInt();

private static synchronized BootstrapInfo getBootstrapInfo()
throws XdsInitializationException, IOException {
if (bootstrapInfo != null) {
return bootstrapInfo;
}
BootstrapInfo bootstrapInfoTmp =
InternalGrpcBootstrapperImpl.parseBootstrap(generateBootstrap());
// Avoid setting global when testing
if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) {
bootstrapInfo = bootstrapInfoTmp;
}
return bootstrapInfoTmp;
}

private final String authority;
private final SynchronizationContext syncContext;
private final Resource<Executor> executorResource;
private final BootstrapSetter bootstrapSetter;
private final String target;
private final MetricRecorder metricRecorder;
private final NameResolver delegate;
private final Random rand;
private final boolean usingExecutorResource;
// It's not possible to use both PSM and DirectPath C2P in the same application.
// Delegate to DNS if user-provided bootstrap is found.
private final String schemeOverride =
!isOnGcp
|| (xdsBootstrapProvided && !enableFederation)
? "dns" : "xds";
private final String schemeOverride = !isOnGcp ? "dns" : "xds";
private XdsClientResult xdsClientPool;
private XdsClient xdsClient;
private Executor executor;
private Listener2 listener;
private boolean succeeded;
private boolean resolving;
private boolean shutdown;

GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource,
BootstrapSetter bootstrapSetter) {
this(targetUri, args, executorResource, new Random(), bootstrapSetter,
GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource) {
this(targetUri, args, executorResource,
NameResolverRegistry.getDefaultRegistry().asFactory());
}

@VisibleForTesting
GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource<Executor> executorResource,
Random rand, BootstrapSetter bootstrapSetter, NameResolver.Factory nameResolverFactory) {
NameResolver.Factory nameResolverFactory) {
this.executorResource = checkNotNull(executorResource, "executorResource");
this.bootstrapSetter = checkNotNull(bootstrapSetter, "bootstrapSetter");
this.rand = checkNotNull(rand, "rand");
String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath");
Preconditions.checkArgument(
targetPath.startsWith("/"),
Expand All @@ -118,9 +127,14 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
authority = GrpcUtil.checkAuthority(targetPath.substring(1));
syncContext = checkNotNull(args, "args").getSynchronizationContext();
targetUri = overrideUriScheme(targetUri, schemeOverride);
if (schemeOverride.equals("xds") && enableFederation) {
if (schemeOverride.equals("xds")) {
targetUri = overrideUriAuthority(targetUri, C2P_AUTHORITY);
args = args.toBuilder()
.setArg(XdsNameResolverProvider.XDS_CLIENT_SUPPLIER, () -> xdsClient)
.build();
}
target = targetUri.toString();
metricRecorder = args.getMetricRecorder();
delegate = checkNotNull(nameResolverFactory, "nameResolverFactory").newNameResolver(
targetUri, args);
executor = args.getOffloadExecutor();
Expand Down Expand Up @@ -150,7 +164,7 @@ private void resolve() {

resolving = true;
if (logger.isLoggable(Level.FINE)) {
logger.fine("resolve with schemaOverride = " + schemeOverride);
logger.log(Level.FINE, "start with schemaOverride = {0}", schemeOverride);
}

if (schemeOverride.equals("dns")) {
Expand All @@ -168,28 +182,28 @@ private void resolve() {
class Resolve implements Runnable {
@Override
public void run() {
ImmutableMap<String, ?> rawBootstrap = null;
BootstrapInfo bootstrapInfo = null;
try {
// User provided bootstrap configs are only supported with federation. If federation is
// not enabled or there is no user provided config, we set a custom bootstrap override.
// Otherwise, we don't set the override, which will allow a user provided bootstrap config
// to take effect.
if (!enableFederation || !xdsBootstrapProvided) {
rawBootstrap = generateBootstrap(queryZoneMetadata(METADATA_URL_ZONE),
queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6));
}
bootstrapInfo = getBootstrapInfo();
} catch (IOException e) {
listener.onError(
Status.INTERNAL.withDescription("Unable to get metadata").withCause(e));
} catch (XdsInitializationException e) {
listener.onError(
Status.INTERNAL.withDescription("Unable to create c2p bootstrap").withCause(e));
} catch (Throwable t) {
listener.onError(
Status.INTERNAL.withDescription("Unexpected error creating c2p bootstrap")
.withCause(t));
} finally {
final ImmutableMap<String, ?> finalRawBootstrap = rawBootstrap;
final BootstrapInfo finalBootstrapInfo = bootstrapInfo;
syncContext.execute(new Runnable() {
@Override
public void run() {
if (!shutdown) {
if (finalRawBootstrap != null) {
bootstrapSetter.setBootstrap(finalRawBootstrap);
}
if (!shutdown && finalBootstrapInfo != null) {
xdsClientPool = InternalSharedXdsClientPoolProvider.getOrCreate(
target, finalBootstrapInfo, metricRecorder, null);
xdsClient = xdsClientPool.getObject();
delegate.start(listener);
succeeded = true;
}
Expand All @@ -203,9 +217,16 @@ public void run() {
executor.execute(new Resolve());
}

private ImmutableMap<String, ?> generateBootstrap(String zone, boolean supportIpv6) {
@VisibleForTesting
static ImmutableMap<String, ?> generateBootstrap() throws IOException {
return generateBootstrap(
queryZoneMetadata(METADATA_URL_ZONE),
queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6));
}

private static ImmutableMap<String, ?> generateBootstrap(String zone, boolean supportIpv6) {
ImmutableMap.Builder<String, Object> nodeBuilder = ImmutableMap.builder();
nodeBuilder.put("id", "C2P-" + (rand.nextInt() & Integer.MAX_VALUE));
nodeBuilder.put("id", "C2P-" + (c2pId & Integer.MAX_VALUE));
if (!zone.isEmpty()) {
nodeBuilder.put("locality", ImmutableMap.of("zone", zone));
}
Expand Down Expand Up @@ -250,12 +271,15 @@ public void shutdown() {
if (delegate != null) {
delegate.shutdown();
}
if (xdsClient != null) {
xdsClient = xdsClientPool.returnObject(xdsClient);
}
if (executor != null && usingExecutorResource) {
executor = SharedResourceHolder.release(executorResource, executor);
}
}

private String queryZoneMetadata(String url) throws IOException {
private static String queryZoneMetadata(String url) throws IOException {
HttpURLConnection con = null;
String respBody;
try {
Expand All @@ -275,7 +299,7 @@ private String queryZoneMetadata(String url) throws IOException {
return index == -1 ? "" : respBody.substring(index + 1);
}

private boolean queryIpv6SupportMetadata(String url) throws IOException {
private static boolean queryIpv6SupportMetadata(String url) throws IOException {
HttpURLConnection con = null;
try {
con = httpConnectionProvider.createConnection(url);
Expand All @@ -294,8 +318,17 @@ private boolean queryIpv6SupportMetadata(String url) throws IOException {
}

@VisibleForTesting
void setHttpConnectionProvider(HttpConnectionProvider httpConnectionProvider) {
this.httpConnectionProvider = httpConnectionProvider;
static void setHttpConnectionProvider(HttpConnectionProvider httpConnectionProvider) {
if (httpConnectionProvider == null) {
GoogleCloudToProdNameResolver.httpConnectionProvider = HttpConnectionFactory.INSTANCE;
} else {
GoogleCloudToProdNameResolver.httpConnectionProvider = httpConnectionProvider;
}
}

@VisibleForTesting
static void setC2pId(int c2pId) {
GoogleCloudToProdNameResolver.c2pId = c2pId;
}

private static URI overrideUriScheme(URI uri, String scheme) {
Expand Down Expand Up @@ -335,8 +368,4 @@ public HttpURLConnection createConnection(String url) throws IOException {
interface HttpConnectionProvider {
HttpURLConnection createConnection(String url) throws IOException;
}

public interface BootstrapSetter {
void setBootstrap(Map<String, ?> bootstrap);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@
import io.grpc.NameResolver.Args;
import io.grpc.NameResolverProvider;
import io.grpc.internal.GrpcUtil;
import io.grpc.xds.InternalSharedXdsClientPoolProvider;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

/**
* A provider for {@link GoogleCloudToProdNameResolver}.
Expand All @@ -52,8 +50,7 @@ public GoogleCloudToProdNameResolverProvider() {
public NameResolver newNameResolver(URI targetUri, Args args) {
if (scheme.equals(targetUri.getScheme())) {
return new GoogleCloudToProdNameResolver(
targetUri, args, GrpcUtil.SHARED_CHANNEL_EXECUTOR,
new SharedXdsClientPoolProviderBootstrapSetter());
targetUri, args, GrpcUtil.SHARED_CHANNEL_EXECUTOR);
}
return null;
}
Expand All @@ -77,12 +74,4 @@ protected int priority() {
public Collection<Class<? extends SocketAddress>> getProducedSocketAddressTypes() {
return Collections.singleton(InetSocketAddress.class);
}

private static final class SharedXdsClientPoolProviderBootstrapSetter
implements GoogleCloudToProdNameResolver.BootstrapSetter {
@Override
public void setBootstrap(Map<String, ?> bootstrap) {
InternalSharedXdsClientPoolProvider.setDefaultProviderBootstrapOverride(bootstrap);
}
}
}
Loading