Combine "can this be" and "make this a" boot disk logic for BIOS
No behaviour change yet, this all assumes version 1 partitioning, i.e. no mix of new and old partitions on a device. But it means we only have to edit one batch of logic when that stops being true.
This commit is contained in:
parent
c226d4e2b5
commit
dfc4a32b0a
|
@ -15,6 +15,9 @@
|
|||
|
||||
import functools
|
||||
|
||||
import attr
|
||||
|
||||
from subiquity.common.filesystem import gaps, sizes
|
||||
from subiquity.models.filesystem import (
|
||||
Disk,
|
||||
Raid,
|
||||
|
@ -50,6 +53,101 @@ def _is_boot_device_raid(raid):
|
|||
return any(p.grub_device for p in raid._partitions)
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class CreatePartPlan:
|
||||
device: object
|
||||
|
||||
offset: int = 0
|
||||
|
||||
spec: dict = attr.ib(factory=dict)
|
||||
args: dict = attr.ib(factory=dict)
|
||||
|
||||
def apply(self, manipulator):
|
||||
manipulator.create_partition(
|
||||
self.device, gaps.Gap(self.device, self.offset, 0), self.spec,
|
||||
**self.args)
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class ResizePlan:
|
||||
part: object
|
||||
size_delta: int = 0
|
||||
|
||||
def apply(self, manipulator):
|
||||
self.part.size += self.size_delta
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class SlidePlan:
|
||||
parts: list
|
||||
offset_delta: int = 0
|
||||
|
||||
def apply(self, manipulator):
|
||||
for part in self.parts:
|
||||
part.offset += self.offset_delta
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class SetAttrPlan:
|
||||
device: object
|
||||
attr: str
|
||||
val: str
|
||||
|
||||
def apply(self, manipulator):
|
||||
setattr(self.device, self.attr, self.val)
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class MultiStepPlan:
|
||||
plans: list
|
||||
|
||||
def apply(self, manipulator):
|
||||
for plan in self.plans:
|
||||
plan.apply(manipulator)
|
||||
|
||||
|
||||
def get_boot_device_plan_bios(device):
|
||||
attr_plan = SetAttrPlan(device, 'grub_device', True)
|
||||
if device.ptable == 'msdos':
|
||||
return attr_plan
|
||||
if device._has_preexisting_partition():
|
||||
if device._partitions[0].flag == "bios_grub":
|
||||
return attr_plan
|
||||
else:
|
||||
return None
|
||||
|
||||
create_part_plan = CreatePartPlan(
|
||||
device=device,
|
||||
offset=sizes.BIOS_GRUB_SIZE_BYTES,
|
||||
spec=dict(size=sizes.BIOS_GRUB_SIZE_BYTES, fstype=None, mount=None),
|
||||
args=dict(flag='bios_grub'))
|
||||
|
||||
partitions = device.partitions()
|
||||
|
||||
if gaps.largest_gap_size(device) >= sizes.BIOS_GRUB_SIZE_BYTES:
|
||||
return MultiStepPlan(plans=[
|
||||
SlidePlan(
|
||||
parts=partitions,
|
||||
offset_delta=sizes.BIOS_GRUB_SIZE_BYTES),
|
||||
create_part_plan,
|
||||
attr_plan,
|
||||
])
|
||||
else:
|
||||
largest_i, largest_part = max(
|
||||
enumerate(partitions),
|
||||
key=lambda i_p: i_p[1].size)
|
||||
return MultiStepPlan(plans=[
|
||||
SlidePlan(
|
||||
parts=partitions[:largest_i+1],
|
||||
offset_delta=sizes.BIOS_GRUB_SIZE_BYTES),
|
||||
ResizePlan(
|
||||
part=largest_part,
|
||||
size_delta=-sizes.BIOS_GRUB_SIZE_BYTES),
|
||||
create_part_plan,
|
||||
attr_plan,
|
||||
])
|
||||
|
||||
|
||||
@functools.singledispatch
|
||||
def can_be_boot_device(device, *, with_reformatting=False):
|
||||
"""Can `device` be made into a boot device?
|
||||
|
@ -63,13 +161,12 @@ def can_be_boot_device(device, *, with_reformatting=False):
|
|||
@can_be_boot_device.register(Disk)
|
||||
def _can_be_boot_device_disk(disk, *, with_reformatting=False):
|
||||
bl = disk._m.bootloader
|
||||
if disk._has_preexisting_partition() and not with_reformatting:
|
||||
if bl == Bootloader.BIOS:
|
||||
if disk.ptable == "msdos":
|
||||
return True
|
||||
else:
|
||||
return disk._partitions[0].flag == "bios_grub"
|
||||
elif bl == Bootloader.UEFI:
|
||||
if with_reformatting:
|
||||
return True
|
||||
if bl == Bootloader.BIOS:
|
||||
return get_boot_device_plan_bios(disk) is not None
|
||||
if disk._has_preexisting_partition():
|
||||
if bl == Bootloader.UEFI:
|
||||
return any(is_esp(p) for p in disk._partitions)
|
||||
elif bl == Bootloader.PREP:
|
||||
return any(p.flag == "prep" for p in disk._partitions)
|
||||
|
|
|
@ -103,11 +103,11 @@ class FilesystemManipulator:
|
|||
spec = dict(size=part_size, fstype='fat32')
|
||||
if self.model._mount_for_path("/boot/efi") is None:
|
||||
spec['mount'] = '/boot/efi'
|
||||
part = self._create_boot_with_resize(
|
||||
self._create_boot_with_resize(
|
||||
disk, spec, flag="boot", grub_device=True)
|
||||
elif bootloader == Bootloader.PREP:
|
||||
log.debug('_create_boot_partition - adding PReP partition')
|
||||
part = self._create_boot_with_resize(
|
||||
self._create_boot_with_resize(
|
||||
disk,
|
||||
dict(size=sizes.PREP_GRUB_SIZE_BYTES, fstype=None, mount=None),
|
||||
# must be wiped or grub-install will fail
|
||||
|
@ -115,12 +115,7 @@ class FilesystemManipulator:
|
|||
flag='prep', grub_device=True)
|
||||
elif bootloader == Bootloader.BIOS:
|
||||
log.debug('_create_boot_partition - adding bios_grub partition')
|
||||
part = self._create_boot_with_resize(
|
||||
disk,
|
||||
dict(size=sizes.BIOS_GRUB_SIZE_BYTES, fstype=None, mount=None),
|
||||
flag='bios_grub')
|
||||
disk.grub_device = True
|
||||
return part
|
||||
boot.get_boot_device_plan_bios(disk).apply(self)
|
||||
|
||||
def create_raid(self, spec):
|
||||
for d in spec['devices'] | spec['spare_devices']:
|
||||
|
@ -221,14 +216,14 @@ class FilesystemManipulator:
|
|||
log.debug('model needs a bootloader partition? {}'.format(needs_boot))
|
||||
can_be_boot = boot.can_be_boot_device(disk)
|
||||
if needs_boot and len(disk.partitions()) == 0 and can_be_boot:
|
||||
part = self._create_boot_partition(disk)
|
||||
self._create_boot_partition(disk)
|
||||
|
||||
# adjust downward the partition size (if necessary) to accommodate
|
||||
# bios/grub partition
|
||||
if spec['size'] > gaps.largest_gap_size(disk):
|
||||
log.debug(
|
||||
"Adjusting request down: %s - %s = %s",
|
||||
spec['size'], part.size, gaps.largest_gap_size(disk))
|
||||
"Adjusting request down from %s to %s",
|
||||
spec['size'], gaps.largest_gap_size(disk))
|
||||
spec['size'] = gaps.largest_gap_size(disk)
|
||||
|
||||
self.create_partition(disk, gap, spec)
|
||||
|
@ -345,10 +340,11 @@ class FilesystemManipulator:
|
|||
if not self.supports_resilient_boot:
|
||||
for disk in boot.all_boot_devices(self.model):
|
||||
self.remove_boot_disk(disk)
|
||||
if bootloader == Bootloader.BIOS:
|
||||
boot.get_boot_device_plan_bios(new_boot_disk).apply(self)
|
||||
return
|
||||
if new_boot_disk._has_preexisting_partition():
|
||||
if bootloader == Bootloader.BIOS:
|
||||
new_boot_disk.grub_device = True
|
||||
elif bootloader == Bootloader.UEFI:
|
||||
if bootloader == Bootloader.UEFI:
|
||||
should_mount = self.model._mount_for_path('/boot/efi') is None
|
||||
for p in new_boot_disk.partitions():
|
||||
if boot.is_esp(p):
|
||||
|
|
|
@ -23,9 +23,11 @@ from subiquity.common.filesystem.manipulator import FilesystemManipulator
|
|||
from subiquity.models.tests.test_filesystem import (
|
||||
make_disk,
|
||||
make_model,
|
||||
make_partition,
|
||||
)
|
||||
from subiquity.models.filesystem import (
|
||||
Bootloader,
|
||||
MiB,
|
||||
)
|
||||
|
||||
|
||||
|
@ -236,3 +238,50 @@ class TestFilesystemManipulator(unittest.TestCase):
|
|||
manipulator.add_boot_disk(disk1)
|
||||
part = gaps.parts_and_gaps(disk1)[0]
|
||||
self.assertEqual(1024 * 1024, part.offset)
|
||||
|
||||
def test_add_boot_BIOS_empty(self):
|
||||
manipulator = make_manipulator(Bootloader.BIOS)
|
||||
disk = make_disk(manipulator.model, preserve=True)
|
||||
manipulator.add_boot_disk(disk)
|
||||
[part] = disk.partitions()
|
||||
self.assertEqual(part.offset, MiB)
|
||||
|
||||
def test_add_boot_BIOS_full(self):
|
||||
manipulator = make_manipulator(Bootloader.BIOS)
|
||||
disk = make_disk(manipulator.model, preserve=True)
|
||||
part = make_partition(
|
||||
manipulator.model, disk, size=gaps.largest_gap_size(disk))
|
||||
size_before = part.size
|
||||
manipulator.add_boot_disk(disk)
|
||||
[p1, p2] = disk.partitions()
|
||||
self.assertIs(p2, part)
|
||||
size_after = p2.size
|
||||
self.assertEqual(p1.offset, MiB)
|
||||
self.assertEqual(p2.offset, 2*MiB)
|
||||
self.assertEqual(size_after, size_before - MiB)
|
||||
|
||||
def test_add_boot_BIOS_half_full(self):
|
||||
manipulator = make_manipulator(Bootloader.BIOS)
|
||||
disk = make_disk(manipulator.model, preserve=True)
|
||||
part = make_partition(
|
||||
manipulator.model, disk, size=gaps.largest_gap_size(disk)//2)
|
||||
size_before = part.size
|
||||
manipulator.add_boot_disk(disk)
|
||||
[p1, p2] = disk.partitions()
|
||||
size_after = p2.size
|
||||
self.assertIs(p2, part)
|
||||
self.assertEqual(p1.offset, MiB)
|
||||
self.assertEqual(p2.offset, 2*MiB)
|
||||
self.assertEqual(size_after, size_before)
|
||||
|
||||
def DONT_test_add_boot_BIOS_preserved(self): # needs v2 partitioning
|
||||
manipulator = make_manipulator(Bootloader.BIOS)
|
||||
disk = make_disk(manipulator.model, preserve=True)
|
||||
half_size = gaps.largest_gap_size(disk)//2
|
||||
part = make_partition(
|
||||
manipulator.model, disk, size=half_size, offset=half_size)
|
||||
manipulator.add_boot_disk(disk)
|
||||
[p1, p2] = disk.partitions()
|
||||
self.assertIs(p2, part)
|
||||
self.assertEqual(p1.offset, MiB)
|
||||
self.assertEqual(p2.offset, half_size)
|
||||
|
|
Loading…
Reference in New Issue