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" />
|
<property name="label" ref="label-1" />
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<services>
|
<features>
|
||||||
<service enabled="false">meminfo-writer</service>
|
<feature name="meminfo-writer"></feature>
|
||||||
<service>qubes-firewall</service>
|
<feature name="qubes-firewall">1</feature>
|
||||||
</services>
|
</features>
|
||||||
|
|
||||||
<devices class="pci">
|
<devices class="pci">
|
||||||
<device>01:23.45</device>
|
<device>01:23.45</device>
|
||||||
|
@ -1262,13 +1262,12 @@ class Qubes(PropertyHolder):
|
|||||||
# Disable ntpd in ClockVM - to not conflict with ntpdate (both are
|
# Disable ntpd in ClockVM - to not conflict with ntpdate (both are
|
||||||
# using 123/udp port)
|
# using 123/udp port)
|
||||||
if hasattr(self, 'clockvm') and self.clockvm is not None:
|
if hasattr(self, 'clockvm') and self.clockvm is not None:
|
||||||
if 'ntpd' in self.clockvm.services:
|
if self.clockvm.features.get('services/ntpd', False):
|
||||||
if self.clockvm.services['ntpd']:
|
self.log.warning("VM set as clockvm ({!r}) has enabled 'ntpd' "
|
||||||
self.log.warning("VM set as clockvm ({!r}) has enabled "
|
"service! Expect failure when syncing time in dom0.".format(
|
||||||
"'ntpd' service! Expect failure when syncing time in "
|
self.clockvm))
|
||||||
"dom0.".format(self.clockvm))
|
|
||||||
else:
|
else:
|
||||||
self.clockvm.services['ntpd'] = False
|
self.clockvm.features['services/ntpd'] = ''
|
||||||
|
|
||||||
for vm in self.domains:
|
for vm in self.domains:
|
||||||
vm.events_enabled = True
|
vm.events_enabled = True
|
||||||
@ -1461,13 +1460,12 @@ class 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 'ntpd' in newvalue.services:
|
if newvalue.features.get('services/ntpd', False):
|
||||||
if newvalue.services['ntpd']:
|
|
||||||
raise qubes.exc.QubesVMError(newvalue,
|
raise qubes.exc.QubesVMError(newvalue,
|
||||||
'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
|
'Cannot set {!r} as {!r} since it has ntpd enabled.'.format(
|
||||||
newvalue.name, name))
|
newvalue.name, name))
|
||||||
else:
|
else:
|
||||||
newvalue.services['ntpd'] = False
|
newvalue.features['services/ntpd'] = ''
|
||||||
|
|
||||||
|
|
||||||
@qubes.events.handler(
|
@qubes.events.handler(
|
||||||
|
@ -178,7 +178,7 @@ class TC_01_Properties(qubes.tests.SystemTestsMixin, qubes.tests.QubesTestCase):
|
|||||||
self.assertEquals(testvm1.include_in_backups,
|
self.assertEquals(testvm1.include_in_backups,
|
||||||
testvm2.include_in_backups)
|
testvm2.include_in_backups)
|
||||||
self.assertEquals(testvm1.default_user, testvm2.default_user)
|
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(),
|
self.assertEquals(testvm1.get_firewall_conf(),
|
||||||
testvm2.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,
|
self.assertEquals(testvm1.include_in_backups,
|
||||||
testvm3.include_in_backups)
|
testvm3.include_in_backups)
|
||||||
self.assertEquals(testvm1.default_user, testvm3.default_user)
|
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(),
|
self.assertEquals(testvm1.get_firewall_conf(),
|
||||||
testvm3.get_firewall_conf())
|
testvm3.get_firewall_conf())
|
||||||
|
|
||||||
|
@ -114,11 +114,11 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
|
|||||||
<tag name="testtag">tagvalue</tag>
|
<tag name="testtag">tagvalue</tag>
|
||||||
</tags>
|
</tags>
|
||||||
|
|
||||||
<services>
|
<features>
|
||||||
<service>testservice</service>
|
<feature name="testfeature_none"/>
|
||||||
<service enabled="True">enabledservice</service>
|
<feature name="testfeature_empty"></feature>
|
||||||
<service enabled="False">disabledservice</service>
|
<feature name="testfeature_aqq">aqq</feature>
|
||||||
</services>
|
</features>
|
||||||
|
|
||||||
<devices class="pci">
|
<devices class="pci">
|
||||||
<device>00:11.22</device>
|
<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.testlabel, 'label-1')
|
||||||
self.assertEqual(vm.defaultprop, 'defaultvalue')
|
self.assertEqual(vm.defaultprop, 'defaultvalue')
|
||||||
self.assertEqual(vm.tags, {'testtag': 'tagvalue'})
|
self.assertEqual(vm.tags, {'testtag': 'tagvalue'})
|
||||||
self.assertEqual(vm.services, {
|
self.assertEqual(vm.features, {
|
||||||
'testservice': True,
|
'testfeature_none': None,
|
||||||
'enabledservice': True,
|
'testfeature_empty': '',
|
||||||
'disabledservice': False,
|
'testfeature_aqq': 'aqq',
|
||||||
})
|
})
|
||||||
|
|
||||||
self.assertItemsEqual(vm.devices.keys(), ('pci',))
|
self.assertItemsEqual(vm.devices.keys(), ('pci',))
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
# TODO list available classes
|
# TODO list available classes
|
||||||
# TODO list labels (maybe in qvm-prefs)
|
# TODO list labels (maybe in qvm-prefs)
|
||||||
# TODO services, devices, tags
|
# TODO features, devices, tags
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
@ -48,6 +48,85 @@ import qubes.plugins
|
|||||||
import qubes.tools.qvm_ls
|
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):
|
class BaseVMMeta(qubes.plugins.Plugin, qubes.events.EmitterMeta):
|
||||||
'''Metaclass for :py:class:`.BaseVM`'''
|
'''Metaclass for :py:class:`.BaseVM`'''
|
||||||
def __init__(cls, name, bases, dict_):
|
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)
|
qubes.tools.qvm_ls.process_class(cls)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceCollection(object):
|
class DeviceCollection(object):
|
||||||
'''Bag for devices.
|
'''Bag for devices.
|
||||||
|
|
||||||
@ -146,7 +224,7 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
|
|
||||||
__metaclass__ = BaseVMMeta
|
__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):
|
**kwargs):
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
|
|
||||||
@ -157,8 +235,8 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
|
|
||||||
super(BaseVM, self).__init__(xml, **kwargs)
|
super(BaseVM, self).__init__(xml, **kwargs)
|
||||||
|
|
||||||
#: dictionary of services that are run on this domain
|
#: dictionary of features of this qube
|
||||||
self.services = services or {}
|
self.features = Features(self, features)
|
||||||
|
|
||||||
#: :py:class:`DeviceManager` object keeping devices that are attached to
|
#: :py:class:`DeviceManager` object keeping devices that are attached to
|
||||||
#: this domain
|
#: this domain
|
||||||
@ -168,10 +246,9 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
self.tags = tags or {}
|
self.tags = tags or {}
|
||||||
|
|
||||||
if self.xml is not None:
|
if self.xml is not None:
|
||||||
# services
|
# features
|
||||||
for node in xml.xpath('./services/service'):
|
for node in xml.xpath('./features/service'):
|
||||||
self.services[node.text] = bool(
|
self.features[node.get('name')] = node.text
|
||||||
ast.literal_eval(node.get('enabled', 'True').capitalize()))
|
|
||||||
|
|
||||||
# devices (pci, usb, ...)
|
# devices (pci, usb, ...)
|
||||||
for parent in xml.xpath('./devices'):
|
for parent in xml.xpath('./devices'):
|
||||||
@ -214,14 +291,12 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
|
|
||||||
element.append(self.xml_properties())
|
element.append(self.xml_properties())
|
||||||
|
|
||||||
services = lxml.etree.Element('services')
|
features = lxml.etree.Element('features')
|
||||||
for service in self.services:
|
for feature in self.features:
|
||||||
node = lxml.etree.Element('service')
|
node = lxml.etree.Element('service', name=feature)
|
||||||
node.text = service
|
node.text = self.features[feature] if feature else None
|
||||||
if not self.services[service]:
|
features.append(node)
|
||||||
node.set('enabled', 'false')
|
element.append(features)
|
||||||
services.append(node)
|
|
||||||
element.append(services)
|
|
||||||
|
|
||||||
for devclass in self.devices:
|
for devclass in self.devices:
|
||||||
devices = lxml.etree.Element('devices')
|
devices = lxml.etree.Element('devices')
|
||||||
@ -325,9 +400,8 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
args['vcpus'] = str(self.vcpus)
|
args['vcpus'] = str(self.vcpus)
|
||||||
args['mem'] = str(min(self.memory, self.maxmem))
|
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']
|
args['maxmem'] = args['mem']
|
||||||
|
|
||||||
if self.netvm is not None:
|
if self.netvm is not None:
|
||||||
@ -481,10 +555,12 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
# Automatically enable/disable 'yum-proxy-setup' service based on
|
# Automatically enable/disable 'yum-proxy-setup' service based on
|
||||||
# allowYumProxy
|
# allowYumProxy
|
||||||
if conf['allowYumProxy']:
|
if conf['allowYumProxy']:
|
||||||
self.services['yum-proxy-setup'] = True
|
self.features['yum-proxy-setup'] = '1'
|
||||||
else:
|
else:
|
||||||
if self.services.has_key('yum-proxy-setup'):
|
try:
|
||||||
self.services.pop('yum-proxy-setup')
|
del self.features['yum-proxy-setup']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
if expiring_rules_present:
|
if expiring_rules_present:
|
||||||
subprocess.call(["sudo", "systemctl", "start",
|
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:
|
if len(self.devices['pci']) > 0:
|
||||||
# Force meminfo-writer disabled when VM have PCI devices
|
# 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) \
|
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
|
# Always set if meminfo-writer should be active or not
|
||||||
self.services['meminfo-writer'] = True
|
self.features['meminfo-writer'] = '1'
|
||||||
|
|
||||||
if xml is None:
|
if xml is None:
|
||||||
# new qube, disable updates check if requested for new qubes
|
# new qube, disable updates check if requested for new qubes
|
||||||
# TODO: when features (#1637) are done, migrate to plugin
|
# TODO: when features (#1637) are done, migrate to plugin
|
||||||
if not self.app.check_updates_vm:
|
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
|
# will be initialized after loading all the properties
|
||||||
self.storage = None
|
self.storage = None
|
||||||
@ -658,7 +658,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if preparing_dvm:
|
if preparing_dvm:
|
||||||
self.services['qubes-dvm'] = True
|
self.features['dvm'] = True
|
||||||
|
|
||||||
self.log.info('Setting Qubes DB info for the VM')
|
self.log.info('Setting Qubes DB info for the VM')
|
||||||
self.start_qubesdb()
|
self.start_qubesdb()
|
||||||
@ -1707,10 +1707,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
if tzname:
|
if tzname:
|
||||||
self.qdb.write('/timezone', tzname)
|
self.qdb.write('/timezone', tzname)
|
||||||
|
|
||||||
for srv in self.services.keys():
|
for feature, value in self.features.items():
|
||||||
# convert True/False to "1"/"0"
|
self.qdb.write('/features/{0}'.format(feature),
|
||||||
self.qdb.write('/qubes-service/{0}'.format(srv),
|
str(value) if value else '')
|
||||||
str(int(self.services[srv])))
|
|
||||||
|
|
||||||
self.qdb.write('/devices/block', '')
|
self.qdb.write('/devices/block', '')
|
||||||
self.qdb.write('/devices/usb', '')
|
self.qdb.write('/devices/usb', '')
|
||||||
|
@ -153,27 +153,26 @@ the parser will complain about missing combine= attribute on the second <start>.
|
|||||||
<ref name="properties" />
|
<ref name="properties" />
|
||||||
|
|
||||||
<optional>
|
<optional>
|
||||||
<element name="services">
|
<element name="features">
|
||||||
<doc:description>
|
<doc:description>
|
||||||
Container for services.
|
Container for features.
|
||||||
</doc:description>
|
</doc:description>
|
||||||
|
|
||||||
<oneOrMore>
|
<oneOrMore>
|
||||||
<element name="service">
|
<element name="feature">
|
||||||
<doc:description>
|
<doc:description>
|
||||||
One service that is either enabled or disabled.
|
One feature of this domain.
|
||||||
</doc:description>
|
</doc:description>
|
||||||
|
|
||||||
<optional>
|
<attribute name="name">
|
||||||
<attribute name="enabled">
|
|
||||||
<doc:description>
|
<doc:description>
|
||||||
Whether service is enabled or disabled.
|
Name of the feature.
|
||||||
Default is ``true``.
|
|
||||||
</doc:description>
|
</doc:description>
|
||||||
|
|
||||||
<data type="boolean" />
|
<data type="string">
|
||||||
|
<param name="pattern">[a-z0-9_-]+</param>
|
||||||
|
</data>
|
||||||
</attribute>
|
</attribute>
|
||||||
</optional>
|
|
||||||
|
|
||||||
<data type="string">
|
<data type="string">
|
||||||
<param name="pattern">[a-z0-9_-]+</param>
|
<param name="pattern">[a-z0-9_-]+</param>
|
||||||
|
Loading…
Reference in New Issue
Block a user