reuse an existing partition if possible in apply_system

This required a bit of a refactor which lead to rewriting tests as well
as adding a new one.
This commit is contained in:
Michael Hudson-Doyle 2022-11-17 12:39:44 +13:00
parent e735f83a8a
commit 72c7e8df43
3 changed files with 118 additions and 57 deletions

View File

@ -452,43 +452,60 @@ class FilesystemController(SubiquityController, FilesystemManipulator):
for structure in volume.structure:
if structure.role == snapdapi.Role.MBR:
continue
if ',' not in structure.type:
continue
if structure.offset is not None:
offset = structure.offset
yield (structure, offset, structure.size)
offset = offset + structure.size
def _add_structure(self, *, disk: Disk, offset: int, size: int,
is_last: bool, structure: snapdapi.VolumeStructure):
if structure.role == snapdapi.Role.SYSTEM_DATA and is_last:
size = gaps.largest_gap(disk).size
flag = ptable_uuid_to_flag_entry(structure.gpt_part_uuid())[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')
self._role_to_device[structure.role] = part
def apply_system(self, disk_id):
disk = self.model._one(id=disk_id)
self.reformat(disk)
[volume] = self._system.volumes.values()
preserved_parts = set()
if volume.schema != disk.ptable:
self.reformat(disk)
disk.ptable = volume.schema
parts_by_offset_size = {}
else:
parts_by_offset_size = {
(part.offset, part.size): part for part in disk.partitions()
}
for _struct, offset, size in self._offsets_and_sizes_for_system():
if (offset, size) in parts_by_offset_size:
preserved_parts.add(parts_by_offset_size[(offset, size)])
for part in disk.partitions():
if part not in preserved_parts:
self.delete_partition(part)
del parts_by_offset_size[(part.offset, part.size)]
for structure, offset, size in self._offsets_and_sizes_for_system():
self._add_structure(
disk=disk, offset=offset, size=size, structure=structure,
is_last=structure == volume.structure[-1])
if (offset, size) in parts_by_offset_size:
part = parts_by_offset_size[(offset, size)]
else:
if structure.role == snapdapi.Role.SYSTEM_DATA and \
structure == volume.structure[-1]:
gap = gaps.largest_gap(disk)
size = gap.size - (offset - gap.offset)
part = self.model.add_partition(disk, offset=offset, size=size)
part.flag = ptable_uuid_to_flag_entry(structure.gpt_part_uuid())[0]
if structure.name:
part.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 part.flag == 'boot':
self.model.add_mount(fs, '/boot/efi')
self._role_to_device[structure.role] = part
disk._partitions.sort(key=lambda p: p.number)
def _on_volumes(self) -> Dict[str, snapdapi.OnVolume]:

View File

@ -476,45 +476,86 @@ class TestCoreBootInstallMethods(IsolatedAsyncioTestCase):
self.fsc._configured = True
self.fsc.model = make_model(Bootloader.UEFI)
def test_add_structure(self):
def _details_for_structures(self, structures):
return snapdapi.SystemDetails(
volumes={'pc': snapdapi.Volume(schema='gpt', structure=structures)}
)
def test_apply_system(self):
disk = make_disk(self.fsc.model)
self.fsc._add_structure(
disk=disk,
offset=100,
size=2 << 20,
is_last=False,
structure=snapdapi.VolumeStructure(
name='ptname',
self.fsc._system = self._details_for_structures([
snapdapi.VolumeStructure(
type="83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
label='label',
size=1 << 20,
role=snapdapi.Role.SYSTEM_DATA,
filesystem='ext4'))
offset=1 << 20,
size=1 << 30,
filesystem='ext4'),
])
self.fsc.apply_system(disk.id)
[part] = disk.partitions()
self.assertEqual(part.offset, 100)
self.assertEqual(part.offset, 1 << 20)
self.assertEqual(part.size, 1 << 30)
self.assertEqual(part.fs().fstype, 'ext4')
def test_apply_system_reuse(self):
disk = make_disk(self.fsc.model)
# Add a partition that matches one in the volume structure
reused_part = make_partition(
self.fsc.model, disk, offset=1 << 20, size=1 << 30, preserve=True)
# And one that does not.
make_partition(
self.fsc.model, disk, offset=2 << 30, size=1 << 30, preserve=True)
self.fsc._system = self._details_for_structures([
snapdapi.VolumeStructure(
type="0FC63DAF-8483-4772-8E79-3D69D8477DE4",
offset=1 << 20,
size=1 << 30,
filesystem='ext4'),
])
self.fsc.apply_system(disk.id)
[part] = disk.partitions()
self.assertEqual(reused_part, part)
self.assertEqual(reused_part.wipe, 'superblock')
self.assertEqual(part.fs().fstype, 'ext4')
def test_apply_system_reuse_no_format(self):
disk = make_disk(self.fsc.model)
existing_part = make_partition(
self.fsc.model, disk, offset=1 << 20, size=1 << 30, preserve=True)
self.fsc._system = self._details_for_structures([
snapdapi.VolumeStructure(
type="0FC63DAF-8483-4772-8E79-3D69D8477DE4",
offset=1 << 20,
size=1 << 30,
filesystem=None),
])
self.fsc.apply_system(disk.id)
[part] = disk.partitions()
self.assertEqual(existing_part, part)
self.assertEqual(existing_part.wipe, None)
def test_apply_system_system_data(self):
disk = make_disk(self.fsc.model)
self.fsc._system = self._details_for_structures([
snapdapi.VolumeStructure(
type="0FC63DAF-8483-4772-8E79-3D69D8477DE4",
offset=2 << 20,
name='ptname',
size=2 << 30,
role=snapdapi.Role.SYSTEM_DATA,
filesystem='ext4'),
])
self.fsc.apply_system(disk.id)
[part] = disk.partitions()
self.assertEqual(part.offset, 2 << 20)
self.assertEqual(part.partition_name, 'ptname')
self.assertEqual(part.flag, 'linux')
self.assertEqual(part.size, 2 << 20)
self.assertEqual(
part.size,
disk.size - (2 << 20) - disk.alignment_data().min_end_offset)
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)
self.fsc._add_structure(
disk=disk,
offset=100,
size=2 << 20,
is_last=False,
structure=snapdapi.VolumeStructure(
type="83,0FC63DAF-8483-4772-8E79-3D69D8477DE4",
size=1 << 20,
filesystem=None))
[part] = disk.partitions()
self.assertEqual(part.size, 2 << 20)
self.assertEqual(part.fs(), None)
self.assertEqual(part.wipe, None)
async def test_from_sample_data(self):
# calling this a unit test is definitely questionable. but it
# runs much more quickly than the integration test!

View File

@ -142,7 +142,10 @@ class VolumeStructure:
update: VolumeUpdate = attr.Factory(VolumeUpdate)
def gpt_part_uuid(self):
return self.type.split(',', 1)[1].upper()
if ',' in self.type:
return self.type.split(',', 1)[1].upper()
else:
return self.type
@attr.s(auto_attribs=True)