123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- #!/usr/bin/python2 -O
- '''Qubes Virtual Machines
- Main public classes
- -------------------
- .. autoclass:: BaseVM
- :members:
- :show-inheritance:
- .. autoclass:: property
- :members:
- :show-inheritance:
- Helper classes and functions
- ----------------------------
- .. autoclass:: VMPlugin
- :members:
- :show-inheritance:
- Particular VM classes
- ---------------------
- Main types:
- .. toctree::
- :maxdepth: 1
- qubesvm
- appvm
- templatevm
- Special VM types:
- .. toctree::
- :maxdepth: 1
- netvm
- proxyvm
- dispvm
- adminvm
- HVMs:
- .. toctree::
- :maxdepth: 1
- hvm
- templatehvm
- '''
- import ast
- import collections
- import functools
- import sys
- import dateutil.parser
- import qubes.plugins
- class property(object):
- '''Qubes VM property.
- This class holds one property that can be saved and loaded from qubes.xml
- :param str name: name of the property
- :param object default: default value
- :param type type: if not :py:obj:`None`, this is used to initialise value
- :param int order: order of evaluation (bigger order values are later)
- :param str doc: docstring
- '''
- def __init__(self, name, default=None, type=None, order=0, doc=None):
- self.__name__ = name
- self._default = default
- self._type = type
- self.order = order
- self.__doc__ = doc
- self._attr_name = '_qubesprop_' + self.__name__
- def __get__(self, instance, owner):
- if instance is None:
- return self
- try:
- return getattr(instance, self._attr_name)
- except AttributeError:
- if self._default is None:
- raise AttributeError('property not set')
- else:
- return self._default
- def __set__(self, instance, value):
- setattr(instance, self._attr_name,
- (self._type(value) if self._type is not None else value))
- def __repr__(self):
- return '<{} object at {:#x} name={!r} default={!r}>'.format(
- self.__class__.__name__, id(self), self.__name__, self._default)
- def __hash__(self):
- return hash(self.__name__)
- def __eq__(self, other):
- return self.__name__ == other.__name__
- class VMPlugin(qubes.plugins.Plugin):
- '''Metaclass for :py:class:`.BaseVM`'''
- def __init__(cls, name, bases, dict_):
- super(VMPlugin, cls).__init__(name, bases, dict_)
- cls.__hooks__ = collections.defaultdict(list)
- class BaseVM(object):
- '''Base class for all VMs
- :param xml: xml node from which to deserialise
- :type xml: :py:class:`lxml.etree._Element` or :py:obj:`None`
- This class is responsible for serialising and deserialising machines and
- provides basic framework. It contains no management logic. For that, see
- :py:class:`qubes.vm.qubesvm.QubesVM`.
- '''
- __metaclass__ = VMPlugin
- def get_props_list(self):
- '''List all properties attached to this VM'''
- props = set()
- for class_ in self.__class__.__mro__:
- props.update(prop for prop in class_.__dict__.values()
- if isinstance(prop, property))
- return sorted(props, key=lambda prop: (prop.order, prop.__name__))
- def __init__(self, xml):
- self._xml = xml
- self.services = {}
- self.devices = collections.defaultdict(list)
- self.tags = {}
- if self._xml is None:
- return
- # properties
- all_names = set(prop.__name__ for prop in self.get_props_list())
- for node in self._xml.xpath('.//property'):
- name = node.get('name')
- value = node.get('ref') or node.text
- if not name in all_names:
- raise AttributeError(
- 'No property {!r} found in {!r}'.format(
- name, self.__class__))
- setattr(self, name, value)
- # tags
- for node in self._xml.xpath('.//tag'):
- self.tags[node.get('name')] = node.text
- # services
- for node in self._xml.xpath('.//service'):
- self.services[node.text] = bool(ast.literal_eval(node.get('enabled', 'True')))
- # devices (pci, usb, ...)
- for parent in self._xml.xpath('.//devices'):
- devclass = parent.get('class')
- for node in parent.xpath('./device'):
- self.devices[devclass].append(node.text)
- # firewall
- #TODO
- def __repr__(self):
- return '<{} object at {:#x} {}>'.format(
- self.__class__.__name__, id(self),
- ' '.join('{}={}'.format(prop.__name__, getattr(self, prop.__name__))
- for prop in self.get_props_list()))
- @classmethod
- def add_hook(cls, event, f):
- '''Add hook to entire VM class and all subclasses
- :param str event: event type
- :param callable f: function to fire on event
- Prototype of the function depends on the exact type of event. Classes
- which inherit from this class will also inherit the hook.
- '''
- cls.__hooks__[event].append(f)
- def fire_hooks(self, event, *args, **kwargs):
- '''Fire hooks associated with an event
- :param str event: event type
- *args* and *kwargs* are passed to each function
- '''
- for cls in self.__class__.__mro__:
- if not hasattr(cls, '__hooks__'): continue
- for hook in cls.__hooks__[event]:
- hook(self, *args, **kwargs)
- def load(class_, D):
- cls = BaseVM[class_]
- return cls(D)
- __all__ = qubes.plugins.load(__file__)
|