-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Use runtime_data and validate connection at setup for dnsip #169745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
eee46de
f4e6a78
57cf2a8
4724cc7
52a8e49
388bd68
b6ab41f
c24c514
2447ee6
ec35e41
bba421d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,119 @@ | ||
| """The DNS IP integration.""" | ||
|
|
||
| import asyncio | ||
| from dataclasses import dataclass | ||
| import logging | ||
|
|
||
| import aiodns | ||
| from aiodns.error import DNSError | ||
| from pycares import AresError | ||
|
|
||
| from homeassistant.config_entries import ConfigEntry | ||
| from homeassistant.const import CONF_PORT | ||
| from homeassistant.core import _LOGGER, HomeAssistant | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import ConfigEntryNotReady | ||
|
|
||
| from .const import ( | ||
| CONF_HOSTNAME, | ||
| CONF_IPV4, | ||
| CONF_IPV6, | ||
| CONF_PORT_IPV6, | ||
| CONF_RESOLVER, | ||
| CONF_RESOLVER_IPV6, | ||
| DEFAULT_PORT, | ||
| PLATFORMS, | ||
| ) | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| @dataclass | ||
| class DnsIPRuntimeData: | ||
| """Runtime data for DNS IP integration.""" | ||
|
|
||
| resolver_ipv4: aiodns.DNSResolver | None | ||
| resolver_ipv6: aiodns.DNSResolver | None | ||
|
|
||
| from .const import CONF_PORT_IPV6, DEFAULT_PORT, PLATFORMS | ||
|
|
||
| type DnsIPConfigEntry = ConfigEntry[DnsIPRuntimeData] | ||
|
|
||
| async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
|
|
||
| async def async_setup_entry(hass: HomeAssistant, entry: DnsIPConfigEntry) -> bool: | ||
| """Set up DNS IP from a config entry.""" | ||
|
|
||
| hostname = entry.data[CONF_HOSTNAME] | ||
| resolver_ipv4: aiodns.DNSResolver | None = None | ||
| resolver_ipv6: aiodns.DNSResolver | None = None | ||
| queries: list = [] | ||
|
|
||
| if entry.data[CONF_IPV4]: | ||
| resolver_ipv4 = aiodns.DNSResolver( | ||
| nameservers=[entry.options[CONF_RESOLVER]], | ||
| tcp_port=entry.options[CONF_PORT], | ||
| udp_port=entry.options[CONF_PORT], | ||
| ) | ||
| queries.append(resolver_ipv4.query(hostname, "A")) | ||
|
|
||
| if entry.data[CONF_IPV6]: | ||
| resolver_ipv6 = aiodns.DNSResolver( | ||
| nameservers=[entry.options[CONF_RESOLVER_IPV6]], | ||
| tcp_port=entry.options[CONF_PORT_IPV6], | ||
| udp_port=entry.options[CONF_PORT_IPV6], | ||
| ) | ||
| queries.append(resolver_ipv6.query(hostname, "AAAA")) | ||
|
|
||
| async def _close_resolvers() -> None: | ||
| if resolver_ipv4 is not None: | ||
| await resolver_ipv4.close() | ||
| if resolver_ipv6 is not None: | ||
| await resolver_ipv6.close() | ||
|
|
||
| try: | ||
| async with asyncio.timeout(10): | ||
| results = await asyncio.gather(*queries, return_exceptions=True) | ||
| except TimeoutError as err: | ||
| await _close_resolvers() | ||
| raise ConfigEntryNotReady( | ||
| f"DNS lookup timed out for {hostname}: {err}" | ||
| ) from err | ||
|
|
||
| errors = [ | ||
| result | ||
| for result in results | ||
| if isinstance( | ||
| result, (TimeoutError, DNSError, AresError, asyncio.CancelledError) | ||
| ) | ||
|
Comment on lines
+80
to
+85
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not implementing. Suggestion that introduced this filter explicitly included asyncio.CancelledError. Background context of PR #170048, where broader handling of pycares/aiodns leaks treats CancelledError as a leakable exception rather than a true cancellation. |
||
| ] | ||
|
Phil-Rad marked this conversation as resolved.
|
||
| if errors and len(errors) == len(results): | ||
| await _close_resolvers() | ||
| raise ConfigEntryNotReady( | ||
| f"DNS lookup failed for {hostname}: {errors[0]}" | ||
| ) from errors[0] | ||
|
gjohansson-ST marked this conversation as resolved.
Comment on lines
+71
to
+91
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tried that earlier in this PR "let's be specific as we know it's 2" suggestion, so we ended up with always-create-both. Going with the codeowner on this one. |
||
|
|
||
|
Phil-Rad marked this conversation as resolved.
|
||
| entry.runtime_data = DnsIPRuntimeData( | ||
| resolver_ipv4=resolver_ipv4, | ||
| resolver_ipv6=resolver_ipv6, | ||
| ) | ||
|
|
||
| await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
|
gjohansson-ST marked this conversation as resolved.
|
||
| return True | ||
|
|
||
|
|
||
| async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
| async def async_unload_entry(hass: HomeAssistant, entry: DnsIPConfigEntry) -> bool: | ||
| """Unload DNS IP config entry.""" | ||
|
|
||
| return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||
| unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) | ||
| if unload_ok: | ||
| if entry.runtime_data.resolver_ipv4 is not None: | ||
| await entry.runtime_data.resolver_ipv4.close() | ||
| if entry.runtime_data.resolver_ipv6 is not None: | ||
| await entry.runtime_data.resolver_ipv6.close() | ||
| return unload_ok | ||
|
|
||
|
|
||
| async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: | ||
| async def async_migrate_entry( | ||
| hass: HomeAssistant, config_entry: DnsIPConfigEntry | ||
| ) -> bool: | ||
| """Migrate old entry to a newer version.""" | ||
|
|
||
| if config_entry.version > 1: | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.