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.label
 | 
				
			||||||
import qubesmgmt.exc
 | 
					import qubesmgmt.exc
 | 
				
			||||||
import qubesmgmt.utils
 | 
					import qubesmgmt.utils
 | 
				
			||||||
 | 
					import qubesmgmt.storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QUBESD_SOCK = '/var/run/qubesd.sock'
 | 
					QUBESD_SOCK = '/var/run/qubesd.sock'
 | 
				
			||||||
BUF_SIZE = 4096
 | 
					BUF_SIZE = 4096
 | 
				
			||||||
@ -109,12 +110,67 @@ class QubesBase(qubesmgmt.base.PropertyHolder):
 | 
				
			|||||||
    domains = None
 | 
					    domains = None
 | 
				
			||||||
    #: labels collection
 | 
					    #: labels collection
 | 
				
			||||||
    labels = None
 | 
					    labels = None
 | 
				
			||||||
 | 
					    #: storage pools
 | 
				
			||||||
 | 
					    pools = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        super(QubesBase, self).__init__(self, 'mgmt.property.', 'dom0')
 | 
					        super(QubesBase, self).__init__(self, 'mgmt.property.', 'dom0')
 | 
				
			||||||
        self.domains = VMCollection(self)
 | 
					        self.domains = VMCollection(self)
 | 
				
			||||||
        self.labels = qubesmgmt.base.WrapperObjectsCollection(
 | 
					        self.labels = qubesmgmt.base.WrapperObjectsCollection(
 | 
				
			||||||
            self, 'mgmt.label.List', qubesmgmt.label.Label)
 | 
					            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):
 | 
					class QubesLocal(QubesBase):
 | 
				
			||||||
 | 
				
			|||||||
@ -80,6 +80,11 @@ class Volume(object):
 | 
				
			|||||||
        info = info.decode('ascii')
 | 
					        info = info.decode('ascii')
 | 
				
			||||||
        self._info = dict([line.split('=', 1) for line in info.splitlines()])
 | 
					        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
 | 
					    @property
 | 
				
			||||||
    def pool(self):
 | 
					    def pool(self):
 | 
				
			||||||
        '''Storage volume pool name.'''
 | 
					        '''Storage volume pool name.'''
 | 
				
			||||||
@ -175,3 +180,59 @@ class Volume(object):
 | 
				
			|||||||
        if not isinstance(revision, str):
 | 
					        if not isinstance(revision, str):
 | 
				
			||||||
            raise TypeError('revision must be a str')
 | 
					            raise TypeError('revision must be a str')
 | 
				
			||||||
        self._qubesd_call('Revert', revision.encode('ascii'))
 | 
					        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'
 | 
					            b'some-id snapid1')] = b'0\x00'
 | 
				
			||||||
        self.vol.revert('snapid1')
 | 
					        self.vol.revert('snapid1')
 | 
				
			||||||
        self.assertAllCalled()
 | 
					        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