diff --git a/subiquity/common/filesystem/manipulator.py b/subiquity/common/filesystem/manipulator.py index 6faa2c60..cffac81d 100644 --- a/subiquity/common/filesystem/manipulator.py +++ b/subiquity/common/filesystem/manipulator.py @@ -15,74 +15,15 @@ import logging -import attr - -from subiquity.common.filesystem import boot, gaps +from subiquity.common.filesystem import boot, gaps, sizes from subiquity.common.types import Bootloader from subiquity.models.filesystem import ( align_up, - MiB, Partition, ) log = logging.getLogger('subiquity.common.filesystem.manipulator') -BIOS_GRUB_SIZE_BYTES = 1 * MiB -PREP_GRUB_SIZE_BYTES = 8 * MiB - - -@attr.s(auto_attribs=True) -class PartitionScaleFactors: - minimum: int - priority: int - maximum: int - - -uefi_scale = PartitionScaleFactors( - minimum=538 * MiB, - priority=538, - maximum=1075 * MiB) -bootfs_scale = PartitionScaleFactors( - minimum=768 * MiB, - priority=1024, - maximum=1536 * MiB) -rootfs_scale = PartitionScaleFactors( - minimum=900 * MiB, - priority=10000, - maximum=-1) - - -def scale_partitions(all_factors, disk_size): - """for the list of scale factors, provide list of scaled partition size. - Assumes at most one scale factor with maximum==-1, and - disk_size is at least as big as the sum of all partition minimums. - The scale factor with maximum==-1 is given all remaining disk space.""" - ret = [] - sum_priorities = sum([factor.priority for factor in all_factors]) - for cur in all_factors: - scaled = int((disk_size / sum_priorities) * cur.priority) - if scaled < cur.minimum: - ret.append(cur.minimum) - elif scaled > cur.maximum: - ret.append(cur.maximum) - else: - ret.append(scaled) - if -1 in ret: - used = sum(filter(lambda x: x != -1, ret)) - idx = ret.index(-1) - ret[idx] = disk_size - used - return ret - - -def get_efi_size(disk): - all_factors = (uefi_scale, bootfs_scale, rootfs_scale) - return scale_partitions(all_factors, disk.size)[0] - - -def get_bootfs_size(disk): - all_factors = (uefi_scale, bootfs_scale, rootfs_scale) - return scale_partitions(all_factors, disk.size)[1] - class FilesystemManipulator: @@ -157,7 +98,7 @@ class FilesystemManipulator: def _create_boot_partition(self, disk): bootloader = self.model.bootloader if bootloader == Bootloader.UEFI: - part_size = get_efi_size(disk) + part_size = sizes.get_efi_size(disk) log.debug('_create_boot_partition - adding EFI partition') spec = dict(size=part_size, fstype='fat32') if self.model._mount_for_path("/boot/efi") is None: @@ -168,7 +109,7 @@ class FilesystemManipulator: log.debug('_create_boot_partition - adding PReP partition') part = self._create_boot_with_resize( disk, - dict(size=PREP_GRUB_SIZE_BYTES, fstype=None, mount=None), + dict(size=sizes.PREP_GRUB_SIZE_BYTES, fstype=None, mount=None), # must be wiped or grub-install will fail wipe='zero', flag='prep', grub_device=True) @@ -176,7 +117,7 @@ class FilesystemManipulator: log.debug('_create_boot_partition - adding bios_grub partition') part = self._create_boot_with_resize( disk, - dict(size=BIOS_GRUB_SIZE_BYTES, fstype=None, mount=None), + dict(size=sizes.BIOS_GRUB_SIZE_BYTES, fstype=None, mount=None), flag='bios_grub') disk.grub_device = True return part diff --git a/subiquity/common/filesystem/sizes.py b/subiquity/common/filesystem/sizes.py new file mode 100644 index 00000000..22edd1be --- /dev/null +++ b/subiquity/common/filesystem/sizes.py @@ -0,0 +1,77 @@ +# Copyright 2022 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import attr + +from subiquity.models.filesystem import ( + MiB, + ) + + +BIOS_GRUB_SIZE_BYTES = 1 * MiB +PREP_GRUB_SIZE_BYTES = 8 * MiB + + +@attr.s(auto_attribs=True) +class PartitionScaleFactors: + minimum: int + priority: int + maximum: int + + +uefi_scale = PartitionScaleFactors( + minimum=538 * MiB, + priority=538, + maximum=1075 * MiB) +bootfs_scale = PartitionScaleFactors( + minimum=768 * MiB, + priority=1024, + maximum=1536 * MiB) +rootfs_scale = PartitionScaleFactors( + minimum=900 * MiB, + priority=10000, + maximum=-1) + + +def scale_partitions(all_factors, disk_size): + """for the list of scale factors, provide list of scaled partition size. + Assumes at most one scale factor with maximum==-1, and + disk_size is at least as big as the sum of all partition minimums. + The scale factor with maximum==-1 is given all remaining disk space.""" + ret = [] + sum_priorities = sum([factor.priority for factor in all_factors]) + for cur in all_factors: + scaled = int((disk_size / sum_priorities) * cur.priority) + if scaled < cur.minimum: + ret.append(cur.minimum) + elif scaled > cur.maximum: + ret.append(cur.maximum) + else: + ret.append(scaled) + if -1 in ret: + used = sum(filter(lambda x: x != -1, ret)) + idx = ret.index(-1) + ret[idx] = disk_size - used + return ret + + +def get_efi_size(disk): + all_factors = (uefi_scale, bootfs_scale, rootfs_scale) + return scale_partitions(all_factors, disk.size)[0] + + +def get_bootfs_size(disk): + all_factors = (uefi_scale, bootfs_scale, rootfs_scale) + return scale_partitions(all_factors, disk.size)[1] diff --git a/subiquity/common/filesystem/tests/test_manipulator.py b/subiquity/common/filesystem/tests/test_manipulator.py index f8a4050c..0f1c6d7b 100644 --- a/subiquity/common/filesystem/tests/test_manipulator.py +++ b/subiquity/common/filesystem/tests/test_manipulator.py @@ -19,15 +19,7 @@ from subiquity.common.filesystem.actions import ( DeviceAction, ) from subiquity.common.filesystem import gaps -from subiquity.common.filesystem.manipulator import ( - bootfs_scale, - FilesystemManipulator, - get_efi_size, - get_bootfs_size, - PartitionScaleFactors, - scale_partitions, - uefi_scale, - ) +from subiquity.common.filesystem.manipulator import FilesystemManipulator from subiquity.models.tests.test_filesystem import ( make_disk, make_model, @@ -242,57 +234,3 @@ class TestFilesystemManipulator(unittest.TestCase): manipulator.add_boot_disk(disk1) part = gaps.parts_and_gaps(disk1)[0] self.assertEqual(1024 * 1024, part.offset) - - -class TestPartitionSizeScaling(unittest.TestCase): - def test_scale_factors(self): - psf = [ - PartitionScaleFactors(minimum=100, priority=500, maximum=500), - PartitionScaleFactors(minimum=1000, priority=9500, maximum=-1), - ] - - # match priorities, should get same values back - self.assertEqual([500, 9500], scale_partitions(psf, 10000)) - - # half priorities, should be scaled - self.assertEqual([250, 4750], scale_partitions(psf, 5000)) - - # hit max on first partition, second should use rest of space - self.assertEqual([500, 19500], scale_partitions(psf, 20000)) - - # minimums - self.assertEqual([100, 1000], scale_partitions(psf, 1100)) - - # ints - self.assertEqual([105, 1996], scale_partitions(psf, 2101)) - - def test_no_max_equal_minus_one(self): - psf = [ - PartitionScaleFactors(minimum=100, priority=500, maximum=500), - PartitionScaleFactors(minimum=100, priority=500, maximum=500), - ] - - self.assertEqual([500, 500], scale_partitions(psf, 2000)) - - def test_efi(self): - manipulator = make_manipulator(Bootloader.UEFI) - tests = [ - # something large to hit maximums - (30 << 30, uefi_scale.maximum, bootfs_scale.maximum), - # and something small to hit minimums - (8 << 30, uefi_scale.minimum, bootfs_scale.minimum), - ] - for disk_size, uefi, bootfs in tests: - disk = make_disk(manipulator.model, preserve=True, size=disk_size) - self.assertEqual(uefi, get_efi_size(disk)) - self.assertEqual(bootfs, get_bootfs_size(disk)) - - # something in between for scaling - disk_size = 15 << 30 - disk = make_disk(manipulator.model, preserve=True, size=disk_size) - efi_size = get_efi_size(disk) - self.assertTrue(uefi_scale.maximum > efi_size) - self.assertTrue(efi_size > uefi_scale.minimum) - bootfs_size = get_bootfs_size(disk) - self.assertTrue(bootfs_scale.maximum > bootfs_size) - self.assertTrue(bootfs_size > bootfs_scale.minimum) diff --git a/subiquity/common/filesystem/tests/test_sizes.py b/subiquity/common/filesystem/tests/test_sizes.py new file mode 100644 index 00000000..277b915f --- /dev/null +++ b/subiquity/common/filesystem/tests/test_sizes.py @@ -0,0 +1,82 @@ +# Copyright 2022 Canonical, Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import unittest + +from subiquity.common.filesystem.sizes import ( + bootfs_scale, + get_efi_size, + get_bootfs_size, + PartitionScaleFactors, + scale_partitions, + uefi_scale, + ) +from subiquity.common.filesystem.tests.test_manipulator import make_manipulator +from subiquity.models.filesystem import Bootloader +from subiquity.models.tests.test_filesystem import make_disk + + +class TestPartitionSizeScaling(unittest.TestCase): + def test_scale_factors(self): + psf = [ + PartitionScaleFactors(minimum=100, priority=500, maximum=500), + PartitionScaleFactors(minimum=1000, priority=9500, maximum=-1), + ] + + # match priorities, should get same values back + self.assertEqual([500, 9500], scale_partitions(psf, 10000)) + + # half priorities, should be scaled + self.assertEqual([250, 4750], scale_partitions(psf, 5000)) + + # hit max on first partition, second should use rest of space + self.assertEqual([500, 19500], scale_partitions(psf, 20000)) + + # minimums + self.assertEqual([100, 1000], scale_partitions(psf, 1100)) + + # ints + self.assertEqual([105, 1996], scale_partitions(psf, 2101)) + + def test_no_max_equal_minus_one(self): + psf = [ + PartitionScaleFactors(minimum=100, priority=500, maximum=500), + PartitionScaleFactors(minimum=100, priority=500, maximum=500), + ] + + self.assertEqual([500, 500], scale_partitions(psf, 2000)) + + def test_efi(self): + manipulator = make_manipulator(Bootloader.UEFI) + tests = [ + # something large to hit maximums + (30 << 30, uefi_scale.maximum, bootfs_scale.maximum), + # and something small to hit minimums + (8 << 30, uefi_scale.minimum, bootfs_scale.minimum), + ] + for disk_size, uefi, bootfs in tests: + disk = make_disk(manipulator.model, preserve=True, size=disk_size) + self.assertEqual(uefi, get_efi_size(disk)) + self.assertEqual(bootfs, get_bootfs_size(disk)) + + # something in between for scaling + disk_size = 15 << 30 + disk = make_disk(manipulator.model, preserve=True, size=disk_size) + efi_size = get_efi_size(disk) + self.assertTrue(uefi_scale.maximum > efi_size) + self.assertTrue(efi_size > uefi_scale.minimum) + bootfs_size = get_bootfs_size(disk) + self.assertTrue(bootfs_scale.maximum > bootfs_size) + self.assertTrue(bootfs_size > bootfs_scale.minimum) diff --git a/subiquity/server/controllers/filesystem.py b/subiquity/server/controllers/filesystem.py index 3bb5beb7..8be3d947 100644 --- a/subiquity/server/controllers/filesystem.py +++ b/subiquity/server/controllers/filesystem.py @@ -41,10 +41,9 @@ from subiquity.common.errorreport import ErrorReportKind from subiquity.common.filesystem.actions import ( DeviceAction, ) -from subiquity.common.filesystem import boot, gaps, labels +from subiquity.common.filesystem import boot, gaps, labels, sizes from subiquity.common.filesystem.manipulator import ( FilesystemManipulator, - get_bootfs_size, ) from subiquity.common.types import ( Bootloader, @@ -146,7 +145,7 @@ class FilesystemController(SubiquityController, FilesystemManipulator): gap = gaps.largest_gap(disk) self.create_partition( device=disk, gap=gap, spec=dict( - size=get_bootfs_size(disk), + size=sizes.get_bootfs_size(disk), fstype="ext4", mount='/boot' ))