2014-11-13 14:38:41 +01:00
|
|
|
#!/usr/bin/python2 -O
|
|
|
|
|
2014-11-13 18:10:27 +01:00
|
|
|
'''Qubes Virtual Machines
|
|
|
|
|
|
|
|
Main public classes
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
.. autoclass:: BaseVM
|
|
|
|
:members:
|
|
|
|
:show-inheritance:
|
|
|
|
|
|
|
|
Helper classes and functions
|
|
|
|
----------------------------
|
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
.. autoclass:: BaseVMMeta
|
2014-11-13 18:10:27 +01:00
|
|
|
: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
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
2014-11-18 17:35:05 +01:00
|
|
|
import ast
|
2014-11-13 14:38:41 +01:00
|
|
|
import collections
|
|
|
|
import functools
|
|
|
|
import sys
|
|
|
|
|
|
|
|
import dateutil.parser
|
2014-12-05 14:58:05 +01:00
|
|
|
import lxml.etree
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
import qubes
|
2014-12-09 14:14:24 +01:00
|
|
|
import qubes.events
|
2014-11-13 14:38:41 +01:00
|
|
|
import qubes.plugins
|
|
|
|
|
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
|
2014-11-13 18:10:27 +01:00
|
|
|
'''Metaclass for :py:class:`.BaseVM`'''
|
2014-11-13 14:38:41 +01:00
|
|
|
def __init__(cls, name, bases, dict_):
|
2014-12-09 14:14:24 +01:00
|
|
|
super(BaseVMMeta, cls).__init__(name, bases, dict_)
|
2014-11-13 14:38:41 +01:00
|
|
|
cls.__hooks__ = collections.defaultdict(list)
|
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
class BaseVM(qubes.PropertyHolder, qubes.events.Emitter):
|
2014-11-18 17:35:05 +01:00
|
|
|
'''Base class for all VMs
|
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
:param app: Qubes application context
|
|
|
|
:type app: :py:class:`qubes.Qubes`
|
2014-11-18 17:35:05 +01:00
|
|
|
: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`.
|
|
|
|
'''
|
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
__metaclass__ = BaseVMMeta
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
def __init__(self, app, xml, load_stage=2, services={}, devices=None,
|
|
|
|
tags={}, *args, **kwargs):
|
2014-12-05 14:58:05 +01:00
|
|
|
self.app = app
|
|
|
|
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))
|
2014-12-09 14:14:24 +01:00
|
|
|
for key in list(kwargs.keys()):
|
2014-12-05 14:58:05 +01:00
|
|
|
if not key in all_names:
|
|
|
|
raise AttributeError(
|
|
|
|
'No property {!r} found in {!r}'.format(
|
|
|
|
key, self.__class__))
|
|
|
|
setattr(self, key, kwargs[key])
|
2014-12-09 14:14:24 +01:00
|
|
|
del kwargs[key]
|
2014-11-13 14:38:41 +01:00
|
|
|
|
2014-12-09 14:14:24 +01:00
|
|
|
super(BaseVM, self).__init__(xml, *args, **kwargs)
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
def add_new_vm(self, vm):
|
|
|
|
'''Add new Virtual Machine to colletion
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
'''
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
vm_cls = QubesVmClasses[vm_type]
|
|
|
|
if 'template' in kwargs:
|
|
|
|
if not vm_cls.is_template_compatible(kwargs['template']):
|
|
|
|
raise QubesException("Template not compatible with selected "
|
|
|
|
"VM type")
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
vm = vm_cls(qid=qid, collection=self, **kwargs)
|
|
|
|
if not self.verify_new_vm(vm):
|
|
|
|
raise QubesException("Wrong VM description!")
|
|
|
|
self[vm.qid] = vm
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
# make first created NetVM the default one
|
|
|
|
if self.default_fw_netvm_qid is None and vm.is_netvm():
|
|
|
|
self.set_default_fw_netvm(vm)
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
if self.default_netvm_qid is None and vm.is_proxyvm():
|
|
|
|
self.set_default_netvm(vm)
|
|
|
|
|
|
|
|
# make first created TemplateVM the default one
|
|
|
|
if self.default_template_qid is None and vm.is_template():
|
|
|
|
self.set_default_template(vm)
|
|
|
|
|
|
|
|
# make first created ProxyVM the UpdateVM
|
|
|
|
if self.updatevm_qid is None and vm.is_proxyvm():
|
|
|
|
self.set_updatevm_vm(vm)
|
|
|
|
|
|
|
|
# by default ClockVM is the first NetVM
|
|
|
|
if self.clockvm_qid is None and vm.is_netvm():
|
|
|
|
self.set_clockvm_vm(vm)
|
|
|
|
|
|
|
|
return vm
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def fromxml(cls, app, xml, load_stage=2):
|
|
|
|
'''Create VM from XML node
|
|
|
|
|
|
|
|
:param qubes.Qubes app: :py:class:`qubes.Qubes` application instance
|
|
|
|
:param lxml.etree._Element xml: XML node reference
|
|
|
|
:param int load_stage: do not change the default (2) unless you know, what you are doing
|
|
|
|
'''
|
|
|
|
|
|
|
|
# sys.stderr.write('{}.fromxml(app={!r}, xml={!r}, load_stage={})\n'.format(
|
|
|
|
# cls.__name__, app, xml, load_stage))
|
|
|
|
if xml is None:
|
|
|
|
return cls(app)
|
|
|
|
|
|
|
|
services = {}
|
|
|
|
devices = collections.defaultdict(list)
|
|
|
|
tags = {}
|
2014-11-18 17:35:05 +01:00
|
|
|
|
|
|
|
# services
|
2014-12-05 14:58:05 +01:00
|
|
|
for node in xml.xpath('./services/service'):
|
|
|
|
services[node.text] = bool(ast.literal_eval(node.get('enabled', 'True')))
|
2014-11-18 17:35:05 +01:00
|
|
|
|
|
|
|
# devices (pci, usb, ...)
|
2014-12-05 14:58:05 +01:00
|
|
|
for parent in xml.xpath('./devices'):
|
2014-11-18 17:35:05 +01:00
|
|
|
devclass = parent.get('class')
|
|
|
|
for node in parent.xpath('./device'):
|
2014-12-05 14:58:05 +01:00
|
|
|
devices[devclass].append(node.text)
|
|
|
|
|
|
|
|
# tags
|
|
|
|
for node in xml.xpath('./tags/tag'):
|
|
|
|
tags[node.get('name')] = node.text
|
2014-11-18 17:35:05 +01:00
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
# properties
|
|
|
|
self = cls(app, xml=xml, services=services, devices=devices, tags=tags)
|
|
|
|
self.load_properties(load_stage=load_stage)
|
|
|
|
|
|
|
|
# TODO: firewall, policy
|
|
|
|
|
|
|
|
# sys.stderr.write('{}.fromxml return\n'.format(cls.__name__))
|
|
|
|
return self
|
|
|
|
|
|
|
|
|
|
|
|
def __xml__(self):
|
|
|
|
element = lxml.etree.Element('domain', id='domain-' + str(self.qid))
|
|
|
|
|
|
|
|
element.append(self.save_properties())
|
|
|
|
|
|
|
|
services = lxml.etree.Element('services')
|
|
|
|
for service in self.services:
|
|
|
|
node = lxml.etree.Element('service')
|
|
|
|
node.text = service
|
|
|
|
if not self.services[service]:
|
|
|
|
node.set('enabled', 'False')
|
|
|
|
services.append(node)
|
|
|
|
element.append(services)
|
|
|
|
|
|
|
|
for devclass in self.devices:
|
|
|
|
devices = lxml.etree.Element('devices')
|
|
|
|
devices.set('class', devclass)
|
|
|
|
for device in self.devices[devclass]:
|
|
|
|
node = lxml.etree.Element('device')
|
|
|
|
node.text = device
|
|
|
|
devices.append(node)
|
|
|
|
element.append(devices)
|
|
|
|
|
|
|
|
tags = lxml.etree.Element('tags')
|
|
|
|
for tag in self.tags:
|
|
|
|
node = lxml.etree.Element('tag', name=tag)
|
|
|
|
node.text = self.tags[tag]
|
|
|
|
tags.append(node)
|
|
|
|
element.append(tags)
|
|
|
|
|
|
|
|
return element
|
2014-11-13 14:38:41 +01:00
|
|
|
|
|
|
|
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()))
|
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
|
2014-11-13 14:38:41 +01:00
|
|
|
@classmethod
|
|
|
|
def add_hook(cls, event, f):
|
2014-11-13 18:10:27 +01:00
|
|
|
'''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.
|
|
|
|
'''
|
|
|
|
|
2014-11-13 14:38:41 +01:00
|
|
|
cls.__hooks__[event].append(f)
|
|
|
|
|
2014-12-05 14:58:05 +01:00
|
|
|
|
2014-11-13 14:38:41 +01:00
|
|
|
def fire_hooks(self, event, *args, **kwargs):
|
2014-11-18 17:35:05 +01:00
|
|
|
'''Fire hooks associated with an event
|
2014-11-13 18:10:27 +01:00
|
|
|
|
|
|
|
:param str event: event type
|
|
|
|
|
|
|
|
*args* and *kwargs* are passed to each function
|
|
|
|
'''
|
|
|
|
|
2014-11-13 14:38:41 +01:00
|
|
|
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__)
|