__init__.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/python2 -O
  2. '''Qubes Virtual Machines
  3. Main public classes
  4. -------------------
  5. .. autoclass:: BaseVM
  6. :members:
  7. :show-inheritance:
  8. .. autoclass:: property
  9. :members:
  10. :show-inheritance:
  11. Helper classes and functions
  12. ----------------------------
  13. .. autoclass:: VMPlugin
  14. :members:
  15. :show-inheritance:
  16. Particular VM classes
  17. ---------------------
  18. Main types:
  19. .. toctree::
  20. :maxdepth: 1
  21. qubesvm
  22. appvm
  23. templatevm
  24. Special VM types:
  25. .. toctree::
  26. :maxdepth: 1
  27. netvm
  28. proxyvm
  29. dispvm
  30. adminvm
  31. HVMs:
  32. .. toctree::
  33. :maxdepth: 1
  34. hvm
  35. templatehvm
  36. '''
  37. import ast
  38. import collections
  39. import functools
  40. import sys
  41. import dateutil.parser
  42. import qubes.plugins
  43. class property(object):
  44. '''Qubes VM property.
  45. This class holds one property that can be saved and loaded from qubes.xml
  46. :param str name: name of the property
  47. :param object default: default value
  48. :param type type: if not :py:obj:`None`, this is used to initialise value
  49. :param int order: order of evaluation (bigger order values are later)
  50. :param str doc: docstring
  51. '''
  52. def __init__(self, name, default=None, type=None, order=0, doc=None):
  53. self.__name__ = name
  54. self._default = default
  55. self._type = type
  56. self.order = order
  57. self.__doc__ = doc
  58. self._attr_name = '_qubesprop_' + self.__name__
  59. def __get__(self, instance, owner):
  60. if instance is None:
  61. return self
  62. try:
  63. return getattr(instance, self._attr_name)
  64. except AttributeError:
  65. if self._default is None:
  66. raise AttributeError('property not set')
  67. else:
  68. return self._default
  69. def __set__(self, instance, value):
  70. setattr(instance, self._attr_name,
  71. (self._type(value) if self._type is not None else value))
  72. def __repr__(self):
  73. return '<{} object at {:#x} name={!r} default={!r}>'.format(
  74. self.__class__.__name__, id(self), self.__name__, self._default)
  75. def __hash__(self):
  76. return hash(self.__name__)
  77. def __eq__(self, other):
  78. return self.__name__ == other.__name__
  79. class VMPlugin(qubes.plugins.Plugin):
  80. '''Metaclass for :py:class:`.BaseVM`'''
  81. def __init__(cls, name, bases, dict_):
  82. super(VMPlugin, cls).__init__(name, bases, dict_)
  83. cls.__hooks__ = collections.defaultdict(list)
  84. class BaseVM(object):
  85. '''Base class for all VMs
  86. :param xml: xml node from which to deserialise
  87. :type xml: :py:class:`lxml.etree._Element` or :py:obj:`None`
  88. This class is responsible for serialising and deserialising machines and
  89. provides basic framework. It contains no management logic. For that, see
  90. :py:class:`qubes.vm.qubesvm.QubesVM`.
  91. '''
  92. __metaclass__ = VMPlugin
  93. def get_props_list(self):
  94. '''List all properties attached to this VM'''
  95. props = set()
  96. for class_ in self.__class__.__mro__:
  97. props.update(prop for prop in class_.__dict__.values()
  98. if isinstance(prop, property))
  99. return sorted(props, key=lambda prop: (prop.order, prop.__name__))
  100. def __init__(self, xml):
  101. self._xml = xml
  102. self.services = {}
  103. self.devices = collections.defaultdict(list)
  104. self.tags = {}
  105. if self._xml is None:
  106. return
  107. # properties
  108. all_names = set(prop.__name__ for prop in self.get_props_list())
  109. for node in self._xml.xpath('.//property'):
  110. name = node.get('name')
  111. value = node.get('ref') or node.text
  112. if not name in all_names:
  113. raise AttributeError(
  114. 'No property {!r} found in {!r}'.format(
  115. name, self.__class__))
  116. setattr(self, name, value)
  117. # tags
  118. for node in self._xml.xpath('.//tag'):
  119. self.tags[node.get('name')] = node.text
  120. # services
  121. for node in self._xml.xpath('.//service'):
  122. self.services[node.text] = bool(ast.literal_eval(node.get('enabled', 'True')))
  123. # devices (pci, usb, ...)
  124. for parent in self._xml.xpath('.//devices'):
  125. devclass = parent.get('class')
  126. for node in parent.xpath('./device'):
  127. self.devices[devclass].append(node.text)
  128. # firewall
  129. #TODO
  130. def __repr__(self):
  131. return '<{} object at {:#x} {}>'.format(
  132. self.__class__.__name__, id(self),
  133. ' '.join('{}={}'.format(prop.__name__, getattr(self, prop.__name__))
  134. for prop in self.get_props_list()))
  135. @classmethod
  136. def add_hook(cls, event, f):
  137. '''Add hook to entire VM class and all subclasses
  138. :param str event: event type
  139. :param callable f: function to fire on event
  140. Prototype of the function depends on the exact type of event. Classes
  141. which inherit from this class will also inherit the hook.
  142. '''
  143. cls.__hooks__[event].append(f)
  144. def fire_hooks(self, event, *args, **kwargs):
  145. '''Fire hooks associated with an event
  146. :param str event: event type
  147. *args* and *kwargs* are passed to each function
  148. '''
  149. for cls in self.__class__.__mro__:
  150. if not hasattr(cls, '__hooks__'): continue
  151. for hook in cls.__hooks__[event]:
  152. hook(self, *args, **kwargs)
  153. def load(class_, D):
  154. cls = BaseVM[class_]
  155. return cls(D)
  156. __all__ = qubes.plugins.load(__file__)