From 9fd3c924791b737d6b6cce04a35e3b82d45b535b Mon Sep 17 00:00:00 2001 From: Olivier Gayot Date: Fri, 13 Sep 2024 12:04:27 +0200 Subject: [PATCH] storage: honor matchers in use_gap autoinstall mode When using the use_gap mode in autoinstall, one may want to ensure that we use a gap on a specific disk. Unfortunately, the match directives were silently ignored. Let's make sure they are honored. LP: #2080608 Signed-off-by: Olivier Gayot --- subiquity/server/controllers/filesystem.py | 5 +- .../controllers/tests/test_filesystem.py | 72 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/subiquity/server/controllers/filesystem.py b/subiquity/server/controllers/filesystem.py index 97fd95545..ca202d281 100644 --- a/subiquity/server/controllers/filesystem.py +++ b/subiquity/server/controllers/filesystem.py @@ -1624,8 +1624,9 @@ async def run_autoinstall_guided(self, layout): disk = self.get_bootable_matching_disk(match) target = GuidedStorageTargetReformat(disk_id=disk.id, allowed=[]) elif mode == "use_gap": - bootable = self.potential_boot_disks(with_reformatting=False) - gap = gaps.largest_gap(bootable) + match = layout.get("match", {}) + bootable_disks = self.get_bootable_matching_disks(match) + gap = gaps.largest_gap(bootable_disks) if not gap: raise Exception( "autoinstall cannot configure storage " diff --git a/subiquity/server/controllers/tests/test_filesystem.py b/subiquity/server/controllers/tests/test_filesystem.py index 5a7fa64d8..32edaa4bf 100644 --- a/subiquity/server/controllers/tests/test_filesystem.py +++ b/subiquity/server/controllers/tests/test_filesystem.py @@ -605,6 +605,78 @@ async def test__get_system_api_error_logged(self): self.assertIn("cannot load assertions for label", logs.output[0]) +class TestRunAutoinstallGuided(IsolatedAsyncioTestCase): + def setUp(self): + self.app = make_app() + self.app.opts.bootloader = None + self.fsc = FilesystemController(self.app) + self.model = self.fsc.model = make_model() + + # This is needed for examine_systems_task + self.app.base_model.source.current.type = "fsimage" + self.app.base_model.source.current.variations = { + "default": CatalogEntryVariation(path="", size=1), + } + + async def asyncSetUp(self): + self.fsc._examine_systems_task.start_sync() + + await self.fsc._examine_systems_task.wait() + + async def test_direct_use_gap__install_media(self): + """Match directives were previously not honored when using mode: use_gap. + This made it not possible for the OEM team to install to the + installation media. LP: #2080608""" + layout = { + "name": "direct", + "mode": "use_gap", + "match": { + "install-media": True, + }, + } + + # The matcher for "install-media": True looks for + # _has_in_use_partition. + iso = make_disk(self.model) + iso._has_in_use_partition = True + + make_disk(self.model) + + p_guided = mock.patch.object(self.fsc, "guided") + p_guided_choice_v2 = mock.patch( + "subiquity.server.controllers.filesystem.GuidedChoiceV2", + wraps=GuidedChoiceV2, + ) + p_largest_gap = mock.patch( + "subiquity.server.controllers.filesystem.gaps.largest_gap", + wraps=gaps.largest_gap, + ) + + with ( + p_guided as m_guided, + p_guided_choice_v2 as m_guided_choice_v2, + p_largest_gap as m_largest_gap, + ): + await self.fsc.run_autoinstall_guided(layout) + + # largest_gap will call itself recursively, so we should not expect a + # single call to it. + m_largest_gap.mock_calls[0] = mock.call([iso]) + + m_guided.assert_called_once() + m_guided_choice_v2.assert_called_once_with( + target=GuidedStorageTargetUseGap( + disk_id=iso.id, gap=gaps.largest_gap([iso]), allowed=[] + ), + capability=GuidedCapability.DIRECT, + password=mock.ANY, + recovery_key=mock.ANY, + sizing_policy=mock.ANY, + reset_partition=mock.ANY, + reset_partition_size=mock.ANY, + ) + + class TestGuided(IsolatedAsyncioTestCase): boot_expectations = [ (Bootloader.UEFI, "gpt", "/boot/efi"),