qubes/vm: change services to features

This commit is contained in:
Wojtek Porczyk 2016-03-03 13:03:27 +01:00
parent bf78e662f6
commit 93686eae06
8 changed files with 144 additions and 72 deletions

View File

@ -16,10 +16,10 @@
<property name="label" ref="label-1" />
</properties>
<services>
<service enabled="false">meminfo-writer</service>
<service>qubes-firewall</service>
</services>
<features>
<feature name="meminfo-writer"></feature>
<feature name="qubes-firewall">1</feature>
</features>
<devices class="pci">
<device>01:23.45</device>

View File

@ -1262,13 +1262,12 @@ class Qubes(PropertyHolder):
# 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 'ntpd' in self.clockvm.services:
if self.clockvm.services['ntpd']:
self.log.warning("VM set as clockvm ({!r}) has enabled "
"'ntpd' service! Expect failure when syncing time in "
"dom0.".format(self.clockvm))
if self.clockvm.features.get('services/ntpd', False):
self.log.warning("VM set as clockvm ({!r}) has enabled 'ntpd' "
"service! Expect failure when syncing time in dom0.".format(
self.clockvm))
else:
self.clockvm.services['ntpd'] = False
self.clockvm.features['services/ntpd'] = ''
for vm in self.domains:
vm.events_enabled = True
@ -1461,13 +1460,12 @@ class Qubes(PropertyHolder):
# pylint: disable=unused-argument,no-self-use
if newvalue is None:
return
if 'ntpd' in newvalue.services:
if newvalue.services['ntpd']:
raise qubes.exc.QubesVMError(newvalue,
'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
newvalue.name, name))
if newvalue.features.get('services/ntpd', False):
raise qubes.exc.QubesVMError(newvalue,
'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
newvalue.name, name))
else:
newvalue.services['ntpd'] = False
newvalue.features['services/ntpd'] = ''
@qubes.events.handler(

View File

@ -178,7 +178,7 @@ class TC_01_Properties(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
self.assertEquals(testvm1.include_in_backups,
testvm2.include_in_backups)
self.assertEquals(testvm1.default_user, testvm2.default_user)
self.assertEquals(testvm1.services, testvm2.services)
self.assertEquals(testvm1.features, testvm2.features)
self.assertEquals(testvm1.get_firewall_conf(),
testvm2.get_firewall_conf())
@ -227,7 +227,7 @@ class TC_01_Properties(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
self.assertEquals(testvm1.include_in_backups,
testvm3.include_in_backups)
self.assertEquals(testvm1.default_user, testvm3.default_user)
self.assertEquals(testvm1.services, testvm3.services)
self.assertEquals(testvm1.features, testvm3.features)
self.assertEquals(testvm1.get_firewall_conf(),
testvm3.get_firewall_conf())

View File

@ -114,11 +114,11 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
<tag name="testtag">tagvalue</tag>
</tags>
<services>
<service>testservice</service>
<service enabled="True">enabledservice</service>
<service enabled="False">disabledservice</service>
</services>
<features>
<feature name="testfeature_none"/>
<feature name="testfeature_empty"></feature>
<feature name="testfeature_aqq">aqq</feature>
</features>
<devices class="pci">
<device>00:11.22</device>
@ -145,10 +145,10 @@ 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.services, {
'testservice': True,
'enabledservice': True,
'disabledservice': False,
self.assertEqual(vm.features, {
'testfeature_none': None,
'testfeature_empty': '',
'testfeature_aqq': 'aqq',
})
self.assertItemsEqual(vm.devices.keys(), ('pci',))

View File

@ -23,7 +23,7 @@
# TODO list available classes
# TODO list labels (maybe in qvm-prefs)
# TODO services, devices, tags
# TODO features, devices, tags
from __future__ import print_function

View File

@ -48,6 +48,85 @@ import qubes.plugins
import qubes.tools.qvm_ls
class Features(dict):
'''Manager of the features.
This class inherits from dict, but has most of the methods that manipulate
the item disarmed (they raise NotImplementedError). The ones that are left
fire appropriate events on the qube that owns an instance of this class.
'''
#
# Those are the methods that affect contents. Either disarm them or make
# them report appropriate events. Good approach is to rewrite them carefully
# using official documentation, but use only our (overloaded) methods.
#
def __init__(self, vm, other=None, **kwargs):
super(Features, self).__init__()
self.vm = vm
self.update(other, **kwargs)
def __delitem__(self, key):
super(Features, self).__delitem__(key)
self.vm.fire_event('domain-feature-delete', key)
def __setitem__(self, key, value):
self.vm.fire_event('domain-feature-set', key, value)
super(Features, self).__setitem__(key, value)
def clear(self):
for key in self:
del self[key]
def pop(self):
'''Not implemented
:raises: NotImplementedError
'''
raise NotImplementedError()
def popitem(self):
'''Not implemented
:raises: NotImplementedError
'''
raise NotImplementedError()
def setdefault(self):
'''Not implemented
:raises: NotImplementedError
'''
raise NotImplementedError()
def update(self, other=None, **kwargs):
if other is not None:
if hasattr(other, 'keys'):
for key in other:
self[key] = other[key]
else:
for key, value in other:
self[key] = value
for key in kwargs:
self[key] = kwargs[key]
#
# end of overriding
#
_NO_DEFAULT = object()
def check_with_template(self, feature, default=_NO_DEFAULT):
if feature in self:
return self[feature]
if hasattr(self.vm, 'template') and self.vm.template is not None \
and feature in self.vm.template.features:
return self.vm.template.features[feature]
if default is self._NO_DEFAULT:
raise KeyError(feature)
return default
class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
'''Metaclass for :py:class:`.BaseVM`'''
def __init__(cls, name, bases, dict_):
@ -55,7 +134,6 @@ class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
qubes.tools.qvm_ls.process_class(cls)
class DeviceCollection(object):
'''Bag for devices.
@ -146,7 +224,7 @@ class BaseVM(qubes.PropertyHolder):
__metaclass__ = BaseVMMeta
def __init__(self, app, xml, services=None, devices=None, tags=None,
def __init__(self, app, xml, features=None, devices=None, tags=None,
**kwargs):
# pylint: disable=redefined-outer-name
@ -157,8 +235,8 @@ class BaseVM(qubes.PropertyHolder):
super(BaseVM, self).__init__(xml, **kwargs)
#: dictionary of services that are run on this domain
self.services = services or {}
#: dictionary of features of this qube
self.features = Features(self, features)
#: :py:class:`DeviceManager` object keeping devices that are attached to
#: this domain
@ -168,10 +246,9 @@ class BaseVM(qubes.PropertyHolder):
self.tags = tags or {}
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()))
# features
for node in xml.xpath('./features/service'):
self.features[node.get('name')] = node.text
# devices (pci, usb, ...)
for parent in xml.xpath('./devices'):
@ -214,14 +291,12 @@ class BaseVM(qubes.PropertyHolder):
element.append(self.xml_properties())
services = lxml.etree.Element('services')
for service in self.services:
node = lxml.etree.Element('service')
node.text = service
if not self.services[service]:
node.set('enabled', 'false')
services.append(node)
element.append(services)
features = lxml.etree.Element('features')
for feature in self.features:
node = lxml.etree.Element('service', name=feature)
node.text = self.features[feature] if feature else None
features.append(node)
element.append(features)
for devclass in self.devices:
devices = lxml.etree.Element('devices')
@ -325,9 +400,8 @@ class BaseVM(qubes.PropertyHolder):
args['vcpus'] = str(self.vcpus)
args['mem'] = str(min(self.memory, self.maxmem))
if 'meminfo-writer' in self.services \
and not self.services['meminfo-writer']:
# If dynamic memory management disabled, set maxmem=mem
# If dynamic memory management disabled, set maxmem=mem
if not self.features.get('meminfo-writer', True):
args['maxmem'] = args['mem']
if self.netvm is not None:
@ -481,10 +555,12 @@ class BaseVM(qubes.PropertyHolder):
# Automatically enable/disable 'yum-proxy-setup' service based on
# allowYumProxy
if conf['allowYumProxy']:
self.services['yum-proxy-setup'] = True
self.features['yum-proxy-setup'] = '1'
else:
if self.services.has_key('yum-proxy-setup'):
self.services.pop('yum-proxy-setup')
try:
del self.features['yum-proxy-setup']
except KeyError:
pass
if expiring_rules_present:
subprocess.call(["sudo", "systemctl", "start",

View File

@ -449,17 +449,17 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if len(self.devices['pci']) > 0:
# Force meminfo-writer disabled when VM have PCI devices
self.services['meminfo-writer'] = False
self.features['meminfo-writer'] = None
elif not isinstance(self, qubes.vm.adminvm.AdminVM) \
and 'meminfo-writer' not in self.services:
and 'meminfo-writer' not in self.features:
# Always set if meminfo-writer should be active or not
self.services['meminfo-writer'] = True
self.features['meminfo-writer'] = '1'
if xml is None:
# new qube, disable updates check if requested for new qubes
# TODO: when features (#1637) are done, migrate to plugin
if not self.app.check_updates_vm:
self.services['qubes-update-check'] = False
self.features['check-updates'] = None
# will be initialized after loading all the properties
self.storage = None
@ -658,7 +658,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
try:
if preparing_dvm:
self.services['qubes-dvm'] = True
self.features['dvm'] = True
self.log.info('Setting Qubes DB info for the VM')
self.start_qubesdb()
@ -1707,10 +1707,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
if tzname:
self.qdb.write('/timezone', tzname)
for srv in self.services.keys():
# convert True/False to "1"/"0"
self.qdb.write('/qubes-service/{0}'.format(srv),
str(int(self.services[srv])))
for feature, value in self.features.items():
self.qdb.write('/features/{0}'.format(feature),
str(value) if value else '')
self.qdb.write('/devices/block', '')
self.qdb.write('/devices/usb', '')

View File

@ -153,27 +153,26 @@ the parser will complain about missing combine= attribute on the second <start>.
<ref name="properties" />
<optional>
<element name="services">
<element name="features">
<doc:description>
Container for services.
Container for features.
</doc:description>
<oneOrMore>
<element name="service">
<element name="feature">
<doc:description>
One service that is either enabled or disabled.
One feature of this domain.
</doc:description>
<optional>
<attribute name="enabled">
<doc:description>
Whether service is enabled or disabled.
Default is ``true``.
</doc:description>
<attribute name="name">
<doc:description>
Name of the feature.
</doc:description>
<data type="boolean" />
</attribute>
</optional>
<data type="string">
<param name="pattern">[a-z0-9_-]+</param>
</data>
</attribute>
<data type="string">
<param name="pattern">[a-z0-9_-]+</param>