Improve D-Bus error handling for NetworkManager (#4720)

* Improve D-Bus error handling for NetworkManager

There are quite some errors captured which are related by seemingly a
suddenly missing NetworkManager. Errors appear as:
23-11-21 17:42:50 ERROR (MainThread) [supervisor.dbus.network] Error while processing /org/freedesktop/NetworkManager/Devices/10: Remote peer disconnected
...
23-11-21 17:42:50 ERROR (MainThread) [supervisor.dbus.network] Error while processing /org/freedesktop/NetworkManager/Devices/35: The name is not activatable

Both errors seem to already happen at introspection time, however
the current code doesn't converts these errors to Supervisor issues.
This PR uses the already existing `DBus.from_dbus_error()`.

Furthermore this adds a new Exception `DBusNoReplyError` for the
`ErrorType.NO_REPLY` (or `org.freedesktop.DBus.Error.NoReply` in
D-Bus terms, which is the type of the first of the two issues above).

And finally it separates the `ErrorType.SERVICE_UNKNOWN` (or
`org.freedesktop.DBus.Error.ServiceUnknown` in D-Bus terms, which is
the second of the above issue) from `DBusInterfaceError` into a new
`DBusServiceUnkownError`.

This allows to handle errors more specifically.

To avoid too much churn, all instances where `DBusInterfaceError`
got handled, we are now also handling `DBusServiceUnkownError`.

The `DBusNoReplyError` and `DBusServiceUnkownError` appear when
the NetworkManager service stops or crashes. Instead of retrying
every interface we know, just give up if one of these issues appear.
This should significantly lower error messages users are seeing
and Sentry events.

* Remove unnecessary statement

* Fix pytests

* Make sure error strings are compared correctly

* Fix typo/remove unnecessary pylint exception

* Fix DBusError typing

* Add pytest for from_dbus_error

* Revert "Make sure error strings are compared correctly"

This reverts commit 10dc2e4c3887532921414b4291fe3987186db408.

* Add test cases

---------

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
This commit is contained in:
Stefan Agner 2023-11-27 23:32:11 +01:00 committed by GitHub
parent 172a7053ed
commit 9088810b49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 232 additions and 41 deletions

View File

@ -6,7 +6,7 @@ from typing import Any
from awesomeversion import AwesomeVersion
from dbus_fast.aio.message_bus import MessageBus
from ...exceptions import DBusError, DBusInterfaceError
from ...exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from ..const import (
DBUS_ATTR_DIAGNOSTICS,
DBUS_ATTR_VERSION,
@ -99,7 +99,7 @@ class OSAgent(DBusInterfaceProxy):
await asyncio.gather(*[dbus.connect(bus) for dbus in self.all])
except DBusError:
_LOGGER.warning("Can't connect to OS-Agent")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No OS-Agent support on the host. Some Host functions have been disabled."
)

View File

@ -3,7 +3,7 @@ import logging
from dbus_fast.aio.message_bus import MessageBus
from ..exceptions import DBusError, DBusInterfaceError
from ..exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from .const import (
DBUS_ATTR_CHASSIS,
DBUS_ATTR_DEPLOYMENT,
@ -39,7 +39,7 @@ class Hostname(DBusInterfaceProxy):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to systemd-hostname")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No hostname support on the host. Hostname functions have been disabled."
)

View File

@ -3,7 +3,7 @@ import logging
from dbus_fast.aio.message_bus import MessageBus
from ..exceptions import DBusError, DBusInterfaceError
from ..exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from .const import DBUS_NAME_LOGIND, DBUS_OBJECT_LOGIND
from .interface import DBusInterface
from .utils import dbus_connected
@ -28,8 +28,8 @@ class Logind(DBusInterface):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to systemd-logind")
except DBusInterfaceError:
_LOGGER.info("No systemd-logind support on the host.")
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning("No systemd-logind support on the host.")
@dbus_connected
async def reboot(self) -> None:

View File

@ -9,6 +9,8 @@ from ...exceptions import (
DBusError,
DBusFatalError,
DBusInterfaceError,
DBusNoReplyError,
DBusServiceUnkownError,
HostNotSupportedError,
NetworkInterfaceNotFound,
)
@ -143,7 +145,7 @@ class NetworkManager(DBusInterfaceProxy):
await self.settings.connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to Network Manager")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No Network Manager support on the host. Local network functions have been disabled."
)
@ -210,8 +212,22 @@ class NetworkManager(DBusInterfaceProxy):
# try to query it. Ignore those cases.
_LOGGER.debug("Can't process %s: %s", device, err)
continue
except (
DBusNoReplyError,
DBusServiceUnkownError,
) as err:
# This typically means that NetworkManager disappeared. Give up immeaditly.
_LOGGER.error(
"NetworkManager not responding while processing %s: %s. Giving up.",
device,
err,
)
capture_exception(err)
return
except Exception as err: # pylint: disable=broad-except
_LOGGER.exception("Error while processing %s: %s", device, err)
_LOGGER.exception(
"Unkown error while processing %s: %s", device, err
)
capture_exception(err)
continue

View File

@ -12,7 +12,7 @@ from ...const import (
ATTR_PRIORITY,
ATTR_VPN,
)
from ...exceptions import DBusError, DBusInterfaceError
from ...exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from ..const import (
DBUS_ATTR_CONFIGURATION,
DBUS_ATTR_MODE,
@ -67,7 +67,7 @@ class NetworkManagerDNS(DBusInterfaceProxy):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to DnsManager")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No DnsManager support on the host. Local DNS functions have been disabled."
)

View File

@ -4,7 +4,7 @@ from typing import Any
from dbus_fast.aio.message_bus import MessageBus
from ...exceptions import DBusError, DBusInterfaceError
from ...exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from ..const import DBUS_NAME_NM, DBUS_OBJECT_SETTINGS
from ..interface import DBusInterface
from ..network.setting import NetworkSetting
@ -28,7 +28,7 @@ class NetworkManagerSettings(DBusInterface):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to Network Manager Settings")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No Network Manager Settings support on the host. Local network functions have been disabled."
)

View File

@ -4,7 +4,7 @@ from typing import Any
from dbus_fast.aio.message_bus import MessageBus
from ..exceptions import DBusError, DBusInterfaceError
from ..exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from ..utils.dbus import DBusSignalWrapper
from .const import (
DBUS_ATTR_BOOT_SLOT,
@ -49,7 +49,7 @@ class Rauc(DBusInterfaceProxy):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to rauc")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning("Host has no rauc support. OTA updates have been disabled.")
@property

View File

@ -5,7 +5,7 @@ import logging
from dbus_fast.aio.message_bus import MessageBus
from ..exceptions import DBusError, DBusInterfaceError
from ..exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from .const import (
DBUS_ATTR_CACHE_STATISTICS,
DBUS_ATTR_CURRENT_DNS_SERVER,
@ -59,7 +59,7 @@ class Resolved(DBusInterfaceProxy):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to systemd-resolved.")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"Host has no systemd-resolved support. DNS will not work correctly."
)

View File

@ -10,6 +10,7 @@ from ..exceptions import (
DBusError,
DBusFatalError,
DBusInterfaceError,
DBusServiceUnkownError,
DBusSystemdNoSuchUnit,
)
from .const import (
@ -86,7 +87,7 @@ class Systemd(DBusInterfaceProxy):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to systemd")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No systemd support on the host. Host control has been disabled."
)

View File

@ -4,7 +4,7 @@ import logging
from dbus_fast.aio.message_bus import MessageBus
from ..exceptions import DBusError, DBusInterfaceError
from ..exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
from ..utils.dt import utc_from_timestamp
from .const import (
DBUS_ATTR_NTP,
@ -63,7 +63,7 @@ class TimeDate(DBusInterfaceProxy):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to systemd-timedate")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No timedate support on the host. Time/Date functions have been disabled."
)

View File

@ -6,7 +6,12 @@ from typing import Any
from awesomeversion import AwesomeVersion
from dbus_fast.aio import MessageBus
from ...exceptions import DBusError, DBusInterfaceError, DBusObjectError
from ...exceptions import (
DBusError,
DBusInterfaceError,
DBusObjectError,
DBusServiceUnkownError,
)
from ..const import (
DBUS_ATTR_SUPPORTED_FILESYSTEMS,
DBUS_ATTR_VERSION,
@ -45,7 +50,7 @@ class UDisks2(DBusInterfaceProxy):
await super().connect(bus)
except DBusError:
_LOGGER.warning("Can't connect to udisks2")
except DBusInterfaceError:
except (DBusServiceUnkownError, DBusInterfaceError):
_LOGGER.warning(
"No udisks2 support on the host. Host control has been disabled."
)

View File

@ -335,6 +335,10 @@ class DBusNotConnectedError(HostNotSupportedError):
"""D-Bus is not connected and call a method."""
class DBusServiceUnkownError(HassioNotSupportedError):
"""D-Bus service was not available."""
class DBusInterfaceError(HassioNotSupportedError):
"""D-Bus interface not connected."""
@ -363,6 +367,10 @@ class DBusTimeoutError(DBusError):
"""D-Bus call timed out."""
class DBusNoReplyError(DBusError):
"""D-Bus remote didn't reply/disconnected."""
class DBusFatalError(DBusError):
"""D-Bus call going wrong.

View File

@ -15,18 +15,21 @@ from dbus_fast import (
)
from dbus_fast.aio.message_bus import MessageBus
from dbus_fast.aio.proxy_object import ProxyInterface, ProxyObject
from dbus_fast.errors import DBusError
from dbus_fast.errors import DBusError as DBusFastDBusError
from dbus_fast.introspection import Node
from ..exceptions import (
DBusError,
DBusFatalError,
DBusInterfaceError,
DBusInterfaceMethodError,
DBusInterfacePropertyError,
DBusInterfaceSignalError,
DBusNoReplyError,
DBusNotConnectedError,
DBusObjectError,
DBusParseError,
DBusServiceUnkownError,
DBusTimeoutError,
HassioNotSupportedError,
)
@ -62,9 +65,11 @@ class DBus:
return self
@staticmethod
def from_dbus_error(err: DBusError) -> HassioNotSupportedError | DBusError:
def from_dbus_error(err: DBusFastDBusError) -> HassioNotSupportedError | DBusError:
"""Return correct dbus error based on type."""
if err.type in {ErrorType.SERVICE_UNKNOWN, ErrorType.UNKNOWN_INTERFACE}:
if err.type == ErrorType.SERVICE_UNKNOWN:
return DBusServiceUnkownError(err.text)
if err.type == ErrorType.UNKNOWN_INTERFACE:
return DBusInterfaceError(err.text)
if err.type in {
ErrorType.UNKNOWN_METHOD,
@ -80,6 +85,8 @@ class DBus:
return DBusNotConnectedError(err.text)
if err.type == ErrorType.TIMEOUT:
return DBusTimeoutError(err.text)
if err.type == ErrorType.NO_REPLY:
return DBusNoReplyError(err.text)
return DBusFatalError(err.text, type_=err.type)
@staticmethod
@ -102,7 +109,7 @@ class DBus:
*args, unpack_variants=True
)
return await getattr(proxy_interface, method)(*args)
except DBusError as err:
except DBusFastDBusError as err:
raise DBus.from_dbus_error(err)
except Exception as err: # pylint: disable=broad-except
capture_exception(err)
@ -126,6 +133,8 @@ class DBus:
raise DBusParseError(
f"Can't parse introspect data: {err}", _LOGGER.error
) from err
except DBusFastDBusError as err:
raise DBus.from_dbus_error(err)
except (EOFError, TimeoutError):
_LOGGER.warning(
"Busy system at %s - %s", self.bus_name, self.object_path

View File

@ -5,11 +5,12 @@ import pytest
from supervisor.dbus.agent import OSAgent
from tests.common import mock_dbus_services
from tests.dbus_service_mocks.base import DBusServiceMock
from tests.dbus_service_mocks.os_agent import OSAgent as OSAgentService
@pytest.fixture(name="os_agent_service", autouse=True)
@pytest.fixture(name="os_agent_service")
async def fixture_os_agent_service(
os_agent_services: dict[str, DBusServiceMock]
) -> OSAgentService:
@ -39,3 +40,36 @@ async def test_dbus_osagent(
await os_agent_service.ping()
await os_agent_service.ping()
assert os_agent.diagnostics is True
@pytest.mark.parametrize(
"skip_service",
[
"os_agent",
"agent_apparmor",
"agent_datadisk",
],
)
async def test_dbus_osagent_connect_error(
skip_service: str, dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test OS Agent errors during connect."""
os_agent_services = {
"os_agent": None,
"agent_apparmor": None,
"agent_cgroup": None,
"agent_datadisk": None,
"agent_system": None,
"agent_boards": None,
"agent_boards_yellow": None,
}
os_agent_services.pop(skip_service)
await mock_dbus_services(
os_agent_services,
dbus_session_bus,
)
os_agent = OSAgent()
await os_agent.connect(dbus_session_bus)
assert "No OS-Agent support on the host" in caplog.text

View File

@ -12,7 +12,7 @@ from tests.common import mock_dbus_services
from tests.dbus_service_mocks.network_dns_manager import DnsManager as DnsManagerService
@pytest.fixture(name="dns_manager_service", autouse=True)
@pytest.fixture(name="dns_manager_service")
async def fixture_dns_manager_service(
dbus_session_bus: MessageBus,
) -> DnsManagerService:
@ -49,3 +49,12 @@ async def test_dns(
await dns_manager_service.ping()
await dns_manager_service.ping()
assert dns_manager.mode == "default"
async def test_dbus_dns_connect_error(
dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test connecting to dns error."""
dns_manager = NetworkManagerDNS()
await dns_manager.connect(dbus_session_bus)
assert "No DnsManager support on the host" in caplog.text

View File

@ -9,7 +9,12 @@ import pytest
from supervisor.dbus.const import ConnectionStateType
from supervisor.dbus.network import NetworkManager
from supervisor.dbus.network.interface import NetworkInterface
from supervisor.exceptions import DBusFatalError, DBusParseError, HostNotSupportedError
from supervisor.exceptions import (
DBusFatalError,
DBusParseError,
DBusServiceUnkownError,
HostNotSupportedError,
)
from supervisor.utils.dbus import DBus
from tests.const import TEST_INTERFACE, TEST_INTERFACE_WLAN
@ -20,7 +25,7 @@ from tests.dbus_service_mocks.network_manager import (
)
@pytest.fixture(name="network_manager_service", autouse=True)
@pytest.fixture(name="network_manager_service")
async def fixture_network_manager_service(
network_manager_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
) -> NetworkManagerService:
@ -134,6 +139,7 @@ async def test_removed_devices_disconnect(
async def test_handling_bad_devices(
network_manager_service: NetworkManagerService,
network_manager: NetworkManager,
caplog: pytest.LogCaptureFixture,
capture_exception: Mock,
@ -161,7 +167,7 @@ async def test_handling_bad_devices(
await network_manager.update(
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/102"]}
)
assert f"Error while processing {device}" in caplog.text
assert f"Unkown error while processing {device}" in caplog.text
capture_exception.assert_called_once_with(err)
# We should be able to debug these situations if necessary
@ -207,3 +213,37 @@ async def test_ignore_veth_only_changes(
)
await network_manager_service.ping()
connect.assert_called_once()
async def test_network_manager_stopped(
network_manager_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
network_manager: NetworkManager,
dbus_session_bus: MessageBus,
caplog: pytest.LogCaptureFixture,
capture_exception: Mock,
):
"""Test network manager stopped and dbus service no longer accessible."""
services = list(network_manager_services.values())
while services:
service = services.pop(0)
if isinstance(service, dict):
services.extend(service.values())
else:
dbus_session_bus.unexport(service.object_path, service)
await dbus_session_bus.release_name("org.freedesktop.NetworkManager")
assert network_manager.is_connected is True
await network_manager.update(
{
"Devices": [
"/org/freedesktop/NetworkManager/Devices/9",
"/org/freedesktop/NetworkManager/Devices/15",
"/org/freedesktop/NetworkManager/Devices/20",
"/org/freedesktop/NetworkManager/Devices/35",
]
}
)
capture_exception.assert_called_once()
assert isinstance(capture_exception.call_args.args[0], DBusServiceUnkownError)
assert "NetworkManager not responding" in caplog.text

View File

@ -11,7 +11,7 @@ from tests.dbus_service_mocks.network_connection_settings import SETTINGS_FIXTUR
from tests.dbus_service_mocks.network_settings import Settings as SettingsService
@pytest.fixture(name="settings_service", autouse=True)
@pytest.fixture(name="settings_service")
async def fixture_settings_service(dbus_session_bus: MessageBus) -> SettingsService:
"""Mock Settings service."""
yield (
@ -55,3 +55,12 @@ async def test_reload_connections(
assert await settings.reload_connections() is True
assert settings_service.ReloadConnections.calls == [tuple()]
async def test_dbus_network_settings_connect_error(
dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test connecting to network settings error."""
settings = NetworkManagerSettings()
await settings.connect(dbus_session_bus)
assert "No Network Manager Settings support on the host" in caplog.text

View File

@ -10,7 +10,7 @@ from tests.common import mock_dbus_services
from tests.dbus_service_mocks.hostname import Hostname as HostnameService
@pytest.fixture(name="hostname_service", autouse=True)
@pytest.fixture(name="hostname_service")
async def fixture_hostname_service(dbus_session_bus: MessageBus) -> HostnameService:
"""Mock hostname dbus service."""
yield (await mock_dbus_services({"hostname": None}, dbus_session_bus))["hostname"]
@ -61,3 +61,12 @@ async def test_dbus_sethostname(
assert hostname_service.SetStaticHostname.calls == [("StarWars", False)]
await hostname_service.ping()
assert hostname.hostname == "StarWars"
async def test_dbus_hostname_connect_error(
dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test connecting to hostname error."""
hostname = Hostname()
await hostname.connect(dbus_session_bus)
assert "No hostname support on the host" in caplog.text

View File

@ -10,7 +10,7 @@ from tests.common import mock_dbus_services
from tests.dbus_service_mocks.logind import Logind as LogindService
@pytest.fixture(name="logind_service", autouse=True)
@pytest.fixture(name="logind_service")
async def fixture_logind_service(dbus_session_bus: MessageBus) -> LogindService:
"""Mock logind dbus service."""
yield (await mock_dbus_services({"logind": None}, dbus_session_bus))["logind"]
@ -42,3 +42,12 @@ async def test_power_off(logind_service: LogindService, dbus_session_bus: Messag
assert await logind.power_off() is None
assert logind_service.PowerOff.calls == [(False,)]
async def test_dbus_logind_connect_error(
dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test connecting to logind error."""
logind = Logind()
await logind.connect(dbus_session_bus)
assert "No systemd-logind support on the host" in caplog.text

View File

@ -11,7 +11,7 @@ from tests.common import mock_dbus_services
from tests.dbus_service_mocks.rauc import Rauc as RaucService
@pytest.fixture(name="rauc_service", autouse=True)
@pytest.fixture(name="rauc_service")
async def fixture_rauc_service(dbus_session_bus: MessageBus) -> RaucService:
"""Mock rauc dbus service."""
yield (await mock_dbus_services({"rauc": None}, dbus_session_bus))["rauc"]
@ -41,7 +41,7 @@ async def test_rauc_info(rauc_service: RaucService, dbus_session_bus: MessageBus
assert rauc.last_error == ""
async def test_install(dbus_session_bus: MessageBus):
async def test_install(rauc_service: RaucService, dbus_session_bus: MessageBus):
"""Test install."""
rauc = Rauc()
@ -55,7 +55,7 @@ async def test_install(dbus_session_bus: MessageBus):
assert await signal.wait_for_signal() == [0]
async def test_get_slot_status(dbus_session_bus: MessageBus):
async def test_get_slot_status(rauc_service: RaucService, dbus_session_bus: MessageBus):
"""Test get slot status."""
rauc = Rauc()
@ -76,7 +76,7 @@ async def test_get_slot_status(dbus_session_bus: MessageBus):
assert slot_status[4][1]["bootname"] == "B"
async def test_mark(dbus_session_bus: MessageBus):
async def test_mark(rauc_service: RaucService, dbus_session_bus: MessageBus):
"""Test mark."""
rauc = Rauc()
@ -88,3 +88,12 @@ async def test_mark(dbus_session_bus: MessageBus):
mark = await rauc.mark(RaucState.GOOD, "booted")
assert mark[0] == "kernel.1"
assert mark[1] == "marked slot kernel.1 as good"
async def test_dbus_rauc_connect_error(
dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test connecting to rauc error."""
rauc = Rauc()
await rauc.connect(dbus_session_bus)
assert "Host has no rauc support" in caplog.text

View File

@ -19,7 +19,7 @@ from tests.common import mock_dbus_services
from tests.dbus_service_mocks.resolved import Resolved as ResolvedService
@pytest.fixture(name="resolved_service", autouse=True)
@pytest.fixture(name="resolved_service")
async def fixture_resolved_service(dbus_session_bus: MessageBus) -> ResolvedService:
"""Mock resolved dbus service."""
yield (await mock_dbus_services({"resolved": None}, dbus_session_bus))["resolved"]
@ -102,3 +102,12 @@ async def test_dbus_resolved_info(
await resolved_service.ping()
await resolved_service.ping() # To process the follow-up get all properties call
assert resolved.llmnr_hostname == "homeassistant"
async def test_dbus_resolved_connect_error(
dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test connecting to resolved error."""
resolved = Resolved()
await resolved.connect(dbus_session_bus)
assert "Host has no systemd-resolved support" in caplog.text

View File

@ -12,7 +12,7 @@ from tests.common import mock_dbus_services
from tests.dbus_service_mocks.timedate import TimeDate as TimeDateService
@pytest.fixture(name="timedate_service", autouse=True)
@pytest.fixture(name="timedate_service")
async def fixture_timedate_service(dbus_session_bus: MessageBus) -> TimeDateService:
"""Mock timedate dbus service."""
yield (await mock_dbus_services({"timedate": None}, dbus_session_bus))["timedate"]
@ -81,3 +81,12 @@ async def test_dbus_setntp(
assert timedate_service.SetNTP.calls == [(False, False)]
await timedate_service.ping()
assert timedate.ntp is False
async def test_dbus_timedate_connect_error(
dbus_session_bus: MessageBus, caplog: pytest.LogCaptureFixture
):
"""Test connecting to timedate error."""
timedate = TimeDate()
await timedate.connect(dbus_session_bus)
assert "No timedate support on the host" in caplog.text

View File

@ -2,12 +2,18 @@
from unittest.mock import AsyncMock, Mock, patch
from dbus_fast import ErrorType
from dbus_fast.aio.message_bus import MessageBus
from dbus_fast.errors import DBusError as DBusFastDBusError
from dbus_fast.service import method, signal
import pytest
from supervisor.dbus.const import DBUS_OBJECT_BASE
from supervisor.exceptions import DBusFatalError, DBusInterfaceError
from supervisor.exceptions import (
DBusFatalError,
DBusInterfaceError,
DBusServiceUnkownError,
)
from supervisor.utils.dbus import DBus
from tests.common import load_fixture
@ -172,3 +178,12 @@ async def test_init_proxy(test_service: TestInterface, dbus_session_bus: Message
test_service.signal_test()
await test_service.ping()
assert callback_count == 0
def test_from_dbus_error():
"""Test converting DBus fast errors to Supervisor specific errors."""
dbus_fast_error = DBusFastDBusError(
ErrorType.SERVICE_UNKNOWN, "The name is not activatable"
)
assert type(DBus.from_dbus_error(dbus_fast_error)) is DBusServiceUnkownError