diff --git a/Dockerfile b/Dockerfile index 172a9ce18..95578a589 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,6 @@ RUN \ eudev \ eudev-libs \ git \ - glib \ libffi \ libpulse \ musl \ diff --git a/requirements.txt b/requirements.txt index 63d4610f2..a740e7b76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,3 +19,4 @@ pyudev==0.22.0 ruamel.yaml==0.15.100 sentry-sdk==1.4.3 voluptuous==0.12.2 +dbus-next==0.2.3 diff --git a/setup.py b/setup.py index f0a8d8b75..9f2bd2431 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ setup( "supervisor.api", "supervisor.backups", "supervisor.dbus.network", - "supervisor.dbus.payloads", + "supervisor.dbus.network.setting", "supervisor.dbus", "supervisor.discovery.services", "supervisor.discovery", diff --git a/supervisor/bootstrap.py b/supervisor/bootstrap.py index bb08b2d82..b31849234 100644 --- a/supervisor/bootstrap.py +++ b/supervisor/bootstrap.py @@ -2,7 +2,6 @@ import logging import os from pathlib import Path -import shutil import signal from colorlog import ColoredFormatter @@ -258,10 +257,6 @@ def check_environment() -> None: if not SOCKET_DOCKER.is_socket(): _LOGGER.critical("Can't find Docker socket!") - # check socat exec - if not shutil.which("gdbus"): - _LOGGER.critical("Can't find gdbus!") - def reg_signal(loop, coresys: CoreSys) -> None: """Register SIGTERM and SIGKILL to stop system.""" diff --git a/supervisor/dbus/agent/__init__.py b/supervisor/dbus/agent/__init__.py index b5694359f..81558922f 100644 --- a/supervisor/dbus/agent/__init__.py +++ b/supervisor/dbus/agent/__init__.py @@ -6,7 +6,7 @@ from typing import Any from awesomeversion import AwesomeVersion from ...exceptions import DBusError, DBusInterfaceError -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_DIAGNOSTICS, DBUS_ATTR_VERSION, diff --git a/supervisor/dbus/agent/apparmor.py b/supervisor/dbus/agent/apparmor.py index 749493cc0..90f862020 100644 --- a/supervisor/dbus/agent/apparmor.py +++ b/supervisor/dbus/agent/apparmor.py @@ -4,7 +4,7 @@ from typing import Any from awesomeversion import AwesomeVersion -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_PARSER_VERSION, DBUS_NAME_HAOS, diff --git a/supervisor/dbus/agent/cgroup.py b/supervisor/dbus/agent/cgroup.py index e51f225ad..b35e2a1c5 100644 --- a/supervisor/dbus/agent/cgroup.py +++ b/supervisor/dbus/agent/cgroup.py @@ -1,6 +1,6 @@ """CGroup object for OS-Agent.""" -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_CGROUP from ..interface import DBusInterface from ..utils import dbus_connected diff --git a/supervisor/dbus/agent/datadisk.py b/supervisor/dbus/agent/datadisk.py index 90ff3abf3..5991920b1 100644 --- a/supervisor/dbus/agent/datadisk.py +++ b/supervisor/dbus/agent/datadisk.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Any -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_CURRENT_DEVICE, DBUS_NAME_HAOS, diff --git a/supervisor/dbus/agent/system.py b/supervisor/dbus/agent/system.py index beddd5e00..5ed3600a7 100644 --- a/supervisor/dbus/agent/system.py +++ b/supervisor/dbus/agent/system.py @@ -1,6 +1,6 @@ """System object for OS-Agent.""" -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import DBUS_NAME_HAOS, DBUS_OBJECT_HAOS_SYSTEM from ..interface import DBusInterface from ..utils import dbus_connected diff --git a/supervisor/dbus/const.py b/supervisor/dbus/const.py index 301df0bb3..51ea28eb9 100644 --- a/supervisor/dbus/const.py +++ b/supervisor/dbus/const.py @@ -17,14 +17,14 @@ DBUS_NAME_IP6CONFIG = "org.freedesktop.NetworkManager.IP6Config" DBUS_NAME_LOGIND = "org.freedesktop.login1" DBUS_NAME_NM = "org.freedesktop.NetworkManager" DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED = ( - "org.freedesktop.NetworkManager.Connection.Active.PropertiesChanged" + "org.freedesktop.NetworkManager.Connection.Active.StateChanged" ) DBUS_NAME_RAUC = "de.pengutronix.rauc" DBUS_NAME_RAUC_INSTALLER = "de.pengutronix.rauc.Installer" DBUS_NAME_RAUC_INSTALLER_COMPLETED = "de.pengutronix.rauc.Installer.Completed" -DBUS_NAME_ROOT = "" DBUS_NAME_SETTINGS_CONNECTION = "org.freedesktop.NetworkManager.Settings.Connection" DBUS_NAME_SYSTEMD = "org.freedesktop.systemd1" +DBUS_NAME_SYSTEMD_MANAGER = "org.freedesktop.systemd1.Manager" DBUS_NAME_TIMEDATE = "org.freedesktop.timedate1" diff --git a/supervisor/dbus/hostname.py b/supervisor/dbus/hostname.py index b489d8251..0952db33c 100644 --- a/supervisor/dbus/hostname.py +++ b/supervisor/dbus/hostname.py @@ -3,7 +3,7 @@ import logging from typing import Any, Optional from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import ( DBUS_ATTR_CHASSIS, DBUS_ATTR_DEPLOYMENT, diff --git a/supervisor/dbus/interface.py b/supervisor/dbus/interface.py index 2af1dafdb..e22075cbb 100644 --- a/supervisor/dbus/interface.py +++ b/supervisor/dbus/interface.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from functools import wraps from typing import Any, Optional -from ..utils.gdbus import DBus +from ..utils.dbus import DBus def dbus_property(func): diff --git a/supervisor/dbus/logind.py b/supervisor/dbus/logind.py index 7e1bad92d..2053f8bc8 100644 --- a/supervisor/dbus/logind.py +++ b/supervisor/dbus/logind.py @@ -2,7 +2,7 @@ import logging from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import DBUS_NAME_LOGIND, DBUS_OBJECT_LOGIND from .interface import DBusInterface from .utils import dbus_connected diff --git a/supervisor/dbus/network/__init__.py b/supervisor/dbus/network/__init__.py index 3a58f79c0..dbfc3367c 100644 --- a/supervisor/dbus/network/__init__.py +++ b/supervisor/dbus/network/__init__.py @@ -7,11 +7,11 @@ import sentry_sdk from ...exceptions import ( DBusError, + DBusFatalError, DBusInterfaceError, - DBusProgramError, HostNotSupportedError, ) -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_CONNECTION_ENABLED, DBUS_ATTR_DEVICES, @@ -77,16 +77,16 @@ class NetworkManager(DBusInterface): ) -> Awaitable[Any]: """Activate a connction on a device.""" return self.dbus.ActivateConnection( - connection_object, device_object, DBUS_OBJECT_BASE + ("o", connection_object), ("o", device_object), ("o", DBUS_OBJECT_BASE) ) @dbus_connected def add_and_activate_connection( - self, settings: str, device_object: str + self, settings: Any, device_object: str ) -> Awaitable[Any]: """Activate a connction on a device.""" return self.dbus.AddAndActivateConnection( - settings, device_object, DBUS_OBJECT_BASE + ("a{sa{sv}}", settings), ("o", device_object), ("o", DBUS_OBJECT_BASE) ) @dbus_connected @@ -145,7 +145,7 @@ class NetworkManager(DBusInterface): # Connect to interface try: await interface.connect() - except DBusProgramError as err: + except DBusFatalError as err: _LOGGER.warning("Can't process %s: %s", device, err) continue except Exception as err: # pylint: disable=broad-except diff --git a/supervisor/dbus/network/accesspoint.py b/supervisor/dbus/network/accesspoint.py index 3e1338929..80fca7e91 100644 --- a/supervisor/dbus/network/accesspoint.py +++ b/supervisor/dbus/network/accesspoint.py @@ -1,6 +1,6 @@ """Connection object for Network Manager.""" -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_FREQUENCY, DBUS_ATTR_HWADDRESS, @@ -44,7 +44,7 @@ class NetworkWirelessAP(DBusInterfaceProxy): @property def strength(self) -> int: """Return details about mac address.""" - return int(self.properties[DBUS_ATTR_STRENGTH][0]) + return int(self.properties[DBUS_ATTR_STRENGTH]) async def connect(self) -> None: """Get connection information.""" diff --git a/supervisor/dbus/network/connection.py b/supervisor/dbus/network/connection.py index d2ed017d8..2a273cb31 100644 --- a/supervisor/dbus/network/connection.py +++ b/supervisor/dbus/network/connection.py @@ -3,7 +3,7 @@ from ipaddress import ip_address, ip_interface from typing import Optional from ...const import ATTR_ADDRESS, ATTR_PREFIX -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_ADDRESS_DATA, DBUS_ATTR_CONNECTION, diff --git a/supervisor/dbus/network/dns.py b/supervisor/dbus/network/dns.py index 8dbb9ba09..843b2992e 100644 --- a/supervisor/dbus/network/dns.py +++ b/supervisor/dbus/network/dns.py @@ -11,7 +11,7 @@ from ...const import ( ATTR_VPN, ) from ...exceptions import DBusError, DBusInterfaceError -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_CONFIGURATION, DBUS_ATTR_MODE, diff --git a/supervisor/dbus/network/interface.py b/supervisor/dbus/network/interface.py index 875ceca04..2aaff5411 100644 --- a/supervisor/dbus/network/interface.py +++ b/supervisor/dbus/network/interface.py @@ -1,7 +1,7 @@ """NetworkInterface object for Network Manager.""" from typing import Optional -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_ACTIVE_CONNECTION, DBUS_ATTR_DEVICE_INTERFACE, diff --git a/supervisor/dbus/network/setting.py b/supervisor/dbus/network/setting/__init__.py similarity index 90% rename from supervisor/dbus/network/setting.py rename to supervisor/dbus/network/setting/__init__.py index 83d1ff3df..0457c6c04 100644 --- a/supervisor/dbus/network/setting.py +++ b/supervisor/dbus/network/setting/__init__.py @@ -1,12 +1,13 @@ """Connection object for Network Manager.""" +import logging from typing import Any, Awaitable, Optional -from ...const import ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID -from ...utils.gdbus import DBus -from ..const import DBUS_NAME_NM -from ..interface import DBusInterfaceProxy -from ..utils import dbus_connected -from .configuration import ( +from ....const import ATTR_METHOD, ATTR_MODE, ATTR_PSK, ATTR_SSID +from ....utils.dbus import DBus +from ...const import DBUS_NAME_NM +from ...interface import DBusInterfaceProxy +from ...utils import dbus_connected +from ..configuration import ( ConnectionProperties, EthernetProperties, IpProperties, @@ -29,10 +30,12 @@ ATTR_TYPE = "type" ATTR_PARENT = "parent" ATTR_ASSIGNED_MAC = "assigned-mac-address" ATTR_POWERSAVE = "powersave" -ATTR_AUTH_ALGO = "auth-algo" +ATTR_AUTH_ALG = "auth-alg" ATTR_KEY_MGMT = "key-mgmt" ATTR_INTERFACE_NAME = "interface-name" +_LOGGER: logging.Logger = logging.getLogger(__name__) + class NetworkSetting(DBusInterfaceProxy): """NetworkConnection object for Network Manager.""" @@ -91,9 +94,9 @@ class NetworkSetting(DBusInterfaceProxy): return self.dbus.Settings.Connection.GetSettings() @dbus_connected - def update(self, settings: str) -> Awaitable[None]: + def update(self, settings: Any) -> Awaitable[None]: """Update connection settings.""" - return self.dbus.Settings.Connection.Update(settings) + return self.dbus.Settings.Connection.Update(("a{sa{sv}}", settings)) @dbus_connected def delete(self) -> Awaitable[None]: @@ -128,7 +131,7 @@ class NetworkSetting(DBusInterfaceProxy): if CONF_ATTR_802_WIRELESS_SECURITY in data: self._wireless_security = WirelessSecurityProperties( - data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_AUTH_ALGO), + data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_AUTH_ALG), data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_KEY_MGMT), data[CONF_ATTR_802_WIRELESS_SECURITY].get(ATTR_PSK), ) diff --git a/supervisor/dbus/network/setting/generate.py b/supervisor/dbus/network/setting/generate.py new file mode 100644 index 000000000..2e62fd372 --- /dev/null +++ b/supervisor/dbus/network/setting/generate.py @@ -0,0 +1,146 @@ +"""Payload generators for DBUS communication.""" +from __future__ import annotations + +import socket +from typing import TYPE_CHECKING, Any +from uuid import uuid4 + +from dbus_next.signature import Variant + +from . import ( + ATTR_ASSIGNED_MAC, + CONF_ATTR_802_ETHERNET, + CONF_ATTR_802_WIRELESS, + CONF_ATTR_802_WIRELESS_SECURITY, + CONF_ATTR_CONNECTION, + CONF_ATTR_IPV4, + CONF_ATTR_IPV6, + CONF_ATTR_VLAN, +) +from ....host.const import InterfaceMethod, InterfaceType + +if TYPE_CHECKING: + from ....host.network import Interface + + +def get_connection_from_interface( + interface: Interface, name: str | None = None, uuid: str | None = None +) -> Any: + """Generate message argument for network interface update.""" + + # Generate/Update ID/name + if not name or not name.startswith("Supervisor"): + name = f"Supervisor {interface.name}" + if interface.type == InterfaceType.VLAN: + name = f"{name}.{interface.vlan.id}" + + if interface.type == InterfaceType.ETHERNET: + iftype = "802-3-ethernet" + elif interface.type == InterfaceType.WIRELESS: + iftype = "802-11-wireless" + else: + iftype = interface.type.value + + # Generate UUID + if not uuid: + uuid = str(uuid4()) + + connection = { + "id": Variant("s", name), + "interface-name": Variant("s", interface.name), + "type": Variant("s", iftype), + "uuid": Variant("s", uuid), + "llmnr": Variant("i", 2), + "mdns": Variant("i", 2), + } + + conn = {} + conn[CONF_ATTR_CONNECTION] = connection + + ipv4 = {} + if interface.ipv4.method == InterfaceMethod.AUTO: + ipv4["method"] = Variant("s", "auto") + elif interface.ipv4.method == InterfaceMethod.DISABLED: + ipv4["method"] = Variant("s", "disabled") + else: + ipv4["method"] = Variant("s", "manual") + ipv4["dns"] = Variant( + "au", + [ + socket.htonl(int(ip_address)) + for ip_address in interface.ipv4.nameservers + ], + ) + + adressdata = [] + for address in interface.ipv4.address: + adressdata.append( + { + "address": Variant("s", str(address.ip)), + "prefix": Variant("u", int(address.with_prefixlen.split("/")[-1])), + } + ) + + ipv4["address-data"] = Variant("aa{sv}", adressdata) + ipv4["gateway"] = Variant("s", str(interface.ipv4.gateway)) + + conn[CONF_ATTR_IPV4] = ipv4 + + ipv6 = {} + if interface.ipv6.method == InterfaceMethod.AUTO: + ipv6["method"] = Variant("s", "auto") + elif interface.ipv6.method == InterfaceMethod.DISABLED: + ipv6["method"] = Variant("s", "disabled") + else: + ipv6["method"] = Variant("s", "manual") + ipv6["dns"] = Variant( + "aay", [ip_address.packed for ip_address in interface.ipv6.nameservers] + ) + + adressdata = [] + for address in interface.ipv6.address: + if address.with_prefixlen.startswith("fe80::"): + continue + adressdata.append( + { + "address": Variant("s", str(address.ip)), + "prefix": Variant("u", int(address.with_prefixlen.split("/")[-1])), + } + ) + + ipv6["address-data"] = Variant("(a{sv})", adressdata) + ipv4["gateway"] = Variant("s", str(interface.ipv6.gateway)) + + conn[CONF_ATTR_IPV6] = ipv6 + + if interface.type == InterfaceType.ETHERNET: + conn[CONF_ATTR_802_ETHERNET] = {ATTR_ASSIGNED_MAC: Variant("s", "preserve")} + elif interface.type == "vlan": + conn[CONF_ATTR_VLAN] = { + "id": Variant("u", interface.vlan.id), + "parent": Variant("s", interface.vlan.interface), + } + elif interface.type == InterfaceType.WIRELESS: + wireless = { + ATTR_ASSIGNED_MAC: Variant("s", "preserve"), + "ssid": Variant("ay", interface.wifi.ssid.encode("UTF-8")), + "mode": Variant("s", "infrastructure"), + "powersave": Variant("i", 1), + } + conn[CONF_ATTR_802_WIRELESS] = wireless + + if interface.wifi.auth != "open": + wireless["security"] = Variant("s", CONF_ATTR_802_WIRELESS_SECURITY) + wireless_security = {} + if interface.wifi.auth == "wep": + wireless_security["auth-alg"] = Variant("s", "none") + wireless_security["key-mgmt"] = Variant("s", "open") + elif interface.wifi.auth == "wpa-psk": + wireless_security["auth-alg"] = Variant("s", "open") + wireless_security["key-mgmt"] = Variant("s", "wpa-psk") + + if interface.wifi.psk: + wireless_security["psk"] = Variant("s", interface.wifi.psk) + conn[CONF_ATTR_802_WIRELESS_SECURITY] = wireless_security + + return conn diff --git a/supervisor/dbus/network/settings.py b/supervisor/dbus/network/settings.py index 457f3d771..758f32a0b 100644 --- a/supervisor/dbus/network/settings.py +++ b/supervisor/dbus/network/settings.py @@ -3,7 +3,7 @@ import logging from typing import Any, Awaitable from ...exceptions import DBusError, DBusInterfaceError -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import DBUS_NAME_NM, DBUS_OBJECT_SETTINGS from ..interface import DBusInterface from ..utils import dbus_connected @@ -26,9 +26,9 @@ class NetworkManagerSettings(DBusInterface): ) @dbus_connected - def add_connection(self, settings: str) -> Awaitable[Any]: + def add_connection(self, settings: Any) -> Awaitable[Any]: """Add new connection.""" - return self.dbus.Settings.AddConnection(settings) + return self.dbus.Settings.AddConnection(("a{sa{sv}}", settings)) @dbus_connected def reload_connections(self) -> Awaitable[Any]: diff --git a/supervisor/dbus/network/wireless.py b/supervisor/dbus/network/wireless.py index 998321447..cf60ed93b 100644 --- a/supervisor/dbus/network/wireless.py +++ b/supervisor/dbus/network/wireless.py @@ -1,7 +1,7 @@ """Connection object for Network Manager.""" from typing import Any, Awaitable, Optional -from ...utils.gdbus import DBus +from ...utils.dbus import DBus from ..const import ( DBUS_ATTR_ACTIVE_ACCESSPOINT, DBUS_NAME_DEVICE_WIRELESS, @@ -31,7 +31,7 @@ class NetworkWireless(DBusInterfaceProxy): @dbus_connected def request_scan(self) -> Awaitable[None]: """Request a new AP scan.""" - return self.dbus.Device.Wireless.RequestScan("[]") + return self.dbus.Device.Wireless.RequestScan(("a{sv}", {})) @dbus_connected def get_all_accesspoints(self) -> Awaitable[Any]: diff --git a/supervisor/dbus/payloads/__init__.py b/supervisor/dbus/payloads/__init__.py deleted file mode 100644 index 5ae0b813c..000000000 --- a/supervisor/dbus/payloads/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Init file for DBUS payloads.""" diff --git a/supervisor/dbus/payloads/generate.py b/supervisor/dbus/payloads/generate.py deleted file mode 100644 index 500f2d93d..000000000 --- a/supervisor/dbus/payloads/generate.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Payload generators for DBUS communication.""" -from __future__ import annotations - -from ipaddress import IPv4Address, IPv6Address -from pathlib import Path -import socket -from typing import TYPE_CHECKING -from uuid import uuid4 - -import jinja2 - -from ...host.const import InterfaceType - -if TYPE_CHECKING: - from ...host.network import Interface - - -INTERFACE_UPDATE_TEMPLATE: Path = ( - Path(__file__).parents[2].joinpath("dbus/payloads/interface_update.tmpl") -) - - -def interface_update_payload( - interface: Interface, name: str | None = None, uuid: str | None = None -) -> str: - """Generate a payload for network interface update.""" - env = jinja2.Environment() - - def ipv4_to_int(ip_address: IPv4Address) -> int: - """Convert an ipv4 to an int.""" - return socket.htonl(int(ip_address)) - - def ipv6_to_byte(ip_address: IPv6Address) -> str: - """Convert an ipv6 to an byte array.""" - return f'[byte {", ".join(f"0x{val:02x}" for val in ip_address.packed)}]' - - # Init template - env.filters["ipv4_to_int"] = ipv4_to_int - env.filters["ipv6_to_byte"] = ipv6_to_byte - template: jinja2.Template = env.from_string(INTERFACE_UPDATE_TEMPLATE.read_text()) - - # Generate UUID - if not uuid: - uuid = str(uuid4()) - - # Generate/Update ID/name - if not name or not name.startswith("Supervisor"): - name = f"Supervisor {interface.name}" - if interface.type == InterfaceType.VLAN: - name = f"{name}.{interface.vlan.id}" - - # Fix SSID - if interface.wifi: - interface.wifi.ssid = ", ".join( - [f"0x{x}" for x in interface.wifi.ssid.encode().hex(",").split(",")] - ) - - return template.render(interface=interface, name=name, uuid=uuid) diff --git a/supervisor/dbus/payloads/interface_update.tmpl b/supervisor/dbus/payloads/interface_update.tmpl deleted file mode 100644 index a2af64ede..000000000 --- a/supervisor/dbus/payloads/interface_update.tmpl +++ /dev/null @@ -1,106 +0,0 @@ -{ - 'connection': - { - 'id': <'{{ name }}'>, -{% if interface.type != "vlan" %} - 'interface-name': <'{{ interface.name }}'>, -{% endif %} - 'type': <'{% if interface.type == "ethernet" %}802-3-ethernet{% elif interface.type == "wireless" %}802-11-wireless{% else %}{{ interface.type.value }}{% endif %}'>, - 'uuid': <'{{ uuid }}'>, - 'llmnr': , - 'mdns': - } - -{% if interface.ipv4 %} - , - 'ipv4': - { -{% if interface.ipv4.method == "auto" %} - 'method': <'auto'> -{% elif interface.ipv4.method == "disabled" %} - 'method': <'disabled'> -{% else %} - 'method': <'manual'>, - 'dns': <[uint32 {{ interface.ipv4.nameservers | map("ipv4_to_int") | join(",") }}]>, - 'address-data': <[ - {% for address in interface.ipv4.address %} - { - 'address': <'{{ address.ip | string }}'>, - 'prefix': - }]>, - {% endfor %} - 'gateway': <'{{ interface.ipv4.gateway | string }}'> -{% endif %} - } -{% endif %} - -{% if interface.ipv6 %} - , - 'ipv6': - { -{% if interface.ipv6.method == "auto" %} - 'method': <'auto'> -{% elif interface.ipv6.method == "disabled" %} - 'method': <'disabled'> -{% else %} - 'method': <'manual'>, - 'dns': <[{{ interface.ipv6.nameservers | map("ipv6_to_byte") | join(",") }}]>, - 'address-data': <[ - {% for address in interface.ipv6.address if not address.with_prefixlen.startswith("fe80::") %} - { - 'address': <'{{ address.ip | string }}'>, - 'prefix': - }]>, - {% endfor %} - 'gateway': <'{{ interface.ipv6.gateway | string }}'> -{% endif %} - } -{% endif %} - -{% if interface.type == "ethernet" %} - , - '802-3-ethernet': - { - 'assigned-mac-address': <'preserve'> - } -{% endif %} - -{% if interface.type == "vlan" %} - , - 'vlan': - { - 'id': , - 'parent': <'{{ interface.vlan.interface }}'> - } -{% endif %} - -{% if interface.type == "wireless" %} - , - '802-11-wireless': - { - 'assigned-mac-address': <'preserve'>, - 'ssid': <[byte {{ interface.wifi.ssid }}]>, - 'mode': <'{{ interface.wifi.mode.value }}'>, - 'powersave': -{% if interface.wifi.auth != "open" %} - , - 'security': <'802-11-wireless-security'> - }, - - '802-11-wireless-security': - { - {% if interface.wifi.auth == "wep" %} - 'auth-alg': <'none'>, - 'key-mgmt': <'none'> - {% elif interface.wifi.auth == "wpa-psk" %} - 'auth-alg': <'open'>, - 'key-mgmt': <'wpa-psk'> - {% endif %} - {% if interface.wifi.psk %} - , - 'psk': <'{{ interface.wifi.psk }}'> - {% endif %} -{% endif %} - } -{% endif %} -} \ No newline at end of file diff --git a/supervisor/dbus/rauc.py b/supervisor/dbus/rauc.py index 7eb1d18c6..d29beed39 100644 --- a/supervisor/dbus/rauc.py +++ b/supervisor/dbus/rauc.py @@ -3,7 +3,7 @@ import logging from typing import Optional from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import ( DBUS_ATTR_BOOT_SLOT, DBUS_ATTR_COMPATIBLE, @@ -75,7 +75,7 @@ class Rauc(DBusInterface): Return a coroutine. """ - return self.dbus.Installer.Install(raucb_file) + return self.dbus.Installer.Install(str(raucb_file)) @dbus_connected def get_slot_status(self): diff --git a/supervisor/dbus/systemd.py b/supervisor/dbus/systemd.py index 246dab8ac..1ec3ca44f 100644 --- a/supervisor/dbus/systemd.py +++ b/supervisor/dbus/systemd.py @@ -3,15 +3,15 @@ import logging from typing import Any from ..exceptions import DBusError, DBusInterfaceError -from ..utils.gdbus import DBus +from ..utils.dbus import DBus from .const import ( DBUS_ATTR_FINISH_TIMESTAMP, DBUS_ATTR_FIRMWARE_TIMESTAMP_MONOTONIC, DBUS_ATTR_KERNEL_TIMESTAMP_MONOTONIC, DBUS_ATTR_LOADER_TIMESTAMP_MONOTONIC, DBUS_ATTR_USERSPACE_TIMESTAMP_MONOTONIC, - DBUS_NAME_ROOT, DBUS_NAME_SYSTEMD, + DBUS_NAME_SYSTEMD_MANAGER, DBUS_OBJECT_SYSTEMD, ) from .interface import DBusInterface, dbus_property @@ -116,4 +116,4 @@ class Systemd(DBusInterface): @dbus_connected async def update(self): """Update Properties.""" - self.properties = await self.dbus.get_properties(DBUS_NAME_ROOT) + self.properties = await self.dbus.get_properties(DBUS_NAME_SYSTEMD_MANAGER) diff --git a/supervisor/dbus/timedate.py b/supervisor/dbus/timedate.py index 24c4a8634..a857de8cf 100644 --- a/supervisor/dbus/timedate.py +++ b/supervisor/dbus/timedate.py @@ -4,8 +4,8 @@ import logging from typing import Any from ..exceptions import DBusError, DBusInterfaceError +from ..utils.dbus import DBus from ..utils.dt import utc_from_timestamp -from ..utils.gdbus import DBus from .const import ( DBUS_ATTR_LOCALRTC, DBUS_ATTR_NTP, diff --git a/supervisor/exceptions.py b/supervisor/exceptions.py index 53e51b98f..626500258 100644 --- a/supervisor/exceptions.py +++ b/supervisor/exceptions.py @@ -263,35 +263,31 @@ class ServicesError(HassioError): """Services Errors.""" -# utils/gdbus +# utils/dbus class DBusError(HassioError): - """DBus generic error.""" + """D-Bus generic error.""" class DBusNotConnectedError(HostNotSupportedError): - """DBus is not connected and call a method.""" + """D-Bus is not connected and call a method.""" class DBusInterfaceError(HassioNotSupportedError): - """DBus interface not connected.""" + """D-Bus interface not connected.""" class DBusFatalError(DBusError): - """DBus call going wrong.""" + """D-Bus call going wrong.""" class DBusInterfaceMethodError(DBusInterfaceError): - """Dbus method was not definied.""" + """D-Bus method was not defined.""" class DBusParseError(DBusError): - """DBus parse error.""" - - -class DBusProgramError(DBusError): - """DBus application error.""" + """D-Bus parse error.""" # util/apparmor diff --git a/supervisor/host/network.py b/supervisor/host/network.py index 76a203447..9151346d8 100644 --- a/supervisor/host/network.py +++ b/supervisor/host/network.py @@ -19,11 +19,10 @@ from ..dbus.const import ( from ..dbus.network.accesspoint import NetworkWirelessAP from ..dbus.network.connection import NetworkConnection from ..dbus.network.interface import NetworkInterface -from ..dbus.payloads.generate import interface_update_payload +from ..dbus.network.setting.generate import get_connection_from_interface from ..exceptions import ( DBusError, DBusNotConnectedError, - DBusProgramError, HostNetworkError, HostNetworkNotFound, HostNotSupportedError, @@ -128,7 +127,8 @@ class NetworkManager(CoreSysAttributes): and inet.settings.connection.interface_name == interface.name and interface.enabled ): - settings = interface_update_payload( + _LOGGER.debug("Updating existing configuration for %s", interface.name) + settings = get_connection_from_interface( interface, name=inet.settings.connection.id, uuid=inet.settings.connection.uuid, @@ -146,12 +146,14 @@ class NetworkManager(CoreSysAttributes): # Create new configuration and activate interface elif inet and interface.enabled: - settings = interface_update_payload(interface) + _LOGGER.debug("Create new configuration for %s", interface.name) + settings = get_connection_from_interface(interface) try: - await self.sys_dbus.network.add_and_activate_connection( + new_con = await self.sys_dbus.network.add_and_activate_connection( settings, inet.object_path ) + _LOGGER.debug("add_and_activate_connection returns %s", new_con) except DBusError as err: raise HostNetworkError( f"Can't create config and activate {interface.name}: {err}", @@ -169,7 +171,7 @@ class NetworkManager(CoreSysAttributes): # Create new interface (like vlan) elif not inet: - settings = interface_update_payload(interface) + settings = get_connection_from_interface(interface) try: await self.sys_dbus.network.settings.add_connection(settings) @@ -182,9 +184,12 @@ class NetworkManager(CoreSysAttributes): "Requested Network interface update is not possible", _LOGGER.warning ) + # This signal is fired twice: Activating -> Activated. It seems we miss the first + # "usually"... We should filter by state and explicitly wait for the second. await self.sys_dbus.network.dbus.wait_signal( DBUS_NAME_NM_CONNECTION_ACTIVE_CHANGED ) + await self.update() async def scan_wifi(self, interface: Interface) -> list[AccessPoint]: @@ -199,9 +204,8 @@ class NetworkManager(CoreSysAttributes): # Request Scan try: await inet.wireless.request_scan() - except DBusProgramError as err: - _LOGGER.debug("Can't request a new scan: %s", err) except DBusError as err: + _LOGGER.warning("Can't request a new scan: %s", err) raise HostNetworkError() from err else: await asyncio.sleep(5) diff --git a/supervisor/utils/dbus.py b/supervisor/utils/dbus.py new file mode 100644 index 000000000..fab5950d7 --- /dev/null +++ b/supervisor/utils/dbus.py @@ -0,0 +1,249 @@ +"""DBus implementation with glib.""" +from __future__ import annotations + +import asyncio +import logging +from typing import Any + +from dbus_next import BusType, InvalidIntrospectionError, Message, MessageType +from dbus_next.aio import MessageBus +from dbus_next.signature import Variant + +from ..exceptions import ( + DBusFatalError, + DBusInterfaceError, + DBusInterfaceMethodError, + DBusNotConnectedError, + DBusParseError, +) + + +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_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll" +DBUS_METHOD_SET: str = "org.freedesktop.DBus.Properties.Set" + + +class DBus: + """DBus handler.""" + + def __init__(self, bus_name: str, object_path: str) -> None: + """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._bus: MessageBus = None + + @staticmethod + async def connect(bus_name: str, object_path: str) -> DBus: + """Read object data.""" + self = DBus(bus_name, object_path) + + # pylint: disable=protected-access + await self._init_proxy() + + _LOGGER.debug("Connect to D-Bus: %s - %s", bus_name, object_path) + return self + + def _add_interfaces(self, introspection: Any): + # Read available methods + for interface in introspection.interfaces: + interface_name = interface.name + + # Methods + for method in interface.methods: + method_name = method.name + self.methods.add(f"{interface_name}.{method_name}") + + # Signals + for signal in interface.signals: + signal_name = signal.name + self.signals.add(f"{interface_name}.{signal_name}") + + async def _init_proxy(self) -> None: + """Read interface data.""" + # Wait for dbus object to be available after restart + try: + self._bus = await MessageBus(bus_type=BusType.SYSTEM).connect() + except Exception as err: + raise DBusFatalError() from err + + try: + introspection = await self._bus.introspect( + self.bus_name, self.object_path, timeout=10 + ) + except InvalidIntrospectionError as err: + _LOGGER.error("Can't parse introspect data: %s", err) + raise DBusParseError() from err + + 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]) -> 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, + ) + ) + + if reply.message_type == MessageType.ERROR: + if reply.error_name in ( + "org.freedesktop.DBus.Error.ServiceUnknown", + "org.freedesktop.DBus.Error.UnknownMethod", + ): + raise DBusInterfaceError(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() + + return _remove_dbus_signature(reply.body) + + 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 set_property( + self, interface: str, name: str, value: Any + ) -> dict[str, Any]: + """Set a property from interface.""" + try: + return (await self.call_dbus(DBUS_METHOD_SET, interface, name, value))[0] + except IndexError as err: + _LOGGER.error("No Set attribute %s for %s", name, interface) + raise DBusFatalError() from err + + async def wait_signal(self, signal): + """Wait for single event.""" + signal_parts = signal.split(".") + interface = ".".join(signal_parts[:-1]) + member = signal_parts[-1] + + _LOGGER.debug("Wait for signal %s", signal) + await self._bus.call( + Message( + destination="org.freedesktop.DBus", + interface="org.freedesktop.DBus", + path="/org/freedesktop/DBus", + member="AddMatch", + signature="s", + body=[f"type='signal',interface={interface},member={member}"], + ) + ) + + loop = asyncio.get_event_loop() + future = loop.create_future() + + def message_handler(msg: Message): + if msg.message_type != MessageType.SIGNAL: + return + + _LOGGER.debug( + "Signal message received %s, %s %s", msg, msg.interface, msg.member + ) + if msg.interface != interface or msg.member != member: + return + + # Avoid race condition: We already received signal but handler not yet removed. + if future.done(): + return + + future.set_result(_remove_dbus_signature(msg.body)) + + self._bus.add_message_handler(message_handler) + result = await future + self._bus.remove_message_handler(message_handler) + + return result + + def __getattr__(self, name: str) -> DBusCallWrapper: + """Map to dbus method.""" + return getattr(DBusCallWrapper(self, self.bus_name), name) + + +class DBusCallWrapper: + """Wrapper a DBus interface for a call.""" + + def __init__(self, dbus: DBus, interface: str) -> None: + """Initialize wrapper.""" + self.dbus: DBus = dbus + self.interface: str = 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): + """Map to dbus method.""" + interface = f"{self.interface}.{name}" + + if interface not in self.dbus.methods: + return DBusCallWrapper(self.dbus, interface) + + def _method_wrapper(*args): + """Wrap method. + + Return a coroutine + """ + return self.dbus.call_dbus(interface, *args) + + return _method_wrapper diff --git a/supervisor/utils/gdbus.py b/supervisor/utils/gdbus.py deleted file mode 100644 index 1c07e93ea..000000000 --- a/supervisor/utils/gdbus.py +++ /dev/null @@ -1,383 +0,0 @@ -"""DBus implementation with glib.""" -from __future__ import annotations - -import asyncio -import json -import logging -import re -import shlex -from signal import SIGINT -from typing import Any -import xml.etree.ElementTree as ET - -import sentry_sdk - -from . import clean_env -from ..exceptions import ( - DBusFatalError, - DBusInterfaceError, - DBusInterfaceMethodError, - DBusNotConnectedError, - DBusParseError, - DBusProgramError, -) - -_LOGGER: logging.Logger = logging.getLogger(__name__) - -# Use to convert GVariant into json -RE_GVARIANT_TYPE: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(boolean|byte|int16|uint16|int32|uint32|handle|int64|uint64|double|" - r"string|objectpath|signature|@[asviumodfy\{\}\(\)]+) " -) -RE_GVARIANT_VARIANT: re.Pattern[Any] = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(<|>)") -RE_GVARIANT_STRING_ESC: re.Pattern[Any] = re.compile( - r"(?<=(?: |{|\[|\(|<))'[^']*?\"[^']*?'(?=(?:|]|}|,|\)|>))" -) -RE_GVARIANT_STRING: re.Pattern[Any] = re.compile( - r"(?<=(?: |{|\[|\(|<))'(.*?)'(?=(?:|]|}|,|\)|>))" -) -RE_GVARIANT_BINARY: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|\[byte (.*?)\]|\[(0x[0-9A-Za-z]{2}.*?)\]|" -) -RE_GVARIANT_BINARY_STRING: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|?" -) -RE_GVARIANT_TUPLE_O: re.Pattern[Any] = re.compile(r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(\()") -RE_GVARIANT_TUPLE_C: re.Pattern[Any] = re.compile( - r"\"[^\"\\]*(?:\\.[^\"\\]*)*\"|(,?\))" -) - -RE_BIN_STRING_OCT: re.Pattern[Any] = re.compile(r"\\\\(\d{3})") -RE_BIN_STRING_HEX: re.Pattern[Any] = re.compile(r"\\\\x([0-9A-Za-z]{2})") - -RE_MONITOR_OUTPUT: re.Pattern[Any] = re.compile( - r".+?: (?P[^\s].+?) (?P.*)" -) - -# Map GDBus to errors -MAP_GDBUS_ERROR: dict[str, Any] = { - "GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown": DBusInterfaceError, - "GDBus.Error:org.freedesktop.DBus.Error.Spawn.ChildExited": DBusFatalError, - "No such file or directory": DBusNotConnectedError, -} - -# Commands for dbus -INTROSPECT: str = "gdbus introspect --system --dest {bus} --object-path {object} --xml" -CALL: str = "gdbus call --system --dest {bus} --object-path {object} --timeout 10 --method {method} {args}" -MONITOR: str = "gdbus monitor --system --dest {bus}" -WAIT: str = "gdbus wait --system --activate {bus} --timeout 5 {bus}" - -DBUS_METHOD_GETALL: str = "org.freedesktop.DBus.Properties.GetAll" -DBUS_METHOD_SET: str = "org.freedesktop.DBus.Properties.Set" - - -def _convert_bytes(value: str) -> str: - """Convert bytes to string or byte-array.""" - data: bytes = bytes(int(char, 0) for char in value.split(", ")) - return f"[{', '.join(str(char) for char in data)}]" - - -def _convert_bytes_string(value: str) -> str: - """Convert bytes to string or byte-array.""" - data = RE_BIN_STRING_OCT.sub(lambda x: chr(int(x.group(1), 8)), value) - data = RE_BIN_STRING_HEX.sub(lambda x: chr(int(f"0x{x.group(1)}", 0)), data) - return f"[{', '.join(str(char) for char in list(char for char in data.encode()))}]" - - -class DBus: - """DBus handler.""" - - def __init__(self, bus_name: str, object_path: str) -> None: - """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() - - @staticmethod - async def connect(bus_name: str, object_path: str) -> DBus: - """Read object data.""" - self = DBus(bus_name, object_path) - - # pylint: disable=protected-access - await self._init_proxy() - - _LOGGER.debug("Connect to D-Bus: %s - %s", bus_name, object_path) - return self - - async def _init_proxy(self) -> None: - """Read interface data.""" - # Wait for dbus object to be available after restart - command_wait = shlex.split(WAIT.format(bus=self.bus_name)) - await self._send(command_wait, silent=True) - - # Introspect object & Parse XML - command_introspect = shlex.split( - INTROSPECT.format(bus=self.bus_name, object=self.object_path) - ) - data = await self._send(command_introspect) - try: - xml = ET.fromstring(data) - except ET.ParseError as err: - _LOGGER.error("Can't parse introspect data: %s", err) - _LOGGER.debug("Introspect %s on %s", self.bus_name, self.object_path) - raise DBusParseError() from err - - # Read available methods - for interface in xml.findall("./interface"): - interface_name = interface.get("name") - - # Methods - for method in interface.findall("./method"): - method_name = method.get("name") - self.methods.add(f"{interface_name}.{method_name}") - - # Signals - for signal in interface.findall("./signal"): - signal_name = signal.get("name") - self.signals.add(f"{interface_name}.{signal_name}") - - @staticmethod - def parse_gvariant(raw: str) -> Any: - """Parse GVariant input to python.""" - # Process first string - json_raw = RE_GVARIANT_STRING_ESC.sub( - lambda x: x.group(0).replace('"', '\\"'), raw - ) - json_raw = RE_GVARIANT_STRING.sub(r'"\1"', json_raw) - - # Handle Bytes - json_raw = RE_GVARIANT_BINARY.sub( - lambda x: x.group(0) - if not (x.group(1) or x.group(2) or x.group(3)) - else _convert_bytes(x.group(1) or x.group(2) or x.group(3)), - json_raw, - ) - json_raw = RE_GVARIANT_BINARY_STRING.sub( - lambda x: x.group(0) - if not x.group(1) - else _convert_bytes_string(x.group(1)), - json_raw, - ) - - # Remove complex type handling - json_raw: str = RE_GVARIANT_TYPE.sub( - lambda x: x.group(0) if not x.group(1) else "", json_raw - ) - json_raw = RE_GVARIANT_VARIANT.sub( - lambda x: x.group(0) if not x.group(1) else "", json_raw - ) - json_raw = RE_GVARIANT_TUPLE_O.sub( - lambda x: x.group(0) if not x.group(1) else "[", json_raw - ) - json_raw = RE_GVARIANT_TUPLE_C.sub( - lambda x: x.group(0) if not x.group(1) else "]", json_raw - ) - - # No data - if json_raw.startswith("[]"): - return [] - - try: - return json.loads(json_raw) - except json.JSONDecodeError as err: - _LOGGER.error("Can't parse '%s': '%s' - %s", json_raw, raw, err) - sentry_sdk.capture_exception(err) - raise DBusParseError() from err - - @staticmethod - def gvariant_args(args: list[Any]) -> str: - """Convert args into gvariant.""" - gvariant = "" - for arg in args: - if isinstance(arg, bool): - gvariant += f" {str(arg).lower()}" - elif isinstance(arg, (int, float)): - gvariant += f" {arg}" - elif isinstance(arg, str): - gvariant += f' "{arg}"' - else: - gvariant += f" {arg!s}" - - return gvariant.lstrip() - - async def call_dbus(self, method: str, *args: list[Any]) -> str: - """Call a dbus method.""" - command = shlex.split( - CALL.format( - bus=self.bus_name, - object=self.object_path, - method=method, - args=self.gvariant_args(args), - ) - ) - - # Run command - _LOGGER.debug("Call %s on %s", method, self.object_path) - data = await self._send(command) - - # Parse and return data - return self.parse_gvariant(data) - - 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 set_property( - self, interface: str, name: str, value: Any - ) -> dict[str, Any]: - """Set a property from interface.""" - try: - return (await self.call_dbus(DBUS_METHOD_SET, interface, name, value))[0] - except IndexError as err: - _LOGGER.error("No Set attribute %s for %s", name, interface) - raise DBusFatalError() from err - - async def _send(self, command: list[str], silent=False) -> str: - """Send command over dbus.""" - # Run command - _LOGGER.debug("Send D-Bus command: %s", command) - try: - proc = await asyncio.create_subprocess_exec( - *command, - stdin=asyncio.subprocess.DEVNULL, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - env=clean_env(), - ) - - data, error = await proc.communicate() - except OSError as err: - _LOGGER.critical("D-Bus fatal error: %s", err) - raise DBusFatalError() from err - - # Success? - if proc.returncode == 0 or silent: - return data.decode() - - # Filter error - error = error.decode() - for msg, exception in MAP_GDBUS_ERROR.items(): - if msg not in error: - continue - raise exception() - - # General - _LOGGER.debug("D-Bus return: %s", error.strip()) - raise DBusProgramError(error.strip()) - - def attach_signals(self, filters=None): - """Generate a signals wrapper.""" - return DBusSignalWrapper(self, filters) - - async def wait_signal(self, signal): - """Wait for single event.""" - monitor = DBusSignalWrapper(self, [signal]) - async with monitor as signals: - async for signal in signals: - return signal - - def __getattr__(self, name: str) -> DBusCallWrapper: - """Map to dbus method.""" - return getattr(DBusCallWrapper(self, self.bus_name), name) - - -class DBusCallWrapper: - """Wrapper a DBus interface for a call.""" - - def __init__(self, dbus: DBus, interface: str) -> None: - """Initialize wrapper.""" - self.dbus: DBus = dbus - self.interface: str = 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): - """Map to dbus method.""" - interface = f"{self.interface}.{name}" - - if interface not in self.dbus.methods: - return DBusCallWrapper(self.dbus, interface) - - def _method_wrapper(*args): - """Wrap method. - - Return a coroutine - """ - return self.dbus.call_dbus(interface, *args) - - return _method_wrapper - - -class DBusSignalWrapper: - """Process Signals.""" - - def __init__(self, dbus: DBus, signals: str | None = None): - """Initialize dbus signal wrapper.""" - self.dbus: DBus = dbus - self._signals: str | None = signals - self._proc: asyncio.Process | None = None - - async def __aenter__(self): - """Start monitor events.""" - _LOGGER.info("Starting dbus monitor on %s", self.dbus.bus_name) - command = shlex.split(MONITOR.format(bus=self.dbus.bus_name)) - self._proc = await asyncio.create_subprocess_exec( - *command, - stdin=asyncio.subprocess.DEVNULL, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE, - env=clean_env(), - ) - - return self - - async def __aexit__(self, exception_type, exception_value, traceback): - """Stop monitor events.""" - _LOGGER.info("Stopping dbus monitor on %s", self.dbus.bus_name) - self._proc.send_signal(SIGINT) - await self._proc.communicate() - - def __aiter__(self): - """Start Iteratation.""" - return self - - async def __anext__(self): - """Get next data.""" - if not self._proc: - raise StopAsyncIteration() from None - - # Read signals - while True: - try: - data = await self._proc.stdout.readline() - except asyncio.TimeoutError: - raise StopAsyncIteration() from None - - # Program close - if not data: - raise StopAsyncIteration() from None - - # Extract metadata - match = RE_MONITOR_OUTPUT.match(data.decode()) - if not match: - continue - signal = match.group("signal") - data = match.group("data") - - # Filter signals? - if self._signals and signal not in self._signals: - _LOGGER.debug("Skiping event %s - %s", signal, data) - continue - - try: - return self.dbus.parse_gvariant(data) - except DBusParseError as err: - raise StopAsyncIteration() from err diff --git a/tests/common.py b/tests/common.py index 2f0149cf3..4d7d86684 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,9 +1,10 @@ """Common test functions.""" import json from pathlib import Path +from typing import Any -def load_json_fixture(filename: str) -> dict: +def load_json_fixture(filename: str) -> Any: """Load a json fixture.""" path = Path(Path(__file__).parent.joinpath("fixtures"), filename) return json.loads(path.read_text(encoding="utf-8")) diff --git a/tests/conftest.py b/tests/conftest.py index 88c808509..d6705410c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,11 +3,13 @@ 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 import pytest from supervisor import config as su_config @@ -20,7 +22,7 @@ from supervisor.dbus.network import NetworkManager from supervisor.docker import DockerAPI from supervisor.store.addon import AddonStore from supervisor.store.repository import Repository -from supervisor.utils.gdbus import DBus +from supervisor.utils.dbus import DBus from .common import exists_fixture, load_fixture, load_json_fixture from .const import TEST_ADDON_SLUG @@ -80,35 +82,38 @@ def dbus() -> DBus: async def mock_wait_signal(_, __): pass - async def mock_send(_, command, silent=False): - if silent: - return "" + async def mock_init_proxy(self): - fixture = command[6].replace("/", "_")[1:] - if command[1] == "introspect": - filetype = "xml" + filetype = "xml" + fixture = self.object_path.replace("/", "_")[1:] + if not exists_fixture(f"{fixture}.{filetype}"): + fixture = re.sub(r"_[0-9]+$", "", fixture) - if not exists_fixture(f"{fixture}.{filetype}"): - fixture = re.sub(r"_[0-9]+$", "", fixture) + # special case + if exists_fixture(f"{fixture}_~.{filetype}"): + fixture = f"{fixture}_~" - # special case - if exists_fixture(f"{fixture}_~.{filetype}"): - fixture = f"{fixture}_~" - else: - fixture = f"{fixture}-{command[10].split('.')[-1]}" - filetype = "fixture" + # Use dbus-next infrastructure to parse introspection xml + node = intr.Node.parse(load_fixture(f"{fixture}.{filetype}")) + self._add_interfaces(node) - dbus_commands.append(fixture) + async def mock_call_dbus(self, method: str, *args: list[Any]): - return load_fixture(f"{fixture}.{filetype}") + fixture = self.object_path.replace("/", "_")[1:] + fixture = f"{fixture}-{method.split('.')[-1]}" + dbus_commands.append(fixture) - with patch("supervisor.utils.gdbus.DBus._send", new=mock_send), patch( - "supervisor.utils.gdbus.DBus.wait_signal", new=mock_wait_signal + return load_json_fixture(f"{fixture}.json") + + with patch("supervisor.utils.dbus.DBus.call_dbus", new=mock_call_dbus), patch( + "supervisor.utils.dbus.DBus.wait_signal", new=mock_wait_signal ), patch( "supervisor.dbus.interface.DBusInterface.is_connected", return_value=True, ), patch( - "supervisor.utils.gdbus.DBus.get_properties", new=mock_get_properties + "supervisor.utils.dbus.DBus.get_properties", new=mock_get_properties + ), patch( + "supervisor.utils.dbus.DBus._init_proxy", new=mock_init_proxy ): yield dbus_commands diff --git a/tests/dbus/network/test_setting.py b/tests/dbus/network/test_setting.py new file mode 100644 index 000000000..1ba3a9e79 --- /dev/null +++ b/tests/dbus/network/test_setting.py @@ -0,0 +1,36 @@ +"""Test NetwrokInterface.""" +import pytest + +from supervisor.dbus.const import DeviceType +from supervisor.dbus.network import NetworkManager +from supervisor.dbus.network.setting.generate import get_connection_from_interface +from supervisor.host.network import Interface + +from tests.const import TEST_INTERFACE, TEST_INTERFACE_WLAN + + +@pytest.mark.asyncio +async def test_get_connection_from_interface(network_manager: NetworkManager): + """Test network interface.""" + dbus_interface = network_manager.interfaces[TEST_INTERFACE] + interface = Interface.from_dbus_interface(dbus_interface) + connection_payload = get_connection_from_interface(interface) + + assert "connection" in connection_payload + + assert connection_payload["connection"]["interface-name"].value == TEST_INTERFACE + assert connection_payload["connection"]["type"].value == "802-3-ethernet" + + assert connection_payload["ipv4"]["method"].value == "auto" + assert "address-data" not in connection_payload["ipv4"] + + assert connection_payload["ipv6"]["method"].value == "auto" + assert "address-data" not in connection_payload["ipv6"] + + +@pytest.mark.asyncio +async def test_network_interface_wlan(network_manager: NetworkManager): + """Test network interface.""" + interface = network_manager.interfaces[TEST_INTERFACE_WLAN] + assert interface.name == TEST_INTERFACE_WLAN + assert interface.type == DeviceType.WIRELESS diff --git a/tests/dbus/payloads/test_interface_update_payload.py b/tests/dbus/payloads/test_interface_update_payload.py deleted file mode 100644 index 69945fc37..000000000 --- a/tests/dbus/payloads/test_interface_update_payload.py +++ /dev/null @@ -1,271 +0,0 @@ -"""Test interface update payload.""" -from ipaddress import ip_address, ip_interface - -import pytest - -from supervisor.dbus.payloads.generate import interface_update_payload -from supervisor.host.const import AuthMethod, InterfaceMethod, InterfaceType, WifiMode -from supervisor.host.network import VlanConfig, WifiConfig -from supervisor.utils.gdbus import DBus - -from tests.const import TEST_INTERFACE - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - data = interface_update_payload(interface) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto" - - assert ( - DBus.parse_gvariant(data)["802-3-ethernet"]["assigned-mac-address"] - == "preserve" - ) - - assert DBus.parse_gvariant(data)["connection"]["mdns"] == 2 - assert DBus.parse_gvariant(data)["connection"]["llmnr"] == 2 - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv4(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv4.method = InterfaceMethod.STATIC - interface.ipv4.address = [ip_interface("192.168.1.1/24")] - interface.ipv4.nameservers = [ip_address("1.1.1.1"), ip_address("1.0.1.1")] - interface.ipv4.gateway = ip_address("192.168.1.1") - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "manual" - assert ( - DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["address"] == "192.168.1.1" - ) - assert DBus.parse_gvariant(data)["ipv4"]["address-data"][0]["prefix"] == 24 - assert DBus.parse_gvariant(data)["ipv4"]["dns"] == [16843009, 16842753] - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - assert DBus.parse_gvariant(data)["ipv4"]["gateway"] == "192.168.1.1" - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv4_disabled(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv4.method = InterfaceMethod.DISABLED - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "disabled" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv4_auto(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv4.method = InterfaceMethod.AUTO - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv6(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv6.method = InterfaceMethod.STATIC - interface.ipv6.address = [ip_interface("2a03:169:3df5:0:6be9:2588:b26a:a679/64")] - interface.ipv6.nameservers = [ - ip_address("2606:4700:4700::64"), - ip_address("2606:4700:4700::6400"), - ip_address("2606:4700:4700::1111"), - ] - interface.ipv6.gateway = ip_address("fe80::da58:d7ff:fe00:9c69") - - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "manual" - assert ( - DBus.parse_gvariant(data)["ipv6"]["address-data"][0]["address"] - == "2a03:169:3df5:0:6be9:2588:b26a:a679" - ) - assert DBus.parse_gvariant(data)["ipv6"]["address-data"][0]["prefix"] == 64 - assert DBus.parse_gvariant(data)["ipv6"]["dns"] == [ - [38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100], - [38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0], - [38, 6, 71, 0, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17], - ] - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - assert DBus.parse_gvariant(data)["ipv6"]["gateway"] == "fe80::da58:d7ff:fe00:9c69" - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv6_disabled(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv6.method = InterfaceMethod.DISABLED - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "disabled" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_ethernet_ipv6_auto(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - inet = coresys.dbus.network.interfaces[TEST_INTERFACE] - - interface.ipv6.method = InterfaceMethod.AUTO - data = interface_update_payload( - interface, - name=inet.settings.connection.id, - uuid=inet.settings.connection.uuid, - ) - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto" - assert ( - DBus.parse_gvariant(data)["connection"]["uuid"] == inet.settings.connection.uuid - ) - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0" - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-3-ethernet" - assert DBus.parse_gvariant(data)["connection"]["interface-name"] == interface.name - - -@pytest.mark.asyncio -async def test_interface_update_payload_wireless_wpa_psk(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.WIRELESS - interface.wifi = WifiConfig( - WifiMode.INFRASTRUCTURE, "Test", AuthMethod.WPA_PSK, "password", 0 - ) - - data = interface_update_payload(interface) - - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless" - assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116] - assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure" - - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["auth-alg"] == "open" - assert ( - DBus.parse_gvariant(data)["802-11-wireless-security"]["key-mgmt"] == "wpa-psk" - ) - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["psk"] == "password" - - -@pytest.mark.asyncio -async def test_interface_update_payload_wireless_web(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.WIRELESS - interface.wifi = WifiConfig( - WifiMode.INFRASTRUCTURE, "Test", AuthMethod.WEP, "password", 0 - ) - - data = interface_update_payload(interface) - - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless" - assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116] - assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure" - - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["auth-alg"] == "none" - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["key-mgmt"] == "none" - assert DBus.parse_gvariant(data)["802-11-wireless-security"]["psk"] == "password" - - -@pytest.mark.asyncio -async def test_interface_update_payload_wireless_open(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.WIRELESS - interface.wifi = WifiConfig( - WifiMode.INFRASTRUCTURE, "Test", AuthMethod.OPEN, None, 0 - ) - - data = interface_update_payload(interface) - - assert DBus.parse_gvariant(data)["connection"]["type"] == "802-11-wireless" - assert DBus.parse_gvariant(data)["802-11-wireless"]["ssid"] == [84, 101, 115, 116] - assert DBus.parse_gvariant(data)["802-11-wireless"]["mode"] == "infrastructure" - assert ( - DBus.parse_gvariant(data)["802-11-wireless"]["assigned-mac-address"] - == "preserve" - ) - assert "802-11-wireless-security" not in DBus.parse_gvariant(data) - - -@pytest.mark.asyncio -async def test_interface_update_payload_vlan(coresys): - """Test interface update payload.""" - interface = coresys.host.network.get(TEST_INTERFACE) - - interface.type = InterfaceType.VLAN - interface.vlan = VlanConfig(10, interface.name) - - data = interface_update_payload(interface) - assert DBus.parse_gvariant(data)["ipv4"]["method"] == "auto" - assert DBus.parse_gvariant(data)["ipv6"]["method"] == "auto" - - assert DBus.parse_gvariant(data)["vlan"]["id"] == 10 - assert DBus.parse_gvariant(data)["vlan"]["parent"] == interface.name - assert DBus.parse_gvariant(data)["connection"]["type"] == "vlan" - assert DBus.parse_gvariant(data)["connection"]["id"] == "Supervisor eth0.10" - assert "interface-name" not in DBus.parse_gvariant(data)["connection"] diff --git a/tests/dbus/test_systemd.py b/tests/dbus/test_systemd.py index 776d2a2ce..d0c5f66c7 100644 --- a/tests/dbus/test_systemd.py +++ b/tests/dbus/test_systemd.py @@ -20,7 +20,7 @@ async def test_dbus_systemd_info(coresys: CoreSys): f"{DBUS_NAME_SYSTEMD.replace('.', '_')}_properties.json" ) - with patch("supervisor.utils.gdbus.DBus.get_properties", new=mock_get_properties): + with patch("supervisor.utils.dbus.DBus.get_properties", new=mock_get_properties): await coresys.dbus.systemd.update() assert coresys.dbus.systemd.boot_timestamp == 1632236713344227 diff --git a/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_AppArmor-LoadProfile.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor-LoadProfile.json b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor-LoadProfile.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.json b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_AppArmor-UnloadProfile.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.json b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_CGroup-AddDevicesAllowed.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.json b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk-ChangeDevice.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.json b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_DataDisk-ReloadDevice.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture deleted file mode 100644 index 3c2c10f15..000000000 --- a/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.fixture +++ /dev/null @@ -1 +0,0 @@ -(,) \ No newline at end of file diff --git a/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.json b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.json new file mode 100644 index 000000000..de601e305 --- /dev/null +++ b/tests/fixtures/io_hass_os_System-ScheduleWipeDevice.json @@ -0,0 +1 @@ +[true] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.fixture b/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.json b/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager-ActivateConnection.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.fixture b/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.json b/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager-AddAndActivateConnection.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.fixture b/tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.json similarity index 100% rename from tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.fixture rename to tests/fixtures/org_freedesktop_NetworkManager-CheckConnectivity.json diff --git a/tests/fixtures/org_freedesktop_NetworkManager.fixture b/tests/fixtures/org_freedesktop_NetworkManager.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json index 7b4e7b6b1..5f738e36e 100644 --- a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json +++ b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43099.json @@ -7,6 +7,6 @@ "HwAddress": "E4:57:40:A9:D7:DE", "Mode": 2, "MaxBitrate": 195000, - "Strength": [47], + "Strength": 47, "LastSeen": 1398776 } diff --git a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json index 15e1efdf6..161c3edfb 100644 --- a/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json +++ b/tests/fixtures/org_freedesktop_NetworkManager_AccessPoint_43100.json @@ -7,6 +7,6 @@ "HwAddress": "18:4B:0D:23:A1:9C", "Mode": 2, "MaxBitrate": 540000, - "Strength": [63], + "Strength": 63, "LastSeen": 1398839 } diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture deleted file mode 100644 index 4758cdca3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.fixture +++ /dev/null @@ -1 +0,0 @@ -([objectpath '/org/freedesktop/NetworkManager/AccessPoint/43099', '/org/freedesktop/NetworkManager/AccessPoint/43100'],) \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json new file mode 100644 index 000000000..d1aabbfbc --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-GetAllAccessPoints.json @@ -0,0 +1 @@ +[["/org/freedesktop/NetworkManager/AccessPoint/43099", "/org/freedesktop/NetworkManager/AccessPoint/43100"]] diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.json b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Devices_3-RequestScan.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.fixture deleted file mode 100644 index 6a452c185..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.fixture +++ /dev/null @@ -1 +0,0 @@ -() diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.json b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Delete.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.fixture deleted file mode 100644 index 233343596..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.fixture +++ /dev/null @@ -1 +0,0 @@ -({'connection': {'id': <'Wired connection 1'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-3-ethernet'>, 'uuid': <'0c23631e-2118-355c-bbb0-8943229cb0d6'>}, 'ipv4': {'address-data': <[{'address': <'192.168.2.148'>, 'prefix': }]>, 'addresses': <[[uint32 2483202240, 24, 16951488]]>, 'dns': <[uint32 16951488]>, 'dns-search': <@as []>, 'gateway': <'192.168.2.1'>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, 'proxy': {}, '802-3-ethernet': {'auto-negotiate': , 'mac-address-blacklist': <@as []>, 's390-options': <@a{ss} {}>}, '802-11-wireless': {'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}},) \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.json b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.json new file mode 100644 index 000000000..1ada5fab7 --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-GetSettings.json @@ -0,0 +1 @@ +[{"connection": {"id": "Wired connection 1", "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": [], "routes": []}, "ipv6": {"address-data": [], "addresses": [], "dns": [], "dns-search": [], "method": "auto", "route-data": [], "routes": []}, "proxy": {}, "802-3-ethernet": {"auto-negotiate": false, "mac-address-blacklist": [], "s390-options": {}}, "802-11-wireless": {"ssid": [78, 69, 84, 84]}}] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.json b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_NetworkManager_Settings_1-Update.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.fixture b/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.json b/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_hostname1-SetStaticHostname.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetNTP.fixture b/tests/fixtures/org_freedesktop_timedate1-SetNTP.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_timedate1-SetNTP.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetNTP.json b/tests/fixtures/org_freedesktop_timedate1-SetNTP.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_timedate1-SetNTP.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetTime.fixture b/tests/fixtures/org_freedesktop_timedate1-SetTime.fixture deleted file mode 100644 index dd626a0f3..000000000 --- a/tests/fixtures/org_freedesktop_timedate1-SetTime.fixture +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/tests/fixtures/org_freedesktop_timedate1-SetTime.json b/tests/fixtures/org_freedesktop_timedate1-SetTime.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/tests/fixtures/org_freedesktop_timedate1-SetTime.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/tests/host/test_connectivity.py b/tests/host/test_connectivity.py index f252a7462..321871e7f 100644 --- a/tests/host/test_connectivity.py +++ b/tests/host/test_connectivity.py @@ -7,13 +7,13 @@ from supervisor.coresys import CoreSys async def test_connectivity_not_connected(coresys: CoreSys): """Test host unknown connectivity.""" - with patch("supervisor.utils.gdbus.DBus._send", 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 async def test_connectivity_connected(coresys: CoreSys): """Test host full connectivity.""" - with patch("supervisor.utils.gdbus.DBus._send", return_value="[4]"): + with patch("supervisor.utils.dbus.DBus.call_dbus", return_value=[4]): await coresys.host.network.check_connectivity() assert coresys.host.network.connectivity diff --git a/tests/utils/test_dbus.py b/tests/utils/test_dbus.py new file mode 100644 index 000000000..5e4a8936e --- /dev/null +++ b/tests/utils/test_dbus.py @@ -0,0 +1,29 @@ +"""Check dbus-next implementation.""" +from dbus_next.signature import Variant + +from supervisor.coresys import CoreSys +from supervisor.utils.dbus import DBus, _remove_dbus_signature + + +def test_remove_dbus_signature(): + """Check D-Bus signature clean-up.""" + test = _remove_dbus_signature(Variant("s", "Value")) + assert isinstance(test, str) + assert test == "Value" + + test_dict = _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")]) + 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("org.freedesktop.systemd1", "/org/freedesktop/systemd1") + signature, args = dbus._prepare_args( + True, 1, 1.0, "Value", ("a{sv}", {"Key": "Value"}) + ) + assert signature == "bidsa{sv}" diff --git a/tests/utils/test_gvariant_parser.py b/tests/utils/test_gvariant_parser.py deleted file mode 100644 index 3d705114f..000000000 --- a/tests/utils/test_gvariant_parser.py +++ /dev/null @@ -1,497 +0,0 @@ -"""Test gdbus gvariant parser.""" -from supervisor.utils.gdbus import DBus - - -def test_simple_return(): - """Test Simple return value.""" - raw = "(objectpath '/org/freedesktop/systemd1/job/35383',)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == ["/org/freedesktop/systemd1/job/35383"] - - -def test_get_property(): - """Test Property parsing.""" - raw = "({'Hostname': <'hassio'>, 'StaticHostname': <'hassio'>, 'PrettyHostname': <''>, 'IconName': <'computer-embedded'>, 'Chassis': <'embedded'>, 'Deployment': <'production'>, 'Location': <''>, 'KernelName': <'Linux'>, 'KernelRelease': <'4.14.98-v7'>, 'KernelVersion': <'#1 SMP Sat May 11 02:17:06 UTC 2019'>, 'OperatingSystemPrettyName': <'HassOS 2.12'>, 'OperatingSystemCPEName': <'cpe:2.3:o:home_assistant:hassos:2.12:*:production:*:*:*:rpi3:*'>, 'HomeURL': <'https://hass.io/'>},)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data[0] == { - "Hostname": "hassio", - "StaticHostname": "hassio", - "PrettyHostname": "", - "IconName": "computer-embedded", - "Chassis": "embedded", - "Deployment": "production", - "Location": "", - "KernelName": "Linux", - "KernelRelease": "4.14.98-v7", - "KernelVersion": "#1 SMP Sat May 11 02:17:06 UTC 2019", - "OperatingSystemPrettyName": "HassOS 2.12", - "OperatingSystemCPEName": "cpe:2.3:o:home_assistant:hassos:2.12:*:production:*:*:*:rpi3:*", - "HomeURL": "https://hass.io/", - } - - -def test_systemd_unitlist_simple(): - """Test Systemd Unit list simple.""" - raw = "([('systemd-remount-fs.service', 'Remount Root and Kernel File Systems', 'loaded', 'active', 'exited', '', objectpath '/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice', uint32 0, '', objectpath '/'), ('sys-subsystem-net-devices-veth5714b4e.device', '/sys/subsystem/net/devices/veth5714b4e', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice', 0, '', '/'), ('rauc.service', 'Rauc Update Service', 'loaded', 'active', 'running', '', '/org/freedesktop/systemd1/unit/rauc_2eservice', 0, '', '/'), ('mnt-data-docker-overlay2-7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2-merged.mount', '/mnt/data/docker/overlay2/7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2/merged', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/mnt_2ddata_2ddocker_2doverlay2_2d7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2_2dmerged_2emount', 0, '', '/'), ('hassos-hardware.target', 'HassOS hardware targets', 'loaded', 'active', 'active', '', '/org/freedesktop/systemd1/unit/hassos_2dhardware_2etarget', 0, '', '/'), ('dev-zram1.device', '/dev/zram1', 'loaded', 'active', 'plugged', 'sys-devices-virtual-block-zram1.device', '/org/freedesktop/systemd1/unit/dev_2dzram1_2edevice', 0, '', '/'), ('sys-subsystem-net-devices-hassio.device', '/sys/subsystem/net/devices/hassio', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dhassio_2edevice', 0, '', '/'), ('cryptsetup.target', 'cryptsetup.target', 'not-found', 'inactive', 'dead', '', '/org/freedesktop/systemd1/unit/cryptsetup_2etarget', 0, '', '/'), ('sys-devices-virtual-net-vethd256dfa.device', '/sys/devices/virtual/net/vethd256dfa', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dvethd256dfa_2edevice', 0, '', '/'), ('network-pre.target', 'Network (Pre)', 'loaded', 'inactive', 'dead', '', '/org/freedesktop/systemd1/unit/network_2dpre_2etarget', 0, '', '/'), ('sys-devices-virtual-net-veth5714b4e.device', '/sys/devices/virtual/net/veth5714b4e', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dveth5714b4e_2edevice', 0, '', '/'), ('sys-kernel-debug.mount', 'Kernel Debug File System', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/sys_2dkernel_2ddebug_2emount', 0, '', '/'), ('slices.target', 'Slices', 'loaded', 'active', 'active', '', '/org/freedesktop/systemd1/unit/slices_2etarget', 0, '', '/'), ('etc-NetworkManager-system\x2dconnections.mount', 'NetworkManager persistent system connections', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/etc_2dNetworkManager_2dsystem_5cx2dconnections_2emount', 0, '', '/'), ('run-docker-netns-26ede3178729.mount', '/run/docker/netns/26ede3178729', 'loaded', 'active', 'mounted', '', '/org/freedesktop/systemd1/unit/run_2ddocker_2dnetns_2d26ede3178729_2emount', 0, '', '/'), ('dev-disk-by\x2dpath-platform\x2d3f202000.mmc\x2dpart2.device', '/dev/disk/by-path/platform-3f202000.mmc-part2', 'loaded', 'active', 'plugged', 'sys-devices-platform-soc-3f202000.mmc-mmc_host-mmc0-mmc0:e624-block-mmcblk0-mmcblk0p2.device', '/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dplatform_5cx2d3f202000_2emmc_5cx2dpart2_2edevice', 0, '', '/')],)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [ - [ - [ - "systemd-remount-fs.service", - "Remount Root and Kernel File Systems", - "loaded", - "active", - "exited", - "", - "/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice", - 0, - "", - "/", - ], - [ - "sys-subsystem-net-devices-veth5714b4e.device", - "/sys/subsystem/net/devices/veth5714b4e", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice", - 0, - "", - "/", - ], - [ - "rauc.service", - "Rauc Update Service", - "loaded", - "active", - "running", - "", - "/org/freedesktop/systemd1/unit/rauc_2eservice", - 0, - "", - "/", - ], - [ - "mnt-data-docker-overlay2-7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2-merged.mount", - "/mnt/data/docker/overlay2/7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2/merged", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/mnt_2ddata_2ddocker_2doverlay2_2d7493c48dd99ab0e68420e3317d93711630dd55a76d4f2a21863a220031203ac2_2dmerged_2emount", - 0, - "", - "/", - ], - [ - "hassos-hardware.target", - "HassOS hardware targets", - "loaded", - "active", - "active", - "", - "/org/freedesktop/systemd1/unit/hassos_2dhardware_2etarget", - 0, - "", - "/", - ], - [ - "dev-zram1.device", - "/dev/zram1", - "loaded", - "active", - "plugged", - "sys-devices-virtual-block-zram1.device", - "/org/freedesktop/systemd1/unit/dev_2dzram1_2edevice", - 0, - "", - "/", - ], - [ - "sys-subsystem-net-devices-hassio.device", - "/sys/subsystem/net/devices/hassio", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dhassio_2edevice", - 0, - "", - "/", - ], - [ - "cryptsetup.target", - "cryptsetup.target", - "not-found", - "inactive", - "dead", - "", - "/org/freedesktop/systemd1/unit/cryptsetup_2etarget", - 0, - "", - "/", - ], - [ - "sys-devices-virtual-net-vethd256dfa.device", - "/sys/devices/virtual/net/vethd256dfa", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dvethd256dfa_2edevice", - 0, - "", - "/", - ], - [ - "network-pre.target", - "Network (Pre)", - "loaded", - "inactive", - "dead", - "", - "/org/freedesktop/systemd1/unit/network_2dpre_2etarget", - 0, - "", - "/", - ], - [ - "sys-devices-virtual-net-veth5714b4e.device", - "/sys/devices/virtual/net/veth5714b4e", - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2ddevices_2dvirtual_2dnet_2dveth5714b4e_2edevice", - 0, - "", - "/", - ], - [ - "sys-kernel-debug.mount", - "Kernel Debug File System", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/sys_2dkernel_2ddebug_2emount", - 0, - "", - "/", - ], - [ - "slices.target", - "Slices", - "loaded", - "active", - "active", - "", - "/org/freedesktop/systemd1/unit/slices_2etarget", - 0, - "", - "/", - ], - [ - "etc-NetworkManager-system-connections.mount", - "NetworkManager persistent system connections", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/etc_2dNetworkManager_2dsystem_5cx2dconnections_2emount", - 0, - "", - "/", - ], - [ - "run-docker-netns-26ede3178729.mount", - "/run/docker/netns/26ede3178729", - "loaded", - "active", - "mounted", - "", - "/org/freedesktop/systemd1/unit/run_2ddocker_2dnetns_2d26ede3178729_2emount", - 0, - "", - "/", - ], - [ - "dev-disk-by-path-platform-3f202000.mmc-part2.device", - "/dev/disk/by-path/platform-3f202000.mmc-part2", - "loaded", - "active", - "plugged", - "sys-devices-platform-soc-3f202000.mmc-mmc_host-mmc0-mmc0:e624-block-mmcblk0-mmcblk0p2.device", - "/org/freedesktop/systemd1/unit/dev_2ddisk_2dby_5cx2dpath_2dplatform_5cx2d3f202000_2emmc_5cx2dpart2_2edevice", - 0, - "", - "/", - ], - ] - ] - - -def test_systemd_unitlist_complex(): - """Test Systemd Unit list simple.""" - raw = "([('systemd-remount-fs.service', 'Remount Root and \"Kernel File Systems\"', 'loaded', 'active', 'exited', '', objectpath '/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice', uint32 0, '', objectpath '/'), ('sys-subsystem-net-devices-veth5714b4e.device', '/sys/subsystem/net/devices/veth5714b4e for \" is', 'loaded', 'active', 'plugged', '', '/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice', 0, '', '/')],)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [ - [ - [ - "systemd-remount-fs.service", - 'Remount Root and "Kernel File Systems"', - "loaded", - "active", - "exited", - "", - "/org/freedesktop/systemd1/unit/systemd_2dremount_2dfs_2eservice", - 0, - "", - "/", - ], - [ - "sys-subsystem-net-devices-veth5714b4e.device", - '/sys/subsystem/net/devices/veth5714b4e for " is', - "loaded", - "active", - "plugged", - "", - "/org/freedesktop/systemd1/unit/sys_2dsubsystem_2dnet_2ddevices_2dveth5714b4e_2edevice", - 0, - "", - "/", - ], - ] - ] - - -def test_networkmanager_dns_properties(): - """Test NetworkManager DNS properties.""" - raw = "({'Mode': <'default'>, 'RcManager': <'file'>, 'Configuration': <[{'nameservers': <['192.168.23.30']>, 'domains': <['syshack.local']>, 'interface': <'eth0'>, 'priority': <100>, 'vpn': }]>},)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "Mode": "default", - "RcManager": "file", - "Configuration": [ - { - "nameservers": ["192.168.23.30"], - "domains": ["syshack.local"], - "interface": "eth0", - "priority": 100, - "vpn": False, - } - ], - } - ] - - -def test_networkmanager_dns_properties_empty(): - """Test NetworkManager DNS properties.""" - raw = "({'Mode': <'default'>, 'RcManager': <'resolvconf'>, 'Configuration': <@aa{sv} []>},)" - - # parse data - data = DBus.parse_gvariant(raw) - - assert data == [{"Mode": "default", "RcManager": "resolvconf", "Configuration": []}] - - -def test_networkmanager_binary_data(): - """Test NetworkManager Binary datastrings.""" - raw = "({'802-11-wireless': {'mac-address-blacklist': <@as []>, 'mode': <'infrastructure'>, 'security': <'802-11-wireless-security'>, 'seen-bssids': <['7C:2E:BD:98:1B:06']>, 'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}, 'connection': {'id': <'NETT'>, 'interface-name': <'wlan0'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-11-wireless'>, 'uuid': <'13f9af79-a6e9-4e07-9353-165ad57bf1a8'>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, '802-11-wireless-security': {'auth-alg': <'open'>, 'key-mgmt': <'wpa-psk'>}, 'ipv4': {'address-data': <@aa{sv} []>, 'addresses': <@aau []>, 'dns': <@au []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'proxy': {}},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "802-11-wireless": { - "mac-address-blacklist": [], - "mode": "infrastructure", - "security": "802-11-wireless-security", - "seen-bssids": ["7C:2E:BD:98:1B:06"], - "ssid": [78, 69, 84, 84], - }, - "connection": { - "id": "NETT", - "interface-name": "wlan0", - "permissions": [], - "timestamp": 1598526799, - "type": "802-11-wireless", - "uuid": "13f9af79-a6e9-4e07-9353-165ad57bf1a8", - }, - "ipv6": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "802-11-wireless-security": {"auth-alg": "open", "key-mgmt": "wpa-psk"}, - "ipv4": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "proxy": {}, - } - ] - - raw = "({'802-11-wireless': {'mac-address-blacklist': <@as []>, 'mac-address': <[byte 0xca, 0x0b, 0x61, 0x00, 0xd8, 0xbd]>, 'mode': <'infrastructure'>, 'security': <'802-11-wireless-security'>, 'seen-bssids': <['7C:2E:BD:98:1B:06']>, 'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}, 'connection': {'id': <'NETT'>, 'interface-name': <'wlan0'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-11-wireless'>, 'uuid': <'13f9af79-a6e9-4e07-9353-165ad57bf1a8'>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, '802-11-wireless-security': {'auth-alg': <'open'>, 'key-mgmt': <'wpa-psk'>}, 'ipv4': {'address-data': <@aa{sv} []>, 'addresses': <@aau []>, 'dns': <@au []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'proxy': {}},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "802-11-wireless": { - "mac-address": [202, 11, 97, 0, 216, 189], - "mac-address-blacklist": [], - "mode": "infrastructure", - "security": "802-11-wireless-security", - "seen-bssids": ["7C:2E:BD:98:1B:06"], - "ssid": [78, 69, 84, 84], - }, - "802-11-wireless-security": {"auth-alg": "open", "key-mgmt": "wpa-psk"}, - "connection": { - "id": "NETT", - "interface-name": "wlan0", - "permissions": [], - "timestamp": 1598526799, - "type": "802-11-wireless", - "uuid": "13f9af79-a6e9-4e07-9353-165ad57bf1a8", - }, - "ipv4": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "ipv6": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "proxy": {}, - } - ] - - -def test_networkmanager_binary_string_data(): - """Test NetworkManager Binary string datastrings.""" - raw = "({'802-11-wireless': {'mac-address-blacklist': <@as []>, 'mac-address': , 'mode': <'infrastructure'>, 'security': <'802-11-wireless-security'>, 'seen-bssids': <['7C:2E:BD:98:1B:06']>, 'ssid': <[byte 0x4e, 0x45, 0x54, 0x54]>}, 'connection': {'id': <'NETT'>, 'interface-name': <'wlan0'>, 'permissions': <@as []>, 'timestamp': , 'type': <'802-11-wireless'>, 'uuid': <'13f9af79-a6e9-4e07-9353-165ad57bf1a8'>}, 'ipv6': {'address-data': <@aa{sv} []>, 'addresses': <@a(ayuay) []>, 'dns': <@aay []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@a(ayuayu) []>}, '802-11-wireless-security': {'auth-alg': <'open'>, 'key-mgmt': <'wpa-psk'>}, 'ipv4': {'address-data': <@aa{sv} []>, 'addresses': <@aau []>, 'dns': <@au []>, 'dns-search': <@as []>, 'method': <'auto'>, 'route-data': <@aa{sv} []>, 'routes': <@aau []>}, 'proxy': {}},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "802-11-wireless": { - "mac-address": [42, 126, 95, 29, 195, 137], - "mac-address-blacklist": [], - "mode": "infrastructure", - "security": "802-11-wireless-security", - "seen-bssids": ["7C:2E:BD:98:1B:06"], - "ssid": [78, 69, 84, 84], - }, - "802-11-wireless-security": {"auth-alg": "open", "key-mgmt": "wpa-psk"}, - "connection": { - "id": "NETT", - "interface-name": "wlan0", - "permissions": [], - "timestamp": 1598526799, - "type": "802-11-wireless", - "uuid": "13f9af79-a6e9-4e07-9353-165ad57bf1a8", - }, - "ipv4": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "ipv6": { - "address-data": [], - "addresses": [], - "dns": [], - "dns-search": [], - "method": "auto", - "route-data": [], - "routes": [], - }, - "proxy": {}, - } - ] - - -def test_v6(): - """Test IPv6 Property.""" - raw = "({'addresses': <[([byte 0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10], uint32 64, [byte 0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])]>, 'dns': <[[byte 0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05], [0x20, 0x01, 0x04, 0x70, 0x79, 0x2d, 0x00, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05]]>})" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "addresses": [ - [ - [32, 1, 4, 112, 121, 45, 0, 1, 0, 18, 0, 0, 0, 0, 0, 16], - 64, - [32, 1, 4, 112, 121, 45, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1], - ] - ], - "dns": [ - [32, 1, 4, 112, 121, 45, 0, 1, 0, 18, 0, 0, 0, 0, 0, 5], - [32, 1, 4, 112, 121, 45, 0, 1, 0, 18, 0, 0, 0, 0, 0, 5], - ], - } - ] - - -def test_single_byte(): - """Test a singlebyte response.""" - raw = "({'Flags': , 'WpaFlags': , 'RsnFlags': , 'Ssid': <[byte 0x53, 0x59, 0x53, 0x48, 0x41, 0x43, 0x4b, 0x5f, 0x48, 0x6f, 0x6d, 0x65]>, 'Frequency': , 'HwAddress': <'18:4B:0D:A3:A1:9C'>, 'Mode': , 'MaxBitrate': , 'Strength': , 'LastSeen': <1646569>},)" - - data = DBus.parse_gvariant(raw) - - assert data == [ - { - "Flags": 1, - "Frequency": 5660, - "HwAddress": "18:4B:0D:A3:A1:9C", - "LastSeen": 1646569, - "MaxBitrate": 540000, - "Mode": 2, - "RsnFlags": 392, - "Ssid": [83, 89, 83, 72, 65, 67, 75, 95, 72, 111, 109, 101], - "Strength": [44], - "WpaFlags": 0, - } - ]