vm: implement tag manager to fire events on change

While at it, adjust implementation to specification: tags don't have
value, only one bit of information (present/not present).

Fixes QubesOS/qubes-issues#2686
This commit is contained in:
Marek Marczykowski-Górecki 2017-05-13 16:27:34 +02:00
parent 68a426f0ba
commit ba86d6da79
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
3 changed files with 97 additions and 5 deletions

View File

@ -67,7 +67,7 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
</properties>
<tags>
<tag name="testtag">tagvalue</tag>
<tag name="testtag"/>
</tags>
<features>
@ -101,7 +101,7 @@ class TC_10_BaseVM(qubes.tests.QubesTestCase):
self.assertEqual(vm.testprop, 'testvalue')
self.assertEqual(vm.testlabel, 'label-1')
self.assertEqual(vm.defaultprop, 'defaultvalue')
self.assertEqual(vm.tags, {'testtag': 'tagvalue'})
self.assertEqual(vm.tags, {'testtag'})
self.assertEqual(vm.features, {
'testfeature_empty': '',
'testfeature_aqq': 'aqq',

View File

@ -28,6 +28,7 @@
import datetime
import os
import re
import string
import subprocess
import sys
import xml.parsers.expat
@ -159,6 +160,82 @@ class Features(dict):
return default
class Tags(set):
'''Manager of the tags.
Tags are simple: tag either can be present on qube or not. Tag is a
simple string consisting of ASCII alphanumeric characters, plus `_` and
`-`.
This class inherits from set, 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, seq=()):
super(Tags, self).__init__()
self.vm = vm
self.update(seq)
def clear(self):
'''Remove all tags'''
for item in tuple(self):
self.remove(item)
def symmetric_difference_update(self, *args, **kwargs):
'''Not implemented
:raises: NotImplementedError
'''
raise NotImplementedError()
def intersection_update(self, *args, **kwargs):
'''Not implemented
:raises: NotImplementedError
'''
raise NotImplementedError()
def pop(self):
'''Not implemented
:raises: NotImplementedError
'''
raise NotImplementedError()
def discard(self, elem):
'''Remove a tag if present'''
if elem in self:
self.remove(elem)
def update(self, *others):
'''Add tags from iterable(s)'''
for other in others:
for elem in other:
self.add(elem)
def add(self, elem):
'''Add a tag'''
allowed_chars = string.ascii_letters + string.digits + '_-'
if any(i not in allowed_chars for i in elem):
raise ValueError('Invalid character in tag')
if elem in self:
return
self.vm.fire_event('domain-tag-add', tag=elem)
super(Tags, self).add(elem)
def remove(self, elem):
'''Remove a tag'''
super(Tags, self).remove(elem)
self.vm.fire_event('domain-tag-delete', tag=elem)
#
# end of overriding
#
class BaseVM(qubes.PropertyHolder):
'''Base class for all VMs
@ -192,7 +269,7 @@ class BaseVM(qubes.PropertyHolder):
self.devices = devices or qubes.devices.DeviceManager(self)
#: user-specified tags
self.tags = tags or {}
self.tags = Tags(self, tags or ())
#: logger instance for logging messages related to this VM
self.log = None
@ -223,7 +300,7 @@ class BaseVM(qubes.PropertyHolder):
# tags
for node in self.xml.xpath('./tags/tag'):
self.tags[node.get('name')] = node.text
self.tags.add(node.get('name'))
# SEE:1815 firewall, policy.
@ -262,7 +339,6 @@ class BaseVM(qubes.PropertyHolder):
tags = lxml.etree.Element('tags')
for tag in self.tags:
node = lxml.etree.Element('tag', name=tag)
node.text = self.tags[tag]
tags.append(node)
element.append(tags)

View File

@ -304,6 +304,22 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
:param event: Event name (``'domain-feature-set'``)
:param key: feature name
.. event:: domain-tag-add (subject, event, tag)
A tag was added.
:param subject: Event emitter (the qube object)
:param event: Event name (``'domain-tag-add'``)
:param tag: tag name
.. event:: domain-tag-delete (subject, event, tag)
A feature was removed.
:param subject: Event emitter (the qube object)
:param event: Event name (``'domain-tag-delete'``)
:param tag: tag name
.. event:: feature-request (subject, event, *, untrusted_features)
The domain is performing a feature request.