3737import com .google .cloud .spanner .ErrorCode ;
3838import com .google .cloud .spanner .Instance ;
3939import com .google .cloud .spanner .InstanceAdminClient ;
40+ import com .google .cloud .spanner .InstanceId ;
4041import com .google .cloud .spanner .IntegrationTestEnv ;
4142import com .google .cloud .spanner .Mutation ;
4243import com .google .cloud .spanner .Options ;
4344import com .google .cloud .spanner .ParallelIntegrationTest ;
45+ import com .google .cloud .spanner .ResultSet ;
4446import com .google .cloud .spanner .SpannerException ;
4547import com .google .cloud .spanner .SpannerExceptionFactory ;
48+ import com .google .cloud .spanner .Statement ;
4649import com .google .cloud .spanner .testing .RemoteSpannerHelper ;
4750import com .google .common .base .Predicate ;
4851import com .google .common .base .Stopwatch ;
@@ -92,6 +95,8 @@ public class ITBackupTest {
9295 private List <String > databases = new ArrayList <>();
9396 private List <String > backups = new ArrayList <>();
9497 private final Random random = new Random ();
98+ private String projectId ;
99+ private String instanceId ;
95100
96101 @ BeforeClass
97102 public static void doNotRunOnEmulator () {
@@ -105,6 +110,8 @@ public void setUp() {
105110 dbAdminClient = testHelper .getClient ().getDatabaseAdminClient ();
106111 instanceAdminClient = testHelper .getClient ().getInstanceAdminClient ();
107112 instance = instanceAdminClient .getInstance (testHelper .getInstanceId ().getInstance ());
113+ projectId = testHelper .getInstanceId ().getProject ();
114+ instanceId = testHelper .getInstanceId ().getInstance ();
108115 logger .info ("Finished setup" );
109116
110117 // 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 {
226233 String backupId1 = testHelper .getUniqueBackupId () + "_bck1" ;
227234 String backupId2 = testHelper .getUniqueBackupId () + "_bck2" ;
228235 Timestamp expireTime = afterDays (7 );
236+ Timestamp versionTime = getCurrentTimestamp (client );
229237 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 );
242255 backups .add (backupId1 );
243256 backups .add (backupId2 );
244257
@@ -274,9 +287,13 @@ public void testBackups() throws InterruptedException, ExecutionException {
274287 "Backup2 still not finished. Test is giving up waiting for it." );
275288 }
276289 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 );
279292 }
293+
294+ // Verifies that backup version time is the specified one
295+ testBackupVersionTime (backup1 , versionTime );
296+
280297 // Insert some more data into db2 to get a timestamp from the server.
281298 Timestamp commitTs =
282299 client .writeAtLeastOnce (
@@ -291,54 +308,40 @@ public void testBackups() throws InterruptedException, ExecutionException {
291308 // Test listing operations.
292309 // List all backups.
293310 logger .info ("Listing all backups" );
294- assertThat (instance .listBackups ().iterateAll ()).containsAtLeast (backup1 , backup2 );
311+ assertThat (this . instance .listBackups ().iterateAll ()).containsAtLeast (backup1 , backup2 );
295312 // List all backups whose names contain 'bck1'.
296313 logger .info ("Listing backups with name bck1" );
297314 assertThat (
298315 dbAdminClient
299316 .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 ())))
302318 .iterateAll ())
303319 .containsExactly (backup1 );
304320 logger .info ("Listing ready backups" );
305321 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 ();
309323 assertThat (readyBackups ).containsAtLeast (backup1 , backup2 );
310324 // List all backups for databases whose names contain 'db1'.
311325 logger .info ("Listing backups for database db1" );
312326 assertThat (
313327 dbAdminClient
314328 .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 ())))
317330 .iterateAll ())
318331 .containsExactly (backup1 );
319332 // List all backups that were created before a certain time.
320333 Timestamp ts = Timestamp .ofTimeSecondsAndNanos (commitTs .getSeconds (), 0 );
321334 logger .info (String .format ("Listing backups created before %s" , ts ));
322335 assertThat (
323336 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 )))
327338 .iterateAll ())
328339 .containsAtLeast (backup1 , backup2 );
329340 // List all backups with a size > 0.
330341 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 ())
336343 .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 ())
342345 .doesNotContain (backup1 );
343346
344347 // Test pagination.
@@ -349,14 +352,79 @@ public void testBackups() throws InterruptedException, ExecutionException {
349352 testGetBackup (db2 , backupId2 , expireTime );
350353 testUpdateBackup (backup1 );
351354 testCreateInvalidExpirationDate (db1 );
352- testRestore (backup1 , op1 );
355+ testRestore (backup1 , op1 , versionTime );
353356
354357 testDelete (backupId2 );
355358 testCancelBackupOperation (db1 );
356359 // Finished all tests.
357360 logger .info ("Finished all backup tests" );
358361 }
359362
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+
360428 private void testMetadata (
361429 OperationFuture <Backup , CreateBackupMetadata > op1 ,
362430 OperationFuture <Backup , CreateBackupMetadata > op2 ,
@@ -391,11 +459,7 @@ private void testCreateInvalidExpirationDate(Database db) throws InterruptedExce
391459 String backupId = testHelper .getUniqueBackupId ();
392460 logger .info (String .format ("Creating backup %s with invalid expiration date" , backupId ));
393461 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 );
399463 backups .add (backupId );
400464 try {
401465 op .get ();
@@ -414,11 +478,7 @@ private void testCancelBackupOperation(Database db)
414478 String backupId = testHelper .getUniqueBackupId ();
415479 logger .info (String .format ("Starting to create backup %s" , backupId ));
416480 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 );
422482 backups .add (backupId );
423483 // Cancel the backup operation.
424484 logger .info (String .format ("Cancelling the creation of backup %s" , backupId ));
@@ -428,8 +488,7 @@ private void testCancelBackupOperation(Database db)
428488 for (Operation operation :
429489 dbAdminClient
430490 .listBackupOperations (
431- testHelper .getInstanceId ().getInstance (),
432- Options .filter (String .format ("name:%s" , op .getName ())))
491+ instanceId , Options .filter (String .format ("name:%s" , op .getName ())))
433492 .iterateAll ()) {
434493 assertThat (operation .getError ().getCode ()).isEqualTo (Status .Code .CANCELLED .value ());
435494 operationFound = true ;
@@ -481,18 +540,15 @@ private void testPagination(int expectedMinimumTotalBackups) {
481540 logger .info ("Listing backups using pagination" );
482541 int numBackups = 0 ;
483542 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 ));
486544 assertThat (page .getValues ()).hasSize (1 );
487545 numBackups ++;
488546 assertThat (page .hasNextPage ()).isTrue ();
489547 while (page .hasNextPage ()) {
490548 logger .info (String .format ("Fetching page %d" , numBackups + 1 ));
491549 page =
492550 dbAdminClient .listBackups (
493- testHelper .getInstanceId ().getInstance (),
494- Options .pageToken (page .getNextPageToken ()),
495- Options .pageSize (1 ));
551+ instanceId , Options .pageToken (page .getNextPageToken ()), Options .pageSize (1 ));
496552 assertThat (page .getValues ()).hasSize (1 );
497553 numBackups ++;
498554 }
@@ -521,7 +577,8 @@ private void testDelete(String backupId) throws InterruptedException {
521577 logger .info ("Finished delete tests" );
522578 }
523579
524- private void testRestore (Backup backup , OperationFuture <Backup , CreateBackupMetadata > backupOp )
580+ private void testRestore (
581+ Backup backup , OperationFuture <Backup , CreateBackupMetadata > backupOp , Timestamp versionTime )
525582 throws InterruptedException , ExecutionException {
526583 // Restore the backup to a new database.
527584 String restoredDb = testHelper .getUniqueDatabaseId ();
@@ -565,6 +622,8 @@ private void testRestore(Backup backup, OperationFuture<Backup, CreateBackupMeta
565622 assertThat (metadata .getSourceType ()).isEqualTo (RestoreSourceType .BACKUP );
566623 assertThat (metadata .getName ())
567624 .isEqualTo (DatabaseId .of (testHelper .getInstanceId (), restoredDb ).getName ());
625+ assertThat (Timestamp .fromProto (metadata .getBackupInfo ().getVersionTime ()))
626+ .isEqualTo (versionTime );
568627
569628 // Ensure the operations show up in the right collections.
570629 // 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
573632 // Wait until the restore operation has finished successfully.
574633 Database database = restoreOp .get ();
575634 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+
576643 // Restoring the backup to an existing database should fail.
577644 try {
578645 logger .info (
0 commit comments