From bfb09f567f68143347eb8e6fc54ea616b3fa737b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Fri, 11 Jan 2019 21:26:49 +0100 Subject: [PATCH] Fix saving 'updates-available' flag reported by TemplateBasedVM Look for the first updateable template up in the template chain, instead of going just one level up. Especially this applies to DispVM -> AppVM -> TemplateVM case. If DispVM reports available updates, 'updates-available' flag should be set on relevant TemplateVM, not AppVM (*-dvm). Include test for the new case. Fixes QubesOS/qubes-issues#3736 --- qubes/api/misc.py | 12 +++++++++--- qubes/tests/api_misc.py | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/qubes/api/misc.py b/qubes/api/misc.py index f2ff3ca5..7739f8de 100644 --- a/qubes/api/misc.py +++ b/qubes/api/misc.py @@ -108,14 +108,20 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI): update_count = int(untrusted_update_count) del untrusted_update_count + # look for the nearest updateable VM up in the template chain + updateable_template = getattr(self.src, 'template', None) + while updateable_template is not None and \ + not updateable_template.updateable: + updateable_template = getattr(updateable_template, 'template', None) + if self.src.updateable: # Just trust information from VM itself self.src.features['updates-available'] = bool(update_count) self.app.save() - elif getattr(self.src, 'template', None) is not None: + elif updateable_template is not None: # Hint about updates availability in template # If template is running - it will notify about updates itself - if self.src.template.is_running(): + if updateable_template.is_running(): return # Ignore no-updates info if update_count > 0: @@ -123,6 +129,6 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI): # in the template - ignore info if self.src.storage.outdated_volumes: return - self.src.template.features['updates-available'] = bool( + updateable_template.features['updates-available'] = bool( update_count) self.app.save() diff --git a/qubes/tests/api_misc.py b/qubes/tests/api_misc.py index 70aef054..3a20b82e 100644 --- a/qubes/tests/api_misc.py +++ b/qubes/tests/api_misc.py @@ -249,6 +249,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase): response = self.call_mgmt_func(b'qubes.NotifyUpdates', payload=b'0\n') self.assertIsNone(response) self.assertEqual(self.tpl.mock_calls, [ + mock.call.updateable.__bool__(), mock.call.is_running(), ]) self.assertEqual(self.app.mock_calls, []) @@ -262,6 +263,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase): self.assertIsNone(response) self.assertEqual(self.src.mock_calls, []) self.assertEqual(self.tpl.mock_calls, [ + mock.call.updateable.__bool__(), mock.call.is_running(), mock.call.features.__setitem__('updates-available', True), ]) @@ -277,6 +279,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase): self.assertIsNone(response) self.assertEqual(self.src.mock_calls, []) self.assertEqual(self.tpl.mock_calls, [ + mock.call.updateable.__bool__(), mock.call.is_running(), ]) self.assertIsInstance(self.tpl.updates_available, mock.Mock) @@ -290,8 +293,29 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase): self.assertIsNone(response) self.assertEqual(self.src.mock_calls, []) self.assertEqual(self.tpl.mock_calls, [ + mock.call.updateable.__bool__(), mock.call.is_running(), ]) self.assertIsInstance(self.src.updates_available, mock.Mock) self.assertEqual(self.app.mock_calls, []) + def test_028_notify_updates_template_based_dispvm(self): + self.dvm = self.src + self.dvm.updateable = False + self.srv = mock.NonCallableMagicMock(template=self.dvm) + self.src.updateable = False + self.src.template.is_running.return_value = False + self.src.storage.outdated_volumes = [] + response = self.call_mgmt_func(b'qubes.NotifyUpdates', payload=b'1\n') + self.assertIsNone(response) + self.assertEqual(self.src.mock_calls, []) + self.assertEqual(self.dvm.mock_calls, []) + self.assertEqual(self.tpl.mock_calls, [ + mock.call.updateable.__bool__(), + mock.call.is_running(), + mock.call.features.__setitem__('updates-available', True), + ]) + self.assertIsInstance(self.src.updates_available, mock.Mock) + self.assertEqual(self.app.mock_calls, [ + mock.call.save() + ])