storage: move volume_config['source'] filling to one place

Don't set 'source' volume in various places (each VM class constructor
etc), do it as part of volume initialization. And when it needs to be
re-calculated, call storage.init_volume again.

This code was duplicated, and as usual in such a case, those copies
were different - one have set 'size', the other one not.

QubesOS/qubes-issues#2256
This commit is contained in:
Marek Marczykowski-Górecki 2017-07-25 05:29:26 +02:00
parent 3a21e1f1b3
commit c5667791e8
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
5 changed files with 32 additions and 55 deletions

View File

@ -349,34 +349,44 @@ class Storage(object):
if hasattr(vm, 'volume_config'): if hasattr(vm, 'volume_config'):
for name, conf in self.vm.volume_config.items(): for name, conf in self.vm.volume_config.items():
conf = conf.copy()
if 'source' in conf:
template = getattr(vm, 'template', None)
# recursively lookup source volume - templates may be
# chained (TemplateVM -> AppVM -> DispVM, where the
# actual source should be used from TemplateVM)
while template:
# we have no control over VM load order,
# so initialize storage recursively if needed
if template.storage is None:
template.storage = Storage(template)
# FIXME: this effectively ignore 'source' value;
# maybe we don't need it at all if it's always from
# VM's template?
conf['source'] = template.volumes[name]
if conf['source'].source is not None:
template = getattr(template, 'template', None)
else:
break
self.init_volume(name, conf) self.init_volume(name, conf)
def _update_volume_config_source(self, name, volume_config):
'''Retrieve 'source' volume from VM's template'''
template = getattr(self.vm, 'template', None)
# recursively lookup source volume - templates may be
# chained (TemplateVM -> AppVM -> DispVM, where the
# actual source should be used from TemplateVM)
while template:
source = template.volumes[name]
volume_config['source'] = source
volume_config['pool'] = source.pool
volume_config['size'] = source.size
if source.source is not None:
template = getattr(template, 'template', None)
else:
break
def init_volume(self, name, volume_config): def init_volume(self, name, volume_config):
''' Initialize Volume instance attached to this domain ''' ''' Initialize Volume instance attached to this domain '''
if 'name' not in volume_config: if 'name' not in volume_config:
volume_config['name'] = name volume_config['name'] = name
if 'source' in volume_config:
# we have no control over VM load order,
# so initialize storage recursively if needed
template = getattr(self.vm, 'template', None)
if template and template.storage is None:
template.storage = Storage(template)
if volume_config['source'] is None:
self._update_volume_config_source(name, volume_config)
else:
# if source is already specified, pool needs to be too
pool = self.vm.app.get_pool(volume_config['pool'])
volume_config['source'] = pool.volumes[volume_config['source']]
# if pool still unknown, load default # if pool still unknown, load default
if 'pool' not in volume_config: if 'pool' not in volume_config:
volume_config['pool'] = \ volume_config['pool'] = \

View File

@ -110,7 +110,7 @@ class TC_90_AppVM(qubes.tests.vm.qubesvm.QubesVMTestsMixin,
self.assertNotEqual(vm.volume_config['root'].get('source', None), self.assertNotEqual(vm.volume_config['root'].get('source', None),
self.template.volumes['root'].source) self.template.volumes['root'].source)
self.assertEqual(vm.volume_config['root'].get('source', None), self.assertEqual(vm.volume_config['root'].get('source', None),
template2.volumes['root'].source) template2.volumes['root'])
def test_003_template_change_running(self): def test_003_template_change_running(self):
vm = self.get_vm() vm = self.get_vm()

View File

@ -79,12 +79,6 @@ class AppVM(qubes.vm.qubesvm.QubesVM):
# 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
# coming from XML, already as we need it # coming from XML, already as we need it
for name, conf in self.volume_config.items():
tpl_volume = template.volumes[name]
self.config_volume_from_source(conf, tpl_volume)
for name, config in template.volume_config.items(): for name, config in template.volume_config.items():
# in case the template vm has more volumes add them to own # in case the template vm has more volumes add them to own
# config # config
@ -120,9 +114,5 @@ class AppVM(qubes.vm.qubesvm.QubesVM):
if conf.get('snap_on_start', False) and \ if conf.get('snap_on_start', False) and \
conf.get('source', None) is None: conf.get('source', None) is None:
config = conf.copy() config = conf.copy()
template_volume = newvalue.volumes[volume_name] self.volume_config[volume_name] = config
self.volume_config[volume_name] = \
self.config_volume_from_source(
config,
template_volume)
self.storage.init_volume(volume_name, config) self.storage.init_volume(volume_name, config)

View File

@ -78,11 +78,6 @@ class DispVM(qubes.vm.qubesvm.QubesVM):
# 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
# coming from XML, already as we need it # coming from XML, already as we need it
for name, conf in self.volume_config.items():
tpl_volume = template.volumes[name]
self.config_volume_from_source(conf, tpl_volume)
for name, config in template.volume_config.items(): for name, config in template.volume_config.items():
# in case the template vm has more volumes add them to own # in case the template vm has more volumes add them to own
# config # config

View File

@ -1682,24 +1682,6 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
# helper methods # helper methods
# #
@staticmethod
def config_volume_from_source(volume_config, source):
'''Adjust storage volume config to use given volume as a source'''
volume_config['size'] = source.size
volume_config['pool'] = source.pool
needs_source = (
'source' in volume_config)
is_snapshot = 'snap_on_start' in volume_config and volume_config[
'snap_on_start']
if is_snapshot and needs_source:
if source.source is not None:
volume_config['source'] = source.source
else:
volume_config['source'] = source
return volume_config
def relative_path(self, path): def relative_path(self, path):
'''Return path relative to py:attr:`dir_path`. '''Return path relative to py:attr:`dir_path`.