From dd7fee56d580a2fe8d0f236d534334c1db62f879 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Fri, 20 Sep 2024 17:56:27 +1200 Subject: [PATCH 1/8] allow install-sources.yaml to specify bridge kernel name, reasons --- subiquity/models/source.py | 58 ++++++++++++++++++++++---- subiquity/models/tests/test_source.py | 2 +- subiquity/server/controllers/source.py | 4 +- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/subiquity/models/source.py b/subiquity/models/source.py index 9382a2cf5..ddc9bde80 100644 --- a/subiquity/models/source.py +++ b/subiquity/models/source.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import enum import logging import os import typing @@ -56,6 +57,34 @@ def __attrs_post_init__(self): ) +# It is possible that on release day the default (i.e. generic) kernel +# will not support ZFS or NVidia drivers. In this case, if the target +# system and the user's choices combine to want to use these features, +# we install a "bridge" kernel instead (when as the generic/default +# kernel does support these features, the bridge kernel metapackage +# will be switched to referencing the default kernel metapackage). All +# the details about this are stored in the source catalog on the ISO. + + +class BridgeKernelReason(enum.Enum): + NVIDIA = "nvidia" + ZFS = "zfs" + + +@attr.s(auto_attribs=True, kw_only=True) +class KernelInfo: + default: str + bridge: typing.Optional[str] = None + bridge_reasons: typing.List[BridgeKernelReason] = attr.Factory(list) + + +@attr.s(auto_attribs=True, kw_only=True) +class SourceCatalog: + version: int + sources: typing.List[CatalogEntry] + kernel: KernelInfo + + legacy_server_entry = CatalogEntry( variant="server", id="synthesized", @@ -71,32 +100,43 @@ def __attrs_post_init__(self): }, ) +_serializer = Serializer(ignore_unknown_fields=True, serialize_enums_by="value") + class SourceModel: def __init__(self): self._dir = "/cdrom/casper" self.current = legacy_server_entry - self.sources = [self.current] + self.catalog = SourceCatalog( + version=1, + sources=[self.current], + kernel=KernelInfo(default="linux-generic"), + ) self.lang = None self.search_drivers = False def load_from_file(self, fp): self._dir = os.path.dirname(fp.name) - self.sources = [] self.current = None - self.sources = Serializer(ignore_unknown_fields=True).deserialize( - typing.List[CatalogEntry], yaml.safe_load(fp) - ) - for entry in self.sources: + content = yaml.safe_load(fp) + if isinstance(content, list): + self.catalog = SourceCatalog( + version=1, + sources=_serializer.deserialize(typing.List[CatalogEntry], content), + kernel=KernelInfo(default="linux-generic"), + ) + else: + self.catalog = _serializer.deserialize(SourceCatalog, content) + for entry in self.catalog.sources: if entry.default: self.current = entry - log.debug("loaded %d sources from %r", len(self.sources), fp.name) + log.debug("loaded %d sources from %r", len(self.catalog.sources), fp.name) if self.current is None: - self.current = self.sources[0] + self.current = self.catalog.sources[0] def get_matching_source(self, id_: str) -> CatalogEntry: """Return a source object that has the ID requested.""" - for source in self.sources: + for source in self.catalog.sources: if source.id == id_: return source raise KeyError diff --git a/subiquity/models/tests/test_source.py b/subiquity/models/tests/test_source.py index a399d7d53..5fb917d64 100644 --- a/subiquity/models/tests/test_source.py +++ b/subiquity/models/tests/test_source.py @@ -116,7 +116,7 @@ def test_canary(self): with open("examples/sources/install-canary.yaml") as fp: model = SourceModel() model.load_from_file(fp) - self.assertEqual(2, len(model.sources)) + self.assertEqual(2, len(model.catalog.sources)) minimal = model.get_matching_source("ubuntu-desktop-minimal") self.assertIsNotNone(minimal.variations) diff --git a/subiquity/server/controllers/source.py b/subiquity/server/controllers/source.py index 526ecc5b9..ee733c2ee 100644 --- a/subiquity/server/controllers/source.py +++ b/subiquity/server/controllers/source.py @@ -114,6 +114,8 @@ def start(self): self.app.hub.subscribe( (InstallerChannels.CONFIGURED, "locale"), self._set_locale ) + if self.model.catalog.version != 1: + raise Exception("unknown source catalog version") def _set_locale(self): current = self.app.base_model.locale.selected_language @@ -128,7 +130,7 @@ async def GET(self) -> SourceSelectionAndSetting: search_drivers = True return SourceSelectionAndSetting( - [convert_source(source, cur_lang) for source in self.model.sources], + [convert_source(source, cur_lang) for source in self.model.catalog.sources], self.model.current.id, search_drivers=search_drivers, ) From 3c1e8a5b3ad428e03d1aae136629c376ffa1cdd7 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 23 Sep 2024 09:56:55 +1200 Subject: [PATCH 2/8] read default kernel from source catalog if not otherwise set --- subiquity/server/controllers/kernel.py | 11 +++++++++-- .../server/controllers/tests/test_kernel.py | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/subiquity/server/controllers/kernel.py b/subiquity/server/controllers/kernel.py index a4c4100c1..958f660a7 100644 --- a/subiquity/server/controllers/kernel.py +++ b/subiquity/server/controllers/kernel.py @@ -18,6 +18,7 @@ from subiquity.server.controller import NonInteractiveController from subiquity.server.kernel import flavor_to_pkgname +from subiquity.server.types import InstallerChannels log = logging.getLogger("subiquity.server.controllers.kernel") @@ -65,8 +66,14 @@ def start(self): log.debug(f"Using kernel {kernel_package} due to {mp_file}") break else: - log.debug("Using default kernel linux-generic") - self.model.metapkg_name = "linux-generic" + # no default kernel found in etc or run, use default from + # source catalog. + self.app.hub.subscribe( + (InstallerChannels.CONFIGURED, "source"), self._set_source + ) + + async def _set_source(self): + self.model.metapkg_name = self.app.base_model.source.catalog.kernel.default def load_autoinstall_data(self, data): if data is None: diff --git a/subiquity/server/controllers/tests/test_kernel.py b/subiquity/server/controllers/tests/test_kernel.py index da61f5424..366ed31de 100644 --- a/subiquity/server/controllers/tests/test_kernel.py +++ b/subiquity/server/controllers/tests/test_kernel.py @@ -18,6 +18,7 @@ from subiquity.models.kernel import KernelModel from subiquity.server.controllers.kernel import KernelController +from subiquity.server.types import InstallerChannels from subiquitycore.tests import SubiTestCase from subiquitycore.tests.mocks import make_app from subiquitycore.tests.parameterized import parameterized @@ -40,13 +41,26 @@ def setup_mpfile(self, dirpath, data): def test_defaults(self): self.controller.start() - self.assertEqual("linux-generic", self.controller.model.metapkg_name) + self.assertEqual(None, self.controller.model.metapkg_name) + + async def test_defaults_from_source(self): + self.app.base_model.source.catalog.kernel.default = "default-kernel" + self.controller.start() + await self.app.hub.abroadcast((InstallerChannels.CONFIGURED, "source")) + self.assertEqual("default-kernel", self.controller.model.metapkg_name) def test_mpfile_run(self): self.setup_mpfile("run", "linux-aaaa") self.controller.start() self.assertEqual("linux-aaaa", self.controller.model.metapkg_name) + async def test_mpfile_run_overrides_source(self): + self.app.base_model.source.catalog.kernel.default = "default-kernel" + self.setup_mpfile("run", "linux-aaaa") + self.controller.start() + await self.app.hub.abroadcast((InstallerChannels.CONFIGURED, "source")) + self.assertEqual("linux-aaaa", self.controller.model.metapkg_name) + def test_mpfile_etc(self): self.setup_mpfile("etc/subiquity", "linux-zzzz") self.controller.start() @@ -60,7 +74,7 @@ def test_mpfile_both(self): @parameterized.expand( [ - [None, None, "linux-generic"], + [None, None, None], [None, {}, "linux-generic"], # when the metapackage file is set, it should be used. ["linux-zzzz", None, "linux-zzzz"], From 9117f1743933422e3b7d050fe96af7c0e104c16a Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 23 Sep 2024 16:20:43 +1200 Subject: [PATCH 3/8] send DRIVERS_DECIDED when it is known which drivers will be installed --- subiquity/server/controllers/drivers.py | 17 +++++++++++++++++ subiquity/server/types.py | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/subiquity/server/controllers/drivers.py b/subiquity/server/controllers/drivers.py index 333102ba7..4dc5bc55d 100644 --- a/subiquity/server/controllers/drivers.py +++ b/subiquity/server/controllers/drivers.py @@ -53,6 +53,7 @@ def __init__(self, app) -> None: self._list_drivers_task: Optional[asyncio.Task] = None self.list_drivers_done_event = asyncio.Event() + self.configured_event = asyncio.Event() # None means that the list has not (yet) been retrieved whereas an # empty list means that no drivers are available. @@ -73,6 +74,13 @@ def start(self): self.app.hub.subscribe( (InstallerChannels.CONFIGURED, "source"), self.restart_querying_drivers_list ) + self.app.hub.subscribe( + (InstallerChannels.CONFIGURED, "drivers"), + self.configured_event.set, + ) + self._send_drivers_decided_task = asyncio.create_task( + self._send_drivers_decided() + ) def restart_querying_drivers_list(self): """Start querying the list of available drivers. This method can be @@ -118,6 +126,15 @@ async def _list_drivers(self, context): self.list_drivers_done_event.set() log.debug("Available drivers to install: %s", self.drivers) + async def _send_drivers_decided(self): + await asyncio.wait( + { + asyncio.create_task(self.list_drivers_done_event.wait()), + asyncio.create_task(self.configured_event.wait()), + } + ) + await self.app.hub.abroadcast(InstallerChannels.DRIVERS_DECIDED) + async def GET(self, wait: bool = False) -> DriversResponse: local_only = not self.app.base_model.network.has_network if wait: diff --git a/subiquity/server/types.py b/subiquity/server/types.py index 28426ec08..c6be20acf 100644 --- a/subiquity/server/types.py +++ b/subiquity/server/types.py @@ -47,3 +47,11 @@ class InstallerChannels(CoreChannels): # step is after logfiles have been copied to the system, so should be used # sparingly and only as absolutely required. PRE_SHUTDOWN = "pre-shutdown" + # This message is sent when it has been decided: + # a. whether drivers should be searched for or not + # b. which drivers ubuntu-drivers recommends for the system + # c. if the user has elected to install these drivers + # Once this has been sent, it is safe to inspect + # app.controllers.Drivers.drivers to see which drivers will be + # installed. + DRIVERS_DECIDED = "drivers-decided" From 6b1f63ba9b19527c8f9595da0a8c008fc012385c Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 23 Sep 2024 12:54:04 +1200 Subject: [PATCH 4/8] logic to decide when to use bridge kernel complete with quite complicated tests --- subiquity/models/filesystem.py | 5 +- subiquity/server/controllers/kernel.py | 41 +++++++++ .../server/controllers/tests/test_kernel.py | 92 +++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) diff --git a/subiquity/models/filesystem.py b/subiquity/models/filesystem.py index fc4e28157..9e953f053 100644 --- a/subiquity/models/filesystem.py +++ b/subiquity/models/filesystem.py @@ -2431,10 +2431,13 @@ def add_zpool( self._actions.append(zpool) return zpool + def uses_zfs(self): + return self._one(type="zpool") is not None + async def live_packages(self) -> Tuple[Set, Set]: before = set() during = set() - if self._one(type="zpool") is not None: + if self.uses_zfs(): before.add("zfsutils-linux") if self.reset_partition is not None: during.add("efibootmgr") diff --git a/subiquity/server/controllers/kernel.py b/subiquity/server/controllers/kernel.py index 958f660a7..662d7deae 100644 --- a/subiquity/server/controllers/kernel.py +++ b/subiquity/server/controllers/kernel.py @@ -16,6 +16,7 @@ import logging import os +from subiquity.models.source import BridgeKernelReason from subiquity.server.controller import NonInteractiveController from subiquity.server.kernel import flavor_to_pkgname from subiquity.server.types import InstallerChannels @@ -60,6 +61,7 @@ def start(self): with open(mp_file) as fp: kernel_package = fp.read().strip() self.model.metapkg_name = kernel_package + self.default_metapkg_name = self.model.metapkg_name # built-in kernel requirements are not considered # explicitly_requested self.model.explicitly_requested = False @@ -71,9 +73,48 @@ def start(self): self.app.hub.subscribe( (InstallerChannels.CONFIGURED, "source"), self._set_source ) + self.needs_bridge = {} + self.app.hub.subscribe( + InstallerChannels.INSTALL_CONFIRMED, + self._confirmed, + ) + self.app.hub.subscribe( + InstallerChannels.DRIVERS_DECIDED, + self._drivers_decided, + ) async def _set_source(self): self.model.metapkg_name = self.app.base_model.source.catalog.kernel.default + self.default_metapkg_name = self.model.metapkg_name + + def _maybe_set_bridge_kernel(self, reason, value): + if reason in self.needs_bridge: + return + reasons = self.app.base_model.source.catalog.kernel.bridge_reasons + if reason not in reasons: + value = False + self.needs_bridge[reason] = value + if len(self.needs_bridge) < len(BridgeKernelReason): + return + log.debug("bridge kernel decided %s", self.needs_bridge) + if any(self.needs_bridge.values()): + self.model.metapkg_name = self.app.base_model.source.catalog.kernel.bridge + else: + self.model.metapkg_name = self.default_metapkg_name + + def _confirmed(self): + fs_model = self.app.base_model.filesystem + self._maybe_set_bridge_kernel(BridgeKernelReason.ZFS, fs_model.uses_zfs()) + if not self.app.base_model.source.search_drivers: + self._maybe_set_bridge_kernel(BridgeKernelReason.NVIDIA, False) + + def _drivers_decided(self): + drivers_controller = self.app.controllers.Drivers + self._maybe_set_bridge_kernel( + BridgeKernelReason.NVIDIA, + drivers_controller.model.do_install + and any("nvidia" in driver for driver in drivers_controller.drivers), + ) def load_autoinstall_data(self, data): if data is None: diff --git a/subiquity/server/controllers/tests/test_kernel.py b/subiquity/server/controllers/tests/test_kernel.py index 366ed31de..9d1c8e8e7 100644 --- a/subiquity/server/controllers/tests/test_kernel.py +++ b/subiquity/server/controllers/tests/test_kernel.py @@ -13,10 +13,14 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import itertools import os import os.path +import attr + from subiquity.models.kernel import KernelModel +from subiquity.models.source import BridgeKernelReason, SourceModel from subiquity.server.controllers.kernel import KernelController from subiquity.server.types import InstallerChannels from subiquitycore.tests import SubiTestCase @@ -24,6 +28,57 @@ from subiquitycore.tests.parameterized import parameterized +@attr.s(auto_attribs=True) +class BridgeTestScenario: + bridge_reasons: list[BridgeKernelReason] + detected_reasons: list[BridgeKernelReason] + search_drivers: bool + drivers_do_install: bool + use_bridge: bool + + +def n_booleans(*, n): + return itertools.product(*(((True, False),) * n)) + + +bridge_scenarios = [] +for bridge_zfs, bridge_nvidia, has_zfs, has_nvidia in n_booleans(n=4): + # We consider scenarios for each reason being a reason to choose + # the kernel and actually discovered. + bridge_reasons = [] + if bridge_zfs: + bridge_reasons.append(BridgeKernelReason.ZFS) + if bridge_nvidia: + bridge_reasons.append(BridgeKernelReason.NVIDIA) + detected_reasons = [] + if has_zfs: + detected_reasons.append(BridgeKernelReason.ZFS) + if has_nvidia: + detected_reasons.append(BridgeKernelReason.NVIDIA) + for search_drivers, do_install in n_booleans(n=2): + # Then we consider scenarios where the user chooses whether or + # not to search for drivers and whether or not to install the + # drivers + if not search_drivers and do_install: + continue + use_bridge = False + if bridge_zfs and has_zfs: + use_bridge = True + if bridge_nvidia and has_nvidia and do_install: + use_bridge = True + bridge_scenarios.append( + ( + BridgeTestScenario( + bridge_reasons=bridge_reasons, + detected_reasons=detected_reasons, + search_drivers=search_drivers, + drivers_do_install=do_install, + use_bridge=use_bridge, + ), + ) + ) + + class TestMetapackageSelection(SubiTestCase): def setUp(self): self.app = make_app() @@ -94,3 +149,40 @@ def test_ai(self, mpfile_data, ai_data, metapkg_name): self.controller.load_autoinstall_data(ai_data) self.controller.start() self.assertEqual(metapkg_name, self.controller.model.metapkg_name) + + @parameterized.expand(bridge_scenarios) + async def test_bridge_options(self, scenario): + # This test "knows" a bit too much about which conditions the + # bridge kernel code uses to check for the various + # reasons. But its better than no tests. + source_model = self.app.base_model.source = SourceModel() + source_model.catalog.kernel.default = "linux-default" + source_model.catalog.kernel.bridge = "linux-bridge" + source_model.catalog.kernel.bridge_reasons = scenario.bridge_reasons + source_model.search_drivers = scenario.search_drivers + + self.controller.start() + + await self.app.hub.abroadcast((InstallerChannels.CONFIGURED, "source")) + + if BridgeKernelReason.ZFS in scenario.detected_reasons: + self.app.base_model.filesystem.uses_zfs.return_value = True + else: + self.app.base_model.filesystem.uses_zfs.return_value = False + + await self.app.hub.abroadcast(InstallerChannels.INSTALL_CONFIRMED) + + if not scenario.search_drivers: + drivers = [] + elif BridgeKernelReason.NVIDIA in scenario.detected_reasons: + drivers = ["something-else", "nvidia-driver"] + else: + drivers = ["something-else"] + self.app.controllers.Drivers.model.do_install = scenario.drivers_do_install + self.app.controllers.Drivers.drivers = drivers + await self.app.hub.abroadcast(InstallerChannels.DRIVERS_DECIDED) + + if scenario.use_bridge: + self.assertEqual("linux-bridge", self.controller.model.metapkg_name) + else: + self.assertEqual("linux-default", self.controller.model.metapkg_name) From 93e106eb16ec4fd7f6a183160c83247e27d4594e Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Fri, 20 Sep 2024 17:59:54 +1200 Subject: [PATCH 5/8] send event when bridge kernel is decided, wait for it before curthooks --- subiquity/server/controllers/install.py | 6 ++++++ subiquity/server/controllers/kernel.py | 1 + subiquity/server/types.py | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/subiquity/server/controllers/install.py b/subiquity/server/controllers/install.py index bc9cf581c..426832b1b 100644 --- a/subiquity/server/controllers/install.py +++ b/subiquity/server/controllers/install.py @@ -82,6 +82,10 @@ class InstallController(SubiquityController): def __init__(self, app): super().__init__(app) self.model = app.base_model + self.bridge_kernel_decided = asyncio.Event() + self.app.hub.subscribe( + InstallerChannels.BRIDGE_KERNEL_DECIDED, self.bridge_kernel_decided.set + ) self.tb_extractor = TracebackExtractor() @@ -446,6 +450,8 @@ async def run_curtin_step(name, stages, step_config, source=None): if self.supports_apt(): await self.pre_curthooks_oem_configuration(context=context) + await self.bridge_kernel_decided.wait() + await run_curtin_step( name="curthooks", stages=["curthooks"], diff --git a/subiquity/server/controllers/kernel.py b/subiquity/server/controllers/kernel.py index 662d7deae..8d9a236ee 100644 --- a/subiquity/server/controllers/kernel.py +++ b/subiquity/server/controllers/kernel.py @@ -101,6 +101,7 @@ def _maybe_set_bridge_kernel(self, reason, value): self.model.metapkg_name = self.app.base_model.source.catalog.kernel.bridge else: self.model.metapkg_name = self.default_metapkg_name + self.app.hub.broadcast(InstallerChannels.BRIDGE_KERNEL_DECIDED) def _confirmed(self): fs_model = self.app.base_model.filesystem diff --git a/subiquity/server/types.py b/subiquity/server/types.py index c6be20acf..a2a9fa7ec 100644 --- a/subiquity/server/types.py +++ b/subiquity/server/types.py @@ -55,3 +55,7 @@ class InstallerChannels(CoreChannels): # app.controllers.Drivers.drivers to see which drivers will be # installed. DRIVERS_DECIDED = "drivers-decided" + # This message is sent when we know whether or not we need to + # install a "bridge kernel". After this is sent we really do + # finally know which kernel we will be installing. + BRIDGE_KERNEL_DECIDED = "bridge-kernel-decided" From ee1f90a10df8e55b566825c6ef845df6972f5e92 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Fri, 20 Sep 2024 18:01:01 +1200 Subject: [PATCH 6/8] add example bridge sources and integration test --- examples/answers/bridge.yaml | 44 ++++++++++++++++++++++++++++++++++++ examples/sources/bridge.yaml | 30 ++++++++++++++++++++++++ scripts/runtests.sh | 4 ++++ 3 files changed, 78 insertions(+) create mode 100644 examples/answers/bridge.yaml create mode 100644 examples/sources/bridge.yaml diff --git a/examples/answers/bridge.yaml b/examples/answers/bridge.yaml new file mode 100644 index 000000000..5c0b4d7d7 --- /dev/null +++ b/examples/answers/bridge.yaml @@ -0,0 +1,44 @@ +#source-catalog: examples/sources/bridge.yaml +Source: + source: ubuntu-server + search_drivers: true +Welcome: + lang: en_US +Refresh: + update: no +Keyboard: + layout: us +Zdev: + accept-default: yes +Network: + accept-default: yes +Proxy: + proxy: "" +Mirror: + country-code: us +Filesystem: + guided: yes + guided-index: 0 +Identity: + realname: Ubuntu + username: ubuntu + hostname: ubuntu-server + # ubuntu + password: '$6$wdAcoXrU039hKYPd$508Qvbe7ObUnxoj15DRCkzC3qO7edjH0VV7BPNRDYK4QR8ofJaEEF2heacn0QgD.f8pO8SNp83XNdWG6tocBM1' +SSH: + install_server: true + pwauth: false + authorized_keys: + - | + ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAA # ssh-import-id lp:subiquity +UbuntuPro: + token: "" +SnapList: + snaps: + hello: + channel: stable + classic: false +InstallProgress: + reboot: yes +Drivers: + install: yes diff --git a/examples/sources/bridge.yaml b/examples/sources/bridge.yaml new file mode 100644 index 000000000..61ead51e7 --- /dev/null +++ b/examples/sources/bridge.yaml @@ -0,0 +1,30 @@ +version: 1 +sources: + - description: + en: This version has been customized to have a small runtime footprint in environments + where humans are not expected to log in. + id: ubuntu-server-minimal + locale_support: none + name: + en: Ubuntu Server (minimized) + path: ubuntu-server-minimal.squashfs + size: 530485248 + type: fsimage + variant: server + - default: true + description: + en: The default install contains a curated set of packages that provide a comfortable + experience for operating your server. + id: ubuntu-server + locale_support: locale-only + name: + en: Ubuntu Server + path: ubuntu-server-minimal.ubuntu-server.squashfs + size: 1066115072 + type: fsimage-layered + variant: server +kernel: + default: linux-generic + bridge: linux-generic-brg-22.04 + bridge_reasons: + - nvidia diff --git a/scripts/runtests.sh b/scripts/runtests.sh index da282be31..cffd5a966 100755 --- a/scripts/runtests.sh +++ b/scripts/runtests.sh @@ -48,6 +48,10 @@ validate () { case $testname in answers-core-desktop|answers-uc24) ;; + answers-bridge) + python3 scripts/check-yaml-fields.py $tmpdir/var/log/installer/curtin-install/subiquity-curthooks.conf \ + kernel.package="linux-generic-brg-22.04" + ;; *) python3 scripts/validate-autoinstall-user-data.py --legacy --check-link < $tmpdir/var/log/installer/autoinstall-user-data # After the lunar release and the introduction of mirror testing, it From 86e075f43410778c0810b21171be495ff1e08884 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Fri, 4 Oct 2024 12:17:37 +1300 Subject: [PATCH 7/8] if there are drivers available, do not send DRIVERS_DECIDED until postinstall is done --- subiquity/server/controllers/drivers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/subiquity/server/controllers/drivers.py b/subiquity/server/controllers/drivers.py index 4dc5bc55d..98d7c5db3 100644 --- a/subiquity/server/controllers/drivers.py +++ b/subiquity/server/controllers/drivers.py @@ -127,12 +127,12 @@ async def _list_drivers(self, context): log.debug("Available drivers to install: %s", self.drivers) async def _send_drivers_decided(self): - await asyncio.wait( - { - asyncio.create_task(self.list_drivers_done_event.wait()), - asyncio.create_task(self.configured_event.wait()), - } - ) + await self.list_drivers_done_event.wait() + if self.drivers: + # If there are drivers, we need to wait until all + # postinstall models are configured before we can be sure + # if the user will change their mind or not. + await self.app.base_model.wait_postinstall() await self.app.hub.abroadcast(InstallerChannels.DRIVERS_DECIDED) async def GET(self, wait: bool = False) -> DriversResponse: From fb9d1b8755531d74278fe49d84b02303f414dd23 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Fri, 4 Oct 2024 12:19:40 +1300 Subject: [PATCH 8/8] broadcast BRIDGE_KERNEL_DECIDED earlier if there is no bridge kernel --- subiquity/server/controllers/kernel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/subiquity/server/controllers/kernel.py b/subiquity/server/controllers/kernel.py index 8d9a236ee..9bf9eb61c 100644 --- a/subiquity/server/controllers/kernel.py +++ b/subiquity/server/controllers/kernel.py @@ -105,6 +105,8 @@ def _maybe_set_bridge_kernel(self, reason, value): def _confirmed(self): fs_model = self.app.base_model.filesystem + if not self.app.base_model.source.catalog.kernel.bridge_reasons: + self.app.hub.broadcast(InstallerChannels.BRIDGE_KERNEL_DECIDED) self._maybe_set_bridge_kernel(BridgeKernelReason.ZFS, fs_model.uses_zfs()) if not self.app.base_model.source.search_drivers: self._maybe_set_bridge_kernel(BridgeKernelReason.NVIDIA, False)