Make add_pool/remove_pool coroutines, allow Pool.{setup,destroy} as coroutines

Pool setup/destroy may be a time consuming operation, allow them to be
asynchronous. Fortunately add_pool and remove_pool are used only through
Admin API, so the change does not require modification of other
components.
This commit is contained in:
Marek Marczykowski-Górecki 2019-02-18 21:13:24 +01:00
parent adbc618545
commit d8b6d3efde
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
12 changed files with 83 additions and 52 deletions

View File

@ -666,7 +666,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
self.fire_event_for_permission(name=pool_name,
pool_config=pool_config)
self.app.add_pool(name=pool_name, driver=self.arg, **pool_config)
yield from self.app.add_pool(name=pool_name, driver=self.arg,
**pool_config)
self.app.save()
@qubes.api.method('admin.pool.Remove', no_payload=True,
@ -678,7 +679,7 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
self.fire_event_for_permission()
self.app.remove_pool(self.arg)
yield from self.app.remove_pool(self.arg)
self.app.save()
@qubes.api.method('admin.pool.Set.revisions_to_keep',

View File

@ -34,6 +34,7 @@ import time
import traceback
import uuid
import asyncio
import jinja2
import libvirt
import lxml.etree
@ -1207,6 +1208,7 @@ class Qubes(qubes.PropertyHolder):
for pool in self.pools.values():
pool.setup()
@asyncio.coroutine
def add_pool(self, name, **kwargs):
""" Add a storage pool to config."""
@ -1216,16 +1218,21 @@ class Qubes(qubes.PropertyHolder):
kwargs['name'] = name
pool = self._get_pool(**kwargs)
pool.setup()
ret = pool.setup()
if asyncio.iscoroutine(ret):
yield from ret
self.pools[name] = pool
return pool
@asyncio.coroutine
def remove_pool(self, name):
""" Remove a storage pool from config file. """
try:
pool = self.pools[name]
del self.pools[name]
pool.destroy()
ret = pool.destroy()
if asyncio.iscoroutine(ret):
yield from ret
except KeyError:
return

View File

@ -782,6 +782,8 @@ class Pool:
def destroy(self):
''' Called when removing the pool. Use this for implementation specific
clean up.
This can be implemented as a coroutine.
'''
raise self._not_implemented("destroy")
@ -793,6 +795,8 @@ class Pool:
def setup(self):
''' Called when adding a pool to the system. Use this for implementation
specific set up.
This can be implemented as a coroutine.
'''
raise self._not_implemented("setup")

View File

@ -786,7 +786,8 @@ class SystemTestCase(QubesTestCase):
format(DEFAULT_LVM_POOL))
self.pool = self._find_pool(volume_group, thin_pool)
if not self.pool:
self.pool = self.app.add_pool(**POOL_CONF)
self.pool = self.loop.run_until_complete(
self.app.add_pool(**POOL_CONF))
self.created_pool = True
def _remove_vm_qubes(self, vm):

View File

@ -68,7 +68,8 @@ class AdminAPITestCase(qubes.tests.QubesTestCase):
app.default_template = 'test-template'
with qubes.tests.substitute_entry_points('qubes.storage',
'qubes.tests.storage'):
app.add_pool('test', driver='test')
self.loop.run_until_complete(
app.add_pool('test', driver='test'))
app.default_pool = 'varlibqubes'
app.save = unittest.mock.Mock()
self.vm = app.add_new_vm('AppVM', label='red', name='test-vm1',
@ -636,7 +637,7 @@ class TC_00_VMs(AdminAPITestCase):
'driver2': ['param3', 'param4']
}[driver]
self.app.add_pool = unittest.mock.Mock()
add_pool_mock, self.app.add_pool = self.coroutine_mock()
value = self.call_mgmt_func(b'admin.pool.Add', b'dom0', b'driver1',
b'name=test-pool\nparam1=some-value\n')
@ -644,7 +645,7 @@ class TC_00_VMs(AdminAPITestCase):
self.assertEqual(mock_drivers.mock_calls, [unittest.mock.call()])
self.assertEqual(mock_parameters.mock_calls,
[unittest.mock.call('driver1')])
self.assertEqual(self.app.add_pool.mock_calls,
self.assertEqual(add_pool_mock.mock_calls,
[unittest.mock.call(name='test-pool', driver='driver1',
param1='some-value')])
self.assertTrue(self.app.save.called)
@ -664,14 +665,14 @@ class TC_00_VMs(AdminAPITestCase):
'driver2': ['param3', 'param4']
}[driver]
self.app.add_pool = unittest.mock.Mock()
add_pool_mock, self.app.add_pool = self.coroutine_mock()
with self.assertRaises(qubes.api.PermissionDenied):
self.call_mgmt_func(b'admin.pool.Add', b'dom0',
b'no-such-driver', b'name=test-pool\nparam1=some-value\n')
self.assertEqual(mock_drivers.mock_calls, [unittest.mock.call()])
self.assertEqual(mock_parameters.mock_calls, [])
self.assertEqual(self.app.add_pool.mock_calls, [])
self.assertEqual(add_pool_mock.mock_calls, [])
self.assertFalse(self.app.save.called)
@ -690,7 +691,7 @@ class TC_00_VMs(AdminAPITestCase):
'driver2': ['param3', 'param4']
}[driver]
self.app.add_pool = unittest.mock.Mock()
add_pool_mock, self.app.add_pool = self.coroutine_mock()
with self.assertRaises(qubes.api.PermissionDenied):
self.call_mgmt_func(b'admin.pool.Add', b'dom0',
@ -698,7 +699,7 @@ class TC_00_VMs(AdminAPITestCase):
self.assertEqual(mock_drivers.mock_calls, [unittest.mock.call()])
self.assertEqual(mock_parameters.mock_calls,
[unittest.mock.call('driver1')])
self.assertEqual(self.app.add_pool.mock_calls, [])
self.assertEqual(add_pool_mock.mock_calls, [])
self.assertFalse(self.app.save.called)
@unittest.mock.patch('qubes.storage.pool_drivers')
@ -716,14 +717,14 @@ class TC_00_VMs(AdminAPITestCase):
'driver2': ['param3', 'param4']
}[driver]
self.app.add_pool = unittest.mock.Mock()
add_pool_mock, self.app.add_pool = self.coroutine_mock()
with self.assertRaises(qubes.api.PermissionDenied):
self.call_mgmt_func(b'admin.pool.Add', b'dom0',
b'driver1', b'param1=value\nparam2=some-value\n')
self.assertEqual(mock_drivers.mock_calls, [unittest.mock.call()])
self.assertEqual(mock_parameters.mock_calls, [])
self.assertEqual(self.app.add_pool.mock_calls, [])
self.assertEqual(add_pool_mock.mock_calls, [])
self.assertFalse(self.app.save.called)
@unittest.mock.patch('qubes.storage.pool_drivers')
@ -741,14 +742,14 @@ class TC_00_VMs(AdminAPITestCase):
'driver2': ['param3', 'param4']
}[driver]
self.app.add_pool = unittest.mock.Mock()
add_pool_mock, self.app.add_pool = self.coroutine_mock()
with self.assertRaises(qubes.api.PermissionDenied):
self.call_mgmt_func(b'admin.pool.Add', b'dom0',
b'driver1', b'name=file\nparam1=value\nparam2=some-value\n')
self.assertEqual(mock_drivers.mock_calls, [unittest.mock.call()])
self.assertEqual(mock_parameters.mock_calls, [])
self.assertEqual(self.app.add_pool.mock_calls, [])
self.assertEqual(add_pool_mock.mock_calls, [])
self.assertFalse(self.app.save.called)
@unittest.mock.patch('qubes.storage.pool_drivers')
@ -767,14 +768,14 @@ class TC_00_VMs(AdminAPITestCase):
'driver2': ['param3', 'param4']
}[driver]
self.app.add_pool = unittest.mock.Mock()
add_pool_mock, self.app.add_pool = self.coroutine_mock()
with self.assertRaises(qubes.api.PermissionDenied):
self.call_mgmt_func(b'admin.pool.Add', b'dom0',
b'driver1', b'name=test-pool\nparam 1=value\n_param2\n')
self.assertEqual(mock_drivers.mock_calls, [unittest.mock.call()])
self.assertEqual(mock_parameters.mock_calls, [])
self.assertEqual(self.app.add_pool.mock_calls, [])
self.assertEqual(add_pool_mock.mock_calls, [])
self.assertFalse(self.app.save.called)
def test_170_pool_remove(self):
@ -783,10 +784,10 @@ class TC_00_VMs(AdminAPITestCase):
'lvm': unittest.mock.Mock(),
'test-pool': unittest.mock.Mock(),
}
self.app.remove_pool = unittest.mock.Mock()
remove_pool_mock, self.app.remove_pool = self.coroutine_mock()
value = self.call_mgmt_func(b'admin.pool.Remove', b'dom0', b'test-pool')
self.assertIsNone(value)
self.assertEqual(self.app.remove_pool.mock_calls,
self.assertEqual(remove_pool_mock.mock_calls,
[unittest.mock.call('test-pool')])
self.assertTrue(self.app.save.called)
@ -796,11 +797,11 @@ class TC_00_VMs(AdminAPITestCase):
'lvm': unittest.mock.Mock(),
'test-pool': unittest.mock.Mock(),
}
self.app.remove_pool = unittest.mock.Mock()
remove_pool_mock, self.app.remove_pool = self.coroutine_mock()
with self.assertRaises(qubes.api.PermissionDenied):
self.call_mgmt_func(b'admin.pool.Remove', b'dom0',
b'no-such-pool')
self.assertEqual(self.app.remove_pool.mock_calls, [])
self.assertEqual(remove_pool_mock.mock_calls, [])
self.assertFalse(self.app.save.called)
def test_180_label_list(self):
@ -1170,6 +1171,14 @@ class TC_00_VMs(AdminAPITestCase):
def dummy_coro(self, *args, **kwargs):
pass
def coroutine_mock(self):
func_mock = unittest.mock.Mock()
@asyncio.coroutine
def coroutine_mock(*args, **kwargs):
return func_mock(*args, **kwargs)
return func_mock, coroutine_mock
@unittest.mock.patch('qubes.storage.Storage.create')
def test_330_vm_create_standalone(self, storage_mock):
storage_mock.side_effect = self.dummy_coro

View File

@ -526,8 +526,9 @@ class TC_00_Backup(BackupTestsMixin, qubes.tests.SystemTestCase):
qubes.tests.storage_lvm.DEFAULT_LVM_POOL.split('/', 1)
self.pool = self._find_pool(volume_group, thin_pool)
if not self.pool:
self.pool = self.app.add_pool(
**qubes.tests.storage_lvm.POOL_CONF)
self.pool = self.loop.run_until_complete(
self.app.add_pool(
**qubes.tests.storage_lvm.POOL_CONF))
self.created_pool = True
vms = self.create_backup_vms(pool=self.pool)
try:
@ -546,8 +547,9 @@ class TC_00_Backup(BackupTestsMixin, qubes.tests.SystemTestCase):
qubes.tests.storage_lvm.DEFAULT_LVM_POOL.split('/', 1)
self.pool = self._find_pool(volume_group, thin_pool)
if not self.pool:
self.pool = self.app.add_pool(
**qubes.tests.storage_lvm.POOL_CONF)
self.pool = self.loop.run_until_complete(
self.app.add_pool(
**qubes.tests.storage_lvm.POOL_CONF))
self.created_pool = True
vms = self.create_backup_vms()
try:

View File

@ -302,8 +302,8 @@ class TC_00_Basic(qubes.tests.SystemTestCase):
pool_path = tempfile.mkdtemp(
prefix='qubes-pool-', dir='/var/tmp')
self.addCleanup(shutil.rmtree, pool_path)
pool = self.app.add_pool('test-filep', dir_path=pool_path,
driver='file')
pool = self.loop.run_until_complete(
self.app.add_pool('test-filep', dir_path=pool_path, driver='file'))
self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM,
name=vmname, template=self.app.default_template,

View File

@ -312,22 +312,23 @@ class StorageTestMixin(object):
class StorageFile(StorageTestMixin, qubes.tests.SystemTestCase):
def init_pool(self):
self.dir_path = '/var/tmp/test-pool'
self.pool = self.app.add_pool(dir_path=self.dir_path,
name='test-pool', driver='file')
self.pool = self.loop.run_until_complete(
self.app.add_pool(dir_path=self.dir_path,
name='test-pool', driver='file'))
os.makedirs(os.path.join(self.dir_path, 'appvms', self.vm1.name),
exist_ok=True)
os.makedirs(os.path.join(self.dir_path, 'appvms', self.vm2.name),
exist_ok=True)
def tearDown(self):
self.app.remove_pool('test-pool')
self.loop.run_until_complete(self.app.remove_pool('test-pool'))
shutil.rmtree(self.dir_path)
super(StorageFile, self).tearDown()
class StorageReflinkMixin(StorageTestMixin):
def tearDown(self):
self.app.remove_pool(self.pool.name)
self.loop.run_until_complete(self.app.remove_pool(self.pool.name))
super().tearDown()
def init_pool(self, fs_type, **kwargs):
@ -335,8 +336,9 @@ class StorageReflinkMixin(StorageTestMixin):
dir_path = os.path.join('/var/tmp', name)
qubes.tests.storage_reflink.mkdir_fs(dir_path, fs_type,
cleanup_via=self.addCleanup)
self.pool = self.app.add_pool(name=name, dir_path=dir_path,
driver='file-reflink', **kwargs)
self.pool = self.loop.run_until_complete(
self.app.add_pool(name=name, dir_path=dir_path,
driver='file-reflink', **kwargs))
class StorageReflinkOnBtrfs(StorageReflinkMixin, qubes.tests.SystemTestCase):
def init_pool(self):
@ -355,13 +357,14 @@ class StorageLVM(StorageTestMixin, qubes.tests.SystemTestCase):
qubes.tests.storage_lvm.DEFAULT_LVM_POOL.split('/', 1)
self.pool = self._find_pool(volume_group, thin_pool)
if not self.pool:
self.pool = self.app.add_pool(**qubes.tests.storage_lvm.POOL_CONF)
self.pool = self.loop.run_until_complete(
self.app.add_pool(**qubes.tests.storage_lvm.POOL_CONF))
self.created_pool = True
def tearDown(self):
''' Remove the default lvm pool if it was created only for this test '''
if self.created_pool:
self.app.remove_pool(self.pool.name)
self.loop.run_until_complete(self.app.remove_pool(self.pool.name))
super(StorageLVM, self).tearDown()
def _find_pool(self, volume_group, thin_pool):

View File

@ -123,15 +123,16 @@ class TC_00_Pool(SystemTestCase):
pool_name = 'asdjhrp89132'
# make sure it's really does not exist
self.app.remove_pool(pool_name)
self.loop.run_until_complete(self.app.remove_pool(pool_name))
self.assertFalse(self.assertPoolExists(pool_name))
self.app.add_pool(name=pool_name,
self.loop.run_until_complete(
self.app.add_pool(name=pool_name,
driver='file',
dir_path='/tmp/asdjhrp89132')
dir_path='/tmp/asdjhrp89132'))
self.assertTrue(self.assertPoolExists(pool_name))
self.app.remove_pool(pool_name)
self.loop.run_until_complete(self.app.remove_pool(pool_name))
self.assertFalse(self.assertPoolExists(pool_name))
def assertPoolExists(self, pool):

View File

@ -114,13 +114,13 @@ class TC_01_FileVolumes(qubes.tests.QubesTestCase):
""" Add a test file based storage pool """
super(TC_01_FileVolumes, self).setUp()
self.app = TestApp()
self.app.add_pool(**self.POOL_CONF)
self.loop.run_until_complete(self.app.add_pool(**self.POOL_CONF))
self.app.default_pool = self.app.get_pool(self.POOL_NAME)
self.app.create_dummy_template()
def tearDown(self):
""" Remove the file based storage pool after testing """
self.app.remove_pool("test-pool")
self.loop.run_until_complete(self.app.remove_pool("test-pool"))
self.app.cleanup()
self.app.close()
del self.app
@ -388,12 +388,12 @@ class TC_03_FilePool(qubes.tests.QubesTestCase):
self.base_dir_patch2.start()
self.base_dir_patch3.start()
self.app = TestApp()
self.app.add_pool(**self.POOL_CONFIG)
self.loop.run_until_complete(self.app.add_pool(**self.POOL_CONFIG))
self.app.create_dummy_template()
def tearDown(self):
""" Remove the file based storage pool after testing """
self.app.remove_pool("test-pool")
self.loop.run_until_complete(self.app.remove_pool("test-pool"))
self.app.cleanup()
self.app.close()
del self.app
@ -419,7 +419,8 @@ class TC_03_FilePool(qubes.tests.QubesTestCase):
self.assertFalse(os.path.exists(pool_dir))
self.app.add_pool(name=pool_name, dir_path=pool_dir, driver='file')
self.loop.run_until_complete(
self.app.add_pool(name=pool_name, dir_path=pool_dir, driver='file'))
self.assertTrue(os.path.exists(pool_dir))
self.assertTrue(os.path.exists(appvms_dir))

View File

@ -74,13 +74,13 @@ class TC_01_KernelVolumes(qubes.tests.QubesTestCase):
super(TC_01_KernelVolumes, self).setUp()
self.app = TestApp()
self.app.create_dummy_template()
self.app.add_pool(**self.POOL_CONF)
self.loop.run_until_complete(self.app.add_pool(**self.POOL_CONF))
os.makedirs(self.POOL_DIR + '/dummy', exist_ok=True)
open('/tmp/test-pool/dummy/modules.img', 'w').close()
def tearDown(self):
""" Remove the file based storage pool after testing """
self.app.remove_pool("test-pool")
self.loop.run_until_complete(self.app.remove_pool("test-pool"))
self.app.cleanup()
self.app.close()
del self.app
@ -231,11 +231,11 @@ class TC_03_KernelPool(qubes.tests.QubesTestCase):
open(os.path.join(dummy_kernel, 'vmlinuz'), 'w').close()
open(os.path.join(dummy_kernel, 'modules.img'), 'w').close()
open(os.path.join(dummy_kernel, 'initramfs'), 'w').close()
self.app.add_pool(**self.POOL_CONFIG)
self.loop.run_until_complete(self.app.add_pool(**self.POOL_CONFIG))
def tearDown(self):
""" Remove the file based storage pool after testing """
self.app.remove_pool("test-pool")
self.loop.run_until_complete(self.app.remove_pool("test-pool"))
self.app.cleanup()
self.app.close()
del self.app

View File

@ -75,13 +75,14 @@ class ThinPoolBase(qubes.tests.QubesTestCase):
volume_group, thin_pool = DEFAULT_LVM_POOL.split('/', 1)
self.pool = self._find_pool(volume_group, thin_pool)
if not self.pool:
self.pool = self.app.add_pool(**POOL_CONF)
self.pool = self.loop.run_until_complete(
self.app.add_pool(**POOL_CONF))
self.created_pool = True
def tearDown(self):
''' Remove the default lvm pool if it was created only for this test '''
if self.created_pool:
self.app.remove_pool(self.pool.name)
self.loop.run_until_complete(self.app.remove_pool(self.pool.name))
super(ThinPoolBase, self).tearDown()
@ -1052,7 +1053,8 @@ class TC_02_StorageHelpers(ThinPoolBase):
'driver': 'file',
'dir_path': subdir
}
pool2 = self.app.add_pool(**file_pool_config)
pool2 = self.loop.run_until_complete(
self.app.add_pool(**file_pool_config))
pool = qubes.storage.search_pool_containing_dir(
self.app.pools.values(), subdir)
self.assertEqual(pool, pool2)