From 36569322049b4f16fb5529b29da893667ddfd1e9 Mon Sep 17 00:00:00 2001 From: Sakthivel Subramanian Date: Wed, 24 Sep 2025 13:47:25 +0530 Subject: [PATCH 1/3] feat: TPC support --- .../cloud/spanner/BuiltInMetricsProvider.java | 15 +++- .../SpannerCloudMonitoringExporter.java | 9 +- .../google/cloud/spanner/SpannerOptions.java | 41 ++++++++-- .../spanner/connection/ConnectionOptions.java | 12 ++- .../connection/ConnectionProperties.java | 8 ++ .../cloud/spanner/connection/SpannerPool.java | 11 ++- .../SpannerCloudMonitoringExporterTest.java | 2 +- .../cloud/spanner/SpannerOptionsTest.java | 10 +-- .../connection/ConnectionOptionsTest.java | 82 +++++++++++++++++++ 9 files changed, 167 insertions(+), 23 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInMetricsProvider.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInMetricsProvider.java index d35c69b499e..76becf25c9c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInMetricsProvider.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BuiltInMetricsProvider.java @@ -68,12 +68,16 @@ final class BuiltInMetricsProvider { private BuiltInMetricsProvider() {} OpenTelemetry getOrCreateOpenTelemetry( - String projectId, @Nullable Credentials credentials, @Nullable String monitoringHost) { + String projectId, + @Nullable Credentials credentials, + @Nullable String monitoringHost, + String universeDomain) { try { if (this.openTelemetry == null) { SdkMeterProviderBuilder sdkMeterProviderBuilder = SdkMeterProvider.builder(); BuiltInMetricsView.registerBuiltinMetrics( - SpannerCloudMonitoringExporter.create(projectId, credentials, monitoringHost), + SpannerCloudMonitoringExporter.create( + projectId, credentials, monitoringHost, universeDomain), sdkMeterProviderBuilder); sdkMeterProviderBuilder.setResource(Resource.create(createResourceAttributes(projectId))); SdkMeterProvider sdkMeterProvider = sdkMeterProviderBuilder.build(); @@ -95,10 +99,13 @@ void enableGrpcMetrics( InstantiatingGrpcChannelProvider.Builder channelProviderBuilder, String projectId, @Nullable Credentials credentials, - @Nullable String monitoringHost) { + @Nullable String monitoringHost, + String universeDomain) { GrpcOpenTelemetry grpcOpenTelemetry = GrpcOpenTelemetry.newBuilder() - .sdk(this.getOrCreateOpenTelemetry(projectId, credentials, monitoringHost)) + .sdk( + this.getOrCreateOpenTelemetry( + projectId, credentials, monitoringHost, universeDomain)) .enableMetrics(BuiltInMetricsConstant.GRPC_METRICS_TO_ENABLE) // Disable gRPCs default metrics as they are not needed for Spanner. .disableMetrics(BuiltInMetricsConstant.GRPC_METRICS_ENABLED_BY_DEFAULT) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java index b43cf43b250..99fc4daeab3 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java @@ -27,6 +27,7 @@ import com.google.cloud.monitoring.v3.MetricServiceClient; import com.google.cloud.monitoring.v3.MetricServiceSettings; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.MoreExecutors; import com.google.monitoring.v3.CreateTimeSeriesRequest; @@ -71,7 +72,10 @@ class SpannerCloudMonitoringExporter implements MetricExporter { private final String spannerProjectId; static SpannerCloudMonitoringExporter create( - String projectId, @Nullable Credentials credentials, @Nullable String monitoringHost) + String projectId, + @Nullable Credentials credentials, + @Nullable String monitoringHost, + String universeDomain) throws IOException { MetricServiceSettings.Builder settingsBuilder = MetricServiceSettings.newBuilder(); CredentialsProvider credentialsProvider; @@ -84,6 +88,9 @@ static SpannerCloudMonitoringExporter create( if (monitoringHost != null) { settingsBuilder.setEndpoint(monitoringHost); } + if (Strings.isNullOrEmpty(universeDomain)) { + settingsBuilder.setUniverseDomain(universeDomain); + } Duration timeout = Duration.ofMinutes(1); // TODO: createServiceTimeSeries needs special handling if the request failed. Leaving 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 2fcb9c09348..da7a3114a63 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 @@ -120,7 +120,8 @@ public class SpannerOptions extends ServiceOptions { private static final String PG_ADAPTER_CLIENT_LIB_TOKEN = "pg-adapter"; private static final String API_SHORT_NAME = "Spanner"; - private static final String DEFAULT_HOST = "https://spanner.googleapis.com"; + private static final String SPANNER_SERVICE_NAME = "spanner"; + private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default"; private static final ImmutableSet SCOPES = @@ -780,9 +781,18 @@ protected SpannerOptions(Builder builder) { databaseRole = builder.databaseRole; sessionLabels = builder.sessionLabels; try { - spannerStubSettings = builder.spannerStubSettingsBuilder.build(); - instanceAdminStubSettings = builder.instanceAdminStubSettingsBuilder.build(); - databaseAdminStubSettings = builder.databaseAdminStubSettingsBuilder.build(); + spannerStubSettings = + builder.spannerStubSettingsBuilder.setUniverseDomain(getResolvedUniverseDomain()).build(); + instanceAdminStubSettings = + builder + .instanceAdminStubSettingsBuilder + .setUniverseDomain(getResolvedUniverseDomain()) + .build(); + databaseAdminStubSettings = + builder + .databaseAdminStubSettingsBuilder + .setUniverseDomain(getResolvedUniverseDomain()) + .build(); } catch (IOException e) { throw SpannerExceptionFactory.newSpannerException(e); } @@ -824,6 +834,11 @@ protected SpannerOptions(Builder builder) { defaultTransactionOptions = builder.defaultTransactionOptions; } + private String getResolvedUniverseDomain() { + String universeDomain = getUniverseDomain(); + return Strings.isNullOrEmpty(universeDomain) ? GOOGLE_DEFAULT_UNIVERSE : universeDomain; + } + /** * The environment to read configuration values from. The default implementation uses environment * variables. @@ -871,6 +886,8 @@ default boolean isEnableEndToEndTracing() { return false; } + @Deprecated + @ObsoleteApi("This will be removed in upcoming version without breaking change release. You should use universalDomain to configure the built-in metrics endpoint for a partner universe.") default String getMonitoringHost() { return null; } @@ -1665,6 +1682,8 @@ public Builder setBuiltInMetricsEnabled(boolean enableBuiltInMetrics) { } /** Sets the monitoring host to be used for Built-in client side metrics */ + @Deprecated + @ObsoleteApi("This will be removed in upcoming version without breaking change release. You should use universalDomain to configure the built-in metrics endpoint for a partner universe.") public Builder setMonitoringHost(String monitoringHost) { this.monitoringHost = monitoringHost; return this; @@ -2035,7 +2054,11 @@ public ApiTracerFactory getApiTracerFactory() { public void enablegRPCMetrics(InstantiatingGrpcChannelProvider.Builder channelProviderBuilder) { if (SpannerOptions.environment.isEnableGRPCBuiltInMetrics()) { this.builtInMetricsProvider.enableGrpcMetrics( - channelProviderBuilder, this.getProjectId(), getCredentials(), this.monitoringHost); + channelProviderBuilder, + this.getProjectId(), + getCredentials(), + this.monitoringHost, + getUniverseDomain()); } } @@ -2081,7 +2104,7 @@ private ApiTracerFactory getDefaultApiTracerFactory() { private ApiTracerFactory createMetricsApiTracerFactory() { OpenTelemetry openTelemetry = this.builtInMetricsProvider.getOrCreateOpenTelemetry( - this.getProjectId(), getCredentials(), this.monitoringHost); + this.getProjectId(), getCredentials(), this.monitoringHost, getUniverseDomain()); return openTelemetry != null ? new BuiltInMetricsTracerFactory( @@ -2181,7 +2204,11 @@ public static GrpcTransportOptions getDefaultGrpcTransportOptions() { @Override protected String getDefaultHost() { - return DEFAULT_HOST; + String universeDomain = getUniverseDomain(); + if (Strings.isNullOrEmpty(universeDomain)) { + universeDomain = GOOGLE_DEFAULT_UNIVERSE; + } + return String.format("https://%s.%s", SPANNER_SERVICE_NAME, universeDomain); } private static class SpannerDefaults implements ServiceDefaults { 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 2c86192443d..dbd20967753 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 @@ -49,6 +49,7 @@ import static com.google.cloud.spanner.connection.ConnectionProperties.TRACING_PREFIX; import static com.google.cloud.spanner.connection.ConnectionProperties.TRACK_CONNECTION_LEAKS; import static com.google.cloud.spanner.connection.ConnectionProperties.TRACK_SESSION_LEAKS; +import static com.google.cloud.spanner.connection.ConnectionProperties.UNIVERSE_DOMAIN; import static com.google.cloud.spanner.connection.ConnectionProperties.USER_AGENT; import static com.google.cloud.spanner.connection.ConnectionProperties.USE_AUTO_SAVEPOINTS_FOR_EMULATOR; import static com.google.cloud.spanner.connection.ConnectionProperties.USE_PLAIN_TEXT; @@ -769,7 +770,7 @@ static String determineHost( boolean autoConfigEmulator, boolean usePlainText, Map environment) { - String host; + String host = null; if (Objects.equals(endpoint, DEFAULT_ENDPOINT) && matcher.group(Builder.HOST_GROUP) == null) { if (autoConfigEmulator) { if (Strings.isNullOrEmpty(environment.get(SPANNER_EMULATOR_HOST_ENV_VAR))) { @@ -777,8 +778,6 @@ static String determineHost( } else { return PLAIN_TEXT_PROTOCOL + "//" + environment.get(SPANNER_EMULATOR_HOST_ENV_VAR); } - } else { - return DEFAULT_HOST; } } else if (!Objects.equals(endpoint, DEFAULT_ENDPOINT)) { // Add '//' at the start of the endpoint to conform to the standard URL specification. @@ -792,6 +791,9 @@ static String determineHost( host = String.format("%s:15000", host); } } + if (host == null) { + return null; + } if (usePlainText) { return PLAIN_TEXT_PROTOCOL + host; } @@ -1086,6 +1088,10 @@ Boolean isEnableDirectAccess() { return getInitialConnectionPropertyValue(ENABLE_DIRECT_ACCESS); } + String getUniverseDomain() { + return getInitialConnectionPropertyValue(UNIVERSE_DOMAIN); + } + 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 febccc3a15c..10797df88c3 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 @@ -200,6 +200,14 @@ public class ConnectionProperties { BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); + static final ConnectionProperty UNIVERSE_DOMAIN = + create( + "universeDomain", + "Configure the connection to try to connect to Spanner using " + + "a different partner Google Universe than GDU (googleapis.com).", + "googleapis.com", + StringValueConverter.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 b21e8c84db0..c1cf3ae6790 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 @@ -165,6 +165,7 @@ static class SpannerPoolKey { private final String clientCertificateKey; private final boolean isExperimentalHost; private final Boolean enableDirectAccess; + private final String universeDomain; @VisibleForTesting static SpannerPoolKey of(ConnectionOptions options) { @@ -200,6 +201,7 @@ private SpannerPoolKey(ConnectionOptions options) throws IOException { this.clientCertificateKey = options.getClientCertificateKey(); this.isExperimentalHost = options.isExperimentalHost(); this.enableDirectAccess = options.isEnableDirectAccess(); + this.universeDomain = options.getUniverseDomain(); } @Override @@ -226,7 +228,8 @@ public boolean equals(Object o) { && Objects.equals(this.clientCertificate, other.clientCertificate) && Objects.equals(this.clientCertificateKey, other.clientCertificateKey) && Objects.equals(this.isExperimentalHost, other.isExperimentalHost) - && Objects.equals(this.enableDirectAccess, other.enableDirectAccess); + && Objects.equals(this.enableDirectAccess, other.enableDirectAccess) + && Objects.equals(this.universeDomain, other.universeDomain); } @Override @@ -249,7 +252,8 @@ public int hashCode() { this.clientCertificate, this.clientCertificateKey, this.isExperimentalHost, - this.enableDirectAccess); + this.enableDirectAccess, + this.universeDomain); } } @@ -419,6 +423,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { if (key.enableDirectAccess != null) { builder.setEnableDirectAccess(key.enableDirectAccess); } + if (key.universeDomain != null) { + builder.setUniverseDomain(key.universeDomain); + } if (options.getConfigurator() != null) { options.getConfigurator().configure(builder); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java index 86053d1b71b..644eb191f25 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java @@ -454,7 +454,7 @@ public void testExportingHistogramDataWithExemplars() { @Test public void getAggregationTemporality() throws IOException { SpannerCloudMonitoringExporter actualExporter = - SpannerCloudMonitoringExporter.create(projectId, null, null); + SpannerCloudMonitoringExporter.create(projectId, null, null, null); assertThat(actualExporter.getAggregationTemporality(InstrumentType.COUNTER)) .isEqualTo(AggregationTemporality.CUMULATIVE); } 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 9fc065f944c..ad710bc9d79 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 @@ -761,11 +761,11 @@ public void testMonitoringHost() { String metricsEndpoint = "test-endpoint:443"; assertNull(SpannerOptions.newBuilder().setProjectId("p").build().getMonitoringHost()); assertThat( - SpannerOptions.newBuilder() - .setProjectId("p") - .setMonitoringHost(metricsEndpoint) - .build() - .getMonitoringHost()) + SpannerOptions.newBuilder() + .setProjectId("p") + .setMonitoringHost(metricsEndpoint) + .build() + .getMonitoringHost()) .isEqualTo(metricsEndpoint); } 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 e274c0e6c04..d3eed2f3b5e 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 @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; import com.google.api.gax.core.CredentialsProvider; import com.google.api.gax.core.NoCredentialsProvider; @@ -36,6 +37,7 @@ import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.NoCredentials; import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerOptions; import com.google.common.collect.ImmutableMap; @@ -47,6 +49,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import org.junit.Test; import org.junit.function.ThrowingRunnable; @@ -1329,4 +1332,83 @@ public void testEnableDirectAccess() { "spanner://localhost:15000/projects/default/instances/default/databases/singers-db;usePlainText=true;enableDirectAccess=true"); assertTrue(builderWithDirectPathParam.build().isEnableDirectAccess()); } + + @Test + public void testUniverseDomain() { + ConnectionImpl connection = mock(ConnectionImpl.class); + + // No universeDomain + AtomicBoolean executedConfigurator = new AtomicBoolean(false); + ConnectionOptions optionsWithNoUniverseDomainParam = + ConnectionOptions.newBuilder() + .setUri("cloudspanner:/projects/default/instances/default/databases/singers-db") + .setConfigurator( + optionsBuilder -> { + executedConfigurator.set(true); + SpannerOptions spannerOptions = optionsBuilder.build(); + assertEquals("googleapis.com", spannerOptions.getUniverseDomain()); + assertEquals("https://spanner.googleapis.com", spannerOptions.getHost()); + }) + .build(); + Spanner spanner = SpannerPool.INSTANCE.getSpanner(optionsWithNoUniverseDomainParam, connection); + spanner.close(); + assertTrue(executedConfigurator.get()); + + // only configuring universal domain + executedConfigurator.set(false); + ConnectionOptions optionsWithUniverseDomainParam = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner:/projects/default/instances/default/databases/singers-db;universeDomain=abc.goog") + .setConfigurator( + optionsBuilder -> { + executedConfigurator.set(true); + SpannerOptions spannerOptions = optionsBuilder.build(); + assertEquals("abc.goog", spannerOptions.getUniverseDomain()); + assertEquals("https://spanner.abc.goog", spannerOptions.getHost()); + }) + .build(); + spanner = SpannerPool.INSTANCE.getSpanner(optionsWithUniverseDomainParam, connection); + spanner.close(); + assertTrue(executedConfigurator.get()); + + // configuring both universal domain and host + executedConfigurator.set(false); + ConnectionOptions optionsWithHostAndUniverseDomainParam = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner://spanner.abc.goog/projects/default/instances/default/databases/singers-db;universeDomain=abc.goog") + .setConfigurator( + optionsBuilder -> { + executedConfigurator.set(true); + SpannerOptions spannerOptions = optionsBuilder.build(); + assertEquals("abc.goog", spannerOptions.getUniverseDomain()); + assertEquals("https://spanner.abc.goog", spannerOptions.getHost()); + }) + .build(); + spanner = SpannerPool.INSTANCE.getSpanner(optionsWithHostAndUniverseDomainParam, connection); + spanner.close(); + assertTrue(executedConfigurator.get()); + + // configuring both universal domain and host(localhost) + executedConfigurator.set(false); + ConnectionOptions optionsWithLocalHostAndUniverseDomainParam = + ConnectionOptions.newBuilder() + .setUri( + "cloudspanner://localhost:15000/projects/default/instances/default/databases/singers-db;usePlainText=true;universeDomain=abc.goog") + .setConfigurator( + optionsBuilder -> { + executedConfigurator.set(true); + SpannerOptions spannerOptions = optionsBuilder.build(); + assertEquals("abc.goog", spannerOptions.getUniverseDomain()); + assertEquals("http://localhost:15000", spannerOptions.getHost()); + }) + .build(); + spanner = + SpannerPool.INSTANCE.getSpanner(optionsWithLocalHostAndUniverseDomainParam, connection); + spanner.close(); + assertTrue(executedConfigurator.get()); + + connection.close(); + } } From 1c1756f63618a4cc3f190f6b302877977fcd02e0 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 24 Sep 2025 11:27:19 +0000 Subject: [PATCH 2/3] Fix test cases --- .../SpannerCloudMonitoringExporter.java | 7 ++++++- .../google/cloud/spanner/SpannerOptions.java | 8 +++++-- .../spanner/connection/ConnectionOptions.java | 3 ++- .../SpannerCloudMonitoringExporterTest.java | 21 +++++++++++++++++++ .../cloud/spanner/SpannerOptionsTest.java | 10 ++++----- .../connection/ConnectionOptionsTest.java | 15 ++++++++----- 6 files changed, 50 insertions(+), 14 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java index 99fc4daeab3..40202a0eefe 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerCloudMonitoringExporter.java @@ -88,7 +88,7 @@ static SpannerCloudMonitoringExporter create( if (monitoringHost != null) { settingsBuilder.setEndpoint(monitoringHost); } - if (Strings.isNullOrEmpty(universeDomain)) { + if (!Strings.isNullOrEmpty(universeDomain)) { settingsBuilder.setUniverseDomain(universeDomain); } @@ -117,6 +117,11 @@ public CompletableResultCode export(@Nonnull Collection collection) return exportSpannerClientMetrics(collection); } + @VisibleForTesting + MetricServiceClient getMetricServiceClient() { + return client; + } + /** Export client built in metrics */ private CompletableResultCode exportSpannerClientMetrics(Collection collection) { // Filter spanner metrics. Only include metrics that contain a valid project. 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 da7a3114a63..74168291275 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 @@ -887,7 +887,9 @@ default boolean isEnableEndToEndTracing() { } @Deprecated - @ObsoleteApi("This will be removed in upcoming version without breaking change release. You should use universalDomain to configure the built-in metrics endpoint for a partner universe.") + @ObsoleteApi( + "This will be removed in an upcoming version without a major version bump. You should use" + + " universalDomain to configure the built-in metrics endpoint for a partner universe.") default String getMonitoringHost() { return null; } @@ -1683,7 +1685,9 @@ public Builder setBuiltInMetricsEnabled(boolean enableBuiltInMetrics) { /** Sets the monitoring host to be used for Built-in client side metrics */ @Deprecated - @ObsoleteApi("This will be removed in upcoming version without breaking change release. You should use universalDomain to configure the built-in metrics endpoint for a partner universe.") + @ObsoleteApi( + "This will be removed in an upcoming version without a major version bump. You should use" + + " universalDomain to configure the built-in metrics endpoint for a partner universe.") public Builder setMonitoringHost(String monitoringHost) { this.monitoringHost = monitoringHost; return this; 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 dbd20967753..741fa5db2b1 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 @@ -77,6 +77,7 @@ import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.connection.StatementExecutor.StatementExecutorType; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.base.Suppliers; @@ -970,7 +971,7 @@ public TransportChannelProvider getChannelProvider() { return null; } try { - URL url = new URL(host); + URL url = new URL(MoreObjects.firstNonNull(host, DEFAULT_HOST)); ExternalChannelProvider provider = ExternalChannelProvider.class.cast(Class.forName(channelProvider).newInstance()); return provider.getChannelProvider(url.getHost(), url.getPort()); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java index 644eb191f25..c52cab0546c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerCloudMonitoringExporterTest.java @@ -30,6 +30,7 @@ import static com.google.cloud.spanner.BuiltInMetricsConstant.OPERATION_LATENCIES_NAME; import static com.google.cloud.spanner.BuiltInMetricsConstant.PROJECT_ID_KEY; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -39,6 +40,7 @@ import com.google.api.core.ApiFutures; import com.google.api.gax.rpc.UnaryCallable; import com.google.cloud.monitoring.v3.MetricServiceClient; +import com.google.cloud.monitoring.v3.MetricServiceSettings; import com.google.cloud.monitoring.v3.stub.MetricServiceStub; import com.google.common.collect.ImmutableList; import com.google.monitoring.v3.CreateTimeSeriesRequest; @@ -459,6 +461,25 @@ public void getAggregationTemporality() throws IOException { .isEqualTo(AggregationTemporality.CUMULATIVE); } + @Test + public void testUniverseDomain() throws IOException { + SpannerCloudMonitoringExporter actualExporter = + SpannerCloudMonitoringExporter.create(projectId, null, null, "abc.goog"); + MetricServiceSettings metricServiceSettings = + actualExporter.getMetricServiceClient().getSettings(); + + assertEquals("abc.goog", metricServiceSettings.getUniverseDomain()); + assertEquals("monitoring.abc.goog:443", metricServiceSettings.getEndpoint()); + + actualExporter = + SpannerCloudMonitoringExporter.create( + projectId, null, "monitoringa.abc.goog:443", "abc.goog"); + metricServiceSettings = actualExporter.getMetricServiceClient().getSettings(); + + assertEquals("abc.goog", metricServiceSettings.getUniverseDomain()); + assertEquals("monitoringa.abc.goog:443", metricServiceSettings.getEndpoint()); + } + private static class FakeMetricServiceClient extends MetricServiceClient { protected FakeMetricServiceClient(MetricServiceStub stub) { 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 ad710bc9d79..9fc065f944c 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 @@ -761,11 +761,11 @@ public void testMonitoringHost() { String metricsEndpoint = "test-endpoint:443"; assertNull(SpannerOptions.newBuilder().setProjectId("p").build().getMonitoringHost()); assertThat( - SpannerOptions.newBuilder() - .setProjectId("p") - .setMonitoringHost(metricsEndpoint) - .build() - .getMonitoringHost()) + SpannerOptions.newBuilder() + .setProjectId("p") + .setMonitoringHost(metricsEndpoint) + .build() + .getMonitoringHost()) .isEqualTo(metricsEndpoint); } 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 d3eed2f3b5e..e9af95d9de0 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 @@ -60,7 +60,7 @@ public class ConnectionOptionsTest { private static final String FILE_TEST_PATH = Objects.requireNonNull(ConnectionOptionsTest.class.getResource("test-key.json")).getFile(); - private static final String DEFAULT_HOST = "https://spanner.googleapis.com"; + private static final String DEFAULT_HOST = null; private static final String TEST_PROJECT = "test-project-123"; private static final String TEST_INSTANCE = "test-instance-123"; private static final String TEST_DATABASE = "test-database-123"; @@ -1341,7 +1341,8 @@ public void testUniverseDomain() { AtomicBoolean executedConfigurator = new AtomicBoolean(false); ConnectionOptions optionsWithNoUniverseDomainParam = ConnectionOptions.newBuilder() - .setUri("cloudspanner:/projects/default/instances/default/databases/singers-db") + .setUri( + "cloudspanner:/projects/default/instances/default/databases/singers-db?usePlainText=true") .setConfigurator( optionsBuilder -> { executedConfigurator.set(true); @@ -1352,6 +1353,7 @@ public void testUniverseDomain() { .build(); Spanner spanner = SpannerPool.INSTANCE.getSpanner(optionsWithNoUniverseDomainParam, connection); spanner.close(); + SpannerPool.INSTANCE.removeConnection(optionsWithNoUniverseDomainParam, connection); assertTrue(executedConfigurator.get()); // only configuring universal domain @@ -1359,7 +1361,7 @@ public void testUniverseDomain() { ConnectionOptions optionsWithUniverseDomainParam = ConnectionOptions.newBuilder() .setUri( - "cloudspanner:/projects/default/instances/default/databases/singers-db;universeDomain=abc.goog") + "cloudspanner:/projects/default/instances/default/databases/singers-db;universeDomain=abc.goog;usePlainText=true") .setConfigurator( optionsBuilder -> { executedConfigurator.set(true); @@ -1370,6 +1372,7 @@ public void testUniverseDomain() { .build(); spanner = SpannerPool.INSTANCE.getSpanner(optionsWithUniverseDomainParam, connection); spanner.close(); + SpannerPool.INSTANCE.removeConnection(optionsWithUniverseDomainParam, connection); assertTrue(executedConfigurator.get()); // configuring both universal domain and host @@ -1377,17 +1380,18 @@ public void testUniverseDomain() { ConnectionOptions optionsWithHostAndUniverseDomainParam = ConnectionOptions.newBuilder() .setUri( - "cloudspanner://spanner.abc.goog/projects/default/instances/default/databases/singers-db;universeDomain=abc.goog") + "cloudspanner://spanner.abc.goog/projects/default/instances/default/databases/singers-db;universeDomain=abc.goog;usePlainText=true") .setConfigurator( optionsBuilder -> { executedConfigurator.set(true); SpannerOptions spannerOptions = optionsBuilder.build(); assertEquals("abc.goog", spannerOptions.getUniverseDomain()); - assertEquals("https://spanner.abc.goog", spannerOptions.getHost()); + assertEquals("http://spanner.abc.goog", spannerOptions.getHost()); }) .build(); spanner = SpannerPool.INSTANCE.getSpanner(optionsWithHostAndUniverseDomainParam, connection); spanner.close(); + SpannerPool.INSTANCE.removeConnection(optionsWithHostAndUniverseDomainParam, connection); assertTrue(executedConfigurator.get()); // configuring both universal domain and host(localhost) @@ -1407,6 +1411,7 @@ public void testUniverseDomain() { spanner = SpannerPool.INSTANCE.getSpanner(optionsWithLocalHostAndUniverseDomainParam, connection); spanner.close(); + SpannerPool.INSTANCE.removeConnection(optionsWithLocalHostAndUniverseDomainParam, connection); assertTrue(executedConfigurator.get()); connection.close(); From dfccf888571d8ac8b321bc83d541b5f049097429 Mon Sep 17 00:00:00 2001 From: Sakthivel Subramanian Date: Wed, 24 Sep 2025 18:45:41 +0530 Subject: [PATCH 3/3] Address comments --- .../main/java/com/google/cloud/spanner/SpannerOptions.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 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 74168291275..af4ed58bad1 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 @@ -781,17 +781,18 @@ protected SpannerOptions(Builder builder) { databaseRole = builder.databaseRole; sessionLabels = builder.sessionLabels; try { + String resolvedUniversalDomain = getResolvedUniverseDomain(); spannerStubSettings = - builder.spannerStubSettingsBuilder.setUniverseDomain(getResolvedUniverseDomain()).build(); + builder.spannerStubSettingsBuilder.setUniverseDomain(resolvedUniversalDomain).build(); instanceAdminStubSettings = builder .instanceAdminStubSettingsBuilder - .setUniverseDomain(getResolvedUniverseDomain()) + .setUniverseDomain(resolvedUniversalDomain) .build(); databaseAdminStubSettings = builder .databaseAdminStubSettingsBuilder - .setUniverseDomain(getResolvedUniverseDomain()) + .setUniverseDomain(resolvedUniversalDomain) .build(); } catch (IOException e) { throw SpannerExceptionFactory.newSpannerException(e);