mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-27 02:56:31 +00:00
Add Supervisor connectivity check after DNS restart (#6005)
* Add Supervisor connectivity check after DNS restart When the DNS plug-in got restarted, check Supervisor connectivity in case the DNS plug-in configuration change influenced Supervisor connectivity. This is helpful when a DHCP server gets started after Home Assistant is up. In that case the network provided DNS server (local DNS server) becomes available after the DNS plug-in restart. Without this change, the Supervisor connectivity will remain false until the a Job triggers a connectivity check, for example the periodic update check (which causes a updater and store reload) by Core. * Fix pytest and add coverage for new functionality
This commit is contained in:
parent
baf9695cf7
commit
9a0f530a2f
@ -15,7 +15,8 @@ from awesomeversion import AwesomeVersion
|
|||||||
import jinja2
|
import jinja2
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from ..const import ATTR_SERVERS, DNS_SUFFIX, LogLevel
|
from ..bus import EventListener
|
||||||
|
from ..const import ATTR_SERVERS, DNS_SUFFIX, BusEvent, LogLevel
|
||||||
from ..coresys import CoreSys
|
from ..coresys import CoreSys
|
||||||
from ..dbus.const import MulticastProtocolEnabled
|
from ..dbus.const import MulticastProtocolEnabled
|
||||||
from ..docker.const import ContainerState
|
from ..docker.const import ContainerState
|
||||||
@ -82,6 +83,7 @@ class PluginDns(PluginBase):
|
|||||||
# Debouncing system for rapid local changes
|
# Debouncing system for rapid local changes
|
||||||
self._locals_changed_handle: asyncio.TimerHandle | None = None
|
self._locals_changed_handle: asyncio.TimerHandle | None = None
|
||||||
self._restart_after_locals_change_handle: asyncio.Task | None = None
|
self._restart_after_locals_change_handle: asyncio.Task | None = None
|
||||||
|
self._connectivity_check_listener: EventListener | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hosts(self) -> Path:
|
def hosts(self) -> Path:
|
||||||
@ -111,6 +113,15 @@ class PluginDns(PluginBase):
|
|||||||
|
|
||||||
return servers
|
return servers
|
||||||
|
|
||||||
|
async def _on_dns_container_running(self, event: DockerContainerStateEvent) -> None:
|
||||||
|
"""Handle DNS container state change to running and trigger connectivity check."""
|
||||||
|
if event.name == self.instance.name and event.state == ContainerState.RUNNING:
|
||||||
|
# Wait before CoreDNS actually becomes available
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
_LOGGER.debug("CoreDNS started, checking connectivity")
|
||||||
|
await self.sys_supervisor.check_connectivity()
|
||||||
|
|
||||||
async def _restart_dns_after_locals_change(self) -> None:
|
async def _restart_dns_after_locals_change(self) -> None:
|
||||||
"""Restart DNS after a debounced delay for local changes."""
|
"""Restart DNS after a debounced delay for local changes."""
|
||||||
old_locals = self._cached_locals
|
old_locals = self._cached_locals
|
||||||
@ -236,6 +247,13 @@ class PluginDns(PluginBase):
|
|||||||
_LOGGER.error("Can't read hosts.tmpl: %s", err)
|
_LOGGER.error("Can't read hosts.tmpl: %s", err)
|
||||||
|
|
||||||
await self._init_hosts()
|
await self._init_hosts()
|
||||||
|
|
||||||
|
# Register Docker event listener for connectivity checks
|
||||||
|
if not self._connectivity_check_listener:
|
||||||
|
self._connectivity_check_listener = self.sys_bus.register_event(
|
||||||
|
BusEvent.DOCKER_CONTAINER_STATE_CHANGE, self._on_dns_container_running
|
||||||
|
)
|
||||||
|
|
||||||
await super().load()
|
await super().load()
|
||||||
|
|
||||||
# Update supervisor
|
# Update supervisor
|
||||||
|
@ -46,7 +46,7 @@ def _check_connectivity_throttle_period(coresys: CoreSys, *_) -> timedelta:
|
|||||||
if coresys.supervisor.connectivity:
|
if coresys.supervisor.connectivity:
|
||||||
return timedelta(minutes=10)
|
return timedelta(minutes=10)
|
||||||
|
|
||||||
return timedelta(seconds=30)
|
return timedelta(seconds=5)
|
||||||
|
|
||||||
|
|
||||||
class Supervisor(CoreSysAttributes):
|
class Supervisor(CoreSysAttributes):
|
||||||
|
@ -406,3 +406,78 @@ async def test_stop_cancels_pending_timers_and_tasks(coresys: CoreSys):
|
|||||||
mock_task_handle.cancel.assert_called_once()
|
mock_task_handle.cancel.assert_called_once()
|
||||||
assert dns_plugin._locals_changed_handle is None
|
assert dns_plugin._locals_changed_handle is None
|
||||||
assert dns_plugin._restart_after_locals_change_handle is None
|
assert dns_plugin._restart_after_locals_change_handle is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_dns_restart_triggers_connectivity_check(coresys: CoreSys):
|
||||||
|
"""Test end-to-end that DNS container restart triggers connectivity check."""
|
||||||
|
dns_plugin = coresys.plugins.dns
|
||||||
|
|
||||||
|
# Load the plugin to register the event listener
|
||||||
|
with (
|
||||||
|
patch.object(type(dns_plugin.instance), "attach"),
|
||||||
|
patch.object(type(dns_plugin.instance), "is_running", return_value=True),
|
||||||
|
):
|
||||||
|
await dns_plugin.load()
|
||||||
|
|
||||||
|
# Verify listener was registered (connectivity check listener should be stored)
|
||||||
|
assert dns_plugin._connectivity_check_listener is not None
|
||||||
|
|
||||||
|
# Create event to signal when connectivity check is called
|
||||||
|
connectivity_check_event = asyncio.Event()
|
||||||
|
|
||||||
|
# Mock connectivity check to set the event when called
|
||||||
|
async def mock_check_connectivity():
|
||||||
|
connectivity_check_event.set()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch.object(
|
||||||
|
coresys.supervisor,
|
||||||
|
"check_connectivity",
|
||||||
|
side_effect=mock_check_connectivity,
|
||||||
|
),
|
||||||
|
patch("supervisor.plugins.dns.asyncio.sleep") as mock_sleep,
|
||||||
|
):
|
||||||
|
# Fire the DNS container state change event through bus system
|
||||||
|
coresys.bus.fire_event(
|
||||||
|
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
|
||||||
|
DockerContainerStateEvent(
|
||||||
|
name="hassio_dns",
|
||||||
|
state=ContainerState.RUNNING,
|
||||||
|
id="test_id",
|
||||||
|
time=1234567890,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for connectivity check to be called
|
||||||
|
await asyncio.wait_for(connectivity_check_event.wait(), timeout=1.0)
|
||||||
|
|
||||||
|
# Verify sleep was called with correct delay
|
||||||
|
mock_sleep.assert_called_once_with(5)
|
||||||
|
|
||||||
|
# Reset and test that other containers don't trigger check
|
||||||
|
connectivity_check_event.clear()
|
||||||
|
mock_sleep.reset_mock()
|
||||||
|
|
||||||
|
# Fire event for different container
|
||||||
|
coresys.bus.fire_event(
|
||||||
|
BusEvent.DOCKER_CONTAINER_STATE_CHANGE,
|
||||||
|
DockerContainerStateEvent(
|
||||||
|
name="hassio_homeassistant",
|
||||||
|
state=ContainerState.RUNNING,
|
||||||
|
id="test_id",
|
||||||
|
time=1234567890,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait a bit and verify connectivity check was NOT triggered
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(connectivity_check_event.wait(), timeout=0.1)
|
||||||
|
assert False, (
|
||||||
|
"Connectivity check should not have been called for other containers"
|
||||||
|
)
|
||||||
|
except TimeoutError:
|
||||||
|
# This is expected - connectivity check should not be called
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Verify sleep was not called for other containers
|
||||||
|
mock_sleep.assert_not_called()
|
||||||
|
@ -50,8 +50,8 @@ async def test_connectivity_check(
|
|||||||
[
|
[
|
||||||
(None, timedelta(minutes=5), True),
|
(None, timedelta(minutes=5), True),
|
||||||
(None, timedelta(minutes=15), False),
|
(None, timedelta(minutes=15), False),
|
||||||
(ClientError(), timedelta(seconds=20), True),
|
(ClientError(), timedelta(seconds=3), True),
|
||||||
(ClientError(), timedelta(seconds=40), False),
|
(ClientError(), timedelta(seconds=10), False),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_connectivity_check_throttling(
|
async def test_connectivity_check_throttling(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user