Files
home-network-k3s-impl/platform/monitoring/tests/keycloak-passkey-reconciler.test.yaml
jakman85andClaude Opus 5.5 7c4eb439b2 feat(monitoring): alert when the keycloak passkey reconciler's Job fails
KeycloakPasskeyReconcilerFailing (warning, for 15m) on
kube_job_status_failed{namespace="keycloak", job_name=~"keycloak-passkey-reconciler-.*"},
following the sibling client-secret reconciler's rule. promtool unit tests pin it in
both directions: quiet when succeeding, firing after 15m on a failed Job, quiet on the
SIBLING's failure, and quiet on a single transient failure. Runbook added and projected
by the runbooks ConfigMap generator.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-29 21:41:15 -05:00

91 lines
5.4 KiB
YAML

# promtool rule-unit tests for KeycloakPasskeyReconcilerFailing (platform/monitoring/alerts.yaml,
# group platform-health.identity).
# Run with: platform/monitoring/tests/run.sh (the merge gate runs the same harness via
# conformance/tests/l0-static/alert-rules-unit-tested)
#
# WHAT THE RULE CLAIMS, PINNED IN BOTH DIRECTIONS. The passkey reconciler
# (platform/keycloak/passkey-reconciler.yaml) runs every 15 minutes with backoffLimit 0, so one
# failed tick is one Job with kube_job_status_failed = 1, kept for an hour (ttlSecondsAfterFinished)
# and then garbage-collected. Its steady state is GREEN whether or not git lets it bind the browser
# flow, so a failed Job is always news. What can go wrong with the rule:
# * it never fires — a job_name regex that misses the CronJob's Job names (`<cronjob>-<minutes>`);
# * it fires on the SIBLING — the client-secret reconciler lives in the same namespace, and its
# failures already have their own alert and runbook; one cause, one alert;
# * it fires on a single transient failure that the next tick cleared — `for: 15m` exists for that.
# Fixture names are prod's real shape: CronJob Jobs are named <cronjob>-<scheduled unix minutes>.
rule_files:
- generated.rules.yaml
evaluation_interval: 1m
tests:
# =============================================================================================
# 1. HEALTHY: every passkey Job succeeded -> SILENT. The series exist with failed = 0, so a rule
# that forgot the `> 0` would fire here.
# =============================================================================================
- interval: 1m
name: passkey reconciler succeeding - quiet
input_series:
- series: 'kube_job_status_failed{job="kube-state-metrics",namespace="keycloak",job_name="keycloak-passkey-reconciler-29889000"}'
values: '0+0x60'
- series: 'kube_job_status_succeeded{job="kube-state-metrics",namespace="keycloak",job_name="keycloak-passkey-reconciler-29889000"}'
values: '1+0x60'
alert_rule_test:
- eval_time: 60m
alertname: KeycloakPasskeyReconcilerFailing
exp_alerts: []
# =============================================================================================
# 2. THE FAILURE: a passkey Job failed and stays failed -> FIRES after 15m (and not before).
# =============================================================================================
- interval: 1m
name: passkey reconciler Job failed - fires after 15m
input_series:
- series: 'kube_job_status_failed{job="kube-state-metrics",namespace="keycloak",job_name="keycloak-passkey-reconciler-29889015"}'
values: '1+0x60'
alert_rule_test:
- eval_time: 10m
alertname: KeycloakPasskeyReconcilerFailing
exp_alerts: []
- eval_time: 20m
alertname: KeycloakPasskeyReconcilerFailing
exp_alerts:
- exp_labels:
severity: warning
exp_annotations:
summary: "Keycloak passkey reconciler failing"
description: "A keycloak-passkey-reconciler Job has been failing for 15m. The realm's passkey policy, the passkey enrolment action or the browser login flow may differ from git, or a default required action (CONFIGURE_TOTP) may be missing. The reconciler never writes the flow users are logging in through, so a failure here does NOT by itself mean login is broken. Read the Job log: every PROBLEM line names the pass and the exact divergence."
runbook_url: "https://${BASE_DOMAIN}/runbooks/keycloak-passkey-reconciler"
# =============================================================================================
# 3. THE SIBLING'S FAILURE IS NOT THIS ALERT. The client-secret reconciler's Job fails in the
# same namespace; KeycloakClientSecretReconcilerFailing owns it. This one must stay quiet.
# =============================================================================================
- interval: 1m
name: only the client-secret reconciler failing - passkey alert quiet
input_series:
- series: 'kube_job_status_failed{job="kube-state-metrics",namespace="keycloak",job_name="keycloak-client-secret-reconciler-29889015"}'
values: '1+0x60'
- series: 'kube_job_status_failed{job="kube-state-metrics",namespace="keycloak",job_name="keycloak-passkey-reconciler-29889015"}'
values: '0+0x60'
alert_rule_test:
- eval_time: 60m
alertname: KeycloakPasskeyReconcilerFailing
exp_alerts: []
# =============================================================================================
# 4. ONE TRANSIENT FAILURE, cleared by the next tick: the failed Job is garbage-collected (its
# series ends) after 5m here and the next Job succeeds -> never held for 15m -> QUIET.
# =============================================================================================
- interval: 1m
name: single transient failure cleared within 15m - quiet
input_series:
- series: 'kube_job_status_failed{job="kube-state-metrics",namespace="keycloak",job_name="keycloak-passkey-reconciler-29889030"}'
values: '1+0x5'
- series: 'kube_job_status_failed{job="kube-state-metrics",namespace="keycloak",job_name="keycloak-passkey-reconciler-29889045"}'
values: '_x15 0+0x45'
alert_rule_test:
- eval_time: 30m
alertname: KeycloakPasskeyReconcilerFailing
exp_alerts: []