mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-15 21:26:29 +00:00
Refactor to dbus-next proxy interfaces (#3862)
* Refactor to dbus-next proxy interfaces * Fix tests mocking dbus methods * Fix call dbus
This commit is contained in:
parent
c67d4d7c0b
commit
d195f19fa8
@ -75,9 +75,7 @@ class OSAgent(DBusInterface):
|
||||
@dbus_property
|
||||
def diagnostics(self, value: bool) -> None:
|
||||
"""Enable or disable OS-Agent diagnostics."""
|
||||
asyncio.create_task(
|
||||
self.dbus.set_property(DBUS_IFACE_HAOS, DBUS_ATTR_DIAGNOSTICS, value)
|
||||
)
|
||||
asyncio.create_task(self.dbus.set_diagnostics(value))
|
||||
|
||||
async def connect(self, bus: MessageBus) -> None:
|
||||
"""Connect to system's D-Bus."""
|
||||
|
@ -41,9 +41,11 @@ class AppArmor(DBusInterface):
|
||||
@dbus_connected
|
||||
async def load_profile(self, profile: Path, cache: Path) -> None:
|
||||
"""Load/Update AppArmor profile."""
|
||||
await self.dbus.AppArmor.LoadProfile(profile.as_posix(), cache.as_posix())
|
||||
await self.dbus.AppArmor.call_load_profile(profile.as_posix(), cache.as_posix())
|
||||
|
||||
@dbus_connected
|
||||
async def unload_profile(self, profile: Path, cache: Path) -> None:
|
||||
"""Remove AppArmor profile."""
|
||||
await self.dbus.AppArmor.UnloadProfile(profile.as_posix(), cache.as_posix())
|
||||
await self.dbus.AppArmor.call_unload_profile(
|
||||
profile.as_posix(), cache.as_posix()
|
||||
)
|
||||
|
@ -18,4 +18,4 @@ class CGroup(DBusInterface):
|
||||
@dbus_connected
|
||||
async def add_devices_allowed(self, container_id: str, permission: str) -> None:
|
||||
"""Update cgroup devices and add new devices."""
|
||||
await self.dbus.CGroup.AddDevicesAllowed(container_id, permission)
|
||||
await self.dbus.CGroup.call_add_devices_allowed(container_id, permission)
|
||||
|
@ -40,9 +40,9 @@ class DataDisk(DBusInterface):
|
||||
@dbus_connected
|
||||
async def change_device(self, device: Path) -> None:
|
||||
"""Migrate data disk to a new device."""
|
||||
await self.dbus.DataDisk.ChangeDevice(device.as_posix())
|
||||
await self.dbus.DataDisk.call_change_device(device.as_posix())
|
||||
|
||||
@dbus_connected
|
||||
async def reload_device(self) -> None:
|
||||
"""Reload device data."""
|
||||
await self.dbus.DataDisk.ReloadDevice()
|
||||
await self.dbus.DataDisk.call_reload_device()
|
||||
|
@ -18,4 +18,4 @@ class System(DBusInterface):
|
||||
@dbus_connected
|
||||
async def schedule_wipe_device(self) -> None:
|
||||
"""Schedule a factory reset on next system boot."""
|
||||
await self.dbus.System.ScheduleWipeDevice()
|
||||
await self.dbus.System.call_schedule_wipe_device()
|
||||
|
@ -87,7 +87,7 @@ class Hostname(DBusInterface):
|
||||
@dbus_connected
|
||||
async def set_static_hostname(self, hostname: str) -> None:
|
||||
"""Change local hostname."""
|
||||
await self.dbus.SetStaticHostname(hostname, False)
|
||||
await self.dbus.call_set_static_hostname(hostname, False)
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
|
@ -32,9 +32,9 @@ class Logind(DBusInterface):
|
||||
@dbus_connected
|
||||
async def reboot(self) -> None:
|
||||
"""Reboot host computer."""
|
||||
await self.dbus.Manager.Reboot(False)
|
||||
await self.dbus.Manager.call_reboot(False)
|
||||
|
||||
@dbus_connected
|
||||
async def power_off(self) -> None:
|
||||
"""Power off host computer."""
|
||||
await self.dbus.Manager.PowerOff(False)
|
||||
await self.dbus.Manager.call_power_off(False)
|
||||
|
@ -112,7 +112,7 @@ class DBusManager(CoreSysAttributes):
|
||||
for dbus in dbus_loads:
|
||||
_LOGGER.info("Load dbus interface %s", dbus.name)
|
||||
try:
|
||||
await dbus.connect(self._bus)
|
||||
await dbus.connect(self.bus)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
_LOGGER.warning("Can't load dbus interface %s: %s", dbus.name, err)
|
||||
|
||||
@ -120,5 +120,9 @@ class DBusManager(CoreSysAttributes):
|
||||
|
||||
async def unload(self) -> None:
|
||||
"""Close connection to D-Bus."""
|
||||
self._bus.disconnect()
|
||||
if not self.bus:
|
||||
_LOGGER.warning("No D-Bus connection to close.")
|
||||
return
|
||||
|
||||
self.bus.disconnect()
|
||||
_LOGGER.info("Closed conection to system D-Bus.")
|
||||
|
@ -17,7 +17,6 @@ from ...exceptions import (
|
||||
from ...utils.dbus import DBus
|
||||
from ..const import (
|
||||
DBUS_ATTR_CONNECTION_ENABLED,
|
||||
DBUS_ATTR_CONNECTIVITY,
|
||||
DBUS_ATTR_DEVICES,
|
||||
DBUS_ATTR_PRIMARY_CONNECTION,
|
||||
DBUS_ATTR_VERSION,
|
||||
@ -88,10 +87,9 @@ class NetworkManager(DBusInterface):
|
||||
self, connection_object: str, device_object: str
|
||||
) -> NetworkConnection:
|
||||
"""Activate a connction on a device."""
|
||||
result = await self.dbus.ActivateConnection(
|
||||
("o", connection_object), ("o", device_object), ("o", DBUS_OBJECT_BASE)
|
||||
obj_active_con = await self.dbus.call_activate_connection(
|
||||
connection_object, device_object, DBUS_OBJECT_BASE
|
||||
)
|
||||
obj_active_con = result[0]
|
||||
active_con = NetworkConnection(obj_active_con)
|
||||
await active_con.connect(self.dbus.bus)
|
||||
return active_con
|
||||
@ -101,8 +99,11 @@ class NetworkManager(DBusInterface):
|
||||
self, settings: Any, device_object: str
|
||||
) -> tuple[NetworkSetting, NetworkConnection]:
|
||||
"""Activate a connction on a device."""
|
||||
obj_con_setting, obj_active_con = await self.dbus.AddAndActivateConnection(
|
||||
("a{sa{sv}}", settings), ("o", device_object), ("o", DBUS_OBJECT_BASE)
|
||||
(
|
||||
obj_con_setting,
|
||||
obj_active_con,
|
||||
) = await self.dbus.call_add_and_activate_connection(
|
||||
settings, device_object, DBUS_OBJECT_BASE
|
||||
)
|
||||
|
||||
con_setting = NetworkSetting(obj_con_setting)
|
||||
@ -116,9 +117,9 @@ class NetworkManager(DBusInterface):
|
||||
async def check_connectivity(self, *, force: bool = False) -> int:
|
||||
"""Check the connectivity of the host."""
|
||||
if force:
|
||||
return (await self.dbus.CheckConnectivity())[0]
|
||||
return await self.dbus.call_check_connectivity()
|
||||
else:
|
||||
return await self.dbus.get_property(DBUS_IFACE_NM, DBUS_ATTR_CONNECTIVITY)
|
||||
return await self.dbus.get_connectivity()
|
||||
|
||||
async def connect(self, bus: MessageBus) -> None:
|
||||
"""Connect to system's D-Bus."""
|
||||
|
@ -124,14 +124,14 @@ class NetworkSetting(DBusInterfaceProxy):
|
||||
@dbus_connected
|
||||
async def get_settings(self) -> dict[str, Any]:
|
||||
"""Return connection settings."""
|
||||
return (await self.dbus.Settings.Connection.GetSettings())[0]
|
||||
return await self.dbus.Settings.Connection.call_get_settings()
|
||||
|
||||
@dbus_connected
|
||||
async def update(self, settings: Any) -> None:
|
||||
"""Update connection settings."""
|
||||
new_settings = (
|
||||
await self.dbus.Settings.Connection.GetSettings(remove_signature=False)
|
||||
)[0]
|
||||
new_settings = await self.dbus.Settings.Connection.call_get_settings(
|
||||
remove_signature=False
|
||||
)
|
||||
|
||||
_merge_settings_attribute(new_settings, settings, CONF_ATTR_CONNECTION)
|
||||
_merge_settings_attribute(new_settings, settings, CONF_ATTR_802_ETHERNET)
|
||||
@ -153,12 +153,12 @@ class NetworkSetting(DBusInterfaceProxy):
|
||||
ignore_current_value=IPV4_6_IGNORE_FIELDS,
|
||||
)
|
||||
|
||||
return await self.dbus.Settings.Connection.Update(("a{sa{sv}}", new_settings))
|
||||
await self.dbus.Settings.Connection.call_update(new_settings)
|
||||
|
||||
@dbus_connected
|
||||
async def delete(self) -> None:
|
||||
"""Delete connection settings."""
|
||||
await self.dbus.Settings.Connection.Delete()
|
||||
await self.dbus.Settings.Connection.call_delete()
|
||||
|
||||
async def connect(self, bus: MessageBus) -> None:
|
||||
"""Get connection information."""
|
||||
|
@ -34,9 +34,7 @@ class NetworkManagerSettings(DBusInterface):
|
||||
@dbus_connected
|
||||
async def add_connection(self, settings: Any) -> NetworkSetting:
|
||||
"""Add new connection."""
|
||||
obj_con_setting = (
|
||||
await self.dbus.Settings.AddConnection(("a{sa{sv}}", settings))
|
||||
)[0]
|
||||
obj_con_setting = await self.dbus.Settings.call_add_connection(settings)
|
||||
con_setting = NetworkSetting(obj_con_setting)
|
||||
await con_setting.connect(self.dbus.bus)
|
||||
return con_setting
|
||||
@ -44,4 +42,4 @@ class NetworkManagerSettings(DBusInterface):
|
||||
@dbus_connected
|
||||
async def reload_connections(self) -> bool:
|
||||
"""Reload all local connection files."""
|
||||
return (await self.dbus.Settings.ReloadConnections())[0]
|
||||
return await self.dbus.Settings.call_reload_connections()
|
||||
|
@ -39,12 +39,12 @@ class NetworkWireless(DBusInterfaceProxy):
|
||||
@dbus_connected
|
||||
async def request_scan(self) -> None:
|
||||
"""Request a new AP scan."""
|
||||
await self.dbus.Device.Wireless.RequestScan(("a{sv}", {}))
|
||||
await self.dbus.Device.Wireless.call_request_scan({})
|
||||
|
||||
@dbus_connected
|
||||
async def get_all_accesspoints(self) -> list[NetworkWirelessAP]:
|
||||
"""Return a list of all access points path."""
|
||||
accesspoints_data = (await self.dbus.Device.Wireless.GetAllAccessPoints())[0]
|
||||
accesspoints_data = await self.dbus.Device.Wireless.call_get_all_access_points()
|
||||
accesspoints = [NetworkWirelessAP(ap_obj) for ap_obj in accesspoints_data]
|
||||
|
||||
for err in await asyncio.gather(
|
||||
|
@ -74,12 +74,12 @@ class Rauc(DBusInterface):
|
||||
@dbus_connected
|
||||
async def install(self, raucb_file) -> None:
|
||||
"""Install rauc bundle file."""
|
||||
await self.dbus.Installer.Install(str(raucb_file))
|
||||
await self.dbus.Installer.call_install(str(raucb_file))
|
||||
|
||||
@dbus_connected
|
||||
async def get_slot_status(self) -> list[tuple[str, dict[str, Any]]]:
|
||||
"""Get slot status."""
|
||||
return (await self.dbus.Installer.GetSlotStatus())[0]
|
||||
return await self.dbus.Installer.call_get_slot_status()
|
||||
|
||||
@dbus_connected
|
||||
def signal_completed(self) -> DBusSignalWrapper:
|
||||
@ -89,7 +89,7 @@ class Rauc(DBusInterface):
|
||||
@dbus_connected
|
||||
async def mark(self, state: RaucState, slot_identifier: str) -> tuple[str, str]:
|
||||
"""Get slot status."""
|
||||
return await self.dbus.Installer.Mark(state, slot_identifier)
|
||||
return await self.dbus.Installer.call_mark(state, slot_identifier)
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
|
@ -65,39 +65,39 @@ class Systemd(DBusInterface):
|
||||
@dbus_connected
|
||||
async def reboot(self) -> None:
|
||||
"""Reboot host computer."""
|
||||
await self.dbus.Manager.Reboot()
|
||||
await self.dbus.Manager.call_reboot()
|
||||
|
||||
@dbus_connected
|
||||
async def power_off(self) -> None:
|
||||
"""Power off host computer."""
|
||||
await self.dbus.Manager.PowerOff()
|
||||
await self.dbus.Manager.call_power_off()
|
||||
|
||||
@dbus_connected
|
||||
async def start_unit(self, unit, mode) -> str:
|
||||
"""Start a systemd service unit. Return job object path."""
|
||||
return (await self.dbus.Manager.StartUnit(unit, mode))[0]
|
||||
"""Start a systemd service unit. Returns object path of job."""
|
||||
return await self.dbus.Manager.call_start_unit(unit, mode)
|
||||
|
||||
@dbus_connected
|
||||
async def stop_unit(self, unit, mode) -> str:
|
||||
"""Stop a systemd service unit."""
|
||||
return (await self.dbus.Manager.StopUnit(unit, mode))[0]
|
||||
"""Stop a systemd service unit. Returns object path of job."""
|
||||
return await self.dbus.Manager.call_stop_unit(unit, mode)
|
||||
|
||||
@dbus_connected
|
||||
async def reload_unit(self, unit, mode) -> str:
|
||||
"""Reload a systemd service unit."""
|
||||
return (await self.dbus.Manager.ReloadOrRestartUnit(unit, mode))[0]
|
||||
"""Reload a systemd service unit. Returns object path of job."""
|
||||
return await self.dbus.Manager.call_reload_or_restart_unit(unit, mode)
|
||||
|
||||
@dbus_connected
|
||||
async def restart_unit(self, unit, mode):
|
||||
"""Restart a systemd service unit."""
|
||||
return (await self.dbus.Manager.RestartUnit(unit, mode))[0]
|
||||
async def restart_unit(self, unit, mode) -> str:
|
||||
"""Restart a systemd service unit. Returns object path of job."""
|
||||
return await self.dbus.Manager.call_restart_unit(unit, mode)
|
||||
|
||||
@dbus_connected
|
||||
async def list_units(
|
||||
self,
|
||||
) -> list[str, str, str, str, str, str, str, int, str, str]:
|
||||
) -> list[tuple[str, str, str, str, str, str, str, int, str, str]]:
|
||||
"""Return a list of available systemd services."""
|
||||
return (await self.dbus.Manager.ListUnits())[0]
|
||||
return await self.dbus.Manager.call_list_units()
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
|
@ -75,12 +75,12 @@ class TimeDate(DBusInterface):
|
||||
@dbus_connected
|
||||
async def set_time(self, utc: datetime) -> None:
|
||||
"""Set time & date on host as UTC."""
|
||||
await self.dbus.SetTime(int(utc.timestamp() * 1000000), False, False)
|
||||
await self.dbus.call_set_time(int(utc.timestamp() * 1000000), False, False)
|
||||
|
||||
@dbus_connected
|
||||
async def set_ntp(self, use_ntp: bool) -> None:
|
||||
"""Turn NTP on or off."""
|
||||
await self.dbus.SetNTP(use_ntp, False)
|
||||
await self.dbus.call_set_ntp(use_ntp, False)
|
||||
|
||||
@dbus_connected
|
||||
async def update(self):
|
||||
|
@ -315,18 +315,34 @@ class DBusInterfaceError(HassioNotSupportedError):
|
||||
"""D-Bus interface not connected."""
|
||||
|
||||
|
||||
class DBusObjectError(HassioNotSupportedError):
|
||||
"""D-Bus object not defined."""
|
||||
|
||||
|
||||
class DBusInterfaceMethodError(DBusInterfaceError):
|
||||
"""D-Bus method not defined or input does not match signature."""
|
||||
|
||||
|
||||
class DBusInterfacePropertyError(DBusInterfaceError):
|
||||
"""D-Bus property not defined or is read-only."""
|
||||
|
||||
|
||||
class DBusInterfaceSignalError(DBusInterfaceError):
|
||||
"""D-Bus signal not defined."""
|
||||
|
||||
|
||||
class DBusFatalError(DBusError):
|
||||
"""D-Bus call going wrong."""
|
||||
|
||||
|
||||
class DBusInterfaceMethodError(DBusInterfaceError):
|
||||
"""D-Bus method was not defined."""
|
||||
|
||||
|
||||
class DBusParseError(DBusError):
|
||||
"""D-Bus parse error."""
|
||||
|
||||
|
||||
class DBusTimeoutError(DBusError):
|
||||
"""D-Bus call timed out."""
|
||||
|
||||
|
||||
# util/apparmor
|
||||
|
||||
|
||||
|
@ -3,10 +3,12 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
from dbus_next import InvalidIntrospectionError, Message, MessageType
|
||||
from dbus_next import ErrorType, InvalidIntrospectionError, Message, MessageType
|
||||
from dbus_next.aio.message_bus import MessageBus
|
||||
from dbus_next.aio.proxy_object import ProxyInterface, ProxyObject
|
||||
from dbus_next.errors import DBusError
|
||||
from dbus_next.introspection import Node
|
||||
from dbus_next.signature import Variant
|
||||
|
||||
@ -14,32 +16,19 @@ from ..exceptions import (
|
||||
DBusFatalError,
|
||||
DBusInterfaceError,
|
||||
DBusInterfaceMethodError,
|
||||
DBusInterfacePropertyError,
|
||||
DBusInterfaceSignalError,
|
||||
DBusNotConnectedError,
|
||||
DBusObjectError,
|
||||
DBusParseError,
|
||||
DBusTimeoutError,
|
||||
HassioNotSupportedError,
|
||||
)
|
||||
|
||||
|
||||
def _remove_dbus_signature(data: Any) -> Any:
|
||||
if isinstance(data, Variant):
|
||||
return _remove_dbus_signature(data.value)
|
||||
elif isinstance(data, dict):
|
||||
for k in data:
|
||||
data[k] = _remove_dbus_signature(data[k])
|
||||
return data
|
||||
elif isinstance(data, list):
|
||||
new_list = []
|
||||
for item in data:
|
||||
new_list.append(_remove_dbus_signature(item))
|
||||
return new_list
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
DBUS_INTERFACE_PROPERTIES: str = "org.freedesktop.DBus.Properties"
|
||||
DBUS_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll"
|
||||
DBUS_METHOD_GET: str = "org.freedesktop.DBus.Properties.Get"
|
||||
DBUS_METHOD_SET: str = "org.freedesktop.DBus.Properties.Set"
|
||||
|
||||
|
||||
class DBus:
|
||||
@ -49,8 +38,8 @@ class DBus:
|
||||
"""Initialize dbus object."""
|
||||
self.bus_name: str = bus_name
|
||||
self.object_path: str = object_path
|
||||
self.methods: set[str] = set()
|
||||
self.signals: set[str] = set()
|
||||
self._proxy_obj: ProxyObject | None = None
|
||||
self._proxies: dict[str, ProxyInterface] = {}
|
||||
self._bus: MessageBus = bus
|
||||
|
||||
@staticmethod
|
||||
@ -64,29 +53,73 @@ class DBus:
|
||||
_LOGGER.debug("Connect to D-Bus: %s - %s", bus_name, object_path)
|
||||
return self
|
||||
|
||||
@property
|
||||
def bus(self) -> MessageBus:
|
||||
"""Return message bus."""
|
||||
return self._bus
|
||||
@staticmethod
|
||||
def remove_dbus_signature(data: Any) -> Any:
|
||||
"""Remove signature info."""
|
||||
if isinstance(data, Variant):
|
||||
return DBus.remove_dbus_signature(data.value)
|
||||
elif isinstance(data, dict):
|
||||
for k in data:
|
||||
data[k] = DBus.remove_dbus_signature(data[k])
|
||||
return data
|
||||
elif isinstance(data, list):
|
||||
new_list = []
|
||||
for item in data:
|
||||
new_list.append(DBus.remove_dbus_signature(item))
|
||||
return new_list
|
||||
else:
|
||||
return data
|
||||
|
||||
def _add_interfaces(self, introspection: Any):
|
||||
# Read available methods
|
||||
for interface in introspection.interfaces:
|
||||
interface_name = interface.name
|
||||
@staticmethod
|
||||
def from_dbus_error(err: DBusError) -> HassioNotSupportedError | DBusError:
|
||||
"""Return correct dbus error based on type."""
|
||||
if err.type in {ErrorType.SERVICE_UNKNOWN, ErrorType.UNKNOWN_INTERFACE}:
|
||||
return DBusInterfaceError(err.text)
|
||||
if err.type in {
|
||||
ErrorType.UNKNOWN_METHOD,
|
||||
ErrorType.INVALID_SIGNATURE,
|
||||
ErrorType.INVALID_ARGS,
|
||||
}:
|
||||
return DBusInterfaceMethodError(err.text)
|
||||
if err.type == ErrorType.UNKNOWN_OBJECT:
|
||||
return DBusObjectError(err.text)
|
||||
if err.type in {ErrorType.UNKNOWN_PROPERTY, ErrorType.PROPERTY_READ_ONLY}:
|
||||
return DBusInterfacePropertyError(err.text)
|
||||
if err.type == ErrorType.DISCONNECTED:
|
||||
return DBusNotConnectedError(err.text)
|
||||
if err.type == ErrorType.TIMEOUT:
|
||||
return DBusTimeoutError(err.text)
|
||||
return DBusFatalError(err.text)
|
||||
|
||||
# Methods
|
||||
for method in interface.methods:
|
||||
method_name = method.name
|
||||
self.methods.add(f"{interface_name}.{method_name}")
|
||||
@staticmethod
|
||||
async def call_dbus(
|
||||
proxy_interface: ProxyInterface,
|
||||
method: str,
|
||||
*args,
|
||||
remove_signature: bool = True,
|
||||
) -> Any:
|
||||
"""Call a dbus method and handle the signature and errors."""
|
||||
_LOGGER.debug(
|
||||
"D-Bus call - %s.%s on %s",
|
||||
proxy_interface.introspection.name,
|
||||
method,
|
||||
proxy_interface.path,
|
||||
)
|
||||
try:
|
||||
body = await getattr(proxy_interface, method)(*args)
|
||||
return DBus.remove_dbus_signature(body) if remove_signature else body
|
||||
except DBusError as err:
|
||||
raise DBus.from_dbus_error(err)
|
||||
|
||||
# Signals
|
||||
for signal in interface.signals:
|
||||
signal_name = signal.name
|
||||
self.signals.add(f"{interface_name}.{signal_name}")
|
||||
def _add_interfaces(self):
|
||||
"""Make proxy interfaces out of introspection data."""
|
||||
self._proxies = {
|
||||
interface.name: self._proxy_obj.get_interface(interface.name)
|
||||
for interface in self._proxy_obj.introspection.interfaces
|
||||
}
|
||||
|
||||
async def _init_proxy(self) -> None:
|
||||
"""Read interface data."""
|
||||
# Wait for dbus object to be available after restart
|
||||
introspection: Node | None = None
|
||||
|
||||
for _ in range(3):
|
||||
@ -112,93 +145,21 @@ class DBus:
|
||||
"Could not get introspection data after 3 attempts", _LOGGER.error
|
||||
)
|
||||
|
||||
self._add_interfaces(introspection)
|
||||
|
||||
def _prepare_args(self, *args: list[Any]) -> tuple[str, list[Any]]:
|
||||
signature = ""
|
||||
arg_list = []
|
||||
|
||||
for arg in args:
|
||||
_LOGGER.debug("...arg %s (type %s)", str(arg), type(arg))
|
||||
if isinstance(arg, bool):
|
||||
signature += "b"
|
||||
arg_list.append(arg)
|
||||
elif isinstance(arg, int):
|
||||
signature += "i"
|
||||
arg_list.append(arg)
|
||||
elif isinstance(arg, float):
|
||||
signature += "d"
|
||||
arg_list.append(arg)
|
||||
elif isinstance(arg, str):
|
||||
signature += "s"
|
||||
arg_list.append(arg)
|
||||
elif isinstance(arg, tuple):
|
||||
signature += arg[0]
|
||||
arg_list.append(arg[1])
|
||||
else:
|
||||
raise DBusFatalError(f"Type {type(arg)} not supported")
|
||||
|
||||
return signature, arg_list
|
||||
|
||||
async def call_dbus(
|
||||
self, method: str, *args: list[Any], remove_signature: bool = True
|
||||
) -> str:
|
||||
"""Call a dbus method."""
|
||||
method_parts = method.split(".")
|
||||
|
||||
signature, arg_list = self._prepare_args(*args)
|
||||
|
||||
_LOGGER.debug("Call %s on %s", method, self.object_path)
|
||||
reply = await self._bus.call(
|
||||
Message(
|
||||
destination=self.bus_name,
|
||||
path=self.object_path,
|
||||
interface=".".join(method_parts[:-1]),
|
||||
member=method_parts[-1],
|
||||
signature=signature,
|
||||
body=arg_list,
|
||||
)
|
||||
self._proxy_obj = self.bus.get_proxy_object(
|
||||
self.bus_name, self.object_path, introspection
|
||||
)
|
||||
self._add_interfaces()
|
||||
|
||||
if reply.message_type == MessageType.ERROR:
|
||||
if reply.error_name == "org.freedesktop.DBus.Error.ServiceUnknown":
|
||||
raise DBusInterfaceError(reply.body[0])
|
||||
if reply.error_name == "org.freedesktop.DBus.Error.UnknownMethod":
|
||||
raise DBusInterfaceMethodError(reply.body[0])
|
||||
if reply.error_name == "org.freedesktop.DBus.Error.Disconnected":
|
||||
raise DBusNotConnectedError()
|
||||
if reply.body and len(reply.body) > 0:
|
||||
raise DBusFatalError(reply.body[0])
|
||||
raise DBusFatalError()
|
||||
|
||||
if remove_signature:
|
||||
return _remove_dbus_signature(reply.body)
|
||||
return reply.body
|
||||
@property
|
||||
def bus(self) -> MessageBus:
|
||||
"""Get message bus."""
|
||||
return self._bus
|
||||
|
||||
async def get_properties(self, interface: str) -> dict[str, Any]:
|
||||
"""Read all properties from interface."""
|
||||
try:
|
||||
return (await self.call_dbus(DBUS_METHOD_GETALL, interface))[0]
|
||||
except IndexError as err:
|
||||
_LOGGER.error("No attributes returned for %s", interface)
|
||||
raise DBusFatalError() from err
|
||||
|
||||
async def get_property(self, interface: str, name: str) -> Any:
|
||||
"""Read value of single property from interface."""
|
||||
try:
|
||||
return (await self.call_dbus(DBUS_METHOD_GET, interface, name))[0]
|
||||
except IndexError as err:
|
||||
_LOGGER.error("No attribute returned for %s on %s", name, interface)
|
||||
raise DBusFatalError() from err
|
||||
|
||||
async def set_property(
|
||||
self,
|
||||
interface: str,
|
||||
name: str,
|
||||
value: Any,
|
||||
) -> list[Any] | dict[str, Any] | None:
|
||||
"""Set a property from interface."""
|
||||
return await self.call_dbus(DBUS_METHOD_SET, interface, name, value)
|
||||
return await DBus.call_dbus(
|
||||
self._proxies[DBUS_INTERFACE_PROPERTIES], "call_get_all", interface
|
||||
)
|
||||
|
||||
def signal(self, signal_member: str) -> DBusSignalWrapper:
|
||||
"""Get signal context manager for this object."""
|
||||
@ -216,29 +177,52 @@ class DBusCallWrapper:
|
||||
"""Initialize wrapper."""
|
||||
self.dbus: DBus = dbus
|
||||
self.interface: str = interface
|
||||
self._proxy: ProxyInterface | None = self.dbus._proxies.get(self.interface)
|
||||
|
||||
def __call__(self) -> None:
|
||||
"""Catch this method from being called."""
|
||||
_LOGGER.error("D-Bus method %s not exists!", self.interface)
|
||||
raise DBusInterfaceMethodError()
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
def __getattr__(self, name: str) -> Awaitable | Callable:
|
||||
"""Map to dbus method."""
|
||||
interface = f"{self.interface}.{name}"
|
||||
if not self._proxy:
|
||||
return DBusCallWrapper(self.dbus, f"{self.interface}.{name}")
|
||||
|
||||
if interface not in self.dbus.methods:
|
||||
return DBusCallWrapper(self.dbus, interface)
|
||||
dbus_type = name.split("_", 1)[0]
|
||||
|
||||
def _method_wrapper(*args, remove_signature: bool = True):
|
||||
"""Wrap method.
|
||||
if not hasattr(self._proxy, name):
|
||||
message = f"{name} does not exist in D-Bus interface {self.interface}!"
|
||||
if dbus_type == "call":
|
||||
raise DBusInterfaceMethodError(message, _LOGGER.error)
|
||||
if dbus_type == "get":
|
||||
raise DBusInterfacePropertyError(message, _LOGGER.error)
|
||||
if dbus_type == "set":
|
||||
raise DBusInterfacePropertyError(message, _LOGGER.error)
|
||||
if dbus_type in ["on", "off"]:
|
||||
raise DBusInterfaceSignalError(message, _LOGGER.error)
|
||||
|
||||
Return a coroutine
|
||||
"""
|
||||
return self.dbus.call_dbus(
|
||||
interface, *args, remove_signature=remove_signature
|
||||
# Not much can be done with these currently. *args callbacks aren't supported so can't wrap it
|
||||
if dbus_type in ["on", "off"]:
|
||||
_LOGGER.debug(
|
||||
"D-Bus signal monitor - %s.%s on %s",
|
||||
self.interface,
|
||||
name,
|
||||
self.dbus.object_path,
|
||||
)
|
||||
return self._method
|
||||
|
||||
return _method_wrapper
|
||||
if dbus_type in ["call", "get", "set"]:
|
||||
|
||||
def _method_wrapper(*args, remove_signature: bool = True) -> Awaitable:
|
||||
return DBus.call_dbus(
|
||||
self._proxy, name, *args, remove_signature=remove_signature
|
||||
)
|
||||
|
||||
return _method_wrapper
|
||||
|
||||
# Didn't reach the dbus call yet, just happened to hit another interface. Return a wrapper
|
||||
return DBusCallWrapper(self.dbus, f"{self.interface}.{name}")
|
||||
|
||||
|
||||
class DBusSignalWrapper:
|
||||
|
@ -201,19 +201,15 @@ async def test_api_network_wireless_scan(api_client):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_network_reload(api_client, coresys):
|
||||
async def test_api_network_reload(api_client, coresys, dbus: list[str]):
|
||||
"""Test network manager reload api."""
|
||||
with patch.object(type(coresys.dbus.network.dbus), "call_dbus") as call_dbus:
|
||||
resp = await api_client.post("/network/reload")
|
||||
result = await resp.json()
|
||||
dbus.clear()
|
||||
resp = await api_client.post("/network/reload")
|
||||
result = await resp.json()
|
||||
|
||||
assert result["result"] == "ok"
|
||||
assert (
|
||||
call_dbus.call_args_list[0][0][0]
|
||||
== "org.freedesktop.NetworkManager.Settings.Connection.GetSettings"
|
||||
)
|
||||
# Check that we forced NM to do an immediate connectivity check
|
||||
assert (
|
||||
call_dbus.call_args_list[1][0][0]
|
||||
== "org.freedesktop.NetworkManager.CheckConnectivity"
|
||||
)
|
||||
assert result["result"] == "ok"
|
||||
# Check that we forced NM to do an immediate connectivity check
|
||||
assert (
|
||||
"/org/freedesktop/NetworkManager-org.freedesktop.NetworkManager.CheckConnectivity"
|
||||
in dbus
|
||||
)
|
||||
|
@ -3,14 +3,14 @@ from functools import partial
|
||||
from inspect import unwrap
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
from aiohttp import web
|
||||
from awesomeversion import AwesomeVersion
|
||||
from dbus_next import introspection as intr
|
||||
from dbus_next.aio.message_bus import MessageBus
|
||||
from dbus_next.aio.proxy_object import ProxyInterface, ProxyObject
|
||||
from dbus_next.introspection import Method, Property, Signal
|
||||
import pytest
|
||||
from securetar import SecureTarFile
|
||||
|
||||
@ -53,7 +53,7 @@ from supervisor.docker.manager import DockerAPI
|
||||
from supervisor.docker.monitor import DockerMonitor
|
||||
from supervisor.store.addon import AddonStore
|
||||
from supervisor.store.repository import Repository
|
||||
from supervisor.utils.dbus import DBus
|
||||
from supervisor.utils.dbus import DBUS_INTERFACE_PROPERTIES, DBus
|
||||
from supervisor.utils.dt import utcnow
|
||||
|
||||
from .common import exists_fixture, load_fixture, load_json_fixture
|
||||
@ -103,10 +103,39 @@ def docker() -> DockerAPI:
|
||||
yield docker_obj
|
||||
|
||||
|
||||
def _get_dbus_name(intr_list: list[Method | Property | Signal], snake_case: str) -> str:
|
||||
"""Find name in introspection list, fallback to ignore case match."""
|
||||
name = "".join([part.capitalize() for part in snake_case.split("_")])
|
||||
names = [item.name for item in intr_list]
|
||||
if name in names:
|
||||
return name
|
||||
|
||||
# Acronyms like NTP can't be easily converted back to camel case. Fallback to ignore case match
|
||||
lower_name = name.lower()
|
||||
for val in names:
|
||||
if lower_name == val.lower():
|
||||
return val
|
||||
|
||||
raise AttributeError(f"Could not find match for {name} in D-Bus introspection!")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def dbus_bus() -> MessageBus:
|
||||
"""Message bus mock."""
|
||||
yield AsyncMock(spec=MessageBus)
|
||||
bus = AsyncMock(spec=MessageBus)
|
||||
setattr(bus, "_name_owners", {})
|
||||
yield bus
|
||||
|
||||
|
||||
def mock_get_properties(object_path: str, interface: str) -> str:
|
||||
"""Mock get dbus properties."""
|
||||
latest = object_path.split("/")[-1]
|
||||
fixture = interface.replace(".", "_")
|
||||
|
||||
if latest.isnumeric():
|
||||
fixture = f"{fixture}_{latest}"
|
||||
|
||||
return load_json_fixture(f"{fixture}.json")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -114,20 +143,6 @@ def dbus(dbus_bus: MessageBus) -> DBus:
|
||||
"""Mock DBUS."""
|
||||
dbus_commands = []
|
||||
|
||||
async def mock_get_properties(dbus_obj, interface):
|
||||
latest = dbus_obj.object_path.split("/")[-1]
|
||||
fixture = interface.replace(".", "_")
|
||||
|
||||
if latest.isnumeric():
|
||||
fixture = f"{fixture}_{latest}"
|
||||
|
||||
return load_json_fixture(f"{fixture}.json")
|
||||
|
||||
async def mock_get_property(dbus_obj, interface, name):
|
||||
dbus_commands.append(f"{dbus_obj.object_path}-{interface}.{name}")
|
||||
properties = await mock_get_properties(dbus_obj, interface)
|
||||
return properties[name]
|
||||
|
||||
async def mock_wait_for_signal(self):
|
||||
if (
|
||||
self._interface + "." + self._member
|
||||
@ -161,41 +176,69 @@ def dbus(dbus_bus: MessageBus) -> DBus:
|
||||
fixture = f"{fixture}_~"
|
||||
|
||||
# Use dbus-next infrastructure to parse introspection xml
|
||||
node = intr.Node.parse(load_fixture(f"{fixture}.{filetype}"))
|
||||
self._add_interfaces(node)
|
||||
self._proxy_obj = ProxyObject(
|
||||
self.bus_name,
|
||||
self.object_path,
|
||||
load_fixture(f"{fixture}.{filetype}"),
|
||||
dbus_bus,
|
||||
)
|
||||
self._add_interfaces()
|
||||
|
||||
async def mock_call_dbus(
|
||||
self, method: str, *args: list[Any], remove_signature: bool = True
|
||||
proxy_interface: ProxyInterface,
|
||||
method: str,
|
||||
*args,
|
||||
remove_signature: bool = True,
|
||||
):
|
||||
if self.object_path != DBUS_OBJECT_BASE:
|
||||
fixture = self.object_path.replace("/", "_")[1:]
|
||||
fixture = f"{fixture}-{method.split('.')[-1]}"
|
||||
else:
|
||||
fixture = method.replace(".", "_")
|
||||
if (
|
||||
proxy_interface.introspection.name == DBUS_INTERFACE_PROPERTIES
|
||||
and method == "call_get_all"
|
||||
):
|
||||
return mock_get_properties(proxy_interface.path, args[0])
|
||||
|
||||
dbus_commands.append(f"{self.object_path}-{method}")
|
||||
[dbus_type, dbus_name] = method.split("_", 1)
|
||||
|
||||
if dbus_type in ["get", "set"]:
|
||||
dbus_name = _get_dbus_name(
|
||||
proxy_interface.introspection.properties, dbus_name
|
||||
)
|
||||
dbus_commands.append(
|
||||
f"{proxy_interface.path}-{proxy_interface.introspection.name}.{dbus_name}"
|
||||
)
|
||||
|
||||
if dbus_type == "set":
|
||||
return
|
||||
|
||||
return mock_get_properties(
|
||||
proxy_interface.path, proxy_interface.introspection.name
|
||||
)[dbus_name]
|
||||
|
||||
dbus_name = _get_dbus_name(proxy_interface.introspection.methods, dbus_name)
|
||||
dbus_commands.append(
|
||||
f"{proxy_interface.path}-{proxy_interface.introspection.name}.{dbus_name}"
|
||||
)
|
||||
|
||||
if proxy_interface.path != DBUS_OBJECT_BASE:
|
||||
fixture = proxy_interface.path.replace("/", "_")[1:]
|
||||
fixture = f"{fixture}-{dbus_name}"
|
||||
else:
|
||||
fixture = (
|
||||
f'{proxy_interface.introspection.name.replace(".", "_")}_{dbus_name}'
|
||||
)
|
||||
|
||||
if exists_fixture(f"{fixture}.json"):
|
||||
return load_json_fixture(f"{fixture}.json")
|
||||
|
||||
return []
|
||||
|
||||
with patch("supervisor.utils.dbus.DBus.call_dbus", new=mock_call_dbus), patch(
|
||||
"supervisor.dbus.interface.DBusInterface.is_connected",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"supervisor.utils.dbus.DBus.get_properties", new=mock_get_properties
|
||||
), patch(
|
||||
"supervisor.utils.dbus.DBus._init_proxy", new=mock_init_proxy
|
||||
), patch(
|
||||
), patch("supervisor.utils.dbus.DBus._init_proxy", new=mock_init_proxy), patch(
|
||||
"supervisor.utils.dbus.DBusSignalWrapper.__aenter__", new=mock_signal___aenter__
|
||||
), patch(
|
||||
"supervisor.utils.dbus.DBusSignalWrapper.__aexit__", new=mock_signal___aexit__
|
||||
), patch(
|
||||
"supervisor.utils.dbus.DBusSignalWrapper.wait_for_signal",
|
||||
new=mock_wait_for_signal,
|
||||
), patch(
|
||||
"supervisor.utils.dbus.DBus.get_property", new=mock_get_property
|
||||
), patch(
|
||||
"supervisor.dbus.manager.MessageBus.connect", return_value=dbus_bus
|
||||
):
|
||||
|
@ -2,12 +2,14 @@
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from dbus_next.aio.proxy_object import ProxyInterface
|
||||
from dbus_next.signature import Variant
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.dbus.network.setting.generate import get_connection_from_interface
|
||||
from supervisor.host.const import InterfaceMethod
|
||||
from supervisor.host.network import Interface
|
||||
from supervisor.utils.dbus import DBus
|
||||
|
||||
from tests.const import TEST_INTERFACE
|
||||
|
||||
@ -68,19 +70,14 @@ SETTINGS_WITH_SIGNATURE = {
|
||||
|
||||
|
||||
async def mock_call_dbus_get_settings_signature(
|
||||
method: str, *args: list[Any], remove_signature: bool = True
|
||||
_: ProxyInterface, method: str, *args, remove_signature: bool = True
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Call dbus method mock for get settings that keeps signature."""
|
||||
if (
|
||||
method == "org.freedesktop.NetworkManager.Settings.Connection.GetSettings"
|
||||
and not remove_signature
|
||||
):
|
||||
return [SETTINGS_WITH_SIGNATURE]
|
||||
if method == "call_get_settings" and not remove_signature:
|
||||
return SETTINGS_WITH_SIGNATURE
|
||||
else:
|
||||
assert method == "org.freedesktop.NetworkManager.Settings.Connection.Update"
|
||||
assert len(args[0]) == 2
|
||||
assert args[0][0] == "a{sa{sv}}"
|
||||
settings = args[0][1]
|
||||
assert method == "call_update"
|
||||
settings = args[0]
|
||||
|
||||
assert "connection" in settings
|
||||
assert settings["connection"]["id"] == Variant("s", "Supervisor eth0")
|
||||
@ -145,7 +142,7 @@ async def test_update(coresys: CoreSys):
|
||||
)
|
||||
|
||||
with patch.object(
|
||||
coresys.dbus.network.interfaces[TEST_INTERFACE].settings.dbus,
|
||||
DBus,
|
||||
"call_dbus",
|
||||
new=mock_call_dbus_get_settings_signature,
|
||||
):
|
||||
|
@ -1,110 +1,108 @@
|
||||
[
|
||||
[
|
||||
[
|
||||
"kernel.0",
|
||||
{
|
||||
"activated.count": 9,
|
||||
"activated.timestamp": "2022-08-23T21:03:22Z",
|
||||
"boot-status": "good",
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "c624db648b8401fae37ee5bb1a6ec90bdf4183aef364b33314a73c7198e49d5b",
|
||||
"state": "inactive",
|
||||
"size": 10371072,
|
||||
"installed.count": 9,
|
||||
"class": "kernel",
|
||||
"device": "/dev/disk/by-partlabel/hassos-kernel0",
|
||||
"type": "raw",
|
||||
"bootname": "A",
|
||||
"bundle.version": "9.0.dev20220818",
|
||||
"installed.timestamp": "2022-08-23T21:03:16Z",
|
||||
"status": "ok"
|
||||
}
|
||||
],
|
||||
[
|
||||
"boot.0",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "a5019b335f33be2cf89c96bb2d0695030adb72c1d13d650a5bbe1806dd76d6cc",
|
||||
"state": "inactive",
|
||||
"size": 25165824,
|
||||
"installed.count": 19,
|
||||
"class": "boot",
|
||||
"device": "/dev/disk/by-partlabel/hassos-boot",
|
||||
"type": "vfat",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:46Z"
|
||||
}
|
||||
],
|
||||
[
|
||||
"rootfs.0",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"parent": "kernel.0",
|
||||
"state": "inactive",
|
||||
"size": 117456896,
|
||||
"sha256": "7d908b4d578d072b1b0f75de8250fd97b6e119bff09518a96fffd6e4aec61721",
|
||||
"class": "rootfs",
|
||||
"device": "/dev/disk/by-partlabel/hassos-system0",
|
||||
"type": "raw",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220818",
|
||||
"installed.timestamp": "2022-08-23T21:03:21Z",
|
||||
"installed.count": 9
|
||||
}
|
||||
],
|
||||
[
|
||||
"spl.0",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "9856a94df1d6abbc672adaf95746ec76abd3a8191f9d08288add6bb39e63ef45",
|
||||
"state": "inactive",
|
||||
"size": 8388608,
|
||||
"installed.count": 19,
|
||||
"class": "spl",
|
||||
"device": "/dev/disk/by-partlabel/hassos-boot",
|
||||
"type": "raw",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:51Z"
|
||||
}
|
||||
],
|
||||
[
|
||||
"kernel.1",
|
||||
{
|
||||
"activated.count": 10,
|
||||
"activated.timestamp": "2022-08-25T21:11:52Z",
|
||||
"boot-status": "good",
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "f57e354b8bd518022721e71fafaf278972af966d8f6cbefb4610db13785801c8",
|
||||
"state": "booted",
|
||||
"size": 10371072,
|
||||
"installed.count": 10,
|
||||
"class": "kernel",
|
||||
"device": "/dev/disk/by-partlabel/hassos-kernel1",
|
||||
"type": "raw",
|
||||
"bootname": "B",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:46Z",
|
||||
"status": "ok"
|
||||
}
|
||||
],
|
||||
[
|
||||
"rootfs.1",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"parent": "kernel.1",
|
||||
"state": "active",
|
||||
"size": 117456896,
|
||||
"sha256": "55936b64d391954ae1aed24dd1460e191e021e78655470051fa7939d12fff68a",
|
||||
"class": "rootfs",
|
||||
"device": "/dev/disk/by-partlabel/hassos-system1",
|
||||
"type": "raw",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:51Z",
|
||||
"installed.count": 10
|
||||
}
|
||||
]
|
||||
"kernel.0",
|
||||
{
|
||||
"activated.count": 9,
|
||||
"activated.timestamp": "2022-08-23T21:03:22Z",
|
||||
"boot-status": "good",
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "c624db648b8401fae37ee5bb1a6ec90bdf4183aef364b33314a73c7198e49d5b",
|
||||
"state": "inactive",
|
||||
"size": 10371072,
|
||||
"installed.count": 9,
|
||||
"class": "kernel",
|
||||
"device": "/dev/disk/by-partlabel/hassos-kernel0",
|
||||
"type": "raw",
|
||||
"bootname": "A",
|
||||
"bundle.version": "9.0.dev20220818",
|
||||
"installed.timestamp": "2022-08-23T21:03:16Z",
|
||||
"status": "ok"
|
||||
}
|
||||
],
|
||||
[
|
||||
"boot.0",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "a5019b335f33be2cf89c96bb2d0695030adb72c1d13d650a5bbe1806dd76d6cc",
|
||||
"state": "inactive",
|
||||
"size": 25165824,
|
||||
"installed.count": 19,
|
||||
"class": "boot",
|
||||
"device": "/dev/disk/by-partlabel/hassos-boot",
|
||||
"type": "vfat",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:46Z"
|
||||
}
|
||||
],
|
||||
[
|
||||
"rootfs.0",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"parent": "kernel.0",
|
||||
"state": "inactive",
|
||||
"size": 117456896,
|
||||
"sha256": "7d908b4d578d072b1b0f75de8250fd97b6e119bff09518a96fffd6e4aec61721",
|
||||
"class": "rootfs",
|
||||
"device": "/dev/disk/by-partlabel/hassos-system0",
|
||||
"type": "raw",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220818",
|
||||
"installed.timestamp": "2022-08-23T21:03:21Z",
|
||||
"installed.count": 9
|
||||
}
|
||||
],
|
||||
[
|
||||
"spl.0",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "9856a94df1d6abbc672adaf95746ec76abd3a8191f9d08288add6bb39e63ef45",
|
||||
"state": "inactive",
|
||||
"size": 8388608,
|
||||
"installed.count": 19,
|
||||
"class": "spl",
|
||||
"device": "/dev/disk/by-partlabel/hassos-boot",
|
||||
"type": "raw",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:51Z"
|
||||
}
|
||||
],
|
||||
[
|
||||
"kernel.1",
|
||||
{
|
||||
"activated.count": 10,
|
||||
"activated.timestamp": "2022-08-25T21:11:52Z",
|
||||
"boot-status": "good",
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"sha256": "f57e354b8bd518022721e71fafaf278972af966d8f6cbefb4610db13785801c8",
|
||||
"state": "booted",
|
||||
"size": 10371072,
|
||||
"installed.count": 10,
|
||||
"class": "kernel",
|
||||
"device": "/dev/disk/by-partlabel/hassos-kernel1",
|
||||
"type": "raw",
|
||||
"bootname": "B",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:46Z",
|
||||
"status": "ok"
|
||||
}
|
||||
],
|
||||
[
|
||||
"rootfs.1",
|
||||
{
|
||||
"bundle.compatible": "haos-odroid-n2",
|
||||
"parent": "kernel.1",
|
||||
"state": "active",
|
||||
"size": 117456896,
|
||||
"sha256": "55936b64d391954ae1aed24dd1460e191e021e78655470051fa7939d12fff68a",
|
||||
"class": "rootfs",
|
||||
"device": "/dev/disk/by-partlabel/hassos-system1",
|
||||
"type": "raw",
|
||||
"status": "ok",
|
||||
"bundle.version": "9.0.dev20220824",
|
||||
"installed.timestamp": "2022-08-25T21:11:51Z",
|
||||
"installed.count": 10
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@ -1 +1 @@
|
||||
[true]
|
||||
true
|
||||
|
@ -1 +1 @@
|
||||
[true]
|
||||
true
|
||||
|
@ -1 +1 @@
|
||||
[true]
|
||||
true
|
||||
|
@ -1 +1 @@
|
||||
[true]
|
||||
true
|
||||
|
@ -1 +1 @@
|
||||
[true]
|
||||
true
|
||||
|
@ -1 +1 @@
|
||||
[true]
|
||||
true
|
||||
|
@ -1 +1 @@
|
||||
["/org/freedesktop/NetworkManager/ActiveConnection/1"]
|
||||
"/org/freedesktop/NetworkManager/ActiveConnection/1"
|
||||
|
@ -1 +1 @@
|
||||
[4]
|
||||
4
|
||||
|
@ -1 +1,4 @@
|
||||
[["/org/freedesktop/NetworkManager/AccessPoint/43099", "/org/freedesktop/NetworkManager/AccessPoint/43100"]]
|
||||
[
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/43099",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/43100"
|
||||
]
|
||||
|
@ -1 +1 @@
|
||||
["/org/freedesktop/NetworkManager/Settings/1"]
|
||||
"/org/freedesktop/NetworkManager/Settings/1"
|
||||
|
@ -1 +1 @@
|
||||
[true]
|
||||
true
|
||||
|
@ -1,41 +1,39 @@
|
||||
[
|
||||
{
|
||||
"connection": {
|
||||
"id": "Wired connection 1",
|
||||
"interface-name": "eth0",
|
||||
"permissions": [],
|
||||
"timestamp": 1598125548,
|
||||
"type": "802-3-ethernet",
|
||||
"uuid": "0c23631e-2118-355c-bbb0-8943229cb0d6"
|
||||
},
|
||||
"ipv4": {
|
||||
"address-data": [{ "address": "192.168.2.148", "prefix": 24 }],
|
||||
"addresses": [[2483202240, 24, 16951488]],
|
||||
"dns": [16951488],
|
||||
"dns-search": [],
|
||||
"gateway": "192.168.2.1",
|
||||
"method": "auto",
|
||||
"route-data": [
|
||||
{ "dest": "192.168.122.0", "prefix": 24, "next-hop": "10.10.10.1" }
|
||||
],
|
||||
"routes": [[8038592, 24, 17435146, 0]]
|
||||
},
|
||||
"ipv6": {
|
||||
"address-data": [],
|
||||
"addresses": [],
|
||||
"dns": [],
|
||||
"dns-search": [],
|
||||
"method": "auto",
|
||||
"route-data": [],
|
||||
"routes": [],
|
||||
"addr-gen-mode": 0
|
||||
},
|
||||
"proxy": {},
|
||||
"802-3-ethernet": {
|
||||
"auto-negotiate": false,
|
||||
"mac-address-blacklist": [],
|
||||
"s390-options": {}
|
||||
},
|
||||
"802-11-wireless": { "ssid": [78, 69, 84, 84] }
|
||||
}
|
||||
]
|
||||
{
|
||||
"connection": {
|
||||
"id": "Wired connection 1",
|
||||
"interface-name": "eth0",
|
||||
"permissions": [],
|
||||
"timestamp": 1598125548,
|
||||
"type": "802-3-ethernet",
|
||||
"uuid": "0c23631e-2118-355c-bbb0-8943229cb0d6"
|
||||
},
|
||||
"ipv4": {
|
||||
"address-data": [{ "address": "192.168.2.148", "prefix": 24 }],
|
||||
"addresses": [[2483202240, 24, 16951488]],
|
||||
"dns": [16951488],
|
||||
"dns-search": [],
|
||||
"gateway": "192.168.2.1",
|
||||
"method": "auto",
|
||||
"route-data": [
|
||||
{ "dest": "192.168.122.0", "prefix": 24, "next-hop": "10.10.10.1" }
|
||||
],
|
||||
"routes": [[8038592, 24, 17435146, 0]]
|
||||
},
|
||||
"ipv6": {
|
||||
"address-data": [],
|
||||
"addresses": [],
|
||||
"dns": [],
|
||||
"dns-search": [],
|
||||
"method": "auto",
|
||||
"route-data": [],
|
||||
"routes": [],
|
||||
"addr-gen-mode": 0
|
||||
},
|
||||
"proxy": {},
|
||||
"802-3-ethernet": {
|
||||
"auto-negotiate": false,
|
||||
"mac-address-blacklist": [],
|
||||
"s390-options": {}
|
||||
},
|
||||
"802-11-wireless": { "ssid": [78, 69, 84, 84] }
|
||||
}
|
||||
|
@ -1,52 +1,50 @@
|
||||
[
|
||||
[
|
||||
[
|
||||
"etc-machine\\x2did.mount",
|
||||
"/etc/machine-id",
|
||||
"loaded",
|
||||
"active",
|
||||
"mounted",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/etc_2dmachine_5cx2did_2emount",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
],
|
||||
[
|
||||
"firewalld.service",
|
||||
"firewalld.service",
|
||||
"not-found",
|
||||
"inactive",
|
||||
"dead",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/firewalld_2eservice",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
],
|
||||
[
|
||||
"sys-devices-virtual-tty-ttypd.device",
|
||||
"/sys/devices/virtual/tty/ttypd",
|
||||
"loaded",
|
||||
"active",
|
||||
"plugged",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dtty_2dttypd_2edevice",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
],
|
||||
[
|
||||
"zram-swap.service",
|
||||
"HassOS ZRAM swap",
|
||||
"loaded",
|
||||
"active",
|
||||
"exited",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/zram_2dswap_2eservice",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
]
|
||||
"etc-machine\\x2did.mount",
|
||||
"/etc/machine-id",
|
||||
"loaded",
|
||||
"active",
|
||||
"mounted",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/etc_2dmachine_5cx2did_2emount",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
],
|
||||
[
|
||||
"firewalld.service",
|
||||
"firewalld.service",
|
||||
"not-found",
|
||||
"inactive",
|
||||
"dead",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/firewalld_2eservice",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
],
|
||||
[
|
||||
"sys-devices-virtual-tty-ttypd.device",
|
||||
"/sys/devices/virtual/tty/ttypd",
|
||||
"loaded",
|
||||
"active",
|
||||
"plugged",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dtty_2dttypd_2edevice",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
],
|
||||
[
|
||||
"zram-swap.service",
|
||||
"HassOS ZRAM swap",
|
||||
"loaded",
|
||||
"active",
|
||||
"exited",
|
||||
"",
|
||||
"/org/freedesktop/systemd1/unit/zram_2dswap_2eservice",
|
||||
0,
|
||||
"",
|
||||
"/"
|
||||
]
|
||||
]
|
||||
|
@ -1 +1 @@
|
||||
["/org/freedesktop/systemd1/job/7623"]
|
||||
"/org/freedesktop/systemd1/job/7623"
|
||||
|
@ -1 +1 @@
|
||||
["/org/freedesktop/systemd1/job/7623"]
|
||||
"/org/freedesktop/systemd1/job/7623"
|
||||
|
@ -1 +1 @@
|
||||
["/org/freedesktop/systemd1/job/7623"]
|
||||
"/org/freedesktop/systemd1/job/7623"
|
||||
|
@ -1 +1 @@
|
||||
["/org/freedesktop/systemd1/job/7623"]
|
||||
"/org/freedesktop/systemd1/job/7623"
|
||||
|
@ -10,33 +10,29 @@ from supervisor.coresys import CoreSys
|
||||
|
||||
async def test_connectivity_not_connected(coresys: CoreSys):
|
||||
"""Test host unknown connectivity."""
|
||||
with patch("supervisor.utils.dbus.DBus.get_property", return_value=0):
|
||||
with patch("supervisor.utils.dbus.DBus.call_dbus", return_value=0):
|
||||
await coresys.host.network.check_connectivity()
|
||||
assert not coresys.host.network.connectivity
|
||||
|
||||
with patch("supervisor.utils.dbus.DBus.call_dbus", return_value=[0]):
|
||||
await coresys.host.network.check_connectivity(force=True)
|
||||
assert not coresys.host.network.connectivity
|
||||
|
||||
|
||||
async def test_connectivity_connected(coresys: CoreSys):
|
||||
async def test_connectivity_connected(coresys: CoreSys, dbus: list[str]):
|
||||
"""Test host full connectivity."""
|
||||
# Variation on above since our default fixture for each of these returns 4
|
||||
with patch(
|
||||
"supervisor.utils.dbus.DBus.get_property", return_value=4
|
||||
) as get_property, patch(
|
||||
"supervisor.utils.dbus.DBus.call_dbus", return_value=[4]
|
||||
) as call_dbus:
|
||||
await coresys.host.network.check_connectivity()
|
||||
assert coresys.host.network.connectivity
|
||||
get_property.assert_called_once()
|
||||
call_dbus.assert_not_called()
|
||||
dbus.clear()
|
||||
await coresys.host.network.check_connectivity()
|
||||
assert coresys.host.network.connectivity
|
||||
assert dbus == [
|
||||
"/org/freedesktop/NetworkManager-org.freedesktop.NetworkManager.Connectivity"
|
||||
]
|
||||
|
||||
get_property.reset_mock()
|
||||
await coresys.host.network.check_connectivity(force=True)
|
||||
assert coresys.host.network.connectivity
|
||||
get_property.assert_not_called()
|
||||
call_dbus.assert_called_once()
|
||||
dbus.clear()
|
||||
await coresys.host.network.check_connectivity(force=True)
|
||||
assert coresys.host.network.connectivity
|
||||
assert dbus == [
|
||||
"/org/freedesktop/NetworkManager-org.freedesktop.NetworkManager.CheckConnectivity"
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("force", [True, False])
|
||||
|
@ -2,6 +2,7 @@
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from unittest.mock import Mock, PropertyMock, patch
|
||||
|
||||
from dbus_next.aio.proxy_object import ProxyInterface
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
@ -127,24 +128,34 @@ async def test_scan_wifi(coresys: CoreSys):
|
||||
async def test_scan_wifi_with_failures(coresys: CoreSys, caplog):
|
||||
"""Test scanning wifi with accesspoint processing failures."""
|
||||
# pylint: disable=protected-access
|
||||
init_proxy = coresys.dbus.network.dbus._init_proxy
|
||||
init_proxy = DBus._init_proxy
|
||||
call_dbus = DBus.call_dbus
|
||||
|
||||
async def mock_init_proxy(self):
|
||||
if self.object_path != "/org/freedesktop/NetworkManager/AccessPoint/99999":
|
||||
return await init_proxy()
|
||||
return await init_proxy(self)
|
||||
|
||||
raise DBusFatalError("Fail")
|
||||
|
||||
with patch("supervisor.host.network.asyncio.sleep"), patch.object(
|
||||
DBus,
|
||||
"call_dbus",
|
||||
return_value=[
|
||||
[
|
||||
async def mock_call_dbus(
|
||||
proxy_interface: ProxyInterface,
|
||||
method: str,
|
||||
*args,
|
||||
remove_signature: bool = True,
|
||||
):
|
||||
if method == "call_get_all_access_points":
|
||||
return [
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/43099",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/43100",
|
||||
"/org/freedesktop/NetworkManager/AccessPoint/99999",
|
||||
]
|
||||
],
|
||||
|
||||
return await call_dbus(
|
||||
proxy_interface, method, *args, remove_signature=remove_signature
|
||||
)
|
||||
|
||||
with patch("supervisor.host.network.asyncio.sleep"), patch(
|
||||
"supervisor.utils.dbus.DBus.call_dbus", new=mock_call_dbus
|
||||
), patch.object(DBus, "_init_proxy", new=mock_init_proxy):
|
||||
aps = await coresys.host.network.scan_wifi(coresys.host.network.get("wlan0"))
|
||||
assert len(aps) == 2
|
||||
|
@ -85,8 +85,8 @@ async def test_internet(
|
||||
mock_websession = AsyncMock()
|
||||
mock_websession.head.side_effect = head_side_effect
|
||||
coresys.supervisor.connectivity = None
|
||||
with patch.object(
|
||||
type(coresys.dbus.network.dbus), "get_property", return_value=connectivity
|
||||
with patch(
|
||||
"supervisor.utils.dbus.DBus.call_dbus", return_value=connectivity
|
||||
), patch.object(
|
||||
CoreSys, "websession", new=PropertyMock(return_value=mock_websession)
|
||||
):
|
||||
|
@ -1,32 +1,19 @@
|
||||
"""Check dbus-next implementation."""
|
||||
from dbus_next.signature import Variant
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.utils.dbus import DBus, _remove_dbus_signature
|
||||
from supervisor.utils.dbus import DBus
|
||||
|
||||
|
||||
def test_remove_dbus_signature():
|
||||
"""Check D-Bus signature clean-up."""
|
||||
test = _remove_dbus_signature(Variant("s", "Value"))
|
||||
test = DBus.remove_dbus_signature(Variant("s", "Value"))
|
||||
assert isinstance(test, str)
|
||||
assert test == "Value"
|
||||
|
||||
test_dict = _remove_dbus_signature({"Key": Variant("s", "Value")})
|
||||
test_dict = DBus.remove_dbus_signature({"Key": Variant("s", "Value")})
|
||||
assert isinstance(test_dict["Key"], str)
|
||||
assert test_dict["Key"] == "Value"
|
||||
|
||||
test_dict = _remove_dbus_signature([Variant("s", "Value")])
|
||||
test_dict = DBus.remove_dbus_signature([Variant("s", "Value")])
|
||||
assert isinstance(test_dict[0], str)
|
||||
assert test_dict[0] == "Value"
|
||||
|
||||
|
||||
async def test_dbus_prepare_args(coresys: CoreSys):
|
||||
"""Check D-Bus dynamic argument builder."""
|
||||
dbus = DBus(
|
||||
coresys.dbus.bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1"
|
||||
)
|
||||
# pylint: disable=protected-access
|
||||
signature, _ = dbus._prepare_args(
|
||||
True, 1, 1.0, "Value", ("a{sv}", {"Key": "Value"})
|
||||
)
|
||||
assert signature == "bidsa{sv}"
|
||||
|
Loading…
x
Reference in New Issue
Block a user