From cf80463e5b621bd8f529a5206573590a38094291 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Tue, 1 Nov 2022 13:53:39 +0100 Subject: [PATCH] do partitioning based on information from gadget --- scripts/runtests.sh | 6 +- subiquity/server/controllers/filesystem.py | 55 +++++++++++++++- .../controllers/tests/test_filesystem.py | 63 ++++++++++++++++++- subiquity/server/snapdapi.py | 9 +-- 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/scripts/runtests.sh b/scripts/runtests.sh index d1ab1bcf..13a2df5d 100755 --- a/scripts/runtests.sh +++ b/scripts/runtests.sh @@ -174,11 +174,7 @@ for answers in examples/answers*.yaml; do --bootloader uefi \ --snaps-from-examples \ --source-catalog $catalog - if [ "$answers" = examples/answers-tpm.yaml ]; then - validate skip - else - validate install - fi + validate install grep -q 'finish: subiquity/Install/install/postinstall/run_unattended_upgrades: SUCCESS: downloading and installing security updates' $tmpdir/subiquity-server-debug.log else # The OOBE doesn't exist in WSL < 20.04 diff --git a/subiquity/server/controllers/filesystem.py b/subiquity/server/controllers/filesystem.py index 7c46d359..cfcfb185 100644 --- a/subiquity/server/controllers/filesystem.py +++ b/subiquity/server/controllers/filesystem.py @@ -23,6 +23,8 @@ import platform import select from typing import List +from curtin.storage_config import ptable_uuid_to_flag_entry + import pyudev from subiquitycore.async_helpers import ( @@ -80,6 +82,7 @@ from subiquity.models.filesystem import ( from subiquity.server.controller import ( SubiquityController, ) +from subiquity.server import snapdapi from subiquity.server.types import InstallerChannels @@ -409,11 +412,59 @@ class FilesystemController(SubiquityController, FilesystemManipulator): core_boot_classic_error=self._core_boot_classic_error, storage_encryption=se) + def _add_structure(self, *, disk: Disk, next_offset: int, is_last: bool, + structure: snapdapi.VolumeStructure): + ptype = structure.type.split(',', 1)[1].upper() + if structure.offset is not None: + offset = structure.offset + else: + offset = next_offset + if structure.role == snapdapi.Role.SYSTEM_DATA and is_last: + size = gaps.largest_gap(disk).size + else: + size = structure.size + next_offset = offset + size + flag = ptable_uuid_to_flag_entry(ptype)[0] + part = self.model.add_partition( + disk, offset=offset, size=size, flag=flag, + partition_name=structure.name) + if structure.filesystem: + part.wipe = 'superblock' + fs = self.model.add_filesystem( + part, structure.filesystem, label=structure.label) + if structure.role == snapdapi.Role.SYSTEM_DATA: + self.model.add_mount(fs, '/') + elif structure.role == snapdapi.Role.SYSTEM_BOOT: + self.model.add_mount(fs, '/boot') + elif flag == 'boot': + self.model.add_mount(fs, '/boot/efi') + return part + + def apply_system(self, disk_id): + disk = self.model._one(id=disk_id) + if len(self._system.volumes) != 1: + raise Exception("multiple volumes not supported") + self.reformat(disk) + [volume] = self._system.volumes.values() + + next_offset = disk.alignment_data().min_start_offset + for structure in volume.structure: + if structure.role == snapdapi.Role.MBR: + continue + if ',' not in structure.type: + continue + part = self._add_structure( + disk=disk, + next_offset=next_offset, + is_last=structure == volume.structure[-1], + structure=structure) + next_offset = part.offset + part.size + disk._partitions.sort(key=lambda p: p.number) + async def guided_POST(self, data: GuidedChoice) -> StorageResponse: log.debug(data) if self._system is not None: - # Here is where we will apply the gadget info from the - # system to the disk! + self.apply_system(data.disk_id) await self.configured() else: self.guided(GuidedChoiceV2.from_guided_choice(data)) diff --git a/subiquity/server/controllers/tests/test_filesystem.py b/subiquity/server/controllers/tests/test_filesystem.py index 2ad8e560..33f2d891 100644 --- a/subiquity/server/controllers/tests/test_filesystem.py +++ b/subiquity/server/controllers/tests/test_filesystem.py @@ -14,6 +14,7 @@ # along with this program. If not, see . import copy +import json from unittest import mock, TestCase, IsolatedAsyncioTestCase from parameterized import parameterized @@ -35,7 +36,7 @@ from subiquity.models.tests.test_filesystem import ( make_model, make_partition, ) - +from subiquity.server import snapdapi bootloaders = [(bl, ) for bl in list(Bootloader)] bootloaders_and_ptables = [(bl, pt) @@ -457,3 +458,63 @@ class TestGuidedV2(IsolatedAsyncioTestCase): self.assertEqual( disk_size - (1 << 20), parts[-1].offset + parts[-1].size, disk_size) + + +class TestApplySystem(TestCase): + + def setUp(self): + self.app = make_app() + self.app.opts.bootloader = 'UEFI' + self.app.report_start_event = mock.Mock() + self.app.report_finish_event = mock.Mock() + self.app.prober = mock.Mock() + self.fsc = FilesystemController(app=self.app) + self.fsc._configured = True + self.fsc.model = make_model(Bootloader.UEFI) + + def test_add_structure(self): + disk = make_disk(self.fsc.model) + part = self.fsc._add_structure( + disk=disk, + next_offset=disk.alignment_data().min_start_offset, + is_last=False, + structure=snapdapi.VolumeStructure( + name='ptname', + type="83,0FC63DAF-8483-4772-8E79-3D69D8477DE4", + label='label', + size=1 << 20, + role=snapdapi.Role.SYSTEM_DATA, + filesystem='ext4')) + self.assertEqual(part.offset, disk.alignment_data().min_start_offset) + self.assertEqual(part.partition_name, 'ptname') + self.assertEqual(part.size, 1 << 20) + self.assertEqual(part.fs().fstype, 'ext4') + self.assertEqual(part.fs().mount().path, '/') + self.assertEqual(part.wipe, 'superblock') + + def test_add_structure_no_fs(self): + disk = make_disk(self.fsc.model) + part = self.fsc._add_structure( + disk=disk, + next_offset=disk.alignment_data().min_start_offset, + is_last=False, + structure=snapdapi.VolumeStructure( + type="83,0FC63DAF-8483-4772-8E79-3D69D8477DE4", + size=1 << 20, + filesystem=None)) + self.assertEqual(part.size, 1 << 20) + self.assertEqual(part.fs(), None) + self.assertEqual(part.wipe, None) + + def test_from_sample_data(self): + self.fsc.model = model = make_model(Bootloader.UEFI) + disk = make_disk(model) + with open('examples/snaps/v2-systems-unavailable.json') as fp: + self.fsc._system = snapdapi.snapd_serializer.deserialize( + snapdapi.SystemDetails, json.load(fp)['result']) + self.fsc.apply_system(disk.id) + self.assertEqual( + len(self.fsc._system.volumes['pc'].structure) - 1, + len(disk.partitions())) + mounts = {m.path for m in model._all(type='mount')} + self.assertEqual(mounts, {'/', '/boot', '/boot/efi'}) diff --git a/subiquity/server/snapdapi.py b/subiquity/server/snapdapi.py index 774e88ab..676c1f52 100644 --- a/subiquity/server/snapdapi.py +++ b/subiquity/server/snapdapi.py @@ -252,7 +252,7 @@ def make_api_client(async_snapd): content = await async_snapd.get(path[1:], **params) else: content = await async_snapd.post(path[1:], json, **params) - response = serializer.deserialize(Response, content) + response = snapd_serializer.deserialize(Response, content) if response.type == ResponseType.SYNC: content = content['result'] elif response.type == ResponseType.ASYNC: @@ -261,10 +261,11 @@ def make_api_client(async_snapd): yield _FakeError() yield _FakeResponse(content) - serializer = Serializer( - ignore_unknown_fields=True, serialize_enums_by='value') + return make_client(SnapdAPI, make_request, serializer=snapd_serializer) - return make_client(SnapdAPI, make_request, serializer=serializer) + +snapd_serializer = Serializer( + ignore_unknown_fields=True, serialize_enums_by='value') async def post_and_wait(client, meth, *args, **kw):