diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 790c602d45b..c3e9215a20e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -1000,7 +1000,7 @@ public static class Builder private String compressorName; private String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST"); private boolean leaderAwareRoutingEnabled = true; - private boolean attemptDirectPath = true; + private boolean attemptDirectPath = false; private DirectedReadOptions directedReadOptions; private boolean useVirtualThreads = false; private OpenTelemetry openTelemetry; @@ -1604,6 +1604,12 @@ public Builder disableLeaderAwareRouting() { return this; } + @BetaApi + public Builder enableDirectPath() { + this.attemptDirectPath = true; + return this; + } + @BetaApi public Builder disableDirectPath() { this.attemptDirectPath = false; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java index 58b2c78fb2f..d9ba873b412 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java @@ -16,6 +16,7 @@ package com.google.cloud.spanner.connection; +import static com.google.cloud.spanner.connection.ConnectionProperties.ATTEMPT_DIRECT_PATH; import static com.google.cloud.spanner.connection.ConnectionProperties.AUTOCOMMIT; import static com.google.cloud.spanner.connection.ConnectionProperties.AUTO_CONFIG_EMULATOR; import static com.google.cloud.spanner.connection.ConnectionProperties.AUTO_PARTITION_MODE; @@ -1081,6 +1082,10 @@ boolean isExperimentalHost() { return getInitialConnectionPropertyValue(IS_EXPERIMENTAL_HOST); } + boolean isAttemptDirectPath() { + return getInitialConnectionPropertyValue(ATTEMPT_DIRECT_PATH); + } + String getClientCertificate() { return getInitialConnectionPropertyValue(CLIENT_CERTIFICATE); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java index d9610a5a08a..042d7217586 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java @@ -183,6 +183,20 @@ public class ConnectionProperties { BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); + static final ConnectionProperty ATTEMPT_DIRECT_PATH = + create( + "attemptDirectPath", + "Configure the connection to try to connect to Spanner using " + + "DirectPath (true/false). The client will try to connect to Spanner " + + "using a direct Google network connection. DirectPath will work only " + + "if the client is trying to establish a connection from a Google Cloud VM. " + + "Otherwise it will automatically fallback to the standard network path. " + + "NOTE: The default for this property is currently false, " + + "but this could be changed in the future.", + false, + BOOLEANS, + BooleanConverter.INSTANCE, + Context.STARTUP); static final ConnectionProperty USE_AUTO_SAVEPOINTS_FOR_EMULATOR = create( "useAutoSavepointsForEmulator", diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java index 8c521b7500e..ea76b727dbb 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java @@ -164,6 +164,7 @@ static class SpannerPoolKey { private final String clientCertificate; private final String clientCertificateKey; private final boolean isExperimentalHost; + private final boolean attemptDirectPath; @VisibleForTesting static SpannerPoolKey of(ConnectionOptions options) { @@ -198,6 +199,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException { this.clientCertificate = options.getClientCertificate(); this.clientCertificateKey = options.getClientCertificateKey(); this.isExperimentalHost = options.isExperimentalHost(); + this.attemptDirectPath = options.isAttemptDirectPath(); } @Override @@ -223,7 +225,8 @@ public boolean equals(Object o) { && Objects.equals(this.enableEndToEndTracing, other.enableEndToEndTracing) && Objects.equals(this.clientCertificate, other.clientCertificate) && Objects.equals(this.clientCertificateKey, other.clientCertificateKey) - && Objects.equals(this.isExperimentalHost, other.isExperimentalHost); + && Objects.equals(this.isExperimentalHost, other.isExperimentalHost) + && Objects.equals(this.attemptDirectPath, other.attemptDirectPath); } @Override @@ -245,7 +248,8 @@ public int hashCode() { this.enableEndToEndTracing, this.clientCertificate, this.clientCertificateKey, - this.isExperimentalHost); + this.isExperimentalHost, + this.attemptDirectPath); } } @@ -412,6 +416,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { if (key.isExperimentalHost) { builder.setExperimentalHost(key.host); } + if (key.attemptDirectPath) { + builder.enableDirectPath(); + } if (options.getConfigurator() != null) { options.getConfigurator().configure(builder); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java index 09aec6f3ebe..1a83a195cec 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java @@ -365,7 +365,7 @@ public GapicSpannerRpc(final SpannerOptions options) { .withEncoding(compressorName)) .setHeaderProvider(headerProviderWithUserAgent) .setAllowNonDefaultServiceAccount(true); - boolean isAttemptDirectPathXds = isEnableDirectPathXdsEnv(); + boolean isAttemptDirectPathXds = isEnableDirectPathXdsEnv() || options.isAttemptDirectPath(); if (isAttemptDirectPathXds) { defaultChannelProviderBuilder.setAttemptDirectPath(true); // This will let the credentials try to fetch a hard-bound access token if the runtime diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java index af391a745f4..9fb6adea6ef 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java @@ -1311,4 +1311,17 @@ public void testExperimentalHost() { .getSessionPoolOptions() .getUseMultiplexedSessionPartitionedOps()); } + + @Test + public void testAttemptDirectPath() { + ConnectionOptions.Builder builderWithoutDirectPathParam = ConnectionOptions.newBuilder(); + builderWithoutDirectPathParam.setUri( + "spanner://localhost:15000/instances/default/databases/singers-db;usePlainText=true"); + assertFalse(builderWithoutDirectPathParam.build().isAttemptDirectPath()); + + ConnectionOptions.Builder builderWithDirectPathParam = ConnectionOptions.newBuilder(); + builderWithDirectPathParam.setUri( + "spanner://localhost:15000/projects/default/instances/default/databases/singers-db;usePlainText=true;attemptDirectPath=true"); + assertTrue(builderWithDirectPathParam.build().isAttemptDirectPath()); + } }