fixing and unit testing multivolume support
authorEduardo Robles Elvira <edulix@wadobo.com>
Sun, 28 Jul 2013 08:44:10 +0000 (10:44 +0200)
committerEduardo Robles Elvira <edulix@wadobo.com>
Sun, 28 Jul 2013 08:44:10 +0000 (10:44 +0200)
deltatar/deltatar.py
deltatar/tarfile.py
testing/test_deltatar.py

index bd8ccab..fa57ac0 100644 (file)
@@ -266,8 +266,8 @@ class DeltaTar(object):
         - source_path: source path to the directory to back up.
         - backup_path: path where the back up will be stored. Backup path will
           be created if not existent.
-        - max_volume_size: maximum volume size. Used to split the backup in
-          volumes. Optional (won't split in volumes by default).
+        - max_volume_size: maximum volume size in megabytes. Used to split the
+          backup in volumes. Optional (won't split in volumes by default).
         '''
         # check input
         if not isinstance(source_path, basestring):
@@ -280,6 +280,11 @@ class DeltaTar(object):
             raise Exception('Source path "%s" does not exist or is not a '\
                             'directory' % source_path)
 
+        if max_volume_size != None and not isinstance(max_volume_size, int):
+            raise Exception('max_volume_size must be an integer')
+        if max_volume_size != None:
+            max_volume_size = max_volume_size*1024*1024
+
         if not os.access(source_path, os.R_OK):
             raise Exception('Source path "%s" is not readable' % source_path)
 
@@ -315,13 +320,25 @@ class DeltaTar(object):
         # TODO: encrypt or compress it if necessary
         index_fd = open(index_path, 'w')
 
-        def new_volume_handler(deltarobj, tarobj, base_name, volume_number):
+        cwd = os.getcwd()
+
+        def new_volume_handler(deltarobj, cwd, backup_path, tarobj, base_name, volume_number):
             '''
             Handles the new volumes
             '''
-            volume_path = deltarobj.volume_name_func(True, volume_number)
-            tarobj.open_volume(volume_path)
-        new_volume_handler = partial(new_volume_handler, self)
+            volume_name = deltarobj.volume_name_func(backup_path, True, volume_number)
+            volume_path = os.path.join(backup_path, volume_name)
+
+            # we convert relative paths into absolute because CWD is changed
+            if not os.path.isabs(volume_path):
+                volume_path = os.path.join(cwd, volume_path)
+            try:
+                tarobj.open_volume(volume_path)
+            except Exception, e:
+                import ipdb; ipdb.set_trace()
+
+        # wraps some args from context into the handler
+        new_volume_handler = partial(new_volume_handler, self, cwd, backup_path)
 
         index_fd.write('{"type": "python-delta-tar-index", version: "1" }\n')
 
@@ -339,7 +356,7 @@ class DeltaTar(object):
                               max_volume_size=max_volume_size,
                               new_volume_handler=new_volume_handler)
 
-        cwd = os.getcwd()
+
         os.chdir(source_path)
         for path in self._recursive_walk_dir('.'):
             # TODO: reduce paths length using previous dir entries
@@ -413,13 +430,22 @@ class DeltaTar(object):
         if not os.path.exists(target_path):
             os.makedirs(target_path)
 
-        def new_volume_handler(deltarobj, tarobj, base_name, volume_number):
+        backup_path = os.path.dirname(backup_tar_path)
+        cwd = os.getcwd()
+        def new_volume_handler(deltarobj, cwd, backup_path, tarobj, base_name, volume_number):
             '''
             Handles the new volumes
             '''
-            volume_path = deltarobj.volume_name_func(True, volume_number)
+            volume_name = deltarobj.volume_name_func(backup_path, True, volume_number)
+            volume_path = os.path.join(backup_path, volume_name)
+
+            # we convert relative paths into absolute because CWD is changed
+            if not os.path.isabs(volume_path):
+                volume_path = os.path.join(cwd, volume_path)
             tarobj.open_volume(volume_path)
-        new_volume_handler = partial(new_volume_handler, self)
+
+        # wraps some args from context into the handler
+        new_volume_handler = partial(new_volume_handler, self, cwd, backup_path)
 
         tarobj = tarfile.TarFile.open(backup_tar_path,
                               mode='r' + self.mode,
@@ -427,7 +453,6 @@ class DeltaTar(object):
                               concat_compression='#gz' in self.mode,
                               password=self.password,
                               new_volume_handler=new_volume_handler)
-        cwd = os.getcwd()
         os.chdir(target_path)
         tarobj.extractall()
         os.chdir(cwd)
index 1734c7b..879597a 100644 (file)
@@ -1714,7 +1714,8 @@ class TarFile(object):
         else:
             if name is None and hasattr(fileobj, "name"):
                 name = fileobj.name
-            if hasattr(fileobj, "mode"):
+            # when fileobj is a gzip.GzipFile, fileobj.mode is an int (not valid for us)
+            if hasattr(fileobj, "mode") and isinstance(fileobj.mode, basestring):
                 self._mode = fileobj.mode
             self._extfileobj = True
         self.base_name = self.name = os.path.abspath(name) if name else None
index d2d04e3..c5ba719 100644 (file)
@@ -39,12 +39,14 @@ class DeltaTarTest(BaseTest):
         '''
         Create base test data
         '''
+        os.system('rm -rf source_dir backup_dir')
         os.makedirs('source_dir/test/test2')
         self.hash = dict()
         self.hash["source_dir/test/test2"] = ''
         self.hash["source_dir/big"]  = self.create_file("source_dir/big", 50000)
         self.hash["source_dir/small"]  = self.create_file("source_dir/small", 100)
         self.hash["source_dir/test/huge"]  = self.create_file("source_dir/test/huge", 700000)
+        self.hash["source_dir/test/huge2"]  = self.create_file("source_dir/test/huge2", 800000)
 
         self.consoleLogger = logging.StreamHandler()
         self.consoleLogger.setLevel(logging.DEBUG)
@@ -119,6 +121,41 @@ class DeltaTarTest(BaseTest):
             elif began_list:
                 crc = binascii.crc32(l, crc) & 0xffffffff
 
+    def test_create_multivol(self):
+        '''
+        Creates a full backup without any filtering with multiple volumes.
+        '''
+        deltatar = DeltaTar(mode=self.MODE, password=self.PASSWORD,
+                            logger=self.consoleLogger)
+
+        # create first backup
+        deltatar.create_full_backup(
+            source_path="source_dir",
+            backup_path="backup_dir",
+            max_volume_size=1)
+
+        assert os.path.exists("backup_dir")
+        assert os.path.exists(os.path.join("backup_dir",
+            deltatar.volume_name_func("backup_dir", True, 0)))
+        assert os.path.exists(os.path.join("backup_dir",
+            deltatar.volume_name_func("backup_dir", True, 1)))
+        assert os.path.exists(os.path.join("backup_dir",
+            deltatar.volume_name_func("backup_dir", True, 2)))
+
+        shutil.rmtree("source_dir")
+
+        tar_filename = deltatar.volume_name_func('backup_dir', True, 0)
+        tar_path = os.path.join("backup_dir", tar_filename)
+
+        # this should automatically restore all volumes
+        deltatar.restore_backup(target_path="source_dir",
+                                backup_tar_path=tar_path)
+
+        for key, value in self.hash.iteritems():
+            assert os.path.exists(key)
+            if value:
+                assert value == self.md5sum(key)
+