qubes/vm: change services to features
This commit is contained in:
parent
bf78e662f6
commit
93686eae06
@ -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>
|
||||
|
@ -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(
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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',))
|
||||
|
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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', '')
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user