feat(monitors): a DNS monitor can name a resolver; the host page puts Ansible above Docker (#5214)
DNS monitors take an optional Resolver, a server's IP. Set, the check asks that server directly for A and AAAA over UDP and records how long it took to answer; empty keeps today's lookup through the container's resolver. "Does my local DNS still answer for public names" and "does 1.1.1.1" are now separate monitors. No new dependency. On a host's page the Ansible card moves above the Docker list, which can run to dozens of containers and hid it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,14 +78,16 @@ def test_run_monitor_http_passes_config(monkeypatch):
|
||||
def test_run_monitor_dns_passes_expected_ip(monkeypatch):
|
||||
seen = {}
|
||||
|
||||
async def fake_dns(address, expected_ip=None):
|
||||
seen["address"], seen["expected_ip"] = address, expected_ip
|
||||
return {"is_up": True, "resolved_ip": "192.0.2.1"}
|
||||
async def fake_dns(address, expected_ip=None, resolver=None):
|
||||
seen["address"], seen["expected_ip"], seen["resolver"] = address, expected_ip, resolver
|
||||
return {"is_up": True, "resolved_ip": "192.0.2.1", "response_ms": 12.5}
|
||||
|
||||
monkeypatch.setattr(runner_mod, "dns_check", fake_dns)
|
||||
res = asyncio.run(runner_mod.run_monitor(_monitor("dns", "host.test", {"expected_ip": "192.0.2.1"})))
|
||||
assert seen == {"address": "host.test", "expected_ip": "192.0.2.1"}
|
||||
assert seen == {"address": "host.test", "expected_ip": "192.0.2.1", "resolver": None}
|
||||
assert res["resolved_ip"] == "192.0.2.1"
|
||||
asyncio.run(runner_mod.run_monitor(_monitor("dns", "host.test", {"resolver": "1.1.1.1"})))
|
||||
assert seen["resolver"] == "1.1.1.1"
|
||||
|
||||
|
||||
# ── config validation (shared by the form and the MCP tools) ─────────────────
|
||||
@@ -107,8 +109,9 @@ def _from_form(form, mtype):
|
||||
def test_build_config_from_the_form_per_type():
|
||||
assert _from_form(_Form(port="8080"), "tcp") == {"port": 8080}
|
||||
assert _from_form(_Form(), "tcp") == {"port": 80}
|
||||
assert _from_form(_Form(expected_ip="10.0.0.1"), "dns") == {"expected_ip": "10.0.0.1"}
|
||||
assert _from_form(_Form(), "dns") == {"expected_ip": None}
|
||||
assert _from_form(_Form(expected_ip="10.0.0.1"), "dns") == {"expected_ip": "10.0.0.1", "resolver": None}
|
||||
assert _from_form(_Form(resolver=" 1.1.1.1 "), "dns") == {"expected_ip": None, "resolver": "1.1.1.1"}
|
||||
assert _from_form(_Form(), "dns") == {"expected_ip": None, "resolver": None}
|
||||
assert _from_form(_Form(), "icmp") == {}
|
||||
|
||||
http = _from_form(_Form(method="get", expected_status="201", follow_redirects="on"), "http")
|
||||
@@ -128,6 +131,7 @@ def test_build_config_from_typed_values_defaults_to_safe_checks():
|
||||
("tcp", {"port": "http"}, "port must be a whole number"),
|
||||
("tcp", {"expected_ip": "1.2.3.4"}, "does not take expected_ip; it takes port"),
|
||||
("icmp", {"port": 22}, "takes no config"),
|
||||
("dns", {"resolver": "dns.google"}, "resolver must be a DNS server's IP address"),
|
||||
("http", {"method": "DELETE"}, "not one of GET, HEAD, POST"),
|
||||
("http", {"expected_status": 42}, "between 100 and 599"),
|
||||
("http", {"timeout_seconds": 0}, "between 1 and 60"),
|
||||
|
||||
Reference in New Issue
Block a user