Fix thread diagnostics loading blocking the event loop (#89307)

* Fix thread diagnostics loading blocking the event loop

* patch target
This commit is contained in:
J. Nick Koston 2023-03-07 15:21:26 -10:00 committed by GitHub
parent 099f16f6b8
commit bde40cde48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 56 additions and 46 deletions

View file

@ -17,9 +17,8 @@ some of their thread accessories can't be pinged, but it's still a thread proble
from __future__ import annotations from __future__ import annotations
from typing import Any, TypedDict from typing import TYPE_CHECKING, Any, TypedDict
from pyroute2 import NDB # pylint: disable=no-name-in-module
from python_otbr_api.tlv_parser import MeshcopTLVType from python_otbr_api.tlv_parser import MeshcopTLVType
from homeassistant.components import zeroconf from homeassistant.components import zeroconf
@ -29,6 +28,9 @@ from homeassistant.core import HomeAssistant
from .dataset_store import async_get_store from .dataset_store import async_get_store
from .discovery import async_read_zeroconf_cache from .discovery import async_read_zeroconf_cache
if TYPE_CHECKING:
from pyroute2 import NDB # pylint: disable=no-name-in-module
class Neighbour(TypedDict): class Neighbour(TypedDict):
"""A neighbour cache entry (ip neigh).""" """A neighbour cache entry (ip neigh)."""
@ -67,16 +69,15 @@ class Network(TypedDict):
unexpected_routers: set[str] unexpected_routers: set[str]
def _get_possible_thread_routes() -> ( def _get_possible_thread_routes(
tuple[dict[str, dict[str, Route]], dict[str, set[str]]] ndb: NDB,
): ) -> tuple[dict[str, dict[str, Route]], dict[str, set[str]]]:
# Build a list of possible thread routes # Build a list of possible thread routes
# Right now, this is ipv6 /64's that have a gateway # Right now, this is ipv6 /64's that have a gateway
# We cross reference with zerconf data to confirm which via's are known border routers # We cross reference with zerconf data to confirm which via's are known border routers
routes: dict[str, dict[str, Route]] = {} routes: dict[str, dict[str, Route]] = {}
reverse_routes: dict[str, set[str]] = {} reverse_routes: dict[str, set[str]] = {}
with NDB() as ndb:
for record in ndb.routes: for record in ndb.routes:
# Limit to IPV6 routes # Limit to IPV6 routes
if record.family != 10: if record.family != 10:
@ -100,25 +101,37 @@ def _get_possible_thread_routes() -> (
return routes, reverse_routes return routes, reverse_routes
def _get_neighbours() -> dict[str, Neighbour]: def _get_neighbours(ndb: NDB) -> dict[str, Neighbour]:
neighbours: dict[str, Neighbour] = {} # Build a list of neighbours
neighbours: dict[str, Neighbour] = {
with NDB() as ndb: record.dst: {
for record in ndb.neighbours:
neighbours[record.dst] = {
"lladdr": record.lladdr, "lladdr": record.lladdr,
"state": record.state, "state": record.state,
"probes": record.probes, "probes": record.probes,
} }
for record in ndb.neighbours
}
return neighbours return neighbours
def _get_routes_and_neighbors():
"""Get the routes and neighbours from pyroute2."""
# Import in the executor since import NDB can take a while
from pyroute2 import ( # pylint: disable=no-name-in-module, import-outside-toplevel
NDB,
)
with NDB() as ndb: # pylint: disable=not-callable
routes, reverse_routes = _get_possible_thread_routes(ndb)
neighbours = _get_neighbours(ndb)
return routes, reverse_routes, neighbours
async def async_get_config_entry_diagnostics( async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: ConfigEntry hass: HomeAssistant, entry: ConfigEntry
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for all known thread networks.""" """Return diagnostics for all known thread networks."""
networks: dict[str, Network] = {} networks: dict[str, Network] = {}
# Start with all networks that HA knows about # Start with all networks that HA knows about
@ -140,13 +153,12 @@ async def async_get_config_entry_diagnostics(
# Find all routes currently act that might be thread related, so we can match them to # Find all routes currently act that might be thread related, so we can match them to
# border routers as we process the zeroconf data. # border routers as we process the zeroconf data.
routes, reverse_routes = await hass.async_add_executor_job( #
_get_possible_thread_routes # Also find all neighbours
routes, reverse_routes, neighbours = await hass.async_add_executor_job(
_get_routes_and_neighbors
) )
# Find all neighbours
neighbours = await hass.async_add_executor_job(_get_neighbours)
aiozc = await zeroconf.async_get_async_instance(hass) aiozc = await zeroconf.async_get_async_instance(hass)
for data in async_read_zeroconf_cache(aiozc): for data in async_read_zeroconf_cache(aiozc):
if not data.extended_pan_id: if not data.extended_pan_id:

View file

@ -133,9 +133,7 @@ class MockNeighbour:
@pytest.fixture @pytest.fixture
def ndb() -> Mock: def ndb() -> Mock:
"""Prevent NDB poking the OS route tables.""" """Prevent NDB poking the OS route tables."""
with patch( with patch("pyroute2.NDB") as ndb, ndb() as instance:
"homeassistant.components.thread.diagnostics.NDB"
) as ndb, ndb() as instance:
instance.neighbours = [] instance.neighbours = []
instance.routes = [] instance.routes = []
yield instance yield instance