filesystem: mount snapd/seed/systems only if it exists

The "$source"/var/lib/snapd/seed/systems directory only exists in certain
scenarios related to TPM-backed FDE. When the directory does not exist,
attempting to bind-mount it to /var/lib/snapd/seed/systems crashes the
install with a CalledProcessError.

We now make sure the directory exists before trying to mount it. For
dry-run test cases, we added a configuration item that simulates the
presence (or the non-presence) of the systems directory on the source.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
Olivier Gayot 2022-11-24 17:31:17 +01:00
parent 93f06eeb0f
commit 217ac98fc6
5 changed files with 25 additions and 1 deletions

View File

@ -1,4 +1,5 @@
#source-catalog: examples/tpm-sources.yaml #source-catalog: examples/tpm-sources.yaml
#dr-config: examples/tpm-dr-config.yaml
Source: Source:
source: src-prefer-encrypted source: src-prefer-encrypted
Serial: Serial:

View File

@ -0,0 +1 @@
systems_dir_exists: true

View File

@ -19,6 +19,7 @@ import glob
import json import json
import logging import logging
import os import os
import pathlib
import platform import platform
import select import select
from typing import Dict, List, Optional from typing import Dict, List, Optional
@ -110,6 +111,10 @@ than GPT, which is not currently supported.
""") """)
class NoSnapdSystemsOnSource(Exception):
pass
class FilesystemController(SubiquityController, FilesystemManipulator): class FilesystemController(SubiquityController, FilesystemManipulator):
endpoint = API.storage endpoint = API.storage
@ -171,6 +176,12 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
source_path = self.app.controllers.Source.source_path source_path = self.app.controllers.Source.source_path
cur_systems_dir = '/var/lib/snapd/seed/systems' cur_systems_dir = '/var/lib/snapd/seed/systems'
source_systems_dir = os.path.join(source_path, cur_systems_dir[1:]) source_systems_dir = os.path.join(source_path, cur_systems_dir[1:])
if self.app.opts.dry_run:
systems_dir_exists = self.app.dr_cfg.systems_dir_exists
else:
systems_dir_exists = pathlib.Path(source_systems_dir).is_dir()
if not systems_dir_exists:
raise NoSnapdSystemsOnSource
self._system_mounter = Mounter(self.app) self._system_mounter = Mounter(self.app)
await self._system_mounter.bind_mount_tree( await self._system_mounter.bind_mount_tree(
source_systems_dir, cur_systems_dir) source_systems_dir, cur_systems_dir)
@ -182,7 +193,10 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
async def _get_system(self): async def _get_system(self):
await self._unmount_system() await self._unmount_system()
try:
await self._mount_system() await self._mount_system()
except NoSnapdSystemsOnSource:
return
self._system = None self._system = None
label = self.app.base_model.source.current.snapd_system_label label = self.app.base_model.source.current.snapd_system_label
if label is not None: if label is not None:

View File

@ -38,6 +38,7 @@ from subiquity.models.tests.test_filesystem import (
) )
from subiquity.server import snapdapi from subiquity.server import snapdapi
from subiquity.server.controllers.filesystem import FilesystemController from subiquity.server.controllers.filesystem import FilesystemController
from subiquity.server.dryrun import DRConfig
bootloaders = [(bl, ) for bl in list(Bootloader)] bootloaders = [(bl, ) for bl in list(Bootloader)]
bootloaders_and_ptables = [(bl, pt) bootloaders_and_ptables = [(bl, pt)
@ -472,6 +473,7 @@ class TestCoreBootInstallMethods(IsolatedAsyncioTestCase):
self.app.prober = mock.Mock() self.app.prober = mock.Mock()
self.app.snapdapi = snapdapi.make_api_client( self.app.snapdapi = snapdapi.make_api_client(
AsyncSnapd(get_fake_connection())) AsyncSnapd(get_fake_connection()))
self.app.dr_cfg = DRConfig()
self.fsc = FilesystemController(app=self.app) self.fsc = FilesystemController(app=self.app)
self.fsc._configured = True self.fsc._configured = True
self.fsc.model = make_model(Bootloader.UEFI) self.fsc.model = make_model(Bootloader.UEFI)
@ -565,6 +567,9 @@ class TestCoreBootInstallMethods(IsolatedAsyncioTestCase):
self.app.base_model.source.current.snapd_system_label = \ self.app.base_model.source.current.snapd_system_label = \
'prefer-encrypted' 'prefer-encrypted'
self.app.controllers.Source.source_path = '' self.app.controllers.Source.source_path = ''
self.app.dr_cfg.systems_dir_exists = True
await self.fsc._get_system_task.start() await self.fsc._get_system_task.start()
await self.fsc._get_system_task.wait() await self.fsc._get_system_task.wait()

View File

@ -34,6 +34,9 @@ class DRConfig:
All variables here should have default values ; to indicate the behavior we All variables here should have default values ; to indicate the behavior we
want by default in dry-run mode. """ want by default in dry-run mode. """
# Tells whether "$source"/var/lib/snapd/seed/systems exists on the source.
systems_dir_exists: bool = False
@classmethod @classmethod
def load(cls, stream): def load(cls, stream):
data = yaml.safe_load(stream) data = yaml.safe_load(stream)