Skip to content

Commit 30956a3

Browse files
sagnghosrahul2393
andauthored
chore(spanner): deprecate experimental host option/parameter to replace with Spanner Omni (#13236)
This PR deprecates the `experimentalHost` / `isExperimentalHost` option and property across the Cloud Spanner client libraries (`SpannerOptions` and `ConnectionOptions`) and introduces a unified `type` option and connection parameter (supporting the values `cloud`, `omni`,). To connect to a Spanner Omni instance, users should now use the `setType(InstanceType.OMNI)` builder option or pass the connection string query parameter `?type=omni`. Refer to discussion: [Spanner Client Library Configuration for Omni](https://docs.google.com/document/d/1n-rsHkNAEwQbaICHKlFhPl3NN8a3QuOknn_tEhaowZQ/edit?resourcekey=0-LnCt0jSFXCoSuVttphq3FQ&tab=t.0) Currently, setting the type to `cloud` or `emulator` acts as a **no-op** (since the client library automatically resolves default behaviors for standard cloud and emulator setups). However, introducing this option makes the client library highly extendable, enabling us to enforce or optimize custom behaviors tailored to specific Spanner instance types in the future. --------- Co-authored-by: rahul2393 <rahulyadavsep92@gmail.com>
1 parent dc2feed commit 30956a3

27 files changed

Lines changed: 428 additions & 141 deletions

java-spanner/google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -910,21 +910,21 @@
910910
<method>java.lang.Object runTransaction(com.google.cloud.spanner.connection.Connection$TransactionCallable)</method>
911911
</difference>
912912

913-
<!-- Added experimental host option -->
913+
<!-- Added Spanner Omni option -->
914914
<difference>
915915
<differenceType>7012</differenceType>
916916
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
917-
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExperimentalHostCredentials()</method>
917+
<method>com.google.auth.oauth2.GoogleCredentials getDefaultSpannerOmniCredentials()</method>
918918
</difference>
919919
<difference>
920920
<differenceType>7002</differenceType>
921921
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
922-
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExternalHostCredentials()</method>
922+
<method>com.google.auth.oauth2.GoogleCredentials getDefaultSpannerOmniCredentials()</method>
923923
</difference>
924924
<difference>
925925
<differenceType>7002</differenceType>
926926
<className>com/google/cloud/spanner/SpannerOptions</className>
927-
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExternalHostCredentialsFromSysEnv()</method>
927+
<method>com.google.auth.oauth2.GoogleCredentials getDefaultSpannerOmniCredentialsFromSysEnv()</method>
928928
</difference>
929929

930930
<!-- Default sequence kind -->

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java

Lines changed: 93 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,8 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
127127
private static final String API_SHORT_NAME = "Spanner";
128128
private static final String SPANNER_SERVICE_NAME = "spanner";
129129
private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com";
130-
private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default";
130+
public static final String SPANNER_OMNI_PROJECT_ID = "default";
131+
public static final String DEFAULT_SPANNER_OMNI_INSTANCE_ID = "default";
131132

132133
static final ImmutableSet<String> SCOPES =
133134
ImmutableSet.of(
@@ -318,6 +319,15 @@ enum TracingFramework {
318319
OPEN_TELEMETRY
319320
}
320321

322+
/**
323+
* Specifies the type of Spanner instance to connect to (CLOUD or OMNI). Setting it to OMNI is
324+
* mandatory when connecting to a Spanner Omni instance.
325+
*/
326+
public enum InstanceType {
327+
CLOUD,
328+
OMNI
329+
}
330+
321331
private static final Object lock = new Object();
322332

323333
@GuardedBy("lock")
@@ -871,14 +881,20 @@ public static CloseableExecutorProvider createAsyncExecutorProvider(
871881
}
872882

873883
protected SpannerOptions(Builder builder) {
874-
super(SpannerFactory.class, SpannerRpcFactory.class, builder, new SpannerDefaults());
884+
super(
885+
SpannerFactory.class,
886+
SpannerRpcFactory.class,
887+
Builder.prepareBuilder(builder),
888+
new SpannerDefaults());
875889
numChannels = builder.numChannels == null ? DEFAULT_CHANNELS : builder.numChannels;
876890
Preconditions.checkArgument(
877891
numChannels >= 1 && numChannels <= MAX_CHANNELS,
878892
"Number of channels must fall in the range [1, %s], found: %s",
879893
MAX_CHANNELS,
880894
numChannels);
881-
895+
Preconditions.checkArgument(
896+
builder.instanceType != InstanceType.OMNI || !Strings.isNullOrEmpty(builder.host),
897+
"Host must be set for connecting to Spanner Omni instances");
882898
transportChannelExecutorThreadNameFormat = builder.transportChannelExecutorThreadNameFormat;
883899
channelProvider = builder.channelProvider;
884900
channelEndpointCacheFactory = builder.channelEndpointCacheFactory;
@@ -928,16 +944,17 @@ protected SpannerOptions(Builder builder) {
928944

929945
// Dynamic channel pooling is disabled by default.
930946
// It is only enabled when:
931-
// 1. enableDynamicChannelPool() was explicitly called (or experimentalHost is set and DCP was
947+
// 1. enableDynamicChannelPool() was explicitly called (or instance is set to OMNI and DCP was
932948
// not explicitly disabled), AND
933949
// 2. grpc-gcp extension is enabled, AND
934950
// 3. numChannels was not explicitly set
935951
boolean dcpRequested =
936952
builder.dynamicChannelPoolEnabled != null
937953
? builder.dynamicChannelPoolEnabled
938-
: builder.experimentalHost != null;
954+
: builder.instanceType == InstanceType.OMNI;
939955
if (dcpRequested) {
940-
// DCP was enabled (explicitly or via experimentalHost), but respect numChannels if set
956+
// DCP was enabled (explicitly or via instance type being OMNI), but respect numChannels if
957+
// set
941958
dynamicChannelPoolEnabled = grpcGcpExtensionEnabled && !builder.numChannelsExplicitlySet;
942959
} else {
943960
// DCP is disabled by default, or was explicitly disabled
@@ -980,14 +997,14 @@ protected SpannerOptions(Builder builder) {
980997
openTelemetry = builder.openTelemetry;
981998
enableApiTracing = builder.enableApiTracing;
982999
enableExtendedTracing = builder.enableExtendedTracing;
983-
if (builder.experimentalHost != null) {
1000+
if (builder.instanceType == InstanceType.OMNI) {
9841001
enableBuiltInMetrics = false;
9851002
} else {
9861003
enableBuiltInMetrics = builder.enableBuiltInMetrics;
9871004
}
988-
// Enable location API when experimental host is set, unless explicitly disabled
1005+
// Enable location API when InstanceType is OMNI, unless explicitly disabled
9891006
// via GOOGLE_SPANNER_EXPERIMENTAL_LOCATION_API=false.
990-
if (builder.experimentalHost != null) {
1007+
if (builder.instanceType == InstanceType.OMNI) {
9911008
String locationApiEnvValue = System.getenv(EXPERIMENTAL_LOCATION_API_ENV_VAR);
9921009
enableLocationApi = locationApiEnvValue == null || Boolean.parseBoolean(locationApiEnvValue);
9931010
} else {
@@ -1091,13 +1108,12 @@ default String getMonitoringHost() {
10911108
return null;
10921109
}
10931110

1094-
default GoogleCredentials getDefaultExperimentalHostCredentials() {
1111+
default GoogleCredentials getDefaultSpannerOmniCredentials() {
10951112
return null;
10961113
}
10971114
}
10981115

1099-
static final String DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS =
1100-
"SPANNER_EXPERIMENTAL_HOST_AUTH_TOKEN";
1116+
static final String DEFAULT_SPANNER_OMNI_CREDENTIALS = "SPANNER_OMNI_AUTH_TOKEN";
11011117

11021118
/**
11031119
* Default implementation of {@link SpannerEnvironment}. Reads all configuration from environment
@@ -1205,14 +1221,33 @@ public String getMonitoringHost() {
12051221
}
12061222

12071223
@Override
1208-
public GoogleCredentials getDefaultExperimentalHostCredentials() {
1209-
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS));
1224+
public GoogleCredentials getDefaultSpannerOmniCredentials() {
1225+
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_OMNI_CREDENTIALS));
12101226
}
12111227
}
12121228

12131229
/** Builder for {@link SpannerOptions} instances. */
12141230
public static class Builder
12151231
extends ServiceOptions.Builder<Spanner, SpannerOptions, SpannerOptions.Builder> {
1232+
private static Builder prepareBuilder(Builder builder) {
1233+
if (builder.instanceType == InstanceType.OMNI) {
1234+
builder.enableBuiltInMetrics = false;
1235+
builder.setProjectId(SPANNER_OMNI_PROJECT_ID);
1236+
builder.configureOmniHost();
1237+
if (builder.sessionPoolOptions == null) {
1238+
builder.sessionPoolOptions =
1239+
SessionPoolOptions.newBuilder().setExperimentalHost().build();
1240+
} else {
1241+
builder.sessionPoolOptions =
1242+
builder.sessionPoolOptions.toBuilder().setExperimentalHost().build();
1243+
}
1244+
if (builder.credentials == null) {
1245+
builder.setCredentials(environment.getDefaultSpannerOmniCredentials());
1246+
}
1247+
}
1248+
return builder;
1249+
}
1250+
12161251
static final int DEFAULT_PREFETCH_CHUNKS = 4;
12171252
static final QueryOptions DEFAULT_QUERY_OPTIONS = QueryOptions.getDefaultInstance();
12181253
static final DecodeMode DEFAULT_DECODE_MODE = DecodeMode.DIRECT;
@@ -1283,10 +1318,11 @@ public static class Builder
12831318
private boolean enableLocationApi = SpannerOptions.environment.isEnableLocationApi();
12841319
private String monitoringHost = SpannerOptions.environment.getMonitoringHost();
12851320
private SslContext mTLSContext = null;
1286-
private String experimentalHost = null;
12871321
private boolean usePlainText = false;
12881322
private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance();
12891323
private RequestOptions.ClientContext clientContext;
1324+
private InstanceType instanceType = InstanceType.CLOUD;
1325+
private String host = null;
12901326
private boolean autoTaggingEnabled = false;
12911327
private List<String> autoTaggingPackages = Collections.emptyList();
12921328
private int autoTaggingTracerLimit = 50;
@@ -1829,29 +1865,50 @@ public Builder setDecodeMode(DecodeMode decodeMode) {
18291865
return this;
18301866
}
18311867

1868+
private void configureOmniHost() {
1869+
if (this.instanceType == InstanceType.OMNI
1870+
&& !Strings.isNullOrEmpty(this.host)
1871+
&& this.usePlainText) {
1872+
Preconditions.checkArgument(
1873+
!this.host.startsWith("https:"),
1874+
"Please remove the 'https:' protocol prefix from the host string when using plain text"
1875+
+ " communication");
1876+
if (!this.host.startsWith("http")) {
1877+
this.host = "http://" + this.host;
1878+
}
1879+
}
1880+
}
1881+
18321882
@Override
18331883
public Builder setHost(String host) {
1834-
super.setHost(host);
1884+
this.host = host;
1885+
configureOmniHost();
1886+
super.setHost(this.host);
18351887
// Setting a host should override any SPANNER_EMULATOR_HOST setting.
18361888
setEmulatorHost(null);
18371889
return this;
18381890
}
18391891

1892+
/**
1893+
* @deprecated Use {@link #setType(InstanceType)} instead.
1894+
*/
1895+
@Deprecated
1896+
@ObsoleteApi("Use setHost(String).setType(InstanceType.OMNI) instead")
18401897
@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676")
18411898
public Builder setExperimentalHost(String host) {
1842-
if (this.usePlainText) {
1843-
Preconditions.checkArgument(
1844-
!host.startsWith("https:"),
1845-
"Please remove the 'https:' protocol prefix from the host string when using plain text"
1846-
+ " communication");
1847-
if (!host.startsWith("http")) {
1848-
host = "http://" + host;
1849-
}
1899+
if (!Strings.isNullOrEmpty(host)) {
1900+
setType(InstanceType.OMNI);
18501901
}
1851-
super.setHost(host);
1852-
super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID);
1853-
setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build());
1854-
this.experimentalHost = host;
1902+
setHost(host);
1903+
return this;
1904+
}
1905+
1906+
/**
1907+
* Specifies the type of Spanner instance to connect to (CLOUD or OMNI). Setting it to OMNI is
1908+
* mandatory when connecting to a Spanner Omni instance.
1909+
*/
1910+
public Builder setType(InstanceType instanceType) {
1911+
this.instanceType = instanceType;
18551912
return this;
18561913
}
18571914

@@ -1953,14 +2010,13 @@ public Builder setEmulatorHost(String emulatorHost) {
19532010
}
19542011

19552012
/**
1956-
* Configures mTLS authentication using the provided client certificate and key files. mTLS is
1957-
* only supported for experimental spanner hosts.
2013+
* Configures mTLS authentication using the provided client certificate and key files. mTLS via
2014+
* useClientCert is only supported for Spanner Omni instances.
19582015
*
19592016
* @param clientCertificate Path to the client certificate file.
19602017
* @param clientCertificateKey Path to the client private key file.
19612018
* @throws SpannerException If an error occurs while configuring the mTLS context
19622019
*/
1963-
@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3574")
19642020
public Builder useClientCert(String clientCertificate, String clientCertificateKey) {
19652021
try {
19662022
this.mTLSContext =
@@ -1978,14 +2034,14 @@ public Builder useClientCert(String clientCertificate, String clientCertificateK
19782034
* credentials to {@link com.google.cloud.NoCredentials} to avoid sending authentication over an
19792035
* unsecured channel.
19802036
*/
1981-
@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/4264")
19822037
public Builder usePlainText() {
19832038
this.usePlainText = true;
19842039
this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext)
19852040
.setCredentials(NoCredentials.getInstance());
1986-
if (this.experimentalHost != null) {
2041+
if (this.instanceType == InstanceType.OMNI) {
19872042
// Re-apply host settings to ensure http:// is prepended.
1988-
setExperimentalHost(this.experimentalHost);
2043+
configureOmniHost();
2044+
super.setHost(this.host);
19892045
}
19902046
return this;
19912047
}
@@ -2191,7 +2247,7 @@ public Builder setAutoTaggingTracerLimit(int autoTaggingTracerLimit) {
21912247
@Override
21922248
public SpannerOptions build() {
21932249
// Set the host of emulator has been set.
2194-
if (emulatorHost != null && experimentalHost == null) {
2250+
if (emulatorHost != null && this.instanceType != InstanceType.OMNI) {
21952251
if (!emulatorHost.startsWith("http")) {
21962252
emulatorHost = "http://" + emulatorHost;
21972253
}
@@ -2201,8 +2257,6 @@ public SpannerOptions build() {
22012257
this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext);
22022258
// As we are using plain text, we should never send any credentials.
22032259
this.setCredentials(NoCredentials.getInstance());
2204-
} else if (experimentalHost != null && credentials == null) {
2205-
credentials = environment.getDefaultExperimentalHostCredentials();
22062260
}
22072261
if (this.numChannels == null) {
22082262
this.numChannels =
@@ -2244,8 +2298,8 @@ public static void useDefaultEnvironment() {
22442298
}
22452299

22462300
@InternalApi
2247-
public static GoogleCredentials getDefaultExperimentalCredentialsFromSysEnv() {
2248-
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS));
2301+
public static GoogleCredentials getDefaultSpannerOmniCredentialsFromSysEnv() {
2302+
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_OMNI_CREDENTIALS));
22492303
}
22502304

22512305
private static @Nullable GoogleCredentials getOAuthTokenFromFile(@Nullable String file) {

java-spanner/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementValueConverters.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.cloud.spanner.Options.RpcPriority;
2727
import com.google.cloud.spanner.SpannerException;
2828
import com.google.cloud.spanner.SpannerExceptionFactory;
29+
import com.google.cloud.spanner.SpannerOptions;
2930
import com.google.cloud.spanner.TimestampBound;
3031
import com.google.cloud.spanner.TimestampBound.Mode;
3132
import com.google.cloud.spanner.connection.PgTransactionMode.AccessMode;
@@ -895,4 +896,27 @@ public Dialect convert(String value) {
895896
return values.get(value);
896897
}
897898
}
899+
900+
static class InstanceTypeConverter
901+
implements ClientSideStatementValueConverter<SpannerOptions.InstanceType> {
902+
static final InstanceTypeConverter INSTANCE = new InstanceTypeConverter();
903+
904+
private final CaseInsensitiveEnumMap<SpannerOptions.InstanceType> values =
905+
new CaseInsensitiveEnumMap<>(SpannerOptions.InstanceType.class);
906+
907+
private InstanceTypeConverter() {}
908+
909+
/** Constructor needed for reflection. */
910+
public InstanceTypeConverter(String allowedValues) {}
911+
912+
@Override
913+
public Class<SpannerOptions.InstanceType> getParameterClass() {
914+
return SpannerOptions.InstanceType.class;
915+
}
916+
917+
@Override
918+
public SpannerOptions.InstanceType convert(String value) {
919+
return values.get(value);
920+
}
921+
}
898922
}

0 commit comments

Comments
 (0)