mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-12-24 20:35:55 +00:00
Create issue for detected DNS server problem (#3578)
* Create issue for detected DNS server problem * Validate behavior on restart as well * tls:// not supported, remove check * Move DNS server checks into resolution checks * Revert all changes to plugins.dns * Run DNS server checks if affected * Mock aiodns query during all checks tests
This commit is contained in:
55
tests/plugins/test_dns.py
Normal file
55
tests/plugins/test_dns.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Test DNS plugin."""
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.docker.interface import DockerInterface
|
||||
|
||||
|
||||
@pytest.fixture(name="docker_interface")
|
||||
async def fixture_docker_interface() -> tuple[AsyncMock, AsyncMock]:
|
||||
"""Mock docker interface methods."""
|
||||
# with patch("supervisor.docker.interface.DockerInterface.run"), patch("supervisor.docker.interface.DockerInterface.restart")
|
||||
with patch.object(DockerInterface, "run") as run, patch.object(
|
||||
DockerInterface, "restart"
|
||||
) as restart:
|
||||
yield (run, restart)
|
||||
|
||||
|
||||
@pytest.fixture(name="write_json")
|
||||
async def fixture_write_json() -> Mock:
|
||||
"""Mock json file writer."""
|
||||
with patch("supervisor.plugins.dns.write_json_file") as write_json_file:
|
||||
yield write_json_file
|
||||
|
||||
|
||||
@pytest.mark.parametrize("start", [True, False])
|
||||
async def test_config_write(
|
||||
coresys: CoreSys,
|
||||
docker_interface: tuple[AsyncMock, AsyncMock],
|
||||
write_json: Mock,
|
||||
start: bool,
|
||||
):
|
||||
"""Test config write on DNS start and restart."""
|
||||
assert coresys.plugins.dns.locals == ["dns://192.168.30.1"]
|
||||
coresys.plugins.dns.servers = ["dns://1.1.1.1", "dns://8.8.8.8"]
|
||||
|
||||
if start:
|
||||
await coresys.plugins.dns.start()
|
||||
docker_interface[0].assert_called_once()
|
||||
docker_interface[1].assert_not_called()
|
||||
else:
|
||||
await coresys.plugins.dns.restart()
|
||||
docker_interface[0].assert_not_called()
|
||||
docker_interface[1].assert_called_once()
|
||||
|
||||
write_json.assert_called_once_with(
|
||||
Path("/data/dns/coredns.json"),
|
||||
{
|
||||
"servers": ["dns://1.1.1.1", "dns://8.8.8.8"],
|
||||
"locals": ["dns://192.168.30.1"],
|
||||
"debug": False,
|
||||
},
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Test check."""
|
||||
# pylint: disable=import-error,protected-access
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -11,6 +11,19 @@ from supervisor.resolution.const import IssueType
|
||||
from supervisor.resolution.validate import get_valid_modules
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def fixture_mock_dns_query():
|
||||
"""Mock aiodns query."""
|
||||
with patch(
|
||||
"supervisor.resolution.checks.dns_server_failure.DNSResolver.query",
|
||||
new_callable=AsyncMock,
|
||||
), patch(
|
||||
"supervisor.resolution.checks.dns_server_ipv6_error.DNSResolver.query",
|
||||
new_callable=AsyncMock,
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
async def test_check_setup(coresys: CoreSys):
|
||||
"""Test check for setup."""
|
||||
coresys.core.state = CoreState.SETUP
|
||||
|
||||
128
tests/resolution/check/test_check_dns_server_failure.py
Normal file
128
tests/resolution/check/test_check_dns_server_failure.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""Test check DNS Servers for failures."""
|
||||
from unittest.mock import AsyncMock, call, patch
|
||||
|
||||
from aiodns.error import DNSError
|
||||
import pytest
|
||||
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.resolution.checks.dns_server_failure import CheckDNSServerFailures
|
||||
from supervisor.resolution.const import ContextType, IssueType
|
||||
|
||||
|
||||
@pytest.fixture(name="dns_query")
|
||||
async def fixture_dns_query() -> AsyncMock:
|
||||
"""Mock aiodns query."""
|
||||
with patch(
|
||||
"supervisor.resolution.checks.dns_server_failure.DNSResolver.query",
|
||||
new_callable=AsyncMock,
|
||||
) as dns_query:
|
||||
yield dns_query
|
||||
|
||||
|
||||
async def test_base(coresys: CoreSys):
|
||||
"""Test check basics."""
|
||||
dns_server_failures = CheckDNSServerFailures(coresys)
|
||||
assert dns_server_failures.slug == "dns_server_failure"
|
||||
assert dns_server_failures.enabled
|
||||
|
||||
|
||||
async def test_check(coresys: CoreSys, dns_query: AsyncMock):
|
||||
"""Test check for DNS server failures."""
|
||||
dns_server_failures = CheckDNSServerFailures(coresys)
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
|
||||
coresys.plugins.dns.servers = ["dns://1.1.1.1"]
|
||||
assert dns_server_failures.dns_servers == [
|
||||
"dns://1.1.1.1",
|
||||
"dns://192.168.30.1",
|
||||
]
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
await dns_server_failures.run_check.__wrapped__(dns_server_failures)
|
||||
assert dns_query.call_args_list == [
|
||||
call("_checkdns.home-assistant.io", "A"),
|
||||
call("_checkdns.home-assistant.io", "A"),
|
||||
]
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
dns_query.reset_mock()
|
||||
coresys.plugins.dns.servers = []
|
||||
assert dns_server_failures.dns_servers == ["dns://192.168.30.1"]
|
||||
|
||||
dns_query.side_effect = DNSError()
|
||||
await dns_server_failures.run_check.__wrapped__(dns_server_failures)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "A")
|
||||
|
||||
assert len(coresys.resolution.issues) == 1
|
||||
assert coresys.resolution.issues[0].type is IssueType.DNS_SERVER_FAILED
|
||||
assert coresys.resolution.issues[0].context is ContextType.DNS_SERVER
|
||||
assert coresys.resolution.issues[0].reference == "dns://192.168.30.1"
|
||||
|
||||
|
||||
async def test_approve(coresys: CoreSys, dns_query: AsyncMock):
|
||||
"""Test approve existing DNS Server failure issues."""
|
||||
dns_server_failures = CheckDNSServerFailures(coresys)
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
|
||||
assert dns_server_failures.dns_servers == ["dns://192.168.30.1"]
|
||||
dns_query.side_effect = DNSError()
|
||||
|
||||
assert await dns_server_failures.approve_check(reference="dns://1.1.1.1") is False
|
||||
dns_query.assert_not_called()
|
||||
|
||||
assert (
|
||||
await dns_server_failures.approve_check(reference="dns://192.168.30.1") is True
|
||||
)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "A")
|
||||
|
||||
dns_query.reset_mock()
|
||||
dns_query.side_effect = None
|
||||
assert (
|
||||
await dns_server_failures.approve_check(reference="dns://192.168.30.1") is False
|
||||
)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "A")
|
||||
|
||||
|
||||
async def test_did_run(coresys: CoreSys):
|
||||
"""Test that the check ran as expected."""
|
||||
dns_server_failures = CheckDNSServerFailures(coresys)
|
||||
should_run = dns_server_failures.states
|
||||
should_not_run = [state for state in CoreState if state not in should_run]
|
||||
assert should_run == [CoreState.RUNNING]
|
||||
assert len(should_not_run) != 0
|
||||
|
||||
with patch.object(CheckDNSServerFailures, "run_check", return_value=None) as check:
|
||||
for state in should_run:
|
||||
coresys.core.state = state
|
||||
await dns_server_failures()
|
||||
check.assert_called_once()
|
||||
check.reset_mock()
|
||||
|
||||
for state in should_not_run:
|
||||
coresys.core.state = state
|
||||
await dns_server_failures()
|
||||
check.assert_not_called()
|
||||
check.reset_mock()
|
||||
|
||||
|
||||
async def test_check_if_affected(coresys: CoreSys):
|
||||
"""Test that check is still executed even if already affected."""
|
||||
dns_server_failures = CheckDNSServerFailures(coresys)
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
|
||||
coresys.resolution.create_issue(
|
||||
IssueType.DNS_SERVER_FAILED,
|
||||
ContextType.DNS_SERVER,
|
||||
reference="dns://192.168.30.1",
|
||||
)
|
||||
assert len(coresys.resolution.issues) == 1
|
||||
|
||||
with patch.object(
|
||||
CheckDNSServerFailures, "approve_check", return_value=True
|
||||
) as approve, patch.object(
|
||||
CheckDNSServerFailures, "run_check", return_value=None
|
||||
) as check:
|
||||
await dns_server_failures()
|
||||
approve.assert_called_once()
|
||||
check.assert_called_once()
|
||||
148
tests/resolution/check/test_check_dns_server_ipv6_error.py
Normal file
148
tests/resolution/check/test_check_dns_server_ipv6_error.py
Normal file
@@ -0,0 +1,148 @@
|
||||
"""Test check DNS Servers for IPv6 errors."""
|
||||
from unittest.mock import AsyncMock, call, patch
|
||||
|
||||
from aiodns.error import DNSError
|
||||
import pytest
|
||||
|
||||
from supervisor.const import CoreState
|
||||
from supervisor.coresys import CoreSys
|
||||
from supervisor.resolution.checks.dns_server_ipv6_error import CheckDNSServerIPv6Errors
|
||||
from supervisor.resolution.const import ContextType, IssueType
|
||||
|
||||
|
||||
@pytest.fixture(name="dns_query")
|
||||
async def fixture_dns_query() -> AsyncMock:
|
||||
"""Mock aiodns query."""
|
||||
with patch(
|
||||
"supervisor.resolution.checks.dns_server_ipv6_error.DNSResolver.query",
|
||||
new_callable=AsyncMock,
|
||||
) as dns_query:
|
||||
yield dns_query
|
||||
|
||||
|
||||
async def test_base(coresys: CoreSys):
|
||||
"""Test check basics."""
|
||||
dns_server_ipv6_errors = CheckDNSServerIPv6Errors(coresys)
|
||||
assert dns_server_ipv6_errors.slug == "dns_server_ipv6_error"
|
||||
assert dns_server_ipv6_errors.enabled
|
||||
|
||||
|
||||
async def test_check(coresys: CoreSys, dns_query: AsyncMock):
|
||||
"""Test check for DNS server IPv6 errors."""
|
||||
dns_server_ipv6_errors = CheckDNSServerIPv6Errors(coresys)
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
|
||||
coresys.plugins.dns.servers = ["dns://1.1.1.1"]
|
||||
assert dns_server_ipv6_errors.dns_servers == [
|
||||
"dns://1.1.1.1",
|
||||
"dns://192.168.30.1",
|
||||
]
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
await dns_server_ipv6_errors.run_check.__wrapped__(dns_server_ipv6_errors)
|
||||
assert dns_query.call_args_list == [
|
||||
call("_checkdns.home-assistant.io", "AAAA"),
|
||||
call("_checkdns.home-assistant.io", "AAAA"),
|
||||
]
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
dns_query.reset_mock()
|
||||
coresys.plugins.dns.servers = []
|
||||
assert dns_server_ipv6_errors.dns_servers == ["dns://192.168.30.1"]
|
||||
|
||||
dns_query.side_effect = DNSError(1, "DNS server returned answer with no data")
|
||||
await dns_server_ipv6_errors.run_check.__wrapped__(dns_server_ipv6_errors)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "AAAA")
|
||||
assert len(coresys.resolution.issues) == 0
|
||||
|
||||
dns_query.reset_mock()
|
||||
dns_query.side_effect = DNSError(4, "Domain name not found")
|
||||
await dns_server_ipv6_errors.run_check.__wrapped__(dns_server_ipv6_errors)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "AAAA")
|
||||
|
||||
assert len(coresys.resolution.issues) == 1
|
||||
assert coresys.resolution.issues[0].type is IssueType.DNS_SERVER_IPV6_ERROR
|
||||
assert coresys.resolution.issues[0].context is ContextType.DNS_SERVER
|
||||
assert coresys.resolution.issues[0].reference == "dns://192.168.30.1"
|
||||
|
||||
|
||||
async def test_approve(coresys: CoreSys, dns_query: AsyncMock):
|
||||
"""Test approve existing DNS Server IPv6 error issues."""
|
||||
dns_server_ipv6_errors = CheckDNSServerIPv6Errors(coresys)
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
|
||||
assert dns_server_ipv6_errors.dns_servers == ["dns://192.168.30.1"]
|
||||
dns_query.side_effect = DNSError(4, "Domain name not found")
|
||||
|
||||
assert (
|
||||
await dns_server_ipv6_errors.approve_check(reference="dns://1.1.1.1") is False
|
||||
)
|
||||
dns_query.assert_not_called()
|
||||
|
||||
assert (
|
||||
await dns_server_ipv6_errors.approve_check(reference="dns://192.168.30.1")
|
||||
is True
|
||||
)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "AAAA")
|
||||
|
||||
dns_query.reset_mock()
|
||||
dns_query.side_effect = DNSError(1, "DNS server returned answer with no data")
|
||||
assert (
|
||||
await dns_server_ipv6_errors.approve_check(reference="dns://192.168.30.1")
|
||||
is False
|
||||
)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "AAAA")
|
||||
|
||||
dns_query.reset_mock()
|
||||
dns_query.side_effect = None
|
||||
assert (
|
||||
await dns_server_ipv6_errors.approve_check(reference="dns://192.168.30.1")
|
||||
is False
|
||||
)
|
||||
dns_query.assert_called_once_with("_checkdns.home-assistant.io", "AAAA")
|
||||
|
||||
|
||||
async def test_did_run(coresys: CoreSys):
|
||||
"""Test that the check ran as expected."""
|
||||
dns_server_ipv6_errors = CheckDNSServerIPv6Errors(coresys)
|
||||
should_run = dns_server_ipv6_errors.states
|
||||
should_not_run = [state for state in CoreState if state not in should_run]
|
||||
assert should_run == [CoreState.RUNNING]
|
||||
assert len(should_not_run) != 0
|
||||
|
||||
with patch.object(
|
||||
CheckDNSServerIPv6Errors, "run_check", return_value=None
|
||||
) as check:
|
||||
for state in should_run:
|
||||
coresys.core.state = state
|
||||
await dns_server_ipv6_errors()
|
||||
check.assert_called_once()
|
||||
check.reset_mock()
|
||||
|
||||
for state in should_not_run:
|
||||
coresys.core.state = state
|
||||
await dns_server_ipv6_errors()
|
||||
check.assert_not_called()
|
||||
check.reset_mock()
|
||||
|
||||
|
||||
async def test_check_if_affected(coresys: CoreSys):
|
||||
"""Test that check is still executed even if already affected."""
|
||||
dns_server_ipv6_errors = CheckDNSServerIPv6Errors(coresys)
|
||||
coresys.core.state = CoreState.RUNNING
|
||||
|
||||
coresys.resolution.create_issue(
|
||||
IssueType.DNS_SERVER_IPV6_ERROR,
|
||||
ContextType.DNS_SERVER,
|
||||
reference="dns://192.168.30.1",
|
||||
)
|
||||
assert len(coresys.resolution.issues) == 1
|
||||
|
||||
with patch.object(
|
||||
CheckDNSServerIPv6Errors, "approve_check", return_value=True
|
||||
) as approve, patch.object(
|
||||
CheckDNSServerIPv6Errors, "run_check", return_value=None
|
||||
) as check:
|
||||
await dns_server_ipv6_errors()
|
||||
approve.assert_called_once()
|
||||
check.assert_called_once()
|
||||
Reference in New Issue
Block a user