core3: basic global events and their documentation
This commit is contained in:
parent
855a434879
commit
1a032ecf2a
12
doc/conf.py
12
doc/conf.py
@ -28,7 +28,17 @@ sys.path.insert(0, os.path.abspath('../'))
|
|||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# 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.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
@ -330,6 +330,9 @@ class VMCollection(object):
|
|||||||
if not isinstance(value, qubes.vm.BaseVM):
|
if not isinstance(value, qubes.vm.BaseVM):
|
||||||
raise TypeError('{} holds only BaseVM instances'.format(self.__class__.__name__))
|
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:
|
if value.qid in self:
|
||||||
raise ValueError('This collection already holds VM that has qid={!r} (!r)'.format(
|
raise ValueError('This collection already holds VM that has qid={!r} (!r)'.format(
|
||||||
value.qid, self[value.qid]))
|
value.qid, self[value.qid]))
|
||||||
@ -338,6 +341,7 @@ class VMCollection(object):
|
|||||||
value.name, self[value.name]))
|
value.name, self[value.name]))
|
||||||
|
|
||||||
self._dict[value.qid] = value
|
self._dict[value.qid] = value
|
||||||
|
self.app.fire_event('domain-added', value)
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
@ -359,7 +363,9 @@ class VMCollection(object):
|
|||||||
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
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):
|
def __contains__(self, key):
|
||||||
@ -467,12 +473,28 @@ class property(object):
|
|||||||
|
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
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:
|
if self._setter is not None:
|
||||||
value = self._setter(instance, self, value)
|
value = self._setter(instance, self, value)
|
||||||
if self._type is not None:
|
if self._type is not None:
|
||||||
value = self._type(value)
|
value = self._type(value)
|
||||||
|
|
||||||
instance._init_property(self, 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):
|
def __repr__(self):
|
||||||
return '<{} object at {:#x} name={!r} default={!r}>'.format(
|
return '<{} object at {:#x} name={!r} default={!r}>'.format(
|
||||||
@ -505,8 +527,27 @@ class property(object):
|
|||||||
prop.__name__, self.__class__.__name__))
|
prop.__name__, self.__class__.__name__))
|
||||||
|
|
||||||
|
|
||||||
class PropertyHolder(object):
|
class PropertyHolder(qubes.events.Emitter):
|
||||||
'''Abstract class for holding :py:class:`qubes.property`'''
|
'''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):
|
def __init__(self, xml, *args, **kwargs):
|
||||||
super(PropertyHolder, self).__init__(*args, **kwargs)
|
super(PropertyHolder, self).__init__(*args, **kwargs)
|
||||||
@ -545,11 +586,14 @@ class PropertyHolder(object):
|
|||||||
def load_properties(self, load_stage=None):
|
def load_properties(self, load_stage=None):
|
||||||
'''Load properties from immediate children of XML node.
|
'''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
|
: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))
|
# 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))
|
all_names = set(prop.__name__ for prop in self.get_props_list(load_stage))
|
||||||
# sys.stderr.write(' all_names={!r}\n'.format(all_names))
|
# sys.stderr.write(' all_names={!r}\n'.format(all_names))
|
||||||
for node in self.xml.xpath('./properties/property'):
|
for node in self.xml.xpath('./properties/property'):
|
||||||
@ -563,6 +607,9 @@ class PropertyHolder(object):
|
|||||||
name, self.__class__))
|
name, self.__class__))
|
||||||
|
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
|
|
||||||
|
self.events_enabled = True
|
||||||
|
self.fire_event('property-loaded')
|
||||||
# sys.stderr.write(' load_properties return\n')
|
# sys.stderr.write(' load_properties return\n')
|
||||||
|
|
||||||
|
|
||||||
@ -630,22 +677,45 @@ class Qubes(PropertyHolder):
|
|||||||
|
|
||||||
:param str store: path to ``qubes.xml``
|
: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
|
1. In the first stage there are loaded some basic features from store
|
||||||
(currently labels).
|
(currently labels).
|
||||||
|
|
||||||
In the second stage stubs for all VMs are loaded. They are filled with
|
2. In the second stage stubs for all VMs are loaded. They are filled
|
||||||
their basic properties, like ``qid`` and ``name``.
|
with their basic properties, like ``qid`` and ``name``.
|
||||||
|
|
||||||
In the third stage all global properties are loaded. They often reference
|
3. In the third stage all global properties are loaded. They often
|
||||||
VMs, like default netvm, so they should be filled after loading VMs.
|
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
|
4. In the fourth stage all remaining VM properties are loaded. They
|
||||||
all VMs loaded, because they represent dependencies between VMs like
|
also need all VMs loaded, because they represent dependencies
|
||||||
aforementioned netvm.
|
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,
|
default_netvm = VMProperty('default_netvm', load_stage=3,
|
||||||
@ -822,22 +892,16 @@ class Qubes(PropertyHolder):
|
|||||||
return labels
|
return labels
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def add_new_vm(self, vm):
|
def add_new_vm(self, vm):
|
||||||
'''Add new Virtual Machine to colletion
|
'''Add new Virtual Machine to colletion
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
if not hasattr(vm, 'qid'):
|
|
||||||
vm.qid = self.domains.get_new_unused_qid()
|
|
||||||
|
|
||||||
self.domains.add(vm)
|
self.domains.add(vm)
|
||||||
|
|
||||||
#
|
@qubes.events.handler('domain-added')
|
||||||
# XXX
|
def on_domain_addedd(self, event, vm):
|
||||||
# all this will be moved to an event handler
|
|
||||||
# and deduplicated with self.load()
|
|
||||||
#
|
|
||||||
|
|
||||||
# make first created NetVM the default one
|
# make first created NetVM the default one
|
||||||
if not hasattr(self, 'default_fw_netvm') \
|
if not hasattr(self, 'default_fw_netvm') \
|
||||||
and vm.provides_network \
|
and vm.provides_network \
|
||||||
@ -866,23 +930,21 @@ class Qubes(PropertyHolder):
|
|||||||
and hasattr(vm, 'netvm'):
|
and hasattr(vm, 'netvm'):
|
||||||
self.default_clockvm = vm
|
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
|
@qubes.events.handler('domain-deleted')
|
||||||
# def pop(self, qid):
|
def on_domain_deleted(self, event, vm):
|
||||||
# if self.default_netvm_qid == qid:
|
if self.default_netvm == vm:
|
||||||
# self.default_netvm_qid = None
|
del self.default_netvm
|
||||||
# if self.default_fw_netvm_qid == qid:
|
if self.default_fw_netvm == vm:
|
||||||
# self.default_fw_netvm_qid = None
|
del self.default_fw_netvm
|
||||||
# if self.clockvm_qid == qid:
|
if self.clockvm == vm:
|
||||||
# self.clockvm_qid = None
|
del self.clockvm
|
||||||
# if self.updatevm_qid == qid:
|
if self.updatevm == vm:
|
||||||
# self.updatevm_qid = None
|
del self.updatevm
|
||||||
# if self.default_template_qid == qid:
|
if self.default_template == vm:
|
||||||
# self.default_template_qid = None
|
del self.default_template
|
||||||
#
|
|
||||||
# return super(QubesVmCollection, self).pop(qid)
|
return super(QubesVmCollection, self).pop(qid)
|
||||||
|
|
||||||
|
|
||||||
# load plugins
|
# load plugins
|
||||||
|
@ -10,6 +10,7 @@ particulary our custom Sphinx extension.
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
import posixpath
|
import posixpath
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import urllib2
|
import urllib2
|
||||||
|
|
||||||
@ -18,7 +19,9 @@ import docutils.nodes
|
|||||||
import docutils.parsers.rst
|
import docutils.parsers.rst
|
||||||
import docutils.parsers.rst.roles
|
import docutils.parsers.rst.roles
|
||||||
import docutils.statemachine
|
import docutils.statemachine
|
||||||
|
import sphinx
|
||||||
import sphinx.locale
|
import sphinx.locale
|
||||||
|
import sphinx.util.docfields
|
||||||
|
|
||||||
def fetch_ticket_info(uri):
|
def fetch_ticket_info(uri):
|
||||||
'''Fetch info about particular trac ticket given
|
'''Fetch info about particular trac ticket given
|
||||||
@ -116,6 +119,30 @@ class VersionCheck(docutils.parsers.rst.Directive):
|
|||||||
return [node]
|
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):
|
def setup(app):
|
||||||
app.add_role('ticket', ticket)
|
app.add_role('ticket', ticket)
|
||||||
app.add_config_value('ticket_base_uri', 'https://wiki.qubes-os.org/ticket/', 'env')
|
app.add_config_value('ticket_base_uri', 'https://wiki.qubes-os.org/ticket/', 'env')
|
||||||
@ -124,5 +151,10 @@ def setup(app):
|
|||||||
man=(visit, depart))
|
man=(visit, depart))
|
||||||
app.add_directive('versioncheck', VersionCheck)
|
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
|
# vim: ts=4 sw=4 et
|
||||||
|
@ -57,6 +57,8 @@ class Emitter(object):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Emitter, self).__init__(*args, **kwargs)
|
super(Emitter, self).__init__(*args, **kwargs)
|
||||||
|
self.events_enabled = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
propnames = set(prop.__name__ for prop in self.get_props_list())
|
propnames = set(prop.__name__ for prop in self.get_props_list())
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@ -95,6 +97,9 @@ class Emitter(object):
|
|||||||
different events.
|
different events.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
if not self.events_enabled:
|
||||||
|
return
|
||||||
|
|
||||||
for handler in self.__handlers__[event]:
|
for handler in self.__handlers__[event]:
|
||||||
if hasattr(handler, 'ha_bound'):
|
if hasattr(handler, 'ha_bound'):
|
||||||
# this is our (bound) method, self is implicit
|
# this is our (bound) method, self is implicit
|
||||||
|
@ -68,7 +68,7 @@ class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
|
|||||||
cls.__hooks__ = collections.defaultdict(list)
|
cls.__hooks__ = collections.defaultdict(list)
|
||||||
|
|
||||||
|
|
||||||
class BaseVM(qubes.PropertyHolder, qubes.events.Emitter):
|
class BaseVM(qubes.PropertyHolder):
|
||||||
'''Base class for all VMs
|
'''Base class for all VMs
|
||||||
|
|
||||||
:param app: Qubes application context
|
: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.devices = collections.defaultdict(list) if devices is None else devices
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
|
|
||||||
|
self.events_enabled = False
|
||||||
all_names = set(prop.__name__ for prop in self.get_props_list(load_stage=2))
|
all_names = set(prop.__name__ for prop in self.get_props_list(load_stage=2))
|
||||||
for key in list(kwargs.keys()):
|
for key in list(kwargs.keys()):
|
||||||
if not key in all_names:
|
if not key in all_names:
|
||||||
@ -101,6 +102,10 @@ class BaseVM(qubes.PropertyHolder, qubes.events.Emitter):
|
|||||||
|
|
||||||
super(BaseVM, self).__init__(xml, *args, **kwargs)
|
super(BaseVM, self).__init__(xml, *args, **kwargs)
|
||||||
|
|
||||||
|
self.events_enabled = True
|
||||||
|
self.fire_event('property-load')
|
||||||
|
|
||||||
|
|
||||||
def add_new_vm(self, vm):
|
def add_new_vm(self, vm):
|
||||||
'''Add new Virtual Machine to colletion
|
'''Add new Virtual Machine to colletion
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user