3,857 bytes · the Python implementation · view raw
from typing import Optional, Sequence
from .monitor_http_check_result_types import CheckOutcome, HttpExpectation, HttpProbe
_ERROR_MESSAGES = {
"timeout": "timed out",
"dns": "DNS lookup failed",
"connection": "connection failed",
"tls": "TLS handshake failed",
"protocol": "protocol error",
}
def _whole(v: object) -> bool:
return isinstance(v, int) andnot isinstance(v, bool)
def _expected_text(statuses: Optional[Sequence[int]]) -> str:
if statuses isNone:
return"2xx"if len(statuses) == 1:
return str(statuses[0])
return"%s or %d" % (", ".join(str(s) for s in statuses[:-1]), statuses[-1])
def _threshold(name: str, v: Optional[int]) -> None:
if v isnotNoneand (not _whole(v) or v < 1):
raise ValueError("%s must be a whole number 1 or more, received %r" % (name, v))
def classify_http_check(probe: HttpProbe, expect: HttpExpectation) -> CheckOutcome:
"""One probe, one verdict, checked in a fixed order so the reason is the most fundamental thing wrong: a transport error, then the status code, then the body, then latency. A 503 that also took ten seconds is reported as the 503, because that is what someone has to fix."""
status_code, latency_ms, body, error = probe.status_code, probe.latency_ms, probe.body, probe.error
if status_code isnotNoneand (not _whole(status_code) or status_code < 100or status_code > 599):
raise ValueError("statusCode must be 100 to 599, received %r" % (status_code,))
if error isnotNoneand error notin _ERROR_MESSAGES:
raise ValueError("unknown probe error: %s" % (error,))
if status_code isNoneand error isNone:
raise ValueError("probe must have a statusCode or an error")
if latency_ms isnotNoneand (not _whole(latency_ms) or latency_ms < 0):
raise ValueError("latencyMs must be a whole number 0 or more, received %r" % (latency_ms,))
statuses, body_contains = expect.statuses, expect.body_contains
degraded, down = expect.degraded_latency_ms, expect.down_latency_ms
if statuses isnotNone:
if len(statuses) == 0:
raise ValueError("statuses must not be empty; use null to accept any 2xx")
for s in statuses:
ifnot _whole(s) or s < 100or s > 599:
raise ValueError("expected statuses must be 100 to 599, received %r" % (s,))
_threshold("degradedLatencyMs", degraded)
_threshold("downLatencyMs", down)
if degraded isnotNoneand down isnotNoneand down < degraded:
raise ValueError("downLatencyMs must not be below degradedLatencyMs: %d < %d" % (down, degraded))
if error isnotNone:
return CheckOutcome(status="down", reason=error, message=_ERROR_MESSAGES[error])
assert status_code isnotNone
accepted = 200 <= status_code <= 299if statuses isNoneelse status_code in statuses
ifnot accepted:
return CheckOutcome(status="down", reason="unexpected-status", message="HTTP %d, expected %s" % (status_code, _expected_text(statuses)))
if body_contains isnotNoneand (body isNoneor body_contains notin body):
return CheckOutcome(status="down", reason="body-mismatch", message='body does not contain "%s"' % (body_contains,))
if latency_ms isnotNone:
if down isnotNoneand latency_ms >= down:
return CheckOutcome(status="down", reason="too-slow", message="latency %d ms at or above %d ms" % (latency_ms, down))
if degraded isnotNoneand latency_ms >= degraded:
return CheckOutcome(status="degraded", reason="slow", message="latency %d ms at or above %d ms" % (latency_ms, degraded))
message = "HTTP %d" % status_code if latency_ms isNoneelse"HTTP %d in %d ms" % (status_code, latency_ms)
return CheckOutcome(status="up", reason="ok", message=message)