@@ -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 ;
@@ -197,6 +202,23 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
197202 this .openTelemetry = HttpStorageOptions .getDefaultInstance ().getOpenTelemetry ();
198203 }
199204
205+ private static String rewriteHost (String endpoint , String oldHost , String newHost ) {
206+ String prefix = "" ;
207+ String rest = endpoint ;
208+ int schemeIndex = endpoint .indexOf ("://" );
209+ if (schemeIndex >= 0 ) {
210+ prefix = endpoint .substring (0 , schemeIndex + 3 );
211+ rest = endpoint .substring (schemeIndex + 3 );
212+ }
213+ if (rest .startsWith (oldHost )) {
214+ int len = oldHost .length ();
215+ if (rest .length () == len || rest .charAt (len ) == ':' || rest .charAt (len ) == '/' ) {
216+ return prefix + newHost + rest .substring (len );
217+ }
218+ }
219+ return endpoint ;
220+ }
221+
200222 /**
201223 * We have to perform several introspections and detections to cross-wire/support several features
202224 * that are either gapic primitives, ServiceOption primitives or GCS semantic requirements.
@@ -230,6 +252,9 @@ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundE
230252 */
231253 private Tuple <StorageSettings , Opts <UserProject >> resolveSettingsAndOpts () throws IOException {
232254 String endpoint = getHost ();
255+ if (attemptDirectPathXdsOverInterconnect ) {
256+ endpoint = rewriteHost (endpoint , "storage.googleapis.com" , "storage-direct.googleapis.com" );
257+ }
233258 URI uri = URI .create (endpoint );
234259 String scheme = uri .getScheme ();
235260 int port = uri .getPort ();
@@ -322,7 +347,8 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw
322347 InstantiatingGrpcChannelProvider .newBuilder ()
323348 .setEndpoint (endpoint )
324349 .setAllowNonDefaultServiceAccount (true )
325- .setAttemptDirectPath (attemptDirectPath );
350+ .setAttemptDirectPath (attemptDirectPath || attemptDirectPathXdsOverInterconnect )
351+ .setAttemptDirectPathXdsOverInterconnect (attemptDirectPathXdsOverInterconnect );
326352
327353 if (!DIRECT_PATH_BOUND_TOKEN_DISABLED ) {
328354 channelProviderBuilder .setAllowHardBoundTokenTypes (
@@ -337,6 +363,18 @@ private Tuple<StorageSettings, Opts<UserProject>> resolveSettingsAndOpts() throw
337363 channelProviderBuilder .setAttemptDirectPathXds ();
338364 }
339365
366+ if (attemptDirectPathXdsOverInterconnect ) {
367+ com .google .api .core .ApiFunction <ManagedChannelBuilder , ManagedChannelBuilder >
368+ existingConfigurator = channelProviderBuilder .getChannelConfigurator ();
369+ channelProviderBuilder .setChannelConfigurator (
370+ channelBuilder -> {
371+ if (existingConfigurator != null ) {
372+ channelBuilder = existingConfigurator .apply (channelBuilder );
373+ }
374+ return channelBuilder .overrideAuthority ("storage.googleapis.com" );
375+ });
376+ }
377+
340378 if (scheme .equals ("http" )) {
341379 channelProviderBuilder .setChannelConfigurator (ManagedChannelBuilder ::usePlaintext );
342380 }
@@ -428,6 +466,7 @@ public int hashCode() {
428466 retryAlgorithmManager ,
429467 terminationAwaitDuration ,
430468 attemptDirectPath ,
469+ attemptDirectPathXdsOverInterconnect ,
431470 enableGrpcClientMetrics ,
432471 grpcInterceptorProvider ,
433472 blobWriteSessionConfig ,
@@ -445,6 +484,7 @@ public boolean equals(Object o) {
445484 }
446485 GrpcStorageOptions that = (GrpcStorageOptions ) o ;
447486 return attemptDirectPath == that .attemptDirectPath
487+ && attemptDirectPathXdsOverInterconnect == that .attemptDirectPathXdsOverInterconnect
448488 && enableGrpcClientMetrics == that .enableGrpcClientMetrics
449489 && Objects .equals (retryAlgorithmManager , that .retryAlgorithmManager )
450490 && Objects .equals (terminationAwaitDuration , that .terminationAwaitDuration )
@@ -494,6 +534,7 @@ public static final class Builder extends StorageOptions.Builder {
494534 private StorageRetryStrategy storageRetryStrategy ;
495535 private java .time .Duration terminationAwaitDuration ;
496536 private boolean attemptDirectPath = GrpcStorageDefaults .INSTANCE .isAttemptDirectPath ();
537+ private boolean attemptDirectPathXdsOverInterconnect = false ;
497538 private boolean enableGrpcClientMetrics =
498539 GrpcStorageDefaults .INSTANCE .isEnableGrpcClientMetrics ();
499540 private GrpcInterceptorProvider grpcInterceptorProvider =
@@ -512,6 +553,7 @@ public static final class Builder extends StorageOptions.Builder {
512553 this .storageRetryStrategy = gso .getRetryAlgorithmManager ().retryStrategy ;
513554 this .terminationAwaitDuration = gso .getTerminationAwaitDuration ();
514555 this .attemptDirectPath = gso .attemptDirectPath ;
556+ this .attemptDirectPathXdsOverInterconnect = gso .attemptDirectPathXdsOverInterconnect ;
515557 this .enableGrpcClientMetrics = gso .enableGrpcClientMetrics ;
516558 this .grpcInterceptorProvider = gso .grpcInterceptorProvider ;
517559 this .blobWriteSessionConfig = gso .blobWriteSessionConfig ;
@@ -556,6 +598,18 @@ public GrpcStorageOptions.Builder setAttemptDirectPath(boolean attemptDirectPath
556598 return this ;
557599 }
558600
601+ /**
602+ * Option for whether this client should attempt to use DirectPath over Interconnect (on-premise
603+ * xDS name resolution).
604+ *
605+ * @since 2.45.0
606+ */
607+ public GrpcStorageOptions .Builder setAttemptDirectPathXdsOverInterconnect (
608+ boolean attemptDirectPathXdsOverInterconnect ) {
609+ this .attemptDirectPathXdsOverInterconnect = attemptDirectPathXdsOverInterconnect ;
610+ return this ;
611+ }
612+
559613 /**
560614 * Option for whether this client should emit internal gRPC client internal metrics to Cloud
561615 * Monitoring. To disable metric reporting, set this to false. True by default. Emitting metrics
0 commit comments