Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 25 additions & 95 deletions subiquity/server/controllers/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,13 @@
import json
import logging
import os
import pathlib
import subprocess
import time
from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union
from typing import Any, Callable, Dict, List, Optional, Sequence, Type, Union

import attr
import pyudev
import requests
from curtin import swap
from curtin.commands.extract import AbstractSourceHandler
from curtin.storage_config import ptable_part_type_to_flag
from curtin.util import human2bytes

Expand Down Expand Up @@ -79,13 +76,14 @@
align_up,
humanize_size,
)
from subiquity.server import snapdapi
from subiquity.server.autoinstall import AutoinstallError
from subiquity.server.controller import SubiquityController
from subiquity.server.controllers.source import SEARCH_DRIVERS_AUTOINSTALL_DEFAULT
from subiquity.server.mounter import Mounter
from subiquity.server.nonreportable import NonReportableException
from subiquity.server.snapdapi import (
from subiquity.server.snapd import api as snapdapi
from subiquity.server.snapd import types as snapdtypes
from subiquity.server.snapd.system_getter import SystemGetter, SystemsDirMounter
from subiquity.server.snapd.types import (
StorageEncryptionSupport,
StorageSafety,
SystemDetails,
Expand Down Expand Up @@ -124,10 +122,6 @@
DRY_RUN_RESET_SIZE = 500 * MiB


class NoSnapdSystemsOnSource(Exception):
pass


class NonReportableSVE(RecoverableError, NonReportableException):
"""Non reportable storage value error"""

Expand Down Expand Up @@ -298,11 +292,10 @@ def __init__(self, app):
self.app.hub.subscribe(InstallerChannels.PRE_SHUTDOWN, self._pre_shutdown)
self._variation_info: Dict[str, VariationInfo] = {}
self._info: Optional[VariationInfo] = None
self._on_volume: Optional[snapdapi.OnVolume] = None
self._source_handler: Optional[AbstractSourceHandler] = None
self._system_mounter: Optional[Mounter] = None
self._role_to_device: Dict[Union[str, snapdapi.Role], _Device] = {}
self._device_to_structure: Dict[_Device, snapdapi.OnVolume] = {}
self._system_getter = SystemGetter(self.app)
self._on_volume: Optional[snapdtypes.OnVolume] = None
self._role_to_device: Dict[Union[str, snapdtypes.Role], _Device] = {}
self._device_to_structure: Dict[_Device, snapdtypes.OnVolume] = {}
self._pyudev_context: Optional[pyudev.Context] = None
self.use_tpm: bool = False
self.locked_probe_data: bool = False
Expand Down Expand Up @@ -350,69 +343,6 @@ async def configured(self):
await super().configured()
self.stop_monitor()

async def _mount_systems_dir(self, variation_name):
self._source_handler = self.app.controllers.Source.get_handler(variation_name)
if self._source_handler is None:
raise NoSnapdSystemsOnSource
source_path = self._source_handler.setup()
cur_systems_dir = "/var/lib/snapd/seed/systems"
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)
if not self.app.opts.dry_run:
await self._system_mounter.bind_mount_tree(
source_systems_dir, cur_systems_dir
)

cur_snaps_dir = "/var/lib/snapd/seed/snaps"
source_snaps_dir = os.path.join(source_path, cur_snaps_dir[1:])
if not self.app.opts.dry_run:
await self._system_mounter.bind_mount_tree(source_snaps_dir, cur_snaps_dir)

async def _unmount_systems_dir(self):
if self._system_mounter is not None:
await self._system_mounter.cleanup()
self._system_mounter = None
if self._source_handler is not None:
self._source_handler.cleanup()
self._source_handler = None

async def _get_system(
self, variation_name, label
) -> Tuple[Optional[SystemDetails], bool]:
"""Return system information for a given system label.

The return value is a SystemDetails object (if any) and True if
the system was found in the layer that the installer is running
in or False if the source layer needed to be mounted to find
it.
"""
systems = await self.app.snapdapi.v2.systems.GET()
labels = {system.label for system in systems.systems}
if label in labels:
try:
return await self.app.snapdapi.v2.systems[label].GET(), True
except requests.exceptions.HTTPError as http_err:
log.warning("v2/systems/%s returned %s", label, http_err.response.text)
raise
else:
try:
await self._mount_systems_dir(variation_name)
except NoSnapdSystemsOnSource:
return None, False
try:
return await self.app.snapdapi.v2.systems[label].GET(), False
except requests.exceptions.HTTPError as http_err:
log.warning("v2/systems/%s returned %s", label, http_err.response.text)
raise
finally:
await self._unmount_systems_dir()

def info_for_system(self, name: str, label: str, system: SystemDetails):
if len(system.volumes) > 1:
log.error("Skipping uninstallable system: %s", system_multiple_volumes_text)
Expand Down Expand Up @@ -502,7 +432,7 @@ async def _examine_systems(self):
system = None
label = variation.snapd_system_label
if label is not None:
system, in_live_layer = await self._get_system(name, label)
system, in_live_layer = await self._system_getter.get(name, label)
log.debug("got system %s for variation %s", system, name)
if system is not None and len(system.volumes) > 0:
if not self.app.opts.enhanced_secureboot:
Expand Down Expand Up @@ -906,7 +836,7 @@ def potential_boot_disks(self, check_boot=True, with_reformatting=False):
def _offsets_and_sizes_for_volume(self, volume):
offset = self.model._partition_alignment_data["gpt"].min_start_offset
for structure in volume.structure:
if structure.role == snapdapi.Role.MBR:
if structure.role == snapdtypes.Role.MBR:
continue
if structure.offset is not None:
offset = structure.offset
Expand All @@ -915,12 +845,12 @@ def _offsets_and_sizes_for_volume(self, volume):

async def guided_core_boot(self, disk: Disk):
if self._info.needs_systems_mount:
await self._mount_systems_dir(self._info.name)
await SystemsDirMounter(self.app, self._info.name).mount()
# Formatting for a core boot classic system relies on some curtin
# features that are only available with v2 partitioning.
self.model.storage_version = 2
[volume] = self._info.system.volumes.values()
self._on_volume = snapdapi.OnVolume.from_volume(volume)
self._on_volume = snapdtypes.OnVolume.from_volume(volume)

preserved_parts = set()

Expand Down Expand Up @@ -953,7 +883,7 @@ async def guided_core_boot(self, disk: Disk):
part = parts_by_offset_size[(offset, size)]
else:
if (
structure.role == snapdapi.Role.SYSTEM_DATA
structure.role == snapdtypes.Role.SYSTEM_DATA
and structure == self._on_volume.structure[-1]
):
gap = gaps.at_offset(disk, offset)
Expand All @@ -974,20 +904,20 @@ async def guided_core_boot(self, disk: Disk):
fs = self.model.add_filesystem(
part, structure.filesystem, label=structure.label
)
if structure.role == snapdapi.Role.SYSTEM_DATA:
if structure.role == snapdtypes.Role.SYSTEM_DATA:
self.model.add_mount(fs, "/")
elif structure.role == snapdapi.Role.SYSTEM_BOOT:
elif structure.role == snapdtypes.Role.SYSTEM_BOOT:
self.model.add_mount(fs, "/boot")
elif part.flag == "boot":
part.grub_device = True
self.model.add_mount(fs, "/boot/efi")
if structure.role != snapdapi.Role.NONE:
if structure.role != snapdtypes.Role.NONE:
self._role_to_device[structure.role] = part
self._device_to_structure[part] = structure

disk._partitions.sort(key=lambda p: p.number)

def _on_volumes(self) -> Dict[str, snapdapi.OnVolume]:
def _on_volumes(self) -> Dict[str, snapdtypes.OnVolume]:
# Return a value suitable for use as the 'on-volumes' part of a
# SystemActionRequest.
#
Expand All @@ -1003,12 +933,12 @@ async def setup_encryption(self, context):
result = await snapdapi.post_and_wait(
self.app.snapdapi,
self.app.snapdapi.v2.systems[label].POST,
snapdapi.SystemActionRequest(
action=snapdapi.SystemAction.INSTALL,
step=snapdapi.SystemActionStep.SETUP_STORAGE_ENCRYPTION,
snapdtypes.SystemActionRequest(
action=snapdtypes.SystemAction.INSTALL,
step=snapdtypes.SystemActionStep.SETUP_STORAGE_ENCRYPTION,
on_volumes=self._on_volumes(),
),
ann=snapdapi.SystemActionResponse,
ann=snapdtypes.SystemActionResponse,
)
for role, enc_path in result.encrypted_devices.items():
arb_device = ArbitraryDevice(m=self.model, path=enc_path)
Expand All @@ -1024,9 +954,9 @@ async def finish_install(self, context):
await snapdapi.post_and_wait(
self.app.snapdapi,
self.app.snapdapi.v2.systems[label].POST,
snapdapi.SystemActionRequest(
action=snapdapi.SystemAction.INSTALL,
step=snapdapi.SystemActionStep.FINISH,
snapdtypes.SystemActionRequest(
action=snapdtypes.SystemAction.INSTALL,
step=snapdtypes.SystemActionStep.FINISH,
on_volumes=self._on_volumes(),
),
)
Expand Down
10 changes: 3 additions & 7 deletions subiquity/server/controllers/refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,10 @@
import requests.exceptions

from subiquity.common.apidef import API
from subiquity.common.types import Change, RefreshCheckState, RefreshStatus
from subiquity.common.types import Change, RefreshCheckState, RefreshStatus, TaskStatus
from subiquity.server.controller import SubiquityController
from subiquity.server.snapdapi import (
SnapAction,
SnapActionRequest,
TaskStatus,
post_and_wait,
)
from subiquity.server.snapd.api import post_and_wait
from subiquity.server.snapd.types import SnapAction, SnapActionRequest
from subiquity.server.types import InstallerChannels
from subiquitycore.async_helpers import SingleInstanceTask, schedule_task
from subiquitycore.context import with_context
Expand Down
Loading