Skip to content

Commit b7739da

Browse files
authored
tests: add backup integration tests (#69)
* tests: add backup integration tests * use unique instance ids for restore instances * remove optimization wait and ensure backups are being cleaned up on failures Co-authored-by: larkee <[email protected]>
1 parent 7a07c2b commit b7739da

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed

tests/system/test_system.py

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,288 @@ def _unit_of_work(transaction, name):
428428
self.assertEqual(len(rows), 2)
429429

430430

431+
@unittest.skipIf(USE_EMULATOR, "Skipping backup tests")
432+
class TestBackupAPI(unittest.TestCase, _TestData):
433+
DATABASE_NAME = "test_database" + unique_resource_id("_")
434+
DATABASE_NAME_2 = "test_database2" + unique_resource_id("_")
435+
436+
@classmethod
437+
def setUpClass(cls):
438+
pool = BurstyPool(labels={"testcase": "database_api"})
439+
db1 = Config.INSTANCE.database(
440+
cls.DATABASE_NAME, ddl_statements=DDL_STATEMENTS, pool=pool
441+
)
442+
db2 = Config.INSTANCE.database(cls.DATABASE_NAME_2, pool=pool)
443+
cls._db = db1
444+
cls._dbs = [db1, db2]
445+
op1 = db1.create()
446+
op2 = db2.create()
447+
op1.result(30) # raises on failure / timeout.
448+
op2.result(30) # raises on failure / timeout.
449+
450+
current_config = Config.INSTANCE.configuration_name
451+
same_config_instance_id = "same-config" + unique_resource_id("-")
452+
cls._same_config_instance = Config.CLIENT.instance(
453+
same_config_instance_id, current_config
454+
)
455+
op = cls._same_config_instance.create()
456+
op.result(30)
457+
cls._instances = [cls._same_config_instance]
458+
459+
retry = RetryErrors(exceptions.ServiceUnavailable)
460+
configs = list(retry(Config.CLIENT.list_instance_configs)())
461+
diff_configs = [
462+
config.name
463+
for config in configs
464+
if "-us-" in config.name and config.name is not current_config
465+
]
466+
cls._diff_config_instance = None
467+
if len(diff_configs) > 0:
468+
diff_config_instance_id = "diff-config" + unique_resource_id("-")
469+
cls._diff_config_instance = Config.CLIENT.instance(
470+
diff_config_instance_id, diff_configs[0]
471+
)
472+
op = cls._diff_config_instance.create()
473+
op.result(30)
474+
cls._instances.append(cls._diff_config_instance)
475+
476+
@classmethod
477+
def tearDownClass(cls):
478+
for db in cls._dbs:
479+
db.drop()
480+
for instance in cls._instances:
481+
instance.delete()
482+
483+
def setUp(self):
484+
self.to_delete = []
485+
self.to_drop = []
486+
487+
def tearDown(self):
488+
for doomed in self.to_delete:
489+
doomed.delete()
490+
for doomed in self.to_drop:
491+
doomed.drop()
492+
493+
def test_create_invalid(self):
494+
from datetime import datetime
495+
from pytz import UTC
496+
497+
backup_id = "backup_id" + unique_resource_id("_")
498+
expire_time = datetime.utcnow()
499+
expire_time = expire_time.replace(tzinfo=UTC)
500+
501+
backup = Config.INSTANCE.backup(
502+
backup_id, database=self._db, expire_time=expire_time
503+
)
504+
505+
with self.assertRaises(exceptions.InvalidArgument):
506+
op = backup.create()
507+
op.result()
508+
509+
def test_backup_workflow(self):
510+
from datetime import datetime
511+
from datetime import timedelta
512+
from pytz import UTC
513+
514+
instance = Config.INSTANCE
515+
backup_id = "backup_id" + unique_resource_id("_")
516+
expire_time = datetime.utcnow() + timedelta(days=3)
517+
expire_time = expire_time.replace(tzinfo=UTC)
518+
519+
# Create backup.
520+
backup = instance.backup(backup_id, database=self._db, expire_time=expire_time)
521+
operation = backup.create()
522+
self.to_delete.append(backup)
523+
524+
# Check metadata.
525+
metadata = operation.metadata
526+
self.assertEqual(backup.name, metadata.name)
527+
self.assertEqual(self._db.name, metadata.database)
528+
operation.result()
529+
530+
# Check backup object.
531+
backup.reload()
532+
self.assertEqual(self._db.name, backup._database)
533+
self.assertEqual(expire_time, backup.expire_time)
534+
self.assertIsNotNone(backup.create_time)
535+
self.assertIsNotNone(backup.size_bytes)
536+
self.assertIsNotNone(backup.state)
537+
538+
# Update with valid argument.
539+
valid_expire_time = datetime.utcnow() + timedelta(days=7)
540+
valid_expire_time = valid_expire_time.replace(tzinfo=UTC)
541+
backup.update_expire_time(valid_expire_time)
542+
self.assertEqual(valid_expire_time, backup.expire_time)
543+
544+
# Restore database to same instance.
545+
restored_id = "restored_db" + unique_resource_id("_")
546+
database = instance.database(restored_id)
547+
self.to_drop.append(database)
548+
operation = database.restore(source=backup)
549+
operation.result()
550+
551+
database.drop()
552+
backup.delete()
553+
self.assertFalse(backup.exists())
554+
555+
def test_restore_to_diff_instance(self):
556+
from datetime import datetime
557+
from datetime import timedelta
558+
from pytz import UTC
559+
560+
backup_id = "backup_id" + unique_resource_id("_")
561+
expire_time = datetime.utcnow() + timedelta(days=3)
562+
expire_time = expire_time.replace(tzinfo=UTC)
563+
564+
# Create backup.
565+
backup = Config.INSTANCE.backup(
566+
backup_id, database=self._db, expire_time=expire_time
567+
)
568+
op = backup.create()
569+
self.to_delete.append(backup)
570+
op.result()
571+
572+
# Restore database to different instance with same config.
573+
restored_id = "restored_db" + unique_resource_id("_")
574+
database = self._same_config_instance.database(restored_id)
575+
self.to_drop.append(database)
576+
operation = database.restore(source=backup)
577+
operation.result()
578+
579+
database.drop()
580+
backup.delete()
581+
self.assertFalse(backup.exists())
582+
583+
def test_multi_create_cancel_update_error_restore_errors(self):
584+
from datetime import datetime
585+
from datetime import timedelta
586+
from pytz import UTC
587+
588+
backup_id_1 = "backup_id1" + unique_resource_id("_")
589+
backup_id_2 = "backup_id2" + unique_resource_id("_")
590+
591+
instance = Config.INSTANCE
592+
expire_time = datetime.utcnow() + timedelta(days=3)
593+
expire_time = expire_time.replace(tzinfo=UTC)
594+
595+
backup1 = instance.backup(
596+
backup_id_1, database=self._dbs[0], expire_time=expire_time
597+
)
598+
backup2 = instance.backup(
599+
backup_id_2, database=self._dbs[1], expire_time=expire_time
600+
)
601+
602+
# Create two backups.
603+
op1 = backup1.create()
604+
op2 = backup2.create()
605+
self.to_delete.extend([backup1, backup2])
606+
607+
backup1.reload()
608+
self.assertFalse(backup1.is_ready())
609+
backup2.reload()
610+
self.assertFalse(backup2.is_ready())
611+
612+
# Cancel a create operation.
613+
op2.cancel()
614+
self.assertTrue(op2.cancelled())
615+
616+
op1.result()
617+
backup1.reload()
618+
self.assertTrue(backup1.is_ready())
619+
620+
# Update expire time to invalid value.
621+
invalid_expire_time = datetime.now() + timedelta(days=366)
622+
invalid_expire_time = invalid_expire_time.replace(tzinfo=UTC)
623+
with self.assertRaises(exceptions.InvalidArgument):
624+
backup1.update_expire_time(invalid_expire_time)
625+
626+
# Restore to existing database.
627+
with self.assertRaises(exceptions.AlreadyExists):
628+
self._db.restore(source=backup1)
629+
630+
# Restore to instance with different config.
631+
if self._diff_config_instance is not None:
632+
return
633+
new_db = self._diff_config_instance.database("diff_config")
634+
op = new_db.create()
635+
op.result(30)
636+
self.to_drop.append(new_db)
637+
with self.assertRaises(exceptions.InvalidArgument):
638+
new_db.restore(source=backup1)
639+
640+
def test_list_backups(self):
641+
from datetime import datetime
642+
from datetime import timedelta
643+
from pytz import UTC
644+
645+
backup_id_1 = "backup_id1" + unique_resource_id("_")
646+
backup_id_2 = "backup_id2" + unique_resource_id("_")
647+
648+
instance = Config.INSTANCE
649+
expire_time_1 = datetime.utcnow() + timedelta(days=21)
650+
expire_time_1 = expire_time_1.replace(tzinfo=UTC)
651+
652+
backup1 = Config.INSTANCE.backup(
653+
backup_id_1, database=self._dbs[0], expire_time=expire_time_1
654+
)
655+
656+
expire_time_2 = datetime.utcnow() + timedelta(days=1)
657+
expire_time_2 = expire_time_2.replace(tzinfo=UTC)
658+
backup2 = Config.INSTANCE.backup(
659+
backup_id_2, database=self._dbs[1], expire_time=expire_time_2
660+
)
661+
662+
# Create two backups.
663+
op1 = backup1.create()
664+
op1.result()
665+
backup1.reload()
666+
create_time_compare = datetime.utcnow().replace(tzinfo=UTC)
667+
668+
backup2.create()
669+
self.to_delete.extend([backup1, backup2])
670+
671+
# List backups filtered by state.
672+
filter_ = "state:CREATING"
673+
for backup in instance.list_backups(filter_=filter_):
674+
self.assertEqual(backup.name, backup2.name)
675+
676+
# List backups filtered by backup name.
677+
filter_ = "name:{0}".format(backup_id_1)
678+
for backup in instance.list_backups(filter_=filter_):
679+
self.assertEqual(backup.name, backup1.name)
680+
681+
# List backups filtered by database name.
682+
filter_ = "database:{0}".format(self._dbs[0].name)
683+
for backup in instance.list_backups(filter_=filter_):
684+
self.assertEqual(backup.name, backup1.name)
685+
686+
# List backups filtered by create time.
687+
filter_ = 'create_time > "{0}"'.format(
688+
create_time_compare.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
689+
)
690+
for backup in instance.list_backups(filter_=filter_):
691+
self.assertEqual(backup.name, backup2.name)
692+
693+
# List backups filtered by expire time.
694+
filter_ = 'expire_time > "{0}"'.format(
695+
expire_time_1.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
696+
)
697+
for backup in instance.list_backups(filter_=filter_):
698+
self.assertEqual(backup.name, backup1.name)
699+
700+
# List backups filtered by size bytes.
701+
filter_ = "size_bytes < {0}".format(backup1.size_bytes)
702+
for backup in instance.list_backups(filter_=filter_):
703+
self.assertEqual(backup.name, backup2.name)
704+
705+
# List backups using pagination.
706+
for page in instance.list_backups(page_size=1).pages:
707+
count = 0
708+
for backup in page:
709+
count += 1
710+
self.assertEqual(count, 1)
711+
712+
431713
SOME_DATE = datetime.date(2011, 1, 17)
432714
SOME_TIME = datetime.datetime(1989, 1, 17, 17, 59, 12, 345612)
433715
NANO_TIME = DatetimeWithNanoseconds(1995, 8, 31, nanosecond=987654321)

0 commit comments

Comments
 (0)