diff --git a/qubes/__init__.py b/qubes/__init__.py index 4f030594..ccc73c69 100644 --- a/qubes/__init__.py +++ b/qubes/__init__.py @@ -807,13 +807,20 @@ class PropertyHolder(qubes.events.Emitter): Members: ''' - def __init__(self, xml, *args, **kwargs): - super(PropertyHolder, self).__init__(*args) + def __init__(self, xml, **kwargs): self.xml = xml for key, value in kwargs.items(): 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 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. - :param lxml.etree._Element xml: XML node reference + :param int load_stage: Stage of loading. ''' - self.events_enabled = False all_names = set( prop.__name__ for prop in self.property_list(load_stage)) for node in self.xml.xpath('./properties/property'): @@ -900,15 +906,10 @@ class PropertyHolder(qubes.events.Emitter): 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__)) + continue setattr(self, name, value) - self.events_enabled = True - self.fire_event('property-loaded') - def xml_properties(self, with_defaults=False): '''Iterator that yields XML nodes representing set properties. @@ -1202,8 +1203,10 @@ class Qubes(PropertyHolder): # stage 2: load VMs for node in self._xml.xpath('./domains/domain'): - cls = qubes.vm.load(node.get("class")) - vm = cls.fromxml(self, node) + # pylint: disable=no-member + cls = qubes.vm.BaseVM.register[node.get('class')] + vm = cls(self, node) + vm.load_properties(load_stage=2) self.domains.add(vm) if not 0 in self.domains: @@ -1236,6 +1239,10 @@ class Qubes(PropertyHolder): else: self.clockvm.services['ntpd'] = False + for vm in self.domains: + vm.events_enabled = True + vm.fire_event('domain-loaded') + def _init(self): self._open_store() diff --git a/qubes/plugins.py b/qubes/plugins.py index 618237bf..453b9f03 100644 --- a/qubes/plugins.py +++ b/qubes/plugins.py @@ -41,8 +41,6 @@ class Plugin(type): # we've got root class cls.register = {} - def __getitem__(cls, name): - return cls.register[name] def load(modfile): '''Load (import) all plugins from subpackage. diff --git a/qubes/tests/vm/init.py b/qubes/tests/vm/init.py index ec86dc4a..865d6471 100644 --- a/qubes/tests/vm/init.py +++ b/qubes/tests/vm/init.py @@ -136,7 +136,8 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase): def test_000_load(self): 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.testprop, 'testvalue') @@ -144,13 +145,15 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase): 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, }) + self.assertItemsEqual(vm.devices.keys(), ('pci',)) + self.assertItemsEqual(vm.devices['pci'], ('00:11.22',)) + self.assertXMLIsValid(vm.__xml__(), 'domain.rng') def test_001_nxproperty(self): @@ -159,6 +162,8 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase): + 1 + domain1 nxvalue @@ -168,5 +173,5 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase): node = xml.xpath('//domain')[0] - with self.assertRaises(AttributeError): - TestVM.fromxml(None, node) + with self.assertRaises(TypeError): + TestVM(None, node) diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index d62cfc96..f48a0844 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -47,9 +47,6 @@ import qubes.plugins class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta): '''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): @@ -122,7 +119,8 @@ class DeviceManager(dict): self._vm = vm def __missing__(self, key): - return DeviceCollection(self._vm, key) + self[key] = DeviceCollection(self._vm, key) + return self[key] class BaseVM(qubes.PropertyHolder): @@ -142,8 +140,12 @@ class BaseVM(qubes.PropertyHolder): __metaclass__ = BaseVMMeta def __init__(self, app, xml, services=None, devices=None, tags=None, - *args, **kwargs): + **kwargs): # pylint: disable=redefined-outer-name + + self.events_enabled = False + super(BaseVM, self).__init__(xml, **kwargs) + #: mother :py:class:`qubes.Qubes` object self.app = app @@ -157,21 +159,35 @@ class BaseVM(qubes.PropertyHolder): #: user-specified tags self.tags = tags or {} - self.events_enabled = False - all_names = set(prop.__name__ - for prop in self.property_list(load_stage=2)) - for key in list(kwargs.keys()): - if not key in all_names: - raise AttributeError( - 'No property {!r} found in {!r}'.format( - key, self.__class__)) - setattr(self, key, kwargs[key]) - del kwargs[key] + if self.xml is not None: + # services + for node in xml.xpath('./services/service'): + self.services[node.text] = bool( + ast.literal_eval(node.get('enabled', 'True').capitalize())) - 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 - self.fire_event('property-load') + # tags + 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): @@ -211,47 +227,6 @@ class BaseVM(qubes.PropertyHolder): 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): element = lxml.etree.Element('domain') @@ -614,9 +589,6 @@ class BaseVM(qubes.PropertyHolder): return conf -def load(class_, D): - cls = BaseVM[class_] - return cls(D) __all__ = ['BaseVMMeta', 'DeviceCollection', 'DeviceManager', 'BaseVM'] \ + qubes.plugins.load(__file__)