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:
parent
42666e0ec5
commit
b1978abce5
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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)]
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
12
setup.py
12
setup.py
@ -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',
|
||||||
|
]
|
||||||
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user