Skip to content

Commit 2edbe9a

Browse files
committed
feat(storage): add support for DirectPath over Interconnect
1 parent f84034b commit 2edbe9a

6 files changed

Lines changed: 605 additions & 88 deletions

File tree

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ public final class GrpcStorageOptions extends StorageOptions
134134
private static final String GCS_SCOPE = "https://www.googleapis.com/auth/devstorage.full_control";
135135
private static final Set<String> SCOPES = ImmutableSet.of(GCS_SCOPE);
136136
private static final String DEFAULT_HOST = "https://storage.googleapis.com";
137+
private static final String DEFAULT_HOST_DIRECT_PATH = "https://storage-direct.googleapis.com";
138+
private static final String DEFAULT_HOST_NO_SCHEME = "storage.googleapis.com";
139+
private static final String DEFAULT_HOST_DIRECT_PATH_NO_SCHEME = "storage-direct.googleapis.com";
137140
// If true, disable the bound-token-by-default feature for DirectPath.
138141
private static final boolean DIRECT_PATH_BOUND_TOKEN_DISABLED =
139142
Boolean.parseBoolean(
@@ -142,6 +145,7 @@ public final class GrpcStorageOptions extends StorageOptions
142145
private final GrpcRetryAlgorithmManager retryAlgorithmManager;
143146
private final java.time.Duration terminationAwaitDuration;
144147
private final boolean attemptDirectPath;
148+
private final boolean attemptDirectPathXdsOverInterconnect;
145149
private final boolean enableGrpcClientMetrics;
146150

147151
private final boolean grpcClientMetricsManuallyEnabled;
@@ -160,6 +164,7 @@ private GrpcStorageOptions(Builder builder, GrpcStorageDefaults serviceDefaults)
160164
builder.terminationAwaitDuration,
161165
serviceDefaults.getTerminationAwaitDurationJavaTime());
162166
this.attemptDirectPath = builder.attemptDirectPath;
167+
this.attemptDirectPathXdsOverInterconnect = builder.attemptDirectPathXdsOverInterconnect;
163168
this.enableGrpcClientMetrics = builder.enableGrpcClientMetrics;
164169
this.grpcClientMetricsManuallyEnabled = builder.grpcMetricsManuallyEnabled;
165170
this.grpcInterceptorProvider = builder.grpcInterceptorProvider;
@@ -230,6 +235,21 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
230235
*/
231236
private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throws IOException {
232237
String endpoint = getHost();
238+
if (attemptDirectPathXdsOverInterconnect) {
239+
if (endpoint.startsWith(DEFAULT_HOST)
240+
&& (endpoint.length() == DEFAULT_HOST.length()
241+
|| endpoint.charAt(DEFAULT_HOST.length()) == ':'
242+
|| endpoint.charAt(DEFAULT_HOST.length()) == '/')) {
243+
endpoint = DEFAULT_HOST_DIRECT_PATH + endpoint.substring(DEFAULT_HOST.length());
244+
} else if (endpoint.startsWith(DEFAULT_HOST_NO_SCHEME)
245+
&& (endpoint.length() == DEFAULT_HOST_NO_SCHEME.length()
246+
|| endpoint.charAt(DEFAULT_HOST_NO_SCHEME.length()) == ':'
247+
|| endpoint.charAt(DEFAULT_HOST_NO_SCHEME.length()) == '/')) {
248+
endpoint =
249+
DEFAULT_HOST_DIRECT_PATH_NO_SCHEME
250+
+ endpoint.substring(DEFAULT_HOST_NO_SCHEME.length());
251+
}
252+
}
233253
URI uri = URI.create(endpoint);
234254
String scheme = uri.getScheme();
235255
int port = uri.getPort();
@@ -322,7 +342,8 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw
322342
InstantiatingGrpcChannelProvider.newBuilder()
323343
.setEndpoint(endpoint)
324344
.setAllowNonDefaultServiceAccount(true)
325-
.setAttemptDirectPath(attemptDirectPath);
345+
.setAttemptDirectPath(attemptDirectPath || attemptDirectPathXdsOverInterconnect)
346+
.setAttemptDirectPathXdsOverInterconnect(attemptDirectPathXdsOverInterconnect);
326347

327348
if (!DIRECT_PATH_BOUND_TOKEN_DISABLED) {
328349
channelProviderBuilder.setAllowHardBoundTokenTypes(
@@ -337,6 +358,18 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw
337358
channelProviderBuilder.setAttemptDirectPathXds();
338359
}
339360

361+
if (attemptDirectPathXdsOverInterconnect) {
362+
com.google.api.core.ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder>
363+
existingConfigurator = channelProviderBuilder.getChannelConfigurator();
364+
channelProviderBuilder.setChannelConfigurator(
365+
channelBuilder -> {
366+
if (existingConfigurator != null) {
367+
channelBuilder = existingConfigurator.apply(channelBuilder);
368+
}
369+
return channelBuilder.overrideAuthority("storage.googleapis.com");
370+
});
371+
}
372+
340373
if (scheme.equals("http")) {
341374
channelProviderBuilder.setChannelConfigurator(ManagedChannelBuilder::usePlaintext);
342375
}
@@ -428,6 +461,7 @@ public int hashCode() {
428461
retryAlgorithmManager,
429462
terminationAwaitDuration,
430463
attemptDirectPath,
464+
attemptDirectPathXdsOverInterconnect,
431465
enableGrpcClientMetrics,
432466
grpcInterceptorProvider,
433467
blobWriteSessionConfig,
@@ -445,6 +479,7 @@ public boolean equals(Object o) {
445479
}
446480
GrpcStorageOptions that = (GrpcStorageOptions) o;
447481
return attemptDirectPath == that.attemptDirectPath
482+
&& attemptDirectPathXdsOverInterconnect == that.attemptDirectPathXdsOverInterconnect
448483
&& enableGrpcClientMetrics == that.enableGrpcClientMetrics
449484
&& Objects.equals(retryAlgorithmManager, that.retryAlgorithmManager)
450485
&& Objects.equals(terminationAwaitDuration, that.terminationAwaitDuration)
@@ -494,6 +529,7 @@ public static final class Builder extends StorageOptions.Builder {
494529
private StorageRetryStrategy storageRetryStrategy;
495530
private java.time.Duration terminationAwaitDuration;
496531
private boolean attemptDirectPath = GrpcStorageDefaults.INSTANCE.isAttemptDirectPath();
532+
private boolean attemptDirectPathXdsOverInterconnect = false;
497533
private boolean enableGrpcClientMetrics =
498534
GrpcStorageDefaults.INSTANCE.isEnableGrpcClientMetrics();
499535
private GrpcInterceptorProvider grpcInterceptorProvider =
@@ -512,6 +548,7 @@ public static final class Builder extends StorageOptions.Builder {
512548
this.storageRetryStrategy = gso.getRetryAlgorithmManager().retryStrategy;
513549
this.terminationAwaitDuration = gso.getTerminationAwaitDuration();
514550
this.attemptDirectPath = gso.attemptDirectPath;
551+
this.attemptDirectPathXdsOverInterconnect = gso.attemptDirectPathXdsOverInterconnect;
515552
this.enableGrpcClientMetrics = gso.enableGrpcClientMetrics;
516553
this.grpcInterceptorProvider = gso.grpcInterceptorProvider;
517554
this.blobWriteSessionConfig = gso.blobWriteSessionConfig;
@@ -556,6 +593,18 @@ public GrpcStorageOptions.Builder setAttemptDirectPath(boolean attemptDirectPath
556593
return this;
557594
}
558595

596+
/**
597+
* Option for whether this client should attempt to use DirectPath over Interconnect (on-premise
598+
* xDS name resolution).
599+
*
600+
* @since 2.45.0
601+
*/
602+
public GrpcStorageOptions.Builder setAttemptDirectPathXdsOverInterconnect(
603+
boolean attemptDirectPathXdsOverInterconnect) {
604+
this.attemptDirectPathXdsOverInterconnect = attemptDirectPathXdsOverInterconnect;
605+
return this;
606+
}
607+
559608
/**
560609
* Option for whether this client should emit internal gRPC client internal metrics to Cloud
561610
* Monitoring. To disable metric reporting, set this to false. True by default. Emitting metrics

java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsBuilderTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,37 @@ public void grpc() throws Exception {
6969
() -> assertThat(rebuilt.hashCode()).isEqualTo(base.hashCode()));
7070
}
7171

72+
@Test
73+
public void grpc_attemptDirectPathXdsOverInterconnect() throws Exception {
74+
com.google.auth.Credentials mockCreds = com.google.cloud.NoCredentials.getInstance();
75+
GrpcStorageOptions options =
76+
GrpcStorageOptions.grpc()
77+
.setCredentials(mockCreds)
78+
.setAttemptDirectPathXdsOverInterconnect(true)
79+
.build();
80+
81+
GrpcStorageOptions rebuilt = options.toBuilder().build();
82+
assertAll(
83+
() -> assertThat(rebuilt).isEqualTo(options),
84+
() -> assertThat(rebuilt.hashCode()).isEqualTo(options.hashCode()));
85+
86+
com.google.storage.v2.StorageSettings settings = options.getStorageSettings();
87+
assertThat(settings.getEndpoint()).isEqualTo("storage-direct.googleapis.com:443");
88+
89+
com.google.api.gax.rpc.TransportChannelProvider tcp = settings.getTransportChannelProvider();
90+
assertThat(tcp).isInstanceOf(com.google.api.gax.grpc.InstantiatingGrpcChannelProvider.class);
91+
com.google.api.gax.grpc.InstantiatingGrpcChannelProvider provider =
92+
(com.google.api.gax.grpc.InstantiatingGrpcChannelProvider) tcp;
93+
94+
// Verify attemptDirectPathXdsOverInterconnect is set to true on the provider using reflection
95+
java.lang.reflect.Field field =
96+
com.google.api.gax.grpc.InstantiatingGrpcChannelProvider.class.getDeclaredField(
97+
"attemptDirectPathXdsOverInterconnect");
98+
field.setAccessible(true);
99+
Boolean value = (Boolean) field.get(provider);
100+
assertThat(value).isTrue();
101+
}
102+
72103
@Test
73104
public void useJwtAccessWithScope_defaultsToFalse() {
74105
HttpStorageOptions httpOptions = HttpStorageOptions.http().build();

java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageOptionsTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,32 @@ public void clientShouldConstructCleanly_directPath() throws Exception {
8484
doTest(options);
8585
}
8686

87+
@Test
88+
public void clientShouldConstructCleanly_directPathXdsOverInterconnect() throws Exception {
89+
StorageOptions options =
90+
StorageOptions.grpc()
91+
.setCredentials(credentials)
92+
.setAttemptDirectPathXdsOverInterconnect(true)
93+
.setEnableGrpcClientMetrics(false)
94+
.build();
95+
doTest(options);
96+
}
97+
98+
@Test
99+
public void clientShouldWork_directPathXdsOverInterconnect() throws Exception {
100+
assumeTrue(
101+
"Environment cannot resolve storage.direct.googleapis.com", canResolveDirectPathAddress());
102+
StorageOptions options =
103+
StorageOptions.grpc()
104+
.setCredentials(credentials)
105+
.setAttemptDirectPathXdsOverInterconnect(true)
106+
.setEnableGrpcClientMetrics(false)
107+
.build();
108+
try (Storage storage = options.getService()) {
109+
storage.list(Storage.BucketListOption.pageSize(1));
110+
}
111+
}
112+
87113
@Test
88114
public void lackOfProjectIdDoesNotPreventConstruction_http() throws Exception {
89115
StorageOptions options = StorageOptions.http().setCredentials(credentials).build();
@@ -105,4 +131,13 @@ private static void doTest(StorageOptions options) throws Exception {
105131
//noinspection EmptyTryBlock
106132
try (Storage ignore = options.getService()) {}
107133
}
134+
135+
private static boolean canResolveDirectPathAddress() {
136+
try {
137+
java.net.InetAddress.getAllByName("storage.direct.googleapis.com");
138+
return true;
139+
} catch (java.net.UnknownHostException e) {
140+
return false;
141+
}
142+
}
108143
}

0 commit comments

Comments
 (0)