From d09bd5ab6acdbf6a2cdb61fb7e4f97f5e64d4bff Mon Sep 17 00:00:00 2001 From: Wojtek Porczyk Date: Fri, 4 Mar 2016 13:03:43 +0100 Subject: [PATCH] qubes: Convert QubesVM and Extension discovery to pkg_resources QubesOS/qubes-issues#1238 --- qubes/__init__.py | 35 ++++++++++++++----- qubes/_pluginloader.py | 7 ---- qubes/ext/__init__.py | 20 ----------- qubes/ext/qubesmanager.py | 4 +-- qubes/plugins.py | 66 ----------------------------------- qubes/tests/__init__.py | 7 +++- qubes/tests/tools/__init__.py | 13 ------- qubes/tools/qvm_create.py | 2 +- qubes/vm/__init__.py | 7 +--- qubes/vm/qubesvm.py | 3 ++ rpm_spec/core-dom0.spec | 2 -- setup.py | 8 +++++ 12 files changed, 48 insertions(+), 126 deletions(-) delete mode 100644 qubes/_pluginloader.py delete mode 100644 qubes/plugins.py diff --git a/qubes/__init__.py b/qubes/__init__.py index 79afe630..18d0177d 100644 --- a/qubes/__init__.py +++ b/qubes/__init__.py @@ -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 diff --git a/qubes/_pluginloader.py b/qubes/_pluginloader.py deleted file mode 100644 index a4eabfd5..00000000 --- a/qubes/_pluginloader.py +++ /dev/null @@ -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 * - diff --git a/qubes/ext/__init__.py b/qubes/ext/__init__.py index 7240cdfb..789b9566 100644 --- a/qubes/ext/__init__.py +++ b/qubes/ext/__init__.py @@ -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__) diff --git a/qubes/ext/qubesmanager.py b/qubes/ext/qubesmanager.py index 840990a2..b030583d 100644 --- a/qubes/ext/qubesmanager.py +++ b/qubes/ext/qubesmanager.py @@ -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 diff --git a/qubes/plugins.py b/qubes/plugins.py deleted file mode 100644 index 453b9f03..00000000 --- a/qubes/plugins.py +++ /dev/null @@ -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 -# Copyright (C) 2014-2015 Wojtek Porczyk -# -# 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)) diff --git a/qubes/tests/__init__.py b/qubes/tests/__init__.py index ea078388..63bbd5c6 100644 --- a/qubes/tests/__init__.py +++ b/qubes/tests/__init__.py @@ -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', diff --git a/qubes/tests/tools/__init__.py b/qubes/tests/tools/__init__.py index 127d5157..388083ed 100644 --- a/qubes/tests/tools/__init__.py +++ b/qubes/tests/tools/__init__.py @@ -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 diff --git a/qubes/tools/qvm_create.py b/qubes/tools/qvm_create.py index 71c16b28..3ee077d6 100644 --- a/qubes/tools/qvm_create.py +++ b/qubes/tools/qvm_create.py @@ -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)) diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index 0b096dc9..ee50758f 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -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__) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 6cbbd521..e30f22b5 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -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): diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 75c960c7..73bbe1fb 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -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* diff --git a/setup.py b/setup.py index 8905c76b..43c38907 100644 --- a/setup.py +++ b/setup.py @@ -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', + ], } )