core3: basic global events and their documentation

This commit is contained in:
Wojtek Porczyk 2014-12-09 18:34:00 +01:00
parent 855a434879
commit 1a032ecf2a
5 changed files with 155 additions and 41 deletions

View File

@ -28,7 +28,17 @@ sys.path.insert(0, os.path.abspath('../'))
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'qubes.dochelpers']
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.coverage',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
'qubes.dochelpers',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

View File

@ -330,6 +330,9 @@ class VMCollection(object):
if not isinstance(value, qubes.vm.BaseVM):
raise TypeError('{} holds only BaseVM instances'.format(self.__class__.__name__))
if not hasattr(value, 'qid'):
value.qid = self.domains.get_new_unused_qid()
if value.qid in self:
raise ValueError('This collection already holds VM that has qid={!r} (!r)'.format(
value.qid, self[value.qid]))
@ -338,6 +341,7 @@ class VMCollection(object):
value.name, self[value.name]))
self._dict[value.qid] = value
self.app.fire_event('domain-added', value)
def __getitem__(self, key):
@ -359,7 +363,9 @@ class VMCollection(object):
def __delitem__(self, key):
del self._dict[self[key].qid]
vm = self[key]
del self._dict[vm.qid]
self.app.fire_event('domain-deleted', vm)
def __contains__(self, key):
@ -467,12 +473,28 @@ class property(object):
def __set__(self, instance, value):
try:
oldvalue = getattr(instance, self.__name__)
has_oldvalue = True
except AttributeError:
has_oldvalue = False
if self._setter is not None:
value = self._setter(instance, self, value)
if self._type is not None:
value = self._type(value)
instance._init_property(self, value)
if has_oldvalue:
instance.fire_event('property-set:' + self.__name__, value, oldvalue)
else:
instance.fire_event('property-set:' + self.__name__, value)
def __delete__(self, instance):
delattr(instance, self._attr_name)
def __repr__(self):
return '<{} object at {:#x} name={!r} default={!r}>'.format(
@ -505,8 +527,27 @@ class property(object):
prop.__name__, self.__class__.__name__))
class PropertyHolder(object):
'''Abstract class for holding :py:class:`qubes.property`'''
class PropertyHolder(qubes.events.Emitter):
'''Abstract class for holding :py:class:`qubes.property`
Events fired by instances of this class:
.. event:: property-load (subject, event)
Fired once after all properties are loaded from XML. Individual
``property-set`` events are not fired.
.. event:: property-set:<propname> (subject, event, name, newvalue[, oldvalue])
Fired when property changes state. Signature is variable, *oldvalue* is
present only if there was an old value.
:param name: Property name
:param newvalue: New value of the property
:param oldvalue: Old value of the property
Members:
'''
def __init__(self, xml, *args, **kwargs):
super(PropertyHolder, self).__init__(*args, **kwargs)
@ -545,11 +586,14 @@ class PropertyHolder(object):
def load_properties(self, load_stage=None):
'''Load properties from immediate children of XML node.
``property-set`` events are not fired for each individual property.
:param lxml.etree._Element xml: XML node reference
'''
# sys.stderr.write('<{}>.load_properties(load_stage={}) xml={!r}\n'.format(hex(id(self)), load_stage, self.xml))
self.events_enabled = False
all_names = set(prop.__name__ for prop in self.get_props_list(load_stage))
# sys.stderr.write(' all_names={!r}\n'.format(all_names))
for node in self.xml.xpath('./properties/property'):
@ -563,6 +607,9 @@ class PropertyHolder(object):
name, self.__class__))
setattr(self, name, value)
self.events_enabled = True
self.fire_event('property-loaded')
# sys.stderr.write(' load_properties return\n')
@ -630,22 +677,45 @@ class Qubes(PropertyHolder):
:param str store: path to ``qubes.xml``
The store is loaded in stages.
The store is loaded in stages:
In the first stage there are loaded some basic features from store
(currently labels).
1. In the first stage there are loaded some basic features from store
(currently labels).
In the second stage stubs for all VMs are loaded. They are filled with
their basic properties, like ``qid`` and ``name``.
2. In the second stage stubs for all VMs are loaded. They are filled
with their basic properties, like ``qid`` and ``name``.
In the third stage all global properties are loaded. They often reference
VMs, like default netvm, so they should be filled after loading VMs.
3. In the third stage all global properties are loaded. They often
reference VMs, like default netvm, so they should be filled after
loading VMs.
In the fourth stage all remaining VM properties are loaded. They also need
all VMs loaded, because they represent dependencies between VMs like
aforementioned netvm.
4. In the fourth stage all remaining VM properties are loaded. They
also need all VMs loaded, because they represent dependencies
between VMs like aforementioned netvm.
In the fifth stage there are some fixups to ensure sane system operation.
5. In the fifth stage there are some fixups to ensure sane system
operation.
This class emits following events:
.. event:: domain-added (subject, event, vm)
When domain is added.
:param subject: Event emitter
:param event: Event name (``'domain-added'``)
:param vm: Domain object
.. event:: domain-deleted (subject, event, vm)
When domain is deleted. VM still has reference to ``app`` object,
but is not contained within VMCollection.
:param subject: Event emitter
:param event: Event name (``'domain-deleted'``)
:param vm: Domain object
Methods and attributes:
'''
default_netvm = VMProperty('default_netvm', load_stage=3,
@ -822,22 +892,16 @@ class Qubes(PropertyHolder):
return labels
def add_new_vm(self, vm):
'''Add new Virtual Machine to colletion
'''
if not hasattr(vm, 'qid'):
vm.qid = self.domains.get_new_unused_qid()
self.domains.add(vm)
#
# XXX
# all this will be moved to an event handler
# and deduplicated with self.load()
#
@qubes.events.handler('domain-added')
def on_domain_addedd(self, event, vm):
# make first created NetVM the default one
if not hasattr(self, 'default_fw_netvm') \
and vm.provides_network \
@ -866,23 +930,21 @@ class Qubes(PropertyHolder):
and hasattr(vm, 'netvm'):
self.default_clockvm = vm
# XXX don't know if it should return self
return vm
# XXX This was in QubesVmCollection, will be in an event
# def pop(self, qid):
# if self.default_netvm_qid == qid:
# self.default_netvm_qid = None
# if self.default_fw_netvm_qid == qid:
# self.default_fw_netvm_qid = None
# if self.clockvm_qid == qid:
# self.clockvm_qid = None
# if self.updatevm_qid == qid:
# self.updatevm_qid = None
# if self.default_template_qid == qid:
# self.default_template_qid = None
#
# return super(QubesVmCollection, self).pop(qid)
@qubes.events.handler('domain-deleted')
def on_domain_deleted(self, event, vm):
if self.default_netvm == vm:
del self.default_netvm
if self.default_fw_netvm == vm:
del self.default_fw_netvm
if self.clockvm == vm:
del self.clockvm
if self.updatevm == vm:
del self.updatevm
if self.default_template == vm:
del self.default_template
return super(QubesVmCollection, self).pop(qid)
# load plugins

View File

@ -10,6 +10,7 @@ particulary our custom Sphinx extension.
import csv
import posixpath
import re
import sys
import urllib2
@ -18,7 +19,9 @@ import docutils.nodes
import docutils.parsers.rst
import docutils.parsers.rst.roles
import docutils.statemachine
import sphinx
import sphinx.locale
import sphinx.util.docfields
def fetch_ticket_info(uri):
'''Fetch info about particular trac ticket given
@ -116,6 +119,30 @@ class VersionCheck(docutils.parsers.rst.Directive):
return [node]
#
# this is lifted from sphinx' own conf.py
#
event_sig_re = re.compile(r'([a-zA-Z-]+)\s*\((.*)\)')
def parse_event(env, sig, signode):
m = event_sig_re.match(sig)
if not m:
signode += sphinx.addnodes.desc_name(sig, sig)
return sig
name, args = m.groups()
signode += sphinx.addnodes.desc_name(name, name)
plist = sphinx.addnodes.desc_parameterlist()
for arg in args.split(','):
arg = arg.strip()
plist += sphinx.addnodes.desc_parameter(arg, arg)
signode += plist
return name
#
# end of codelifting
#
def setup(app):
app.add_role('ticket', ticket)
app.add_config_value('ticket_base_uri', 'https://wiki.qubes-os.org/ticket/', 'env')
@ -124,5 +151,10 @@ def setup(app):
man=(visit, depart))
app.add_directive('versioncheck', VersionCheck)
fdesc = sphinx.util.docfields.GroupedField('parameter', label='Parameters',
names=['param'], can_collapse=True)
app.add_object_type('event', 'event', 'pair: %s; event', parse_event,
doc_field_types=[fdesc])
# vim: ts=4 sw=4 et

View File

@ -57,6 +57,8 @@ class Emitter(object):
def __init__(self, *args, **kwargs):
super(Emitter, self).__init__(*args, **kwargs)
self.events_enabled = True
try:
propnames = set(prop.__name__ for prop in self.get_props_list())
except AttributeError:
@ -95,6 +97,9 @@ class Emitter(object):
different events.
'''
if not self.events_enabled:
return
for handler in self.__handlers__[event]:
if hasattr(handler, 'ha_bound'):
# this is our (bound) method, self is implicit

View File

@ -68,7 +68,7 @@ class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
cls.__hooks__ = collections.defaultdict(list)
class BaseVM(qubes.PropertyHolder, qubes.events.Emitter):
class BaseVM(qubes.PropertyHolder):
'''Base class for all VMs
:param app: Qubes application context
@ -90,6 +90,7 @@ class BaseVM(qubes.PropertyHolder, qubes.events.Emitter):
self.devices = collections.defaultdict(list) if devices is None else devices
self.tags = tags
self.events_enabled = False
all_names = set(prop.__name__ for prop in self.get_props_list(load_stage=2))
for key in list(kwargs.keys()):
if not key in all_names:
@ -101,6 +102,10 @@ class BaseVM(qubes.PropertyHolder, qubes.events.Emitter):
super(BaseVM, self).__init__(xml, *args, **kwargs)
self.events_enabled = True
self.fire_event('property-load')
def add_new_vm(self, vm):
'''Add new Virtual Machine to colletion