Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -910,21 +910,21 @@
<method>java.lang.Object runTransaction(com.google.cloud.spanner.connection.Connection$TransactionCallable)</method>
</difference>

<!-- Added experimental host option -->
<!-- Added Spanner Omni option -->
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExperimentalHostCredentials()</method>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultSpannerOmniCredentials()</method>
</difference>
<difference>
<differenceType>7002</differenceType>
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExternalHostCredentials()</method>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultSpannerOmniCredentials()</method>
</difference>
<difference>
<differenceType>7002</differenceType>
<className>com/google/cloud/spanner/SpannerOptions</className>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExternalHostCredentialsFromSysEnv()</method>
<method>com.google.auth.oauth2.GoogleCredentials getDefaultSpannerOmniCredentialsFromSysEnv()</method>
</difference>

<!-- Default sequence kind -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
private static final String API_SHORT_NAME = "Spanner";
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";
public static final String SPANNER_OMNI_PROJECT_ID = "default";
public static final String DEFAULT_SPANNER_OMNI_INSTANCE_ID = "default";

static final ImmutableSet<String> SCOPES =
ImmutableSet.of(
Expand Down Expand Up @@ -314,6 +315,18 @@ enum TracingFramework {
OPEN_TELEMETRY
}

/**
* Specifies the type of Spanner instance to connect to (CLOUD, OMNI, or EMULATOR). Currently,
* this is a no-op for most types, but setting it to OMNI is mandatory when connecting to a
* Spanner Omni instance.
*/
public enum InstanceType {
UNSPECIFIED,
CLOUD,
Comment thread
sagnghos marked this conversation as resolved.
OMNI,
EMULATOR
Comment thread
sagnghos marked this conversation as resolved.
Outdated
}

private static final Object lock = new Object();

@GuardedBy("lock")
Expand Down Expand Up @@ -874,7 +887,9 @@ protected SpannerOptions(Builder builder) {
"Number of channels must fall in the range [1, %s], found: %s",
MAX_CHANNELS,
numChannels);

Preconditions.checkArgument(
builder.instanceType != InstanceType.OMNI || !Strings.isNullOrEmpty(builder.host),
"Host must be set for connecting to Spanner Omni instances");
transportChannelExecutorThreadNameFormat = builder.transportChannelExecutorThreadNameFormat;
channelProvider = builder.channelProvider;
channelEndpointCacheFactory = builder.channelEndpointCacheFactory;
Expand Down Expand Up @@ -924,16 +939,17 @@ protected SpannerOptions(Builder builder) {

// Dynamic channel pooling is disabled by default.
// It is only enabled when:
// 1. enableDynamicChannelPool() was explicitly called (or experimentalHost is set and DCP was
// 1. enableDynamicChannelPool() was explicitly called (or instance is set to OMNI and DCP was
// not explicitly disabled), AND
// 2. grpc-gcp extension is enabled, AND
// 3. numChannels was not explicitly set
boolean dcpRequested =
builder.dynamicChannelPoolEnabled != null
? builder.dynamicChannelPoolEnabled
: builder.experimentalHost != null;
: builder.instanceType == InstanceType.OMNI;
if (dcpRequested) {
// DCP was enabled (explicitly or via experimentalHost), but respect numChannels if set
// DCP was enabled (explicitly or via instance type being OMNI), but respect numChannels if
// set
dynamicChannelPoolEnabled = grpcGcpExtensionEnabled && !builder.numChannelsExplicitlySet;
} else {
// DCP is disabled by default, or was explicitly disabled
Expand Down Expand Up @@ -976,14 +992,14 @@ protected SpannerOptions(Builder builder) {
openTelemetry = builder.openTelemetry;
enableApiTracing = builder.enableApiTracing;
enableExtendedTracing = builder.enableExtendedTracing;
if (builder.experimentalHost != null) {
if (builder.instanceType == InstanceType.OMNI) {
enableBuiltInMetrics = false;
} else {
enableBuiltInMetrics = builder.enableBuiltInMetrics;
}
// Enable location API when experimental host is set, unless explicitly disabled
// Enable location API when InstanceType is OMNI, unless explicitly disabled
// via GOOGLE_SPANNER_EXPERIMENTAL_LOCATION_API=false.
if (builder.experimentalHost != null) {
if (builder.instanceType == InstanceType.OMNI) {
String locationApiEnvValue = System.getenv(EXPERIMENTAL_LOCATION_API_ENV_VAR);
enableLocationApi = locationApiEnvValue == null || Boolean.parseBoolean(locationApiEnvValue);
} else {
Expand Down Expand Up @@ -1072,13 +1088,12 @@ default String getMonitoringHost() {
return null;
}

default GoogleCredentials getDefaultExperimentalHostCredentials() {
default GoogleCredentials getDefaultSpannerOmniCredentials() {
return null;
}
}

static final String DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS =
"SPANNER_EXPERIMENTAL_HOST_AUTH_TOKEN";
static final String DEFAULT_SPANNER_OMNI_CREDENTIALS = "SPANNER_OMNI_AUTH_TOKEN";

/**
* Default implementation of {@link SpannerEnvironment}. Reads all configuration from environment
Expand Down Expand Up @@ -1174,8 +1189,8 @@ public String getMonitoringHost() {
}

@Override
public GoogleCredentials getDefaultExperimentalHostCredentials() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS));
public GoogleCredentials getDefaultSpannerOmniCredentials() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_OMNI_CREDENTIALS));
}
}

Expand Down Expand Up @@ -1252,10 +1267,11 @@ public static class Builder
private boolean enableLocationApi = SpannerOptions.environment.isEnableLocationApi();
private String monitoringHost = SpannerOptions.environment.getMonitoringHost();
private SslContext mTLSContext = null;
private String experimentalHost = null;
private boolean usePlainText = false;
private TransactionOptions defaultTransactionOptions = TransactionOptions.getDefaultInstance();
private RequestOptions.ClientContext clientContext;
private InstanceType instanceType = InstanceType.UNSPECIFIED;
private String host = null;

private static String createCustomClientLibToken(String token) {
return token + " " + ServiceOptions.getGoogApiClientLibName();
Expand Down Expand Up @@ -1794,27 +1810,48 @@ public Builder setDecodeMode(DecodeMode decodeMode) {

@Override
public Builder setHost(String host) {
super.setHost(host);
this.host = host;
if (this.instanceType == InstanceType.OMNI
Comment thread
sagnghos marked this conversation as resolved.
&& !Strings.isNullOrEmpty(this.host)
&& this.usePlainText) {
Preconditions.checkArgument(
!this.host.startsWith("https:"),
"Please remove the 'https:' protocol prefix from the host string when using plain text"
+ " communication");
if (!this.host.startsWith("http")) {
this.host = "http://" + this.host;
}
}
super.setHost(this.host);
// Setting a host should override any SPANNER_EMULATOR_HOST setting.
setEmulatorHost(null);
return this;
}

/** @deprecated Use {@link #setType(InstanceType)} instead. */
@Deprecated
@ObsoleteApi("Use setHost(String).setType(InstanceType.OMNI) instead")
@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676")
public Builder setExperimentalHost(String 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;
}
if (!Strings.isNullOrEmpty(host)) {
setType(InstanceType.OMNI);
}
setHost(host);
return this;
}

/**
* Specifies the type of Spanner instance to connect to (CLOUD, OMNI, or EMULATOR). Currently,
* this is a no-op for most types, but setting it to OMNI is mandatory when connecting to a
* Spanner Omni instance.
*/
public Builder setType(InstanceType instanceType) {
this.instanceType = instanceType;
if (instanceType == InstanceType.OMNI) {
setBuiltInMetricsEnabled(false);
Comment thread
sakthivelmanii marked this conversation as resolved.
Outdated
super.setProjectId(SPANNER_OMNI_PROJECT_ID);
setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build());
Comment thread
sagnghos marked this conversation as resolved.
Outdated
}
super.setHost(host);
super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID);
setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build());
this.experimentalHost = host;
return this;
}

Expand Down Expand Up @@ -1916,14 +1953,13 @@ public Builder setEmulatorHost(String emulatorHost) {
}

/**
* Configures mTLS authentication using the provided client certificate and key files. mTLS is
* only supported for experimental spanner hosts.
* Configures mTLS authentication using the provided client certificate and key files. mTLS via
* useClientCert is only supported for Spanner Omni instances.
*
* @param clientCertificate Path to the client certificate file.
* @param clientCertificateKey Path to the client private key file.
* @throws SpannerException If an error occurs while configuring the mTLS context
*/
@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3574")
public Builder useClientCert(String clientCertificate, String clientCertificateKey) {
try {
this.mTLSContext =
Expand All @@ -1941,14 +1977,13 @@ public Builder useClientCert(String clientCertificate, String clientCertificateK
* 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() {
this.usePlainText = true;
this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext)
.setCredentials(NoCredentials.getInstance());
if (this.experimentalHost != null) {
if (this.instanceType == InstanceType.OMNI) {
// Re-apply host settings to ensure http:// is prepended.
setExperimentalHost(this.experimentalHost);
setHost(this.host);
}
return this;
}
Expand Down Expand Up @@ -2124,7 +2159,7 @@ public Builder setDefaultClientContext(RequestOptions.ClientContext clientContex
@Override
public SpannerOptions build() {
// Set the host of emulator has been set.
if (emulatorHost != null && experimentalHost == null) {
if (emulatorHost != null && this.instanceType != InstanceType.OMNI) {
if (!emulatorHost.startsWith("http")) {
emulatorHost = "http://" + emulatorHost;
}
Expand All @@ -2134,8 +2169,8 @@ 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 (experimentalHost != null && credentials == null) {
credentials = environment.getDefaultExperimentalHostCredentials();
} else if (this.instanceType == InstanceType.OMNI && credentials == null) {
credentials = environment.getDefaultSpannerOmniCredentials();
}
if (this.numChannels == null) {
this.numChannels =
Expand Down Expand Up @@ -2177,8 +2212,8 @@ public static void useDefaultEnvironment() {
}

@InternalApi
public static GoogleCredentials getDefaultExperimentalCredentialsFromSysEnv() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXPERIMENTAL_HOST_CREDENTIALS));
public static GoogleCredentials getDefaultSpannerOmniCredentialsFromSysEnv() {
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_OMNI_CREDENTIALS));
}

private static @Nullable GoogleCredentials getOAuthTokenFromFile(@Nullable String file) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.google.cloud.spanner.Options.RpcPriority;
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.TimestampBound;
import com.google.cloud.spanner.TimestampBound.Mode;
import com.google.cloud.spanner.connection.PgTransactionMode.AccessMode;
Expand Down Expand Up @@ -895,4 +896,31 @@ public Dialect convert(String value) {
return values.get(value);
}
}

static class InstanceTypeConverter
implements ClientSideStatementValueConverter<SpannerOptions.InstanceType> {
static final InstanceTypeConverter INSTANCE = new InstanceTypeConverter();

private final CaseInsensitiveEnumMap<SpannerOptions.InstanceType> values =
new CaseInsensitiveEnumMap<>(SpannerOptions.InstanceType.class);

private InstanceTypeConverter() {}

/** Constructor needed for reflection. */
public InstanceTypeConverter(String allowedValues) {}

@Override
public Class<SpannerOptions.InstanceType> getParameterClass() {
return SpannerOptions.InstanceType.class;
}

@Override
public SpannerOptions.InstanceType convert(String value) {
SpannerOptions.InstanceType converted = values.get(value);
if (converted == SpannerOptions.InstanceType.UNSPECIFIED) {
return null;
}
return converted;
}
}
}
Loading
Loading