From b1978abce536d6d8bf9839ce1a0b41e2b82be3a0 Mon Sep 17 00:00:00 2001 From: Bahtiar `kalkin-` Gadimov Date: Sun, 20 Mar 2016 20:29:46 +0100 Subject: [PATCH] Use entry_points for pool driver discovery - Add qubes.storage entry point to setup.py - Removed the old pool driver class loading logic - Reworked pool tests --- etc/storage.conf | 5 +-- qubes/config.py | 2 -- qubes/storage/__init__.py | 66 ++++++++++----------------------------- qubes/tests/storage.py | 62 +++++++++++++++--------------------- setup.py | 12 ++++--- 5 files changed, 52 insertions(+), 95 deletions(-) diff --git a/etc/storage.conf b/etc/storage.conf index e9d067e5..78fd2a3a 100644 --- a/etc/storage.conf +++ b/etc/storage.conf @@ -2,10 +2,11 @@ driver=xen ; the default xen storage ; class = qubes.storage.xen.XenStorage ; class always overwrites the driver ; -; To use our own storage adapter, you need just to specify the module path and +; To use our own pool driver it needs to provide `qubes.storage` entry_point +; see also: https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins ; class name ; [pool-b] -; class = foo.bar.MyStorage +; driver = foo ; ; [test-dummy] ; driver=dummy diff --git a/qubes/config.py b/qubes/config.py index 62af4dbb..a09e2f35 100644 --- a/qubes/config.py +++ b/qubes/config.py @@ -83,8 +83,6 @@ defaults = { 'private_img_size': 2*1024*1024*1024, 'root_img_size': 10*1024*1024*1024, - 'storage_class': 'qubes.storage.xen.XenStorage', - 'pool_drivers': {'xen': 'qubes.storage.xen.XenPool'}, 'pool_config': {'dir_path': '/var/lib/qubes'}, # how long (in sec) to wait for VMs to shutdown, diff --git a/qubes/storage/__init__.py b/qubes/storage/__init__.py index 968d763a..4ddbe34b 100644 --- a/qubes/storage/__init__.py +++ b/qubes/storage/__init__.py @@ -24,23 +24,24 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # +""" Qubes storage system""" + from __future__ import absolute_import import ConfigParser -import importlib import os import os.path -import re import shutil import subprocess -import sys +import pkg_resources import qubes import qubes.exc import qubes.utils BLKSIZE = 512 CONFIG_FILE = '/etc/qubes/storage.conf' +STORAGE_ENTRY_POINT = 'qubes.storage' class StoragePoolException(qubes.exc.QubesException): @@ -78,7 +79,6 @@ class Storage(object): #: Additional drive (currently used only by HVM) self.drive = None - def get_config_params(self): args = {} args['rootdev'] = self.root_dev_config() @@ -90,7 +90,6 @@ class Storage(object): return args - def root_dev_config(self): raise NotImplementedError() @@ -357,42 +356,6 @@ def get_disk_usage(path): return ret -def load(clsname): - '''Given a dotted full module string representation of a class it loads it - - Args: - string (str) i.e. 'qubes.storage.xen.QubesXenVmStorage' - - Returns: - type - - See also: - :func:`qubes.storage.dump` - - :raises ImportError: when storage class specified in config cannot be found - :raises KeyError: when storage class specified in config cannot be found - ''' - - if not isinstance(clsname, basestring): - return clsname - pkg, cls = clsname.strip().rsplit('.', 1) - - # this may raise ImportError or KeyError, that's okay - return importlib.import_module(pkg).__dict__[cls] - - -def dump(o): - """ Returns a string represention of the given object - - Args: - o (object): anything that response to `__module__` and `__class__` - - Given the class :class:`qubes.storage.QubesVmStorage` it returns - 'qubes.storage.QubesVmStorage' as string - """ - return o.__module__ + '.' + o.__class__.__name__ - - def get_pool(name, vm): """ Instantiates the storage for the specified vm """ config = _get_storage_config_parser() @@ -465,16 +428,15 @@ def _get_pool_klass(name, config=None): if not config.has_section(name): raise StoragePoolException('Uknown storage pool ' + name) + elif not config.has_option(name, 'driver'): + raise StoragePoolException('No driver specified for pool ' + name) - if config.has_option(name, 'class'): - klass = load(config.get(name, 'class')) - elif config.has_option(name, 'driver'): - pool_driver = config.get(name, 'driver') - klass = load(qubes.config.defaults['pool_drivers'][pool_driver]) - else: - raise StoragePoolException('Uknown storage pool driver ' + name) - return klass + driver = config.get(name, 'driver') + try: + return qubes.get_entry_point_one(STORAGE_ENTRY_POINT, driver) + except KeyError: + raise StoragePoolException('Driver %s for pool %s' % (driver, name)) class Pool(object): def __init__(self, vm, dir_path): @@ -533,3 +495,9 @@ class Pool(object): """ if not os.path.exists(path): os.mkdir(path) + + +def pool_drivers(): + """ Return a list of EntryPoints names """ + return [ep.name + for ep in pkg_resources.iter_entry_points(STORAGE_ENTRY_POINT)] diff --git a/qubes/tests/storage.py b/qubes/tests/storage.py index 0d9b91b3..8cb9f9fc 100644 --- a/qubes/tests/storage.py +++ b/qubes/tests/storage.py @@ -16,16 +16,16 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import qubes.storage import qubes.log -from qubes.config import defaults -from qubes.storage.xen import XenPool, XenStorage -from qubes.tests import QubesTestCase, SystemTestsMixin +from qubes.storage import StoragePoolException, pool_drivers +from qubes.storage.xen import XenPool +from qubes.tests import QubesTestCase + class TestApp(qubes.tests.TestEmitter): pass + class TestVM(object): def __init__(self, app, qid, name, pool_name, template=None): super(TestVM, self).__init__() @@ -35,8 +35,8 @@ class TestVM(object): self.pool_name = pool_name self.template = template self.hvm = False - self.storage = qubes.storage.get_pool( - self.pool_name, self).get_storage() + self.storage = qubes.storage.get_pool(self.pool_name, + self).get_storage() self.log = qubes.log.get_vm_logger(self.name) def is_template(self): @@ -54,56 +54,44 @@ class TestTemplateVM(TestVM): def is_template(self): return True + class TestDisposableVM(TestVM): def is_disposablevm(self): return True -class TC_00_Storage(QubesTestCase): +class TC_00_Pool(QubesTestCase): """ This class tests the utility methods from :mod:``qubes.storage`` """ def setUp(self): - super(TC_00_Storage, self).setUp() - self.app = TestApp() + super(TC_00_Pool, self).setUp() - def test_000_dump(self): - """ Dumps storage instance to a storage string """ - vmname = self.make_vm_name('appvm') - template = TestTemplateVM(self.app, 1, - qubes.tests.VMPREFIX + 'template', pool_name='default') - vm = TestVM(self.app, qid=2, name=vmname, pool_name='default', - template=template) - storage = vm.storage - result = qubes.storage.dump(storage) - expected = 'qubes.storage.xen.XenStorage' - self.assertEquals(result, expected) + def test_000_unknown_pool_driver(self): + # :pylint: disable=protected-access + """ Expect an exception when unknown pool is requested""" + with self.assertRaises(StoragePoolException): + qubes.storage._get_pool_klass('foo-bar') - def test_001_load(self): - """ Loads storage driver from a storage string """ - result = qubes.storage.load('qubes.storage.xen.XenStorage') - self.assertTrue(result is XenStorage) + def test_001_all_pool_drivers(self): + """ The only predefined pool driver is file """ + self.assertEquals(["xen"], pool_drivers()) - def test_002_default_pool_drivers(self): - """ The only predifined pool driver is xen """ - result = defaults['pool_drivers'].keys() - expected = ["xen"] - self.assertEquals(result, expected) - - def test_003_get_pool_klass(self): + def test_002_get_pool_klass(self): """ Expect the default pool to be `XenPool` """ + # :pylint: disable=protected-access result = qubes.storage._get_pool_klass('default') self.assertTrue(result is XenPool) - def test_004_pool_exists_default(self): + def test_003_pool_exists_default(self): """ Expect the default pool to exists """ self.assertTrue(qubes.storage.pool_exists('default')) - def test_005_pool_exists_random(self): + def test_004_pool_exists_random(self): """ Expect this pool to not a exist """ - self.assertFalse( - qubes.storage.pool_exists('asdh312096r832598213iudhas')) + self.assertFalse(qubes.storage.pool_exists( + 'asdh312096r832598213iudhas')) - def test_006_add_remove_pool(self): + def test_005_add_remove_pool(self): """ Tries to adding and removing a pool. """ pool_name = 'asdjhrp89132' diff --git a/setup.py b/setup.py index f8789ccf..29eddd84 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,11 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 -import glob import os + import setuptools + # don't import: import * is unreliable and there is no need, since this is # compile time and we have source files def get_console_scripts(): @@ -15,6 +16,7 @@ def get_console_scripts(): yield '{} = qubes.tools.{}:main'.format( basename.replace('_', '-'), basename) + if __name__ == '__main__': setuptools.setup( name='qubes', @@ -24,9 +26,7 @@ if __name__ == '__main__': description='Qubes core package', license='GPL2+', url='https://www.qubes-os.org/', - packages=setuptools.find_packages(exclude=('core*', 'tests')), - entry_points={ 'console_scripts': list(get_console_scripts()), 'qubes.vm': [ @@ -42,5 +42,7 @@ if __name__ == '__main__': 'qubes.devices': [ 'pci = qubes.devices:PCIDevice', ], - } - ) + 'qubes.storage': [ + 'xen = qubes.storage.xen:XenPool', + ] + })