Browse Source

qubes: fix VM instantiation and loading

Wojtek Porczyk 9 years ago
parent
commit
45977fc873
4 changed files with 62 additions and 80 deletions
  1. 19 12
      qubes/__init__.py
  2. 0 2
      qubes/plugins.py
  3. 9 4
      qubes/tests/vm/init.py
  4. 34 62
      qubes/vm/__init__.py

+ 19 - 12
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()

+ 0 - 2
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.

+ 9 - 4
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):
     <domains>
         <domain id="domain-1" class="TestVM">
             <properties>
+                <property name="qid">1</property>
+                <property name="name">domain1</property>
                 <property name="nxproperty">nxvalue</property>
             </properties>
         </domain>
@@ -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)

+ 34 - 62
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()))
+
+            # 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)
 
-        super(BaseVM, self).__init__(xml, *args, **kwargs)
+            # tags
+            for node in xml.xpath('./tags/tag'):
+                self.tags[node.get('name')] = node.text
 
-        self.events_enabled = True
-        self.fire_event('property-load')
+            # 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__)