Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import io.grpc.MetricRecorder;
import io.grpc.NameResolver;
import io.grpc.NameResolverRegistry;
import io.grpc.QueryParams;
import io.grpc.Status;
import io.grpc.SynchronizationContext;
import io.grpc.Uri;
Expand All @@ -47,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;
Expand Down Expand Up @@ -81,18 +81,26 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
private static HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE;
private static int c2pId = new Random().nextInt();

private static synchronized BootstrapInfo getBootstrapInfo()
private static synchronized BootstrapInfo getBootstrapInfo(boolean isForcedXds)
throws XdsInitializationException, IOException {
if (bootstrapInfo != null) {
return bootstrapInfo;
}
BootstrapInfo bootstrapInfoTmp =
InternalGrpcBootstrapperImpl.parseBootstrap(generateBootstrap());
BootstrapInfo newInfo;
if (isForcedXds) {
newInfo = InternalGrpcBootstrapperImpl.parseBootstrap(
generateBootstrap("", true));
} else {
newInfo = InternalGrpcBootstrapperImpl.parseBootstrap(
generateBootstrap(
queryZoneMetadata(METADATA_URL_ZONE),
queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6)));
}
// Avoid setting global when testing
if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) {
bootstrapInfo = bootstrapInfoTmp;
bootstrapInfo = newInfo;
}
return bootstrapInfoTmp;
return newInfo;
}

private final String authority;
Expand All @@ -102,7 +110,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;
Expand All @@ -122,16 +131,33 @@ private static synchronized BootstrapInfo getBootstrapInfo()
NameResolver.Factory nameResolverFactory) {
this.executorResource = checkNotNull(executorResource, "executorResource");
String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath");
Uri grpcUri = Uri.create(targetUri.toString());
QueryParams queryParams = QueryParams.fromRawQuery(grpcUri.getRawQuery());
this.forceXds = checkForceXds(queryParams);
this.schemeOverride = (forceXds || isOnGcp) ? "xds" : "dns";
stripForceXds(queryParams);
String newQuery = queryParams.toRawQuery();

Preconditions.checkArgument(
targetPath.startsWith("/"),
"the path component (%s) of the target (%s) must start with '/'",
targetPath,
targetUri);
authority = GrpcUtil.checkAuthority(targetPath.substring(1));
syncContext = checkNotNull(args, "args").getSynchronizationContext();
targetUri = overrideUriScheme(targetUri, schemeOverride);

Uri.Builder modifiedTargetBuilder = grpcUri.toBuilder().setScheme(schemeOverride);
if (newQuery != null) {
modifiedTargetBuilder.setRawQuery(newQuery);
} else {
modifiedTargetBuilder.setRawQuery(null);
}
if (schemeOverride.equals("xds")) {
modifiedTargetBuilder.setRawAuthority(C2P_AUTHORITY);
}
targetUri = URI.create(modifiedTargetBuilder.build().toString());

if (schemeOverride.equals("xds")) {
targetUri = overrideUriAuthority(targetUri, C2P_AUTHORITY);
args = args.toBuilder()
.setArg(XdsNameResolverProvider.XDS_CLIENT_SUPPLIER, () -> xdsClient)
.build();
Expand All @@ -155,6 +181,12 @@ private static synchronized BootstrapInfo getBootstrapInfo()
Resource<Executor> executorResource,
NameResolver.Factory nameResolverFactory) {
this.executorResource = checkNotNull(executorResource, "executorResource");
QueryParams queryParams = QueryParams.fromRawQuery(targetUri.getRawQuery());
this.forceXds = checkForceXds(queryParams);
this.schemeOverride = (forceXds || isOnGcp) ? "xds" : "dns";
stripForceXds(queryParams);
String newQuery = queryParams.toRawQuery();

Preconditions.checkArgument(
targetUri.isPathAbsolute(),
"the path component of the target (%s) must start with '/'",
Expand All @@ -167,6 +199,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.setRawQuery(null);
}

if (schemeOverride.equals("xds")) {
modifiedTargetBuilder.setRawAuthority(C2P_AUTHORITY);
args =
Expand Down Expand Up @@ -226,7 +264,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));
Expand Down Expand Up @@ -259,16 +297,11 @@ public void run() {
executor.execute(new Resolve());
}

@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) {
private static ImmutableMap<String, ?> generateBootstrap(
String zone, boolean supportIpv6) {
ImmutableMap.Builder<String, Object> nodeBuilder = ImmutableMap.builder();
nodeBuilder.put("id", "C2P-" + (c2pId & Integer.MAX_VALUE));
String nodeIdPrefix = isOnGcp ? "C2P-" : "C2P-non-gcp-";
nodeBuilder.put("id", nodeIdPrefix + (c2pId & Integer.MAX_VALUE));
if (!zone.isEmpty()) {
nodeBuilder.put("locality", ImmutableMap.of("zone", zone));
}
Expand Down Expand Up @@ -373,24 +406,17 @@ 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(QueryParams params) {
for (QueryParams.Entry entry : params.asList()) {
if ("force-xds".equals(entry.getKey())) {
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);
}
return res;
private static void stripForceXds(QueryParams params) {
params.asList().removeIf(entry -> "force-xds".equals(entry.getKey()));
}

private enum HttpConnectionFactory implements HttpConnectionProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import io.grpc.ChannelLogger;
import io.grpc.MetricRecorder;
Expand All @@ -46,7 +44,6 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executor;
Expand Down Expand Up @@ -103,6 +100,8 @@ public void close(Executor instance) {}

private final NameResolverRegistry nsRegistry = new NameResolverRegistry();
private final Map<String, NameResolver> delegatedResolver = new HashMap<>();
private final Map<String, URI> delegatedUri = new HashMap<>();
private final Map<String, Uri> delegatedRfcUri = new HashMap<>();

@Mock
private NameResolver.Listener2 mockListener;
Expand Down Expand Up @@ -187,57 +186,125 @@ public void onGcpAndNoProvidedBootstrap_DelegateToXds() {
verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
}

@SuppressWarnings("unchecked")
@Test
public void generateBootstrap_ipv6() throws IOException {
Map<String, ?> bootstrap = GoogleCloudToProdNameResolver.generateBootstrap();
Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
assertThat(node).containsExactly(
"id", "C2P-991614323",
"locality", ImmutableMap.of("zone", ZONE),
"metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true));
Map<String, ?> server = Iterables.getOnlyElement(
(List<Map<String, ?>>) 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<String, ?> authorities = (Map<String, ?>) bootstrap.get("authorities");
assertThat(authorities).containsExactly(
"traffic-director-c2p.xds.googleapis.com",
ImmutableMap.of("xds_servers", ImmutableList.of(server)));
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.getRawQuery()).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.getRawQuery()).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.getRawQuery()).isEqualTo("foo=bar&baz=qux");
} else {
URI delegatedUriValue = delegatedUri.get("xds");
assertThat(delegatedUriValue).isNotNull();
assertThat(delegatedUriValue.getQuery()).isEqualTo("foo=bar&baz=qux");
}
}

@SuppressWarnings("unchecked")
@Test
public void generateBootstrap_noIpV6() throws IOException {
responseToIpV6 = null;
Map<String, ?> bootstrap = GoogleCloudToProdNameResolver.generateBootstrap();
Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
assertThat(node).containsExactly(
"id", "C2P-991614323",
"locality", ImmutableMap.of("zone", ZONE));
Map<String, ?> server = Iterables.getOnlyElement(
(List<Map<String, ?>>) 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<String, ?> authorities = (Map<String, ?>) bootstrap.get("authorities");
assertThat(authorities).containsExactly(
"traffic-director-c2p.xds.googleapis.com",
ImmutableMap.of("xds_servers", ImmutableList.of(server)));
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");
}
}

@SuppressWarnings("unchecked")
@Test
public void emptyResolverMeetadataValue() throws IOException {
responseToIpV6 = "";
Map<String, ?> bootstrap = GoogleCloudToProdNameResolver.generateBootstrap();
Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
assertThat(node).containsExactly(
"id", "C2P-991614323",
"locality", ImmutableMap.of("zone", ZONE));
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.getRawQuery()).isEqualTo("FORCE-XDS");
} else {
URI delegatedUriValue = delegatedUri.get("dns");
assertThat(delegatedUriValue).isNotNull();
assertThat(delegatedUriValue.getQuery()).isEqualTo("FORCE-XDS");
}
}

@Test
Expand Down Expand Up @@ -270,6 +337,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;
Expand Down
Loading