From 72b24f6f7d1263c0f6546da723fef5fc3b96861b Mon Sep 17 00:00:00 2001 From: MV Shiva Prasad Date: Wed, 15 Apr 2026 11:57:03 +0530 Subject: [PATCH 1/2] direct-path-interconnect --- api/src/main/java/io/grpc/Uri.java | 2 +- .../GoogleCloudToProdNameResolver.java | 138 ++++++++--- .../GoogleCloudToProdNameResolverTest.java | 219 ++++++++++++++++++ 3 files changed, 324 insertions(+), 35 deletions(-) diff --git a/api/src/main/java/io/grpc/Uri.java b/api/src/main/java/io/grpc/Uri.java index 9f8a5a87848..c45abef5e7d 100644 --- a/api/src/main/java/io/grpc/Uri.java +++ b/api/src/main/java/io/grpc/Uri.java @@ -793,7 +793,7 @@ public Builder setQuery(@Nullable String query) { } @CanIgnoreReturnValue - Builder setRawQuery(String query) { + public Builder setRawQuery(String query) { checkPercentEncodedArg(query, "query", queryChars); this.query = query; return this; diff --git a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java index 427c0658531..59a898783f8 100644 --- a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java +++ b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java @@ -20,6 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.io.CharStreams; @@ -76,23 +77,43 @@ final class GoogleCloudToProdNameResolver extends NameResolver { private static final String serverUriOverride = System.getenv("GRPC_TEST_ONLY_GOOGLE_C2P_RESOLVER_TRAFFIC_DIRECTOR_URI"); - @GuardedBy("GoogleCloudToProdNameResolver.class") + private static final Object BOOTSTRAP_LOCK = new Object(); + private static final Object FORCE_XDS_BOOTSTRAP_LOCK = new Object(); + + @GuardedBy("BOOTSTRAP_LOCK") private static BootstrapInfo bootstrapInfo; + @GuardedBy("FORCE_XDS_BOOTSTRAP_LOCK") + private static BootstrapInfo forceXdsBootstrapInfo; private static HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE; private static int c2pId = new Random().nextInt(); - private static synchronized BootstrapInfo getBootstrapInfo() + private static BootstrapInfo getBootstrapInfo(boolean isForcedXds) throws XdsInitializationException, IOException { - if (bootstrapInfo != null) { - return bootstrapInfo; - } - BootstrapInfo bootstrapInfoTmp = - InternalGrpcBootstrapperImpl.parseBootstrap(generateBootstrap()); - // Avoid setting global when testing - if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) { - bootstrapInfo = bootstrapInfoTmp; + if (isForcedXds) { + synchronized (FORCE_XDS_BOOTSTRAP_LOCK) { + if (forceXdsBootstrapInfo != null) { + return forceXdsBootstrapInfo; + } + BootstrapInfo newInfo = InternalGrpcBootstrapperImpl.parseBootstrap( + generateBootstrap("", true, true)); + if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) { + forceXdsBootstrapInfo = newInfo; + } + return newInfo; + } + } else { + synchronized (BOOTSTRAP_LOCK) { + if (bootstrapInfo != null) { + return bootstrapInfo; + } + BootstrapInfo newInfo = InternalGrpcBootstrapperImpl.parseBootstrap( + generateBootstrap()); + if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) { + bootstrapInfo = newInfo; + } + return newInfo; + } } - return bootstrapInfoTmp; } private final String authority; @@ -102,7 +123,8 @@ private static synchronized BootstrapInfo getBootstrapInfo() private final MetricRecorder metricRecorder; private final NameResolver delegate; private final boolean usingExecutorResource; - private final String schemeOverride = !isOnGcp ? "dns" : "xds"; + private final boolean forceXds; + private final String schemeOverride; private XdsClientResult xdsClientPool; private XdsClient xdsClient; private Executor executor; @@ -121,6 +143,11 @@ private static synchronized BootstrapInfo getBootstrapInfo() GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource executorResource, NameResolver.Factory nameResolverFactory) { this.executorResource = checkNotNull(executorResource, "executorResource"); + String query = targetUri.getRawQuery(); + this.forceXds = checkForceXds(query); + this.schemeOverride = (forceXds || isOnGcp) ? "xds" : "dns"; + String newQuery = stripForceXds(query); + String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath"); Preconditions.checkArgument( targetPath.startsWith("/"), @@ -129,9 +156,31 @@ private static synchronized BootstrapInfo getBootstrapInfo() targetUri); authority = GrpcUtil.checkAuthority(targetPath.substring(1)); syncContext = checkNotNull(args, "args").getSynchronizationContext(); - targetUri = overrideUriScheme(targetUri, schemeOverride); + + String rawAuthority = schemeOverride.equals("xds") + ? C2P_AUTHORITY + : targetUri.getRawAuthority(); + String rawPath = targetUri.getRawPath(); + String rawFragment = targetUri.getRawFragment(); + try { + StringBuilder uriStr = new StringBuilder(); + uriStr.append(schemeOverride).append(":"); + if (rawAuthority != null) { + uriStr.append("//").append(rawAuthority); + } + uriStr.append(rawPath); + if (newQuery != null) { + uriStr.append("?").append(newQuery); + } + if (rawFragment != null) { + uriStr.append("#").append(rawFragment); + } + targetUri = new URI(uriStr.toString()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid URI", e); + } + if (schemeOverride.equals("xds")) { - targetUri = overrideUriAuthority(targetUri, C2P_AUTHORITY); args = args.toBuilder() .setArg(XdsNameResolverProvider.XDS_CLIENT_SUPPLIER, () -> xdsClient) .build(); @@ -155,6 +204,11 @@ private static synchronized BootstrapInfo getBootstrapInfo() Resource executorResource, NameResolver.Factory nameResolverFactory) { this.executorResource = checkNotNull(executorResource, "executorResource"); + String query = targetUri.getRawQuery(); + this.forceXds = checkForceXds(query); + this.schemeOverride = (forceXds || isOnGcp) ? "xds" : "dns"; + String newQuery = stripForceXds(query); + Preconditions.checkArgument( targetUri.isPathAbsolute(), "the path component of the target (%s) must start with '/'", @@ -167,6 +221,12 @@ private static synchronized BootstrapInfo getBootstrapInfo() authority = GrpcUtil.checkAuthority(pathSegments.get(0)); syncContext = checkNotNull(args, "args").getSynchronizationContext(); Uri.Builder modifiedTargetBuilder = targetUri.toBuilder().setScheme(schemeOverride); + if (newQuery != null) { + modifiedTargetBuilder.setRawQuery(newQuery); + } else { + modifiedTargetBuilder.setQuery(null); + } + if (schemeOverride.equals("xds")) { modifiedTargetBuilder.setRawAuthority(C2P_AUTHORITY); args = @@ -226,7 +286,7 @@ class Resolve implements Runnable { public void run() { BootstrapInfo bootstrapInfo = null; try { - bootstrapInfo = getBootstrapInfo(); + bootstrapInfo = getBootstrapInfo(forceXds); } catch (IOException e) { listener.onError( Status.INTERNAL.withDescription("Unable to get metadata").withCause(e)); @@ -263,16 +323,18 @@ public void run() { static ImmutableMap generateBootstrap() throws IOException { return generateBootstrap( queryZoneMetadata(METADATA_URL_ZONE), - queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6)); + queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6), false); } - private static ImmutableMap generateBootstrap(String zone, boolean supportIpv6) { + static ImmutableMap generateBootstrap( + String zone, boolean supportIpv6, boolean isForcedXds) { ImmutableMap.Builder nodeBuilder = ImmutableMap.builder(); - nodeBuilder.put("id", "C2P-" + (c2pId & Integer.MAX_VALUE)); - if (!zone.isEmpty()) { + String nodeIdPrefix = isOnGcp ? "C2P-" : "C2P-non-gcp-"; + nodeBuilder.put("id", nodeIdPrefix + (c2pId & Integer.MAX_VALUE)); + if (!isForcedXds && !zone.isEmpty()) { nodeBuilder.put("locality", ImmutableMap.of("zone", zone)); } - if (supportIpv6) { + if (isForcedXds || supportIpv6) { nodeBuilder.put("metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true)); } @@ -373,24 +435,32 @@ static void setC2pId(int c2pId) { GoogleCloudToProdNameResolver.c2pId = c2pId; } - private static URI overrideUriScheme(URI uri, String scheme) { - URI res; - try { - res = new URI(scheme, uri.getAuthority(), uri.getPath(), uri.getQuery(), uri.getFragment()); - } catch (URISyntaxException ex) { - throw new IllegalArgumentException("Invalid scheme: " + scheme, ex); + private static boolean checkForceXds(String query) { + if (query == null) { + return false; + } + for (String part : Splitter.on('&').split(query)) { + if (part.equals("force-xds") || part.startsWith("force-xds=")) { + return true; + } } - return res; + return false; } - private static URI overrideUriAuthority(URI uri, String authority) { - URI res; - try { - res = new URI(uri.getScheme(), authority, uri.getPath(), uri.getQuery(), uri.getFragment()); - } catch (URISyntaxException ex) { - throw new IllegalArgumentException("Invalid authority: " + authority, ex); + private static String stripForceXds(String query) { + if (query == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + for (String part : Splitter.on('&').split(query)) { + if (!part.equals("force-xds") && !part.startsWith("force-xds=")) { + if (sb.length() > 0) { + sb.append("&"); + } + sb.append(part); + } } - return res; + return sb.length() == 0 ? null : sb.toString(); } private enum HttpConnectionFactory implements HttpConnectionProvider { diff --git a/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java index d3d3cfc4bff..3c4866ca224 100644 --- a/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java +++ b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java @@ -103,6 +103,8 @@ public void close(Executor instance) {} private final NameResolverRegistry nsRegistry = new NameResolverRegistry(); private final Map delegatedResolver = new HashMap<>(); + private final Map delegatedUri = new HashMap<>(); + private final Map delegatedRfcUri = new HashMap<>(); @Mock private NameResolver.Listener2 mockListener; @@ -187,9 +189,178 @@ public void onGcpAndNoProvidedBootstrap_DelegateToXds() { verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener); } + @Test + public void notOnGcpButForceXds_DelegateToXds() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?force-xds"; + resolver = + enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + fakeExecutor.runDueTasks(); + assertThat(delegatedResolver.keySet()).containsExactly("xds"); + + if (enableRfc3986UrisParam) { + Uri delegatedRfcUriValue = delegatedRfcUri.get("xds"); + assertThat(delegatedRfcUriValue).isNotNull(); + assertThat(delegatedRfcUriValue.getQuery()).isNull(); + } else { + URI delegatedUriValue = delegatedUri.get("xds"); + assertThat(delegatedUriValue).isNotNull(); + assertThat(delegatedUriValue.getQuery()).isNull(); + } + } + + @Test + public void notOnGcpButForceXds_KeyValueTrue_DelegateToXds() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?force-xds=true"; + resolver = enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + fakeExecutor.runDueTasks(); + assertThat(delegatedResolver.keySet()).containsExactly("xds"); + + if (enableRfc3986UrisParam) { + Uri delegatedRfcUriValue = delegatedRfcUri.get("xds"); + assertThat(delegatedRfcUriValue).isNotNull(); + assertThat(delegatedRfcUriValue.getQuery()).isNull(); + } else { + URI delegatedUriValue = delegatedUri.get("xds"); + assertThat(delegatedUriValue).isNotNull(); + assertThat(delegatedUriValue.getQuery()).isNull(); + } + } + + @Test + public void notOnGcpButForceXds_KeyValueOne_DelegateToXds() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?force-xds=1"; + resolver = enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + fakeExecutor.runDueTasks(); + assertThat(delegatedResolver.keySet()).containsExactly("xds"); + + if (enableRfc3986UrisParam) { + Uri delegatedRfcUriValue = delegatedRfcUri.get("xds"); + assertThat(delegatedRfcUriValue).isNotNull(); + assertThat(delegatedRfcUriValue.getQuery()).isNull(); + } else { + URI delegatedUriValue = delegatedUri.get("xds"); + assertThat(delegatedUriValue).isNotNull(); + assertThat(delegatedUriValue.getQuery()).isNull(); + } + } + + @Test + public void notOnGcpButForceXds_WithMultipleParams_DelegateToXds() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?foo=bar&force-xds&baz=qux"; + resolver = enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + fakeExecutor.runDueTasks(); + assertThat(delegatedResolver.keySet()).containsExactly("xds"); + + if (enableRfc3986UrisParam) { + Uri delegatedRfcUriValue = delegatedRfcUri.get("xds"); + assertThat(delegatedRfcUriValue).isNotNull(); + assertThat(delegatedRfcUriValue.getQuery()).isEqualTo("foo=bar&baz=qux"); + } else { + URI delegatedUriValue = delegatedUri.get("xds"); + assertThat(delegatedUriValue).isNotNull(); + assertThat(delegatedUriValue.getQuery()).isEqualTo("foo=bar&baz=qux"); + } + } + + @Test + public void notOnGcpButForceXds_WithEncodedAmpersand_DelegateToXds() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?force-xds&foo=bar%26baz"; + resolver = enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + fakeExecutor.runDueTasks(); + assertThat(delegatedResolver.keySet()).containsExactly("xds"); + + if (enableRfc3986UrisParam) { + Uri delegatedRfcUriValue = delegatedRfcUri.get("xds"); + assertThat(delegatedRfcUriValue).isNotNull(); + assertThat(delegatedRfcUriValue.getRawQuery()).isEqualTo("foo=bar%26baz"); + } else { + URI delegatedUriValue = delegatedUri.get("xds"); + assertThat(delegatedUriValue).isNotNull(); + assertThat(delegatedUriValue.getRawQuery()).isEqualTo("foo=bar%26baz"); + } + } + + @Test + public void notOnGcpButForceXds_EdgeCaseAmpersands_DelegateToXds() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?&force-xds&"; + resolver = enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + fakeExecutor.runDueTasks(); + assertThat(delegatedResolver.keySet()).containsExactly("xds"); + + if (enableRfc3986UrisParam) { + Uri delegatedRfcUriValue = delegatedRfcUri.get("xds"); + assertThat(delegatedRfcUriValue).isNotNull(); + assertThat(delegatedRfcUriValue.getQuery()).isNull(); + } else { + URI delegatedUriValue = delegatedUri.get("xds"); + assertThat(delegatedUriValue).isNotNull(); + assertThat(delegatedUriValue.getQuery()).isNull(); + } + } + + @Test + public void notOnGcpButForceXds_CaseSensitive_DelegateToDns() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?FORCE-XDS"; + resolver = enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + assertThat(delegatedResolver.keySet()).containsExactly("dns"); + + if (enableRfc3986UrisParam) { + Uri delegatedRfcUriValue = delegatedRfcUri.get("dns"); + assertThat(delegatedRfcUriValue).isNotNull(); + assertThat(delegatedRfcUriValue.getQuery()).isEqualTo("FORCE-XDS"); + } else { + URI delegatedUriValue = delegatedUri.get("dns"); + assertThat(delegatedUriValue).isNotNull(); + assertThat(delegatedUriValue.getQuery()).isEqualTo("FORCE-XDS"); + } + } + @SuppressWarnings("unchecked") @Test public void generateBootstrap_ipv6() throws IOException { + GoogleCloudToProdNameResolver.isOnGcp = true; Map bootstrap = GoogleCloudToProdNameResolver.generateBootstrap(); Map node = (Map) bootstrap.get("node"); assertThat(node).containsExactly( @@ -208,9 +379,44 @@ public void generateBootstrap_ipv6() throws IOException { ImmutableMap.of("xds_servers", ImmutableList.of(server))); } + @SuppressWarnings("unchecked") + @Test + public void generateBootstrap_forceXds() throws IOException { + GoogleCloudToProdNameResolver.isOnGcp = false; + Map bootstrap = GoogleCloudToProdNameResolver.generateBootstrap("", true, true); + Map node = (Map) bootstrap.get("node"); + assertThat(node).containsExactly( + "id", "C2P-non-gcp-991614323", + "metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true)); + + Map server = Iterables.getOnlyElement( + (List>) bootstrap.get("xds_servers")); + assertThat(server).containsExactly( + "server_uri", "directpath-pa.googleapis.com", + "channel_creds", ImmutableList.of(ImmutableMap.of("type", "google_default")), + "server_features", ImmutableList.of("xds_v3", "ignore_resource_deletion")); + Map authorities = (Map) bootstrap.get("authorities"); + assertThat(authorities).containsExactly( + "traffic-director-c2p.xds.googleapis.com", + ImmutableMap.of("xds_servers", ImmutableList.of(server))); + } + + @SuppressWarnings("unchecked") + @Test + public void generateBootstrap_onGcpAndForceXds() throws IOException { + GoogleCloudToProdNameResolver.isOnGcp = true; + Map bootstrap = GoogleCloudToProdNameResolver.generateBootstrap("", true, true); + Map node = (Map) bootstrap.get("node"); + assertThat(node).containsExactly( + "id", "C2P-991614323", + "metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true)); + assertThat(node).doesNotContainKey("locality"); + } + @SuppressWarnings("unchecked") @Test public void generateBootstrap_noIpV6() throws IOException { + GoogleCloudToProdNameResolver.isOnGcp = true; responseToIpV6 = null; Map bootstrap = GoogleCloudToProdNameResolver.generateBootstrap(); Map node = (Map) bootstrap.get("node"); @@ -232,6 +438,7 @@ public void generateBootstrap_noIpV6() throws IOException { @SuppressWarnings("unchecked") @Test public void emptyResolverMeetadataValue() throws IOException { + GoogleCloudToProdNameResolver.isOnGcp = true; responseToIpV6 = ""; Map bootstrap = GoogleCloudToProdNameResolver.generateBootstrap(); Map node = (Map) bootstrap.get("node"); @@ -270,6 +477,18 @@ private FakeNsProvider(String scheme) { @Override public NameResolver newNameResolver(URI targetUri, Args args) { if (scheme.equals(targetUri.getScheme())) { + delegatedUri.put(scheme, targetUri); + NameResolver resolver = mock(NameResolver.class); + delegatedResolver.put(scheme, resolver); + return resolver; + } + return null; + } + + @Override + public NameResolver newNameResolver(Uri targetUri, Args args) { + if (scheme.equals(targetUri.getScheme())) { + delegatedRfcUri.put(scheme, targetUri); NameResolver resolver = mock(NameResolver.class); delegatedResolver.put(scheme, resolver); return resolver; From 9b65e38b82162acb20571faa9078fe62fc4b759f Mon Sep 17 00:00:00 2001 From: MV Shiva Prasad Date: Wed, 15 Apr 2026 13:43:54 +0530 Subject: [PATCH 2/2] direct-path-interconnect --- .../GoogleCloudToProdNameResolver.java | 44 ++++++++----------- .../GoogleCloudToProdNameResolverTest.java | 14 ++++++ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java index 59a898783f8..797de707be4 100644 --- a/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java +++ b/googleapis/src/main/java/io/grpc/googleapis/GoogleCloudToProdNameResolver.java @@ -48,7 +48,6 @@ import java.io.Reader; import java.net.HttpURLConnection; import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.List; @@ -96,6 +95,7 @@ private static BootstrapInfo getBootstrapInfo(boolean isForcedXds) } BootstrapInfo newInfo = InternalGrpcBootstrapperImpl.parseBootstrap( generateBootstrap("", true, true)); + // Avoid setting global when testing if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) { forceXdsBootstrapInfo = newInfo; } @@ -108,6 +108,7 @@ private static BootstrapInfo getBootstrapInfo(boolean isForcedXds) } BootstrapInfo newInfo = InternalGrpcBootstrapperImpl.parseBootstrap( generateBootstrap()); + // Avoid setting global when testing if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) { bootstrapInfo = newInfo; } @@ -143,12 +144,13 @@ private static BootstrapInfo getBootstrapInfo(boolean isForcedXds) GoogleCloudToProdNameResolver(URI targetUri, Args args, Resource executorResource, NameResolver.Factory nameResolverFactory) { this.executorResource = checkNotNull(executorResource, "executorResource"); - String query = targetUri.getRawQuery(); + String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath"); + Uri grpcUri = Uri.create(targetUri.toString()); + String query = grpcUri.getRawQuery(); this.forceXds = checkForceXds(query); this.schemeOverride = (forceXds || isOnGcp) ? "xds" : "dns"; String newQuery = stripForceXds(query); - String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath"); Preconditions.checkArgument( targetPath.startsWith("/"), "the path component (%s) of the target (%s) must start with '/'", @@ -157,28 +159,16 @@ private static BootstrapInfo getBootstrapInfo(boolean isForcedXds) authority = GrpcUtil.checkAuthority(targetPath.substring(1)); syncContext = checkNotNull(args, "args").getSynchronizationContext(); - String rawAuthority = schemeOverride.equals("xds") - ? C2P_AUTHORITY - : targetUri.getRawAuthority(); - String rawPath = targetUri.getRawPath(); - String rawFragment = targetUri.getRawFragment(); - try { - StringBuilder uriStr = new StringBuilder(); - uriStr.append(schemeOverride).append(":"); - if (rawAuthority != null) { - uriStr.append("//").append(rawAuthority); - } - uriStr.append(rawPath); - if (newQuery != null) { - uriStr.append("?").append(newQuery); - } - if (rawFragment != null) { - uriStr.append("#").append(rawFragment); - } - targetUri = new URI(uriStr.toString()); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid URI", e); + Uri.Builder modifiedTargetBuilder = grpcUri.toBuilder().setScheme(schemeOverride); + if (newQuery != null) { + modifiedTargetBuilder.setRawQuery(newQuery); + } else { + modifiedTargetBuilder.setQuery(null); } + if (schemeOverride.equals("xds")) { + modifiedTargetBuilder.setRawAuthority(C2P_AUTHORITY); + } + targetUri = URI.create(modifiedTargetBuilder.build().toString()); if (schemeOverride.equals("xds")) { args = args.toBuilder() @@ -440,9 +430,13 @@ private static boolean checkForceXds(String query) { return false; } for (String part : Splitter.on('&').split(query)) { - if (part.equals("force-xds") || part.startsWith("force-xds=")) { + if (part.equals("force-xds")) { return true; } + if (part.startsWith("force-xds=")) { + String value = part.substring("force-xds=".length()); + return !value.equalsIgnoreCase("false") && !value.equals("0"); + } } return false; } diff --git a/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java index 3c4866ca224..ee73afa17af 100644 --- a/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java +++ b/googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java @@ -238,6 +238,20 @@ public void notOnGcpButForceXds_KeyValueTrue_DelegateToXds() { } } + @Test + public void notOnGcpButForceXds_KeyValueFalse_DelegateToDns() { + GoogleCloudToProdNameResolver.isOnGcp = false; + String target = TARGET_URI + "?force-xds=false"; + resolver = enableRfc3986UrisParam + ? new GoogleCloudToProdNameResolver( + Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory()) + : new GoogleCloudToProdNameResolver( + URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory()); + resolver.start(mockListener); + fakeExecutor.runDueTasks(); + assertThat(delegatedResolver.keySet()).containsExactly("dns"); + } + @Test public void notOnGcpButForceXds_KeyValueOne_DelegateToXds() { GoogleCloudToProdNameResolver.isOnGcp = false;