dispvm: fix setting up new DispVM
Clone properties from DispVM template after setting base properties (qid, name, uuid). This means we can use standard clone_properties() function. Otherwise various setters may fail - for example netvm setter require uuid property initialized (for VM lookup in VM collection). Also, make dispvm_allowed check more robust - include direct creation of DispVM, and also check just before VM startup (if property was changed in the meantime). Fixes QubesOS/qubes-issues#3057
This commit is contained in:
parent
a3f542edcd
commit
c247ddff72
@ -640,7 +640,7 @@ class PropertyHolder(qubes.events.Emitter):
|
|||||||
'''Clone properties from other object.
|
'''Clone properties from other object.
|
||||||
|
|
||||||
:param PropertyHolder src: source object
|
:param PropertyHolder src: source object
|
||||||
:param list proplist: list of properties \
|
:param iterable proplist: list of properties \
|
||||||
(:py:obj:`None` or omit for all properties except those with \
|
(:py:obj:`None` or omit for all properties except those with \
|
||||||
:py:attr:`property.clone` set to :py:obj:`False`)
|
:py:attr:`property.clone` set to :py:obj:`False`)
|
||||||
'''
|
'''
|
||||||
|
@ -93,3 +93,51 @@ class TC_00_DispVM(qubes.tests.QubesTestCase):
|
|||||||
with self.assertRaises(qubes.exc.QubesException):
|
with self.assertRaises(qubes.exc.QubesException):
|
||||||
dispvm = self.loop.run_until_complete(
|
dispvm = self.loop.run_until_complete(
|
||||||
qubes.vm.dispvm.DispVM.from_appvm(self.appvm))
|
qubes.vm.dispvm.DispVM.from_appvm(self.appvm))
|
||||||
|
|
||||||
|
def test_010_create_direct(self):
|
||||||
|
self.appvm.dispvm_allowed = True
|
||||||
|
orig_getitem = self.app.domains.__getitem__
|
||||||
|
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_getitem
|
||||||
|
})
|
||||||
|
dispvm = self.app.add_new_vm(qubes.vm.dispvm.DispVM,
|
||||||
|
name='test-dispvm', template=self.appvm)
|
||||||
|
mock_domains.get_new_unused_dispid.assert_called_once_with()
|
||||||
|
self.assertEqual(dispvm.name, 'test-dispvm')
|
||||||
|
self.assertEqual(dispvm.template, self.appvm)
|
||||||
|
self.assertEqual(dispvm.label, self.appvm.label)
|
||||||
|
self.assertEqual(dispvm.label, self.appvm.label)
|
||||||
|
self.assertEqual(dispvm.auto_cleanup, False)
|
||||||
|
|
||||||
|
def test_011_create_direct_generate_name(self):
|
||||||
|
self.appvm.dispvm_allowed = True
|
||||||
|
orig_getitem = self.app.domains.__getitem__
|
||||||
|
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_getitem
|
||||||
|
})
|
||||||
|
dispvm = self.app.add_new_vm(qubes.vm.dispvm.DispVM,
|
||||||
|
template=self.appvm)
|
||||||
|
mock_domains.get_new_unused_dispid.assert_called_once_with()
|
||||||
|
self.assertEqual(dispvm.name, 'disp42')
|
||||||
|
self.assertEqual(dispvm.template, self.appvm)
|
||||||
|
self.assertEqual(dispvm.label, self.appvm.label)
|
||||||
|
self.assertEqual(dispvm.auto_cleanup, False)
|
||||||
|
|
||||||
|
def test_011_create_direct_reject(self):
|
||||||
|
orig_getitem = self.app.domains.__getitem__
|
||||||
|
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_getitem
|
||||||
|
})
|
||||||
|
with self.assertRaises(qubes.exc.QubesException):
|
||||||
|
self.app.add_new_vm(qubes.vm.dispvm.DispVM,
|
||||||
|
name='test-dispvm', template=self.appvm)
|
||||||
|
self.assertFalse(mock_domains.get_new_unused_dispid.called)
|
||||||
|
@ -77,22 +77,18 @@ class DispVM(qubes.vm.qubesvm.QubesVM):
|
|||||||
template = kwargs.get('template', None)
|
template = kwargs.get('template', None)
|
||||||
|
|
||||||
if xml is None:
|
if xml is None:
|
||||||
|
assert template is not None
|
||||||
|
|
||||||
|
if not template.dispvm_allowed:
|
||||||
|
raise qubes.exc.QubesValueError(
|
||||||
|
'template for DispVM ({}) needs to have '
|
||||||
|
'dispvm_allowed=True'.format(template.name))
|
||||||
|
|
||||||
if 'dispid' not in kwargs:
|
if 'dispid' not in kwargs:
|
||||||
kwargs['dispid'] = app.domains.get_new_unused_dispid()
|
kwargs['dispid'] = app.domains.get_new_unused_dispid()
|
||||||
if 'name' not in kwargs:
|
if 'name' not in kwargs:
|
||||||
kwargs['name'] = 'disp' + str(kwargs['dispid'])
|
kwargs['name'] = 'disp' + str(kwargs['dispid'])
|
||||||
|
|
||||||
# by default inherit properties from the DispVM template
|
|
||||||
proplist = [prop.__name__ for prop in template.property_list()
|
|
||||||
if prop.clone and prop.__name__ not in ['template']]
|
|
||||||
self_props = [prop.__name__ for prop in self.property_list()]
|
|
||||||
for prop in proplist:
|
|
||||||
if prop not in self_props:
|
|
||||||
continue
|
|
||||||
if prop not in kwargs and \
|
|
||||||
not template.property_is_default(prop):
|
|
||||||
kwargs[prop] = getattr(template, prop)
|
|
||||||
|
|
||||||
if template is not None:
|
if template is not None:
|
||||||
# template is only passed if the AppVM is created, in other cases we
|
# template is only passed if the AppVM is created, in other cases we
|
||||||
# don't need to patch the volume_config because the config is
|
# don't need to patch the volume_config because the config is
|
||||||
@ -108,6 +104,13 @@ class DispVM(qubes.vm.qubesvm.QubesVM):
|
|||||||
super(DispVM, self).__init__(app, xml, *args, **kwargs)
|
super(DispVM, self).__init__(app, xml, *args, **kwargs)
|
||||||
|
|
||||||
if xml is None:
|
if xml is None:
|
||||||
|
# by default inherit properties from the DispVM template
|
||||||
|
proplist = [prop.__name__ for prop in template.property_list()
|
||||||
|
if prop.clone and prop.__name__ not in ['template']]
|
||||||
|
self_props = [prop.__name__ for prop in self.property_list()]
|
||||||
|
self.clone_properties(template, set(proplist).intersection(
|
||||||
|
self_props))
|
||||||
|
|
||||||
self.firewall.clone(template.firewall)
|
self.firewall.clone(template.firewall)
|
||||||
self.features.update(template.features)
|
self.features.update(template.features)
|
||||||
self.tags.update(template.tags)
|
self.tags.update(template.tags)
|
||||||
@ -189,3 +192,15 @@ class DispVM(qubes.vm.qubesvm.QubesVM):
|
|||||||
yield from self.remove_from_disk()
|
yield from self.remove_from_disk()
|
||||||
del self.app.domains[self]
|
del self.app.domains[self]
|
||||||
self.app.save()
|
self.app.save()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def start(self, **kwargs):
|
||||||
|
# pylint: disable=arguments-differ
|
||||||
|
|
||||||
|
# sanity check, if template_for_dispvm got changed in the meantime
|
||||||
|
if not self.template.dispvm_allowed:
|
||||||
|
raise qubes.exc.QubesException(
|
||||||
|
'template for DispVM ({}) needs to have '
|
||||||
|
'dispvm_allowed=True'.format(self.template.name))
|
||||||
|
|
||||||
|
yield from super(DispVM, self).start(**kwargs)
|
||||||
|
Loading…
Reference in New Issue
Block a user