Bläddra i källkod

Merge branch 'services'

* services:
  tests: check clockvm-related handlers
  doc: include list of extensions
  qubesvm: fix docstring
  ext/services: move exporting 'service.*' features to extensions
  app: update handling features/service os ClockVM
Marek Marczykowski-Górecki 6 år sedan
förälder
incheckning
36f1a3abaf
6 ändrade filer med 113 tillägg och 28 borttagningar
  1. 14 0
      doc/qubes-ext.rst
  2. 8 18
      qubes/app.py
  3. 62 0
      qubes/ext/services.py
  4. 27 1
      qubes/tests/app.py
  5. 1 9
      qubes/vm/qubesvm.py
  6. 1 0
      rpm_spec/core-dom0.spec

+ 14 - 0
doc/qubes-ext.rst

@@ -1,8 +1,22 @@
 :py:mod:`qubes.ext` -- Qubes extensions
 =======================================
 
+Module contents
+---------------
+
 .. automodule:: qubes.ext
    :members:
    :show-inheritance:
 
+Extensions defined here
+-----------------------
+
+.. autoclass:: qubes.ext.admin.AdminExtension
+.. autoclass:: qubes.ext.block.BlockDeviceExtension
+.. autoclass:: qubes.ext.core_features.CoreFeatures
+.. autoclass:: qubes.ext.gui.GUI
+.. autoclass:: qubes.ext.pci.PCIDeviceExtension
+.. autoclass:: qubes.ext.r3compatibility.R3Compatibility
+.. autoclass:: qubes.ext.services.ServicesExtension
+
 .. vim: ts=3 sw=3 et

+ 8 - 18
qubes/app.py

@@ -680,7 +680,7 @@ class Qubes(qubes.PropertyHolder):
         doc='''Which VM to use as `yum` proxy for updating AdminVM and
             TemplateVMs''')
     clockvm = qubes.VMProperty('clockvm', load_stage=3,
-        allow_none=True,
+        default=None, allow_none=True,
         doc='Which VM to use as NTP proxy for updating AdminVM')
     default_kernel = qubes.property('default_kernel', load_stage=3,
         doc='Which kernel to use when not overriden in VM')
@@ -830,17 +830,6 @@ class Qubes(qubes.PropertyHolder):
         self.property_require('clockvm', allow_none=True)
         self.property_require('updatevm', allow_none=True)
 
-        # Disable ntpd in ClockVM - to not conflict with ntpdate (both are
-        # using 123/udp port)
-        if hasattr(self, 'clockvm') and self.clockvm is not None:
-            if self.clockvm.features.get('service.ntpd', False):
-                self.log.warning(
-                    'VM set as clockvm (%r) has enabled \'ntpd\' service! '
-                    'Expect failure when syncing time in dom0.',
-                    self.clockvm)
-            else:
-                self.clockvm.features['service.ntpd'] = ''
-
         for vm in self.domains:
             vm.events_enabled = True
             vm.fire_event('domain-load')
@@ -1173,13 +1162,14 @@ class Qubes(qubes.PropertyHolder):
         # pylint: disable=unused-argument,no-self-use
         if newvalue is None:
             return
-        if newvalue.features.get('service.ntpd', False):
-            raise qubes.exc.QubesVMError(newvalue,
-                'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
-                    newvalue.name, name))
-        else:
-            newvalue.features['service.ntpd'] = ''
+        if 'service.clocksync' not in newvalue.features:
+            newvalue.features['service.clocksync'] = True
 
+    @qubes.events.handler('property-set:clockvm')
+    def on_property_set_clockvm(self, event, name, newvalue, oldvalue=None):
+        # pylint: disable=unused-argument,no-self-use
+        if oldvalue and oldvalue.features.get('service.clocksync', False):
+            del oldvalue.features['service.clocksync']
 
     @qubes.events.handler(
         'property-pre-set:default_netvm',

+ 62 - 0
qubes/ext/services.py

@@ -0,0 +1,62 @@
+# -*- encoding: utf-8 -*-
+#
+# The Qubes OS Project, http://www.qubes-os.org
+#
+# Copyright (C) 2017 Marek Marczykowski-Górecki
+#                               <marmarek@invisiblethingslab.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+
+'''Extension responsible for qvm-service framework'''
+
+import qubes.ext
+
+class ServicesExtension(qubes.ext.Extension):
+    '''This extension export features with 'service.' prefix to QubesDB in
+    /qubes-service/ tree.
+    '''
+    # pylint: disable=no-self-use
+    @qubes.ext.handler('domain-qdb-create')
+    def on_domain_qdb_create(self, vm):
+        '''Actually export features'''
+        for feature, value in vm.features.items():
+            if not feature.startswith('service.'):
+                continue
+            service = feature[len('service.'):]
+            # forcefully convert to '0' or '1'
+            vm.untrusted_qdb.write('/qubes-service/{}'.format(service),
+                str(int(bool(value))))
+
+    @qubes.ext.handler('domain-feature-set')
+    def on_domain_feature_set(self, vm, feature, value, oldvalue=None):
+        '''Update /qubes-service/ QubesDB tree in runtime'''
+        # pylint: disable=unused-argument
+        if not vm.is_running():
+            return
+        if not feature.startswith('service.'):
+            return
+        service = feature[len('service.'):]
+        # forcefully convert to '0' or '1'
+        vm.untrusted_qdb.write('/qubes-service/{}'.format(service),
+            str(int(bool(value))))
+
+    @qubes.ext.handler('domain-feature-delete')
+    def on_domain_feature_delete(self, vm, feature):
+        '''Update /qubes-service/ QubesDB tree in runtime'''
+        if not vm.is_running():
+            return
+        if not feature.startswith('service.'):
+            return
+        service = feature[len('service.'):]
+        vm.untrusted_qdb.rm('/qubes-service/{}'.format(service))

+ 27 - 1
qubes/tests/app.py

@@ -261,15 +261,41 @@ class TC_30_VMCollection(qubes.tests.QubesTestCase):
 
 
 class TC_90_Qubes(qubes.tests.QubesTestCase):
+    def tearDown(self):
+        try:
+            os.unlink('/tmp/qubestest.xml')
+        except:
+            pass
+        super(TC_90_Qubes, self).tearDown()
+
     @qubes.tests.skipUnlessDom0
     def test_000_init_empty(self):
         # pylint: disable=no-self-use,unused-variable,bare-except
         try:
             os.unlink('/tmp/qubestest.xml')
-        except:
+        except FileNotFoundError:
             pass
         qubes.Qubes.create_empty_store('/tmp/qubestest.xml')
 
+    def test_100_clockvm(self):
+        app = qubes.Qubes('/tmp/qubestest.xml', load=False, offline_mode=True)
+        app.load_initial_values()
+
+        template = app.add_new_vm('TemplateVM', name='test-template',
+            label='green')
+        appvm = app.add_new_vm('AppVM', name='test-vm', template=template,
+            label='red')
+        self.assertIsNone(app.clockvm)
+        self.assertNotIn('service.clocksync', appvm.features)
+        self.assertNotIn('service.clocksync', template.features)
+        app.clockvm = appvm
+        self.assertIn('service.clocksync', appvm.features)
+        self.assertTrue(appvm.features['service.clocksync'])
+        app.clockvm = template
+        self.assertNotIn('service.clocksync', appvm.features)
+        self.assertIn('service.clocksync', template.features)
+        self.assertTrue(template.features['service.clocksync'])
+
     @qubes.tests.skipUnlessGit
     def test_900_example_xml_in_doc(self):
         self.assertXMLIsValid(

+ 1 - 9
qubes/vm/qubesvm.py

@@ -289,7 +289,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
             :param value: new value
             :param oldvalue: old value, if any
 
-        .. event:: domain-feature-delete (subject, event, key)
+        .. event:: domain-feature-delete (subject, event, feature)
 
             A feature was removed.
 
@@ -1746,14 +1746,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
         if tzname:
             self.untrusted_qdb.write('/qubes-timezone', tzname)
 
-        for feature, value in self.features.items():
-            if not feature.startswith('service.'):
-                continue
-            service = feature[len('service.'):]
-            # forcefully convert to '0' or '1'
-            self.untrusted_qdb.write('/qubes-service/{}'.format(service),
-                str(int(bool(value))))
-
         self.untrusted_qdb.write('/qubes-block-devices', '')
 
         self.untrusted_qdb.write('/qubes-usb-devices', '')

+ 1 - 0
rpm_spec/core-dom0.spec

@@ -294,6 +294,7 @@ fi
 %{python3_sitelib}/qubes/ext/pci.py
 %{python3_sitelib}/qubes/ext/qubesmanager.py
 %{python3_sitelib}/qubes/ext/r3compatibility.py
+%{python3_sitelib}/qubes/ext/services.py
 
 %dir %{python3_sitelib}/qubes/tests
 %dir %{python3_sitelib}/qubes/tests/__pycache__