From 0eab082d853cdc07800ccb4bef8a818a1d828da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sun, 11 Nov 2018 07:50:36 +0100 Subject: [PATCH] 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. --- qubes/ext/core_features.py | 4 +- qubes/tests/ext.py | 117 ++++++++++++++++++++----------------- qubes/vm/qubesvm.py | 9 +++ 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/qubes/ext/core_features.py b/qubes/ext/core_features.py index f07f5e93..153677db 100644 --- a/qubes/ext/core_features.py +++ b/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 . +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') diff --git a/qubes/tests/ext.py b/qubes/tests/ext.py index 6f59132f..04d70327 100644 --- a/qubes/tests/ext.py +++ b/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',), {}), diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 2b46c5b6..c51713b2 100644 --- a/qubes/vm/qubesvm.py +++ b/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). ''' #