derive test skeleton for disaster rescue mode
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Tue, 22 Aug 2017 15:17:04 +0000 (17:17 +0200)
committerThomas Jarosch <thomas.jarosch@intra2net.com>
Mon, 2 Apr 2018 11:34:09 +0000 (13:34 +0200)
testing/test_recover.py

index 91b10f7..fbb31bd 100644 (file)
@@ -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