Add app.pools collection and related functions
Collection to access storage pools and methods to create/remove pools.
This commit is contained in:
		
							parent
							
								
									64d2f13212
								
							
						
					
					
						commit
						e7d89bcc0d
					
				@ -31,6 +31,7 @@ import qubesmgmt.vm
 | 
			
		||||
import qubesmgmt.label
 | 
			
		||||
import qubesmgmt.exc
 | 
			
		||||
import qubesmgmt.utils
 | 
			
		||||
import qubesmgmt.storage
 | 
			
		||||
 | 
			
		||||
QUBESD_SOCK = '/var/run/qubesd.sock'
 | 
			
		||||
BUF_SIZE = 4096
 | 
			
		||||
@ -109,12 +110,67 @@ class QubesBase(qubesmgmt.base.PropertyHolder):
 | 
			
		||||
    domains = None
 | 
			
		||||
    #: labels collection
 | 
			
		||||
    labels = None
 | 
			
		||||
    #: storage pools
 | 
			
		||||
    pools = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super(QubesBase, self).__init__(self, 'mgmt.property.', 'dom0')
 | 
			
		||||
        self.domains = VMCollection(self)
 | 
			
		||||
        self.labels = qubesmgmt.base.WrapperObjectsCollection(
 | 
			
		||||
            self, 'mgmt.label.List', qubesmgmt.label.Label)
 | 
			
		||||
        self.pools = qubesmgmt.base.WrapperObjectsCollection(
 | 
			
		||||
            self, 'mgmt.pool.List', qubesmgmt.storage.Pool)
 | 
			
		||||
        #: cache for available storage pool drivers and options to create them
 | 
			
		||||
        self._pool_drivers = None
 | 
			
		||||
 | 
			
		||||
    def _refresh_pool_drivers(self):
 | 
			
		||||
        '''
 | 
			
		||||
        Refresh cached storage pool drivers and their parameters.
 | 
			
		||||
 | 
			
		||||
        :return: None
 | 
			
		||||
        '''
 | 
			
		||||
        if self._pool_drivers is None:
 | 
			
		||||
            pool_drivers_data = self.qubesd_call(
 | 
			
		||||
                'dom0', 'mgmt.pool.ListDrivers', None, None)
 | 
			
		||||
            assert pool_drivers_data.endswith(b'\n')
 | 
			
		||||
            pool_drivers = {}
 | 
			
		||||
            for driver_line in pool_drivers_data.decode('ascii').splitlines():
 | 
			
		||||
                if not driver_line:
 | 
			
		||||
                    continue
 | 
			
		||||
                driver_name, driver_options = driver_line.split(' ', 1)
 | 
			
		||||
                pool_drivers[driver_name] = driver_options.split(' ')
 | 
			
		||||
            self._pool_drivers = pool_drivers
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def pool_drivers(self):
 | 
			
		||||
        ''' Available storage pool drivers '''
 | 
			
		||||
        self._refresh_pool_drivers()
 | 
			
		||||
        return self._pool_drivers.keys()
 | 
			
		||||
 | 
			
		||||
    def pool_driver_parameters(self, driver):
 | 
			
		||||
        ''' Parameters to initialize storage pool using given driver '''
 | 
			
		||||
        self._refresh_pool_drivers()
 | 
			
		||||
        return self._pool_drivers[driver]
 | 
			
		||||
 | 
			
		||||
    def add_pool(self, name, driver, **kwargs):
 | 
			
		||||
        ''' Add a storage pool to config
 | 
			
		||||
 | 
			
		||||
        :param name: name of storage pool to create
 | 
			
		||||
        :param driver: driver to use, see :py:meth:`pool_drivers` for
 | 
			
		||||
        available drivers
 | 
			
		||||
        :param kwargs: configuration parameters for storage pool,
 | 
			
		||||
        see :py:meth:`pool_driver_parameters` for a list
 | 
			
		||||
        '''
 | 
			
		||||
        # sort parameters only to ease testing, not required by API
 | 
			
		||||
        payload = 'name={}\n'.format(name) + \
 | 
			
		||||
                  ''.join('{}={}\n'.format(key, value)
 | 
			
		||||
            for key, value in sorted(kwargs.items()))
 | 
			
		||||
        self.qubesd_call('dom0', 'mgmt.pool.Add', driver,
 | 
			
		||||
            payload.encode('utf-8'))
 | 
			
		||||
 | 
			
		||||
    def remove_pool(self, name):
 | 
			
		||||
        ''' Remove a storage pool '''
 | 
			
		||||
        self.qubesd_call('dom0', 'mgmt.pool.Remove', name, None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QubesLocal(QubesBase):
 | 
			
		||||
 | 
			
		||||
@ -80,6 +80,11 @@ class Volume(object):
 | 
			
		||||
        info = info.decode('ascii')
 | 
			
		||||
        self._info = dict([line.split('=', 1) for line in info.splitlines()])
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        if isinstance(other, Volume):
 | 
			
		||||
            return self.pool == other.pool and self.vid == other.vid
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def pool(self):
 | 
			
		||||
        '''Storage volume pool name.'''
 | 
			
		||||
@ -175,3 +180,59 @@ class Volume(object):
 | 
			
		||||
        if not isinstance(revision, str):
 | 
			
		||||
            raise TypeError('revision must be a str')
 | 
			
		||||
        self._qubesd_call('Revert', revision.encode('ascii'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Pool(object):
 | 
			
		||||
    ''' A Pool is used to manage different kind of volumes (File
 | 
			
		||||
        based/LVM/Btrfs/...).
 | 
			
		||||
    '''
 | 
			
		||||
    def __init__(self, app, name=None):
 | 
			
		||||
        ''' Initialize storage pool wrapper
 | 
			
		||||
 | 
			
		||||
        :param app: Qubes() object
 | 
			
		||||
        :param name: name of the pool
 | 
			
		||||
        '''
 | 
			
		||||
        self.app = app
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self._config = None
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        if isinstance(other, Pool):
 | 
			
		||||
            return self.name == other.name
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    def __lt__(self, other):
 | 
			
		||||
        if isinstance(other, Pool):
 | 
			
		||||
            return self.name < other.name
 | 
			
		||||
        return NotImplemented
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def config(self):
 | 
			
		||||
        ''' Storage pool config '''
 | 
			
		||||
        if self._config is None:
 | 
			
		||||
            pool_info_data = self.app.qubesd_call(
 | 
			
		||||
                'dom0', 'mgmt.pool.Info', self.name, None)
 | 
			
		||||
            pool_info_data = pool_info_data.decode('utf-8')
 | 
			
		||||
            assert pool_info_data.endswith('\n')
 | 
			
		||||
            pool_info_data = pool_info_data[:-1]
 | 
			
		||||
            self._config = dict(
 | 
			
		||||
                l.split('=', 1) for l in pool_info_data.splitlines())
 | 
			
		||||
        return self._config
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def driver(self):
 | 
			
		||||
        ''' Storage pool driver '''
 | 
			
		||||
        return self.config['driver']
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def volumes(self):
 | 
			
		||||
        ''' Volumes managed by this pool '''
 | 
			
		||||
        volumes_data = self.app.qubesd_call(
 | 
			
		||||
            'dom0', 'mgmt.pool.volume.List', self.name, None)
 | 
			
		||||
        assert volumes_data.endswith(b'\n')
 | 
			
		||||
        volumes_data = volumes_data[:-1].decode('ascii')
 | 
			
		||||
        for vid in volumes_data.splitlines():
 | 
			
		||||
            yield Volume(self.app, self.name, vid)
 | 
			
		||||
 | 
			
		||||
@ -232,3 +232,78 @@ class TestPoolVolume(TestVMVolume):
 | 
			
		||||
            b'some-id snapid1')] = b'0\x00'
 | 
			
		||||
        self.vol.revert('snapid1')
 | 
			
		||||
        self.assertAllCalled()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestPool(qubesmgmt.tests.QubesTestCase):
 | 
			
		||||
    def test_000_list(self):
 | 
			
		||||
        self.app.expected_calls[('dom0', 'mgmt.pool.List', None, None)] = \
 | 
			
		||||
            b'0\x00file\nlvm\n'
 | 
			
		||||
        seen = set()
 | 
			
		||||
        for pool in self.app.pools:
 | 
			
		||||
            self.assertIsInstance(pool, qubesmgmt.storage.Pool)
 | 
			
		||||
            self.assertIn(pool.name, ('file', 'lvm'))
 | 
			
		||||
            self.assertNotIn(pool.name, seen)
 | 
			
		||||
            seen.add(pool.name)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(seen, set(['file', 'lvm']))
 | 
			
		||||
        self.assertAllCalled()
 | 
			
		||||
 | 
			
		||||
    def test_010_config(self):
 | 
			
		||||
        self.app.expected_calls[('dom0', 'mgmt.pool.List', None, None)] = \
 | 
			
		||||
            b'0\x00file\nlvm\n'
 | 
			
		||||
        self.app.expected_calls[('dom0', 'mgmt.pool.Info', 'file', None)] = \
 | 
			
		||||
            b'0\x00driver=file\n' \
 | 
			
		||||
            b'dir_path=/var/lib/qubes\n' \
 | 
			
		||||
            b'name=file\n' \
 | 
			
		||||
            b'revisions_to_keep=3\n'
 | 
			
		||||
        pool = self.app.pools['file']
 | 
			
		||||
        self.assertEqual(pool.config, {
 | 
			
		||||
            'driver': 'file',
 | 
			
		||||
            'dir_path': '/var/lib/qubes',
 | 
			
		||||
            'name': 'file',
 | 
			
		||||
            'revisions_to_keep': '3',
 | 
			
		||||
        })
 | 
			
		||||
        self.assertAllCalled()
 | 
			
		||||
 | 
			
		||||
    def test_020_volumes(self):
 | 
			
		||||
        self.app.expected_calls[('dom0', 'mgmt.pool.List', None, None)] = \
 | 
			
		||||
            b'0\x00file\nlvm\n'
 | 
			
		||||
        self.app.expected_calls[
 | 
			
		||||
            ('dom0', 'mgmt.pool.volume.List', 'file', None)] = \
 | 
			
		||||
            b'0\x00vol1\n' \
 | 
			
		||||
            b'vol2\n'
 | 
			
		||||
        pool = self.app.pools['file']
 | 
			
		||||
        seen = set()
 | 
			
		||||
        for volume in pool.volumes:
 | 
			
		||||
            self.assertIsInstance(volume, qubesmgmt.storage.Volume)
 | 
			
		||||
            self.assertIn(volume.vid, ('vol1', 'vol2'))
 | 
			
		||||
            self.assertEqual(volume.pool, 'file')
 | 
			
		||||
            self.assertNotIn(volume.vid, seen)
 | 
			
		||||
            seen.add(volume.vid)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(seen, set(['vol1', 'vol2']))
 | 
			
		||||
        self.assertAllCalled()
 | 
			
		||||
 | 
			
		||||
    def test_030_pool_drivers(self):
 | 
			
		||||
        self.app.expected_calls[
 | 
			
		||||
            ('dom0', 'mgmt.pool.ListDrivers', None, None)] = \
 | 
			
		||||
            b'0\x00file dir_path revisions_to_keep\n' \
 | 
			
		||||
            b'lvm volume_group thin_pool revisions_to_keep\n'
 | 
			
		||||
        self.assertEqual(set(self.app.pool_drivers), set(['file', 'lvm']))
 | 
			
		||||
        self.assertEqual(set(self.app.pool_driver_parameters('file')),
 | 
			
		||||
            set(['dir_path', 'revisions_to_keep']))
 | 
			
		||||
        self.assertAllCalled()
 | 
			
		||||
 | 
			
		||||
    def test_040_add(self):
 | 
			
		||||
        self.app.expected_calls[
 | 
			
		||||
            ('dom0', 'mgmt.pool.Add', 'some-driver',
 | 
			
		||||
            b'name=test-pool\nparam1=value1\nparam2=123\n')] = b'0\x00'
 | 
			
		||||
        self.app.add_pool('test-pool', driver='some-driver',
 | 
			
		||||
            param1='value1', param2=123)
 | 
			
		||||
        self.assertAllCalled()
 | 
			
		||||
 | 
			
		||||
    def test_050_remove(self):
 | 
			
		||||
        self.app.expected_calls[
 | 
			
		||||
            ('dom0', 'mgmt.pool.Remove', 'test-pool', None)] = b'0\x00'
 | 
			
		||||
        self.app.remove_pool('test-pool')
 | 
			
		||||
        self.assertAllCalled()
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user