|
37 | 37 | import com.google.cloud.spanner.ErrorCode;
|
38 | 38 | import com.google.cloud.spanner.Instance;
|
39 | 39 | import com.google.cloud.spanner.InstanceAdminClient;
|
| 40 | +import com.google.cloud.spanner.InstanceId; |
40 | 41 | import com.google.cloud.spanner.IntegrationTestEnv;
|
41 | 42 | import com.google.cloud.spanner.Mutation;
|
42 | 43 | import com.google.cloud.spanner.Options;
|
43 | 44 | import com.google.cloud.spanner.ParallelIntegrationTest;
|
| 45 | +import com.google.cloud.spanner.ResultSet; |
44 | 46 | import com.google.cloud.spanner.SpannerException;
|
45 | 47 | import com.google.cloud.spanner.SpannerExceptionFactory;
|
| 48 | +import com.google.cloud.spanner.Statement; |
46 | 49 | import com.google.cloud.spanner.testing.RemoteSpannerHelper;
|
47 | 50 | import com.google.common.base.Predicate;
|
48 | 51 | import com.google.common.base.Stopwatch;
|
@@ -92,6 +95,8 @@ public class ITBackupTest {
|
92 | 95 | private List<String> databases = new ArrayList<>();
|
93 | 96 | private List<String> backups = new ArrayList<>();
|
94 | 97 | private final Random random = new Random();
|
| 98 | +private String projectId; |
| 99 | +private String instanceId; |
95 | 100 |
|
96 | 101 | @BeforeClass
|
97 | 102 | public static void doNotRunOnEmulator() {
|
@@ -105,6 +110,8 @@ public void setUp() {
|
105 | 110 | dbAdminClient = testHelper.getClient().getDatabaseAdminClient();
|
106 | 111 | instanceAdminClient = testHelper.getClient().getInstanceAdminClient();
|
107 | 112 | instance = instanceAdminClient.getInstance(testHelper.getInstanceId().getInstance());
|
| 113 | +projectId = testHelper.getInstanceId().getProject(); |
| 114 | +instanceId = testHelper.getInstanceId().getInstance(); |
108 | 115 | logger.info("Finished setup");
|
109 | 116 |
|
110 | 117 | // Cancel any backup operation that has been started by this integration test if it has been
|
@@ -226,19 +233,25 @@ public void testBackups() throws InterruptedException, ExecutionException {
|
226 | 233 | String backupId1 = testHelper.getUniqueBackupId() + "_bck1";
|
227 | 234 | String backupId2 = testHelper.getUniqueBackupId() + "_bck2";
|
228 | 235 | Timestamp expireTime = afterDays(7);
|
| 236 | +Timestamp versionTime = getCurrentTimestamp(client); |
229 | 237 | logger.info(String.format("Creating backups %s and %s in parallel", backupId1, backupId2));
|
230 |
| -OperationFuture<Backup, CreateBackupMetadata> op1 = |
231 |
| -dbAdminClient.createBackup( |
232 |
| -testHelper.getInstanceId().getInstance(), |
233 |
| -backupId1, |
234 |
| -db1.getId().getDatabase(), |
235 |
| -expireTime); |
236 |
| -OperationFuture<Backup, CreateBackupMetadata> op2 = |
237 |
| -dbAdminClient.createBackup( |
238 |
| -testHelper.getInstanceId().getInstance(), |
239 |
| -backupId2, |
240 |
| -db2.getId().getDatabase(), |
241 |
| -expireTime); |
| 238 | +// This backup has the version time specified as the server's current timestamp |
| 239 | +final Backup backupToCreate1 = |
| 240 | +dbAdminClient |
| 241 | +.newBackupBuilder(BackupId.of(projectId, instanceId, backupId1)) |
| 242 | +.setDatabase(db1.getId()) |
| 243 | +.setExpireTime(expireTime) |
| 244 | +.setVersionTime(versionTime) |
| 245 | +.build(); |
| 246 | +// This backup has no version time specified |
| 247 | +final Backup backupToCreate2 = |
| 248 | +dbAdminClient |
| 249 | +.newBackupBuilder(BackupId.of(projectId, instanceId, backupId2)) |
| 250 | +.setDatabase(db2.getId()) |
| 251 | +.setExpireTime(expireTime) |
| 252 | +.build(); |
| 253 | +OperationFuture<Backup, CreateBackupMetadata> op1 = dbAdminClient.createBackup(backupToCreate1); |
| 254 | +OperationFuture<Backup, CreateBackupMetadata> op2 = dbAdminClient.createBackup(backupToCreate2); |
242 | 255 | backups.add(backupId1);
|
243 | 256 | backups.add(backupId2);
|
244 | 257 |
|
@@ -274,9 +287,13 @@ public void testBackups() throws InterruptedException, ExecutionException {
|
274 | 287 | "Backup2 still not finished. Test is giving up waiting for it.");
|
275 | 288 | }
|
276 | 289 | logger.info("Long-running operations finished. Getting backups by id.");
|
277 |
| -backup1 = dbAdminClient.getBackup(instance.getId().getInstance(), backupId1); |
278 |
| -backup2 = dbAdminClient.getBackup(instance.getId().getInstance(), backupId2); |
| 290 | +backup1 = dbAdminClient.getBackup(this.instance.getId().getInstance(), backupId1); |
| 291 | +backup2 = dbAdminClient.getBackup(this.instance.getId().getInstance(), backupId2); |
279 | 292 | }
|
| 293 | + |
| 294 | +// Verifies that backup version time is the specified one |
| 295 | +testBackupVersionTime(backup1, versionTime); |
| 296 | + |
280 | 297 | // Insert some more data into db2 to get a timestamp from the server.
|
281 | 298 | Timestamp commitTs =
|
282 | 299 | client.writeAtLeastOnce(
|
@@ -291,54 +308,40 @@ public void testBackups() throws InterruptedException, ExecutionException {
|
291 | 308 | // Test listing operations.
|
292 | 309 | // List all backups.
|
293 | 310 | logger.info("Listing all backups");
|
294 |
| -assertThat(instance.listBackups().iterateAll()).containsAtLeast(backup1, backup2); |
| 311 | +assertThat(this.instance.listBackups().iterateAll()).containsAtLeast(backup1, backup2); |
295 | 312 | // List all backups whose names contain 'bck1'.
|
296 | 313 | logger.info("Listing backups with name bck1");
|
297 | 314 | assertThat(
|
298 | 315 | dbAdminClient
|
299 | 316 | .listBackups(
|
300 |
| -testHelper.getInstanceId().getInstance(), |
301 |
| -Options.filter(String.format("name:%s", backup1.getId().getName()))) |
| 317 | +instanceId, Options.filter(String.format("name:%s", backup1.getId().getName()))) |
302 | 318 | .iterateAll())
|
303 | 319 | .containsExactly(backup1);
|
304 | 320 | logger.info("Listing ready backups");
|
305 | 321 | Iterable<Backup> readyBackups =
|
306 |
| -dbAdminClient |
307 |
| -.listBackups(testHelper.getInstanceId().getInstance(), Options.filter("state:READY")) |
308 |
| -.iterateAll(); |
| 322 | +dbAdminClient.listBackups(instanceId, Options.filter("state:READY")).iterateAll(); |
309 | 323 | assertThat(readyBackups).containsAtLeast(backup1, backup2);
|
310 | 324 | // List all backups for databases whose names contain 'db1'.
|
311 | 325 | logger.info("Listing backups for database db1");
|
312 | 326 | assertThat(
|
313 | 327 | dbAdminClient
|
314 | 328 | .listBackups(
|
315 |
| -testHelper.getInstanceId().getInstance(), |
316 |
| -Options.filter(String.format("database:%s", db1.getId().getName()))) |
| 329 | +instanceId, Options.filter(String.format("database:%s", db1.getId().getName()))) |
317 | 330 | .iterateAll())
|
318 | 331 | .containsExactly(backup1);
|
319 | 332 | // List all backups that were created before a certain time.
|
320 | 333 | Timestamp ts = Timestamp.ofTimeSecondsAndNanos(commitTs.getSeconds(), 0);
|
321 | 334 | logger.info(String.format("Listing backups created before %s", ts));
|
322 | 335 | assertThat(
|
323 | 336 | dbAdminClient
|
324 |
| -.listBackups( |
325 |
| -testHelper.getInstanceId().getInstance(), |
326 |
| -Options.filter(String.format("create_time<\"%s\"", ts))) |
| 337 | +.listBackups(instanceId, Options.filter(String.format("create_time<\"%s\"", ts))) |
327 | 338 | .iterateAll())
|
328 | 339 | .containsAtLeast(backup1, backup2);
|
329 | 340 | // List all backups with a size > 0.
|
330 | 341 | logger.info("Listing backups with size>0");
|
331 |
| -assertThat( |
332 |
| -dbAdminClient |
333 |
| -.listBackups( |
334 |
| -testHelper.getInstanceId().getInstance(), Options.filter("size_bytes>0")) |
335 |
| -.iterateAll()) |
| 342 | +assertThat(dbAdminClient.listBackups(instanceId, Options.filter("size_bytes>0")).iterateAll()) |
336 | 343 | .contains(backup2);
|
337 |
| -assertThat( |
338 |
| -dbAdminClient |
339 |
| -.listBackups( |
340 |
| -testHelper.getInstanceId().getInstance(), Options.filter("size_bytes>0")) |
341 |
| -.iterateAll()) |
| 344 | +assertThat(dbAdminClient.listBackups(instanceId, Options.filter("size_bytes>0")).iterateAll()) |
342 | 345 | .doesNotContain(backup1);
|
343 | 346 |
|
344 | 347 | // Test pagination.
|
@@ -349,14 +352,79 @@ public void testBackups() throws InterruptedException, ExecutionException {
|
349 | 352 | testGetBackup(db2, backupId2, expireTime);
|
350 | 353 | testUpdateBackup(backup1);
|
351 | 354 | testCreateInvalidExpirationDate(db1);
|
352 |
| -testRestore(backup1, op1); |
| 355 | +testRestore(backup1, op1, versionTime); |
353 | 356 |
|
354 | 357 | testDelete(backupId2);
|
355 | 358 | testCancelBackupOperation(db1);
|
356 | 359 | // Finished all tests.
|
357 | 360 | logger.info("Finished all backup tests");
|
358 | 361 | }
|
359 | 362 |
|
| 363 | +@Test(expected = SpannerException.class) |
| 364 | +public void backupCreationWithVersionTimeTooFarInThePastFails() throws Exception { |
| 365 | +final Database testDatabase = testHelper.createTestDatabase(); |
| 366 | +final DatabaseId databaseId = testDatabase.getId(); |
| 367 | +final InstanceId instanceId = databaseId.getInstanceId(); |
| 368 | +final String backupId = testHelper.getUniqueBackupId(); |
| 369 | +final Timestamp expireTime = afterDays(7); |
| 370 | +final Timestamp versionTime = daysAgo(30); |
| 371 | +final Backup backupToCreate = |
| 372 | +dbAdminClient |
| 373 | +.newBackupBuilder(BackupId.of(instanceId, backupId)) |
| 374 | +.setDatabase(databaseId) |
| 375 | +.setExpireTime(expireTime) |
| 376 | +.setVersionTime(versionTime) |
| 377 | +.build(); |
| 378 | + |
| 379 | +getOrThrow(dbAdminClient.createBackup(backupToCreate)); |
| 380 | +} |
| 381 | + |
| 382 | +@Test(expected = SpannerException.class) |
| 383 | +public void backupCreationWithVersionTimeInTheFutureFails() throws Exception { |
| 384 | +final Database testDatabase = testHelper.createTestDatabase(); |
| 385 | +final DatabaseId databaseId = testDatabase.getId(); |
| 386 | +final InstanceId instanceId = databaseId.getInstanceId(); |
| 387 | +final String backupId = testHelper.getUniqueBackupId(); |
| 388 | +final Timestamp expireTime = afterDays(7); |
| 389 | +final Timestamp versionTime = afterDays(1); |
| 390 | +final Backup backupToCreate = |
| 391 | +dbAdminClient |
| 392 | +.newBackupBuilder(BackupId.of(instanceId, backupId)) |
| 393 | +.setDatabase(databaseId) |
| 394 | +.setExpireTime(expireTime) |
| 395 | +.setVersionTime(versionTime) |
| 396 | +.build(); |
| 397 | + |
| 398 | +getOrThrow(dbAdminClient.createBackup(backupToCreate)); |
| 399 | +} |
| 400 | + |
| 401 | +private <T> T getOrThrow(OperationFuture<T, ?> operation) |
| 402 | +throws InterruptedException, ExecutionException { |
| 403 | +try { |
| 404 | +return operation.get(); |
| 405 | +} catch (ExecutionException e) { |
| 406 | +if (e.getCause() instanceof SpannerException) { |
| 407 | +throw (SpannerException) e.getCause(); |
| 408 | +} else { |
| 409 | +throw e; |
| 410 | +} |
| 411 | +} |
| 412 | +} |
| 413 | + |
| 414 | +private Timestamp getCurrentTimestamp(DatabaseClient client) { |
| 415 | +try (ResultSet resultSet = |
| 416 | +client.singleUse().executeQuery(Statement.of("SELECT CURRENT_TIMESTAMP()"))) { |
| 417 | +resultSet.next(); |
| 418 | +return resultSet.getTimestamp(0); |
| 419 | +} |
| 420 | +} |
| 421 | + |
| 422 | +private void testBackupVersionTime(Backup backup, Timestamp versionTime) { |
| 423 | +logger.info("Verifying backup version time for " + backup.getId()); |
| 424 | +assertThat(backup.getVersionTime()).isEqualTo(versionTime); |
| 425 | +logger.info("Done verifying backup version time for " + backup.getId()); |
| 426 | +} |
| 427 | + |
360 | 428 | private void testMetadata(
|
361 | 429 | OperationFuture<Backup, CreateBackupMetadata> op1,
|
362 | 430 | OperationFuture<Backup, CreateBackupMetadata> op2,
|
@@ -391,11 +459,7 @@ private void testCreateInvalidExpirationDate(Database db) throws InterruptedExce
|
391 | 459 | String backupId = testHelper.getUniqueBackupId();
|
392 | 460 | logger.info(String.format("Creating backup %s with invalid expiration date", backupId));
|
393 | 461 | OperationFuture<Backup, CreateBackupMetadata> op =
|
394 |
| -dbAdminClient.createBackup( |
395 |
| -testHelper.getInstanceId().getInstance(), |
396 |
| -backupId, |
397 |
| -db.getId().getDatabase(), |
398 |
| -expireTime); |
| 462 | +dbAdminClient.createBackup(instanceId, backupId, db.getId().getDatabase(), expireTime); |
399 | 463 | backups.add(backupId);
|
400 | 464 | try {
|
401 | 465 | op.get();
|
@@ -414,11 +478,7 @@ private void testCancelBackupOperation(Database db)
|
414 | 478 | String backupId = testHelper.getUniqueBackupId();
|
415 | 479 | logger.info(String.format("Starting to create backup %s", backupId));
|
416 | 480 | OperationFuture<Backup, CreateBackupMetadata> op =
|
417 |
| -dbAdminClient.createBackup( |
418 |
| -testHelper.getInstanceId().getInstance(), |
419 |
| -backupId, |
420 |
| -db.getId().getDatabase(), |
421 |
| -expireTime); |
| 481 | +dbAdminClient.createBackup(instanceId, backupId, db.getId().getDatabase(), expireTime); |
422 | 482 | backups.add(backupId);
|
423 | 483 | // Cancel the backup operation.
|
424 | 484 | logger.info(String.format("Cancelling the creation of backup %s", backupId));
|
@@ -428,8 +488,7 @@ private void testCancelBackupOperation(Database db)
|
428 | 488 | for (Operation operation :
|
429 | 489 | dbAdminClient
|
430 | 490 | .listBackupOperations(
|
431 |
| -testHelper.getInstanceId().getInstance(), |
432 |
| -Options.filter(String.format("name:%s", op.getName()))) |
| 491 | +instanceId, Options.filter(String.format("name:%s", op.getName()))) |
433 | 492 | .iterateAll()) {
|
434 | 493 | assertThat(operation.getError().getCode()).isEqualTo(Status.Code.CANCELLED.value());
|
435 | 494 | operationFound = true;
|
@@ -481,18 +540,15 @@ private void testPagination(int expectedMinimumTotalBackups) {
|
481 | 540 | logger.info("Listing backups using pagination");
|
482 | 541 | int numBackups = 0;
|
483 | 542 | logger.info("Fetching first page");
|
484 |
| -Page<Backup> page = |
485 |
| -dbAdminClient.listBackups(testHelper.getInstanceId().getInstance(), Options.pageSize(1)); |
| 543 | +Page<Backup> page = dbAdminClient.listBackups(instanceId, Options.pageSize(1)); |
486 | 544 | assertThat(page.getValues()).hasSize(1);
|
487 | 545 | numBackups++;
|
488 | 546 | assertThat(page.hasNextPage()).isTrue();
|
489 | 547 | while (page.hasNextPage()) {
|
490 | 548 | logger.info(String.format("Fetching page %d", numBackups + 1));
|
491 | 549 | page =
|
492 | 550 | dbAdminClient.listBackups(
|
493 |
| -testHelper.getInstanceId().getInstance(), |
494 |
| -Options.pageToken(page.getNextPageToken()), |
495 |
| -Options.pageSize(1)); |
| 551 | +instanceId, Options.pageToken(page.getNextPageToken()), Options.pageSize(1)); |
496 | 552 | assertThat(page.getValues()).hasSize(1);
|
497 | 553 | numBackups++;
|
498 | 554 | }
|
@@ -521,7 +577,8 @@ private void testDelete(String backupId) throws InterruptedException {
|
521 | 577 | logger.info("Finished delete tests");
|
522 | 578 | }
|
523 | 579 |
|
524 |
| -private void testRestore(Backup backup, OperationFuture<Backup, CreateBackupMetadata> backupOp) |
| 580 | +private void testRestore( |
| 581 | +Backup backup, OperationFuture<Backup, CreateBackupMetadata> backupOp, Timestamp versionTime) |
525 | 582 | throws InterruptedException, ExecutionException {
|
526 | 583 | // Restore the backup to a new database.
|
527 | 584 | String restoredDb = testHelper.getUniqueDatabaseId();
|
@@ -565,6 +622,8 @@ private void testRestore(Backup backup, OperationFuture<Backup, CreateBackupMeta
|
565 | 622 | assertThat(metadata.getSourceType()).isEqualTo(RestoreSourceType.BACKUP);
|
566 | 623 | assertThat(metadata.getName())
|
567 | 624 | .isEqualTo(DatabaseId.of(testHelper.getInstanceId(), restoredDb).getName());
|
| 625 | +assertThat(Timestamp.fromProto(metadata.getBackupInfo().getVersionTime())) |
| 626 | +.isEqualTo(versionTime); |
568 | 627 |
|
569 | 628 | // Ensure the operations show up in the right collections.
|
570 | 629 | // TODO: Re-enable when it is clear why this fails on the CI environment.
|
@@ -573,6 +632,14 @@ private void testRestore(Backup backup, OperationFuture<Backup, CreateBackupMeta
|
573 | 632 | // Wait until the restore operation has finished successfully.
|
574 | 633 | Database database = restoreOp.get();
|
575 | 634 | assertThat(database.getId().getDatabase()).isEqualTo(restoredDb);
|
| 635 | + |
| 636 | +// Reloads the database |
| 637 | +final Database reloadedDatabase = database.reload(); |
| 638 | +assertThat( |
| 639 | +Timestamp.fromProto( |
| 640 | +reloadedDatabase.getProto().getRestoreInfo().getBackupInfo().getVersionTime())) |
| 641 | +.isEqualTo(versionTime); |
| 642 | + |
576 | 643 | // Restoring the backup to an existing database should fail.
|
577 | 644 | try {
|
578 | 645 | logger.info(
|
|
0 commit comments