From 0e0fadd72da086f5f06f7baebfbf6b0d5c42c109 Mon Sep 17 00:00:00 2001 From: Mike Degatano Date: Mon, 18 Mar 2024 13:30:10 -0400 Subject: [PATCH] Fix some expected boot slot fields are optional (#4964) * Fix some expected boot slot fields are optional * Move stuff around to make pylint happy --- supervisor/dbus/rauc.py | 18 +-- supervisor/os/manager.py | 65 ++++---- tests/dbus_service_mocks/rauc.py | 255 ++++++++++++++++--------------- tests/os/test_manager.py | 121 +++++++++++++++ 4 files changed, 297 insertions(+), 162 deletions(-) diff --git a/supervisor/dbus/rauc.py b/supervisor/dbus/rauc.py index 94602cb39..ab90ef0b7 100644 --- a/supervisor/dbus/rauc.py +++ b/supervisor/dbus/rauc.py @@ -28,17 +28,17 @@ _LOGGER: logging.Logger = logging.getLogger(__name__) SlotStatusDataType = TypedDict( "SlotStatusDataType", { - "bundle.compatible": str, - "sha256": str, - "state": str, - "size": c_uint64, - "installed.count": c_uint32, "class": str, - "device": str, "type": str, - "bundle.version": str, - "installed.timestamp": str, - "status": str, + "state": str, + "device": str, + "bundle.compatible": NotRequired[str], + "sha256": NotRequired[str], + "size": NotRequired[c_uint64], + "installed.count": NotRequired[c_uint32], + "bundle.version": NotRequired[str], + "installed.timestamp": NotRequired[str], + "status": NotRequired[str], "activated.count": NotRequired[c_uint32], "activated.timestamp": NotRequired[str], "boot-status": NotRequired[str], diff --git a/supervisor/os/manager.py b/supervisor/os/manager.py index cf7567cef..141eb508a 100644 --- a/supervisor/os/manager.py +++ b/supervisor/os/manager.py @@ -33,17 +33,17 @@ _LOGGER: logging.Logger = logging.getLogger(__name__) class SlotStatus: """Status of a slot.""" - bundle_compatible: str - sha256: str - state: str - size: int - installed_count: int class_: str - device: PurePath type_: str - bundle_version: AwesomeVersion - installed_timestamp: datetime - status: str + state: str + device: PurePath + bundle_compatible: str | None = None + sha256: str | None = None + size: int | None = None + installed_count: int | None = None + bundle_version: AwesomeVersion | None = None + installed_timestamp: datetime | None = None + status: str | None = None activated_count: int | None = None activated_timestamp: datetime | None = None boot_status: RaucState | None = None @@ -54,17 +54,21 @@ class SlotStatus: def from_dict(cls, data: SlotStatusDataType) -> "SlotStatus": """Create SlotStatus from dictionary.""" return cls( - bundle_compatible=data["bundle.compatible"], - sha256=data["sha256"], - state=data["state"], - size=data["size"], - installed_count=data["installed.count"], class_=data["class"], - device=PurePath(data["device"]), type_=data["type"], - bundle_version=AwesomeVersion(data["bundle.version"]), - installed_timestamp=datetime.fromisoformat(data["installed.timestamp"]), - status=data["status"], + state=data["state"], + device=PurePath(data["device"]), + bundle_compatible=data.get("bundle.compatible"), + sha256=data.get("sha256"), + size=data.get("size"), + installed_count=data.get("installed.count"), + bundle_version=AwesomeVersion(data["bundle.version"]) + if "bundle.version" in data + else None, + installed_timestamp=datetime.fromisoformat(data["installed.timestamp"]) + if "installed.timestamp" in data + else None, + status=data.get("status"), activated_count=data.get("activated.count"), activated_timestamp=datetime.fromisoformat(data["activated.timestamp"]) if "activated.timestamp" in data @@ -77,19 +81,26 @@ class SlotStatus: def to_dict(self) -> SlotStatusDataType: """Get dictionary representation.""" out: SlotStatusDataType = { - "bundle.compatible": self.bundle_compatible, - "sha256": self.sha256, - "state": self.state, - "size": self.size, - "installed.count": self.installed_count, "class": self.class_, - "device": self.device.as_posix(), "type": self.type_, - "bundle.version": str(self.bundle_version), - "installed.timestamp": str(self.installed_timestamp), - "status": self.status, + "state": self.state, + "device": self.device.as_posix(), } + if self.bundle_compatible is not None: + out["bundle.compatible"] = self.bundle_compatible + if self.sha256 is not None: + out["sha256"] = self.sha256 + if self.size is not None: + out["size"] = self.size + if self.installed_count is not None: + out["installed.count"] = self.installed_count + if self.bundle_version is not None: + out["bundle.version"] = str(self.bundle_version) + if self.installed_timestamp is not None: + out["installed.timestamp"] = str(self.installed_timestamp) + if self.status is not None: + out["status"] = self.status if self.activated_count is not None: out["activated.count"] = self.activated_count if self.activated_timestamp: diff --git a/tests/dbus_service_mocks/rauc.py b/tests/dbus_service_mocks/rauc.py index 2266ec2e0..ba17c1fbf 100644 --- a/tests/dbus_service_mocks/rauc.py +++ b/tests/dbus_service_mocks/rauc.py @@ -7,6 +7,133 @@ from .base import DBusServiceMock, dbus_method BUS_NAME = "de.pengutronix.rauc" +SLOT_STATUS_FIXTURE: list[tuple[str | dict[str, Variant]]] = [ + ( + "kernel.0", + { + "activated.count": Variant("u", 9), + "activated.timestamp": Variant("s", "2022-08-23T21:03:22Z"), + "boot-status": Variant("s", "good"), + "bundle.compatible": Variant("s", "haos-odroid-n2"), + "sha256": Variant( + "s", + "c624db648b8401fae37ee5bb1a6ec90bdf4183aef364b33314a73c7198e49d5b", + ), + "state": Variant("s", "inactive"), + "size": Variant("t", 10371072), + "installed.count": Variant("u", 9), + "class": Variant("s", "kernel"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-kernel0"), + "type": Variant("s", "raw"), + "bootname": Variant("s", "A"), + "bundle.version": Variant("s", "9.0.dev20220818"), + "installed.timestamp": Variant("s", "2022-08-23T21:03:16Z"), + "status": Variant("s", "ok"), + }, + ), + ( + "boot.0", + { + "bundle.compatible": Variant("s", "haos-odroid-n2"), + "sha256": Variant( + "s", + "a5019b335f33be2cf89c96bb2d0695030adb72c1d13d650a5bbe1806dd76d6cc", + ), + "state": Variant("s", "inactive"), + "size": Variant("t", 25165824), + "installed.count": Variant("u", 19), + "class": Variant("s", "boot"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-boot"), + "type": Variant("s", "vfat"), + "status": Variant("s", "ok"), + "bundle.version": Variant("s", "9.0.dev20220824"), + "installed.timestamp": Variant("s", "2022-08-25T21:11:46Z"), + }, + ), + ( + "rootfs.0", + { + "bundle.compatible": Variant("s", "haos-odroid-n2"), + "parent": Variant("s", "kernel.0"), + "state": Variant("s", "inactive"), + "size": Variant("t", 117456896), + "sha256": Variant( + "s", + "7d908b4d578d072b1b0f75de8250fd97b6e119bff09518a96fffd6e4aec61721", + ), + "class": Variant("s", "rootfs"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-system0"), + "type": Variant("s", "raw"), + "status": Variant("s", "ok"), + "bundle.version": Variant("s", "9.0.dev20220818"), + "installed.timestamp": Variant("s", "2022-08-23T21:03:21Z"), + "installed.count": Variant("u", 9), + }, + ), + ( + "spl.0", + { + "bundle.compatible": Variant("s", "haos-odroid-n2"), + "sha256": Variant( + "s", + "9856a94df1d6abbc672adaf95746ec76abd3a8191f9d08288add6bb39e63ef45", + ), + "state": Variant("s", "inactive"), + "size": Variant("t", 8388608), + "installed.count": Variant("u", 19), + "class": Variant("s", "spl"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-boot"), + "type": Variant("s", "raw"), + "status": Variant("s", "ok"), + "bundle.version": Variant("s", "9.0.dev20220824"), + "installed.timestamp": Variant("s", "2022-08-25T21:11:51Z"), + }, + ), + ( + "kernel.1", + { + "activated.count": Variant("u", 10), + "activated.timestamp": Variant("s", "2022-08-25T21:11:52Z"), + "boot-status": Variant("s", "good"), + "bundle.compatible": Variant("s", "haos-odroid-n2"), + "sha256": Variant( + "s", + "f57e354b8bd518022721e71fafaf278972af966d8f6cbefb4610db13785801c8", + ), + "state": Variant("s", "booted"), + "size": Variant("t", 10371072), + "installed.count": Variant("u", 10), + "class": Variant("s", "kernel"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-kernel1"), + "type": Variant("s", "raw"), + "bootname": Variant("s", "B"), + "bundle.version": Variant("s", "9.0.dev20220824"), + "installed.timestamp": Variant("s", "2022-08-25T21:11:46Z"), + "status": Variant("s", "ok"), + }, + ), + ( + "rootfs.1", + { + "bundle.compatible": Variant("s", "haos-odroid-n2"), + "parent": Variant("s", "kernel.1"), + "state": Variant("s", "active"), + "size": Variant("t", 117456896), + "sha256": Variant( + "s", + "55936b64d391954ae1aed24dd1460e191e021e78655470051fa7939d12fff68a", + ), + "class": Variant("s", "rootfs"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-system1"), + "type": Variant("s", "raw"), + "status": Variant("s", "ok"), + "bundle.version": Variant("s", "9.0.dev20220824"), + "installed.timestamp": Variant("s", "2022-08-25T21:11:51Z"), + "installed.count": Variant("u", 10), + }, + ), +] + def setup(object_path: str | None = None) -> DBusServiceMock: """Create dbus mock object.""" @@ -22,6 +149,7 @@ class Rauc(DBusServiceMock): object_path = "/" interface = "de.pengutronix.rauc.Installer" response_mark: list[str] | DBusError = ["kernel.1", "marked slot kernel.1 as good"] + response_get_slot_status = SLOT_STATUS_FIXTURE @dbus_property(access=PropertyAccess.READ) def Operation(self) -> "s": @@ -81,129 +209,4 @@ class Rauc(DBusServiceMock): @dbus_method() def GetSlotStatus(self) -> "a(sa{sv})": """Get slot status.""" - return [ - ( - "kernel.0", - { - "activated.count": Variant("u", 9), - "activated.timestamp": Variant("s", "2022-08-23T21:03:22Z"), - "boot-status": Variant("s", "good"), - "bundle.compatible": Variant("s", "haos-odroid-n2"), - "sha256": Variant( - "s", - "c624db648b8401fae37ee5bb1a6ec90bdf4183aef364b33314a73c7198e49d5b", - ), - "state": Variant("s", "inactive"), - "size": Variant("t", 10371072), - "installed.count": Variant("u", 9), - "class": Variant("s", "kernel"), - "device": Variant("s", "/dev/disk/by-partlabel/hassos-kernel0"), - "type": Variant("s", "raw"), - "bootname": Variant("s", "A"), - "bundle.version": Variant("s", "9.0.dev20220818"), - "installed.timestamp": Variant("s", "2022-08-23T21:03:16Z"), - "status": Variant("s", "ok"), - }, - ), - ( - "boot.0", - { - "bundle.compatible": Variant("s", "haos-odroid-n2"), - "sha256": Variant( - "s", - "a5019b335f33be2cf89c96bb2d0695030adb72c1d13d650a5bbe1806dd76d6cc", - ), - "state": Variant("s", "inactive"), - "size": Variant("t", 25165824), - "installed.count": Variant("u", 19), - "class": Variant("s", "boot"), - "device": Variant("s", "/dev/disk/by-partlabel/hassos-boot"), - "type": Variant("s", "vfat"), - "status": Variant("s", "ok"), - "bundle.version": Variant("s", "9.0.dev20220824"), - "installed.timestamp": Variant("s", "2022-08-25T21:11:46Z"), - }, - ), - ( - "rootfs.0", - { - "bundle.compatible": Variant("s", "haos-odroid-n2"), - "parent": Variant("s", "kernel.0"), - "state": Variant("s", "inactive"), - "size": Variant("t", 117456896), - "sha256": Variant( - "s", - "7d908b4d578d072b1b0f75de8250fd97b6e119bff09518a96fffd6e4aec61721", - ), - "class": Variant("s", "rootfs"), - "device": Variant("s", "/dev/disk/by-partlabel/hassos-system0"), - "type": Variant("s", "raw"), - "status": Variant("s", "ok"), - "bundle.version": Variant("s", "9.0.dev20220818"), - "installed.timestamp": Variant("s", "2022-08-23T21:03:21Z"), - "installed.count": Variant("u", 9), - }, - ), - ( - "spl.0", - { - "bundle.compatible": Variant("s", "haos-odroid-n2"), - "sha256": Variant( - "s", - "9856a94df1d6abbc672adaf95746ec76abd3a8191f9d08288add6bb39e63ef45", - ), - "state": Variant("s", "inactive"), - "size": Variant("t", 8388608), - "installed.count": Variant("u", 19), - "class": Variant("s", "spl"), - "device": Variant("s", "/dev/disk/by-partlabel/hassos-boot"), - "type": Variant("s", "raw"), - "status": Variant("s", "ok"), - "bundle.version": Variant("s", "9.0.dev20220824"), - "installed.timestamp": Variant("s", "2022-08-25T21:11:51Z"), - }, - ), - ( - "kernel.1", - { - "activated.count": Variant("u", 10), - "activated.timestamp": Variant("s", "2022-08-25T21:11:52Z"), - "boot-status": Variant("s", "good"), - "bundle.compatible": Variant("s", "haos-odroid-n2"), - "sha256": Variant( - "s", - "f57e354b8bd518022721e71fafaf278972af966d8f6cbefb4610db13785801c8", - ), - "state": Variant("s", "booted"), - "size": Variant("t", 10371072), - "installed.count": Variant("u", 10), - "class": Variant("s", "kernel"), - "device": Variant("s", "/dev/disk/by-partlabel/hassos-kernel1"), - "type": Variant("s", "raw"), - "bootname": Variant("s", "B"), - "bundle.version": Variant("s", "9.0.dev20220824"), - "installed.timestamp": Variant("s", "2022-08-25T21:11:46Z"), - "status": Variant("s", "ok"), - }, - ), - ( - "rootfs.1", - { - "bundle.compatible": Variant("s", "haos-odroid-n2"), - "parent": Variant("s", "kernel.1"), - "state": Variant("s", "active"), - "size": Variant("t", 117456896), - "sha256": Variant( - "s", - "55936b64d391954ae1aed24dd1460e191e021e78655470051fa7939d12fff68a", - ), - "class": Variant("s", "rootfs"), - "device": Variant("s", "/dev/disk/by-partlabel/hassos-system1"), - "type": Variant("s", "raw"), - "status": Variant("s", "ok"), - "bundle.version": Variant("s", "9.0.dev20220824"), - "installed.timestamp": Variant("s", "2022-08-25T21:11:51Z"), - "installed.count": Variant("u", 10), - }, - ), - ] + return self.response_get_slot_status diff --git a/tests/os/test_manager.py b/tests/os/test_manager.py index 8da4a148a..4c5c06753 100644 --- a/tests/os/test_manager.py +++ b/tests/os/test_manager.py @@ -3,12 +3,16 @@ from unittest.mock import PropertyMock, patch from awesomeversion import AwesomeVersion +from dbus_fast import Variant import pytest from supervisor.const import CoreState from supervisor.coresys import CoreSys from supervisor.exceptions import HassOSJobError +from tests.dbus_service_mocks.base import DBusServiceMock +from tests.dbus_service_mocks.rauc import Rauc as RaucService + # pylint: disable=protected-access @@ -78,3 +82,120 @@ async def test_board_name_supervised(coresys: CoreSys) -> None: await coresys.dbus.hostname.connect(coresys.dbus.bus) await coresys.os.load() assert coresys.os.board == "supervised" + + +async def test_load_slot_status_fresh_install( + coresys: CoreSys, + all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]], +) -> None: + """Test load works when slot status returns minimal fresh install response.""" + rauc_service: RaucService = all_dbus_services["rauc"] + rauc_service.response_get_slot_status = [ + ( + "kernel.0", + { + "class": Variant("s", "kernel"), + "boot-status": Variant("s", "good"), + "type": Variant("s", "raw"), + "bootname": Variant("s", "A"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-kernel0"), + "state": Variant("s", "inactive"), + }, + ), + ( + "boot.0", + { + "bundle.compatible": Variant("s", "haos-green"), + "sha256": Variant( + "s", + "f0b8a08d9bc49acbb230cf709beb0aa214cbee09969566755dff52fb8b3cc29b", + ), + "state": Variant("s", "inactive"), + "size": Variant("t", 16777216), + "installed.count": Variant("u", 1), + "class": Variant("s", "boot"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-boot"), + "type": Variant("s", "vfat"), + "status": Variant("s", "ok"), + "bundle.version": Variant("s", "12.2.dev20240313"), + "installed.timestamp": Variant("s", "2024-03-15T17:27:38Z"), + }, + ), + ( + "rootfs.0", + { + "class": Variant("s", "rootfs"), + "parent": Variant("s", "kernel.0"), + "type": Variant("s", "raw"), + "state": Variant("s", "inactive"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-system0"), + }, + ), + ( + "spl.0", + { + "bundle.compatible": Variant("s", "haos-green"), + "sha256": Variant( + "s", + "97e4f1616250e7f9d2b20d98a972cf3aab03849a8cf50a8630f96a183b64384f", + ), + "state": Variant("s", "inactive"), + "size": Variant("t", 16777216), + "installed.count": Variant("u", 1), + "class": Variant("s", "spl"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-boot"), + "type": Variant("s", "raw"), + "status": Variant("s", "ok"), + "bundle.version": Variant("s", "12.2.dev20240313"), + "installed.timestamp": Variant("s", "2024-03-15T17:27:47Z"), + }, + ), + ( + "kernel.1", + { + "activated.count": Variant("u", 1), + "activated.timestamp": Variant("s", "2024-03-15T17:27:47Z"), + "boot-status": Variant("s", "good"), + "bundle.compatible": Variant("s", "haos-green"), + "sha256": Variant( + "s", + "c327b3c2ac4f56926d0d7c4693fe79c67dc05ed49c4abd020da981bf4faf977f", + ), + "state": Variant("s", "booted"), + "size": Variant("t", 13410304), + "installed.count": Variant("u", 1), + "class": Variant("s", "kernel"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-kernel1"), + "type": Variant("s", "raw"), + "bootname": Variant("s", "B"), + "bundle.version": Variant("s", "12.2.dev20240313"), + "installed.timestamp": Variant("s", "2024-03-15T17:27:39Z"), + "status": Variant("s", "ok"), + }, + ), + ( + "rootfs.1", + { + "bundle.compatible": Variant("s", "haos-green"), + "parent": Variant("s", "kernel.1"), + "state": Variant("s", "active"), + "size": Variant("t", 194560000), + "sha256": Variant( + "s", + "151dbfff469a7f1252cb8482e7a9439c5164f52c53ed141e377c10e6858208cb", + ), + "class": Variant("s", "rootfs"), + "device": Variant("s", "/dev/disk/by-partlabel/hassos-system1"), + "type": Variant("s", "raw"), + "status": Variant("s", "ok"), + "bundle.version": Variant("s", "12.2.dev20240313"), + "installed.timestamp": Variant("s", "2024-03-15T17:27:45Z"), + "installed.count": Variant("u", 1), + }, + ), + ] + + await coresys.os.load() + assert len(coresys.os.slots) == 6 + assert coresys.os.get_slot_name("A") == "kernel.0" + assert coresys.os.get_slot_name("B") == "kernel.1"