mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-06-19 08:26:30 +00:00
Unsupported if wrong image used on virtualization (#4968)
* Unsupported if wrong image used on virtualization * Add generic-aarch64 as supported image * Add virtualization field to API * Change startup to setup in check
This commit is contained in:
parent
d685780a4a
commit
90c971f9f1
@ -66,6 +66,7 @@ ATTR_USAGE = "usage"
|
|||||||
ATTR_USE_NTP = "use_ntp"
|
ATTR_USE_NTP = "use_ntp"
|
||||||
ATTR_USERS = "users"
|
ATTR_USERS = "users"
|
||||||
ATTR_VENDOR = "vendor"
|
ATTR_VENDOR = "vendor"
|
||||||
|
ATTR_VIRTUALIZATION = "virtualization"
|
||||||
|
|
||||||
|
|
||||||
class BootSlot(StrEnum):
|
class BootSlot(StrEnum):
|
||||||
|
@ -49,6 +49,7 @@ from .const import (
|
|||||||
ATTR_LLMNR_HOSTNAME,
|
ATTR_LLMNR_HOSTNAME,
|
||||||
ATTR_STARTUP_TIME,
|
ATTR_STARTUP_TIME,
|
||||||
ATTR_USE_NTP,
|
ATTR_USE_NTP,
|
||||||
|
ATTR_VIRTUALIZATION,
|
||||||
CONTENT_TYPE_TEXT,
|
CONTENT_TYPE_TEXT,
|
||||||
CONTENT_TYPE_X_LOG,
|
CONTENT_TYPE_X_LOG,
|
||||||
)
|
)
|
||||||
@ -73,6 +74,7 @@ class APIHost(CoreSysAttributes):
|
|||||||
ATTR_AGENT_VERSION: self.sys_dbus.agent.version,
|
ATTR_AGENT_VERSION: self.sys_dbus.agent.version,
|
||||||
ATTR_APPARMOR_VERSION: self.sys_host.apparmor.version,
|
ATTR_APPARMOR_VERSION: self.sys_host.apparmor.version,
|
||||||
ATTR_CHASSIS: self.sys_host.info.chassis,
|
ATTR_CHASSIS: self.sys_host.info.chassis,
|
||||||
|
ATTR_VIRTUALIZATION: self.sys_host.info.virtualization,
|
||||||
ATTR_CPE: self.sys_host.info.cpe,
|
ATTR_CPE: self.sys_host.info.cpe,
|
||||||
ATTR_DEPLOYMENT: self.sys_host.info.deployment,
|
ATTR_DEPLOYMENT: self.sys_host.info.deployment,
|
||||||
ATTR_DISK_FREE: self.sys_host.info.free_space,
|
ATTR_DISK_FREE: self.sys_host.info.free_space,
|
||||||
|
@ -180,6 +180,7 @@ DBUS_ATTR_UUID = "Uuid"
|
|||||||
DBUS_ATTR_VARIANT = "Variant"
|
DBUS_ATTR_VARIANT = "Variant"
|
||||||
DBUS_ATTR_VENDOR = "Vendor"
|
DBUS_ATTR_VENDOR = "Vendor"
|
||||||
DBUS_ATTR_VERSION = "Version"
|
DBUS_ATTR_VERSION = "Version"
|
||||||
|
DBUS_ATTR_VIRTUALIZATION = "Virtualization"
|
||||||
DBUS_ATTR_WHAT = "What"
|
DBUS_ATTR_WHAT = "What"
|
||||||
DBUS_ATTR_WWN = "WWN"
|
DBUS_ATTR_WWN = "WWN"
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ from .const import (
|
|||||||
DBUS_ATTR_KERNEL_TIMESTAMP_MONOTONIC,
|
DBUS_ATTR_KERNEL_TIMESTAMP_MONOTONIC,
|
||||||
DBUS_ATTR_LOADER_TIMESTAMP_MONOTONIC,
|
DBUS_ATTR_LOADER_TIMESTAMP_MONOTONIC,
|
||||||
DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC,
|
DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC,
|
||||||
|
DBUS_ATTR_VIRTUALIZATION,
|
||||||
DBUS_ERR_SYSTEMD_NO_SUCH_UNIT,
|
DBUS_ERR_SYSTEMD_NO_SUCH_UNIT,
|
||||||
DBUS_IFACE_SYSTEMD_MANAGER,
|
DBUS_IFACE_SYSTEMD_MANAGER,
|
||||||
DBUS_NAME_SYSTEMD,
|
DBUS_NAME_SYSTEMD,
|
||||||
@ -114,6 +115,12 @@ class Systemd(DBusInterfaceProxy):
|
|||||||
"""Return the boot timestamp."""
|
"""Return the boot timestamp."""
|
||||||
return self.properties[DBUS_ATTR_FINISH_TIMESTAMP]
|
return self.properties[DBUS_ATTR_FINISH_TIMESTAMP]
|
||||||
|
|
||||||
|
@property
|
||||||
|
@dbus_property
|
||||||
|
def virtualization(self) -> str:
|
||||||
|
"""Return virtualization hypervisor being used."""
|
||||||
|
return self.properties[DBUS_ATTR_VIRTUALIZATION]
|
||||||
|
|
||||||
@dbus_connected
|
@dbus_connected
|
||||||
async def reboot(self) -> None:
|
async def reboot(self) -> None:
|
||||||
"""Reboot host computer."""
|
"""Reboot host computer."""
|
||||||
|
@ -129,6 +129,11 @@ class InfoCenter(CoreSysAttributes):
|
|||||||
self.coresys.config.path_supervisor
|
self.coresys.config.path_supervisor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def virtualization(self) -> str | None:
|
||||||
|
"""Return virtualization hypervisor being used."""
|
||||||
|
return self.sys_dbus.systemd.virtualization
|
||||||
|
|
||||||
async def get_dmesg(self) -> bytes:
|
async def get_dmesg(self) -> bytes:
|
||||||
"""Return host dmesg output."""
|
"""Return host dmesg output."""
|
||||||
proc = await asyncio.create_subprocess_shell(
|
proc = await asyncio.create_subprocess_shell(
|
||||||
|
@ -53,6 +53,7 @@ class UnsupportedReason(StrEnum):
|
|||||||
SYSTEMD = "systemd"
|
SYSTEMD = "systemd"
|
||||||
SYSTEMD_JOURNAL = "systemd_journal"
|
SYSTEMD_JOURNAL = "systemd_journal"
|
||||||
SYSTEMD_RESOLVED = "systemd_resolved"
|
SYSTEMD_RESOLVED = "systemd_resolved"
|
||||||
|
VIRTUALIZATION_IMAGE = "virtualization_image"
|
||||||
|
|
||||||
|
|
||||||
class UnhealthyReason(StrEnum):
|
class UnhealthyReason(StrEnum):
|
||||||
|
39
supervisor/resolution/evaluations/virtualization_image.py
Normal file
39
supervisor/resolution/evaluations/virtualization_image.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"""Evaluation class for virtualization image."""
|
||||||
|
|
||||||
|
from ...const import CoreState
|
||||||
|
from ...coresys import CoreSys
|
||||||
|
from ..const import UnsupportedReason
|
||||||
|
from .base import EvaluateBase
|
||||||
|
|
||||||
|
|
||||||
|
def setup(coresys: CoreSys) -> EvaluateBase:
|
||||||
|
"""Initialize evaluation-setup function."""
|
||||||
|
return EvaluateVirtualizationImage(coresys)
|
||||||
|
|
||||||
|
|
||||||
|
class EvaluateVirtualizationImage(EvaluateBase):
|
||||||
|
"""Evaluate correct OS image used when running under virtualization."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reason(self) -> UnsupportedReason:
|
||||||
|
"""Return a UnsupportedReason enum."""
|
||||||
|
return UnsupportedReason.VIRTUALIZATION_IMAGE
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_failure(self) -> str:
|
||||||
|
"""Return a string that is printed when self.evaluate is True."""
|
||||||
|
return "Image of Home Assistant OS in use does not support virtualization."
|
||||||
|
|
||||||
|
@property
|
||||||
|
def states(self) -> list[CoreState]:
|
||||||
|
"""Return a list of valid states when this evaluation can run."""
|
||||||
|
return [CoreState.SETUP]
|
||||||
|
|
||||||
|
async def evaluate(self):
|
||||||
|
"""Run evaluation."""
|
||||||
|
if not self.sys_os.available:
|
||||||
|
return False
|
||||||
|
return self.sys_host.info.virtualization and self.sys_os.board not in {
|
||||||
|
"ova",
|
||||||
|
"generic-aarch64",
|
||||||
|
}
|
@ -9,6 +9,9 @@ from supervisor.coresys import CoreSys
|
|||||||
from supervisor.dbus.resolved import Resolved
|
from supervisor.dbus.resolved import Resolved
|
||||||
from supervisor.host.const import LogFormat, LogFormatter
|
from supervisor.host.const import LogFormat, LogFormatter
|
||||||
|
|
||||||
|
from tests.dbus_service_mocks.base import DBusServiceMock
|
||||||
|
from tests.dbus_service_mocks.systemd import Systemd as SystemdService
|
||||||
|
|
||||||
DEFAULT_RANGE = "entries=:-100:"
|
DEFAULT_RANGE = "entries=:-100:"
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
@ -147,6 +150,26 @@ async def test_api_identifiers_info(api_client: TestClient, journald_logs: Magic
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_api_virtualization_info(
|
||||||
|
api_client: TestClient,
|
||||||
|
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
||||||
|
coresys_disk_info: CoreSys,
|
||||||
|
):
|
||||||
|
"""Test getting virtualization info."""
|
||||||
|
systemd_service: SystemdService = all_dbus_services["systemd"]
|
||||||
|
|
||||||
|
resp = await api_client.get("/host/info")
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["data"]["virtualization"] == ""
|
||||||
|
|
||||||
|
systemd_service.virtualization = "vmware"
|
||||||
|
await coresys_disk_info.dbus.systemd.update()
|
||||||
|
|
||||||
|
resp = await api_client.get("/host/info")
|
||||||
|
result = await resp.json()
|
||||||
|
assert result["data"]["virtualization"] == "vmware"
|
||||||
|
|
||||||
|
|
||||||
async def test_advanced_logs(
|
async def test_advanced_logs(
|
||||||
api_client: TestClient, coresys: CoreSys, journald_logs: MagicMock
|
api_client: TestClient, coresys: CoreSys, journald_logs: MagicMock
|
||||||
):
|
):
|
||||||
|
@ -28,6 +28,7 @@ class Systemd(DBusServiceMock):
|
|||||||
reboot_watchdog_usec = 600000000
|
reboot_watchdog_usec = 600000000
|
||||||
kexec_watchdog_usec = 0
|
kexec_watchdog_usec = 0
|
||||||
service_watchdogs = True
|
service_watchdogs = True
|
||||||
|
virtualization = ""
|
||||||
response_get_unit: dict[str, list[str | DBusError]] | list[
|
response_get_unit: dict[str, list[str | DBusError]] | list[
|
||||||
str | DBusError
|
str | DBusError
|
||||||
] | str | DBusError = "/org/freedesktop/systemd1/unit/tmp_2dyellow_2emount"
|
] | str | DBusError = "/org/freedesktop/systemd1/unit/tmp_2dyellow_2emount"
|
||||||
@ -53,7 +54,7 @@ class Systemd(DBusServiceMock):
|
|||||||
@dbus_property(access=PropertyAccess.READ)
|
@dbus_property(access=PropertyAccess.READ)
|
||||||
def Virtualization(self) -> "s":
|
def Virtualization(self) -> "s":
|
||||||
"""Get Virtualization."""
|
"""Get Virtualization."""
|
||||||
return ""
|
return self.virtualization
|
||||||
|
|
||||||
@dbus_property(access=PropertyAccess.READ)
|
@dbus_property(access=PropertyAccess.READ)
|
||||||
def Architecture(self) -> "s":
|
def Architecture(self) -> "s":
|
||||||
|
89
tests/resolution/evaluation/test_virtualization_image.py
Normal file
89
tests/resolution/evaluation/test_virtualization_image.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
"""Test evaluation base."""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from supervisor.const import CoreState
|
||||||
|
from supervisor.coresys import CoreSys
|
||||||
|
from supervisor.resolution.evaluations.virtualization_image import (
|
||||||
|
EvaluateVirtualizationImage,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.dbus_service_mocks.base import DBusServiceMock
|
||||||
|
from tests.dbus_service_mocks.systemd import Systemd as SystemdService
|
||||||
|
|
||||||
|
|
||||||
|
async def test_evaluation(
|
||||||
|
coresys: CoreSys,
|
||||||
|
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
||||||
|
):
|
||||||
|
"""Test evaluation."""
|
||||||
|
systemd_service: SystemdService = all_dbus_services["systemd"]
|
||||||
|
virtualization = EvaluateVirtualizationImage(coresys)
|
||||||
|
coresys.core.state = CoreState.SETUP
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"supervisor.os.manager.CPE.get_target_hardware", return_value=["generic-x86-64"]
|
||||||
|
):
|
||||||
|
systemd_service.virtualization = "vmware"
|
||||||
|
await coresys.dbus.systemd.update()
|
||||||
|
|
||||||
|
assert not coresys.os.available
|
||||||
|
await virtualization()
|
||||||
|
assert virtualization.reason not in coresys.resolution.unsupported
|
||||||
|
|
||||||
|
await coresys.os.load()
|
||||||
|
assert coresys.os.available
|
||||||
|
await virtualization()
|
||||||
|
assert virtualization.reason in coresys.resolution.unsupported
|
||||||
|
|
||||||
|
systemd_service.virtualization = ""
|
||||||
|
await coresys.dbus.systemd.update()
|
||||||
|
await virtualization()
|
||||||
|
assert virtualization.reason not in coresys.resolution.unsupported
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("board", ["ova", "generic-aarch64"])
|
||||||
|
async def test_evaluation_supported_images(
|
||||||
|
coresys: CoreSys,
|
||||||
|
all_dbus_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
||||||
|
board: str,
|
||||||
|
):
|
||||||
|
"""Test supported images for virtualization do not trigger unsupported."""
|
||||||
|
systemd_service: SystemdService = all_dbus_services["systemd"]
|
||||||
|
virtualization = EvaluateVirtualizationImage(coresys)
|
||||||
|
coresys.core.state = CoreState.SETUP
|
||||||
|
|
||||||
|
with patch("supervisor.os.manager.CPE.get_target_hardware", return_value=[board]):
|
||||||
|
systemd_service.virtualization = "vmware"
|
||||||
|
await coresys.dbus.systemd.update()
|
||||||
|
await coresys.os.load()
|
||||||
|
|
||||||
|
await virtualization()
|
||||||
|
assert virtualization.reason not in coresys.resolution.unsupported
|
||||||
|
|
||||||
|
|
||||||
|
async def test_did_run(coresys: CoreSys):
|
||||||
|
"""Test that the evaluation ran as expected."""
|
||||||
|
virtualization = EvaluateVirtualizationImage(coresys)
|
||||||
|
should_run = virtualization.states
|
||||||
|
should_not_run = [state for state in CoreState if state not in should_run]
|
||||||
|
assert len(should_run) != 0
|
||||||
|
assert len(should_not_run) != 0
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"supervisor.resolution.evaluations.virtualization_image.EvaluateVirtualizationImage.evaluate",
|
||||||
|
return_value=None,
|
||||||
|
) as evaluate:
|
||||||
|
for state in should_run:
|
||||||
|
coresys.core.state = state
|
||||||
|
await virtualization()
|
||||||
|
evaluate.assert_called_once()
|
||||||
|
evaluate.reset_mock()
|
||||||
|
|
||||||
|
for state in should_not_run:
|
||||||
|
coresys.core.state = state
|
||||||
|
await virtualization()
|
||||||
|
evaluate.assert_not_called()
|
||||||
|
evaluate.reset_mock()
|
Loading…
x
Reference in New Issue
Block a user