Skip to content

Commit

Permalink
storage/lvm: allow starting a VM even if not all its volumes are active
Browse files Browse the repository at this point in the history
Normally, VM gets snapshots of its volumes anyway, so it makes little
sense to enforce they are already active. This requires several changes:
1. Including inactive volumes in the size_cache (those will have 0 as
   usage, because inactive do not report usage).
2. Do not check if volume is active in the verify() function.
3. With the above changed, adjust checking for volume existence by
   looking at size_cache instead of /dev/ node existence. For now do
   this in code paths related to VM startup.
4. In the rare case of not using snapshot (or new volatile volume) do
   activate the volume if needed.

Do not deactivate volumes on stop to not lose information about usage.

All that should solve an issue when qubesd (and possibly some VMs) are
started while LVM is still activating volumes. A simpler (and more
reliable) solution would be to order qubesd.service after activating all
(present at boot) volumes, but unfortunately current LVM + systemd
integration doesn't provide anything to set such ordering.
  • Loading branch information
marmarek committed Nov 4, 2024
1 parent 6935f26 commit d76622f
Showing 1 changed file with 23 additions and 10 deletions.
33 changes: 23 additions & 10 deletions qubes/storage/lvm.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,17 @@ def _parse_lvm_cache(lvm_output):
size, usage_percent = row['lv_size'], row['data_percent']
except KeyError:
continue
if '' in [pool_name, name, size, usage_percent]:
if '' in [pool_name, name, size]:
continue
pool_lv, attr, origin = row['pool_lv'], row['lv_attr'], row['origin']
metadata_size = row['lv_metadata_size']
metadata_percent = row['metadata_percent']
name = pool_name + "/" + name
size = int(size[:-1]) # Remove 'B' suffix
usage = int(size / 100 * float(usage_percent))
if usage_percent:
usage = int(size / 100 * float(usage_percent))
else:
usage = 0
if metadata_size:
metadata_size = int(metadata_size[:-1])
metadata_usage = int(metadata_size / 100 * float(metadata_percent))
Expand Down Expand Up @@ -384,6 +387,18 @@ async def _remove_revisions(self, revisions=None):
except qubes.storage.StoragePoolException:
pass

async def _activate(self):
"""
Ensure the volume is active.
:return: None
"""
vol_info = size_cache[self._vid_current]
if vol_info['attr'][4] == 'a':
return
cmd = ["activate", self._vid_current]
await qubes_lvm_coro(cmd, self.log)

async def _commit(self, vid_to_commit=None, keep=False):
'''
Commit temporary volume into current one. By default
Expand All @@ -407,7 +422,7 @@ async def _commit(self, vid_to_commit=None, keep=False):
vid_to_commit = self._vid_snap

assert self._lock.locked()
if not os.path.exists('/dev/' + vid_to_commit):
if vid_to_commit not in size_cache:
# nothing to commit
return False

Expand Down Expand Up @@ -453,7 +468,7 @@ async def create(self):
async def remove(self):
assert self.vid
try:
if os.path.exists('/dev/' + self._vid_snap):
if self._vid_snap in size_cache:
cmd = ['remove', self._vid_snap]
await qubes_lvm_coro(cmd, self.log)
except AttributeError:
Expand Down Expand Up @@ -558,8 +573,7 @@ async def import_data_end(self, success):

def abort_if_import_in_progress(self):
try:
devpath = '/dev/' + self._vid_import
if os.path.exists(devpath):
if self._vid_import in size_cache:
raise qubes.storage.StoragePoolException(
'Import operation in progress on {}'.format(self.vid))
except AttributeError: # self._vid_import
Expand All @@ -568,7 +582,7 @@ def abort_if_import_in_progress(self):

def is_dirty(self):
if self.save_on_stop:
return os.path.exists('/dev/' + self._vid_snap)
return self._vid_snap in size_cache
return False

def is_outdated(self):
Expand Down Expand Up @@ -673,6 +687,8 @@ async def start(self):
if self.snap_on_start or self.save_on_stop:
if not self.save_on_stop or not self.is_dirty():
await self._snapshot()
else:
await self._activate()
else:
await self._reset()
finally:
Expand Down Expand Up @@ -707,9 +723,6 @@ async def verify(self):
vid = self._vid_current
try:
vol_info = size_cache[vid]
if vol_info['attr'][4] != 'a':
raise qubes.storage.StoragePoolException(
'volume {} not active'.format(vid))
except KeyError:
raise qubes.storage.StoragePoolException(
'volume {} missing'.format(vid))
Expand Down

0 comments on commit d76622f

Please sign in to comment.