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