mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 23:26:29 +00:00
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:
parent
172a7053ed
commit
9088810b49
@ -6,7 +6,7 @@ from typing import Any
|
|||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
|
|
||||||
from ...exceptions import DBusError, DBusInterfaceError
|
from ...exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
|
||||||
from ..const import (
|
from ..const import (
|
||||||
DBUS_ATTR_DIAGNOSTICS,
|
DBUS_ATTR_DIAGNOSTICS,
|
||||||
DBUS_ATTR_VERSION,
|
DBUS_ATTR_VERSION,
|
||||||
@ -99,7 +99,7 @@ class OSAgent(DBusInterfaceProxy):
|
|||||||
await asyncio.gather(*[dbus.connect(bus) for dbus in self.all])
|
await asyncio.gather(*[dbus.connect(bus) for dbus in self.all])
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to OS-Agent")
|
_LOGGER.warning("Can't connect to OS-Agent")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No OS-Agent support on the host. Some Host functions have been disabled."
|
"No OS-Agent support on the host. Some Host functions have been disabled."
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
|
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
|
|
||||||
from ..exceptions import DBusError, DBusInterfaceError
|
from ..exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
|
||||||
from .const import (
|
from .const import (
|
||||||
DBUS_ATTR_CHASSIS,
|
DBUS_ATTR_CHASSIS,
|
||||||
DBUS_ATTR_DEPLOYMENT,
|
DBUS_ATTR_DEPLOYMENT,
|
||||||
@ -39,7 +39,7 @@ class Hostname(DBusInterfaceProxy):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to systemd-hostname")
|
_LOGGER.warning("Can't connect to systemd-hostname")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No hostname support on the host. Hostname functions have been disabled."
|
"No hostname support on the host. Hostname functions have been disabled."
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
|
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
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 .const import DBUS_NAME_LOGIND, DBUS_OBJECT_LOGIND
|
||||||
from .interface import DBusInterface
|
from .interface import DBusInterface
|
||||||
from .utils import dbus_connected
|
from .utils import dbus_connected
|
||||||
@ -28,8 +28,8 @@ class Logind(DBusInterface):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to systemd-logind")
|
_LOGGER.warning("Can't connect to systemd-logind")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.info("No systemd-logind support on the host.")
|
_LOGGER.warning("No systemd-logind support on the host.")
|
||||||
|
|
||||||
@dbus_connected
|
@dbus_connected
|
||||||
async def reboot(self) -> None:
|
async def reboot(self) -> None:
|
||||||
|
@ -9,6 +9,8 @@ from ...exceptions import (
|
|||||||
DBusError,
|
DBusError,
|
||||||
DBusFatalError,
|
DBusFatalError,
|
||||||
DBusInterfaceError,
|
DBusInterfaceError,
|
||||||
|
DBusNoReplyError,
|
||||||
|
DBusServiceUnkownError,
|
||||||
HostNotSupportedError,
|
HostNotSupportedError,
|
||||||
NetworkInterfaceNotFound,
|
NetworkInterfaceNotFound,
|
||||||
)
|
)
|
||||||
@ -143,7 +145,7 @@ class NetworkManager(DBusInterfaceProxy):
|
|||||||
await self.settings.connect(bus)
|
await self.settings.connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to Network Manager")
|
_LOGGER.warning("Can't connect to Network Manager")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No Network Manager support on the host. Local network functions have been disabled."
|
"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.
|
# try to query it. Ignore those cases.
|
||||||
_LOGGER.debug("Can't process %s: %s", device, err)
|
_LOGGER.debug("Can't process %s: %s", device, err)
|
||||||
continue
|
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
|
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)
|
capture_exception(err)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ from ...const import (
|
|||||||
ATTR_PRIORITY,
|
ATTR_PRIORITY,
|
||||||
ATTR_VPN,
|
ATTR_VPN,
|
||||||
)
|
)
|
||||||
from ...exceptions import DBusError, DBusInterfaceError
|
from ...exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
|
||||||
from ..const import (
|
from ..const import (
|
||||||
DBUS_ATTR_CONFIGURATION,
|
DBUS_ATTR_CONFIGURATION,
|
||||||
DBUS_ATTR_MODE,
|
DBUS_ATTR_MODE,
|
||||||
@ -67,7 +67,7 @@ class NetworkManagerDNS(DBusInterfaceProxy):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to DnsManager")
|
_LOGGER.warning("Can't connect to DnsManager")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No DnsManager support on the host. Local DNS functions have been disabled."
|
"No DnsManager support on the host. Local DNS functions have been disabled."
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ from typing import Any
|
|||||||
|
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
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 ..const import DBUS_NAME_NM, DBUS_OBJECT_SETTINGS
|
||||||
from ..interface import DBusInterface
|
from ..interface import DBusInterface
|
||||||
from ..network.setting import NetworkSetting
|
from ..network.setting import NetworkSetting
|
||||||
@ -28,7 +28,7 @@ class NetworkManagerSettings(DBusInterface):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to Network Manager Settings")
|
_LOGGER.warning("Can't connect to Network Manager Settings")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No Network Manager Settings support on the host. Local network functions have been disabled."
|
"No Network Manager Settings support on the host. Local network functions have been disabled."
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ from typing import Any
|
|||||||
|
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
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 ..utils.dbus import DBusSignalWrapper
|
||||||
from .const import (
|
from .const import (
|
||||||
DBUS_ATTR_BOOT_SLOT,
|
DBUS_ATTR_BOOT_SLOT,
|
||||||
@ -49,7 +49,7 @@ class Rauc(DBusInterfaceProxy):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to rauc")
|
_LOGGER.warning("Can't connect to rauc")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning("Host has no rauc support. OTA updates have been disabled.")
|
_LOGGER.warning("Host has no rauc support. OTA updates have been disabled.")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -5,7 +5,7 @@ import logging
|
|||||||
|
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
|
|
||||||
from ..exceptions import DBusError, DBusInterfaceError
|
from ..exceptions import DBusError, DBusInterfaceError, DBusServiceUnkownError
|
||||||
from .const import (
|
from .const import (
|
||||||
DBUS_ATTR_CACHE_STATISTICS,
|
DBUS_ATTR_CACHE_STATISTICS,
|
||||||
DBUS_ATTR_CURRENT_DNS_SERVER,
|
DBUS_ATTR_CURRENT_DNS_SERVER,
|
||||||
@ -59,7 +59,7 @@ class Resolved(DBusInterfaceProxy):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to systemd-resolved.")
|
_LOGGER.warning("Can't connect to systemd-resolved.")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Host has no systemd-resolved support. DNS will not work correctly."
|
"Host has no systemd-resolved support. DNS will not work correctly."
|
||||||
)
|
)
|
||||||
|
@ -10,6 +10,7 @@ from ..exceptions import (
|
|||||||
DBusError,
|
DBusError,
|
||||||
DBusFatalError,
|
DBusFatalError,
|
||||||
DBusInterfaceError,
|
DBusInterfaceError,
|
||||||
|
DBusServiceUnkownError,
|
||||||
DBusSystemdNoSuchUnit,
|
DBusSystemdNoSuchUnit,
|
||||||
)
|
)
|
||||||
from .const import (
|
from .const import (
|
||||||
@ -86,7 +87,7 @@ class Systemd(DBusInterfaceProxy):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to systemd")
|
_LOGGER.warning("Can't connect to systemd")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No systemd support on the host. Host control has been disabled."
|
"No systemd support on the host. Host control has been disabled."
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ import logging
|
|||||||
|
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
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 ..utils.dt import utc_from_timestamp
|
||||||
from .const import (
|
from .const import (
|
||||||
DBUS_ATTR_NTP,
|
DBUS_ATTR_NTP,
|
||||||
@ -63,7 +63,7 @@ class TimeDate(DBusInterfaceProxy):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to systemd-timedate")
|
_LOGGER.warning("Can't connect to systemd-timedate")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No timedate support on the host. Time/Date functions have been disabled."
|
"No timedate support on the host. Time/Date functions have been disabled."
|
||||||
)
|
)
|
||||||
|
@ -6,7 +6,12 @@ from typing import Any
|
|||||||
from awesomeversion import AwesomeVersion
|
from awesomeversion import AwesomeVersion
|
||||||
from dbus_fast.aio import MessageBus
|
from dbus_fast.aio import MessageBus
|
||||||
|
|
||||||
from ...exceptions import DBusError, DBusInterfaceError, DBusObjectError
|
from ...exceptions import (
|
||||||
|
DBusError,
|
||||||
|
DBusInterfaceError,
|
||||||
|
DBusObjectError,
|
||||||
|
DBusServiceUnkownError,
|
||||||
|
)
|
||||||
from ..const import (
|
from ..const import (
|
||||||
DBUS_ATTR_SUPPORTED_FILESYSTEMS,
|
DBUS_ATTR_SUPPORTED_FILESYSTEMS,
|
||||||
DBUS_ATTR_VERSION,
|
DBUS_ATTR_VERSION,
|
||||||
@ -45,7 +50,7 @@ class UDisks2(DBusInterfaceProxy):
|
|||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
except DBusError:
|
except DBusError:
|
||||||
_LOGGER.warning("Can't connect to udisks2")
|
_LOGGER.warning("Can't connect to udisks2")
|
||||||
except DBusInterfaceError:
|
except (DBusServiceUnkownError, DBusInterfaceError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"No udisks2 support on the host. Host control has been disabled."
|
"No udisks2 support on the host. Host control has been disabled."
|
||||||
)
|
)
|
||||||
|
@ -335,6 +335,10 @@ class DBusNotConnectedError(HostNotSupportedError):
|
|||||||
"""D-Bus is not connected and call a method."""
|
"""D-Bus is not connected and call a method."""
|
||||||
|
|
||||||
|
|
||||||
|
class DBusServiceUnkownError(HassioNotSupportedError):
|
||||||
|
"""D-Bus service was not available."""
|
||||||
|
|
||||||
|
|
||||||
class DBusInterfaceError(HassioNotSupportedError):
|
class DBusInterfaceError(HassioNotSupportedError):
|
||||||
"""D-Bus interface not connected."""
|
"""D-Bus interface not connected."""
|
||||||
|
|
||||||
@ -363,6 +367,10 @@ class DBusTimeoutError(DBusError):
|
|||||||
"""D-Bus call timed out."""
|
"""D-Bus call timed out."""
|
||||||
|
|
||||||
|
|
||||||
|
class DBusNoReplyError(DBusError):
|
||||||
|
"""D-Bus remote didn't reply/disconnected."""
|
||||||
|
|
||||||
|
|
||||||
class DBusFatalError(DBusError):
|
class DBusFatalError(DBusError):
|
||||||
"""D-Bus call going wrong.
|
"""D-Bus call going wrong.
|
||||||
|
|
||||||
|
@ -15,18 +15,21 @@ from dbus_fast import (
|
|||||||
)
|
)
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
from dbus_fast.aio.proxy_object import ProxyInterface, ProxyObject
|
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 dbus_fast.introspection import Node
|
||||||
|
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
|
DBusError,
|
||||||
DBusFatalError,
|
DBusFatalError,
|
||||||
DBusInterfaceError,
|
DBusInterfaceError,
|
||||||
DBusInterfaceMethodError,
|
DBusInterfaceMethodError,
|
||||||
DBusInterfacePropertyError,
|
DBusInterfacePropertyError,
|
||||||
DBusInterfaceSignalError,
|
DBusInterfaceSignalError,
|
||||||
|
DBusNoReplyError,
|
||||||
DBusNotConnectedError,
|
DBusNotConnectedError,
|
||||||
DBusObjectError,
|
DBusObjectError,
|
||||||
DBusParseError,
|
DBusParseError,
|
||||||
|
DBusServiceUnkownError,
|
||||||
DBusTimeoutError,
|
DBusTimeoutError,
|
||||||
HassioNotSupportedError,
|
HassioNotSupportedError,
|
||||||
)
|
)
|
||||||
@ -62,9 +65,11 @@ class DBus:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_dbus_error(err: DBusError) -> HassioNotSupportedError | DBusError:
|
def from_dbus_error(err: DBusFastDBusError) -> HassioNotSupportedError | DBusError:
|
||||||
"""Return correct dbus error based on type."""
|
"""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)
|
return DBusInterfaceError(err.text)
|
||||||
if err.type in {
|
if err.type in {
|
||||||
ErrorType.UNKNOWN_METHOD,
|
ErrorType.UNKNOWN_METHOD,
|
||||||
@ -80,6 +85,8 @@ class DBus:
|
|||||||
return DBusNotConnectedError(err.text)
|
return DBusNotConnectedError(err.text)
|
||||||
if err.type == ErrorType.TIMEOUT:
|
if err.type == ErrorType.TIMEOUT:
|
||||||
return DBusTimeoutError(err.text)
|
return DBusTimeoutError(err.text)
|
||||||
|
if err.type == ErrorType.NO_REPLY:
|
||||||
|
return DBusNoReplyError(err.text)
|
||||||
return DBusFatalError(err.text, type_=err.type)
|
return DBusFatalError(err.text, type_=err.type)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -102,7 +109,7 @@ class DBus:
|
|||||||
*args, unpack_variants=True
|
*args, unpack_variants=True
|
||||||
)
|
)
|
||||||
return await getattr(proxy_interface, method)(*args)
|
return await getattr(proxy_interface, method)(*args)
|
||||||
except DBusError as err:
|
except DBusFastDBusError as err:
|
||||||
raise DBus.from_dbus_error(err)
|
raise DBus.from_dbus_error(err)
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
capture_exception(err)
|
capture_exception(err)
|
||||||
@ -126,6 +133,8 @@ class DBus:
|
|||||||
raise DBusParseError(
|
raise DBusParseError(
|
||||||
f"Can't parse introspect data: {err}", _LOGGER.error
|
f"Can't parse introspect data: {err}", _LOGGER.error
|
||||||
) from err
|
) from err
|
||||||
|
except DBusFastDBusError as err:
|
||||||
|
raise DBus.from_dbus_error(err)
|
||||||
except (EOFError, TimeoutError):
|
except (EOFError, TimeoutError):
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Busy system at %s - %s", self.bus_name, self.object_path
|
"Busy system at %s - %s", self.bus_name, self.object_path
|
||||||
|
@ -5,11 +5,12 @@ import pytest
|
|||||||
|
|
||||||
from supervisor.dbus.agent import OSAgent
|
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.base import DBusServiceMock
|
||||||
from tests.dbus_service_mocks.os_agent import OSAgent as OSAgentService
|
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(
|
async def fixture_os_agent_service(
|
||||||
os_agent_services: dict[str, DBusServiceMock]
|
os_agent_services: dict[str, DBusServiceMock]
|
||||||
) -> OSAgentService:
|
) -> OSAgentService:
|
||||||
@ -39,3 +40,36 @@ async def test_dbus_osagent(
|
|||||||
await os_agent_service.ping()
|
await os_agent_service.ping()
|
||||||
await os_agent_service.ping()
|
await os_agent_service.ping()
|
||||||
assert os_agent.diagnostics is True
|
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
|
||||||
|
@ -12,7 +12,7 @@ from tests.common import mock_dbus_services
|
|||||||
from tests.dbus_service_mocks.network_dns_manager import DnsManager as DnsManagerService
|
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(
|
async def fixture_dns_manager_service(
|
||||||
dbus_session_bus: MessageBus,
|
dbus_session_bus: MessageBus,
|
||||||
) -> DnsManagerService:
|
) -> DnsManagerService:
|
||||||
@ -49,3 +49,12 @@ async def test_dns(
|
|||||||
await dns_manager_service.ping()
|
await dns_manager_service.ping()
|
||||||
await dns_manager_service.ping()
|
await dns_manager_service.ping()
|
||||||
assert dns_manager.mode == "default"
|
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
|
||||||
|
@ -9,7 +9,12 @@ import pytest
|
|||||||
from supervisor.dbus.const import ConnectionStateType
|
from supervisor.dbus.const import ConnectionStateType
|
||||||
from supervisor.dbus.network import NetworkManager
|
from supervisor.dbus.network import NetworkManager
|
||||||
from supervisor.dbus.network.interface import NetworkInterface
|
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 supervisor.utils.dbus import DBus
|
||||||
|
|
||||||
from tests.const import TEST_INTERFACE, TEST_INTERFACE_WLAN
|
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(
|
async def fixture_network_manager_service(
|
||||||
network_manager_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
network_manager_services: dict[str, DBusServiceMock | dict[str, DBusServiceMock]],
|
||||||
) -> NetworkManagerService:
|
) -> NetworkManagerService:
|
||||||
@ -134,6 +139,7 @@ async def test_removed_devices_disconnect(
|
|||||||
|
|
||||||
|
|
||||||
async def test_handling_bad_devices(
|
async def test_handling_bad_devices(
|
||||||
|
network_manager_service: NetworkManagerService,
|
||||||
network_manager: NetworkManager,
|
network_manager: NetworkManager,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
capture_exception: Mock,
|
capture_exception: Mock,
|
||||||
@ -161,7 +167,7 @@ async def test_handling_bad_devices(
|
|||||||
await network_manager.update(
|
await network_manager.update(
|
||||||
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/102"]}
|
{"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)
|
capture_exception.assert_called_once_with(err)
|
||||||
|
|
||||||
# We should be able to debug these situations if necessary
|
# 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()
|
await network_manager_service.ping()
|
||||||
connect.assert_called_once()
|
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
|
||||||
|
@ -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
|
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:
|
async def fixture_settings_service(dbus_session_bus: MessageBus) -> SettingsService:
|
||||||
"""Mock Settings service."""
|
"""Mock Settings service."""
|
||||||
yield (
|
yield (
|
||||||
@ -55,3 +55,12 @@ async def test_reload_connections(
|
|||||||
|
|
||||||
assert await settings.reload_connections() is True
|
assert await settings.reload_connections() is True
|
||||||
assert settings_service.ReloadConnections.calls == [tuple()]
|
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
|
||||||
|
@ -10,7 +10,7 @@ from tests.common import mock_dbus_services
|
|||||||
from tests.dbus_service_mocks.hostname import Hostname as HostnameService
|
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:
|
async def fixture_hostname_service(dbus_session_bus: MessageBus) -> HostnameService:
|
||||||
"""Mock hostname dbus service."""
|
"""Mock hostname dbus service."""
|
||||||
yield (await mock_dbus_services({"hostname": None}, dbus_session_bus))["hostname"]
|
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)]
|
assert hostname_service.SetStaticHostname.calls == [("StarWars", False)]
|
||||||
await hostname_service.ping()
|
await hostname_service.ping()
|
||||||
assert hostname.hostname == "StarWars"
|
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
|
||||||
|
@ -10,7 +10,7 @@ from tests.common import mock_dbus_services
|
|||||||
from tests.dbus_service_mocks.logind import Logind as LogindService
|
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:
|
async def fixture_logind_service(dbus_session_bus: MessageBus) -> LogindService:
|
||||||
"""Mock logind dbus service."""
|
"""Mock logind dbus service."""
|
||||||
yield (await mock_dbus_services({"logind": None}, dbus_session_bus))["logind"]
|
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 await logind.power_off() is None
|
||||||
assert logind_service.PowerOff.calls == [(False,)]
|
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
|
||||||
|
@ -11,7 +11,7 @@ from tests.common import mock_dbus_services
|
|||||||
from tests.dbus_service_mocks.rauc import Rauc as RaucService
|
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:
|
async def fixture_rauc_service(dbus_session_bus: MessageBus) -> RaucService:
|
||||||
"""Mock rauc dbus service."""
|
"""Mock rauc dbus service."""
|
||||||
yield (await mock_dbus_services({"rauc": None}, dbus_session_bus))["rauc"]
|
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 == ""
|
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."""
|
"""Test install."""
|
||||||
rauc = Rauc()
|
rauc = Rauc()
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ async def test_install(dbus_session_bus: MessageBus):
|
|||||||
assert await signal.wait_for_signal() == [0]
|
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."""
|
"""Test get slot status."""
|
||||||
rauc = Rauc()
|
rauc = Rauc()
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ async def test_get_slot_status(dbus_session_bus: MessageBus):
|
|||||||
assert slot_status[4][1]["bootname"] == "B"
|
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."""
|
"""Test mark."""
|
||||||
rauc = Rauc()
|
rauc = Rauc()
|
||||||
|
|
||||||
@ -88,3 +88,12 @@ async def test_mark(dbus_session_bus: MessageBus):
|
|||||||
mark = await rauc.mark(RaucState.GOOD, "booted")
|
mark = await rauc.mark(RaucState.GOOD, "booted")
|
||||||
assert mark[0] == "kernel.1"
|
assert mark[0] == "kernel.1"
|
||||||
assert mark[1] == "marked slot kernel.1 as good"
|
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
|
||||||
|
@ -19,7 +19,7 @@ from tests.common import mock_dbus_services
|
|||||||
from tests.dbus_service_mocks.resolved import Resolved as ResolvedService
|
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:
|
async def fixture_resolved_service(dbus_session_bus: MessageBus) -> ResolvedService:
|
||||||
"""Mock resolved dbus service."""
|
"""Mock resolved dbus service."""
|
||||||
yield (await mock_dbus_services({"resolved": None}, dbus_session_bus))["resolved"]
|
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()
|
||||||
await resolved_service.ping() # To process the follow-up get all properties call
|
await resolved_service.ping() # To process the follow-up get all properties call
|
||||||
assert resolved.llmnr_hostname == "homeassistant"
|
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
|
||||||
|
@ -12,7 +12,7 @@ from tests.common import mock_dbus_services
|
|||||||
from tests.dbus_service_mocks.timedate import TimeDate as TimeDateService
|
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:
|
async def fixture_timedate_service(dbus_session_bus: MessageBus) -> TimeDateService:
|
||||||
"""Mock timedate dbus service."""
|
"""Mock timedate dbus service."""
|
||||||
yield (await mock_dbus_services({"timedate": None}, dbus_session_bus))["timedate"]
|
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)]
|
assert timedate_service.SetNTP.calls == [(False, False)]
|
||||||
await timedate_service.ping()
|
await timedate_service.ping()
|
||||||
assert timedate.ntp is False
|
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
|
||||||
|
@ -2,12 +2,18 @@
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
|
from dbus_fast import ErrorType
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
|
from dbus_fast.errors import DBusError as DBusFastDBusError
|
||||||
from dbus_fast.service import method, signal
|
from dbus_fast.service import method, signal
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from supervisor.dbus.const import DBUS_OBJECT_BASE
|
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 supervisor.utils.dbus import DBus
|
||||||
|
|
||||||
from tests.common import load_fixture
|
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()
|
test_service.signal_test()
|
||||||
await test_service.ping()
|
await test_service.ping()
|
||||||
assert callback_count == 0
|
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user