Browse Source

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
Marek Marczykowski-Górecki 7 years ago
parent
commit
ba86d6da79
3 changed files with 97 additions and 5 deletions
  1. 2 2
      qubes/tests/vm/init.py
  2. 79 3
      qubes/vm/__init__.py
  3. 16 0
      qubes/vm/qubesvm.py

+ 2 - 2
qubes/tests/vm/init.py

@@ -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',

+ 79 - 3
qubes/vm/__init__.py

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

+ 16 - 0
qubes/vm/qubesvm.py

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