Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.google.cloud.spanner.SessionPool.Position;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import io.grpc.ExperimentalApi;
import java.time.Duration;
import java.util.Locale;
import java.util.Objects;
Expand All @@ -34,6 +35,7 @@ public class SessionPoolOptions {
private static final int DEFAULT_MAX_SESSIONS = 400;
private static final int DEFAULT_MIN_SESSIONS = 100;
private static final int DEFAULT_INC_STEP = 25;
private static final int EXPERIMENTAL_HOST_REGULAR_SESSIONS = 0;
private static final ActionOnExhaustion DEFAULT_ACTION = ActionOnExhaustion.BLOCK;
private final int minSessions;
private final int maxSessions;
Expand Down Expand Up @@ -89,8 +91,12 @@ private SessionPoolOptions(Builder builder) {
// minSessions > maxSessions is only possible if the user has only set a value for maxSessions.
// We allow that to prevent code that only sets a value for maxSessions to break if the
// maxSessions value is less than the default for minSessions.
this.minSessions = Math.min(builder.minSessions, builder.maxSessions);
this.maxSessions = builder.maxSessions;
this.minSessions =
builder.isExperimentalHost
? EXPERIMENTAL_HOST_REGULAR_SESSIONS
: Math.min(builder.minSessions, builder.maxSessions);
this.maxSessions =
builder.isExperimentalHost ? EXPERIMENTAL_HOST_REGULAR_SESSIONS : builder.maxSessions;
this.incStep = builder.incStep;
this.maxIdleSessions = builder.maxIdleSessions;
this.writeSessionsFraction = builder.writeSessionsFraction;
Expand All @@ -114,26 +120,33 @@ private SessionPoolOptions(Builder builder) {
// useMultiplexedSession priority => Environment var > private setter > client default
Boolean useMultiplexedSessionFromEnvVariable = getUseMultiplexedSessionFromEnvVariable();
this.useMultiplexedSession =
(useMultiplexedSessionFromEnvVariable != null)
? useMultiplexedSessionFromEnvVariable
: builder.useMultiplexedSession;
builder.isExperimentalHost
? true
: ((useMultiplexedSessionFromEnvVariable != null)
? useMultiplexedSessionFromEnvVariable
: builder.useMultiplexedSession);
// useMultiplexedSessionForRW priority => Environment var > private setter > client default
Boolean useMultiplexedSessionForRWFromEnvVariable =
getUseMultiplexedSessionForRWFromEnvVariable();
this.useMultiplexedSessionForRW =
(useMultiplexedSessionForRWFromEnvVariable != null)
? useMultiplexedSessionForRWFromEnvVariable
: builder.useMultiplexedSessionForRW;
builder.isExperimentalHost
? true
: ((useMultiplexedSessionForRWFromEnvVariable != null)
? useMultiplexedSessionForRWFromEnvVariable
: builder.useMultiplexedSessionForRW);
// useMultiplexedSessionPartitionedOps priority => Environment var > private setter > client
// default
Boolean useMultiplexedSessionFromEnvVariablePartitionedOps =
getUseMultiplexedSessionFromEnvVariablePartitionedOps();
this.useMultiplexedSessionForPartitionedOps =
(useMultiplexedSessionFromEnvVariablePartitionedOps != null)
? useMultiplexedSessionFromEnvVariablePartitionedOps
: builder.useMultiplexedSessionPartitionedOps;
builder.isExperimentalHost
? true
: ((useMultiplexedSessionFromEnvVariablePartitionedOps != null)
? useMultiplexedSessionFromEnvVariablePartitionedOps
: builder.useMultiplexedSessionPartitionedOps);
this.multiplexedSessionMaintenanceDuration = builder.multiplexedSessionMaintenanceDuration;
this.skipVerifyingBeginTransactionForMuxRW = builder.skipVerifyingBeginTransactionForMuxRW;
this.skipVerifyingBeginTransactionForMuxRW =
builder.isExperimentalHost ? true : builder.skipVerifyingBeginTransactionForMuxRW;
}

@Override
Expand Down Expand Up @@ -617,6 +630,7 @@ public static class Builder {
private Duration multiplexedSessionMaintenanceDuration = Duration.ofDays(7);
private Clock poolMaintainerClock = Clock.INSTANCE;
private boolean skipVerifyingBeginTransactionForMuxRW = false;
private boolean isExperimentalHost = false;

private static Position getReleaseToPositionFromSystemProperty() {
// NOTE: This System property is a beta feature. Support for it can be removed in the future.
Expand Down Expand Up @@ -813,6 +827,12 @@ public Builder setWarnAndCloseIfInactiveTransactions() {
return this;
}

@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676")
public Builder setExperimentalHost() {
this.isExperimentalHost = true;
return this;
}

/**
* If there are inactive transactions, release the resources consumed by such transactions. A
* transaction is classified as inactive if it executes for more than a system defined duration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,6 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
Expand All @@ -117,10 +116,7 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {

private static final String API_SHORT_NAME = "Spanner";
private static final String DEFAULT_HOST = "https://spanner.googleapis.com";
private static final String CLOUD_SPANNER_HOST_FORMAT = ".*\\.googleapis\\.com.*";

@VisibleForTesting
static final Pattern CLOUD_SPANNER_HOST_PATTERN = Pattern.compile(CLOUD_SPANNER_HOST_FORMAT);
private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default";

private static final ImmutableSet<String> SCOPES =
ImmutableSet.of(
Expand Down Expand Up @@ -991,7 +987,7 @@ public static class Builder
private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics();
private String monitoringHost = SpannerOptions.environment.getMonitoringHost();
private SslContext mTLSContext = null;
private boolean isExternalHost = false;
private boolean isExperimentalHost = false;

private static String createCustomClientLibToken(String token) {
return token + " " + ServiceOptions.getGoogApiClientLibName();
Expand Down Expand Up @@ -1484,14 +1480,20 @@ public Builder setDecodeMode(DecodeMode decodeMode) {
@Override
public Builder setHost(String host) {
super.setHost(host);
if (!CLOUD_SPANNER_HOST_PATTERN.matcher(host).matches()) {
this.isExternalHost = true;
}
// Setting a host should override any SPANNER_EMULATOR_HOST setting.
setEmulatorHost(null);
return this;
}

@ExperimentalApi("https://github.com/googleapis/java-spanner/pull/3676")
public Builder setExperimentalHost(String host) {
super.setHost(host);
super.setProjectId(EXPERIMENTAL_HOST_PROJECT_ID);
setSessionPoolOption(SessionPoolOptions.newBuilder().setExperimentalHost().build());
this.isExperimentalHost = true;
return this;
}

/**
* Enables gRPC-GCP extension with the default settings. Do not set
* GOOGLE_CLOUD_SPANNER_MULTIPLEXED_SESSIONS to true in combination with this option, as
Expand Down Expand Up @@ -1657,7 +1659,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 (isExternalHost && credentials == null) {
} else if (isExperimentalHost && credentials == null) {
credentials = environment.getDefaultExternalHostCredentials();
}
if (this.numChannels == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static com.google.cloud.spanner.connection.ConnectionProperties.ENABLE_EXTENDED_TRACING;
import static com.google.cloud.spanner.connection.ConnectionProperties.ENCODED_CREDENTIALS;
import static com.google.cloud.spanner.connection.ConnectionProperties.ENDPOINT;
import static com.google.cloud.spanner.connection.ConnectionProperties.IS_EXPERIMENTAL_HOST;
import static com.google.cloud.spanner.connection.ConnectionProperties.LENIENT;
import static com.google.cloud.spanner.connection.ConnectionProperties.MAX_COMMIT_DELAY;
import static com.google.cloud.spanner.connection.ConnectionProperties.MAX_PARTITIONED_PARALLELISM;
Expand Down Expand Up @@ -221,6 +222,7 @@ public String[] getValidValues() {
private static final LocalConnectionChecker LOCAL_CONNECTION_CHECKER =
new LocalConnectionChecker();
static final boolean DEFAULT_USE_PLAIN_TEXT = false;
static final boolean DEFAULT_IS_EXPERIMENTAL_HOST = false;
static final boolean DEFAULT_AUTOCOMMIT = true;
static final boolean DEFAULT_READONLY = false;
static final boolean DEFAULT_RETRY_ABORTS_INTERNALLY = true;
Expand Down Expand Up @@ -260,6 +262,8 @@ public String[] getValidValues() {
static final boolean DEFAULT_AUTO_BATCH_DML = false;
static final long DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT = 1L;
static final boolean DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION = true;
private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default";
private static final String DEFAULT_EXPERIMENTAL_HOST_INSTANCE_ID = "default";

private static final String PLAIN_TEXT_PROTOCOL = "http:";
private static final String HOST_PROTOCOL = "https:";
Expand All @@ -268,6 +272,8 @@ public String[] getValidValues() {
private static final String DEFAULT_EMULATOR_HOST = "http://localhost:9010";
/** Use plain text is only for local testing purposes. */
static final String USE_PLAIN_TEXT_PROPERTY_NAME = "usePlainText";
/** Connect to a Experimental Host * */
static final String IS_EXPERIMENTAL_HOST_PROPERTY_NAME = "isExperimentalHost";
/** Client certificate path to establish mTLS */
static final String CLIENT_CERTIFICATE_PROPERTY_NAME = "clientCertificate";
/** Client key path to establish mTLS */
Expand Down Expand Up @@ -444,6 +450,10 @@ static boolean isEnableTransactionalConnectionStateForPostgreSQL() {
USE_PLAIN_TEXT_PROPERTY_NAME,
"Use a plain text communication channel (i.e. non-TLS) for communicating with the server (true/false). Set this value to true for communication with the Cloud Spanner emulator.",
DEFAULT_USE_PLAIN_TEXT),
ConnectionProperty.createBooleanProperty(
IS_EXPERIMENTAL_HOST_PROPERTY_NAME,
"Set this value to true for communication with a Experimental Host.",
DEFAULT_IS_EXPERIMENTAL_HOST),
ConnectionProperty.createStringProperty(
CLIENT_CERTIFICATE_PROPERTY_NAME,
"Specifies the file path to the client certificate required for establishing an mTLS connection."),
Expand Down Expand Up @@ -857,10 +867,10 @@ public static Builder newBuilder() {

private ConnectionOptions(Builder builder) {
Matcher matcher;
boolean isExternalHost = false;
boolean isExperimentalHostPattern = false;
if (builder.isValidExternalHostUri(builder.uri)) {
matcher = Builder.EXTERNAL_HOST_PATTERN.matcher(builder.uri);
isExternalHost = true;
isExperimentalHostPattern = true;
} else {
matcher = Builder.SPANNER_URI_PATTERN.matcher(builder.uri);
}
Expand Down Expand Up @@ -939,7 +949,8 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null
this.credentials =
new GoogleCredentials(
new AccessToken(getInitialConnectionPropertyValue(OAUTH_TOKEN), null));
} else if (isExternalHost && defaultExternalHostCredentials != null) {
} else if ((isExperimentalHostPattern || isExperimentalHost())
&& defaultExternalHostCredentials != null) {
this.credentials = defaultExternalHostCredentials;
} else if (getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER) != null) {
try {
Expand Down Expand Up @@ -981,16 +992,19 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null
this.sessionPoolOptions = sessionPoolOptionsBuilder.build();
} else if (builder.sessionPoolOptions != null) {
this.sessionPoolOptions = builder.sessionPoolOptions;
} else if (isExperimentalHostPattern || isExperimentalHost()) {
this.sessionPoolOptions =
SessionPoolOptions.newBuilder().setExperimentalHost().setAutoDetectDialect(true).build();
} else {
this.sessionPoolOptions = SessionPoolOptions.newBuilder().setAutoDetectDialect(true).build();
}

String projectId = "default";
String projectId = EXPERIMENTAL_HOST_PROJECT_ID;
String instanceId = matcher.group(Builder.INSTANCE_GROUP);
if (!isExternalHost) {
if (!isExperimentalHost() && !isExperimentalHostPattern) {
projectId = matcher.group(Builder.PROJECT_GROUP);
} else if (instanceId == null) {
instanceId = "default";
instanceId = DEFAULT_EXPERIMENTAL_HOST_INSTANCE_ID;
}
if (Builder.DEFAULT_PROJECT_ID_PLACEHOLDER.equalsIgnoreCase(projectId)) {
projectId = getDefaultProjectId(this.credentials);
Expand Down Expand Up @@ -1311,6 +1325,10 @@ boolean isUsePlainText() {
|| getInitialConnectionPropertyValue(USE_PLAIN_TEXT);
}

boolean isExperimentalHost() {
return getInitialConnectionPropertyValue(IS_EXPERIMENTAL_HOST);
}

String getClientCertificate() {
return getInitialConnectionPropertyValue(CLIENT_CERTIFICATE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_END_TO_END_TRACING;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENABLE_EXTENDED_TRACING;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_ENDPOINT;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_IS_EXPERIMENTAL_HOST;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_KEEP_TRANSACTION_ALIVE;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_LENIENT;
import static com.google.cloud.spanner.connection.ConnectionOptions.DEFAULT_MAX_PARTITIONED_PARALLELISM;
Expand Down Expand Up @@ -76,6 +77,7 @@
import static com.google.cloud.spanner.connection.ConnectionOptions.ENABLE_EXTENDED_TRACING_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENCODED_CREDENTIALS_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.ENDPOINT_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.IS_EXPERIMENTAL_HOST_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.KEEP_TRANSACTION_ALIVE_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.LENIENT_PROPERTY_NAME;
import static com.google.cloud.spanner.connection.ConnectionOptions.MAX_PARTITIONED_PARALLELISM_PROPERTY_NAME;
Expand Down Expand Up @@ -197,7 +199,14 @@ public class ConnectionProperties {
BOOLEANS,
BooleanConverter.INSTANCE,
Context.STARTUP);

static final ConnectionProperty<Boolean> IS_EXPERIMENTAL_HOST =
create(
IS_EXPERIMENTAL_HOST_PROPERTY_NAME,
"Set this value to true for communication with a Experimental Host.",
DEFAULT_IS_EXPERIMENTAL_HOST,
BOOLEANS,
BooleanConverter.INSTANCE,
Context.STARTUP);
static final ConnectionProperty<String> CLIENT_CERTIFICATE =
create(
CLIENT_CERTIFICATE_PROPERTY_NAME,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.google.cloud.spanner;

import static com.google.cloud.spanner.SpannerOptions.CLOUD_SPANNER_HOST_PATTERN;
import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
Expand Down Expand Up @@ -1167,10 +1166,17 @@ public void checkGlobalOpenTelemetryWhenNotInjected() {
}

@Test
public void testCloudSpannerHostPattern() {
assertTrue(CLOUD_SPANNER_HOST_PATTERN.matcher("https://spanner.googleapis.com").matches());
assertTrue(
CLOUD_SPANNER_HOST_PATTERN.matcher("https://product-area.googleapis.com:443").matches());
assertFalse(CLOUD_SPANNER_HOST_PATTERN.matcher("https://some-company.com:443").matches());
public void testExperimentalHostOptions() {
SpannerOptions options =
SpannerOptions.newBuilder()
.setExperimentalHost("localhost:8080")
.setCredentials(NoCredentials.getInstance())
.build();
assertEquals("default", options.getProjectId());
assertEquals("localhost:8080", options.getHost());
assertEquals(0, options.getSessionPoolOptions().getMinSessions());
assertTrue(options.getSessionPoolOptions().getUseMultiplexedSession());
assertTrue(options.getSessionPoolOptions().getUseMultiplexedSessionForRW());
assertTrue(options.getSessionPoolOptions().getUseMultiplexedSessionPartitionedOps());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1270,4 +1270,39 @@ public void testBuildWithValidURIWithPrefixSpanner() {
assertThat(options.isAutocommit()).isEqualTo(false);
assertThat(options.isReadOnly()).isEqualTo(true);
}

@Test
public void testExperimentalHost() {
ConnectionOptions.Builder builderWithoutExperimentalHostParam = ConnectionOptions.newBuilder();
builderWithoutExperimentalHostParam.setUri(
"spanner://localhost:15000/instances/default/databases/singers-db;usePlainText=true");
ConnectionOptions optionsWithoutExperimentalHostParam =
builderWithoutExperimentalHostParam.build();
assertFalse(optionsWithoutExperimentalHostParam.isExperimentalHost());
assertEquals(0, optionsWithoutExperimentalHostParam.getSessionPoolOptions().getMinSessions());
assertTrue(
optionsWithoutExperimentalHostParam.getSessionPoolOptions().getUseMultiplexedSession());
assertTrue(
optionsWithoutExperimentalHostParam
.getSessionPoolOptions()
.getUseMultiplexedSessionForRW());
assertTrue(
optionsWithoutExperimentalHostParam
.getSessionPoolOptions()
.getUseMultiplexedSessionPartitionedOps());

ConnectionOptions.Builder builderWithExperimentalHostParam = ConnectionOptions.newBuilder();
builderWithExperimentalHostParam.setUri(
"spanner://localhost:15000/projects/default/instances/default/databases/singers-db;usePlainText=true;isExperimentalHost=true");
ConnectionOptions optionsWithExperimentalHostParam = builderWithExperimentalHostParam.build();
assertTrue(optionsWithExperimentalHostParam.isExperimentalHost());
assertEquals(0, optionsWithExperimentalHostParam.getSessionPoolOptions().getMinSessions());
assertTrue(optionsWithExperimentalHostParam.getSessionPoolOptions().getUseMultiplexedSession());
assertTrue(
optionsWithExperimentalHostParam.getSessionPoolOptions().getUseMultiplexedSessionForRW());
assertTrue(
optionsWithExperimentalHostParam
.getSessionPoolOptions()
.getUseMultiplexedSessionPartitionedOps());
}
}
Loading