소스 검색

qubes: Convert QubesVM and Extension discovery to pkg_resources

QubesOS/qubes-issues#1238
Wojtek Porczyk 8 년 전
부모
커밋
d09bd5ab6a
12개의 변경된 파일48개의 추가작업 그리고 126개의 파일을 삭제
  1. 27 8
      qubes/__init__.py
  2. 0 7
      qubes/_pluginloader.py
  3. 0 20
      qubes/ext/__init__.py
  4. 2 2
      qubes/ext/qubesmanager.py
  5. 0 66
      qubes/plugins.py
  6. 6 1
      qubes/tests/__init__.py
  7. 0 13
      qubes/tests/tools/__init__.py
  8. 1 1
      qubes/tools/qvm_create.py
  9. 1 6
      qubes/vm/__init__.py
  10. 3 0
      qubes/vm/qubesvm.py
  11. 0 2
      rpm_spec/core-dom0.spec
  12. 8 0
      setup.py

+ 27 - 8
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

+ 0 - 7
qubes/_pluginloader.py

@@ -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 *
-

+ 0 - 20
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__)

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

+ 0 - 66
qubes/plugins.py

@@ -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))

+ 6 - 1
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',

+ 0 - 13
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

+ 1 - 1
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))
 

+ 1 - 6
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__)

+ 3 - 0
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):

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

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