diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index 52dca286..dc7423a5 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -51,6 +51,7 @@ HVMs: ''' +import ast import collections import functools import sys @@ -115,6 +116,16 @@ class VMPlugin(qubes.plugins.Plugin): 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): @@ -125,10 +136,45 @@ class BaseVM(object): if isinstance(prop, property)) return sorted(props, key=lambda prop: (prop.order, prop.__name__)) - def __init__(self, D): - for prop in self.get_props_list(): - if prop.__name__ in D: - setattr(self, prop.__name__, D[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( @@ -150,7 +196,7 @@ class BaseVM(object): cls.__hooks__[event].append(f) def fire_hooks(self, event, *args, **kwargs): - '''Fire hooks associated with an event. + '''Fire hooks associated with an event :param str event: event type diff --git a/tests/vm.py b/tests/vm.py new file mode 100644 index 00000000..273875e8 --- /dev/null +++ b/tests/vm.py @@ -0,0 +1,88 @@ +#!/usr/bin/python2 -O + +import sys +import unittest + +import lxml.etree + +sys.path.insert(0, '../') +import qubes.vm + +class TestVM(qubes.vm.BaseVM): + testprop = qubes.vm.property('testprop') + testlabel = qubes.vm.property('testlabel') + defaultprop = qubes.vm.property('defaultprop', default='defaultvalue') + +class TC_BaseVM(unittest.TestCase): + def setUp(self): + self.xml = lxml.etree.XML(''' + + + + + + + + + testvalue + + + + + tagvalue + + + + testservice + enabledservice + disabledservice + + + + 00:11.22 + + + + + + + + + + + ''') + + def test_000_BaseVM_load(self): + node = self.xml.xpath('//domain')[0] + vm = TestVM(node) + + self.assertEqual(vm.testprop, 'testvalue') + self.assertEqual(vm.testlabel, 'label-1') + self.assertEqual(vm.defaultprop, 'defaultvalue') + self.assertEqual(vm.tags, {'testtag': 'tagvalue'}) + self.assertEqual(vm.devices, {'pci': ['00:11.22']}) + self.assertEqual(vm.services, { + 'testservice': True, + 'enabledservice': True, + 'disabledservice': False, + }) + + def test_001_BaseVM_nxproperty(self): + xml = lxml.etree.XML(''' + + + + + nxvalue + + + + + ''') + + node = xml.xpath('//domain')[0] + + def f(): + vm = TestVM(node) + + self.assertRaises(AttributeError, f)