Skip to content

Commit

Permalink
oem: make sure storage is configured before using is_core_boot_classic
Browse files Browse the repository at this point in the history
Before using fs_controller.is_core_boot_classic(), we wait for the call
to /meta/confirmation?tty=xxx. That said, in semi-automated desktop
installs, sometimes the call to /meta/confirmation happens before
marking storage configured. This leads to the following error:

  File "subiquity/server/controllers/oem.py", line 209, in apply_autoinstall_config
    await self.load_metapkgs_task
  File "subiquity/server/controllers/oem.py", line 81, in list_and_mark_configured
    await self.load_metapackages_list()
  File "subiquitycore/context.py", line 149, in decorated_async
    return await meth(self, **kw)
  File "subiquity/server/controllers/oem.py", line 136, in load_metapackages_list
    if fs_controller.is_core_boot_classic():
  File "subiquity/server/controllers/filesystem.py", line 284, in is_core_boot_classic
    return self._info.is_core_boot_classic()
AttributeError: 'NoneType' object has no attribute 'is_core_boot_classic'

Receiving the confirmation before getting the storage configured is
arguably wrong - but let's be prepared for it just in case.

Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
  • Loading branch information
ogayot committed Sep 27, 2023
1 parent f951146 commit 59849f7
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 0 deletions.
10 changes: 10 additions & 0 deletions subiquity/server/controllers/oem.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def __init__(self, app) -> None:

self.load_metapkgs_task: Optional[asyncio.Task] = None
self.kernel_configured_event = asyncio.Event()
self.fs_configured_event = asyncio.Event()

def start(self) -> None:
self._wait_confirmation = asyncio.Event()
Expand All @@ -76,6 +77,9 @@ def start(self) -> None:
self.app.hub.subscribe(
(InstallerChannels.CONFIGURED, "kernel"), self.kernel_configured_event.set
)
self.app.hub.subscribe(
(InstallerChannels.CONFIGURED, "filesystem"), self.fs_configured_event.set
)

async def list_and_mark_configured() -> None:
await self.load_metapackages_list()
Expand Down Expand Up @@ -128,6 +132,12 @@ async def wants_oem_kernel(self, pkgname: str, *, context, overlay) -> bool:
async def load_metapackages_list(self, context) -> None:
with context.child("wait_confirmation"):
await self._wait_confirmation.wait()
# In normal scenarios, the confirmation event comes after the
# storage/filesystem is configured. However, in semi automated desktop
# installs (especially in CI), it is possible that the events come in
# the reverse order. Let's be prepared for it by also waiting for the
# storage configured event.
await self.fs_configured_event.wait()

# Only look for OEM meta-packages on supported variants and if we are
# not running core boot.
Expand Down
32 changes: 32 additions & 0 deletions subiquity/tests/api/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1820,6 +1820,38 @@ async def test_listing_certified_ubuntu_desktop(self):
source_id="ubuntu-desktop", expected=["oem-somerville-tentacool-meta"]
)

async def test_confirmation_before_storage_configured(self):
# On ubuntu-desktop, the confirmation event sometimes comes before the
# storage configured event. This was known to cause OEM to fail with
# the following error:
# File "server/controllers/oem.py", in load_metapackages_list
# if fs_controller.is_core_boot_classic():
# File "server/controllers/filesystem.py", in is_core_boot_classic
# return self._info.is_core_boot_classic()
# AttributeError: 'NoneType' object has no attribute
# 'is_core_boot_classic'
with patch.dict(os.environ, {"SUBIQUITY_DEBUG": "has-drivers"}):
config = "examples/machines/simple.json"
args = ["--source-catalog", "examples/sources/mixed.yaml"]
async with start_server(config, extra_args=args) as inst:
await inst.post("/source", source_id="ubuntu-desktop")
names = [
"locale",
"keyboard",
"source",
"network",
"proxy",
"mirror",
"storage",
]
await inst.post("/meta/confirm", tty="/dev/tty1")
await inst.post("/meta/mark_configured", endpoint_names=names)

resp = await inst.get("/oem", wait=True)
self.assertEqual(
["oem-somerville-tentacool-meta"], resp["metapackages"]
)


class TestSource(TestAPI):
@timeout()
Expand Down

0 comments on commit 59849f7

Please sign in to comment.