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
This commit is contained in:
Marek Marczykowski-Górecki 2017-07-29 05:09:32 +02:00
commit 36f1a3abaf
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
6 changed files with 113 additions and 28 deletions

View File

@ -1,8 +1,22 @@
:py:mod:`qubes.ext` -- Qubes extensions :py:mod:`qubes.ext` -- Qubes extensions
======================================= =======================================
Module contents
---------------
.. automodule:: qubes.ext .. automodule:: qubes.ext
:members: :members:
:show-inheritance: :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 .. vim: ts=3 sw=3 et

View File

@ -680,7 +680,7 @@ class Qubes(qubes.PropertyHolder):
doc='''Which VM to use as `yum` proxy for updating AdminVM and doc='''Which VM to use as `yum` proxy for updating AdminVM and
TemplateVMs''') TemplateVMs''')
clockvm = qubes.VMProperty('clockvm', load_stage=3, 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') doc='Which VM to use as NTP proxy for updating AdminVM')
default_kernel = qubes.property('default_kernel', load_stage=3, default_kernel = qubes.property('default_kernel', load_stage=3,
doc='Which kernel to use when not overriden in VM') 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('clockvm', allow_none=True)
self.property_require('updatevm', 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: for vm in self.domains:
vm.events_enabled = True vm.events_enabled = True
vm.fire_event('domain-load') vm.fire_event('domain-load')
@ -1173,13 +1162,14 @@ class Qubes(qubes.PropertyHolder):
# pylint: disable=unused-argument,no-self-use # pylint: disable=unused-argument,no-self-use
if newvalue is None: if newvalue is None:
return return
if newvalue.features.get('service.ntpd', False): if 'service.clocksync' not in newvalue.features:
raise qubes.exc.QubesVMError(newvalue, newvalue.features['service.clocksync'] = True
'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
newvalue.name, name))
else:
newvalue.features['service.ntpd'] = ''
@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( @qubes.events.handler(
'property-pre-set:default_netvm', 'property-pre-set:default_netvm',

62
qubes/ext/services.py Normal file
View File

@ -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))

View File

@ -261,15 +261,41 @@ class TC_30_VMCollection(qubes.tests.QubesTestCase):
class TC_90_Qubes(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 @qubes.tests.skipUnlessDom0
def test_000_init_empty(self): def test_000_init_empty(self):
# pylint: disable=no-self-use,unused-variable,bare-except # pylint: disable=no-self-use,unused-variable,bare-except
try: try:
os.unlink('/tmp/qubestest.xml') os.unlink('/tmp/qubestest.xml')
except: except FileNotFoundError:
pass pass
qubes.Qubes.create_empty_store('/tmp/qubestest.xml') 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 @qubes.tests.skipUnlessGit
def test_900_example_xml_in_doc(self): def test_900_example_xml_in_doc(self):
self.assertXMLIsValid( self.assertXMLIsValid(

View File

@ -289,7 +289,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
:param value: new value :param value: new value
:param oldvalue: old value, if any :param oldvalue: old value, if any
.. event:: domain-feature-delete (subject, event, key) .. event:: domain-feature-delete (subject, event, feature)
A feature was removed. A feature was removed.
@ -1746,14 +1746,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if tzname: if tzname:
self.untrusted_qdb.write('/qubes-timezone', 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-block-devices', '')
self.untrusted_qdb.write('/qubes-usb-devices', '') self.untrusted_qdb.write('/qubes-usb-devices', '')

View File

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