diff --git a/subiquity/server/controllers/filesystem.py b/subiquity/server/controllers/filesystem.py index ec5503ad..4b4db809 100644 --- a/subiquity/server/controllers/filesystem.py +++ b/subiquity/server/controllers/filesystem.py @@ -63,6 +63,7 @@ from subiquity.common.types import ( StorageResponseV2, ) from subiquity.models.filesystem import ( + align_up, align_down, LVM_CHUNK_SIZE, Raid, @@ -137,20 +138,33 @@ class FilesystemController(SubiquityController, FilesystemManipulator): "autoinstall config did not create needed bootloader " "partition") - def setup_disk_for_guided(self, disk, mode): + def setup_disk_for_guided(self, target, mode): + if isinstance(target, gaps.Gap): + disk = target.device + gap = target + else: + disk = target + gap = None if mode is None or mode == 'reformat_disk': self.reformat(disk, wipe='superblock-recursive') if DeviceAction.TOGGLE_BOOT in DeviceAction.supported(disk): - self.add_boot_disk(disk) - return gaps.largest_gap(disk) + self.add_boot_disk(target) + if gap is None: + return disk, gaps.largest_gap(disk) + else: + # find what's left of the gap after adding boot + gap = gaps.within(disk, gap) + if gap is None: + raise Exception(f'failed to locate gap after adding boot') + return disk, gap - def guided_direct(self, disk, mode=None): - gap = self.setup_disk_for_guided(disk, mode) + def guided_direct(self, target, mode=None): + disk, gap = self.setup_disk_for_guided(target, mode) spec = dict(fstype="ext4", mount="/") self.create_partition(device=disk, gap=gap, spec=spec) - def guided_lvm(self, disk, mode=None, lvm_options=None): - gap = self.setup_disk_for_guided(disk, mode) + def guided_lvm(self, target, mode=None, lvm_options=None): + disk, gap = self.setup_disk_for_guided(target, mode) gap_boot, gap_rest = gap.split(sizes.get_bootfs_size(gap.size)) spec = dict(fstype="ext4", mount='/boot') self.create_partition(device=disk, gap=gap_boot, spec=spec) @@ -195,15 +209,27 @@ class FilesystemController(SubiquityController, FilesystemManipulator): def guided(self, choice: GuidedChoiceV2): self.model.guided_configuration = choice + disk = self.model._one(id=choice.target.disk_id) if isinstance(choice.target, GuidedStorageTargetReformat): mode = 'reformat_disk' + target = disk elif isinstance(choice.target, GuidedStorageTargetUseGap): - mode = 'use_gap' # FIXME not the correct gap + mode = 'use_gap' + target = gaps.at_offset(disk, choice.target.gap.offset) elif isinstance(choice.target, GuidedStorageTargetResize): - mode = 'resize' # FIXME not actually working + partition = self.get_partition( + disk, choice.target.partition_number) + part_align = disk.alignment_data().part_align + new_size = align_up(choice.target.new_size, part_align) + if new_size > partition.size: + raise Exception(f'Aligned requested size {new_size} too large') + gap_offset = partition.offset + new_size + partition.size = new_size + partition.resize = True + mode = 'use_gap' + target = gaps.at_offset(disk, gap_offset) else: raise Exception(f'Unknown guided target {choice.target}') - disk = self.model._one(id=choice.target.disk_id) if choice.use_lvm: lvm_options = None @@ -214,9 +240,9 @@ class FilesystemController(SubiquityController, FilesystemManipulator): 'password': choice.password, }, } - self.guided_lvm(disk, mode=mode, lvm_options=lvm_options) + self.guided_lvm(target, mode=mode, lvm_options=lvm_options) else: - self.guided_direct(disk, mode=mode) + self.guided_direct(target, mode=mode) async def _probe_response(self, wait, resp_cls): if self._probe_task.task is None or not self._probe_task.task.done(): @@ -381,7 +407,10 @@ class FilesystemController(SubiquityController, FilesystemManipulator): for disk in self.get_guided_disks(with_reformatting=False): gap = gaps.largest_gap(disk) if gap is not None and gap.size >= install_min: - use_gap = GuidedStorageTargetUseGap(disk_id=disk.id, gap=gap) + api_gap = labels.for_client(gap) + use_gap = GuidedStorageTargetUseGap( + disk_id=disk.id, + gap=api_gap) scenarios.append((gap.size, use_gap)) for disk in self.get_guided_disks(check_boot=False): @@ -539,8 +568,8 @@ class FilesystemController(SubiquityController, FilesystemManipulator): if mode == 'reformat_disk': match = layout.get("match", {'size': 'largest'}) - disk = self.model.disk_for_match(self.model.all_disks(), match) - if not disk: + target = self.model.disk_for_match(self.model.all_disks(), match) + if not target: raise Exception("autoinstall cannot configure storage " "- no disk found large enough for install") elif mode == 'use_gap': @@ -552,10 +581,10 @@ class FilesystemController(SubiquityController, FilesystemManipulator): "- no gap found large enough for install") # This is not necessarily the exact gap to be used, as the gap size # may change once add_boot_disk has sorted things out. - disk = gap.device + target = gap log.info(f'autoinstall: running guided {name} install in mode {mode} ' - f'using {disk}') - guided_method(disk=disk, mode=mode) + f'using {target}') + guided_method(target=target, mode=mode) def validate_layout_mode(self, mode): if mode not in ('reformat_disk', 'use_gap'): diff --git a/subiquity/server/controllers/tests/test_filesystem.py b/subiquity/server/controllers/tests/test_filesystem.py index 33a7aa03..ad4c18dd 100644 --- a/subiquity/server/controllers/tests/test_filesystem.py +++ b/subiquity/server/controllers/tests/test_filesystem.py @@ -21,6 +21,7 @@ from subiquity.server.controllers.filesystem import FilesystemController from subiquitycore.tests.mocks import make_app from subiquity.common.filesystem import gaps +from subiquity.common import types from subiquity.common.types import ( Bootloader, GuidedStorageTargetReformat, @@ -154,10 +155,11 @@ class TestGuidedV2(IsolatedAsyncioTestCase): @parameterized.expand(bootloaders) async def test_blank_disk(self, bootloader): self._setup(bootloader) - d = make_disk(self.model) + d = make_disk(self.model, size=(30 << 30) + (2 << 20)) + gap = types.Gap(offset=1 << 20, size=30 << 30) expected = [ GuidedStorageTargetReformat(disk_id=d.id), - GuidedStorageTargetUseGap(disk_id=d.id, gap=gaps.largest_gap(d)), + GuidedStorageTargetUseGap(disk_id=d.id, gap=gap), ] resp = await self.fsc.v2_guided_GET() self.assertEqual(expected, resp.possible) @@ -165,14 +167,14 @@ class TestGuidedV2(IsolatedAsyncioTestCase): @parameterized.expand(bootloaders) async def test_used_half_disk(self, bootloader): self._setup(bootloader) - d = make_disk(self.model) - p1 = make_partition(self.model, d) + d = make_disk(self.model, size=(30 << 30) + (2 << 20)) + p1 = make_partition(self.model, d, preserve=True, size=15 << 30) self.fs_probe[p1._path()] = {'ESTIMATED_MIN_SIZE': 1 << 20} resp = await self.fsc.v2_guided_GET() reformat = resp.possible[0] self.assertEqual(GuidedStorageTargetReformat(disk_id=d.id), reformat) if bootloader != Bootloader.BIOS: - gap = gaps.largest_gap(d) + gap = types.Gap(offset=(15 << 30) + (1 << 20), size=15 << 30) self.assertEqual( GuidedStorageTargetUseGap(disk_id=d.id, gap=gap), resp.possible[1])