mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-24 01:26:35 +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 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 ..dbus.const import MulticastProtocolEnabled
|
||||
from ..docker.const import ContainerState
|
||||
@ -82,6 +83,7 @@ class PluginDns(PluginBase):
|
||||
# Debouncing system for rapid local changes
|
||||
self._locals_changed_handle: asyncio.TimerHandle | None = None
|
||||
self._restart_after_locals_change_handle: asyncio.Task | None = None
|
||||
self._connectivity_check_listener: EventListener | None = None
|
||||
|
||||
@property
|
||||
def hosts(self) -> Path:
|
||||
@ -111,6 +113,15 @@ class PluginDns(PluginBase):
|
||||
|
||||
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:
|
||||
"""Restart DNS after a debounced delay for local changes."""
|
||||
old_locals = self._cached_locals
|
||||
@ -236,6 +247,13 @@ class PluginDns(PluginBase):
|
||||
_LOGGER.error("Can't read hosts.tmpl: %s", err)
|
||||
|
||||
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()
|
||||
|
||||
# Update supervisor
|
||||
|
@ -46,7 +46,7 @@ def _check_connectivity_throttle_period(coresys: CoreSys, *_) -> timedelta:
|
||||
if coresys.supervisor.connectivity:
|
||||
return timedelta(minutes=10)
|
||||
|
||||
return timedelta(seconds=30)
|
||||
return timedelta(seconds=5)
|
||||
|
||||
|
||||
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()
|
||||
assert dns_plugin._locals_changed_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=15), False),
|
||||
(ClientError(), timedelta(seconds=20), True),
|
||||
(ClientError(), timedelta(seconds=40), False),
|
||||
(ClientError(), timedelta(seconds=3), True),
|
||||
(ClientError(), timedelta(seconds=10), False),
|
||||
],
|
||||
)
|
||||
async def test_connectivity_check_throttling(
|
||||
|
Loading…
x
Reference in New Issue
Block a user