Browse Source

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
Bahtiar `kalkin-` Gadimov 8 years ago
parent
commit
b1978abce5
5 changed files with 55 additions and 98 deletions
  1. 3 2
      etc/storage.conf
  2. 0 2
      qubes/config.py
  3. 17 49
      qubes/storage/__init__.py
  4. 28 40
      qubes/tests/storage.py
  5. 7 5
      setup.py

+ 3 - 2
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

+ 0 - 2
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,

+ 17 - 49
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)]

+ 28 - 40
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()
-
-    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_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_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):
+        super(TC_00_Pool, self).setUp()
+
+    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_all_pool_drivers(self):
+        """ The only predefined pool driver is file """
+        self.assertEquals(["xen"], pool_drivers())
+
+    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'
 

+ 7 - 5
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',
+            ]
+        })