qubes: Convert QubesVM and Extension discovery to pkg_resources

QubesOS/qubes-issues#1238
This commit is contained in:
Wojtek Porczyk 2016-03-04 13:03:43 +01:00
parent 93686eae06
commit d09bd5ab6a
12 changed files with 48 additions and 126 deletions

View File

@ -54,7 +54,7 @@ import __builtin__
import docutils.core
import docutils.io
import lxml.etree
import pkg_resources
import qubes.config
import qubes.events
@ -1170,8 +1170,8 @@ class Qubes(PropertyHolder):
self.log = logging.getLogger('app')
# pylint: disable=no-member
self._extensions = set(
ext(self) for ext in qubes.ext.Extension.register.values())
self.extensions = set(ext.load()(self)
for ext in pkg_resources.iter_entry_points('qubes.ext'))
#: collection of all VMs managed by this Qubes instance
self.domains = VMCollection(self)
@ -1234,7 +1234,7 @@ class Qubes(PropertyHolder):
# stage 2: load VMs
for node in self.xml.xpath('./domains/domain'):
# pylint: disable=no-member
cls = qubes.vm.BaseVM.register[node.get('class')]
cls = self.get_vm_class(node.get('class'))
vm = cls(self, node)
vm.load_properties(load_stage=2)
vm.init_log()
@ -1384,6 +1384,29 @@ class Qubes(PropertyHolder):
return labels
def get_vm_class(self, clsname):
'''Find the class for a domain.
Classess are registered as setuptools' entry points in ``qubes.vm``
group. Any package may supply their own classess.
:param str clsname: name of the class
:return type: class
'''
epoints = tuple(pkg_resources.iter_entry_points('qubes.vm', clsname))
if not epoints:
raise qubes.exc.QubesException(
'no such VM class: {!r}'.format(clsname))
elif len(epoints) > 1:
raise qubes.exc.QubesException(
'more than 1 implementation of {!r} found: {}'.format(
clsname,
', '.join(
'{}.{}'.format(ep.module_name, '.'.join(ep.attrs))
for ep in epoints)))
return epoints[0].load()
def add_new_vm(self, cls, qid=None, **kwargs):
'''Add new Virtual Machine to colletion
@ -1502,7 +1525,3 @@ class Qubes(PropertyHolder):
# fire property-del:netvm as it is responsible for resetting
# netvm to it's default value
vm.fire_event('property-del:netvm', 'netvm', newvalue, oldvalue)
# load plugins
import qubes._pluginloader

View File

@ -1,7 +0,0 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
# pylint: disable=wildcard-import,unused-wildcard-import
from qubes.vm import *
from qubes.ext import *

View File

@ -29,22 +29,8 @@ some systems. They may be OS- or architecture-dependent or custom-developed for
particular customer.
'''
import inspect
import qubes.events
import qubes.plugins
class ExtensionPlugin(qubes.plugins.Plugin):
'''Metaclass for :py:class:`Extension`'''
def __init__(cls, name, bases, dict_):
super(ExtensionPlugin, cls).__init__(name, bases, dict_)
cls._instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(ExtensionPlugin, cls).__call__(
*args, **kwargs)
return cls._instance
class Extension(object):
'''Base class for all extensions
@ -52,8 +38,6 @@ class Extension(object):
:param qubes.Qubes app: application object
''' # pylint: disable=too-few-public-methods
__metaclass__ = ExtensionPlugin
def __init__(self, app):
self.app = app
@ -100,7 +84,3 @@ def handler(*events, **kwargs):
return func
return decorator
__all__ = ['Extension', 'ExtensionPlugin', 'handler'] \
+ qubes.plugins.load(__file__)

View File

@ -33,9 +33,9 @@ import qubes.ext
import dbus
class QubesManagerExtension(qubes.ext.Extension):
class QubesManager(qubes.ext.Extension):
def __init__(self, *args, **kwargs):
super(QubesManagerExtension, self).__init__(*args, **kwargs)
super(QubesManager, self).__init__(*args, **kwargs)
self._system_bus = dbus.SystemBus()
# pylint: disable=no-self-use,unused-argument,too-few-public-methods

View File

@ -1,66 +0,0 @@
#!/usr/bin/python2 -O
# vim: fileencoding=utf-8
#
# The Qubes OS Project, https://www.qubes-os.org/
#
# Copyright (C) 2014-2015 Joanna Rutkowska <joanna@invisiblethingslab.com>
# Copyright (C) 2014-2015 Wojtek Porczyk <woju@invisiblethingslab.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
'''Plugins helpers for Qubes
Qubes uses two types of plugins: virtual machines and extensions.
'''
import imp
import os
class Plugin(type):
'''Base metaclass for plugins'''
def __init__(cls, name, bases, dict_):
super(Plugin, cls).__init__(name, bases, dict_)
# pylint: disable=unused-argument
if hasattr(cls, 'register'):
cls.register[cls.__name__] = cls
else:
# we've got root class
cls.register = {}
def load(modfile):
'''Load (import) all plugins from subpackage.
This function should be invoked from ``__init__.py`` in a package like that:
>>> __all__ = qubes.plugins.load(__file__) # doctest: +SKIP
'''
path = os.path.dirname(modfile)
listdir = os.listdir(path)
ret = set()
# pylint: disable=unused-variable
for suffix, mode, type_ in imp.get_suffixes():
for filename in listdir:
if filename.endswith(suffix):
ret.add(filename[:-len(suffix)])
if '__init__' in ret:
ret.remove('__init__')
return list(sorted(ret))

View File

@ -786,8 +786,13 @@ def load_tests(loader, tests, pattern): # pylint: disable=unused-argument
'qubes.tests.vm.qubesvm',
'qubes.tests.vm.adminvm',
'qubes.tests.init2',
'qubes.tests.tools',
):
tests.addTests(loader.loadTestsFromName(modname))
tests.addTests(loader.discover(
os.path.join(os.path.dirname(__file__), 'tools')))
for modname in (
# integration tests
'qubes.tests.int.basic',
'qubes.tests.int.dom0_update',

View File

@ -1,14 +1 @@
# pylint: skip-file
import importlib
import qubes.plugins
import qubes.tests
__all__ = qubes.plugins.load(__file__)
def load_tests(loader, tests, pattern):
for name in __all__:
mod = importlib.import_module('.' + name, __name__)
tests.addTests(loader.loadTestsFromModule(mod))
return tests

View File

@ -93,7 +93,7 @@ def main(args=None):
', '.join(repr(l.name) for l in args.app.labels)))
try:
cls = qubes.vm.BaseVM.register[args.cls] # pylint: disable=no-member
cls = args.app.get_vm_class(args.cls)
except KeyError:
parser.error('no such domain class: {!r}'.format(args.cls))

View File

@ -44,7 +44,6 @@ import lxml.etree
import qubes
import qubes.log
import qubes.events
import qubes.plugins
import qubes.tools.qvm_ls
@ -127,7 +126,7 @@ class Features(dict):
return default
class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
class BaseVMMeta(qubes.events.EmitterMeta):
'''Metaclass for :py:class:`.BaseVM`'''
def __init__(cls, name, bases, dict_):
super(BaseVMMeta, cls).__init__(name, bases, dict_)
@ -649,7 +648,3 @@ class BaseVM(qubes.PropertyHolder):
return None
return conf
__all__ = ['BaseVMMeta', 'DeviceCollection', 'DeviceManager', 'BaseVM'] \
+ qubes.plugins.load(__file__)

View File

@ -392,11 +392,13 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
def is_template(self):
warnings.warn('vm.is_template() is deprecated, use isinstance()',
DeprecationWarning)
import qubes.vm.templatevm
return isinstance(self, qubes.vm.templatevm.TemplateVM)
def is_appvm(self):
warnings.warn('vm.is_appvm() is deprecated, use isinstance()',
DeprecationWarning)
import qubes.vm.appvm
return isinstance(self, qubes.vm.appvm.AppVM)
def is_proxyvm(self):
@ -407,6 +409,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
def is_disposablevm(self):
warnings.warn('vm.is_disposable() is deprecated, use isinstance()',
DeprecationWarning)
import qubes.vm.dispvm
return isinstance(self, qubes.vm.dispvm.DispVM)
def is_netvm(self):

View File

@ -201,13 +201,11 @@ fi
%dir %{python_sitelib}/qubes
%{python_sitelib}/qubes/__init__.py*
%{python_sitelib}/qubes/_pluginloader.py*
%{python_sitelib}/qubes/config.py*
%{python_sitelib}/qubes/dochelpers.py*
%{python_sitelib}/qubes/events.py*
%{python_sitelib}/qubes/exc.py*
%{python_sitelib}/qubes/log.py*
%{python_sitelib}/qubes/plugins.py*
%{python_sitelib}/qubes/rngdoc.py*
%{python_sitelib}/qubes/utils.py*

View File

@ -29,5 +29,13 @@ if __name__ == '__main__':
entry_points={
'console_scripts': list(get_console_scripts()),
'qubes.vm': [
'AppVM = qubes.vm.appvm:AppVM',
'TemplateVM = qubes.vm.templatevm:TemplateVM',
'AdminVM = qubes.vm.adminvm:AdminVM',
],
'qubes.ext': [
'qubes.ext.qubesmanager = qubes.ext.qubesmanager:QubesManager',
],
}
)