From 7d2455731584dc0cd5833b5f94c0bc46584da42c Mon Sep 17 00:00:00 2001 From: sagnghos Date: Sun, 14 Dec 2025 04:08:49 +0000 Subject: [PATCH 1/6] chore: abstract plain text settings into a SpannerOption --- .../google/cloud/spanner/SpannerOptions.java | 28 ++++++++++++++++--- .../testing/ExperimentalHostHelper.java | 6 +--- 2 files changed, 25 insertions(+), 9 deletions(-) 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 20ed760c18d..40d74659246 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 @@ -825,7 +825,7 @@ protected SpannerOptions(Builder builder) { openTelemetry = builder.openTelemetry; enableApiTracing = builder.enableApiTracing; enableExtendedTracing = builder.enableExtendedTracing; - if (builder.isExperimentalHost) { + if (builder.experimentalHost != null) { enableBuiltInMetrics = false; } else { enableBuiltInMetrics = builder.enableBuiltInMetrics; @@ -1047,7 +1047,8 @@ public static class Builder private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics(); private String monitoringHost = SpannerOptions.environment.getMonitoringHost(); private SslContext mTLSContext = null; - private boolean isExperimentalHost = false; + private String experimentalHost = null; + private boolean usePlainText = false; private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance(); private static String createCustomClientLibToken(String token) { @@ -1550,10 +1551,13 @@ public Builder setHost(String host) { @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676") public Builder setExperimentalHost(String host) { + if (usePlainText && !host.startsWith("http")) { + host = "http://" + host; + } super.setHost(host); super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID); setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build()); - this.isExperimentalHost = true; + experimentalHost = host; return this; } @@ -1608,6 +1612,22 @@ public Builder useClientCert(String clientCertificate, String clientCertificateK return this; } + /** + * {@code usePlainText} is only supported for experimental spanner hosts. This will configure + * the transport to use plaintext (no TLS) and will set credentials to {@link + * com.google.cloud.NoCredentials} to avoid sending authentication over an unsecured channel. + */ + @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3574") + public Builder usePlainText() { + this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext) + .setCredentials(NoCredentials.getInstance()); + if (this.experimentalHost != null && !this.experimentalHost.startsWith("http")) { + this.experimentalHost = "http://" + this.experimentalHost; + super.setHost(this.experimentalHost); + } + return this; + } + /** * Sets OpenTelemetry object to be used for Spanner Metrics and Traces. GlobalOpenTelemetry will * be used as fallback if this options is not set. @@ -1783,7 +1803,7 @@ public SpannerOptions build() { this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext); // As we are using plain text, we should never send any credentials. this.setCredentials(NoCredentials.getInstance()); - } else if (isExperimentalHost && credentials == null) { + } else if (experimentalHost != null && credentials == null) { credentials = environment.getDefaultExperimentalHostCredentials(); } if (this.numChannels == null) { diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java index b79622bce5a..f6387535e4d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/ExperimentalHostHelper.java @@ -16,10 +16,8 @@ package com.google.cloud.spanner.testing; -import com.google.cloud.NoCredentials; import com.google.cloud.spanner.SpannerOptions; import com.google.common.base.Strings; -import io.grpc.ManagedChannelBuilder; public class ExperimentalHostHelper { private static final String EXPERIMENTAL_HOST = "spanner.experimental_host"; @@ -58,9 +56,7 @@ public static void setExperimentalHostSpannerOptions(SpannerOptions.Builder buil builder.setExperimentalHost(experimentalHost); builder.setBuiltInMetricsEnabled(false); if (usePlainText) { - builder - .setChannelConfigurator(ManagedChannelBuilder::usePlaintext) - .setCredentials(NoCredentials.getInstance()); + builder.usePlainText(); } if (isMtlsSetup()) { String clientCertificate = System.getProperty(CLIENT_CERT_PATH, ""); From cb9d64ae73d07b59990dd80bd914452f42e7ed8f Mon Sep 17 00:00:00 2001 From: sagnghos Date: Sun, 14 Dec 2025 06:36:18 +0000 Subject: [PATCH 2/6] add unit test for plain text scenarios --- .../google/cloud/spanner/SpannerOptions.java | 1 + .../cloud/spanner/SpannerOptionsTest.java | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) 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 40d74659246..18cfd757390 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 @@ -1625,6 +1625,7 @@ public Builder usePlainText() { this.experimentalHost = "http://" + this.experimentalHost; super.setHost(this.experimentalHost); } + this.usePlainText = true; return this; } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index be292eab5aa..7fe3d145135 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -1201,4 +1201,27 @@ public void testExperimentalHostOptions() { assertTrue(options.getSessionPoolOptions().getUseMultiplexedSessionForRW()); assertTrue(options.getSessionPoolOptions().getUseMultiplexedSessionPartitionedOps()); } + + @Test + public void testPlainTextOptions() { + SpannerOptions options = + SpannerOptions.newBuilder().setExperimentalHost("localhost:8080").usePlainText().build(); + assertEquals("http://localhost:8080", options.getHost()); + assertEquals(NoCredentials.getInstance(), options.getCredentials()); + options = + SpannerOptions.newBuilder() + .setExperimentalHost("http://localhost:8080") + .usePlainText() + .build(); + assertEquals("http://localhost:8080", options.getHost()); + options = + SpannerOptions.newBuilder().usePlainText().setExperimentalHost("localhost:8080").build(); + assertEquals("http://localhost:8080", options.getHost()); + options = + SpannerOptions.newBuilder() + .usePlainText() + .setExperimentalHost("http://localhost:8080") + .build(); + assertEquals("http://localhost:8080", options.getHost()); + } } From 024fa0afa0384e428a6f740867991616c7530234 Mon Sep 17 00:00:00 2001 From: sagnghos Date: Mon, 15 Dec 2025 04:58:27 +0000 Subject: [PATCH 3/6] gemini suggested code changes --- .../com/google/cloud/spanner/SpannerOptions.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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 18cfd757390..2823980187c 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 @@ -1551,13 +1551,13 @@ public Builder setHost(String host) { @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676") public Builder setExperimentalHost(String host) { - if (usePlainText && !host.startsWith("http")) { + if (this.usePlainText && !host.startsWith("http")) { host = "http://" + host; } super.setHost(host); super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID); setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build()); - experimentalHost = host; + this.experimentalHost = host; return this; } @@ -1617,15 +1617,15 @@ public Builder useClientCert(String clientCertificate, String clientCertificateK * the transport to use plaintext (no TLS) and will set credentials to {@link * com.google.cloud.NoCredentials} to avoid sending authentication over an unsecured channel. */ - @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3574") + @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/4264") public Builder usePlainText() { + this.usePlainText = true; this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext) .setCredentials(NoCredentials.getInstance()); - if (this.experimentalHost != null && !this.experimentalHost.startsWith("http")) { - this.experimentalHost = "http://" + this.experimentalHost; - super.setHost(this.experimentalHost); + if (this.experimentalHost != null) { + // Re-apply host settings to ensure http:// is prepended. + setExperimentalHost(this.experimentalHost); } - this.usePlainText = true; return this; } From fbe6fb1cbb36035fe858487720ee3d7ea0516556 Mon Sep 17 00:00:00 2001 From: sagnghos Date: Mon, 15 Dec 2025 07:07:47 +0000 Subject: [PATCH 4/6] updated javadoc --- .../com/google/cloud/spanner/SpannerOptions.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) 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 2823980187c..cfa52cb18a7 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 @@ -1551,8 +1551,13 @@ public Builder setHost(String host) { @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676") public Builder setExperimentalHost(String host) { - if (this.usePlainText && !host.startsWith("http")) { - host = "http://" + host; + if (this.usePlainText) { + Preconditions.checkArgument( + !host.startsWith("https:"), + "Please remove the 'https:' protocol prefix from the host string when using plain text communication"); + if (!host.startsWith("http")) { + host = "http://" + host; + } } super.setHost(host); super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID); @@ -1613,9 +1618,9 @@ public Builder useClientCert(String clientCertificate, String clientCertificateK } /** - * {@code usePlainText} is only supported for experimental spanner hosts. This will configure - * the transport to use plaintext (no TLS) and will set credentials to {@link - * com.google.cloud.NoCredentials} to avoid sending authentication over an unsecured channel. + * {@code usePlainText} will configure the transport to use plaintext (no TLS) and will set + * credentials to {@link com.google.cloud.NoCredentials} to avoid sending authentication over an + * unsecured channel. */ @ExperimentalApi("https://github.com/googleapis/java-spanner/pull/4264") public Builder usePlainText() { From b0762a9a530bc2f1439b2e15ee1cfec40036d658 Mon Sep 17 00:00:00 2001 From: sagnghos Date: Wed, 17 Dec 2025 04:29:16 +0000 Subject: [PATCH 5/6] missing space --- .../test/java/com/google/cloud/spanner/SpannerOptionsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index 5085bad3e39..759888a6673 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -1360,7 +1360,7 @@ public void testCreateDefaultDynamicChannelPoolOptions() { assertEquals( SpannerOptions.DEFAULT_DYNAMIC_POOL_CLEANUP_INTERVAL, defaults.getCleanupInterval()); } - + @Test public void testPlainTextOptions() { SpannerOptions options = From c23f49f530bd6890efd578baec2b4958c34ffb9d Mon Sep 17 00:00:00 2001 From: sagnghos Date: Wed, 17 Dec 2025 06:44:09 +0000 Subject: [PATCH 6/6] fixed unit test --- .../src/main/java/com/google/cloud/spanner/SpannerOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7470e952184..086aa3b9859 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 @@ -1950,7 +1950,7 @@ public Builder setDefaultTransactionOptions( @Override public SpannerOptions build() { // Set the host of emulator has been set. - if (emulatorHost != null) { + if (emulatorHost != null && experimentalHost == null) { if (!emulatorHost.startsWith("http")) { emulatorHost = "http://" + emulatorHost; }