Skip to content

Commit

Permalink
storage: advertise erase and install scenarios
Browse files Browse the repository at this point in the history
Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
  • Loading branch information
ogayot committed Dec 2, 2024
1 parent 7d347d8 commit 3b2f2fc
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 2 deletions.
1 change: 1 addition & 0 deletions subiquity/common/types/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ class GuidedStorageTargetManual:


GuidedStorageTarget = Union[
GuidedStorageTargetEraseInstall,
GuidedStorageTargetReformat,
GuidedStorageTargetResize,
GuidedStorageTargetUseGap,
Expand Down
57 changes: 55 additions & 2 deletions subiquity/server/controllers/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,8 +890,10 @@ async def POST(self, config: list):
self.model.expose_recovery_keys()
await self.configured()

def potential_boot_disks(self, check_boot=True, with_reformatting=False):
disks = []
def potential_boot_disks(
self, check_boot=True, with_reformatting=False
) -> list[ModelDisk | Raid]:
disks: list[ModelDisk | Raid] = []
for raid in self.model._all(type="raid"):
if check_boot and not boot.can_be_boot_device(
raid, with_reformatting=with_reformatting
Expand Down Expand Up @@ -1277,6 +1279,56 @@ def available_target_resize_scenarios(
scenarios.append((vals.install_max, resize))
return scenarios

def available_erase_install_scenarios(
self, install_min: int
) -> list[tuple[int, GuidedStorageTargetEraseInstall]]:
scenarios: list[tuple[int, GuidedStorageTargetEraseInstall]] = []

for disk in self.potential_boot_disks(check_boot=False):
# Skip RAID until we know how to proceed.
if not isinstance(disk, ModelDisk):
continue

for partition in disk.partitions():
if partition._is_in_use:
continue

if partition.os is None:
continue

# Make an ephemeral copy of the disk object with the relevant
# partition removed. Then it's as if we're installing in the
# resulting gap (which will include free space that was
# directly before or after the partition that we removed).
altered_disk = disk._excluding_partition(partition)
if not boot.can_be_boot_device(altered_disk, with_reformatting=False):
continue

gap = gaps.includes(altered_disk, offset=partition.offset)
if not self.use_gap_has_enough_room_for_partitions(altered_disk, gap):
log.error(
"skipping TargetEraseInstall: not enough room for primary"
" partitions"
)
continue

capability_info = CapabilityInfo()
for variation in self._variation_info.values():
if variation.is_core_boot_classic():
continue
capability_info.combine(
variation.capability_info_for_gap(gap, install_min)
)

erase = GuidedStorageTargetEraseInstall(
disk.id,
partition.number,
allowed=capability_info.allowed,
disallowed=capability_info.disallowed,
)
scenarios.append((gap.size, erase))
return scenarios

async def v2_guided_GET(self, wait: bool = False) -> GuidedStorageResponseV2:
"""Acquire a list of possible guided storage configuration scenarios.
Results are sorted by the size of the space potentially available to
Expand Down Expand Up @@ -1314,6 +1366,7 @@ async def v2_guided_GET(self, wait: bool = False) -> GuidedStorageResponseV2:

scenarios.extend(self.available_use_gap_scenarios(install_min))
scenarios.extend(self.available_target_resize_scenarios(install_min))
scenarios.extend(self.available_erase_install_scenarios(install_min))

scenarios.sort(reverse=True, key=lambda x: x[0])
return GuidedStorageResponseV2(
Expand Down
44 changes: 44 additions & 0 deletions subiquity/server/controllers/tests/test_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -1709,6 +1709,50 @@ async def test_available_use_gap_scenarios(

self.assertEqual(expected_scenario, scenarios != [])

async def test_available_erase_install_scenarios(self):
await self._setup(Bootloader.NONE, "gpt", fix_bios=True)
install_min = self.fsc.calculate_suggested_install_min()

p1 = make_partition(self.model, self.disk, preserve=True, size=4 << 20)
p2 = make_partition(self.model, self.disk, preserve=True, size=4 << 20)

self.model._probe_data["os"] = {
p1._path(): {
"label": "Ubuntu",
"long": "Ubuntu 22.04.1 LTS",
"type": "linux",
"version": "22.04.1",
},
p2._path(): {
"label": "Ubuntu",
"long": "Ubuntu 20.04.7 LTS",
"type": "linux",
"version": "20.04.7",
},
}

scenario1, scenario2 = self.fsc.available_erase_install_scenarios(install_min)

# available_*_scenarios returns a list of tuple having an int as an index
scenario1 = scenario1[1]
scenario2 = scenario2[1]

self.assertIsInstance(scenario1, GuidedStorageTargetEraseInstall)
self.assertEqual(self.disk.id, scenario1.disk_id)
self.assertEqual(p1.number, scenario1.partition_number)
self.assertIsInstance(scenario2, GuidedStorageTargetEraseInstall)
self.assertEqual(self.disk.id, scenario2.disk_id)
self.assertEqual(p2.number, scenario2.partition_number)

async def test_available_erase_install_scenarios__no_os(self):
await self._setup(Bootloader.NONE, "gpt", fix_bios=True)
install_min = self.fsc.calculate_suggested_install_min()

make_partition(self.model, self.disk, preserve=True, size=4 << 20)
make_partition(self.model, self.disk, preserve=True, size=4 << 20)

self.assertFalse(self.fsc.available_erase_install_scenarios(install_min))

async def test_resize_has_enough_room_for_partitions__one_primary(self):
await self._setup(Bootloader.NONE, "gpt", fix_bios=True)

Expand Down

0 comments on commit 3b2f2fc

Please sign in to comment.