Skip to content

Commit

Permalink
ext/core-features: make 'template-postinstall' event async
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
marmarek committed Nov 15, 2018
1 parent d2585aa commit 0eab082
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 54 deletions.
4 changes: 3 additions & 1 deletion qubes/ext/core_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -58,4 +60,4 @@ def qubes_features_request(self, vm, event, untrusted_features):
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')
117 changes: 64 additions & 53 deletions qubes/tests/ext.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,33 @@ def setUp(self):

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',), {}),
('features.__setitem__', ('qrexec', True), {}),
('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',), {}),
Expand All @@ -75,43 +78,47 @@ def test_011_notify_tools_uninstall(self):

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), {}),
])

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',), {}),
('features.__setitem__', ('qrexec', True), {}),
('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',), {}),
Expand All @@ -121,29 +128,32 @@ def test_015_notify_tools_invalid_value_qrexec(self):

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 '
Expand All @@ -154,12 +164,13 @@ def test_018_notify_tools_already_installed(self):
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',), {}),
Expand Down
9 changes: 9 additions & 0 deletions qubes/vm/qubesvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
'''

#
Expand Down

0 comments on commit 0eab082

Please sign in to comment.