mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-13 04:06:33 +00:00
Ignore veth changes (#3955)
* Reduce log noise for unmanaged interfaces * Ignore signals with veth changes only * Fix test and add one
This commit is contained in:
parent
34afcef4f1
commit
bde5c938a7
@ -5,6 +5,8 @@ from typing import Any
|
|||||||
|
|
||||||
from dbus_fast.aio.message_bus import MessageBus
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
|
|
||||||
|
from supervisor.exceptions import DBusInterfaceError
|
||||||
|
|
||||||
from ..utils.dbus import DBus
|
from ..utils.dbus import DBus
|
||||||
from .utils import dbus_connected
|
from .utils import dbus_connected
|
||||||
|
|
||||||
@ -70,8 +72,14 @@ class DBusInterfaceProxy(DBusInterface):
|
|||||||
async def connect(self, bus: MessageBus) -> None:
|
async def connect(self, bus: MessageBus) -> None:
|
||||||
"""Connect to D-Bus."""
|
"""Connect to D-Bus."""
|
||||||
await super().connect(bus)
|
await super().connect(bus)
|
||||||
await self.update()
|
|
||||||
|
|
||||||
|
if not self.dbus.properties:
|
||||||
|
self.disconnect()
|
||||||
|
raise DBusInterfaceError(
|
||||||
|
f"D-Bus object {self.object_path} is not usable, introspection is missing required properties interface"
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.update()
|
||||||
if self.sync_properties and self.is_connected:
|
if self.sync_properties and self.is_connected:
|
||||||
self.dbus.sync_property_changes(self.properties_interface, self.update)
|
self.dbus.sync_property_changes(self.properties_interface, self.update)
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ from ...exceptions import (
|
|||||||
DBusError,
|
DBusError,
|
||||||
DBusFatalError,
|
DBusFatalError,
|
||||||
DBusInterfaceError,
|
DBusInterfaceError,
|
||||||
DBusInterfaceMethodError,
|
|
||||||
HostNotSupportedError,
|
HostNotSupportedError,
|
||||||
)
|
)
|
||||||
from ..const import (
|
from ..const import (
|
||||||
@ -163,7 +162,16 @@ class NetworkManager(DBusInterfaceProxy):
|
|||||||
if not changed and self.dns.is_connected:
|
if not changed and self.dns.is_connected:
|
||||||
await self.dns.update()
|
await self.dns.update()
|
||||||
|
|
||||||
if changed and DBUS_ATTR_DEVICES not in changed:
|
if changed and (
|
||||||
|
DBUS_ATTR_DEVICES not in changed
|
||||||
|
or {
|
||||||
|
intr.object_path for intr in self.interfaces.values() if intr.managed
|
||||||
|
}.issubset(set(changed[DBUS_ATTR_DEVICES]))
|
||||||
|
):
|
||||||
|
# If none of our managed devices were removed then most likely this is just veths changing.
|
||||||
|
# We don't care about veths and reprocessing all their changes can swamp a system when
|
||||||
|
# docker is having issues. This does mean we may miss activation of a new managed device
|
||||||
|
# in rare occaisions but we'll catch it on the next host update scheduled task.
|
||||||
return
|
return
|
||||||
|
|
||||||
interfaces = {}
|
interfaces = {}
|
||||||
@ -178,14 +186,14 @@ class NetworkManager(DBusInterfaceProxy):
|
|||||||
# Connect to interface
|
# Connect to interface
|
||||||
try:
|
try:
|
||||||
await interface.connect(self.dbus.bus)
|
await interface.connect(self.dbus.bus)
|
||||||
except (DBusFatalError, DBusInterfaceMethodError) as err:
|
except (DBusFatalError, DBusInterfaceError) as err:
|
||||||
# Docker creates and deletes interfaces quite often, sometimes
|
# Docker creates and deletes interfaces quite often, sometimes
|
||||||
# this causes a race condition: A device disappears while we
|
# this causes a race condition: A device disappears while we
|
||||||
# try to query it. Ignore those cases.
|
# try to query it. Ignore those cases.
|
||||||
_LOGGER.warning("Can't process %s: %s", device, err)
|
_LOGGER.debug("Can't process %s: %s", device, err)
|
||||||
continue
|
continue
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Error while processing interface: %s", err)
|
_LOGGER.exception("Error while processing %s: %s", device, err)
|
||||||
sentry_sdk.capture_exception(err)
|
sentry_sdk.capture_exception(err)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import socket
|
|||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from dbus_fast.signature import Variant
|
from dbus_fast import Variant
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
ATTR_ASSIGNED_MAC,
|
ATTR_ASSIGNED_MAC,
|
||||||
|
@ -5,12 +5,17 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any, Awaitable, Callable, Coroutine
|
from typing import Any, Awaitable, Callable, Coroutine
|
||||||
|
|
||||||
from dbus_fast import ErrorType, InvalidIntrospectionError, Message, MessageType
|
from dbus_fast import (
|
||||||
|
ErrorType,
|
||||||
|
InvalidIntrospectionError,
|
||||||
|
Message,
|
||||||
|
MessageType,
|
||||||
|
Variant,
|
||||||
|
)
|
||||||
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
|
||||||
from dbus_fast.introspection import Node
|
from dbus_fast.introspection import Node
|
||||||
from dbus_fast.signature import Variant
|
|
||||||
|
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
DBusFatalError,
|
DBusFatalError,
|
||||||
@ -143,15 +148,19 @@ class DBus:
|
|||||||
return self._bus
|
return self._bus
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def properties(self) -> ProxyInterface:
|
def properties(self) -> DBusCallWrapper | None:
|
||||||
"""Get properties proxy interface."""
|
"""Get properties proxy interface."""
|
||||||
|
if DBUS_INTERFACE_PROPERTIES not in self._proxies:
|
||||||
|
return None
|
||||||
return DBusCallWrapper(self, DBUS_INTERFACE_PROPERTIES)
|
return DBusCallWrapper(self, DBUS_INTERFACE_PROPERTIES)
|
||||||
|
|
||||||
async def get_properties(self, interface: str) -> dict[str, Any]:
|
async def get_properties(self, interface: str) -> dict[str, Any]:
|
||||||
"""Read all properties from interface."""
|
"""Read all properties from interface."""
|
||||||
return await DBusCallWrapper(self, DBUS_INTERFACE_PROPERTIES).call_get_all(
|
if not self.properties:
|
||||||
interface
|
raise DBusInterfaceError(
|
||||||
)
|
f"DBus Object does not have interface {DBUS_INTERFACE_PROPERTIES}"
|
||||||
|
)
|
||||||
|
return await self.properties.call_get_all(interface)
|
||||||
|
|
||||||
def sync_property_changes(
|
def sync_property_changes(
|
||||||
self,
|
self,
|
||||||
|
@ -253,7 +253,6 @@ async def network_manager(dbus, dbus_bus: MessageBus) -> NetworkManager:
|
|||||||
|
|
||||||
# Init
|
# Init
|
||||||
await nm_obj.connect(dbus_bus)
|
await nm_obj.connect(dbus_bus)
|
||||||
await nm_obj.update()
|
|
||||||
|
|
||||||
yield nm_obj
|
yield nm_obj
|
||||||
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
"""Test NetworkInterface."""
|
"""Test NetworkInterface."""
|
||||||
import asyncio
|
import asyncio
|
||||||
from unittest.mock import AsyncMock
|
import logging
|
||||||
|
from unittest.mock import AsyncMock, PropertyMock, patch
|
||||||
|
|
||||||
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
import pytest
|
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.exceptions import HostNotSupportedError
|
from supervisor.dbus.network.interface import NetworkInterface
|
||||||
|
from supervisor.exceptions import DBusFatalError, DBusParseError, HostNotSupportedError
|
||||||
|
from supervisor.utils.dbus import DBus
|
||||||
|
|
||||||
from .setting.test_init import SETTINGS_WITH_SIGNATURE
|
from .setting.test_init import SETTINGS_WITH_SIGNATURE
|
||||||
|
|
||||||
@ -107,3 +111,77 @@ async def test_removed_devices_disconnect(network_manager: NetworkManager):
|
|||||||
|
|
||||||
assert TEST_INTERFACE_WLAN not in network_manager.interfaces
|
assert TEST_INTERFACE_WLAN not in network_manager.interfaces
|
||||||
assert wlan.is_connected is False
|
assert wlan.is_connected is False
|
||||||
|
|
||||||
|
|
||||||
|
async def test_handling_bad_devices(
|
||||||
|
network_manager: NetworkManager, caplog: pytest.LogCaptureFixture
|
||||||
|
):
|
||||||
|
"""Test handling of bad and disappearing devices."""
|
||||||
|
caplog.clear()
|
||||||
|
caplog.set_level(logging.INFO, "supervisor.dbus.network")
|
||||||
|
|
||||||
|
with patch.object(DBus, "_init_proxy", side_effect=DBusFatalError()):
|
||||||
|
await network_manager.update(
|
||||||
|
{"Devices": ["/org/freedesktop/NetworkManager/Devices/100"]}
|
||||||
|
)
|
||||||
|
assert not caplog.text
|
||||||
|
|
||||||
|
await network_manager.update()
|
||||||
|
with patch.object(DBus, "properties", new=PropertyMock(return_value=None)):
|
||||||
|
await network_manager.update(
|
||||||
|
{"Devices": ["/org/freedesktop/NetworkManager/Devices/101"]}
|
||||||
|
)
|
||||||
|
assert not caplog.text
|
||||||
|
|
||||||
|
# Unparseable introspections shouldn't happen, this one is logged and captured
|
||||||
|
await network_manager.update()
|
||||||
|
with patch.object(
|
||||||
|
DBus, "_init_proxy", side_effect=(err := DBusParseError())
|
||||||
|
), patch("supervisor.dbus.network.sentry_sdk.capture_exception") as capture:
|
||||||
|
await network_manager.update(
|
||||||
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/102"]}
|
||||||
|
)
|
||||||
|
assert f"Error while processing {device}" in caplog.text
|
||||||
|
capture.assert_called_once_with(err)
|
||||||
|
|
||||||
|
# We should be able to debug these situations if necessary
|
||||||
|
caplog.set_level(logging.DEBUG, "supervisor.dbus.network")
|
||||||
|
await network_manager.update()
|
||||||
|
with patch.object(DBus, "_init_proxy", side_effect=DBusFatalError()):
|
||||||
|
await network_manager.update(
|
||||||
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/103"]}
|
||||||
|
)
|
||||||
|
assert f"Can't process {device}" in caplog.text
|
||||||
|
|
||||||
|
await network_manager.update()
|
||||||
|
with patch.object(DBus, "properties", new=PropertyMock(return_value=None)):
|
||||||
|
await network_manager.update(
|
||||||
|
{"Devices": [device := "/org/freedesktop/NetworkManager/Devices/104"]}
|
||||||
|
)
|
||||||
|
assert f"Can't process {device}" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_ignore_veth_only_changes(
|
||||||
|
network_manager: NetworkManager, dbus_bus: MessageBus
|
||||||
|
):
|
||||||
|
"""Changes to list of devices is ignored unless it changes managed devices."""
|
||||||
|
assert network_manager.properties["Devices"] == [
|
||||||
|
"/org/freedesktop/NetworkManager/Devices/1",
|
||||||
|
"/org/freedesktop/NetworkManager/Devices/3",
|
||||||
|
]
|
||||||
|
with patch.object(NetworkInterface, "update") as update:
|
||||||
|
await network_manager.update(
|
||||||
|
{
|
||||||
|
"Devices": [
|
||||||
|
"/org/freedesktop/NetworkManager/Devices/1",
|
||||||
|
"/org/freedesktop/NetworkManager/Devices/3",
|
||||||
|
"/org/freedesktop/NetworkManager/Devices/35",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
update.assert_not_called()
|
||||||
|
|
||||||
|
await network_manager.update(
|
||||||
|
{"Devices": ["/org/freedesktop/NetworkManager/Devices/35"]}
|
||||||
|
)
|
||||||
|
update.assert_called_once()
|
||||||
|
@ -7,7 +7,9 @@ from unittest.mock import MagicMock
|
|||||||
from dbus_fast.aio.message_bus import MessageBus
|
from dbus_fast.aio.message_bus import MessageBus
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from supervisor.dbus.const import DBUS_OBJECT_BASE
|
||||||
from supervisor.dbus.interface import DBusInterfaceProxy
|
from supervisor.dbus.interface import DBusInterfaceProxy
|
||||||
|
from supervisor.exceptions import DBusInterfaceError
|
||||||
|
|
||||||
from tests.common import fire_property_change_signal, fire_watched_signal
|
from tests.common import fire_property_change_signal, fire_watched_signal
|
||||||
|
|
||||||
@ -106,3 +108,15 @@ async def test_dbus_proxy_shutdown_pending_task(proxy: DBusInterfaceProxyMock):
|
|||||||
proxy.obj.shutdown()
|
proxy.obj.shutdown()
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
assert device == "/test/obj/1"
|
assert device == "/test/obj/1"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_proxy_missing_properties_interface(dbus_bus: MessageBus):
|
||||||
|
"""Test proxy instance disconnects and errors when missing properties interface."""
|
||||||
|
proxy = DBusInterfaceProxy()
|
||||||
|
proxy.bus_name = "test.no.properties.interface"
|
||||||
|
proxy.object_path = DBUS_OBJECT_BASE
|
||||||
|
proxy.properties_interface = "test.no.properties.interface"
|
||||||
|
|
||||||
|
with pytest.raises(DBusInterfaceError):
|
||||||
|
await proxy.connect(dbus_bus)
|
||||||
|
assert proxy.is_connected is False
|
||||||
|
@ -3,7 +3,7 @@ from dbus_fast.aio.message_bus import MessageBus
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from supervisor.dbus.const import DBUS_OBJECT_BASE
|
from supervisor.dbus.const import DBUS_OBJECT_BASE
|
||||||
from supervisor.exceptions import DBusInterfaceMethodError
|
from supervisor.exceptions import DBusInterfaceError
|
||||||
from supervisor.utils.dbus import DBus
|
from supervisor.utils.dbus import DBus
|
||||||
|
|
||||||
|
|
||||||
@ -12,5 +12,5 @@ async def test_missing_properties_interface(dbus_bus: MessageBus, dbus: list[str
|
|||||||
service = await DBus.connect(
|
service = await DBus.connect(
|
||||||
dbus_bus, "test.no.properties.interface", DBUS_OBJECT_BASE
|
dbus_bus, "test.no.properties.interface", DBUS_OBJECT_BASE
|
||||||
)
|
)
|
||||||
with pytest.raises(DBusInterfaceMethodError):
|
with pytest.raises(DBusInterfaceError):
|
||||||
await service.get_properties("test.no.properties.interface")
|
await service.get_properties("test.no.properties.interface")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user