core3: event framework adjusted for global Qubes object
From now, global events are emitted by qubes.Qubes object and handlers are registered there.
This commit is contained in:
parent
b623a71d87
commit
855a434879
@ -29,6 +29,9 @@ import __builtin__
|
||||
import lxml.etree
|
||||
import xml.parsers.expat
|
||||
|
||||
import qubes.ext
|
||||
|
||||
|
||||
if os.name == 'posix':
|
||||
import fcntl
|
||||
elif os.name == 'nt':
|
||||
@ -661,6 +664,8 @@ class Qubes(PropertyHolder):
|
||||
|
||||
|
||||
def __init__(self, store='/var/lib/qubes/qubes.xml'):
|
||||
self._extensions = set(ext(self) for ext in qubes.ext.Extension.register.values())
|
||||
|
||||
#: collection of all VMs managed by this Qubes instance
|
||||
self.domains = VMCollection()
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
from qubes.vm import *
|
||||
from qubes.ext import *
|
||||
|
||||
import qubes.ext
|
||||
|
||||
qubes.ext.init()
|
||||
|
@ -9,37 +9,28 @@ etc.
|
||||
|
||||
import collections
|
||||
|
||||
import qubes.vm
|
||||
|
||||
#: collection of system-wide hooks
|
||||
system_hooks = collections.defaultdict(list)
|
||||
|
||||
def hook(event, vm=None, system=False):
|
||||
'''Decorator factory.
|
||||
def handler(event):
|
||||
'''Event handler decorator factory.
|
||||
|
||||
To hook an event, decorate a method in your plugin class with this
|
||||
decorator.
|
||||
|
||||
.. note::
|
||||
For hooking events from extensions, see :py:func:`qubes.ext.handler`.
|
||||
|
||||
:param str event: event type
|
||||
:param type vm: VM to hook (leave as None to hook all VMs)
|
||||
:param bool system: when :py:obj:`True`, hook is system-wide (not attached to any VM)
|
||||
'''
|
||||
|
||||
def decorator(f):
|
||||
f.ho_event = event
|
||||
|
||||
if system:
|
||||
f.ho_vm = None
|
||||
elif vm is None:
|
||||
f.ho_vm = qubes.vm.BaseVM
|
||||
else:
|
||||
f.ho_vm = vm
|
||||
|
||||
f.ha_event = event
|
||||
f.ha_bound = True
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
def ishook(o):
|
||||
|
||||
def ishandler(o):
|
||||
'''Test if a method is hooked to an event.
|
||||
|
||||
:param object o: suspected hook
|
||||
@ -48,24 +39,67 @@ def ishook(o):
|
||||
'''
|
||||
|
||||
return callable(o) \
|
||||
and hasattr(o, 'ho_event') \
|
||||
and hasattr(o, 'ho_vm')
|
||||
and hasattr(o, 'ha_event')
|
||||
|
||||
def add_system_hook(event, f):
|
||||
'''Add system-wide hook.
|
||||
|
||||
:param callable f: function to call
|
||||
class EmitterMeta(type):
|
||||
'''Metaclass for :py:class:`Emitter`'''
|
||||
def __init__(cls, name, bases, dict_):
|
||||
super(type, cls).__init__(name, bases, dict_)
|
||||
cls.__handlers__ = collections.defaultdict(set)
|
||||
|
||||
|
||||
class Emitter(object):
|
||||
'''Subject that can emit events
|
||||
'''
|
||||
|
||||
global_hooks[event].append(f)
|
||||
__metaclass__ = EmitterMeta
|
||||
|
||||
def fire_system_hooks(event, *args, **kwargs):
|
||||
'''Fire system-wide hooks.
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Emitter, self).__init__(*args, **kwargs)
|
||||
try:
|
||||
propnames = set(prop.__name__ for prop in self.get_props_list())
|
||||
except AttributeError:
|
||||
propnames = set()
|
||||
|
||||
:param str event: event type
|
||||
for attr in dir(self):
|
||||
if attr in propnames:
|
||||
# we have to be careful, not to getattr() on properties which
|
||||
# may be unset
|
||||
continue
|
||||
|
||||
*args* and *kwargs* are passed to all hooks.
|
||||
'''
|
||||
attr = getattr(self, attr)
|
||||
if not ishandler(attr):
|
||||
continue
|
||||
|
||||
for hook in system_hooks[event]:
|
||||
hook(self, *args, **kwargs)
|
||||
self.add_handler(attr.ha_event, attr)
|
||||
|
||||
|
||||
@classmethod
|
||||
def add_handler(cls, event, handler):
|
||||
'''Add event handler to subject's class
|
||||
|
||||
:param str event: event identificator
|
||||
:param collections.Callable handler: handler callable
|
||||
'''
|
||||
|
||||
cls.__handlers__[event].add(handler)
|
||||
|
||||
|
||||
def fire_event(self, event, *args, **kwargs):
|
||||
'''Call all handlers for an event
|
||||
|
||||
:param str event: event identificator
|
||||
|
||||
All *args* and *kwargs* are passed verbatim. They are different for
|
||||
different events.
|
||||
'''
|
||||
|
||||
for handler in self.__handlers__[event]:
|
||||
if hasattr(handler, 'ha_bound'):
|
||||
# this is our (bound) method, self is implicit
|
||||
handler(event, *args, **kwargs)
|
||||
else:
|
||||
# this is from extension or hand-added, so we see method as
|
||||
# unbound, therefore we need to pass self
|
||||
handler(self, event, *args, **kwargs)
|
||||
|
@ -33,22 +33,56 @@ class ExtensionPlugin(qubes.plugins.Plugin):
|
||||
return cls._instance
|
||||
|
||||
class Extension(object):
|
||||
'''Base class for all extensions'''
|
||||
'''Base class for all extensions
|
||||
|
||||
:param qubes.Qubes app: application object
|
||||
'''
|
||||
|
||||
__metaclass__ = ExtensionPlugin
|
||||
def __init__(self):
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
for name in dir(self):
|
||||
attr = getattr(self, name)
|
||||
if not ishook(attr):
|
||||
if not qubes.events.ishandler(attr):
|
||||
continue
|
||||
|
||||
if attr.ho_vm is not None:
|
||||
attr.ho_vm.add_hook(event, attr)
|
||||
if attr.ha_vm is not None:
|
||||
attr.ha_vm.add_hook(attr.ha_event, attr)
|
||||
else:
|
||||
# global hook
|
||||
qubes.events.add_system_hook(event, attr)
|
||||
self.app.add_hook(attr.ha_event, attr)
|
||||
|
||||
|
||||
def handler(event, vm=None, system=False):
|
||||
'''Event handler decorator factory.
|
||||
|
||||
To hook an event, decorate a method in your plugin class with this
|
||||
decorator. You may hook both per-vm-class and global events.
|
||||
|
||||
.. note::
|
||||
This decorator is intended only for extensions! For regular use in the
|
||||
core, see py:func:`qubes.events.handler`.
|
||||
|
||||
:param str event: event type
|
||||
:param type vm: VM to hook (leave as None to hook all VMs)
|
||||
:param bool system: when :py:obj:`True`, hook is system-wide (not attached to any VM)
|
||||
'''
|
||||
|
||||
def decorator(f):
|
||||
f.ho_event = event
|
||||
|
||||
if system:
|
||||
f.ha_vm = None
|
||||
elif vm is None:
|
||||
f.ha_vm = qubes.vm.BaseVM
|
||||
else:
|
||||
f.ha_vm = vm
|
||||
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
def init():
|
||||
for ext in Extension.register.values():
|
||||
instance = ext()
|
||||
|
||||
__all__ = qubes.plugins.load(__file__)
|
||||
|
@ -12,7 +12,7 @@ Main public classes
|
||||
Helper classes and functions
|
||||
----------------------------
|
||||
|
||||
.. autoclass:: VMPlugin
|
||||
.. autoclass:: BaseVMMeta
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@ -57,19 +57,22 @@ import dateutil.parser
|
||||
import lxml.etree
|
||||
|
||||
import qubes
|
||||
import qubes.events
|
||||
import qubes.plugins
|
||||
|
||||
|
||||
class VMPlugin(qubes.plugins.Plugin):
|
||||
class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
|
||||
'''Metaclass for :py:class:`.BaseVM`'''
|
||||
def __init__(cls, name, bases, dict_):
|
||||
super(VMPlugin, cls).__init__(name, bases, dict_)
|
||||
super(BaseVMMeta, cls).__init__(name, bases, dict_)
|
||||
cls.__hooks__ = collections.defaultdict(list)
|
||||
|
||||
|
||||
class BaseVM(qubes.PropertyHolder):
|
||||
class BaseVM(qubes.PropertyHolder, qubes.events.Emitter):
|
||||
'''Base class for all VMs
|
||||
|
||||
:param app: Qubes application context
|
||||
:type app: :py:class:`qubes.Qubes`
|
||||
:param xml: xml node from which to deserialise
|
||||
:type xml: :py:class:`lxml.etree._Element` or :py:obj:`None`
|
||||
|
||||
@ -78,23 +81,25 @@ class BaseVM(qubes.PropertyHolder):
|
||||
:py:class:`qubes.vm.qubesvm.QubesVM`.
|
||||
'''
|
||||
|
||||
__metaclass__ = VMPlugin
|
||||
__metaclass__ = BaseVMMeta
|
||||
|
||||
def __init__(self, app, xml=None, load_stage=2, services={}, devices=None, tags={}, **kwargs):
|
||||
def __init__(self, app, xml, load_stage=2, services={}, devices=None,
|
||||
tags={}, *args, **kwargs):
|
||||
self.app = app
|
||||
self.xml = xml
|
||||
self.services = services
|
||||
self.devices = collections.defaultdict(list) if devices is None else devices
|
||||
self.tags = tags
|
||||
|
||||
all_names = set(prop.__name__ for prop in self.get_props_list(load_stage=2))
|
||||
for key in kwargs:
|
||||
for key in list(kwargs.keys()):
|
||||
if not key in all_names:
|
||||
raise AttributeError(
|
||||
'No property {!r} found in {!r}'.format(
|
||||
key, self.__class__))
|
||||
setattr(self, key, kwargs[key])
|
||||
del kwargs[key]
|
||||
|
||||
super(BaseVM, self).__init__(xml, *args, **kwargs)
|
||||
|
||||
def add_new_vm(self, vm):
|
||||
'''Add new Virtual Machine to colletion
|
||||
|
37
tests/events.py
Normal file
37
tests/events.py
Normal file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/python2 -O
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, '..')
|
||||
import qubes.events
|
||||
|
||||
class TC_Emitter(unittest.TestCase):
|
||||
def test_000_add_handler(self):
|
||||
# need something mutable
|
||||
testevent_fired = [False]
|
||||
|
||||
def on_testevent(subject, event):
|
||||
if event == 'testevent':
|
||||
testevent_fired[0] = True
|
||||
|
||||
emitter = qubes.events.Emitter()
|
||||
emitter.add_handler('testevent', on_testevent)
|
||||
emitter.fire_event('testevent')
|
||||
self.assertTrue(testevent_fired[0])
|
||||
|
||||
|
||||
def test_001_decorator(self):
|
||||
class TestEmitter(qubes.events.Emitter):
|
||||
def __init__(self):
|
||||
super(TestEmitter, self).__init__()
|
||||
self.testevent_fired = False
|
||||
|
||||
@qubes.events.handler('testevent')
|
||||
def on_testevent(self, event):
|
||||
if event == 'testevent':
|
||||
self.testevent_fired = True
|
||||
|
||||
emitter = TestEmitter()
|
||||
emitter.fire_event('testevent')
|
||||
self.assertTrue(emitter.testevent_fired)
|
@ -104,8 +104,8 @@ class TC_11_VMCollection(unittest.TestCase):
|
||||
# XXX passing None may be wrong in the future
|
||||
self.vms = qubes.VMCollection(None)
|
||||
|
||||
self.testvm1 = TestVM(None, qid=1, name='testvm1')
|
||||
self.testvm2 = TestVM(None, qid=2, name='testvm2')
|
||||
self.testvm1 = TestVM(None, None, qid=1, name='testvm1')
|
||||
self.testvm2 = TestVM(None, None, qid=2, name='testvm2')
|
||||
|
||||
def test_000_contains(self):
|
||||
self.vms._dict = {1: self.testvm1}
|
||||
@ -132,8 +132,8 @@ class TC_11_VMCollection(unittest.TestCase):
|
||||
with self.assertRaises(TypeError):
|
||||
self.vms.add(object())
|
||||
|
||||
testvm_qid_collision = TestVM(None, name='testvm2', qid=1)
|
||||
testvm_name_collision = TestVM(None, name='testvm1', qid=2)
|
||||
testvm_qid_collision = TestVM(None, None, name='testvm2', qid=1)
|
||||
testvm_name_collision = TestVM(None, None, name='testvm1', qid=2)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
self.vms.add(testvm_qid_collision)
|
||||
|
Loading…
Reference in New Issue
Block a user