qubes: fix VM instantiation and loading

This commit is contained in:
Wojtek Porczyk 2015-01-21 15:24:29 +01:00
parent 8c437f4053
commit 45977fc873
4 changed files with 62 additions and 80 deletions

View File

@ -807,13 +807,20 @@ class PropertyHolder(qubes.events.Emitter):
Members: Members:
''' '''
def __init__(self, xml, *args, **kwargs): def __init__(self, xml, **kwargs):
super(PropertyHolder, self).__init__(*args)
self.xml = xml self.xml = xml
for key, value in kwargs.items(): for key, value in kwargs.items():
setattr(self, key, value) setattr(self, key, value)
all_names = set(prop.__name__ for prop in self.property_list())
for key in list(kwargs.keys()):
if not key in all_names:
continue
setattr(self, key, kwargs.pop(key))
super(PropertyHolder, self).__init__(**kwargs)
@classmethod @classmethod
def property_list(cls, load_stage=None): def property_list(cls, load_stage=None):
@ -889,10 +896,9 @@ class PropertyHolder(qubes.events.Emitter):
``property-set`` events are not fired for each individual property. ``property-set`` events are not fired for each individual property.
:param lxml.etree._Element xml: XML node reference :param int load_stage: Stage of loading.
''' '''
self.events_enabled = False
all_names = set( all_names = set(
prop.__name__ for prop in self.property_list(load_stage)) prop.__name__ for prop in self.property_list(load_stage))
for node in self.xml.xpath('./properties/property'): for node in self.xml.xpath('./properties/property'):
@ -900,15 +906,10 @@ class PropertyHolder(qubes.events.Emitter):
value = node.get('ref') or node.text value = node.get('ref') or node.text
if not name in all_names: if not name in all_names:
raise AttributeError( continue
'No property {!r} found in {!r}'.format(
name, self.__class__))
setattr(self, name, value) setattr(self, name, value)
self.events_enabled = True
self.fire_event('property-loaded')
def xml_properties(self, with_defaults=False): def xml_properties(self, with_defaults=False):
'''Iterator that yields XML nodes representing set properties. '''Iterator that yields XML nodes representing set properties.
@ -1202,8 +1203,10 @@ class Qubes(PropertyHolder):
# stage 2: load VMs # stage 2: load VMs
for node in self._xml.xpath('./domains/domain'): for node in self._xml.xpath('./domains/domain'):
cls = qubes.vm.load(node.get("class")) # pylint: disable=no-member
vm = cls.fromxml(self, node) cls = qubes.vm.BaseVM.register[node.get('class')]
vm = cls(self, node)
vm.load_properties(load_stage=2)
self.domains.add(vm) self.domains.add(vm)
if not 0 in self.domains: if not 0 in self.domains:
@ -1236,6 +1239,10 @@ class Qubes(PropertyHolder):
else: else:
self.clockvm.services['ntpd'] = False self.clockvm.services['ntpd'] = False
for vm in self.domains:
vm.events_enabled = True
vm.fire_event('domain-loaded')
def _init(self): def _init(self):
self._open_store() self._open_store()

View File

@ -41,8 +41,6 @@ class Plugin(type):
# we've got root class # we've got root class
cls.register = {} cls.register = {}
def __getitem__(cls, name):
return cls.register[name]
def load(modfile): def load(modfile):
'''Load (import) all plugins from subpackage. '''Load (import) all plugins from subpackage.

View File

@ -136,7 +136,8 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
def test_000_load(self): def test_000_load(self):
node = self.xml.xpath('//domain')[0] node = self.xml.xpath('//domain')[0]
vm = TestVM.fromxml(None, node) vm = TestVM(None, node)
vm.load_properties(load_stage=None)
self.assertEqual(vm.qid, 1) self.assertEqual(vm.qid, 1)
self.assertEqual(vm.testprop, 'testvalue') self.assertEqual(vm.testprop, 'testvalue')
@ -144,13 +145,15 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
self.assertEqual(vm.testlabel, 'label-1') self.assertEqual(vm.testlabel, 'label-1')
self.assertEqual(vm.defaultprop, 'defaultvalue') self.assertEqual(vm.defaultprop, 'defaultvalue')
self.assertEqual(vm.tags, {'testtag': 'tagvalue'}) self.assertEqual(vm.tags, {'testtag': 'tagvalue'})
self.assertEqual(vm.devices, {'pci': ['00:11.22']})
self.assertEqual(vm.services, { self.assertEqual(vm.services, {
'testservice': True, 'testservice': True,
'enabledservice': True, 'enabledservice': True,
'disabledservice': False, 'disabledservice': False,
}) })
self.assertItemsEqual(vm.devices.keys(), ('pci',))
self.assertItemsEqual(vm.devices['pci'], ('00:11.22',))
self.assertXMLIsValid(vm.__xml__(), 'domain.rng') self.assertXMLIsValid(vm.__xml__(), 'domain.rng')
def test_001_nxproperty(self): def test_001_nxproperty(self):
@ -159,6 +162,8 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
<domains> <domains>
<domain id="domain-1" class="TestVM"> <domain id="domain-1" class="TestVM">
<properties> <properties>
<property name="qid">1</property>
<property name="name">domain1</property>
<property name="nxproperty">nxvalue</property> <property name="nxproperty">nxvalue</property>
</properties> </properties>
</domain> </domain>
@ -168,5 +173,5 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
node = xml.xpath('//domain')[0] node = xml.xpath('//domain')[0]
with self.assertRaises(AttributeError): with self.assertRaises(TypeError):
TestVM.fromxml(None, node) TestVM(None, node)

View File

@ -47,9 +47,6 @@ import qubes.plugins
class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta): class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
'''Metaclass for :py:class:`.BaseVM`''' '''Metaclass for :py:class:`.BaseVM`'''
def __init__(cls, name, bases, dict_):
super(BaseVMMeta, cls).__init__(name, bases, dict_)
cls.__hooks__ = collections.defaultdict(list)
class DeviceCollection(object): class DeviceCollection(object):
@ -122,7 +119,8 @@ class DeviceManager(dict):
self._vm = vm self._vm = vm
def __missing__(self, key): def __missing__(self, key):
return DeviceCollection(self._vm, key) self[key] = DeviceCollection(self._vm, key)
return self[key]
class BaseVM(qubes.PropertyHolder): class BaseVM(qubes.PropertyHolder):
@ -142,8 +140,12 @@ class BaseVM(qubes.PropertyHolder):
__metaclass__ = BaseVMMeta __metaclass__ = BaseVMMeta
def __init__(self, app, xml, services=None, devices=None, tags=None, def __init__(self, app, xml, services=None, devices=None, tags=None,
*args, **kwargs): **kwargs):
# pylint: disable=redefined-outer-name # pylint: disable=redefined-outer-name
self.events_enabled = False
super(BaseVM, self).__init__(xml, **kwargs)
#: mother :py:class:`qubes.Qubes` object #: mother :py:class:`qubes.Qubes` object
self.app = app self.app = app
@ -157,21 +159,35 @@ class BaseVM(qubes.PropertyHolder):
#: user-specified tags #: user-specified tags
self.tags = tags or {} self.tags = tags or {}
self.events_enabled = False if self.xml is not None:
all_names = set(prop.__name__ # services
for prop in self.property_list(load_stage=2)) for node in xml.xpath('./services/service'):
for key in list(kwargs.keys()): self.services[node.text] = bool(
if not key in all_names: ast.literal_eval(node.get('enabled', 'True').capitalize()))
raise AttributeError(
'No property {!r} found in {!r}'.format(
key, self.__class__))
setattr(self, key, kwargs[key])
del kwargs[key]
super(BaseVM, self).__init__(xml, *args, **kwargs) # devices (pci, usb, ...)
for parent in xml.xpath('./devices'):
devclass = parent.get('class')
for node in parent.xpath('./device'):
self.devices[devclass].attach(node.text)
self.events_enabled = True # tags
self.fire_event('property-load') for node in xml.xpath('./tags/tag'):
self.tags[node.get('name')] = node.text
# TODO: firewall, policy
# check if properties are appropriate
all_names = set(prop.__name__ for prop in self.property_list())
sys.stderr.write('all_names={!r}\n'.format(all_names))
for node in self.xml.xpath('./properties/property'):
name = node.get('name')
if not name in all_names:
raise TypeError(
'property {!r} not applicable to {!r}'.format(
name, self.__class__.__name__))
def add_new_vm(self, vm): def add_new_vm(self, vm):
@ -211,47 +227,6 @@ class BaseVM(qubes.PropertyHolder):
return 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
''' # pylint: disable=redefined-outer-name
if xml is None:
return cls(app)
services = {}
devices = collections.defaultdict(list)
tags = {}
# services
for node in xml.xpath('./services/service'):
services[node.text] = bool(
ast.literal_eval(node.get('enabled', 'True')))
# devices (pci, usb, ...)
for parent in xml.xpath('./devices'):
devclass = parent.get('class')
for node in parent.xpath('./device'):
devices[devclass].append(node.text)
# tags
for node in xml.xpath('./tags/tag'):
tags[node.get('name')] = node.text
# 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): def __xml__(self):
element = lxml.etree.Element('domain') element = lxml.etree.Element('domain')
@ -614,9 +589,6 @@ class BaseVM(qubes.PropertyHolder):
return conf return conf
def load(class_, D):
cls = BaseVM[class_]
return cls(D)
__all__ = ['BaseVMMeta', 'DeviceCollection', 'DeviceManager', 'BaseVM'] \ __all__ = ['BaseVMMeta', 'DeviceCollection', 'DeviceManager', 'BaseVM'] \
+ qubes.plugins.load(__file__) + qubes.plugins.load(__file__)