Make dbus-fast calls more robust (#4005)

* Make dbus-fast calls more robust

* Handle all exceptions and add test

* DBus minimal can't return commands list

Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
This commit is contained in:
Pascal Vizeli 2022-11-15 22:19:38 +01:00 committed by GitHub
parent 348fb56cb5
commit 2809f23391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 27 deletions

View File

@ -30,6 +30,7 @@ from ..exceptions import (
DBusTimeoutError,
HassioNotSupportedError,
)
from .sentry import capture_exception
_LOGGER: logging.Logger = logging.getLogger(__name__)
@ -103,6 +104,9 @@ class DBus:
return await getattr(proxy_interface, method)(*args)
except DBusError as err:
raise DBus.from_dbus_error(err)
except Exception as err: # pylint: disable=broad-except
capture_exception(err)
raise DBusFatalError(str(err)) from err
def _add_interfaces(self):
"""Make proxy interfaces out of introspection data."""

View File

@ -129,8 +129,41 @@ def mock_get_properties(object_path: str, interface: str) -> str:
return load_json_fixture(f"{fixture}.json")
async def mock_init_proxy(self):
"""Mock init dbus proxy."""
filetype = "xml"
fixture = (
self.object_path.replace("/", "_")[1:]
if self.object_path != DBUS_OBJECT_BASE
else self.bus_name.replace(".", "_")
)
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}_~"
# Use dbus-next infrastructure to parse introspection xml
self._proxy_obj = ProxyObject(
self.bus_name,
self.object_path,
load_fixture(f"{fixture}.{filetype}"),
self._bus,
)
self._add_interfaces()
if DBUS_INTERFACE_PROPERTIES in self._proxies:
setattr(
self._proxies[DBUS_INTERFACE_PROPERTIES],
"call_get_all",
lambda interface: mock_get_properties(self.object_path, interface),
)
@pytest.fixture
def dbus(dbus_bus: MessageBus) -> DBus:
def dbus(dbus_bus: MessageBus) -> list[str]:
"""Mock DBUS."""
dbus_commands = []
@ -150,31 +183,6 @@ def dbus(dbus_bus: MessageBus) -> DBus:
async def mock_signal___aexit__(self, exc_t, exc_v, exc_tb):
pass
async def mock_init_proxy(self):
filetype = "xml"
fixture = (
self.object_path.replace("/", "_")[1:]
if self.object_path != DBUS_OBJECT_BASE
else self.bus_name.replace(".", "_")
)
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}_~"
# Use dbus-next infrastructure to parse introspection xml
self._proxy_obj = ProxyObject(
self.bus_name,
self.object_path,
load_fixture(f"{fixture}.{filetype}"),
dbus_bus,
)
self._add_interfaces()
async def mock_call_dbus(
proxy_interface: ProxyInterface,
method: str,
@ -235,6 +243,15 @@ def dbus(dbus_bus: MessageBus) -> DBus:
yield dbus_commands
@pytest.fixture
async def dbus_minimal(dbus_bus: MessageBus) -> MessageBus:
"""Mock DBus without mocking call_dbus or signals but handle properties fixture."""
with patch("supervisor.utils.dbus.DBus._init_proxy", new=mock_init_proxy), patch(
"supervisor.dbus.manager.MessageBus.connect", return_value=dbus_bus
):
yield dbus_bus
@pytest.fixture
async def dbus_is_connected():
"""Mock DBusInterface.is_connected for tests."""

View File

@ -1,9 +1,12 @@
"""Test dbus utility."""
from unittest.mock import AsyncMock, Mock
from dbus_fast.aio.message_bus import MessageBus
import pytest
from supervisor.dbus.const import DBUS_OBJECT_BASE
from supervisor.exceptions import DBusInterfaceError
from supervisor.exceptions import DBusFatalError, DBusInterfaceError
from supervisor.utils.dbus import DBus
@ -14,3 +17,26 @@ async def test_missing_properties_interface(dbus_bus: MessageBus, dbus: list[str
)
with pytest.raises(DBusInterfaceError):
await service.get_properties("test.no.properties.interface")
@pytest.mark.parametrize("err", [BrokenPipeError(), EOFError(), OSError()])
async def test_internal_dbus_errors(
dbus_minimal: MessageBus,
capture_exception: Mock,
err: Exception,
):
"""Test internal dbus library errors become dbus error."""
rauc = await DBus.connect(dbus_minimal, "de.pengutronix.rauc", DBUS_OBJECT_BASE)
setattr(
# pylint: disable=protected-access
rauc._proxies["de.pengutronix.rauc.Installer"],
# pylint: enable=protected-access
"call_mark",
proxy_mock := AsyncMock().call_mark,
)
proxy_mock.side_effect = err
with pytest.raises(DBusFatalError):
await rauc.Installer.call_mark("good", "booted")
capture_exception.assert_called_once_with(err)