Skip to content

Commit 86fa860

Browse files
authored
googleapis: DirectPath over Interconnect (#12760)
Implements go/directpath-interconnect-client
1 parent ecbe721 commit 86fa860

2 files changed

Lines changed: 182 additions & 81 deletions

File tree

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

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.grpc.MetricRecorder;
2828
import io.grpc.NameResolver;
2929
import io.grpc.NameResolverRegistry;
30+
import io.grpc.QueryParams;
3031
import io.grpc.Status;
3132
import io.grpc.SynchronizationContext;
3233
import io.grpc.Uri;
@@ -47,7 +48,6 @@
4748
import java.io.Reader;
4849
import java.net.HttpURLConnection;
4950
import java.net.URI;
50-
import java.net.URISyntaxException;
5151
import java.net.URL;
5252
import java.nio.charset.StandardCharsets;
5353
import java.util.List;
@@ -81,18 +81,26 @@ final class GoogleCloudToProdNameResolver extends NameResolver {
8181
private static HttpConnectionProvider httpConnectionProvider = HttpConnectionFactory.INSTANCE;
8282
private static int c2pId = new Random().nextInt();
8383

84-
private static synchronized BootstrapInfo getBootstrapInfo()
84+
private static synchronized BootstrapInfo getBootstrapInfo(boolean isForcedXds)
8585
throws XdsInitializationException, IOException {
8686
if (bootstrapInfo != null) {
8787
return bootstrapInfo;
8888
}
89-
BootstrapInfo bootstrapInfoTmp =
90-
InternalGrpcBootstrapperImpl.parseBootstrap(generateBootstrap());
89+
BootstrapInfo newInfo;
90+
if (isForcedXds) {
91+
newInfo = InternalGrpcBootstrapperImpl.parseBootstrap(
92+
generateBootstrap("", true));
93+
} else {
94+
newInfo = InternalGrpcBootstrapperImpl.parseBootstrap(
95+
generateBootstrap(
96+
queryZoneMetadata(METADATA_URL_ZONE),
97+
queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6)));
98+
}
9199
// Avoid setting global when testing
92100
if (httpConnectionProvider == HttpConnectionFactory.INSTANCE) {
93-
bootstrapInfo = bootstrapInfoTmp;
101+
bootstrapInfo = newInfo;
94102
}
95-
return bootstrapInfoTmp;
103+
return newInfo;
96104
}
97105

98106
private final String authority;
@@ -102,7 +110,8 @@ private static synchronized BootstrapInfo getBootstrapInfo()
102110
private final MetricRecorder metricRecorder;
103111
private final NameResolver delegate;
104112
private final boolean usingExecutorResource;
105-
private final String schemeOverride = !isOnGcp ? "dns" : "xds";
113+
private final boolean forceXds;
114+
private final String schemeOverride;
106115
private XdsClientResult xdsClientPool;
107116
private XdsClient xdsClient;
108117
private Executor executor;
@@ -122,16 +131,29 @@ private static synchronized BootstrapInfo getBootstrapInfo()
122131
NameResolver.Factory nameResolverFactory) {
123132
this.executorResource = checkNotNull(executorResource, "executorResource");
124133
String targetPath = checkNotNull(checkNotNull(targetUri, "targetUri").getPath(), "targetPath");
134+
Uri grpcUri = Uri.create(targetUri.toString());
135+
QueryParams queryParams = QueryParams.fromRawQuery(grpcUri.getRawQuery());
136+
this.forceXds = checkForceXds(queryParams);
137+
this.schemeOverride = (forceXds || isOnGcp) ? "xds" : "dns";
138+
stripForceXds(queryParams);
139+
String newQuery = queryParams.toRawQuery();
140+
125141
Preconditions.checkArgument(
126142
targetPath.startsWith("/"),
127143
"the path component (%s) of the target (%s) must start with '/'",
128144
targetPath,
129145
targetUri);
130146
authority = GrpcUtil.checkAuthority(targetPath.substring(1));
131147
syncContext = checkNotNull(args, "args").getSynchronizationContext();
132-
targetUri = overrideUriScheme(targetUri, schemeOverride);
148+
149+
Uri.Builder modifiedTargetBuilder = grpcUri.toBuilder().setScheme(schemeOverride);
150+
modifiedTargetBuilder.setRawQuery(newQuery);
151+
if (schemeOverride.equals("xds")) {
152+
modifiedTargetBuilder.setRawAuthority(C2P_AUTHORITY);
153+
}
154+
targetUri = URI.create(modifiedTargetBuilder.build().toString());
155+
133156
if (schemeOverride.equals("xds")) {
134-
targetUri = overrideUriAuthority(targetUri, C2P_AUTHORITY);
135157
args = args.toBuilder()
136158
.setArg(XdsNameResolverProvider.XDS_CLIENT_SUPPLIER, () -> xdsClient)
137159
.build();
@@ -155,6 +177,12 @@ private static synchronized BootstrapInfo getBootstrapInfo()
155177
Resource<Executor> executorResource,
156178
NameResolver.Factory nameResolverFactory) {
157179
this.executorResource = checkNotNull(executorResource, "executorResource");
180+
QueryParams queryParams = QueryParams.fromRawQuery(targetUri.getRawQuery());
181+
this.forceXds = checkForceXds(queryParams);
182+
this.schemeOverride = (forceXds || isOnGcp) ? "xds" : "dns";
183+
stripForceXds(queryParams);
184+
String newQuery = queryParams.toRawQuery();
185+
158186
Preconditions.checkArgument(
159187
targetUri.isPathAbsolute(),
160188
"the path component of the target (%s) must start with '/'",
@@ -167,6 +195,12 @@ private static synchronized BootstrapInfo getBootstrapInfo()
167195
authority = GrpcUtil.checkAuthority(pathSegments.get(0));
168196
syncContext = checkNotNull(args, "args").getSynchronizationContext();
169197
Uri.Builder modifiedTargetBuilder = targetUri.toBuilder().setScheme(schemeOverride);
198+
if (newQuery != null) {
199+
modifiedTargetBuilder.setRawQuery(newQuery);
200+
} else {
201+
modifiedTargetBuilder.setRawQuery(null);
202+
}
203+
170204
if (schemeOverride.equals("xds")) {
171205
modifiedTargetBuilder.setRawAuthority(C2P_AUTHORITY);
172206
args =
@@ -226,7 +260,7 @@ class Resolve implements Runnable {
226260
public void run() {
227261
BootstrapInfo bootstrapInfo = null;
228262
try {
229-
bootstrapInfo = getBootstrapInfo();
263+
bootstrapInfo = getBootstrapInfo(forceXds);
230264
} catch (IOException e) {
231265
listener.onError(
232266
Status.INTERNAL.withDescription("Unable to get metadata").withCause(e));
@@ -259,16 +293,11 @@ public void run() {
259293
executor.execute(new Resolve());
260294
}
261295

262-
@VisibleForTesting
263-
static ImmutableMap<String, ?> generateBootstrap() throws IOException {
264-
return generateBootstrap(
265-
queryZoneMetadata(METADATA_URL_ZONE),
266-
queryIpv6SupportMetadata(METADATA_URL_SUPPORT_IPV6));
267-
}
268-
269-
private static ImmutableMap<String, ?> generateBootstrap(String zone, boolean supportIpv6) {
296+
private static ImmutableMap<String, ?> generateBootstrap(
297+
String zone, boolean supportIpv6) {
270298
ImmutableMap.Builder<String, Object> nodeBuilder = ImmutableMap.builder();
271-
nodeBuilder.put("id", "C2P-" + (c2pId & Integer.MAX_VALUE));
299+
String nodeIdPrefix = isOnGcp ? "C2P-" : "C2P-non-gcp-";
300+
nodeBuilder.put("id", nodeIdPrefix + (c2pId & Integer.MAX_VALUE));
272301
if (!zone.isEmpty()) {
273302
nodeBuilder.put("locality", ImmutableMap.of("zone", zone));
274303
}
@@ -373,24 +402,17 @@ static void setC2pId(int c2pId) {
373402
GoogleCloudToProdNameResolver.c2pId = c2pId;
374403
}
375404

376-
private static URI overrideUriScheme(URI uri, String scheme) {
377-
URI res;
378-
try {
379-
res = new URI(scheme, uri.getAuthority(), uri.getPath(), uri.getQuery(), uri.getFragment());
380-
} catch (URISyntaxException ex) {
381-
throw new IllegalArgumentException("Invalid scheme: " + scheme, ex);
405+
private static boolean checkForceXds(QueryParams params) {
406+
for (QueryParams.Entry entry : params.asList()) {
407+
if ("force-xds".equals(entry.getKey())) {
408+
return true;
409+
}
382410
}
383-
return res;
411+
return false;
384412
}
385413

386-
private static URI overrideUriAuthority(URI uri, String authority) {
387-
URI res;
388-
try {
389-
res = new URI(uri.getScheme(), authority, uri.getPath(), uri.getQuery(), uri.getFragment());
390-
} catch (URISyntaxException ex) {
391-
throw new IllegalArgumentException("Invalid authority: " + authority, ex);
392-
}
393-
return res;
414+
private static void stripForceXds(QueryParams params) {
415+
params.asList().removeIf(entry -> "force-xds".equals(entry.getKey()));
394416
}
395417

396418
private enum HttpConnectionFactory implements HttpConnectionProvider {

googleapis/src/test/java/io/grpc/googleapis/GoogleCloudToProdNameResolverTest.java

Lines changed: 126 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import static org.mockito.Mockito.verify;
2222
import static org.mockito.Mockito.when;
2323

24-
import com.google.common.collect.ImmutableList;
25-
import com.google.common.collect.ImmutableMap;
2624
import com.google.common.collect.Iterables;
2725
import io.grpc.ChannelLogger;
2826
import io.grpc.MetricRecorder;
@@ -46,7 +44,6 @@
4644
import java.nio.charset.StandardCharsets;
4745
import java.util.Arrays;
4846
import java.util.HashMap;
49-
import java.util.List;
5047
import java.util.Map;
5148
import java.util.Random;
5249
import java.util.concurrent.Executor;
@@ -103,6 +100,8 @@ public void close(Executor instance) {}
103100

104101
private final NameResolverRegistry nsRegistry = new NameResolverRegistry();
105102
private final Map<String, NameResolver> delegatedResolver = new HashMap<>();
103+
private final Map<String, URI> delegatedUri = new HashMap<>();
104+
private final Map<String, Uri> delegatedRfcUri = new HashMap<>();
106105

107106
@Mock
108107
private NameResolver.Listener2 mockListener;
@@ -187,57 +186,125 @@ public void onGcpAndNoProvidedBootstrap_DelegateToXds() {
187186
verify(Iterables.getOnlyElement(delegatedResolver.values())).start(mockListener);
188187
}
189188

190-
@SuppressWarnings("unchecked")
191189
@Test
192-
public void generateBootstrap_ipv6() throws IOException {
193-
Map<String, ?> bootstrap = GoogleCloudToProdNameResolver.generateBootstrap();
194-
Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
195-
assertThat(node).containsExactly(
196-
"id", "C2P-991614323",
197-
"locality", ImmutableMap.of("zone", ZONE),
198-
"metadata", ImmutableMap.of("TRAFFICDIRECTOR_DIRECTPATH_C2P_IPV6_CAPABLE", true));
199-
Map<String, ?> server = Iterables.getOnlyElement(
200-
(List<Map<String, ?>>) bootstrap.get("xds_servers"));
201-
assertThat(server).containsExactly(
202-
"server_uri", "directpath-pa.googleapis.com",
203-
"channel_creds", ImmutableList.of(ImmutableMap.of("type", "google_default")),
204-
"server_features", ImmutableList.of("xds_v3", "ignore_resource_deletion"));
205-
Map<String, ?> authorities = (Map<String, ?>) bootstrap.get("authorities");
206-
assertThat(authorities).containsExactly(
207-
"traffic-director-c2p.xds.googleapis.com",
208-
ImmutableMap.of("xds_servers", ImmutableList.of(server)));
190+
public void notOnGcpButForceXds_DelegateToXds() {
191+
GoogleCloudToProdNameResolver.isOnGcp = false;
192+
String target = TARGET_URI + "?force-xds";
193+
resolver =
194+
enableRfc3986UrisParam
195+
? new GoogleCloudToProdNameResolver(
196+
Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory())
197+
: new GoogleCloudToProdNameResolver(
198+
URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory());
199+
resolver.start(mockListener);
200+
fakeExecutor.runDueTasks();
201+
assertThat(delegatedResolver.keySet()).containsExactly("xds");
202+
203+
if (enableRfc3986UrisParam) {
204+
Uri delegatedRfcUriValue = delegatedRfcUri.get("xds");
205+
assertThat(delegatedRfcUriValue).isNotNull();
206+
assertThat(delegatedRfcUriValue.getRawQuery()).isNull();
207+
} else {
208+
URI delegatedUriValue = delegatedUri.get("xds");
209+
assertThat(delegatedUriValue).isNotNull();
210+
assertThat(delegatedUriValue.getQuery()).isNull();
211+
}
212+
}
213+
214+
@Test
215+
public void notOnGcpButForceXds_KeyValueTrue_DelegateToXds() {
216+
GoogleCloudToProdNameResolver.isOnGcp = false;
217+
String target = TARGET_URI + "?force-xds=true";
218+
resolver = enableRfc3986UrisParam
219+
? new GoogleCloudToProdNameResolver(
220+
Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory())
221+
: new GoogleCloudToProdNameResolver(
222+
URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory());
223+
resolver.start(mockListener);
224+
fakeExecutor.runDueTasks();
225+
assertThat(delegatedResolver.keySet()).containsExactly("xds");
226+
227+
if (enableRfc3986UrisParam) {
228+
Uri delegatedRfcUriValue = delegatedRfcUri.get("xds");
229+
assertThat(delegatedRfcUriValue).isNotNull();
230+
assertThat(delegatedRfcUriValue.getRawQuery()).isNull();
231+
} else {
232+
URI delegatedUriValue = delegatedUri.get("xds");
233+
assertThat(delegatedUriValue).isNotNull();
234+
assertThat(delegatedUriValue.getQuery()).isNull();
235+
}
236+
}
237+
238+
239+
@Test
240+
public void notOnGcpButForceXds_WithMultipleParams_DelegateToXds() {
241+
GoogleCloudToProdNameResolver.isOnGcp = false;
242+
String target = TARGET_URI + "?foo=bar&force-xds&baz=qux";
243+
resolver = enableRfc3986UrisParam
244+
? new GoogleCloudToProdNameResolver(
245+
Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory())
246+
: new GoogleCloudToProdNameResolver(
247+
URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory());
248+
resolver.start(mockListener);
249+
fakeExecutor.runDueTasks();
250+
assertThat(delegatedResolver.keySet()).containsExactly("xds");
251+
252+
if (enableRfc3986UrisParam) {
253+
Uri delegatedRfcUriValue = delegatedRfcUri.get("xds");
254+
assertThat(delegatedRfcUriValue).isNotNull();
255+
assertThat(delegatedRfcUriValue.getRawQuery()).isEqualTo("foo=bar&baz=qux");
256+
} else {
257+
URI delegatedUriValue = delegatedUri.get("xds");
258+
assertThat(delegatedUriValue).isNotNull();
259+
assertThat(delegatedUriValue.getQuery()).isEqualTo("foo=bar&baz=qux");
260+
}
209261
}
210262

211-
@SuppressWarnings("unchecked")
212263
@Test
213-
public void generateBootstrap_noIpV6() throws IOException {
214-
responseToIpV6 = null;
215-
Map<String, ?> bootstrap = GoogleCloudToProdNameResolver.generateBootstrap();
216-
Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
217-
assertThat(node).containsExactly(
218-
"id", "C2P-991614323",
219-
"locality", ImmutableMap.of("zone", ZONE));
220-
Map<String, ?> server = Iterables.getOnlyElement(
221-
(List<Map<String, ?>>) bootstrap.get("xds_servers"));
222-
assertThat(server).containsExactly(
223-
"server_uri", "directpath-pa.googleapis.com",
224-
"channel_creds", ImmutableList.of(ImmutableMap.of("type", "google_default")),
225-
"server_features", ImmutableList.of("xds_v3", "ignore_resource_deletion"));
226-
Map<String, ?> authorities = (Map<String, ?>) bootstrap.get("authorities");
227-
assertThat(authorities).containsExactly(
228-
"traffic-director-c2p.xds.googleapis.com",
229-
ImmutableMap.of("xds_servers", ImmutableList.of(server)));
264+
public void notOnGcpButForceXds_WithEncodedAmpersand_DelegateToXds() {
265+
GoogleCloudToProdNameResolver.isOnGcp = false;
266+
String target = TARGET_URI + "?force-xds&foo=bar%26baz";
267+
resolver = enableRfc3986UrisParam
268+
? new GoogleCloudToProdNameResolver(
269+
Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory())
270+
: new GoogleCloudToProdNameResolver(
271+
URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory());
272+
resolver.start(mockListener);
273+
fakeExecutor.runDueTasks();
274+
assertThat(delegatedResolver.keySet()).containsExactly("xds");
275+
276+
if (enableRfc3986UrisParam) {
277+
Uri delegatedRfcUriValue = delegatedRfcUri.get("xds");
278+
assertThat(delegatedRfcUriValue).isNotNull();
279+
assertThat(delegatedRfcUriValue.getRawQuery()).isEqualTo("foo=bar%26baz");
280+
} else {
281+
URI delegatedUriValue = delegatedUri.get("xds");
282+
assertThat(delegatedUriValue).isNotNull();
283+
assertThat(delegatedUriValue.getRawQuery()).isEqualTo("foo=bar%26baz");
284+
}
230285
}
231286

232-
@SuppressWarnings("unchecked")
233287
@Test
234-
public void emptyResolverMeetadataValue() throws IOException {
235-
responseToIpV6 = "";
236-
Map<String, ?> bootstrap = GoogleCloudToProdNameResolver.generateBootstrap();
237-
Map<String, ?> node = (Map<String, ?>) bootstrap.get("node");
238-
assertThat(node).containsExactly(
239-
"id", "C2P-991614323",
240-
"locality", ImmutableMap.of("zone", ZONE));
288+
public void notOnGcpButForceXds_CaseSensitive_DelegateToDns() {
289+
GoogleCloudToProdNameResolver.isOnGcp = false;
290+
String target = TARGET_URI + "?FORCE-XDS";
291+
resolver = enableRfc3986UrisParam
292+
? new GoogleCloudToProdNameResolver(
293+
Uri.create(target), args, fakeExecutorResource, nsRegistry.asFactory())
294+
: new GoogleCloudToProdNameResolver(
295+
URI.create(target), args, fakeExecutorResource, nsRegistry.asFactory());
296+
resolver.start(mockListener);
297+
assertThat(delegatedResolver.keySet()).containsExactly("dns");
298+
299+
if (enableRfc3986UrisParam) {
300+
Uri delegatedRfcUriValue = delegatedRfcUri.get("dns");
301+
assertThat(delegatedRfcUriValue).isNotNull();
302+
assertThat(delegatedRfcUriValue.getRawQuery()).isEqualTo("FORCE-XDS");
303+
} else {
304+
URI delegatedUriValue = delegatedUri.get("dns");
305+
assertThat(delegatedUriValue).isNotNull();
306+
assertThat(delegatedUriValue.getQuery()).isEqualTo("FORCE-XDS");
307+
}
241308
}
242309

243310
@Test
@@ -270,6 +337,18 @@ private FakeNsProvider(String scheme) {
270337
@Override
271338
public NameResolver newNameResolver(URI targetUri, Args args) {
272339
if (scheme.equals(targetUri.getScheme())) {
340+
delegatedUri.put(scheme, targetUri);
341+
NameResolver resolver = mock(NameResolver.class);
342+
delegatedResolver.put(scheme, resolver);
343+
return resolver;
344+
}
345+
return null;
346+
}
347+
348+
@Override
349+
public NameResolver newNameResolver(Uri targetUri, Args args) {
350+
if (scheme.equals(targetUri.getScheme())) {
351+
delegatedRfcUri.put(scheme, targetUri);
273352
NameResolver resolver = mock(NameResolver.class);
274353
delegatedResolver.put(scheme, resolver);
275354
return resolver;

0 commit comments

Comments
 (0)