From 26999121179ff7599d0ecccdd826a4d09bfeefbf Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 22 Sep 2020 14:10:52 +1000 Subject: [PATCH 01/21] feat: exposes new pitr-lite database fields Exposes earliest version time and version retention period fields in the database class. --- .../com/google/cloud/spanner/Database.java | 2 ++ .../google/cloud/spanner/DatabaseInfo.java | 36 +++++++++++++++++++ .../spanner/DatabaseAdminClientImplTest.java | 11 +++++- .../google/cloud/spanner/DatabaseTest.java | 7 ++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index 89ee6e875ac..d1061ac2c0e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -177,6 +177,8 @@ static Database fromProto( .setState(fromProtoState(proto.getState())) .setCreateTime(Timestamp.fromProto(proto.getCreateTime())) .setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo())) + .setVersionRetentionPeriod(proto.getVersionRetentionPeriod()) + .setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime())) .setProto(proto) .build(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 5b7faa324e6..1abf4c964b9 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -30,6 +30,10 @@ public abstract static class Builder { abstract Builder setRestoreInfo(RestoreInfo restoreInfo); + abstract Builder setVersionRetentionPeriod(String versionRetentionPeriod); + + abstract Builder setEarliestVersionTime(Timestamp earliestVersionTime); + abstract Builder setProto(com.google.spanner.admin.database.v1.Database proto); /** Builds the database from this builder. */ @@ -41,6 +45,8 @@ abstract static class BuilderImpl extends Builder { private State state = State.UNSPECIFIED; private Timestamp createTime; private RestoreInfo restoreInfo; + private String versionRetentionPeriod; + private Timestamp earliestVersionTime; private com.google.spanner.admin.database.v1.Database proto; BuilderImpl(DatabaseId id) { @@ -52,6 +58,8 @@ abstract static class BuilderImpl extends Builder { this.state = other.state; this.createTime = other.createTime; this.restoreInfo = other.restoreInfo; + this.versionRetentionPeriod = other.versionRetentionPeriod; + this.earliestVersionTime = other.earliestVersionTime; this.proto = other.proto; } @@ -73,6 +81,18 @@ Builder setRestoreInfo(@Nullable RestoreInfo restoreInfo) { return this; } + @Override + Builder setVersionRetentionPeriod(String versionRetentionPeriod) { + this.versionRetentionPeriod = versionRetentionPeriod; + return this; + } + + @Override + Builder setEarliestVersionTime(Timestamp earliestVersionTime) { + this.earliestVersionTime = earliestVersionTime; + return this; + } + @Override Builder setProto(@Nullable com.google.spanner.admin.database.v1.Database proto) { this.proto = proto; @@ -96,6 +116,8 @@ public enum State { private final State state; private final Timestamp createTime; private final RestoreInfo restoreInfo; + private final String versionRetentionPeriod; + private final Timestamp earliestVersionTime; private final com.google.spanner.admin.database.v1.Database proto; public DatabaseInfo(DatabaseId id, State state) { @@ -103,6 +125,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.state = state; this.createTime = null; this.restoreInfo = null; + this.versionRetentionPeriod = null; + this.earliestVersionTime = null; this.proto = null; } @@ -111,6 +135,8 @@ public DatabaseInfo(DatabaseId id, State state) { this.state = builder.state; this.createTime = builder.createTime; this.restoreInfo = builder.restoreInfo; + this.versionRetentionPeriod = builder.versionRetentionPeriod; + this.earliestVersionTime = builder.earliestVersionTime; this.proto = builder.proto; } @@ -129,6 +155,16 @@ public Timestamp getCreateTime() { return createTime; } + /** Returns the version retention period of the database. */ + public String getVersionRetentionPeriod() { + return versionRetentionPeriod; + } + + /** Returns the earliest version time of the database. */ + public Timestamp getEarliestVersionTime() { + return earliestVersionTime; + } + /** * Returns the {@link RestoreInfo} of the database if any is available, or null if no * {@link RestoreInfo} is available for this database. diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index b2676473cf1..ddaa3d4e4ee 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -69,6 +69,8 @@ public class DatabaseAdminClientImplTest { private static final String BK_ID = "my-bk"; private static final String BK_NAME = "projects/my-project/instances/my-instance/backups/my-bk"; private static final String BK_NAME2 = "projects/my-project/instances/my-instance/backups/my-bk2"; + private static final Timestamp EARLIEST_VERSION_TIME = Timestamp.now(); + private static final String VERSION_RETENTION_PERIOD = "7d"; @Mock SpannerRpc rpc; DatabaseAdminClientImpl client; @@ -80,7 +82,12 @@ public void setUp() { } private Database getDatabaseProto() { - return Database.newBuilder().setName(DB_NAME).setState(Database.State.READY).build(); + return Database.newBuilder() + .setName(DB_NAME) + .setState(Database.State.READY) + .setEarliestVersionTime(EARLIEST_VERSION_TIME.toProto()) + .setVersionRetentionPeriod(VERSION_RETENTION_PERIOD) + .build(); } private Database getAnotherDatabaseProto() { @@ -116,6 +123,8 @@ public void getDatabase() { com.google.cloud.spanner.Database db = client.getDatabase(INSTANCE_ID, DB_ID); assertThat(db.getId().getName()).isEqualTo(DB_NAME); assertThat(db.getState()).isEqualTo(DatabaseInfo.State.READY); + assertThat(db.getEarliestVersionTime()).isEqualTo(EARLIEST_VERSION_TIME); + assertThat(db.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); } @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java index 95576150071..a65f2b4a02c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java @@ -42,6 +42,9 @@ public class DatabaseTest { private static final String NAME = "projects/test-project/instances/test-instance/databases/database-1"; + private static final Timestamp EARLIEST_VERSION_TIME = Timestamp.now(); + private static final String VERSION_RETENTION_PERIOD = "7d"; + @Mock DatabaseAdminClient dbClient; @Before @@ -82,6 +85,8 @@ public void fromProto() { Database db = createDatabase(); assertThat(db.getId().getName()).isEqualTo(NAME); assertThat(db.getState()).isEqualTo(DatabaseInfo.State.CREATING); + assertThat(db.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(db.getEarliestVersionTime()).isEqualTo(EARLIEST_VERSION_TIME); } @Test @@ -119,6 +124,8 @@ private Database createDatabase() { com.google.spanner.admin.database.v1.Database.newBuilder() .setName(NAME) .setState(com.google.spanner.admin.database.v1.Database.State.CREATING) + .setEarliestVersionTime(EARLIEST_VERSION_TIME.toProto()) + .setVersionRetentionPeriod(VERSION_RETENTION_PERIOD) .build(); return Database.fromProto(proto, dbClient); } From ad219b7b8369629796df9ac3c6fa929fccfd1ae8 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 22 Sep 2020 16:18:32 +1000 Subject: [PATCH 02/21] feat: adds it tests for updating version retention Adds integration tests for updating the version retention period. --- .../cloud/spanner/it/ITPitrUpdateDdlTest.java | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDdlTest.java diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDdlTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDdlTest.java new file mode 100644 index 00000000000..9221c65d117 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDdlTest.java @@ -0,0 +1,159 @@ +/* + * Copyright 2020 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.google.cloud.spanner.it; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.api.gax.paging.Page; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.Duration; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITPitrUpdateDdlTest { + + private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(1); + private static final String VERSION_RETENTION_PERIOD = "7d"; + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private static DatabaseAdminClient dbAdminClient; + private static DatabaseClient dbClient; + private static String projectId; + private static String instanceId; + private static String databaseId; + + @BeforeClass + public static void setUp() throws Exception { + final RemoteSpannerHelper testHelper = env.getTestHelper(); + projectId = testHelper.getOptions().getProjectId(); + instanceId = testHelper.getInstanceId().getInstance(); + databaseId = testHelper.getUniqueDatabaseId(); + dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); + + createDatabase(dbAdminClient, instanceId, databaseId); + updateVersionRetentionPeriod(dbAdminClient, instanceId, databaseId, VERSION_RETENTION_PERIOD); + + dbClient = + testHelper.getClient().getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); + } + + @AfterClass + public static void tearDown() { + dbAdminClient.dropDatabase(instanceId, databaseId); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() { + final Database database = dbAdminClient.getDatabase(instanceId, databaseId); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughListDatabases() { + final Page page = dbAdminClient.listDatabases(instanceId); + + for (Database database : page.iterateAll()) { + if (!database.getId().getDatabase().equals(databaseId)) { + continue; + } + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { + final List ddls = dbAdminClient.getDatabaseDdl(instanceId, databaseId); + + boolean hasVersionRetentionPeriodStatement = false; + for (String ddl : ddls) { + hasVersionRetentionPeriodStatement = + ddl.contains("version_retention_period = '" + VERSION_RETENTION_PERIOD + "'"); + if (hasVersionRetentionPeriodStatement) { + break; + } + } + assertThat(hasVersionRetentionPeriodStatement).isTrue(); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughInformationSchema() { + final ResultSet rs = + dbClient + .singleUse() + .executeQuery( + Statement.of( + "SELECT OPTION_VALUE AS version_retention_period " + + "FROM INFORMATION_SCHEMA.DATABASE_OPTIONS " + + "WHERE SCHEMA_NAME = '' AND OPTION_NAME = 'version_retention_period'")); + + String versionRetentionPeriod = null; + while (rs.next()) { + versionRetentionPeriod = rs.getString("version_retention_period"); + } + + assertThat(versionRetentionPeriod).isEqualTo(VERSION_RETENTION_PERIOD); + } + + private static void createDatabase( + final DatabaseAdminClient dbAdminClient, final String instanceId, final String databaseId) + throws Exception { + dbAdminClient + .createDatabase(instanceId, databaseId, Collections.emptyList()) + .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + } + + private static void updateVersionRetentionPeriod( + final DatabaseAdminClient dbAdminClient, + final String instanceId, + final String databaseId, + final String versionRetentionPeriod) + throws Exception { + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, + Collections.singletonList( + "ALTER DATABASE " + + databaseId + + " SET OPTIONS ( version_retention_period = '" + + versionRetentionPeriod + + "' )"), + "updateddl_version_retention_period") + .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + } +} From e53653823bbf55176b8eb1ece0cf8537fabf386c Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 22 Sep 2020 17:14:40 +1000 Subject: [PATCH 03/21] feat: adds new create database tests for pitr Adds create database tests for PITR and refactors the integration test class. --- ...teDdlTest.java => ITPitrDatabaseTest.java} | 109 ++++++++++++++++-- 1 file changed, 98 insertions(+), 11 deletions(-) rename google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/{ITPitrUpdateDdlTest.java => ITPitrDatabaseTest.java} (55%) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDdlTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrDatabaseTest.java similarity index 55% rename from google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDdlTest.java rename to google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrDatabaseTest.java index 9221c65d117..ffb0447a676 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDdlTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrDatabaseTest.java @@ -17,15 +17,19 @@ package com.google.cloud.spanner.it; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; import com.google.api.gax.paging.Page; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.DatabaseNotFoundException; +import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.IntegrationTestEnv; import com.google.cloud.spanner.ParallelIntegrationTest; import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.testing.RemoteSpannerHelper; import java.util.Collections; @@ -42,12 +46,13 @@ @Category(ParallelIntegrationTest.class) @RunWith(JUnit4.class) -public class ITPitrUpdateDdlTest { +public class ITPitrDatabaseTest { private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(1); private static final String VERSION_RETENTION_PERIOD = "7d"; @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private static RemoteSpannerHelper testHelper; private static DatabaseAdminClient dbAdminClient; private static DatabaseClient dbClient; private static String projectId; @@ -56,13 +61,13 @@ public class ITPitrUpdateDdlTest { @BeforeClass public static void setUp() throws Exception { - final RemoteSpannerHelper testHelper = env.getTestHelper(); + testHelper = env.getTestHelper(); projectId = testHelper.getOptions().getProjectId(); instanceId = testHelper.getInstanceId().getInstance(); databaseId = testHelper.getUniqueDatabaseId(); dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); - createDatabase(dbAdminClient, instanceId, databaseId); + createDatabase(dbAdminClient, instanceId, databaseId, Collections.emptyList()); updateVersionRetentionPeriod(dbAdminClient, instanceId, databaseId, VERSION_RETENTION_PERIOD); dbClient = @@ -75,7 +80,7 @@ public static void tearDown() { } @Test - public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() { + public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughGetDatabase() { final Database database = dbAdminClient.getDatabase(instanceId, databaseId); assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); @@ -83,7 +88,7 @@ public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() { } @Test - public void returnsTheVersionRetentionPeriodSetThroughListDatabases() { + public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughListDatabases() { final Page page = dbAdminClient.listDatabases(instanceId); for (Database database : page.iterateAll()) { @@ -96,7 +101,7 @@ public void returnsTheVersionRetentionPeriodSetThroughListDatabases() { } @Test - public void returnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { + public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { final List ddls = dbAdminClient.getDatabaseDdl(instanceId, databaseId); boolean hasVersionRetentionPeriodStatement = false; @@ -111,7 +116,7 @@ public void returnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { } @Test - public void returnsTheVersionRetentionPeriodSetThroughInformationSchema() { + public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughInformationSchema() { final ResultSet rs = dbClient .singleUse() @@ -129,11 +134,93 @@ public void returnsTheVersionRetentionPeriodSetThroughInformationSchema() { assertThat(versionRetentionPeriod).isEqualTo(VERSION_RETENTION_PERIOD); } - private static void createDatabase( - final DatabaseAdminClient dbAdminClient, final String instanceId, final String databaseId) + @Test + public void inUpdateDatabaseReturnsAnErrorWhenAnInvalidRetentionPeriodIsGiven() { + try { + dbAdminClient + .updateDatabaseDdl( + instanceId, + databaseId, + Collections.singletonList( + "ALTER DATABASE " + + databaseId + + " SET OPTIONS (version_retention_period = '0d')"), + "op_invalid_retention_period_" + databaseId) + .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + fail("Expected invalid argument error when setting invalid version retention period"); + } catch (Exception e) { + SpannerException spannerException = (SpannerException) e.getCause(); + assertThat(spannerException.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + + final Database database = dbAdminClient.getDatabase(instanceId, databaseId); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + @Test + public void inNewDatabaseReturnsTheVersionRetentionPeriodSetThroughCreateDatabase() throws Exception { - dbAdminClient - .createDatabase(instanceId, databaseId, Collections.emptyList()) + final String instanceId = testHelper.getInstanceId().getInstance(); + final String newDatabaseId = testHelper.getUniqueDatabaseId(); + final String versionRetentionPeriodStatement = + "ALTER DATABASE " + + newDatabaseId + + " SET OPTIONS (version_retention_period = '" + + VERSION_RETENTION_PERIOD + + "')"; + + try { + final Database database = + createDatabase( + dbAdminClient, + instanceId, + newDatabaseId, + Collections.singletonList(versionRetentionPeriodStatement)); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } finally { + dbAdminClient.dropDatabase(instanceId, newDatabaseId); + } + } + + @Test + public void inNewDatabaseReturnsAnErrorWhenAnInvalidVersionRetentionPeriodIsGiven() { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String newDatabaseId = testHelper.getUniqueDatabaseId(); + final String versionRetentionPeriodStatement = + "ALTER DATABASE " + newDatabaseId + " SET OPTIONS (version_retention_period = '0d')"; + + try { + createDatabase( + dbAdminClient, + instanceId, + newDatabaseId, + Collections.singletonList(versionRetentionPeriodStatement)); + fail("Expected invalid argument error when setting invalid version retention period"); + } catch (Exception e) { + SpannerException spannerException = (SpannerException) e.getCause(); + assertThat(spannerException.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + + try { + dbAdminClient.getDatabase(instanceId, newDatabaseId); + fail("Expected database not found error"); + } catch (DatabaseNotFoundException e) { + // Success case + } + } + + private static Database createDatabase( + final DatabaseAdminClient dbAdminClient, + final String instanceId, + final String databaseId, + final Iterable extraStatements) + throws Exception { + return dbAdminClient + .createDatabase(instanceId, databaseId, extraStatements) .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); } From 2da2ac3de36b5e3e85bfb6bb224937dc2406afb8 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 23 Sep 2020 09:18:47 +1000 Subject: [PATCH 04/21] chore: refactors tests Separates PITR database tests into 2 files for clarity. --- .../spanner/it/ITPitrCreateDatabaseTest.java | 136 ++++++++++++++++++ ...est.java => ITPitrUpdateDatabaseTest.java} | 75 ++-------- 2 files changed, 145 insertions(+), 66 deletions(-) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java rename google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/{ITPitrDatabaseTest.java => ITPitrUpdateDatabaseTest.java} (70%) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java new file mode 100644 index 00000000000..11676e891bb --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java @@ -0,0 +1,136 @@ +/* + * Copyright 2020 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.google.cloud.spanner.it; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.DatabaseNotFoundException; +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.threeten.bp.Duration; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITPitrCreateDatabaseTest { + + private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(2); + private static final String VERSION_RETENTION_PERIOD = "7d"; + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private RemoteSpannerHelper testHelper; + private DatabaseAdminClient dbAdminClient; + private List databasesToDrop; + + @Before + public void setUp() { + testHelper = env.getTestHelper(); + dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); + databasesToDrop = new ArrayList<>(); + } + + @After + public void tearDown() { + for (Database database : databasesToDrop) { + final DatabaseId id = database.getId(); + dbAdminClient.dropDatabase(id.getInstanceId().getInstance(), id.getDatabase()); + } + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughCreateDatabase() throws Exception { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final String extraStatement = + "ALTER DATABASE " + + databaseId + + " SET OPTIONS (version_retention_period = '" + + VERSION_RETENTION_PERIOD + + "')"; + + final Database database = createDatabase(instanceId, databaseId, extraStatement); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + @Test + public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() throws Exception { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final String extraStatement = + "ALTER DATABASE " + + databaseId + + " SET OPTIONS (version_retention_period = '" + + VERSION_RETENTION_PERIOD + + "')"; + + createDatabase(instanceId, databaseId, extraStatement); + final Database database = dbAdminClient.getDatabase(instanceId, databaseId); + + assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(database.getEarliestVersionTime()).isNotNull(); + } + + @Test(expected = DatabaseNotFoundException.class) + public void returnsAnErrorWhenAnInvalidVersionRetentionPeriodIsGiven() { + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final String extraStatement = + "ALTER DATABASE " + databaseId + " SET OPTIONS (version_retention_period = '0d')"; + + try { + createDatabase(instanceId, databaseId, extraStatement); + fail("Expected invalid argument error when setting invalid version retention period"); + } catch (Exception e) { + SpannerException spannerException = (SpannerException) e.getCause(); + assertThat(spannerException.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); + } + + // Expects a database not found exception + dbAdminClient.getDatabase(instanceId, databaseId); + } + + private Database createDatabase( + final String instanceId, final String databaseId, final String extraStatement) + throws Exception { + final Database database = + dbAdminClient + .createDatabase(instanceId, databaseId, Collections.singletonList(extraStatement)) + .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + databasesToDrop.add(database); + + return database; + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java similarity index 70% rename from google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrDatabaseTest.java rename to google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java index ffb0447a676..a651ea89bf6 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrDatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java @@ -24,7 +24,6 @@ import com.google.cloud.spanner.DatabaseAdminClient; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.DatabaseId; -import com.google.cloud.spanner.DatabaseNotFoundException; import com.google.cloud.spanner.ErrorCode; import com.google.cloud.spanner.IntegrationTestEnv; import com.google.cloud.spanner.ParallelIntegrationTest; @@ -46,23 +45,21 @@ @Category(ParallelIntegrationTest.class) @RunWith(JUnit4.class) -public class ITPitrDatabaseTest { +public class ITPitrUpdateDatabaseTest { - private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(1); + private static final Duration OPERATION_TIMEOUT = Duration.ofMinutes(2); private static final String VERSION_RETENTION_PERIOD = "7d"; @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); - private static RemoteSpannerHelper testHelper; private static DatabaseAdminClient dbAdminClient; private static DatabaseClient dbClient; - private static String projectId; private static String instanceId; private static String databaseId; @BeforeClass public static void setUp() throws Exception { - testHelper = env.getTestHelper(); - projectId = testHelper.getOptions().getProjectId(); + final RemoteSpannerHelper testHelper = env.getTestHelper(); + final String projectId = testHelper.getOptions().getProjectId(); instanceId = testHelper.getInstanceId().getInstance(); databaseId = testHelper.getUniqueDatabaseId(); dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); @@ -80,7 +77,7 @@ public static void tearDown() { } @Test - public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughGetDatabase() { + public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() { final Database database = dbAdminClient.getDatabase(instanceId, databaseId); assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); @@ -88,7 +85,7 @@ public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughGetDataba } @Test - public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughListDatabases() { + public void returnsTheVersionRetentionPeriodSetThroughListDatabases() { final Page page = dbAdminClient.listDatabases(instanceId); for (Database database : page.iterateAll()) { @@ -101,7 +98,7 @@ public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughListDatab } @Test - public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { + public void returnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { final List ddls = dbAdminClient.getDatabaseDdl(instanceId, databaseId); boolean hasVersionRetentionPeriodStatement = false; @@ -116,7 +113,7 @@ public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughGetDataba } @Test - public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughInformationSchema() { + public void returnsTheVersionRetentionPeriodSetThroughInformationSchema() { final ResultSet rs = dbClient .singleUse() @@ -135,7 +132,7 @@ public void inUpdatedDatabaseReturnsTheVersionRetentionPeriodSetThroughInformati } @Test - public void inUpdateDatabaseReturnsAnErrorWhenAnInvalidRetentionPeriodIsGiven() { + public void returnsAnErrorWhenAnInvalidRetentionPeriodIsGiven() { try { dbAdminClient .updateDatabaseDdl( @@ -159,60 +156,6 @@ public void inUpdateDatabaseReturnsAnErrorWhenAnInvalidRetentionPeriodIsGiven() assertThat(database.getEarliestVersionTime()).isNotNull(); } - @Test - public void inNewDatabaseReturnsTheVersionRetentionPeriodSetThroughCreateDatabase() - throws Exception { - final String instanceId = testHelper.getInstanceId().getInstance(); - final String newDatabaseId = testHelper.getUniqueDatabaseId(); - final String versionRetentionPeriodStatement = - "ALTER DATABASE " - + newDatabaseId - + " SET OPTIONS (version_retention_period = '" - + VERSION_RETENTION_PERIOD - + "')"; - - try { - final Database database = - createDatabase( - dbAdminClient, - instanceId, - newDatabaseId, - Collections.singletonList(versionRetentionPeriodStatement)); - - assertThat(database.getVersionRetentionPeriod()).isEqualTo(VERSION_RETENTION_PERIOD); - assertThat(database.getEarliestVersionTime()).isNotNull(); - } finally { - dbAdminClient.dropDatabase(instanceId, newDatabaseId); - } - } - - @Test - public void inNewDatabaseReturnsAnErrorWhenAnInvalidVersionRetentionPeriodIsGiven() { - final String instanceId = testHelper.getInstanceId().getInstance(); - final String newDatabaseId = testHelper.getUniqueDatabaseId(); - final String versionRetentionPeriodStatement = - "ALTER DATABASE " + newDatabaseId + " SET OPTIONS (version_retention_period = '0d')"; - - try { - createDatabase( - dbAdminClient, - instanceId, - newDatabaseId, - Collections.singletonList(versionRetentionPeriodStatement)); - fail("Expected invalid argument error when setting invalid version retention period"); - } catch (Exception e) { - SpannerException spannerException = (SpannerException) e.getCause(); - assertThat(spannerException.getErrorCode()).isEqualTo(ErrorCode.INVALID_ARGUMENT); - } - - try { - dbAdminClient.getDatabase(instanceId, newDatabaseId); - fail("Expected database not found error"); - } catch (DatabaseNotFoundException e) { - // Success case - } - } - private static Database createDatabase( final DatabaseAdminClient dbAdminClient, final String instanceId, From 7968a9f79bece961a647e493a30bdc20f308df58 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 23 Sep 2020 15:09:16 +1000 Subject: [PATCH 05/21] fix: disables pitr-lite tests in emulator The feature is not supported in the emulator currently. --- .../google/cloud/spanner/it/ITPitrCreateDatabaseTest.java | 8 ++++++++ .../google/cloud/spanner/it/ITPitrUpdateDatabaseTest.java | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java index 11676e891bb..f9fa081e413 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrCreateDatabaseTest.java @@ -16,8 +16,10 @@ 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.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseAdminClient; @@ -34,6 +36,7 @@ import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -53,6 +56,11 @@ public class ITPitrCreateDatabaseTest { private DatabaseAdminClient dbAdminClient; private List databasesToDrop; + @BeforeClass + public static void doNotRunOnEmulator() { + assumeFalse("PITR-lite features are not supported by the emulator", isUsingEmulator()); + } + @Before public void setUp() { testHelper = env.getTestHelper(); 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 a651ea89bf6..e4130fe67ad 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 @@ -16,8 +16,10 @@ 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.paging.Page; import com.google.cloud.spanner.Database; @@ -58,6 +60,8 @@ public class ITPitrUpdateDatabaseTest { @BeforeClass public static void setUp() throws Exception { + assumeFalse("PITR-lite features are not supported by the emulator", isUsingEmulator()); + final RemoteSpannerHelper testHelper = env.getTestHelper(); final String projectId = testHelper.getOptions().getProjectId(); instanceId = testHelper.getInstanceId().getInstance(); @@ -73,7 +77,9 @@ public static void setUp() throws Exception { @AfterClass public static void tearDown() { - dbAdminClient.dropDatabase(instanceId, databaseId); + if (!isUsingEmulator()) { + dbAdminClient.dropDatabase(instanceId, databaseId); + } } @Test From 3764028832eca107a43807cffb2bcd0abe764b30 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Fri, 2 Oct 2020 09:09:18 +1000 Subject: [PATCH 06/21] fix: closes result set in test Addresses PR comment. --- .../spanner/it/ITPitrUpdateDatabaseTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) 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 e4130fe67ad..4fd34aeac2e 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 @@ -120,21 +120,22 @@ public void returnsTheVersionRetentionPeriodSetThroughGetDatabaseDdl() { @Test public void returnsTheVersionRetentionPeriodSetThroughInformationSchema() { - final ResultSet rs = + try (final ResultSet rs = dbClient .singleUse() .executeQuery( Statement.of( "SELECT OPTION_VALUE AS version_retention_period " + "FROM INFORMATION_SCHEMA.DATABASE_OPTIONS " - + "WHERE SCHEMA_NAME = '' AND OPTION_NAME = 'version_retention_period'")); + + "WHERE SCHEMA_NAME = '' AND OPTION_NAME = 'version_retention_period'"))) { - String versionRetentionPeriod = null; - while (rs.next()) { - versionRetentionPeriod = rs.getString("version_retention_period"); - } + String versionRetentionPeriod = null; + while (rs.next()) { + versionRetentionPeriod = rs.getString("version_retention_period"); + } - assertThat(versionRetentionPeriod).isEqualTo(VERSION_RETENTION_PERIOD); + assertThat(versionRetentionPeriod).isEqualTo(VERSION_RETENTION_PERIOD); + } } @Test From f3abb93bdee2f4a4af1eaeeea716a2cfaff54933 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Thu, 12 Nov 2020 13:02:29 +1100 Subject: [PATCH 07/21] fix: updates DatabaseInfo equals/hashcode To compare version retention period and earliest version time. --- .../google/cloud/spanner/DatabaseInfo.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 1abf4c964b9..2d7eb4f73d3 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -190,16 +190,31 @@ public boolean equals(Object o) { return id.equals(that.id) && state == that.state && Objects.equals(createTime, that.createTime) - && Objects.equals(restoreInfo, that.restoreInfo); + && Objects.equals(restoreInfo, that.restoreInfo) + && Objects.equals(versionRetentionPeriod, that.versionRetentionPeriod) + && Objects.equals(earliestVersionTime, that.earliestVersionTime); } @Override public int hashCode() { - return Objects.hash(id, state, createTime, restoreInfo); + return Objects.hash( + id, + state, + createTime, + restoreInfo, + versionRetentionPeriod, + earliestVersionTime); } @Override public String toString() { - return String.format("Database[%s, %s, %s, %s]", id.getName(), state, createTime, restoreInfo); + return String.format( + "Database[%s, %s, %s, %s, %s, %s]", + id.getName(), + state, + createTime, + restoreInfo, + versionRetentionPeriod, + earliestVersionTime); } } From 594d9fd863c5aea3798e968ec9ad066e6d5bfb30 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 17 Nov 2020 10:13:23 +1100 Subject: [PATCH 08/21] fix: formatting Fixes formatting of the DatabaseInfo --- .../com/google/cloud/spanner/DatabaseInfo.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 2d7eb4f73d3..0cbb438e85e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -198,23 +198,13 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash( - id, - state, - createTime, - restoreInfo, - versionRetentionPeriod, - earliestVersionTime); + id, state, createTime, restoreInfo, versionRetentionPeriod, earliestVersionTime); } @Override public String toString() { return String.format( "Database[%s, %s, %s, %s, %s, %s]", - id.getName(), - state, - createTime, - restoreInfo, - versionRetentionPeriod, - earliestVersionTime); + id.getName(), state, createTime, restoreInfo, versionRetentionPeriod, earliestVersionTime); } } From 60a084e26b3b03f9d71da033d8ad8678c5259b8e Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Tue, 24 Nov 2020 18:10:05 +1100 Subject: [PATCH 09/21] feature: adds test for throttled pitr field Adds test to check for the throttled field in the update database ddl metadata. --- .../spanner/it/ITPitrUpdateDatabaseTest.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) 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 4fd34aeac2e..72a52b39078 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 @@ -21,6 +21,7 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; +import com.google.api.gax.longrunning.OperationFuture; import com.google.api.gax.paging.Page; import com.google.cloud.spanner.Database; import com.google.cloud.spanner.DatabaseAdminClient; @@ -33,6 +34,7 @@ import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -57,6 +59,7 @@ public class ITPitrUpdateDatabaseTest { private static DatabaseClient dbClient; private static String instanceId; private static String databaseId; + private static UpdateDatabaseDdlMetadata metadata; @BeforeClass public static void setUp() throws Exception { @@ -69,7 +72,9 @@ public static void setUp() throws Exception { dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); createDatabase(dbAdminClient, instanceId, databaseId, Collections.emptyList()); - updateVersionRetentionPeriod(dbAdminClient, instanceId, databaseId, VERSION_RETENTION_PERIOD); + metadata = + updateVersionRetentionPeriod( + dbAdminClient, instanceId, databaseId, VERSION_RETENTION_PERIOD); dbClient = testHelper.getClient().getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId)); @@ -82,6 +87,11 @@ public static void tearDown() { } } + @Test + public void checksThatTheOperationWasNotThrottled() { + assertThat(metadata.getThrottled()).isFalse(); + } + @Test public void returnsTheVersionRetentionPeriodSetThroughGetDatabase() { final Database database = dbAdminClient.getDatabase(instanceId, databaseId); @@ -174,14 +184,14 @@ private static Database createDatabase( .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); } - private static void updateVersionRetentionPeriod( + private static UpdateDatabaseDdlMetadata updateVersionRetentionPeriod( final DatabaseAdminClient dbAdminClient, final String instanceId, final String databaseId, final String versionRetentionPeriod) throws Exception { - dbAdminClient - .updateDatabaseDdl( + final OperationFuture op = + dbAdminClient.updateDatabaseDdl( instanceId, databaseId, Collections.singletonList( @@ -190,7 +200,8 @@ private static void updateVersionRetentionPeriod( + " SET OPTIONS ( version_retention_period = '" + versionRetentionPeriod + "' )"), - "updateddl_version_retention_period") - .get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + "updateddl_version_retention_period"); + op.get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); + return op.getMetadata().get(OPERATION_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); } } From 3208de98945381430a43873f14c1d8a4b1abdb99 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Thu, 26 Nov 2020 12:41:50 +1100 Subject: [PATCH 10/21] fix: explain further the pitr-lite params in docs Adds more explanations to the purpose of the added params for pitr-lite: version_retention_period and earliest_version_time. --- .../java/com/google/cloud/spanner/DatabaseInfo.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java index 0cbb438e85e..5ba9f0aa765 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseInfo.java @@ -155,12 +155,19 @@ public Timestamp getCreateTime() { return createTime; } - /** Returns the version retention period of the database. */ + /** + * Returns the version retention period of the database. This is the period for which Cloud + * Spanner retains all versions of data for the database. For instance, if set to 3 days, Cloud + * Spanner will retain data versions that are up to 3 days old. + */ public String getVersionRetentionPeriod() { return versionRetentionPeriod; } - /** Returns the earliest version time of the database. */ + /** + * Returns the earliest version time of the database. This is the oldest timestamp that can be + * used to read old versions of the data. + */ public Timestamp getEarliestVersionTime() { return earliestVersionTime; } From 72e01d7fa8001847c8ae5e335607721adcb0e100 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 13 Jan 2021 10:39:34 +1100 Subject: [PATCH 11/21] feat: adds version time to backups Adds PITR-lite version time to backups. This should make it possible to specify the consistent time for copying the database. --- .../java/com/google/cloud/spanner/Backup.java | 3 +- .../com/google/cloud/spanner/BackupInfo.java | 33 ++++++++++++++++- .../com/google/cloud/spanner/Database.java | 13 ++++++- .../cloud/spanner/DatabaseAdminClient.java | 26 +++++++++++++ .../spanner/DatabaseAdminClientImpl.java | 30 +++++++++++---- .../com/google/cloud/spanner/BackupTest.java | 24 +++++++++++- .../spanner/DatabaseAdminClientImplTest.java | 37 ++++++++++++++++++- .../spanner/DatabaseAdminClientTest.java | 26 +++++++++++++ 8 files changed, 178 insertions(+), 14 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java index d49e9f7f50b..a0052cd381a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java @@ -65,7 +65,7 @@ public OperationFuture create() { getExpireTime() != null, "Cannot create a backup without an expire time"); Preconditions.checkState( getDatabase() != null, "Cannot create a backup without a source database"); - return dbClient.createBackup(instance(), backup(), sourceDatabase(), getExpireTime()); + return dbClient.createBackup(this); } /** @@ -182,6 +182,7 @@ static Backup fromProto( .setState(fromProtoState(proto.getState())) .setSize(proto.getSizeBytes()) .setExpireTime(Timestamp.fromProto(proto.getExpireTime())) + .setVersionTime(Timestamp.fromProto(proto.getVersionTime())) .setDatabase(DatabaseId.of(proto.getDatabase())) .setProto(proto) .build(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java index 5968bcdc213..0435d32e805 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java @@ -18,6 +18,7 @@ import com.google.api.client.util.Preconditions; import com.google.cloud.Timestamp; +import com.google.spanner.admin.database.v1.Database; import java.util.Objects; import javax.annotation.Nullable; @@ -40,6 +41,17 @@ public abstract static class Builder { */ public abstract Builder setExpireTime(Timestamp expireTime); + /** + * Optional for creating a new backup. + * + *

Specifies the timestamp to have an externally consistent copy of the database. If no + * version time is specified, it will be automatically set to the backup create time. + * + *

The version time can be as far in the past as specified by the database earliest version + * time (see {@link Database#getEarliestVersionTime()}). + */ + public abstract Builder setVersionTime(Timestamp versionTime); + /** * Required for creating a new backup. * @@ -55,6 +67,7 @@ abstract static class BuilderImpl extends Builder { protected final BackupId id; private State state = State.UNSPECIFIED; private Timestamp expireTime; + private Timestamp versionTime; private DatabaseId database; private long size; private com.google.spanner.admin.database.v1.Backup proto; @@ -67,6 +80,7 @@ abstract static class BuilderImpl extends Builder { this.id = other.id; this.state = other.state; this.expireTime = other.expireTime; + this.versionTime = other.versionTime; this.database = other.database; this.size = other.size; this.proto = other.proto; @@ -84,6 +98,12 @@ public Builder setExpireTime(Timestamp expireTime) { return this; } + @Override + public Builder setVersionTime(Timestamp versionTime) { + this.versionTime = Preconditions.checkNotNull(versionTime); + return this; + } + @Override public Builder setDatabase(DatabaseId database) { Preconditions.checkArgument( @@ -119,6 +139,7 @@ public enum State { private final BackupId id; private final State state; private final Timestamp expireTime; + private final Timestamp versionTime; private final DatabaseId database; private final long size; private final com.google.spanner.admin.database.v1.Backup proto; @@ -128,6 +149,7 @@ public enum State { this.state = builder.state; this.size = builder.size; this.expireTime = builder.expireTime; + this.versionTime = builder.versionTime; this.database = builder.database; this.proto = builder.proto; } @@ -157,6 +179,11 @@ public Timestamp getExpireTime() { return expireTime; } + /** Returns the version time of the backup. */ + public Timestamp getVersionTime() { + return versionTime; + } + /** Returns the id of the database that was used to create the backup. */ public DatabaseId getDatabase() { return database; @@ -180,17 +207,19 @@ public boolean equals(Object o) { && state == that.state && size == that.size && Objects.equals(expireTime, that.expireTime) + && Objects.equals(versionTime, that.versionTime) && Objects.equals(database, that.database); } @Override public int hashCode() { - return Objects.hash(id, state, size, expireTime, database); + return Objects.hash(id, state, size, expireTime, versionTime, database); } @Override public String toString() { return String.format( - "Backup[%s, %s, %d, %s, %s]", id.getName(), state, size, expireTime, database); + "Backup[%s, %s, %d, %s, %s, %s]", + id.getName(), state, size, expireTime, versionTime, database); } } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index d1061ac2c0e..92b55100613 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -118,8 +118,17 @@ public OperationFuture backup(Backup backup) { Preconditions.checkArgument( backup.getInstanceId().equals(getId().getInstanceId()), "The instance of the backup must be equal to the instance of this database."); - return dbClient.createBackup( - instance(), backup.getId().getBackup(), database(), backup.getExpireTime()); + + final BackupInfo.Builder backupBuilder = + dbClient + .newBackupBuilder(backup.getId()) + .setDatabase(getId()) + .setExpireTime(backup.getExpireTime()); + if (backup.getVersionTime() != null) { + backupBuilder.setVersionTime(backup.getVersionTime()); + } + + return dbClient.createBackup(backupBuilder.build()); } /** diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java index e7a8ed67871..eae1a3cdf4e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClient.java @@ -101,6 +101,32 @@ OperationFuture createBackup( String sourceInstanceId, String backupId, String databaseId, Timestamp expireTime) throws SpannerException; + /** + * Creates a new backup from a database in a Cloud Spanner instance. + * + *

Example to create a backup. + * + *

{@code
+   * BackupId backupId     = BackupId.of("project", "instance", "backup-id");
+   * DatabaseId databaseId = DatabaseId.of("project", "instance", "database-id");
+   * Timestamp expireTime  = Timestamp.ofTimeMicroseconds(expireTimeMicros);
+   * Timestamp versionTime = Timestamp.ofTimeMicroseconds(versionTimeMicros);
+   *
+   * Backup backupToCreate = dbAdminClient
+   *     .newBackupBuilder(backupId)
+   *     .setDatabase(databaseId)
+   *     .setExpireTime(expireTime)
+   *     .setVersionTime(versionTime)
+   *     .build();
+   *
+   * OperationFuture op = dbAdminClient.createBackup(backupToCreate);
+   * Backup createdBackup = op.get();
+   * }
+ * + * @param backup the backup to be created + */ + OperationFuture createBackup(Backup backup) throws SpannerException; + /** * Restore a database from a backup. The database that is restored will be created and may not * already exist. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java index 28de150caec..a5eed214a4e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java @@ -114,16 +114,31 @@ public Database apply(Exception e) { public OperationFuture createBackup( String instanceId, String backupId, String databaseId, Timestamp expireTime) throws SpannerException { - com.google.spanner.admin.database.v1.Backup backup = + final Backup backup = + newBackupBuilder(BackupId.of(projectId, instanceId, backupId)) + .setDatabase(DatabaseId.of(projectId, instanceId, databaseId)) + .setExpireTime(expireTime) + .build(); + return createBackup(backup); + } + + @Override + public OperationFuture createBackup(final Backup backup) { + final String instanceId = backup.getInstanceId().getInstance(); + final String databaseId = backup.getDatabase().getDatabase(); + final String backupId = backup.getId().getBackup(); + final com.google.spanner.admin.database.v1.Backup.Builder backupBuilder = com.google.spanner.admin.database.v1.Backup.newBuilder() .setDatabase(getDatabaseName(instanceId, databaseId)) - .setExpireTime(expireTime.toProto()) - .build(); - String instanceName = getInstanceName(instanceId); - OperationFuture - rawOperationFuture = rpc.createBackup(instanceName, backupId, backup); + .setExpireTime(backup.getExpireTime().toProto()); + if (backup.getVersionTime() != null) { + backupBuilder.setVersionTime(backup.getVersionTime().toProto()); + } + final String instanceName = getInstanceName(instanceId); + final OperationFuture + rawOperationFuture = rpc.createBackup(instanceName, backupId, backupBuilder.build()); - return new OperationFutureImpl( + return new OperationFutureImpl<>( rawOperationFuture.getPollingFuture(), rawOperationFuture.getInitialFuture(), new ApiFunction() { @@ -137,6 +152,7 @@ public Backup apply(OperationSnapshot snapshot) { com.google.spanner.admin.database.v1.Backup.newBuilder(proto) .setName(proto.getName()) .setExpireTime(proto.getExpireTime()) + .setVersionTime(proto.getVersionTime()) .setState(proto.getState()) .build(), DatabaseAdminClientImpl.this); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java index 3bdc6738382..80b99f679b4 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/BackupTest.java @@ -46,6 +46,7 @@ public class BackupTest { "projects/test-project/instances/test-instance/backups/backup-1"; private static final String DB = "projects/test-project/instances/test-instance/databases/db-1"; private static final Timestamp EXP_TIME = Timestamp.ofTimeSecondsAndNanos(1000L, 1000); + private static final Timestamp VERSION_TIME = Timestamp.ofTimeSecondsAndNanos(2000L, 2000); @Mock DatabaseAdminClient dbClient; @@ -65,11 +66,13 @@ public Builder answer(InvocationOnMock invocation) { @Test public void build() { Timestamp expireTime = Timestamp.now(); + Timestamp versionTime = Timestamp.ofTimeMicroseconds(10L); Backup backup = dbClient .newBackupBuilder(BackupId.of("test-project", "instance-id", "backup-id")) .setDatabase(DatabaseId.of("test-project", "instance-id", "src-database")) .setExpireTime(expireTime) + .setVersionTime(versionTime) .setSize(100L) .setState(State.CREATING) .build(); @@ -77,6 +80,7 @@ public void build() { assertThat(copy.getId()).isEqualTo(backup.getId()); assertThat(copy.getDatabase()).isEqualTo(backup.getDatabase()); assertThat(copy.getExpireTime()).isEqualTo(backup.getExpireTime()); + assertThat(copy.getVersionTime()).isEqualTo(backup.getVersionTime()); assertThat(copy.getSize()).isEqualTo(backup.getSize()); assertThat(copy.getState()).isEqualTo(backup.getState()); } @@ -84,14 +88,16 @@ public void build() { @Test public void create() { Timestamp expireTime = Timestamp.now(); + Timestamp versionTime = Timestamp.ofTimeMicroseconds(10L); Backup backup = dbClient .newBackupBuilder(BackupId.of("test-project", "instance-id", "backup-id")) .setDatabase(DatabaseId.of("test-project", "instance-id", "src-database")) .setExpireTime(expireTime) + .setVersionTime(versionTime) .build(); backup.create(); - verify(dbClient).createBackup("instance-id", "backup-id", "src-database", expireTime); + verify(dbClient).createBackup(backup); } @Test @@ -125,6 +131,19 @@ public void createWithoutExpireTime() { } } + @Test + public void createWithoutVersionTimeShouldSucceed() { + final Timestamp expireTime = Timestamp.now(); + Backup backup = + dbClient + .newBackupBuilder(BackupId.of("test-project", "instance-id", "backup-id")) + .setDatabase(DatabaseId.of("test-project", "instance-id", "src-database")) + .setExpireTime(expireTime) + .build(); + backup.create(); + verify(dbClient).createBackup(backup); + } + @Test public void exists() { when(dbClient.getBackup("test-instance", "test-backup")) @@ -298,6 +317,7 @@ public void fromProto() { assertThat(backup.getId().getName()).isEqualTo(NAME); assertThat(backup.getState()).isEqualTo(BackupInfo.State.CREATING); assertThat(backup.getExpireTime()).isEqualTo(EXP_TIME); + assertThat(backup.getVersionTime()).isEqualTo(VERSION_TIME); } private Backup createBackup() { @@ -307,6 +327,8 @@ private Backup createBackup() { .setDatabase(DB) .setExpireTime( com.google.protobuf.Timestamp.newBuilder().setSeconds(1000L).setNanos(1000).build()) + .setVersionTime( + com.google.protobuf.Timestamp.newBuilder().setSeconds(2000L).setNanos(2000).build()) .setState(com.google.spanner.admin.database.v1.Backup.State.CREATING) .build(); return Backup.fromProto(proto, dbClient); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java index ddaa3d4e4ee..fb617797aa0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientImplTest.java @@ -48,6 +48,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.junit.Assert; import org.junit.Before; @@ -301,7 +302,7 @@ public void testDatabaseIAMPermissions() { } @Test - public void createBackup() throws Exception { + public void createBackupWithParams() throws Exception { OperationFuture rawOperationFuture = OperationFutureUtil.immediateOperationFuture( "createBackup", getBackupProto(), CreateBackupMetadata.getDefaultInstance()); @@ -317,6 +318,40 @@ public void createBackup() throws Exception { assertThat(op.get().getId().getName()).isEqualTo(BK_NAME); } + @Test + public void createBackupWithBackupObject() throws ExecutionException, InterruptedException { + final OperationFuture rawOperationFuture = + OperationFutureUtil.immediateOperationFuture( + "createBackup", getBackupProto(), CreateBackupMetadata.getDefaultInstance()); + final Timestamp expireTime = + Timestamp.ofTimeMicroseconds( + TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()) + + TimeUnit.HOURS.toMicros(28)); + final Timestamp versionTime = + Timestamp.ofTimeMicroseconds( + TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()) - TimeUnit.DAYS.toMicros(2)); + final Backup expectedCallBackup = + Backup.newBuilder() + .setDatabase(DB_NAME) + .setExpireTime(expireTime.toProto()) + .setVersionTime(versionTime.toProto()) + .build(); + final com.google.cloud.spanner.Backup requestBackup = + client + .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BK_ID)) + .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + when(rpc.createBackup(INSTANCE_NAME, BK_ID, expectedCallBackup)).thenReturn(rawOperationFuture); + + final OperationFuture op = + client.createBackup(requestBackup); + assertThat(op.isDone()).isTrue(); + assertThat(op.get().getId().getName()).isEqualTo(BK_NAME); + } + @Test public void deleteBackup() { client.deleteBackup(INSTANCE_ID, BK_ID); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java index 8b30df912d3..500699e4a23 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java @@ -241,6 +241,7 @@ public void backupCreate() throws InterruptedException, ExecutionException { .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) .setExpireTime(after7Days()) + .setVersionTime(sevenDaysAgo()) .build(); OperationFuture op = backup.create(); backup = op.get(); @@ -251,6 +252,25 @@ public void backupCreate() throws InterruptedException, ExecutionException { assertThat(client.getBackup(INSTANCE_ID, backupId)).isEqualTo(backup); } + @Test + public void databaseAdminBackupCreate() throws ExecutionException, InterruptedException { + final String backupId = "other-backup-id"; + Backup backup = + client + .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) + .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) + .setExpireTime(after7Days()) + .setVersionTime(sevenDaysAgo()) + .build(); + final OperationFuture op = client.createBackup(backup); + backup = op.get(); + assertThat(backup.getId().getName()) + .isEqualTo( + String.format( + "projects/%s/instances/%s/backups/%s", PROJECT_ID, INSTANCE_ID, backupId)); + assertThat(client.getBackup(INSTANCE_ID, backupId)).isEqualTo(backup); + } + @Test public void backupCreateCancel() { final String backupId = "other-backup-id"; @@ -791,6 +811,12 @@ public void testDatabaseIAMPermissions() { assertThat(permissions).containsExactly("spanner.databases.select"); } + private Timestamp sevenDaysAgo() { + return Timestamp.ofTimeMicroseconds( + TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) + - TimeUnit.MICROSECONDS.convert(7, TimeUnit.DAYS)); + } + private Timestamp after7Days() { return Timestamp.ofTimeMicroseconds( TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) From e139666621a9bfc92a7f04c54a9e048a9a29d4d2 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 13 Jan 2021 11:54:34 +1100 Subject: [PATCH 12/21] test: adds integration tests for pitr backups --- .../spanner/testing/RemoteSpannerHelper.java | 10 ++ .../spanner/testing/TimestampHelper.java | 41 +++++ .../spanner/DatabaseAdminClientTest.java | 53 +++--- .../google/cloud/spanner/it/ITBackupTest.java | 51 ++---- .../spanner/it/ITPitrBackupAndRestore.java | 169 ++++++++++++++++++ 5 files changed, 253 insertions(+), 71 deletions(-) create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/TimestampHelper.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java index ef979a87060..6d2fe90227c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/RemoteSpannerHelper.java @@ -30,6 +30,7 @@ import java.util.Arrays; import java.util.List; import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,6 +46,8 @@ public class RemoteSpannerHelper { private final InstanceId instanceId; private static int dbSeq; private static int dbPrefix = new Random().nextInt(Integer.MAX_VALUE); + private static final AtomicInteger backupSeq = new AtomicInteger(); + private static int backupPrefix = new Random().nextInt(Integer.MAX_VALUE); private final List dbs = new ArrayList<>(); protected RemoteSpannerHelper(SpannerOptions options, InstanceId instanceId, Spanner client) { @@ -100,6 +103,13 @@ public String getUniqueDatabaseId() { return String.format("testdb_%d_%04d", dbPrefix, dbSeq++); } + /** + * Returns a backup id which is guaranteed to be unique within the context of this environment. + */ + public String getUniqueBackupId() { + return String.format("testbck_%06d_%04d", backupPrefix, backupSeq.incrementAndGet()); + } + /** * Creates a test database defined by {@code statements} in the test instance. A {@code CREATE * DATABASE ...} statement should not be included; an appropriate name will be chosen and the diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/TimestampHelper.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/TimestampHelper.java new file mode 100644 index 00000000000..021adf4dd7f --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/testing/TimestampHelper.java @@ -0,0 +1,41 @@ +/* + * 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.google.cloud.spanner.testing; + +import com.google.cloud.Timestamp; +import java.util.concurrent.TimeUnit; + +public class TimestampHelper { + + public static Timestamp daysAgo(int days) { + return Timestamp.ofTimeMicroseconds( + TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) + - TimeUnit.MICROSECONDS.convert(days, TimeUnit.DAYS)); + } + + public static Timestamp afterDays(int days) { + return Timestamp.ofTimeMicroseconds( + TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) + + TimeUnit.MICROSECONDS.convert(days, TimeUnit.DAYS)); + } + + public static Timestamp afterMinutes(int minutes) { + return Timestamp.ofTimeMicroseconds( + TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) + + TimeUnit.MICROSECONDS.convert(minutes, TimeUnit.MINUTES)); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java index 500699e4a23..95641ef3fff 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseAdminClientTest.java @@ -16,6 +16,7 @@ package com.google.cloud.spanner; +import static com.google.cloud.spanner.testing.TimestampHelper.afterDays; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; @@ -224,7 +225,7 @@ public void tearDown() { public void dbAdminCreateBackup() throws InterruptedException, ExecutionException { final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, backupId, DB_ID, afterDays(7)); Backup backup = op.get(); assertThat(backup.getId().getName()) .isEqualTo( @@ -240,7 +241,7 @@ public void backupCreate() throws InterruptedException, ExecutionException { client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .setVersionTime(sevenDaysAgo()) .build(); OperationFuture op = backup.create(); @@ -259,7 +260,7 @@ public void databaseAdminBackupCreate() throws ExecutionException, InterruptedEx client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .setVersionTime(sevenDaysAgo()) .build(); final OperationFuture op = client.createBackup(backup); @@ -319,7 +320,7 @@ public void databaseBackup() throws InterruptedException, ExecutionException { db.backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()) .get(); assertThat(backup.getId().getName()) @@ -332,7 +333,7 @@ public void databaseBackup() throws InterruptedException, ExecutionException { @Test public void dbAdminCreateBackupAlreadyExists() throws InterruptedException { OperationFuture op = - client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, afterDays(7)); try { op.get(); fail("missing expected exception"); @@ -349,7 +350,7 @@ public void backupCreateAlreadyExists() throws InterruptedException { client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BCK_ID)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, DB_ID)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build(); try { backup.create().get(); @@ -368,7 +369,7 @@ public void databaseBackupAlreadyExists() throws InterruptedException { db.backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BCK_ID)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()); try { op.get(); @@ -384,7 +385,7 @@ public void databaseBackupAlreadyExists() throws InterruptedException { public void dbAdminCreateBackupDbNotFound() throws InterruptedException { final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, "does-not-exist", after7Days()); + client.createBackup(INSTANCE_ID, backupId, "does-not-exist", afterDays(7)); try { op.get(); fail("missing expected exception"); @@ -401,7 +402,7 @@ public void backupCreateDbNotFound() throws InterruptedException { client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) .setDatabase(DatabaseId.of(PROJECT_ID, INSTANCE_ID, "does-not-exist")) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build(); try { backup.create().get(); @@ -422,7 +423,7 @@ public void databaseBackupDbNotFound() throws InterruptedException { db.backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, backupId)) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()); try { op.get(); @@ -519,7 +520,7 @@ public void dbClientListBackups() throws SpannerException, InterruptedException, ExecutionException { Backup backup = client.getBackup(INSTANCE_ID, BCK_ID); assertThat(client.listBackups(INSTANCE_ID).iterateAll()).containsExactly(backup); - Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, after7Days()).get(); + Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, afterDays(7)).get(); assertThat(client.listBackups(INSTANCE_ID).iterateAll()).containsExactly(backup, backup2); backup2.delete(); assertThat(client.listBackups(INSTANCE_ID).iterateAll()).containsExactly(backup); @@ -535,7 +536,7 @@ public void instanceListBackups() .build(); Backup backup = client.getBackup(INSTANCE_ID, BCK_ID); assertThat(instance.listBackups().iterateAll()).containsExactly(backup); - Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, after7Days()).get(); + Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, afterDays(7)).get(); assertThat(instance.listBackups().iterateAll()).containsExactly(backup, backup2); backup2.delete(); assertThat(instance.listBackups().iterateAll()).containsExactly(backup); @@ -552,7 +553,7 @@ public void instanceListBackupsWithFilter() Backup backup = client.getBackup(INSTANCE_ID, BCK_ID); assertThat(instance.listBackups().iterateAll()).containsExactly(backup); - Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, after7Days()).get(); + Backup backup2 = client.createBackup(INSTANCE_ID, "backup2", DB_ID, afterDays(7)).get(); // All backups. assertThat(instance.listBackups().iterateAll()).containsExactly(backup, backup2); @@ -569,7 +570,7 @@ public void instanceListBackupsWithFilter() .containsExactly(backup, backup2); // All backups that expire before a certain time. - String ts = after14Days().toString(); + String ts = afterDays(14).toString(); filter = String.format("expire_time < \"%s\"", ts); mockDatabaseAdmin.addFilterMatches(filter, backup.getId().getName(), backup2.getId().getName()); assertThat(instance.listBackups(Options.filter(filter)).iterateAll()) @@ -721,7 +722,7 @@ public void databaseListDatabaseOperations() public void dbClientListBackupOperations() throws SpannerException, InterruptedException, ExecutionException { assertThat(client.listBackupOperations(INSTANCE_ID).iterateAll()).hasSize(1); - client.createBackup(INSTANCE_ID, "other-backup", DB_ID, after7Days()).get(); + client.createBackup(INSTANCE_ID, "other-backup", DB_ID, afterDays(7)).get(); assertThat(client.listBackupOperations(INSTANCE_ID).iterateAll()).hasSize(2); // Restore a backup. This creates 2 DATABASE operations: One to restore the database and // one to optimize it. @@ -743,7 +744,7 @@ public void instanceListBackupOperations() .backup( client .newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, "other-backup")) - .setExpireTime(after7Days()) + .setExpireTime(afterDays(7)) .build()) .get(); assertThat(instance.listBackupOperations().iterateAll()).hasSize(2); @@ -788,7 +789,7 @@ public void backupListBackupOperations() Backup backup = client.newBackupBuilder(BackupId.of(PROJECT_ID, INSTANCE_ID, BCK_ID)).build(); mockDatabaseAdmin.addFilterMatches("name:backups/" + BCK_ID, createBackupOperation.getName()); assertThat(backup.listBackupOperations().iterateAll()).hasSize(1); - client.createBackup(INSTANCE_ID, "other-backup", DB_ID, after7Days()).get(); + client.createBackup(INSTANCE_ID, "other-backup", DB_ID, afterDays(7)).get(); assertThat(backup.listBackupOperations().iterateAll()).hasSize(1); } @@ -817,18 +818,6 @@ private Timestamp sevenDaysAgo() { - TimeUnit.MICROSECONDS.convert(7, TimeUnit.DAYS)); } - private Timestamp after7Days() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(7, TimeUnit.DAYS)); - } - - private Timestamp after14Days() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(14, TimeUnit.DAYS)); - } - private void createTestDatabase() { try { createDatabaseOperation = client.createDatabase(INSTANCE_ID, DB_ID, INITIAL_STATEMENTS); @@ -840,7 +829,7 @@ private void createTestDatabase() { private void createTestBackup() { try { - createBackupOperation = client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, after7Days()); + createBackupOperation = client.createBackup(INSTANCE_ID, BCK_ID, DB_ID, afterDays(7)); createBackupOperation.get(); } catch (InterruptedException | ExecutionException e) { throw SpannerExceptionFactory.newSpannerException(e); @@ -865,7 +854,7 @@ public void retryCreateBackupSlowResponse() throws Exception { SimulatedExecutionTime.ofException(Status.DEADLINE_EXCEEDED.asRuntimeException())); final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, backupId, DB_ID, afterDays(7)); Backup backup = op.get(); assertThat(backup.getId().getName()) .isEqualTo( @@ -883,7 +872,7 @@ public void retryCreateBackupSlowStartup() throws Exception { SimulatedExecutionTime.ofException(Status.DEADLINE_EXCEEDED.asRuntimeException())); final String backupId = "other-backup-id"; OperationFuture op = - client.createBackup(INSTANCE_ID, backupId, DB_ID, after7Days()); + client.createBackup(INSTANCE_ID, backupId, DB_ID, afterDays(7)); Backup backup = op.get(); assertThat(backup.getId().getName()) .isEqualTo( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java index 0fbd0702ec5..e9b98871847 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITBackupTest.java @@ -17,6 +17,9 @@ package com.google.cloud.spanner.it; import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; +import static com.google.cloud.spanner.testing.TimestampHelper.afterDays; +import static com.google.cloud.spanner.testing.TimestampHelper.afterMinutes; +import static com.google.cloud.spanner.testing.TimestampHelper.daysAgo; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -58,7 +61,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -87,7 +89,6 @@ public class ITBackupTest { private InstanceAdminClient instanceAdminClient; private Instance instance; private RemoteSpannerHelper testHelper; - private final AtomicInteger backupSeq = new AtomicInteger(); private List databases = new ArrayList<>(); private List backups = new ArrayList<>(); private final Random random = new Random(); @@ -188,34 +189,6 @@ private void waitForDbOperations(String backupId) throws InterruptedException { } } - private String getUniqueBackupId() { - return String.format("testbck_%06d_%04d", random.nextInt(1000000), backupSeq.incrementAndGet()); - } - - private static Timestamp after7Days() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(7L, TimeUnit.DAYS)); - } - - private Timestamp after5Minutes() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(5L, TimeUnit.MINUTES)); - } - - private Timestamp tomorrow() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - + TimeUnit.MICROSECONDS.convert(1L, TimeUnit.DAYS)); - } - - private Timestamp yesterday() { - return Timestamp.ofTimeMicroseconds( - TimeUnit.MICROSECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS) - - TimeUnit.MICROSECONDS.convert(1L, TimeUnit.DAYS)); - } - @Test public void testBackups() throws InterruptedException, ExecutionException { // Create two test databases in parallel. @@ -250,9 +223,9 @@ public void testBackups() throws InterruptedException, ExecutionException { .build())); // Create two backups in parallel. - String backupId1 = getUniqueBackupId() + "_bck1"; - String backupId2 = getUniqueBackupId() + "_bck2"; - Timestamp expireTime = after7Days(); + String backupId1 = testHelper.getUniqueBackupId() + "_bck1"; + String backupId2 = testHelper.getUniqueBackupId() + "_bck2"; + Timestamp expireTime = afterDays(7); logger.info(String.format("Creating backups %s and %s in parallel", backupId1, backupId2)); OperationFuture op1 = dbAdminClient.createBackup( @@ -414,8 +387,8 @@ private void testMetadata( private void testCreateInvalidExpirationDate(Database db) throws InterruptedException { // This is not allowed, the expiration date must be at least 6 hours in the future. - Timestamp expireTime = yesterday(); - String backupId = getUniqueBackupId(); + Timestamp expireTime = daysAgo(1); + String backupId = testHelper.getUniqueBackupId(); logger.info(String.format("Creating backup %s with invalid expiration date", backupId)); OperationFuture op = dbAdminClient.createBackup( @@ -437,8 +410,8 @@ private void testCreateInvalidExpirationDate(Database db) throws InterruptedExce private void testCancelBackupOperation(Database db) throws InterruptedException, ExecutionException { - Timestamp expireTime = after7Days(); - String backupId = getUniqueBackupId(); + Timestamp expireTime = afterDays(7); + String backupId = testHelper.getUniqueBackupId(); logger.info(String.format("Starting to create backup %s", backupId)); OperationFuture op = dbAdminClient.createBackup( @@ -477,7 +450,7 @@ private void testGetBackup(Database db, String backupId, Timestamp expireTime) { private void testUpdateBackup(Backup backup) { // Update the expire time. - Timestamp tomorrow = tomorrow(); + Timestamp tomorrow = afterDays(1); backup = backup.toBuilder().setExpireTime(tomorrow).build(); logger.info( String.format("Updating expire time of backup %s to 1 week", backup.getId().getBackup())); @@ -488,7 +461,7 @@ private void testUpdateBackup(Backup backup) { assertThat(backup.getExpireTime()).isEqualTo(tomorrow); // Try to set the expire time to 5 minutes in the future. - Timestamp in5Minutes = after5Minutes(); + Timestamp in5Minutes = afterMinutes(5); backup = backup.toBuilder().setExpireTime(in5Minutes).build(); try { logger.info( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java new file mode 100644 index 00000000000..4ac4a9fa424 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -0,0 +1,169 @@ +/* + * 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.google.cloud.spanner.it; + +import static com.google.cloud.spanner.testing.EmulatorSpannerHelper.isUsingEmulator; +import static com.google.cloud.spanner.testing.TimestampHelper.afterDays; +import static com.google.cloud.spanner.testing.TimestampHelper.daysAgo; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeFalse; + +import com.google.api.gax.longrunning.OperationFuture; +import com.google.cloud.Timestamp; +import com.google.cloud.spanner.Backup; +import com.google.cloud.spanner.BackupId; +import com.google.cloud.spanner.Database; +import com.google.cloud.spanner.DatabaseAdminClient; +import com.google.cloud.spanner.DatabaseId; +import com.google.cloud.spanner.InstanceId; +import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.ParallelIntegrationTest; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@Category(ParallelIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITPitrBackupAndRestore { + private static final Logger logger = Logger.getLogger(ITPitrBackupAndRestore.class.getName()); + + @ClassRule public static IntegrationTestEnv env = new IntegrationTestEnv(); + private static final long OP_TIMEOUT = 10; + private static final TimeUnit OP_TIMEOUT_UNIT = TimeUnit.MINUTES; + private static RemoteSpannerHelper testHelper; + private static DatabaseAdminClient dbAdminClient; + private static Database testDatabase; + private static List backupsToDrop; + + @BeforeClass + public static void doNotRunOnEmulator() { + assumeFalse("PITR features are not supported by the emulator", isUsingEmulator()); + } + + @BeforeClass + public static void setUp() throws Exception { + testHelper = env.getTestHelper(); + dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); + testDatabase = createTestDatabase(); + backupsToDrop = new ArrayList<>(); + } + + @AfterClass + public static void tearDown() { + int droppedBackups = 0; + for (Backup backup : backupsToDrop) { + try { + backup.delete(); + droppedBackups++; + } catch (SpannerException e) { + logger.log(Level.SEVERE, "Failed to drop test backup " + backup.getId(), e); + } + } + logger.log(Level.INFO, "Dropped {0} test backup(s)", droppedBackups); + } + + @Test + public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() throws Exception { + final DatabaseId databaseId = testDatabase.getId(); + final InstanceId instanceId = databaseId.getInstanceId(); + final String backupId = testHelper.getUniqueBackupId(); + final Timestamp expireTime = afterDays(7); + final Timestamp versionTime = daysAgo(3); + final Backup backupToCreate = + dbAdminClient + .newBackupBuilder(BackupId.of(instanceId, backupId)) + .setDatabase(databaseId) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + final Backup createdBackup = createBackup(backupToCreate); + + assertThat(createdBackup.getVersionTime()).isEqualTo(backupToCreate.getVersionTime()); + } + + @Test(expected = SpannerException.class) + public void backupCreationWithVersionTimeTooFarInThePastFails() throws Exception { + final DatabaseId databaseId = testDatabase.getId(); + final InstanceId instanceId = databaseId.getInstanceId(); + final String backupId = testHelper.getUniqueBackupId(); + final Timestamp expireTime = afterDays(7); + final Timestamp versionTime = daysAgo(30); + final Backup backupToCreate = + dbAdminClient + .newBackupBuilder(BackupId.of(instanceId, backupId)) + .setDatabase(databaseId) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + createBackup(backupToCreate); + } + + @Test(expected = SpannerException.class) + public void backupCreationWithVersionTimeInTheFutureFails() throws Exception { + final DatabaseId databaseId = testDatabase.getId(); + final InstanceId instanceId = databaseId.getInstanceId(); + final String backupId = testHelper.getUniqueBackupId(); + final Timestamp expireTime = afterDays(7); + final Timestamp versionTime = afterDays(1); + final Backup backupToCreate = + dbAdminClient + .newBackupBuilder(BackupId.of(instanceId, backupId)) + .setDatabase(databaseId) + .setExpireTime(expireTime) + .setVersionTime(versionTime) + .build(); + + createBackup(backupToCreate); + } + + private Backup createBackup(Backup backup) + throws InterruptedException, ExecutionException, TimeoutException { + backupsToDrop.add(backup); + return dbAdminClient.createBackup(backup).get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + } + + private static Database createTestDatabase() + throws InterruptedException, ExecutionException, TimeoutException { + final Database database = testHelper.createTestDatabase(); + final String instanceId = database.getId().getInstanceId().getInstance(); + final String databaseId = database.getId().getDatabase(); + final String statement = + "ALTER DATABASE " + databaseId + " SET OPTIONS (version_retention_period = '7d')"; + final OperationFuture op = + dbAdminClient.updateDatabaseDdl( + instanceId, databaseId, Collections.singletonList(statement), "op_" + databaseId); + op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + return database; + } +} From b489b8c4d20359de67a03a026efed3b7d2f7c85c Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 13 Jan 2021 12:16:06 +1100 Subject: [PATCH 13/21] test: adds tests for pitr restore --- .../spanner/it/ITPitrBackupAndRestore.java | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java index 4ac4a9fa424..93b8dcc7d81 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -23,6 +23,7 @@ import static org.junit.Assume.assumeFalse; 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.BackupId; @@ -31,9 +32,11 @@ import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.InstanceId; import com.google.cloud.spanner.IntegrationTestEnv; +import com.google.cloud.spanner.Options; import com.google.cloud.spanner.ParallelIntegrationTest; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.testing.RemoteSpannerHelper; +import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata; import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata; import java.util.ArrayList; import java.util.Collections; @@ -63,6 +66,7 @@ public class ITPitrBackupAndRestore { private static DatabaseAdminClient dbAdminClient; private static Database testDatabase; private static List backupsToDrop; + private static List databasesToDrop; @BeforeClass public static void doNotRunOnEmulator() { @@ -75,40 +79,63 @@ public static void setUp() throws Exception { dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); testDatabase = createTestDatabase(); backupsToDrop = new ArrayList<>(); + databasesToDrop = new ArrayList<>(); } @AfterClass public static void tearDown() { - int droppedBackups = 0; + int numDropped = 0; for (Backup backup : backupsToDrop) { try { backup.delete(); - droppedBackups++; + numDropped++; } catch (SpannerException e) { logger.log(Level.SEVERE, "Failed to drop test backup " + backup.getId(), e); } } - logger.log(Level.INFO, "Dropped {0} test backup(s)", droppedBackups); + logger.log(Level.INFO, "Dropped {0} test backup(s)", numDropped); + + numDropped = 0; + for (Database database : databasesToDrop) { + try { + database.drop(); + numDropped++; + } catch (SpannerException e) { + logger.log(Level.SEVERE, "Failed to drop test database " + database.getId(), e); + } + } + logger.log(Level.INFO, "Dropped {0} test databases(s)", numDropped); } @Test public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() throws Exception { - final DatabaseId databaseId = testDatabase.getId(); - final InstanceId instanceId = databaseId.getInstanceId(); + final DatabaseId backupDatabaseId = testDatabase.getId(); + final String restoreDatabaseId = testHelper.getUniqueDatabaseId(); + final String projectId = backupDatabaseId.getInstanceId().getProject(); + final String instanceId = backupDatabaseId.getInstanceId().getInstance(); final String backupId = testHelper.getUniqueBackupId(); final Timestamp expireTime = afterDays(7); final Timestamp versionTime = daysAgo(3); final Backup backupToCreate = dbAdminClient - .newBackupBuilder(BackupId.of(instanceId, backupId)) - .setDatabase(databaseId) + .newBackupBuilder(BackupId.of(projectId, instanceId, backupId)) + .setDatabase(backupDatabaseId) .setExpireTime(expireTime) .setVersionTime(versionTime) .build(); final Backup createdBackup = createBackup(backupToCreate); - - assertThat(createdBackup.getVersionTime()).isEqualTo(backupToCreate.getVersionTime()); + final RestoreDatabaseMetadata restoreDatabaseMetadata = + restoreDatabase(instanceId, backupId, restoreDatabaseId); + final Database retrievedDatabase = dbAdminClient.getDatabase(instanceId, restoreDatabaseId); + final Database listedDatabase = listDatabase(instanceId, restoreDatabaseId); + + assertThat(createdBackup.getVersionTime()).isEqualTo(versionTime); + assertThat(restoreDatabaseMetadata.getBackupInfo().getVersionTime()).isEqualTo(versionTime); + assertThat(retrievedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime()) + .isEqualTo(versionTime); + assertThat(listedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime()) + .isEqualTo(versionTime); } @Test(expected = SpannerException.class) @@ -147,10 +174,28 @@ public void backupCreationWithVersionTimeInTheFutureFails() throws Exception { createBackup(backupToCreate); } - private Backup createBackup(Backup backup) + private Backup createBackup(Backup backupToCreate) throws InterruptedException, ExecutionException, TimeoutException { - backupsToDrop.add(backup); - return dbAdminClient.createBackup(backup).get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + final Backup createdBackup = + dbAdminClient.createBackup(backupToCreate).get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + backupsToDrop.add(createdBackup); + return createdBackup; + } + + private RestoreDatabaseMetadata restoreDatabase( + String instanceId, String backupId, String databaseId) + throws InterruptedException, ExecutionException, TimeoutException { + final OperationFuture op = + dbAdminClient.restoreDatabase(instanceId, backupId, instanceId, databaseId); + final Database database = op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + databasesToDrop.add(database); + return op.getMetadata().get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + } + + private Database listDatabase(String instanceId, String databaseId) { + final Page page = + dbAdminClient.listDatabases(instanceId, Options.filter("name:" + databaseId)); + return page.getValues().iterator().next(); } private static Database createTestDatabase() From bc7fb5079cc14b0fed86a20e8f99cff619cd85ba Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 13 Jan 2021 14:10:37 +1100 Subject: [PATCH 14/21] test: fixes integration test for pitr restore --- .../spanner/it/ITPitrBackupAndRestore.java | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java index 93b8dcc7d81..302258bc81d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -32,12 +32,11 @@ import com.google.cloud.spanner.DatabaseId; import com.google.cloud.spanner.InstanceId; import com.google.cloud.spanner.IntegrationTestEnv; -import com.google.cloud.spanner.Options; import com.google.cloud.spanner.ParallelIntegrationTest; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.testing.RemoteSpannerHelper; +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 java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -75,11 +74,11 @@ public static void doNotRunOnEmulator() { @BeforeClass public static void setUp() throws Exception { + backupsToDrop = new ArrayList<>(); + databasesToDrop = new ArrayList<>(); testHelper = env.getTestHelper(); dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); testDatabase = createTestDatabase(); - backupsToDrop = new ArrayList<>(); - databasesToDrop = new ArrayList<>(); } @AfterClass @@ -115,7 +114,7 @@ public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() final String instanceId = backupDatabaseId.getInstanceId().getInstance(); final String backupId = testHelper.getUniqueBackupId(); final Timestamp expireTime = afterDays(7); - final Timestamp versionTime = daysAgo(3); + final Timestamp versionTime = testDatabase.getEarliestVersionTime(); final Backup backupToCreate = dbAdminClient .newBackupBuilder(BackupId.of(projectId, instanceId, backupId)) @@ -125,15 +124,19 @@ public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() .build(); final Backup createdBackup = createBackup(backupToCreate); + assertThat(createdBackup.getVersionTime()).isEqualTo(versionTime); + final RestoreDatabaseMetadata restoreDatabaseMetadata = restoreDatabase(instanceId, backupId, restoreDatabaseId); - final Database retrievedDatabase = dbAdminClient.getDatabase(instanceId, restoreDatabaseId); - final Database listedDatabase = listDatabase(instanceId, restoreDatabaseId); - - assertThat(createdBackup.getVersionTime()).isEqualTo(versionTime); assertThat(restoreDatabaseMetadata.getBackupInfo().getVersionTime()).isEqualTo(versionTime); + + final Database retrievedDatabase = dbAdminClient.getDatabase(instanceId, restoreDatabaseId); + assertThat(retrievedDatabase).isNotNull(); assertThat(retrievedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime()) .isEqualTo(versionTime); + + final Database listedDatabase = listDatabase(instanceId, restoreDatabaseId); + assertThat(listedDatabase).isNotNull(); assertThat(listedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime()) .isEqualTo(versionTime); } @@ -193,22 +196,30 @@ private RestoreDatabaseMetadata restoreDatabase( } private Database listDatabase(String instanceId, String databaseId) { - final Page page = - dbAdminClient.listDatabases(instanceId, Options.filter("name:" + databaseId)); - return page.getValues().iterator().next(); + Page page = dbAdminClient.listDatabases(instanceId); + while (page != null) { + for (Database database : page.getValues()) { + if (database.getId().getDatabase().equals(databaseId)) { + return database; + } + } + page = page.getNextPage(); + } + return null; } private static Database createTestDatabase() throws InterruptedException, ExecutionException, TimeoutException { - final Database database = testHelper.createTestDatabase(); - final String instanceId = database.getId().getInstanceId().getInstance(); - final String databaseId = database.getId().getDatabase(); - final String statement = - "ALTER DATABASE " + databaseId + " SET OPTIONS (version_retention_period = '7d')"; - final OperationFuture op = - dbAdminClient.updateDatabaseDdl( - instanceId, databaseId, Collections.singletonList(statement), "op_" + databaseId); - op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + final String instanceId = testHelper.getInstanceId().getInstance(); + final String databaseId = testHelper.getUniqueDatabaseId(); + final OperationFuture op = + dbAdminClient.createDatabase( + instanceId, + databaseId, + Collections.singletonList( + "ALTER DATABASE " + databaseId + " SET OPTIONS (version_retention_period = '7d')")); + final Database database = op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + databasesToDrop.add(database); return database; } } From 8c34fc32fff9ddba94c1cb3ffce501b513065aaa Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 13 Jan 2021 16:16:32 +1100 Subject: [PATCH 15/21] test: fixes backup unit test --- .../test/java/com/google/cloud/spanner/DatabaseTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java index a65f2b4a02c..7c61720b219 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseTest.java @@ -64,12 +64,13 @@ public Backup.Builder answer(InvocationOnMock invocation) { public void backup() { Timestamp expireTime = Timestamp.now(); Database db = createDatabase(); - db.backup( + Backup backup = dbClient .newBackupBuilder(BackupId.of("test-project", "test-instance", "test-backup")) .setExpireTime(expireTime) - .build()); - verify(dbClient).createBackup("test-instance", "test-backup", "database-1", expireTime); + .build(); + db.backup(backup); + verify(dbClient).createBackup(backup.toBuilder().setDatabase(db.getId()).build()); } @Test From 9faa574aca6819815b239cfc62a747d0c5115829 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 13 Jan 2021 16:44:25 +1100 Subject: [PATCH 16/21] test: fixes npe on pitr backup test --- .../com/google/cloud/spanner/it/ITPitrBackupAndRestore.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java index 302258bc81d..846dcc53cb5 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -64,8 +64,8 @@ public class ITPitrBackupAndRestore { private static RemoteSpannerHelper testHelper; private static DatabaseAdminClient dbAdminClient; private static Database testDatabase; - private static List backupsToDrop; - private static List databasesToDrop; + private static final List backupsToDrop = new ArrayList<>(); + private static final List databasesToDrop = new ArrayList<>(); @BeforeClass public static void doNotRunOnEmulator() { @@ -74,8 +74,6 @@ public static void doNotRunOnEmulator() { @BeforeClass public static void setUp() throws Exception { - backupsToDrop = new ArrayList<>(); - databasesToDrop = new ArrayList<>(); testHelper = env.getTestHelper(); dbAdminClient = testHelper.getClient().getDatabaseAdminClient(); testDatabase = createTestDatabase(); From 5fbb0b2b31812540dfa1360abb68710b71dabb2c Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 13 Jan 2021 16:51:27 +1100 Subject: [PATCH 17/21] chore: fixes clirr errors --- google-cloud-spanner/clirr-ignored-differences.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index 8dd5d9dad6d..744c27d45aa 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -453,4 +453,16 @@ com/google/cloud/spanner/TransactionContext com.google.api.core.ApiFuture executeUpdateAsync(com.google.cloud.spanner.Statement) + + + + 7013 + com/google/cloud/spanner/BackupInfo$Builder + com.google.cloud.spanner.BackupInfo$Builder setVersionTime(com.google.cloud.Timestamp) + + + 7012 + com/google/cloud/spanner/DatabaseAdminClient + com.google.api.gax.longrunning.OperationFuture createBackup(com.google.cloud.spanner.Backup) + From aa2f11ec75c2dcbc7ec00c5dc2f274dfd8f54197 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Fri, 15 Jan 2021 12:37:35 +1100 Subject: [PATCH 18/21] chore: refactors / addresses pr comments --- .../java/com/google/cloud/spanner/BackupInfo.java | 2 +- .../main/java/com/google/cloud/spanner/Database.java | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java index 0435d32e805..199e6ae2ae4 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java @@ -100,7 +100,7 @@ public Builder setExpireTime(Timestamp expireTime) { @Override public Builder setVersionTime(Timestamp versionTime) { - this.versionTime = Preconditions.checkNotNull(versionTime); + this.versionTime = versionTime; return this; } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java index 92b55100613..a442ad2399c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java @@ -119,16 +119,13 @@ public OperationFuture backup(Backup backup) { backup.getInstanceId().equals(getId().getInstanceId()), "The instance of the backup must be equal to the instance of this database."); - final BackupInfo.Builder backupBuilder = + return dbClient.createBackup( dbClient .newBackupBuilder(backup.getId()) .setDatabase(getId()) - .setExpireTime(backup.getExpireTime()); - if (backup.getVersionTime() != null) { - backupBuilder.setVersionTime(backup.getVersionTime()); - } - - return dbClient.createBackup(backupBuilder.build()); + .setExpireTime(backup.getExpireTime()) + .setVersionTime(backup.getVersionTime()) + .build()); } /** From 66f4b296ce7ff17f4de0b1fc64016b451323da51 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Thu, 4 Feb 2021 15:34:05 +1100 Subject: [PATCH 19/21] test: fixes the it pitr sad cases tests --- .../spanner/it/ITPitrBackupAndRestore.java | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java index 846dcc53cb5..139091c454e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -177,8 +177,7 @@ public void backupCreationWithVersionTimeInTheFutureFails() throws Exception { private Backup createBackup(Backup backupToCreate) throws InterruptedException, ExecutionException, TimeoutException { - final Backup createdBackup = - dbAdminClient.createBackup(backupToCreate).get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + final Backup createdBackup = getOrThrow(dbAdminClient.createBackup(backupToCreate)); backupsToDrop.add(createdBackup); return createdBackup; } @@ -188,7 +187,7 @@ private RestoreDatabaseMetadata restoreDatabase( throws InterruptedException, ExecutionException, TimeoutException { final OperationFuture op = dbAdminClient.restoreDatabase(instanceId, backupId, instanceId, databaseId); - final Database database = op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + final Database database = getOrThrow(op); databasesToDrop.add(database); return op.getMetadata().get(OP_TIMEOUT, OP_TIMEOUT_UNIT); } @@ -216,8 +215,21 @@ private static Database createTestDatabase() databaseId, Collections.singletonList( "ALTER DATABASE " + databaseId + " SET OPTIONS (version_retention_period = '7d')")); - final Database database = op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + final Database database = getOrThrow(op); databasesToDrop.add(database); return database; } + + private static T getOrThrow(OperationFuture op) + throws TimeoutException, InterruptedException, ExecutionException { + try { + return op.get(OP_TIMEOUT, OP_TIMEOUT_UNIT); + } catch (ExecutionException e) { + if (e.getCause() != null && e.getCause() instanceof SpannerException) { + throw (SpannerException) e.getCause(); + } else { + throw e; + } + } + } } From d4d795ec0fc17ef788cdfb77b66cf9476ecd567b Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Thu, 4 Feb 2021 17:32:11 +1100 Subject: [PATCH 20/21] test: fixes pitr backup and restore tests --- .../spanner/it/ITPitrBackupAndRestore.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java index 139091c454e..4624e04219a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -82,26 +82,26 @@ public static void setUp() throws Exception { @AfterClass public static void tearDown() { int numDropped = 0; - for (Backup backup : backupsToDrop) { + for (Database database : databasesToDrop) { try { - backup.delete(); + database.drop(); numDropped++; } catch (SpannerException e) { - logger.log(Level.SEVERE, "Failed to drop test backup " + backup.getId(), e); + logger.log(Level.SEVERE, "Failed to drop test database " + database.getId(), e); } } - logger.log(Level.INFO, "Dropped {0} test backup(s)", numDropped); + logger.log(Level.INFO, "Dropped {0} test databases(s)", numDropped); numDropped = 0; - for (Database database : databasesToDrop) { + for (Backup backup : backupsToDrop) { try { - database.drop(); + backup.delete(); numDropped++; } catch (SpannerException e) { - logger.log(Level.SEVERE, "Failed to drop test database " + database.getId(), e); + logger.log(Level.SEVERE, "Failed to drop test backup " + backup.getId(), e); } } - logger.log(Level.INFO, "Dropped {0} test databases(s)", numDropped); + logger.log(Level.INFO, "Dropped {0} test backup(s)", numDropped); } @Test @@ -126,16 +126,21 @@ public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() final RestoreDatabaseMetadata restoreDatabaseMetadata = restoreDatabase(instanceId, backupId, restoreDatabaseId); - assertThat(restoreDatabaseMetadata.getBackupInfo().getVersionTime()).isEqualTo(versionTime); + assertThat(Timestamp.fromProto(restoreDatabaseMetadata.getBackupInfo().getVersionTime())) + .isEqualTo(versionTime); final Database retrievedDatabase = dbAdminClient.getDatabase(instanceId, restoreDatabaseId); assertThat(retrievedDatabase).isNotNull(); - assertThat(retrievedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime()) + assertThat( + Timestamp.fromProto( + retrievedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime())) .isEqualTo(versionTime); final Database listedDatabase = listDatabase(instanceId, restoreDatabaseId); assertThat(listedDatabase).isNotNull(); - assertThat(listedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime()) + assertThat( + Timestamp.fromProto( + listedDatabase.getRestoreInfo().getProto().getBackupInfo().getVersionTime())) .isEqualTo(versionTime); } From da00ad6ecf27246bbf0bf225edcfa770c72dc4a1 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Wed, 17 Feb 2021 11:51:13 +1100 Subject: [PATCH 21/21] test: skips pitr backup and restore tests This is because the backend for these features is not ready yet. --- .../com/google/cloud/spanner/it/ITPitrBackupAndRestore.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java index 4624e04219a..bfdaa60a258 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITPitrBackupAndRestore.java @@ -48,6 +48,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -105,6 +106,7 @@ public static void tearDown() { } @Test + @Ignore("backup and restore for pitr is not released yet") public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() throws Exception { final DatabaseId backupDatabaseId = testDatabase.getId(); final String restoreDatabaseId = testHelper.getUniqueDatabaseId(); @@ -145,6 +147,7 @@ public void backupCreationWithVersionTimeWithinVersionRetentionPeriodSucceeds() } @Test(expected = SpannerException.class) + @Ignore("backup and restore for pitr is not released yet") public void backupCreationWithVersionTimeTooFarInThePastFails() throws Exception { final DatabaseId databaseId = testDatabase.getId(); final InstanceId instanceId = databaseId.getInstanceId(); @@ -163,6 +166,7 @@ public void backupCreationWithVersionTimeTooFarInThePastFails() throws Exception } @Test(expected = SpannerException.class) + @Ignore("backup and restore for pitr is not released yet") public void backupCreationWithVersionTimeInTheFutureFails() throws Exception { final DatabaseId databaseId = testDatabase.getId(); final InstanceId instanceId = databaseId.getInstanceId();