diff --git a/qubes/app.py b/qubes/app.py index 30570e6e..805a64f4 100644 --- a/qubes/app.py +++ b/qubes/app.py @@ -911,6 +911,7 @@ class Qubes(qubes.PropertyHolder): pool = self._get_pool(**kwargs) pool.setup() self.pools[name] = pool + return pool def remove_pool(self, name): """ Remove a storage pool from config file. """ diff --git a/qubes/storage/__init__.py b/qubes/storage/__init__.py index 5f5bbda1..a8aa7ae2 100644 --- a/qubes/storage/__init__.py +++ b/qubes/storage/__init__.py @@ -42,6 +42,7 @@ STORAGE_ENTRY_POINT = 'qubes.storage' class StoragePoolException(qubes.exc.QubesException): + ''' A general storage exception ''' pass @@ -98,6 +99,9 @@ class Volume(object): def __hash__(self): return hash('%s:%s %s' % (self.pool, self.vid, self.volume_type)) + def __str__(self): + return "{!s}:{!s}".format(self.pool, self.vid) + class Storage(object): ''' Class for handling VM virtual disks. @@ -198,7 +202,7 @@ class Storage(object): self.get_pool(volume).resize(volume, size) def create(self, source_template=None): - + ''' Creates volumes on disk ''' if source_template is None and hasattr(self.vm, 'template'): source_template = self.vm.template @@ -213,6 +217,7 @@ class Storage(object): os.umask(old_umask) def clone(self, src_vm): + ''' Clone volumes from the specified vm ''' self.vm.log.info('Creating directory: {0}'.format(self.vm.dir_path)) if not os.path.exists(self.vm.dir_path): self.log.info('Creating directory: {0}'.format(self.vm.dir_path)) @@ -288,6 +293,7 @@ class Storage(object): return self.pools[volume.name] def commit_template_changes(self): + ''' Makes changes to an 'origin' volume persistent ''' for volume in self.vm.volumes.values(): if volume.volume_type == 'origin': self.get_pool(volume).commit_template_changes(volume) @@ -318,15 +324,25 @@ class Pool(object): private_img_size = qubes.config.defaults['private_img_size'] root_img_size = qubes.config.defaults['root_img_size'] + def __eq__(self, other): + return self.name == other.name + + + def __neq__(self, other): + return not self.__eq__(other) + def __init__(self, name, **kwargs): super(Pool, self).__init__(**kwargs) self.name = name kwargs['name'] = self.name + def __str__(self): + return self.name + def __xml__(self): return lxml.etree.Element('pool', **self.config) - def create(self, volume, source_volume): + def create(self, volume, source_volume=None): ''' Create the given volume on disk or copy from provided `source_volume`. ''' @@ -351,6 +367,9 @@ class Pool(object): self.name) def destroy(self): + ''' Called when removing the pool. Use this for implementation specific + clean up. + ''' raise NotImplementedError("Pool %s has destroy() not implemented" % self.name) @@ -374,6 +393,9 @@ class Pool(object): self.name) def setup(self): + ''' Called when adding a pool to the system. Use this for implementation + specific set up. + ''' raise NotImplementedError("Pool %s has setup() not implemented" % self.name) diff --git a/qubes/storage/file.py b/qubes/storage/file.py index a5c33777..d66be0fb 100644 --- a/qubes/storage/file.py +++ b/qubes/storage/file.py @@ -91,7 +91,7 @@ class FilePool(Pool): ''' Expands volume, throws :py:class:`qubst.storage.StoragePoolException` if given size is less than current_size - ''' + ''' # pylint: disable=no-self-use _type = volume.volume_type if _type not in ['origin', 'read-write', 'volatile']: raise StoragePoolException('Can not resize a %s volume %s' % @@ -112,7 +112,18 @@ class FilePool(Pool): with open(path, 'a+b') as fd: fd.truncate(size) - self._resize_loop_device(path) + p = subprocess.Popen( + ['sudo', 'losetup', '--associated', path], + stdout=subprocess.PIPE) + result = p.communicate() + + m = re.match(r'^(/dev/loop\d+):\s', result[0]) + if m is not None: + loop_dev = m.group(1) + + # resize loop device + subprocess.check_call(['sudo', 'losetup', '--set-capacity', loop_dev + ]) def remove(self, volume): if volume.volume_type in ['read-write', 'volatile']: @@ -133,23 +144,6 @@ class FilePool(Pool): return volume - @staticmethod - def _resize_loop_device(path): - ''' Sets the loop device capacity ''' - # find loop device if any - p = subprocess.Popen( - ['sudo', 'losetup', '--associated', path], - stdout=subprocess.PIPE) - result = p.communicate() - - m = re.match(r'^(/dev/loop\d+):\s', result[0]) - if m is not None: - loop_dev = m.group(1) - - # resize loop device - subprocess.check_call(['sudo', 'losetup', '--set-capacity', - loop_dev]) - def commit_template_changes(self, volume): if volume.volume_type != 'origin': return volume @@ -175,7 +169,7 @@ class FilePool(Pool): def start(self, volume): if volume.volume_type == 'volatile': - self._reset_volume(volume) + _reset_volume(volume) if volume.volume_type in ['origin', 'snapshot']: _check_path(volume.path_origin) _check_path(volume.path_cow) @@ -187,18 +181,6 @@ class FilePool(Pool): def stop(self, volume): pass - @staticmethod - def _reset_volume(volume): - ''' Remove and recreate a volatile volume ''' - assert volume.volume_type == 'volatile', "Not a volatile volume" - assert volume.size - - _remove_if_exists(volume.path) - - with open(volume.path, "w") as f_volatile: - f_volatile.truncate(volume.size) - return volume - def target_dir(self, vm): """ Returns the path to vmdir depending on the type of the VM. @@ -259,6 +241,7 @@ class FilePool(Pool): expected_origin_type origin_pool = vm.app.get_pool(origin_vm.volume_config[name]['pool']) + assert isinstance(origin_pool, FilePool), 'Origin volume not a file volume' @@ -558,7 +541,7 @@ def copy_file(source, destination): def _remove_if_exists(path): - ''' Removes a path if it exist, silently succeeds if file does not exist ''' + ''' Removes a file if it exist, silently succeeds if file does not exist ''' if os.path.exists(path): os.remove(path) @@ -567,3 +550,16 @@ def _check_path(path): ''' Raise an StoragePoolException if ``path`` does not exist''' if not os.path.exists(path): raise StoragePoolException('Missing image file: %s' % path) + + +def _reset_volume(volume): + ''' Remove and recreate a volatile volume ''' + assert volume.volume_type == 'volatile', "Not a volatile volume" + + assert volume.size + + _remove_if_exists(volume.path) + + with open(volume.path, "w") as f_volatile: + f_volatile.truncate(volume.size) + return volume diff --git a/qubes/storage/kernels.py b/qubes/storage/kernels.py index afd323e8..cb7557ec 100644 --- a/qubes/storage/kernels.py +++ b/qubes/storage/kernels.py @@ -63,7 +63,7 @@ class LinuxKernel(Pool): def clone(self, source, target): return target - def create(self, volume, source_volume): + def create(self, volume, source_volume=None): return volume def commit_template_changes(self, volume):