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
This commit is contained in:
Bahtiar `kalkin-` Gadimov 2016-03-20 20:29:46 +01:00 committed by Wojtek Porczyk
parent 42666e0ec5
commit b1978abce5
5 changed files with 52 additions and 95 deletions

View File

@ -2,10 +2,11 @@
driver=xen ; the default xen storage driver=xen ; the default xen storage
; class = qubes.storage.xen.XenStorage ; class always overwrites the driver ; 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 ; class name
; [pool-b] ; [pool-b]
; class = foo.bar.MyStorage ; driver = foo
; ;
; [test-dummy] ; [test-dummy]
; driver=dummy ; driver=dummy

View File

@ -83,8 +83,6 @@ defaults = {
'private_img_size': 2*1024*1024*1024, 'private_img_size': 2*1024*1024*1024,
'root_img_size': 10*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'}, 'pool_config': {'dir_path': '/var/lib/qubes'},
# how long (in sec) to wait for VMs to shutdown, # how long (in sec) to wait for VMs to shutdown,

View File

@ -24,23 +24,24 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# #
""" Qubes storage system"""
from __future__ import absolute_import from __future__ import absolute_import
import ConfigParser import ConfigParser
import importlib
import os import os
import os.path import os.path
import re
import shutil import shutil
import subprocess import subprocess
import sys
import pkg_resources
import qubes import qubes
import qubes.exc import qubes.exc
import qubes.utils import qubes.utils
BLKSIZE = 512 BLKSIZE = 512
CONFIG_FILE = '/etc/qubes/storage.conf' CONFIG_FILE = '/etc/qubes/storage.conf'
STORAGE_ENTRY_POINT = 'qubes.storage'
class StoragePoolException(qubes.exc.QubesException): class StoragePoolException(qubes.exc.QubesException):
@ -78,7 +79,6 @@ class Storage(object):
#: Additional drive (currently used only by HVM) #: Additional drive (currently used only by HVM)
self.drive = None self.drive = None
def get_config_params(self): def get_config_params(self):
args = {} args = {}
args['rootdev'] = self.root_dev_config() args['rootdev'] = self.root_dev_config()
@ -90,7 +90,6 @@ class Storage(object):
return args return args
def root_dev_config(self): def root_dev_config(self):
raise NotImplementedError() raise NotImplementedError()
@ -357,42 +356,6 @@ def get_disk_usage(path):
return ret 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): def get_pool(name, vm):
""" Instantiates the storage for the specified vm """ """ Instantiates the storage for the specified vm """
config = _get_storage_config_parser() config = _get_storage_config_parser()
@ -465,16 +428,15 @@ def _get_pool_klass(name, config=None):
if not config.has_section(name): if not config.has_section(name):
raise StoragePoolException('Uknown storage pool ' + 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): class Pool(object):
def __init__(self, vm, dir_path): def __init__(self, vm, dir_path):
@ -533,3 +495,9 @@ class Pool(object):
""" """
if not os.path.exists(path): if not os.path.exists(path):
os.mkdir(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)]

View File

@ -16,16 +16,16 @@
# with this program; if not, write to the Free Software Foundation, Inc., # with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import qubes.storage
import qubes.log import qubes.log
from qubes.config import defaults from qubes.storage import StoragePoolException, pool_drivers
from qubes.storage.xen import XenPool, XenStorage from qubes.storage.xen import XenPool
from qubes.tests import QubesTestCase, SystemTestsMixin from qubes.tests import QubesTestCase
class TestApp(qubes.tests.TestEmitter): class TestApp(qubes.tests.TestEmitter):
pass pass
class TestVM(object): class TestVM(object):
def __init__(self, app, qid, name, pool_name, template=None): def __init__(self, app, qid, name, pool_name, template=None):
super(TestVM, self).__init__() super(TestVM, self).__init__()
@ -35,8 +35,8 @@ class TestVM(object):
self.pool_name = pool_name self.pool_name = pool_name
self.template = template self.template = template
self.hvm = False self.hvm = False
self.storage = qubes.storage.get_pool( self.storage = qubes.storage.get_pool(self.pool_name,
self.pool_name, self).get_storage() self).get_storage()
self.log = qubes.log.get_vm_logger(self.name) self.log = qubes.log.get_vm_logger(self.name)
def is_template(self): def is_template(self):
@ -54,56 +54,44 @@ class TestTemplateVM(TestVM):
def is_template(self): def is_template(self):
return True return True
class TestDisposableVM(TestVM): class TestDisposableVM(TestVM):
def is_disposablevm(self): def is_disposablevm(self):
return True return True
class TC_00_Storage(QubesTestCase):
class TC_00_Pool(QubesTestCase):
""" This class tests the utility methods from :mod:``qubes.storage`` """ """ This class tests the utility methods from :mod:``qubes.storage`` """
def setUp(self): def setUp(self):
super(TC_00_Storage, self).setUp() super(TC_00_Pool, self).setUp()
self.app = TestApp()
def test_000_dump(self): def test_000_unknown_pool_driver(self):
""" Dumps storage instance to a storage string """ # :pylint: disable=protected-access
vmname = self.make_vm_name('appvm') """ Expect an exception when unknown pool is requested"""
template = TestTemplateVM(self.app, 1, with self.assertRaises(StoragePoolException):
qubes.tests.VMPREFIX + 'template', pool_name='default') qubes.storage._get_pool_klass('foo-bar')
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): def test_001_all_pool_drivers(self):
""" Loads storage driver from a storage string """ """ The only predefined pool driver is file """
result = qubes.storage.load('qubes.storage.xen.XenStorage') self.assertEquals(["xen"], pool_drivers())
self.assertTrue(result is XenStorage)
def test_002_default_pool_drivers(self): def test_002_get_pool_klass(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):
""" Expect the default pool to be `XenPool` """ """ Expect the default pool to be `XenPool` """
# :pylint: disable=protected-access
result = qubes.storage._get_pool_klass('default') result = qubes.storage._get_pool_klass('default')
self.assertTrue(result is XenPool) 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 """ """ Expect the default pool to exists """
self.assertTrue(qubes.storage.pool_exists('default')) 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 """ """ Expect this pool to not a exist """
self.assertFalse( self.assertFalse(qubes.storage.pool_exists(
qubes.storage.pool_exists('asdh312096r832598213iudhas')) 'asdh312096r832598213iudhas'))
def test_006_add_remove_pool(self): def test_005_add_remove_pool(self):
""" Tries to adding and removing a pool. """ """ Tries to adding and removing a pool. """
pool_name = 'asdjhrp89132' pool_name = 'asdjhrp89132'

View File

@ -1,10 +1,11 @@
#!/usr/bin/python2 -O #!/usr/bin/python2 -O
# vim: fileencoding=utf-8 # vim: fileencoding=utf-8
import glob
import os import os
import setuptools import setuptools
# don't import: import * is unreliable and there is no need, since this is # don't import: import * is unreliable and there is no need, since this is
# compile time and we have source files # compile time and we have source files
def get_console_scripts(): def get_console_scripts():
@ -15,6 +16,7 @@ def get_console_scripts():
yield '{} = qubes.tools.{}:main'.format( yield '{} = qubes.tools.{}:main'.format(
basename.replace('_', '-'), basename) basename.replace('_', '-'), basename)
if __name__ == '__main__': if __name__ == '__main__':
setuptools.setup( setuptools.setup(
name='qubes', name='qubes',
@ -24,9 +26,7 @@ if __name__ == '__main__':
description='Qubes core package', description='Qubes core package',
license='GPL2+', license='GPL2+',
url='https://www.qubes-os.org/', url='https://www.qubes-os.org/',
packages=setuptools.find_packages(exclude=('core*', 'tests')), packages=setuptools.find_packages(exclude=('core*', 'tests')),
entry_points={ entry_points={
'console_scripts': list(get_console_scripts()), 'console_scripts': list(get_console_scripts()),
'qubes.vm': [ 'qubes.vm': [
@ -42,5 +42,7 @@ if __name__ == '__main__':
'qubes.devices': [ 'qubes.devices': [
'pci = qubes.devices:PCIDevice', 'pci = qubes.devices:PCIDevice',
], ],
} 'qubes.storage': [
) 'xen = qubes.storage.xen:XenPool',
]
})