From: Philipp Gesang Date: Tue, 22 Aug 2017 15:17:04 +0000 (+0200) Subject: derive test skeleton for disaster rescue mode X-Git-Tag: v2.2~7^2~56 X-Git-Url: http://developer.intra2net.com/git/?p=python-delta-tar;a=commitdiff_plain;h=0c6682ce490bb871c4ae8bf2a510a8eb46621eb1 derive test skeleton for disaster rescue mode --- diff --git a/testing/test_recover.py b/testing/test_recover.py index 91b10f7..fbb31bd 100644 --- a/testing/test_recover.py +++ b/testing/test_recover.py @@ -187,7 +187,7 @@ def corrupt_hole (_, fname, compress, encrypt): ## tests ## ############################################################################### -class RecoverTest (BaseTest): +class DefectiveTest (BaseTest): """ Disaster recovery: restore corrupt backups. """ @@ -227,6 +227,11 @@ class RecoverTest (BaseTest): os.system("rm -rf source_dir source_dir2 backup_dir*") +class RecoverTest (DefectiveTest): + """ + Recover: restore corrupt backups from index file information. + """ + def test_recover_corrupt (self): """ Perform various damaging actions that cause unreadable objects. @@ -349,6 +354,127 @@ class RecoverTest (BaseTest): shutil.rmtree (self.dst_path) +class RescueTest (DefectiveTest): + """ + Rescue: restore corrupt backups from backup set that is damaged to a degree + that the index file is worthless. + """ + + def test_rescue_corrupt (self): + """ + Perform various damaging actions that cause unreadable objects, then + attempt to extract objects regardless. + """ + mode = self.COMPRESSION or "#" + bak_path = "backup_dir" + backup_file = "the_full_backup_%0.2d.tar" + backup_full = ("%s/%s" % (bak_path, backup_file)) % 0 + index_file = "the_full_index" + + if self.COMPRESSION is not None: + backup_file += ".gz" + backup_full += ".gz" + index_file += ".gz" + + if self.PASSWORD is not None: + backup_file = "%s.%s" % (backup_file, deltatar.PDTCRYPT_EXTENSION) + backup_full = "%s.%s" % (backup_full, deltatar.PDTCRYPT_EXTENSION) + index_file = "%s.%s" % (index_file , deltatar.PDTCRYPT_EXTENSION) + + if self.VOLUMES > 1: + # add n files for one nth the volume size each, corrected + # for metadata and tar block overhead + fsiz = int ( ( TEST_VOLSIZ + / (TEST_FILESPERVOL * VOLUME_OVERHEAD)) + * 1024 * 1024) + fcnt = (self.VOLUMES - 1) * TEST_FILESPERVOL + for i in range (fcnt): + nvol, invol = divmod(i, TEST_FILESPERVOL) + f = "dummy_vol_%d_n_%0.2d" % (nvol, invol) + self.hash [f] = self.create_file ("%s/%s" + % (self.src_path, f), + fsiz, + random=True) + + def vname (_x, _y, n, *a, **kwa): + return backup_file % n + + dtar = deltatar.DeltaTar (mode=mode, + logger=None, + password=self.PASSWORD, + index_name_func=lambda _: index_file, + volume_name_func=vname) + + dtar.create_full_backup \ + (source_path=self.src_path, backup_path=bak_path, + max_volume_size=1) + + if self.PASSWORD is not None: + # ensure all files are at least superficially in PDT format + for f in os.listdir (bak_path): + assert is_pdt_encrypted ("%s/%s" % (bak_path, f)) + + # first restore must succeed + dtar.restore_backup(target_path=self.dst_path, + backup_indexes_paths=[ + "%s/%s" % (bak_path, index_file) + ]) + for key, value in self.hash.items (): + f = "%s/%s" % (self.dst_path, key) + assert os.path.exists (f) + assert value == self.md5sum (f) + shutil.rmtree (self.dst_path) + shutil.rmtree (self.src_path) + + self.CORRUPT (backup_full, + self.COMPRESSION is not None, + self.PASSWORD is not None) + + # normal restore must fail + try: + dtar.restore_backup(target_path=self.dst_path, + backup_tar_path=backup_full) + except tarfile.CompressionError: + if self.PASSWORD is not None or self.COMPRESSION is not None: + pass + else: + raise + except tarfile.ReadError: + # can happen with all three modes + pass + except tarfile.DecryptionError: + if self.PASSWORD is not None: + pass + else: + raise + + os.chdir (self.pwd) # not restored due to the error above + # but recover will succeed + failed = dtar.rescue_backup(target_path=self.dst_path, + backup_indexes_paths=[ + "%s/%s" % (bak_path, index_file) + ]) + + assert len (failed) == self.FAILURES + + # with one file missing + missing = [] + mismatch = [] + for key, value in self.hash.items (): + kkey = "%s/%s" % (self.dst_path, key) + if os.path.exists (kkey): + if value != self.md5sum (kkey): + mismatch.append (key) + else: + missing.append (key) + + ssert len (missing) == (self.MISSING if self.MISSING is not None + else self.FAILURES) + assert len (mismatch) == self.MISMATCHES + + shutil.rmtree (self.dst_path) + + class RecoverCorruptPayloadTestBase (RecoverTest): COMPRESSION = None PASSWORD = None