docs: health check wording follows the code (rework for #302)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
The README, the comments and TODO.md say the health check gives error only when every lookup in a round failed because no root server gave a usable reply, and the README drops the claim that the other failures are reported as notifications. Model: opus-5-5
This commit is contained in:
@@ -356,15 +356,15 @@ dnswatcher exposes a lightweight HTTP API for operational visibility:
|
|||||||
| `GET /metrics` | Prometheus metrics, see below |
|
| `GET /metrics` | Prometheus metrics, see below |
|
||||||
|
|
||||||
The health check, at `/.well-known/healthcheck` and `/health`, follows the most
|
The health check, at `/.well-known/healthcheck` and `/health`, follows the most
|
||||||
recent round of DNS checks. When no lookup in that round got a reply from a root
|
recent round of DNS checks. When every lookup in that round failed because no
|
||||||
server, because none of them answered or because the network intercepts DNS
|
root server gave a usable reply, as when none of them answers or the network
|
||||||
queries (see DNS Resolution Strategy), dnswatcher could not reach the DNS at
|
intercepts DNS queries (see DNS Resolution Strategy), dnswatcher could not reach
|
||||||
all. The health check then gives `status` `error` and answers
|
the DNS at all. The health check then gives `status` `error` and answers
|
||||||
`503 Service Unavailable`, so the image's health check fails and Docker and
|
`503 Service Unavailable`, so the image's health check fails and Docker and
|
||||||
upaas see the container as unhealthy. Otherwise it gives `ok` and answers
|
upaas see the container as unhealthy. Otherwise it gives `ok` and answers
|
||||||
`200 OK`, as it also does before the first round finishes. A round in which the
|
`200 OK`, as it also does before the first round finishes. A round gives `ok`
|
||||||
root servers answered gives `ok` even when a name's own nameservers do not
|
even when a name's own nameservers do not answer or a domain does not exist,
|
||||||
answer or a domain does not exist; those are reported as notifications.
|
since the root servers gave a usable reply to those lookups.
|
||||||
|
|
||||||
In `/api/v1/status`, each nameserver entry and certificate entry whose `status`
|
In `/api/v1/status`, each nameserver entry and certificate entry whose `status`
|
||||||
is `error` also has `error`, the reason, as in the state file (see State File
|
is `error` also has `error`, the reason, as in the state file (see State File
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ milestone: https://git.eeqj.de/sneak/dnswatcher/milestone/7
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-10-18: the health check answers 503 with `status` `error` when no lookup
|
- 2026-10-18: the health check answers 503 with `status` `error` when every
|
||||||
in the most recent round of DNS checks got a reply from a root server, as on a
|
lookup in the most recent round of DNS checks failed because no root server
|
||||||
network that intercepts DNS queries (closes #302).
|
gave a usable reply, as on a network that intercepts DNS queries (closes
|
||||||
|
#302).
|
||||||
- 2026-10-16: the first reply marked "recursion available" from a root server's
|
- 2026-10-16: the first reply marked "recursion available" from a root server's
|
||||||
address, a refusal included, stops the lookup with "this network intercepts
|
address, a refusal included, stops the lookup with "this network intercepts
|
||||||
DNS queries", so on a network that sends DNS to a resolver dnswatcher and its
|
DNS queries", so on a network that sends DNS to a resolver dnswatcher and its
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import (
|
|||||||
// four operators mark their replies recursion available, as on a network
|
// four operators mark their replies recursion available, as on a network
|
||||||
// that intercepts DNS queries; one that gives no reply is passed over
|
// that intercepts DNS queries; one that gives no reply is passed over
|
||||||
// for the next, and if none replies, no root server answered. Either
|
// for the next, and if none replies, no root server answered. Either
|
||||||
// way no lookup got a reply from a root server, so the round could not
|
// way every lookup failed because no root server gave a usable reply,
|
||||||
// reach the DNS. TestFirstRunBaseline and TestDomainThatDoesNotExist
|
// so the round could not reach the DNS. TestFirstRunBaseline and
|
||||||
// check rounds in which the root servers answered.
|
// TestDomainThatDoesNotExist check rounds in which the root servers
|
||||||
|
// gave a usable reply.
|
||||||
func TestDNSUnreachableWhenNoRootServerReplies(t *testing.T) {
|
func TestDNSUnreachableWhenNoRootServerReplies(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -58,7 +59,7 @@ func TestDNSUnreachableWhenNoRootServerReplies(t *testing.T) {
|
|||||||
|
|
||||||
if !w.DNSUnreachable() {
|
if !w.DNSUnreachable() {
|
||||||
t.Error("DNSUnreachable is false after a round in " +
|
t.Error("DNSUnreachable is false after a round in " +
|
||||||
"which no lookup got a reply from a root server")
|
"which no root server gave a usable reply")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -204,13 +204,13 @@ func (w *Watcher) RunOnce(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DNSUnreachable reports whether the most recent round of DNS checks
|
// DNSUnreachable reports whether the most recent round of DNS checks
|
||||||
// could not reach the DNS at all: no lookup in it got a reply from a
|
// could not reach the DNS at all: every lookup in it failed because no
|
||||||
// root server, because none of them answered or because the network
|
// root server gave a usable reply, as when none of them answered or the
|
||||||
// intercepts DNS queries. A round in which the root servers answered
|
// network intercepts DNS queries. A round reached the DNS even when a
|
||||||
// reached the DNS, even when a name's own nameservers did not answer or
|
// name's own nameservers did not answer or a domain does not exist,
|
||||||
// a domain does not exist. It is false until the first round finishes,
|
// since the root servers gave a usable reply to those lookups. It is
|
||||||
// and a round that shutdown cut short changes nothing. It may be called
|
// false until the first round finishes, and a round that shutdown cut
|
||||||
// while a round runs.
|
// short changes nothing. It may be called while a round runs.
|
||||||
func (w *Watcher) DNSUnreachable() bool {
|
func (w *Watcher) DNSUnreachable() bool {
|
||||||
return w.dnsUnreachable.Load()
|
return w.dnsUnreachable.Load()
|
||||||
}
|
}
|
||||||
@@ -304,10 +304,10 @@ func (w *Watcher) runDNSChecks(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// noRootServerReplied reports whether errs, the errors of the lookups in
|
// noRootServerReplied reports whether errs, the errors of the lookups in
|
||||||
// a round of DNS checks, nil for a lookup that succeeded, show that no
|
// a round of DNS checks, nil for a lookup that succeeded, show that
|
||||||
// lookup got a reply from a root server: each failed because none of
|
// every lookup failed because no root server gave a usable reply: each
|
||||||
// them answered, or because the network intercepts DNS queries. A round
|
// is ErrNoRootServerAnswered or ErrIntercepted. A round with no lookups
|
||||||
// with no lookups shows nothing.
|
// shows nothing.
|
||||||
func noRootServerReplied(errs []error) bool {
|
func noRootServerReplied(errs []error) bool {
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
if !errors.Is(err, resolver.ErrNoRootServerAnswered) &&
|
if !errors.Is(err, resolver.ErrNoRootServerAnswered) &&
|
||||||
|
|||||||
Reference in New Issue
Block a user