diff --git a/qubes/tests/vm/__init__.py b/qubes/tests/vm/__init__.py index 54564b75..9262e3d6 100644 --- a/qubes/tests/vm/__init__.py +++ b/qubes/tests/vm/__init__.py @@ -48,6 +48,9 @@ class TestVMsCollection(dict): def close(self): self.clear() + def __iter__(self): + return iter(self.values()) + class TestVolume(object): def __init__(self, pool): self.pool = pool diff --git a/qubes/tests/vm/dispvm.py b/qubes/tests/vm/dispvm.py index 1e562858..ea30fbd3 100644 --- a/qubes/tests/vm/dispvm.py +++ b/qubes/tests/vm/dispvm.py @@ -60,6 +60,9 @@ class TC_00_DispVM(qubes.tests.QubesTestCase): self.addCleanup(self.cleanup_dispvm) def cleanup_dispvm(self): + if hasattr(self, 'dispvm'): + self.dispvm.close() + del self.dispvm self.template.close() self.appvm.close() del self.template @@ -120,6 +123,41 @@ class TC_00_DispVM(qubes.tests.QubesTestCase): with self.assertRaises(qubes.exc.QubesValueError): dispvm.template = qubes.property.DEFAULT + def test_003_dvmtemplate_template_change(self): + self.appvm.template_for_dispvms = True + orig_domains = self.app.domains + with mock.patch.object(self.app, 'domains', wraps=self.app.domains) \ + as mock_domains: + mock_domains.configure_mock(**{ + 'get_new_unused_dispid': mock.Mock(return_value=42), + '__getitem__.side_effect': orig_domains.__getitem__, + '__iter__.side_effect': orig_domains.__iter__, + '__setitem__.side_effect': orig_domains.__setitem__, + }) + self.dispvm = self.app.add_new_vm(qubes.vm.dispvm.DispVM, + name='test-dispvm', template=self.appvm) + + with self.assertRaises(qubes.exc.QubesVMInUseError): + self.appvm.template = self.template + with self.assertRaises(qubes.exc.QubesValueError): + self.appvm.template = qubes.property.DEFAULT + + def test_004_dvmtemplate_allowed_change(self): + self.appvm.template_for_dispvms = True + orig_domains = self.app.domains + with mock.patch.object(self.app, 'domains', wraps=self.app.domains) \ + as mock_domains: + mock_domains.configure_mock(**{ + 'get_new_unused_dispid': mock.Mock(return_value=42), + '__getitem__.side_effect': orig_domains.__getitem__, + '__iter__.side_effect': orig_domains.__iter__, + '__setitem__.side_effect': orig_domains.__setitem__, + }) + self.dispvm = self.app.add_new_vm(qubes.vm.dispvm.DispVM, + name='test-dispvm', template=self.appvm) + + with self.assertRaises(qubes.exc.QubesVMInUseError): + self.appvm.template_for_dispvms = False def test_010_create_direct(self): self.appvm.template_for_dispvms = True diff --git a/qubes/vm/mix/dvmtemplate.py b/qubes/vm/mix/dvmtemplate.py index 8c46125f..ffff5e54 100644 --- a/qubes/vm/mix/dvmtemplate.py +++ b/qubes/vm/mix/dvmtemplate.py @@ -28,6 +28,23 @@ class DVMTemplateMixin(qubes.events.Emitter): default=False, doc='Should this VM be allowed to start as Disposable VM') + @qubes.events.handler('property-pre-set:template_for_dispvms') + def __on_pre_set_dvmtemplate(self, event, name, + newvalue, oldvalue=None): + # pylint: disable=unused-argument + if newvalue: + return + if any(self.dispvms): + raise qubes.exc.QubesVMInUseError(self, + 'Cannot change template_for_dispvms to False while there are ' + 'some DispVMs based on this DVM template') + + @qubes.events.handler('property-pre-del:template_for_dispvms') + def __on_pre_del_dvmtemplate(self, event, name, + oldvalue=None): + self.__on_pre_set_dvmtemplate( + event, name, False, oldvalue) + @qubes.events.handler('property-pre-set:template') def __on_property_pre_set_template(self, event, name, newvalue, oldvalue=None):