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:
parent
e735f83a8a
commit
72c7e8df43
|
@ -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]:
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue