Browse Source

ext/core-features: make 'template-postinstall' event async

It makes a lot of sense to call long-running operations in that event
handler, including calling back into the VM. Allow that by using
fire_event_async, not just fire_event.

Also, document the event.
Marek Marczykowski-Górecki 5 years ago
parent
commit
0eab082d85
3 changed files with 76 additions and 54 deletions
  1. 3 1
      qubes/ext/core_features.py
  2. 64 53
      qubes/tests/ext.py
  3. 9 0
      qubes/vm/qubesvm.py

+ 3 - 1
qubes/ext/core_features.py

@@ -17,12 +17,14 @@
 #
 # You should have received a copy of the GNU Lesser General Public
 # License along with this library; if not, see <https://www.gnu.org/licenses/>.
+import asyncio
 
 import qubes.ext
 
 class CoreFeatures(qubes.ext.Extension):
     # pylint: disable=too-few-public-methods
     @qubes.ext.handler('features-request')
+    @asyncio.coroutine
     def qubes_features_request(self, vm, event, untrusted_features):
         '''Handle features provided by qubes-core-agent and qubes-gui-agent'''
         # pylint: disable=no-self-use,unused-argument
@@ -58,4 +60,4 @@ class CoreFeatures(qubes.ext.Extension):
         if not qrexec_before and vm.features.get('qrexec', False):
             # if this is the first time qrexec was advertised, now can finish
             #  template setup
-            vm.fire_event('template-postinstall')
+            yield from vm.fire_event_async('template-postinstall')

+ 64 - 53
qubes/tests/ext.py

@@ -40,12 +40,13 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
 
     def test_010_notify_tools(self):
         del self.vm.template
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'gui': '1',
-                'version': '1',
-                'default-user': 'user',
-                'qrexec': '1'}),
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'gui': '1',
+                    'version': '1',
+                    'default-user': 'user',
+                    'qrexec': '1'}))
         self.assertEqual(self.vm.mock_calls, [
             ('features.get', ('qrexec', False), {}),
             ('features.__contains__', ('qrexec',), {}),
@@ -53,17 +54,19 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
             ('features.__contains__', ('gui',), {}),
             ('features.__setitem__', ('gui', True), {}),
             ('features.get', ('qrexec', False), {}),
-            ('fire_event', ('template-postinstall',), {})
+            ('fire_event_async', ('template-postinstall',), {}),
+            ('fire_event_async().__iter__', (), {}),
         ])
 
     def test_011_notify_tools_uninstall(self):
         del self.vm.template
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'gui': '0',
-                'version': '1',
-                'default-user': 'user',
-                'qrexec': '0'}),
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'gui': '0',
+                    'version': '1',
+                    'default-user': 'user',
+                    'qrexec': '0'}))
         self.assertEqual(self.vm.mock_calls, [
             ('features.get', ('qrexec', False), {}),
             ('features.__contains__', ('qrexec',), {}),
@@ -75,11 +78,12 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
 
     def test_012_notify_tools_uninstall2(self):
         del self.vm.template
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'version': '1',
-                'default-user': 'user',
-            })
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'version': '1',
+                    'default-user': 'user',
+                }))
         self.assertEqual(self.vm.mock_calls, [
             ('features.get', ('qrexec', False), {}),
             ('features.get', ('qrexec', False), {}),
@@ -87,12 +91,13 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
 
     def test_013_notify_tools_no_version(self):
         del self.vm.template
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'qrexec': '1',
-                'gui': '1',
-                'default-user': 'user',
-            })
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'qrexec': '1',
+                    'gui': '1',
+                    'default-user': 'user',
+                }))
         self.assertEqual(self.vm.mock_calls, [
             ('features.get', ('qrexec', False), {}),
             ('features.__contains__', ('qrexec',), {}),
@@ -100,18 +105,20 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
             ('features.__contains__', ('gui',), {}),
             ('features.__setitem__', ('gui', True), {}),
             ('features.get', ('qrexec', False), {}),
-            ('fire_event', ('template-postinstall',), {})
+            ('fire_event_async', ('template-postinstall',), {}),
+            ('fire_event_async().__iter__', (), {}),
         ])
 
     def test_015_notify_tools_invalid_value_qrexec(self):
         del self.vm.template
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'version': '1',
-                'qrexec': 'invalid',
-                'gui': '1',
-                'default-user': 'user',
-            })
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'version': '1',
+                    'qrexec': 'invalid',
+                    'gui': '1',
+                    'default-user': 'user',
+                }))
         self.assertEqual(self.vm.mock_calls, [
             ('features.get', ('qrexec', False), {}),
             ('features.__contains__', ('gui',), {}),
@@ -121,29 +128,32 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
 
     def test_016_notify_tools_invalid_value_gui(self):
         del self.vm.template
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'version': '1',
-                'qrexec': '1',
-                'gui': 'invalid',
-                'default-user': 'user',
-            })
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'version': '1',
+                    'qrexec': '1',
+                    'gui': 'invalid',
+                    'default-user': 'user',
+                }))
         self.assertEqual(self.vm.mock_calls, [
             ('features.get', ('qrexec', False), {}),
             ('features.__contains__', ('qrexec',), {}),
             ('features.__setitem__', ('qrexec', True), {}),
             ('features.get', ('qrexec', False), {}),
-            ('fire_event', ('template-postinstall',), {})
+            ('fire_event_async', ('template-postinstall',), {}),
+            ('fire_event_async().__iter__', (), {}),
         ])
 
     def test_017_notify_tools_template_based(self):
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'version': '1',
-                'qrexec': '1',
-                'gui': '1',
-                'default-user': 'user',
-            })
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'version': '1',
+                    'qrexec': '1',
+                    'gui': '1',
+                    'default-user': 'user',
+                }))
         self.assertEqual(self.vm.mock_calls, [
             ('template.__bool__', (), {}),
             ('log.warning', ('Ignoring qubes.NotifyTools for template-based '
@@ -154,12 +164,13 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
         self.features['qrexec'] = True
         self.features['gui'] = True
         del self.vm.template
-        self.ext.qubes_features_request(self.vm, 'features-request',
-            untrusted_features={
-                'gui': '1',
-                'version': '1',
-                'default-user': 'user',
-                'qrexec': '1'}),
+        self.loop.run_until_complete(
+            self.ext.qubes_features_request(self.vm, 'features-request',
+                untrusted_features={
+                    'gui': '1',
+                    'version': '1',
+                    'default-user': 'user',
+                    'qrexec': '1'}))
         self.assertEqual(self.vm.mock_calls, [
             ('features.get', ('qrexec', False), {}),
             ('features.__contains__', ('qrexec',), {}),

+ 9 - 0
qubes/vm/qubesvm.py

@@ -413,6 +413,15 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
 
             On the `vm` object there was probably ``property-set:netvm`` fired
             earlier.
+
+        .. event:: template-postinstall (subject, event)
+
+            Fired on non-template-based domain (TemplateVM, StandaloneVM) when
+            it first reports qrexec presence. This happens at the first
+            domain startup just after its installation and is suitable for
+            performing various post-installation setup.
+
+            Handler for this event can be asynchronous (a coroutine).
     '''
 
     #