diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3932a70d4e1..2425d7234f4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,14 +9,15 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11] + java: [8, 11, 17] steps: - uses: actions/checkout@v2 - uses: stCarolas/setup-maven@v4 with: maven-version: 3.8.1 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v2 with: + distribution: zulu java-version: ${{matrix.java}} - run: java -version - run: .kokoro/build.sh @@ -29,8 +30,9 @@ jobs: - uses: stCarolas/setup-maven@v4 with: maven-version: 3.8.1 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v2 with: + distribution: zulu java-version: 8 - run: java -version - run: .kokoro/build.bat @@ -40,14 +42,15 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - java: [8, 11] + java: [8, 11, 17] steps: - uses: actions/checkout@v2 - uses: stCarolas/setup-maven@v4 with: maven-version: 3.8.1 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v2 with: + distribution: zulu java-version: ${{matrix.java}} - run: java -version - run: .kokoro/dependencies.sh @@ -58,8 +61,9 @@ jobs: - uses: stCarolas/setup-maven@v4 with: maven-version: 3.8.1 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v2 with: + distribution: zulu java-version: 8 - run: java -version - run: .kokoro/build.sh @@ -72,8 +76,9 @@ jobs: - uses: stCarolas/setup-maven@v4 with: maven-version: 3.8.1 - - uses: actions/setup-java@v1 + - uses: actions/setup-java@v2 with: + distribution: zulu java-version: 8 - run: java -version - run: .kokoro/build.sh diff --git a/.kokoro/dependencies.sh b/.kokoro/dependencies.sh index 9030ba8f99f..d7476cfe972 100755 --- a/.kokoro/dependencies.sh +++ b/.kokoro/dependencies.sh @@ -28,7 +28,26 @@ source ${scriptDir}/common.sh java -version echo $JOB_TYPE -export MAVEN_OPTS="-Xmx1024m -XX:MaxPermSize=128m" +function determineMavenOpts() { + local javaVersion=$( + # filter down to the version line, then pull out the version between quotes, + # then trim the version number down to its minimal number (removing any + # update or suffix number). + java -version 2>&1 | grep "version" \ + | sed -E 's/^.*"(.*?)".*$/\1/g' \ + | sed -E 's/^(1\.[0-9]\.0).*$/\1/g' + ) + + if [[ $javaVersion == 17* ]] + then + # MaxPermSize is no longer supported as of jdk 17 + echo -n "-Xmx1024m" + else + echo -n "-Xmx1024m -XX:MaxPermSize=128m" + fi +} + +export MAVEN_OPTS=$(determineMavenOpts) # this should run maven enforcer retry_with_backoff 3 10 \ diff --git a/.kokoro/presubmit/graalvm-native.cfg b/.kokoro/presubmit/graalvm-native.cfg new file mode 100644 index 00000000000..4c7225ec924 --- /dev/null +++ b/.kokoro/presubmit/graalvm-native.cfg @@ -0,0 +1,33 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/graalvm" +} + +env_vars: { + key: "JOB_TYPE" + value: "graalvm" +} + +# TODO: remove this after we've migrated all tests and scripts +env_vars: { + key: "GCLOUD_PROJECT" + value: "gcloud-devel" +} + +env_vars: { + key: "GOOGLE_CLOUD_PROJECT" + value: "gcloud-devel" +} + +env_vars: { + key: "GOOGLE_APPLICATION_CREDENTIALS" + value: "secret_manager/java-it-service-account" +} + +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "java-it-service-account" +} diff --git a/.repo-metadata.json b/.repo-metadata.json index adbaf69f81f..ddbcd2b6cd2 100644 --- a/.repo-metadata.json +++ b/.repo-metadata.json @@ -2,7 +2,7 @@ "name": "spanner", "name_pretty": "Cloud Spanner", "product_documentation": "https://cloud.google.com/spanner/docs/", - "client_documentation": "https://googleapis.dev/java/google-cloud-spanner/latest/", + "client_documentation": "https://cloud.google.com/java/docs/reference/google-cloud-spanner/latest/history", "api_description": "is a fully managed, mission-critical, \nrelational database service that offers transactional consistency at global scale, \nschemas, SQL (ANSI 2011 with extensions), and automatic, synchronous replication \nfor high availability.\n\nBe sure to activate the Cloud Spanner API on the Developer's Console to\nuse Cloud Spanner from your project.", "issue_tracker": "https://issuetracker.google.com/issues?q=componentid:190851%2B%20status:open", "release_level": "ga", diff --git a/CHANGELOG.md b/CHANGELOG.md index 04b4aaeeaa2..3a54d4b44be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## [6.14.0](https://www.github.com/googleapis/java-spanner/compare/v6.13.0...v6.14.0) (2021-10-25) + + +### Features + +* Introduce Native Image testing build script changes ([#1500](https://www.github.com/googleapis/java-spanner/issues/1500)) ([7a034c9](https://www.github.com/googleapis/java-spanner/commit/7a034c9120ffa433f64e67d565c854f1fb3ce9f5)) + + +### Bug Fixes + +* **java:** java 17 dependency arguments ([#1512](https://www.github.com/googleapis/java-spanner/issues/1512)) ([4cebefa](https://www.github.com/googleapis/java-spanner/commit/4cebefa1ce6502d48c2e2e0a3a484f60eeed450f)) + + +### Dependencies + +* update dependency com.google.cloud:google-cloud-monitoring to v3.1.0 ([#1506](https://www.github.com/googleapis/java-spanner/issues/1506)) ([ea35b27](https://www.github.com/googleapis/java-spanner/commit/ea35b2723fcc8c255ab0e52306e066c689c6a0c6)) +* update dependency com.google.cloud:google-cloud-shared-dependencies to v2.4.0 ([#1501](https://www.github.com/googleapis/java-spanner/issues/1501)) ([d5a37b8](https://www.github.com/googleapis/java-spanner/commit/d5a37b8853fc21a28b6610b2933ed31fcbe206e2)) +* update dependency com.google.cloud:google-cloud-trace to v2.0.6 ([#1504](https://www.github.com/googleapis/java-spanner/issues/1504)) ([667b8b1](https://www.github.com/googleapis/java-spanner/commit/667b8b17cc2f8d217ecda0af89bdc668670f3aab)) + ## [6.13.0](https://www.github.com/googleapis/java-spanner/compare/v6.12.5...v6.13.0) (2021-10-07) diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml index a08766a6f48..3d9db44023c 100644 --- a/google-cloud-spanner-bom/pom.xml +++ b/google-cloud-spanner-bom/pom.xml @@ -3,12 +3,12 @@ 4.0.0 com.google.cloud google-cloud-spanner-bom - 6.13.0 + 6.14.0 pom com.google.cloud google-cloud-shared-config - 1.0.3 + 1.2.0 Google Cloud Spanner BOM @@ -54,43 +54,43 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.13.0 + 6.14.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.13.0 + 6.14.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.13.0 + 6.14.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.13.0 + 6.14.0 com.google.cloud google-cloud-spanner - 6.13.0 + 6.14.0 com.google.cloud google-cloud-spanner test-jar - 6.13.0 + 6.14.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.13.0 + 6.14.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.13.0 + 6.14.0 diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml index e870f315367..0b110f4a0a4 100644 --- a/google-cloud-spanner/pom.xml +++ b/google-cloud-spanner/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.google.cloud google-cloud-spanner - 6.13.0 + 6.14.0 jar Google Cloud Spanner https://github.com/googleapis/java-spanner @@ -11,7 +11,7 @@ com.google.cloud google-cloud-spanner-parent - 6.13.0 + 6.14.0 google-cloud-spanner @@ -74,14 +74,27 @@ gcloud-devel projects/gcloud-devel/locations/us-central1/keyRings/spanner-test-keyring/cryptoKeys/spanner-test-key - 6000 + 3000 default - com.google.cloud.spanner.SerialIntegrationTest, com.google.cloud.spanner.ParallelIntegrationTest + com.google.cloud.spanner.SerialIntegrationTest + + + + + parallel-integration-test + + integration-test + + + com.google.cloud.spanner.ParallelIntegrationTest + 8 + true + com.google.cloud.spanner.ParallelIntegrationTest @@ -415,7 +428,7 @@ true ipv4 - 6000 + 3000 diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java index 48837d21cec..5559177d537 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITDatabaseAdminTest.java @@ -16,50 +16,27 @@ package com.google.cloud.spanner.it; -import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; -import static org.junit.Assume.assumeFalse; -import com.google.api.gax.grpc.GrpcInterceptorProvider; import com.google.api.gax.longrunning.OperationFuture; import com.google.api.gax.paging.Page; -import com.google.cloud.Timestamp; -import com.google.cloud.spanner.Backup; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.IntegrationTestEnv; import com.google.cloud.spanner.Options; import com.google.cloud.spanner.ParallelIntegrationTest; -import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerException; -import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.testing.RemoteSpannerHelper; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; -import com.google.spanner.admin.database.v1.CreateBackupMetadata; import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; -import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ClientInterceptor; -import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; -import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; -import io.grpc.Metadata; -import io.grpc.MethodDescriptor; -import io.grpc.Status; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Random; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Before; import org.junit.ClassRule; @@ -72,6 +49,7 @@ @Category(ParallelIntegrationTest.class) @RunWith(JUnit4.class) public class ITDatabaseAdminTest { + private static final long TIMEOUT_MINUTES = 5; @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); private DatabaseAdminClient dbAdminClient; private RemoteSpannerHelper testHelper; @@ -98,7 +76,7 @@ public void databaseOperations() throws Exception { String statement1 = "CREATE TABLE T (\n" + " K STRING(MAX),\n" + ") PRIMARY KEY(K)"; OperationFuture op = dbAdminClient.createDatabase(instanceId, dbId, ImmutableList.of(statement1)); - Database db = op.get(); + Database db = op.get(TIMEOUT_MINUTES, TimeUnit.MINUTES); dbs.add(db); assertThat(db.getId().getDatabase()).isEqualTo(dbId); @@ -119,7 +97,7 @@ public void databaseOperations() throws Exception { String statement2 = "CREATE TABLE T2 (\n" + " K2 STRING(MAX),\n" + ") PRIMARY KEY(K2)"; OperationFuture op2 = dbAdminClient.updateDatabaseDdl(instanceId, dbId, ImmutableList.of(statement2), null); - op2.get(); + op2.get(TIMEOUT_MINUTES, TimeUnit.MINUTES); List statementsInDb = dbAdminClient.getDatabaseDdl(instanceId, dbId); assertThat(statementsInDb).containsExactly(statement1, statement2); @@ -140,15 +118,15 @@ public void updateDdlRetry() throws Exception { String statement1 = "CREATE TABLE T (\n" + " K STRING(MAX),\n" + ") PRIMARY KEY(K)"; OperationFuture op = dbAdminClient.createDatabase(instanceId, dbId, ImmutableList.of(statement1)); - Database db = op.get(); + Database db = op.get(TIMEOUT_MINUTES, TimeUnit.MINUTES); dbs.add(db); String statement2 = "CREATE TABLE T2 (\n" + " K2 STRING(MAX),\n" + ") PRIMARY KEY(K2)"; OperationFuture op1 = dbAdminClient.updateDatabaseDdl(instanceId, dbId, ImmutableList.of(statement2), "myop"); OperationFuture op2 = dbAdminClient.updateDatabaseDdl(instanceId, dbId, ImmutableList.of(statement2), "myop"); - op1.get(); - op2.get(); + op1.get(TIMEOUT_MINUTES, TimeUnit.MINUTES); + op2.get(TIMEOUT_MINUTES, TimeUnit.MINUTES); // Remove the progress list from the metadata before comparing, as there could be small // differences between the two in the reported progress depending on exactly when each @@ -167,7 +145,7 @@ public void databaseOperationsViaEntity() throws Exception { String statement1 = "CREATE TABLE T (\n" + " K STRING(MAX),\n" + ") PRIMARY KEY(K)"; OperationFuture op = dbAdminClient.createDatabase(instanceId, dbId, ImmutableList.of(statement1)); - Database db = op.get(); + Database db = op.get(TIMEOUT_MINUTES, TimeUnit.MINUTES); dbs.add(db); assertThat(db.getId().getDatabase()).isEqualTo(dbId); @@ -176,7 +154,7 @@ public void databaseOperationsViaEntity() throws Exception { String statement2 = "CREATE TABLE T2 (\n" + " K2 STRING(MAX),\n" + ") PRIMARY KEY(K2)"; OperationFuture op2 = db.updateDdl(ImmutableList.of(statement2), null); - op2.get(); + op2.get(TIMEOUT_MINUTES, TimeUnit.MINUTES); Iterable statementsInDb = db.getDdl(); assertThat(statementsInDb).containsExactly(statement1, statement2); db.drop(); @@ -216,217 +194,4 @@ public void listPagination() throws Exception { } assertThat(dbIdsGot).containsAtLeastElementsIn(dbIds); } - - private static final class InjectErrorInterceptorProvider implements GrpcInterceptorProvider { - final AtomicBoolean injectError = new AtomicBoolean(true); - final AtomicInteger getOperationCount = new AtomicInteger(); - final AtomicInteger methodCount = new AtomicInteger(); - final String methodName; - - private InjectErrorInterceptorProvider(String methodName) { - this.methodName = methodName; - } - - @Override - public List getInterceptors() { - ClientInterceptor interceptor = - new ClientInterceptor() { - @Override - public ClientCall interceptCall( - MethodDescriptor method, CallOptions callOptions, Channel next) { - if (method.getFullMethodName().contains("GetOperation")) { - getOperationCount.incrementAndGet(); - } - if (!method.getFullMethodName().contains(methodName)) { - return next.newCall(method, callOptions); - } - - methodCount.incrementAndGet(); - final AtomicBoolean errorInjected = new AtomicBoolean(); - final ClientCall clientCall = next.newCall(method, callOptions); - - return new SimpleForwardingClientCall(clientCall) { - @Override - public void start(Listener responseListener, Metadata headers) { - super.start( - new SimpleForwardingClientCallListener(responseListener) { - @Override - public void onMessage(RespT message) { - if (injectError.getAndSet(false)) { - errorInjected.set(true); - clientCall.cancel("Cancelling call for injected error", null); - } else { - super.onMessage(message); - } - } - - @Override - public void onClose(Status status, Metadata metadata) { - if (errorInjected.get()) { - status = Status.UNAVAILABLE.augmentDescription("INJECTED BY TEST"); - } - super.onClose(status, metadata); - } - }, - headers); - } - }; - } - }; - return Collections.singletonList(interceptor); - } - } - - @Test - public void testRetryNonIdempotentRpcsReturningLongRunningOperations() throws Exception { - assumeFalse( - "Querying long-running operations is not supported on the emulator", isUsingEmulator()); - - // RPCs that return a long-running operation such as CreateDatabase, CreateBackup and - // RestoreDatabase are non-idempotent and can normally not be automatically retried in case of a - // transient failure. The client library will however automatically query the backend to check - // whether the corresponding operation was started or not, and if it was, it will pick up the - // existing operation. If no operation is found, a new RPC call will be executed to start the - // operation. - - List databases = new ArrayList<>(); - List backups = new ArrayList<>(); - String initialDatabaseId; - Timestamp initialDbCreateTime; - - try { - // CreateDatabase - InjectErrorInterceptorProvider createDbInterceptor = - new InjectErrorInterceptorProvider("CreateDatabase"); - SpannerOptions options = - testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build(); - try (Spanner spanner = options.getService()) { - initialDatabaseId = testHelper.getUniqueDatabaseId(); - DatabaseAdminClient client = spanner.getDatabaseAdminClient(); - OperationFuture op = - client.createDatabase( - testHelper.getInstanceId().getInstance(), - initialDatabaseId, - Collections.emptyList()); - databases.add(op.get()); - // Keep track of the original create time of this database, as we will drop this database - // later and create another one with the exact same name. That means that the ListOperations - // call will return at least two CreateDatabase operations. The retry logic should always - // pick the last one. - initialDbCreateTime = op.get().getCreateTime(); - // Assert that the CreateDatabase RPC was called only once, and that the operation tracking - // was resumed through a GetOperation call. - assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); - assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); - } - - // CreateBackup - InjectErrorInterceptorProvider createBackupInterceptor = - new InjectErrorInterceptorProvider("CreateBackup"); - options = - testHelper - .getOptions() - .toBuilder() - .setInterceptorProvider(createBackupInterceptor) - .build(); - try (Spanner spanner = options.getService()) { - String databaseId = databases.get(0).getId().getDatabase(); - String backupId = String.format("test-bck-%08d", new Random().nextInt(100000000)); - DatabaseAdminClient client = spanner.getDatabaseAdminClient(); - OperationFuture op = - client.createBackup( - testHelper.getInstanceId().getInstance(), - backupId, - databaseId, - Timestamp.ofTimeSecondsAndNanos( - Timestamp.now().getSeconds() + TimeUnit.SECONDS.convert(7L, TimeUnit.DAYS), 0)); - backups.add(op.get()); - // Assert that the CreateBackup RPC was called only once, and that the operation tracking - // was resumed through a GetOperation call. - assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); - assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); - } - - // RestoreBackup - int attempts = 0; - while (true) { - InjectErrorInterceptorProvider restoreBackupInterceptor = - new InjectErrorInterceptorProvider("RestoreBackup"); - options = - testHelper - .getOptions() - .toBuilder() - .setInterceptorProvider(restoreBackupInterceptor) - .build(); - try (Spanner spanner = options.getService()) { - String backupId = backups.get(0).getId().getBackup(); - String restoredDbId = testHelper.getUniqueDatabaseId(); - DatabaseAdminClient client = spanner.getDatabaseAdminClient(); - OperationFuture op = - client.restoreDatabase( - testHelper.getInstanceId().getInstance(), - backupId, - testHelper.getInstanceId().getInstance(), - restoredDbId); - databases.add(op.get()); - // Assert that the RestoreDatabase RPC was called only once, and that the operation - // tracking was resumed through a GetOperation call. - assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); - assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); - break; - } catch (ExecutionException e) { - if (e.getCause() instanceof SpannerException - && ((SpannerException) e.getCause()).getErrorCode() == ErrorCode.FAILED_PRECONDITION - && e.getCause() - .getMessage() - .contains("Please retry the operation once the pending restores complete")) { - attempts++; - if (attempts == 10) { - // Still same error after 10 attempts. Ignore. - break; - } - // wait and then retry. - Thread.sleep(60_000L); - } else { - throw e; - } - } - } - - // Create another database with the exact same name as the first database. - createDbInterceptor = new InjectErrorInterceptorProvider("CreateDatabase"); - options = - testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build(); - try (Spanner spanner = options.getService()) { - DatabaseAdminClient client = spanner.getDatabaseAdminClient(); - // First drop the initial database. - client.dropDatabase(testHelper.getInstanceId().getInstance(), initialDatabaseId); - // Now re-create a database with the exact same name. - OperationFuture op = - client.createDatabase( - testHelper.getInstanceId().getInstance(), - initialDatabaseId, - Collections.emptyList()); - // Check that the second database was created and has a greater creation time than the - // first. - Timestamp secondCreationTime = op.get().getCreateTime(); - // TODO: Change this to greaterThan when the create time of a database is reported back by - // the server. - assertThat(secondCreationTime).isAtLeast(initialDbCreateTime); - // Assert that the CreateDatabase RPC was called only once, and that the operation tracking - // was resumed through a GetOperation call. - assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); - assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); - } - } finally { - DatabaseAdminClient client = testHelper.getClient().getDatabaseAdminClient(); - for (Database database : databases) { - client.dropDatabase( - database.getId().getInstanceId().getInstance(), database.getId().getDatabase()); - } - for (Backup backup : backups) { - client.deleteBackup(backup.getInstanceId().getInstance(), backup.getId().getBackup()); - } - } - } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java index d1c09c247df..fa756b2f277 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java @@ -51,7 +51,7 @@ @RunWith(JUnit4.class) public class ITPitrUpdateDatabaseTest { - private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(5); + private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(20); private static final String VERSION_RETENTION_PERIOD = "7d"; @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java index 672f6551e99..8bcaa1c54cf 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/slow/ITBackupTest.java @@ -25,6 +25,7 @@ import static org.junit.Assume.assumeFalse; import com.google.api.client.util.Lists; +import com.google.api.gax.grpc.GrpcInterceptorProvider; import com.google.api.gax.longrunning.OperationFuture; import com.google.api.gax.paging.Page; import com.google.api.gax.rpc.FailedPreconditionException; @@ -45,8 +46,10 @@ import com.google.cloud.spanner.Restore; import com.google.cloud.spanner.ResultSet; import com.google.cloud.spanner.SlowTest; +import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerExceptionFactory; +import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.encryption.EncryptionConfigs; import com.google.cloud.spanner.testing.RemoteSpannerHelper; @@ -59,15 +62,26 @@ import com.google.spanner.admin.database.v1.CreateDatabaseMetadata; import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.RestoreSourceType; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; import io.grpc.Status; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -88,6 +102,9 @@ @Category(SlowTest.class) @RunWith(JUnit4.class) public class ITBackupTest { + private static final long DATABASE_TIMEOUT_MINUTES = 5; + private static final long RESTORE_TIMEOUT_MINUTES = 20; + private static final long BACKUP_TIMEOUT_MINUTES = 30; private static final Logger logger = Logger.getLogger(ITBackupTest.class.getName()); private static final String EXPECTED_OP_NAME_FORMAT = "%s/backups/%s/operations/"; private static final String KMS_KEY_NAME_PROPERTY = "spanner.testenv.kms_key.name"; @@ -206,7 +223,7 @@ private void waitForDbOperations(String backupId) throws InterruptedException { } @Test - public void testBackups() throws InterruptedException, ExecutionException { + public void testBackups() throws InterruptedException, ExecutionException, TimeoutException { // Create two test databases in parallel. final String db1Id = testHelper.getUniqueDatabaseId() + "_db1"; final Database sourceDatabase1 = @@ -229,8 +246,8 @@ public void testBackups() throws InterruptedException, ExecutionException { Collections.singletonList( "CREATE TABLE BAR (ID INT64, NAME STRING(100)) PRIMARY KEY (ID)")); // Make sure all databases are created before we try to create any backups. - Database db1 = dbOp1.get(); - Database db2 = dbOp2.get(); + Database db1 = dbOp1.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES); + Database db2 = dbOp2.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES); databases.add(db1.getId().getDatabase()); databases.add(db2.getId().getDatabase()); // Insert some data into db2 to make sure the backup will have a size>0. @@ -382,6 +399,141 @@ public void testBackups() throws InterruptedException, ExecutionException { logger.info("Finished all backup tests"); } + @Test + public void testRetryNonIdempotentRpcsReturningLongRunningOperations() throws Exception { + assumeFalse( + "Querying long-running operations is not supported on the emulator", isUsingEmulator()); + + // RPCs that return a long-running operation such as CreateDatabase, CreateBackup and + // RestoreDatabase are non-idempotent and can normally not be automatically retried in case of a + // transient failure. The client library will however automatically query the backend to check + // whether the corresponding operation was started or not, and if it was, it will pick up the + // existing operation. If no operation is found, a new RPC call will be executed to start the + // operation. + + List databases = new ArrayList<>(); + List backups = new ArrayList<>(); + String initialDatabaseId; + Timestamp initialDbCreateTime; + + // CreateDatabase + InjectErrorInterceptorProvider createDbInterceptor = + new InjectErrorInterceptorProvider("CreateDatabase"); + SpannerOptions options = + testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build(); + try (Spanner spanner = options.getService()) { + initialDatabaseId = testHelper.getUniqueDatabaseId(); + DatabaseAdminClient client = spanner.getDatabaseAdminClient(); + OperationFuture op = + client.createDatabase( + testHelper.getInstanceId().getInstance(), initialDatabaseId, Collections.emptyList()); + databases.add(op.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES)); + // Keep track of the original create time of this database, as we will drop this database + // later and create another one with the exact same name. That means that the ListOperations + // call will return at least two CreateDatabase operations. The retry logic should always + // pick the last one. + initialDbCreateTime = op.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES).getCreateTime(); + // Assert that the CreateDatabase RPC was called only once, and that the operation tracking + // was resumed through a GetOperation call. + assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); + assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); + } + + // CreateBackup + InjectErrorInterceptorProvider createBackupInterceptor = + new InjectErrorInterceptorProvider("CreateBackup"); + options = + testHelper.getOptions().toBuilder().setInterceptorProvider(createBackupInterceptor).build(); + try (Spanner spanner = options.getService()) { + String databaseId = databases.get(0).getId().getDatabase(); + String backupId = String.format("test-bck-%08d", new Random().nextInt(100000000)); + DatabaseAdminClient client = spanner.getDatabaseAdminClient(); + OperationFuture op = + client.createBackup( + testHelper.getInstanceId().getInstance(), + backupId, + databaseId, + Timestamp.ofTimeSecondsAndNanos( + Timestamp.now().getSeconds() + TimeUnit.SECONDS.convert(7L, TimeUnit.DAYS), 0)); + backups.add(op.get(BACKUP_TIMEOUT_MINUTES, TimeUnit.MINUTES)); + // Assert that the CreateBackup RPC was called only once, and that the operation tracking + // was resumed through a GetOperation call. + assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); + assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); + } + + // RestoreBackup + int attempts = 0; + while (true) { + InjectErrorInterceptorProvider restoreBackupInterceptor = + new InjectErrorInterceptorProvider("RestoreBackup"); + options = + testHelper + .getOptions() + .toBuilder() + .setInterceptorProvider(restoreBackupInterceptor) + .build(); + try (Spanner spanner = options.getService()) { + String backupId = backups.get(0).getId().getBackup(); + String restoredDbId = testHelper.getUniqueDatabaseId(); + DatabaseAdminClient client = spanner.getDatabaseAdminClient(); + OperationFuture op = + client.restoreDatabase( + testHelper.getInstanceId().getInstance(), + backupId, + testHelper.getInstanceId().getInstance(), + restoredDbId); + databases.add(op.get(RESTORE_TIMEOUT_MINUTES, TimeUnit.MINUTES)); + // Assert that the RestoreDatabase RPC was called only once, and that the operation + // tracking was resumed through a GetOperation call. + assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); + assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); + break; + } catch (ExecutionException e) { + if (e.getCause() instanceof SpannerException + && ((SpannerException) e.getCause()).getErrorCode() == ErrorCode.FAILED_PRECONDITION + && e.getCause() + .getMessage() + .contains("Please retry the operation once the pending restores complete")) { + attempts++; + if (attempts == 10) { + // Still same error after 10 attempts. Ignore. + break; + } + // wait and then retry. + Thread.sleep(60_000L); + } else { + throw e; + } + } + } + + // Create another database with the exact same name as the first database. + createDbInterceptor = new InjectErrorInterceptorProvider("CreateDatabase"); + options = + testHelper.getOptions().toBuilder().setInterceptorProvider(createDbInterceptor).build(); + try (Spanner spanner = options.getService()) { + DatabaseAdminClient client = spanner.getDatabaseAdminClient(); + // First drop the initial database. + client.dropDatabase(testHelper.getInstanceId().getInstance(), initialDatabaseId); + // Now re-create a database with the exact same name. + OperationFuture op = + client.createDatabase( + testHelper.getInstanceId().getInstance(), initialDatabaseId, Collections.emptyList()); + // Check that the second database was created and has a greater creation time than the + // first. + Timestamp secondCreationTime = + op.get(DATABASE_TIMEOUT_MINUTES, TimeUnit.MINUTES).getCreateTime(); + // TODO: Change this to greaterThan when the create time of a database is reported back by + // the server. + assertThat(secondCreationTime).isAtLeast(initialDbCreateTime); + // Assert that the CreateDatabase RPC was called only once, and that the operation tracking + // was resumed through a GetOperation call. + assertThat(createDbInterceptor.methodCount.get()).isEqualTo(1); + assertThat(createDbInterceptor.getOperationCount.get()).isAtLeast(1); + } + } + @Test(expected = SpannerException.class) public void backupCreationWithVersionTimeTooFarInThePastFails() throws Exception { final Database testDatabase = testHelper.createTestDatabase(); @@ -749,4 +901,64 @@ private void verifyRestoreOperations( input -> input.getName().equals(restoreOperationName))) .isTrue(); } + + private static final class InjectErrorInterceptorProvider implements GrpcInterceptorProvider { + final AtomicBoolean injectError = new AtomicBoolean(true); + final AtomicInteger getOperationCount = new AtomicInteger(); + final AtomicInteger methodCount = new AtomicInteger(); + final String methodName; + + private InjectErrorInterceptorProvider(String methodName) { + this.methodName = methodName; + } + + @Override + public List getInterceptors() { + ClientInterceptor interceptor = + new ClientInterceptor() { + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + if (method.getFullMethodName().contains("GetOperation")) { + getOperationCount.incrementAndGet(); + } + if (!method.getFullMethodName().contains(methodName)) { + return next.newCall(method, callOptions); + } + + methodCount.incrementAndGet(); + final AtomicBoolean errorInjected = new AtomicBoolean(); + final ClientCall clientCall = next.newCall(method, callOptions); + + return new SimpleForwardingClientCall(clientCall) { + @Override + public void start(Listener responseListener, Metadata headers) { + super.start( + new SimpleForwardingClientCallListener(responseListener) { + @Override + public void onMessage(RespT message) { + if (injectError.getAndSet(false)) { + errorInjected.set(true); + clientCall.cancel("Cancelling call for injected error", null); + } else { + super.onMessage(message); + } + } + + @Override + public void onClose(Status status, Metadata metadata) { + if (errorInjected.get()) { + status = Status.UNAVAILABLE.augmentDescription("INJECTED BY TEST"); + } + super.onClose(status, metadata); + } + }, + headers); + } + }; + } + }; + return Collections.singletonList(interceptor); + } + } } diff --git a/grpc-google-cloud-spanner-admin-database-v1/pom.xml b/grpc-google-cloud-spanner-admin-database-v1/pom.xml index c5a9067fba1..9908058de37 100644 --- a/grpc-google-cloud-spanner-admin-database-v1/pom.xml +++ b/grpc-google-cloud-spanner-admin-database-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.13.0 + 6.14.0 grpc-google-cloud-spanner-admin-database-v1 GRPC library for grpc-google-cloud-spanner-admin-database-v1 com.google.cloud google-cloud-spanner-parent - 6.13.0 + 6.14.0 diff --git a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml index 14e7d776641..99aeb7cd43f 100644 --- a/grpc-google-cloud-spanner-admin-instance-v1/pom.xml +++ b/grpc-google-cloud-spanner-admin-instance-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.13.0 + 6.14.0 grpc-google-cloud-spanner-admin-instance-v1 GRPC library for grpc-google-cloud-spanner-admin-instance-v1 com.google.cloud google-cloud-spanner-parent - 6.13.0 + 6.14.0 diff --git a/grpc-google-cloud-spanner-v1/pom.xml b/grpc-google-cloud-spanner-v1/pom.xml index ec67266f4c7..f37c72aadf3 100644 --- a/grpc-google-cloud-spanner-v1/pom.xml +++ b/grpc-google-cloud-spanner-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.13.0 + 6.14.0 grpc-google-cloud-spanner-v1 GRPC library for grpc-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.13.0 + 6.14.0 diff --git a/pom.xml b/pom.xml index e12a475672c..ab56795501f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-spanner-parent pom - 6.13.0 + 6.14.0 Google Cloud Spanner Parent https://github.com/googleapis/java-spanner @@ -14,7 +14,7 @@ com.google.cloud google-cloud-shared-config - 1.0.3 + 1.2.0 @@ -54,7 +54,7 @@ UTF-8 github google-cloud-spanner-parent - 2.3.0 + 2.4.0 @@ -62,37 +62,37 @@ com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.13.0 + 6.14.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.13.0 + 6.14.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.13.0 + 6.14.0 com.google.api.grpc grpc-google-cloud-spanner-v1 - 6.13.0 + 6.14.0 com.google.api.grpc grpc-google-cloud-spanner-admin-instance-v1 - 6.13.0 + 6.14.0 com.google.api.grpc grpc-google-cloud-spanner-admin-database-v1 - 6.13.0 + 6.14.0 com.google.cloud google-cloud-spanner - 6.13.0 + 6.14.0 diff --git a/proto-google-cloud-spanner-admin-database-v1/pom.xml b/proto-google-cloud-spanner-admin-database-v1/pom.xml index 205821a9883..c7df8eb8796 100644 --- a/proto-google-cloud-spanner-admin-database-v1/pom.xml +++ b/proto-google-cloud-spanner-admin-database-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-database-v1 - 6.13.0 + 6.14.0 proto-google-cloud-spanner-admin-database-v1 PROTO library for proto-google-cloud-spanner-admin-database-v1 com.google.cloud google-cloud-spanner-parent - 6.13.0 + 6.14.0 diff --git a/proto-google-cloud-spanner-admin-instance-v1/pom.xml b/proto-google-cloud-spanner-admin-instance-v1/pom.xml index b1eefd5afe7..e7086bbbead 100644 --- a/proto-google-cloud-spanner-admin-instance-v1/pom.xml +++ b/proto-google-cloud-spanner-admin-instance-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-admin-instance-v1 - 6.13.0 + 6.14.0 proto-google-cloud-spanner-admin-instance-v1 PROTO library for proto-google-cloud-spanner-admin-instance-v1 com.google.cloud google-cloud-spanner-parent - 6.13.0 + 6.14.0 diff --git a/proto-google-cloud-spanner-v1/pom.xml b/proto-google-cloud-spanner-v1/pom.xml index 1aefbca8a9c..72c1229c01c 100644 --- a/proto-google-cloud-spanner-v1/pom.xml +++ b/proto-google-cloud-spanner-v1/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-spanner-v1 - 6.13.0 + 6.14.0 proto-google-cloud-spanner-v1 PROTO library for proto-google-cloud-spanner-v1 com.google.cloud google-cloud-spanner-parent - 6.13.0 + 6.14.0 diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 093486551d2..6c22d293eff 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -22,8 +22,8 @@ 1.8 UTF-8 0.28.3 - 2.0.5 - 3.0.7 + 2.0.6 + 3.1.0 @@ -32,7 +32,7 @@ com.google.cloud google-cloud-spanner - 6.12.5 + 6.13.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index f54e71a2fa0..f05c416264a 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -22,8 +22,8 @@ 1.8 UTF-8 0.28.3 - 2.0.5 - 3.0.7 + 2.0.6 + 3.1.0 @@ -31,7 +31,7 @@ com.google.cloud google-cloud-spanner - 6.13.0 + 6.14.0 diff --git a/samples/snippets/src/main/java/com/example/spanner/TagSample.java b/samples/snippets/src/main/java/com/example/spanner/TagSample.java new file mode 100644 index 00000000000..d8124d3ae26 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/TagSample.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner; + +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.Options; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Statement; + +/** + * Sample showing how to add transaction and query tags to Cloud Spanner operations. + */ +public class TagSample { + + // [START spanner_set_transaction_tag] + static void setTransactionTag(DatabaseClient databaseClient) { + // Sets the transaction tag to "app=concert,env=dev". + // This transaction tag will be applied to all the individual operations inside this + // transaction. + databaseClient + .readWriteTransaction(Options.tag("app=concert,env=dev")) + .run(transaction -> { + // Sets the request tag to "app=concert,env=dev,action=update". + // This request tag will only be set on this request. + transaction.executeUpdate( + Statement.of("UPDATE Venues" + + " SET Capacity = CAST(Capacity/4 AS INT64)" + + " WHERE OutdoorVenue = false"), + Options.tag("app=concert,env=dev,action=update")); + System.out.println("Venue capacities updated."); + + Statement insertStatement = Statement.newBuilder( + "INSERT INTO Venues" + + " (VenueId, VenueName, Capacity, OutdoorVenue, LastUpdateTime)" + + " VALUES (" + + " @venueId, @venueName, @capacity, @outdoorVenue, PENDING_COMMIT_TIMESTAMP()" + + " )") + .bind("venueId") + .to(81) + .bind("venueName") + .to("Venue 81") + .bind("capacity") + .to(1440) + .bind("outdoorVenue") + .to(true) + .build(); + + // Sets the request tag to "app=concert,env=dev,action=insert". + // This request tag will only be set on this request. + transaction.executeUpdate( + insertStatement, + Options.tag("app=concert,env=dev,action=insert")); + System.out.println("New venue inserted."); + + return null; + }); + } + // [END spanner_set_transaction_tag] + + // [START spanner_set_request_tag] + static void setRequestTag(DatabaseClient databaseClient) { + // Sets the request tag to "app=concert,env=dev,action=select". + // This request tag will only be set on this request. + try (ResultSet resultSet = databaseClient + .singleUse() + .executeQuery( + Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"), + Options.tag("app=concert,env=dev,action=select"))) { + while (resultSet.next()) { + System.out.printf( + "SingerId: %d, AlbumId: %d, AlbumTitle: %s\n", + resultSet.getLong(0), + resultSet.getLong(1), + resultSet.getString(2)); + } + } + } + // [END spanner_set_request_tag] +} diff --git a/samples/snippets/src/test/java/com/example/spanner/TagSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/TagSampleIT.java new file mode 100644 index 00000000000..61733c1f0a4 --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/TagSampleIT.java @@ -0,0 +1,118 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner; + +import static com.example.spanner.SampleRunner.runSample; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.KeySet; +import com.google.cloud.spanner.Mutation; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Integration tests for {@link TagSample} + */ +@RunWith(JUnit4.class) +public class TagSampleIT extends SampleTestBase { + + private static DatabaseId databaseId; + + @BeforeClass + public static void createTestDatabase() throws Exception { + final String database = idGenerator.generateDatabaseId(); + databaseAdminClient + .createDatabase( + instanceId, + database, + ImmutableList.of( + "CREATE TABLE Albums (" + + " SingerId INT64 NOT NULL," + + " AlbumId INT64," + + " AlbumTitle STRING(1024)" + + ") PRIMARY KEY (SingerId, AlbumId)", + "CREATE TABLE Venues (" + + " VenueId INT64 NOT NULL," + + " VenueName STRING(MAX)," + + " Capacity INT64," + + " OutdoorVenue BOOL," + + " LastUpdateTime TIMESTAMP OPTIONS (allow_commit_timestamp=true)" + + ") PRIMARY KEY (VenueId)")) + .get(10, TimeUnit.MINUTES); + databaseId = DatabaseId.of(projectId, instanceId, database); + } + + @Before + public void insertTestData() { + final DatabaseClient client = spanner.getDatabaseClient(databaseId); + client.write( + Arrays.asList( + Mutation.newInsertOrUpdateBuilder("Albums") + .set("SingerId") + .to(1L) + .set("AlbumId") + .to(1L) + .set("AlbumTitle") + .to("title 1") + .build(), + Mutation.newInsertOrUpdateBuilder("Venues") + .set("VenueId") + .to(4L) + .set("VenueName") + .to("name") + .set("Capacity") + .to(4000000) + .set("OutdoorVenue") + .to(false) + .build())); + } + + @After + public void removeTestData() { + final DatabaseClient client = spanner.getDatabaseClient(databaseId); + client.write(Collections.singletonList(Mutation.delete("Albums", KeySet.all()))); + client.write(Collections.singleton(Mutation.delete("Venues", KeySet.all()))); + } + + @Test + public void testSetRequestTag() throws Exception { + final DatabaseClient client = spanner.getDatabaseClient(databaseId); + + final String out = runSample(() -> TagSample.setRequestTag(client)); + assertTrue(out.contains("SingerId: 1, AlbumId: 1, AlbumTitle: title 1")); + } + + @Test + public void testSetTransactionTag() throws Exception { + final DatabaseClient client = spanner.getDatabaseClient(databaseId); + + final String out = runSample(() -> TagSample.setTransactionTag(client)); + assertTrue(out.contains("Venue capacities updated.")); + assertTrue(out.contains("New venue inserted.")); + } +} diff --git a/synth.metadata b/synth.metadata index 392814c1d12..a295fe17c93 100644 --- a/synth.metadata +++ b/synth.metadata @@ -4,7 +4,7 @@ "git": { "name": ".", "remote": "https://github.com/googleapis/java-spanner.git", - "sha": "06f98b174b785d61ad430b27a3a8216e3c874318" + "sha": "9083e827f82d0f11b97a311d9e2a1af7447fc1de" } }, { @@ -19,7 +19,7 @@ "git": { "name": "synthtool", "remote": "https://github.com/googleapis/synthtool.git", - "sha": "0752ff727a19a467dffed335d5e59303689cf0d1" + "sha": "d45942be8066ad57bd0509f4a16e1fac78ecc50f" } } ], @@ -79,6 +79,7 @@ ".kokoro/populate-secrets.sh", ".kokoro/presubmit/clirr.cfg", ".kokoro/presubmit/dependencies.cfg", + ".kokoro/presubmit/graalvm-native.cfg", ".kokoro/presubmit/integration.cfg", ".kokoro/presubmit/java11.cfg", ".kokoro/presubmit/java7.cfg", diff --git a/versions.txt b/versions.txt index 87d31737895..ef14e5ba025 100644 --- a/versions.txt +++ b/versions.txt @@ -1,10 +1,10 @@ # Format: # module:released-version:current-version -proto-google-cloud-spanner-admin-instance-v1:6.13.0:6.13.0 -proto-google-cloud-spanner-v1:6.13.0:6.13.0 -proto-google-cloud-spanner-admin-database-v1:6.13.0:6.13.0 -grpc-google-cloud-spanner-v1:6.13.0:6.13.0 -grpc-google-cloud-spanner-admin-instance-v1:6.13.0:6.13.0 -grpc-google-cloud-spanner-admin-database-v1:6.13.0:6.13.0 -google-cloud-spanner:6.13.0:6.13.0 \ No newline at end of file +proto-google-cloud-spanner-admin-instance-v1:6.14.0:6.14.0 +proto-google-cloud-spanner-v1:6.14.0:6.14.0 +proto-google-cloud-spanner-admin-database-v1:6.14.0:6.14.0 +grpc-google-cloud-spanner-v1:6.14.0:6.14.0 +grpc-google-cloud-spanner-admin-instance-v1:6.14.0:6.14.0 +grpc-google-cloud-spanner-admin-database-v1:6.14.0:6.14.0 +google-cloud-spanner:6.14.0:6.14.0 \ No newline at end of file