Add app.pools collection and related functions

Collection to access storage pools and methods to create/remove pools.
This commit is contained in:
Marek Marczykowski-Górecki 2017-03-12 21:21:16 +01:00
parent 64d2f13212
commit e7d89bcc0d
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
3 changed files with 192 additions and 0 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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()