Files
patrickandClaude Sonnet 5.5 453c077386 feat: Phase 6d API-Selbsttest gegen Checkmk
Lesender Selbsttest (Version, Services, Hosts, Downtimes) beim Start und
täglich 04:00, Seite /selftest, Teilcheck checkmk_api in /health, Chat-Meldung
bei Wechsel. Toleranter Antwort-Parser (value/members, state_type int/String),
Fallbacks für Host-Abfrage und Downtime-Liste, Fehlerhinweise je HTTP-Status.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 12:55:57 +01:00

57 lines
2.7 KiB
Python

import asyncio
import time
import uuid
from typing import Callable, Iterable, Sequence
from ..chat import Target
from ..models import KIND_SYSTEM, STATE_CRIT, STATE_OK, Event, Key, StateRow
from ..poller.outage import OutageNotice, RestoredNotice
from .delivery import AdapterProvider, Deliverer
from .extras import AlertActions
from .repository import AlertRepository
from .sendlimit import SendLimiter
from .system_text import (SERVICE_BASELINE, SERVICE_CHECKMK, SERVICE_SELFTEST, SYSTEM_HOST, baseline_message,
baseline_plain, outage_plain, restored_plain, wrap)
class SystemNotifier:
def __init__(self, repo: AlertRepository, adapters: AdapterProvider, limiter: SendLimiter,
send_limit: Callable[[], int], actions: AlertActions | None = None,
on_baseline: Callable[[Iterable[Key]], None] | None = None,
clock: Callable[[], float] = time.time):
self._repo = repo
self._adapters = adapters
self._deliverer = Deliverer(adapters)
self._limiter = limiter
self._send_limit = send_limit
self._actions = actions
self._on_baseline = on_baseline
self._clock = clock
async def outage(self, notice: OutageNotice) -> None:
await self._post(SERVICE_CHECKMK, STATE_CRIT, outage_plain(notice))
async def restored(self, notice: RestoredNotice) -> None:
await self._post(SERVICE_CHECKMK, STATE_OK, restored_plain(notice))
async def baseline(self, rows: Sequence[StateRow]) -> None:
if self._on_baseline is not None:
self._on_baseline([row.key for row in rows])
await self._post(SERVICE_BASELINE, STATE_OK, baseline_plain(rows), baseline_message(rows))
async def selftest(self, ok: bool, plain: str) -> None:
await self._post(SERVICE_SELFTEST, STATE_OK if ok else STATE_CRIT, plain)
async def _post(self, service: str, state: int, plain: str, message: str | None = None) -> None:
event = Event(ts=int(self._clock()), host=SYSTEM_HOST, service=service, kind=KIND_SYSTEM, state=state,
output=plain, ai_enabled=False)
ids = await asyncio.to_thread(self._repo.insert_alerts, [event], uuid.uuid4().hex)
targets = [Target(a.name, a.default_target) for a in await self._adapters()]
for target in targets:
self._limiter.acquire(target, self._send_limit(), force=True)
extras = (lambda backend: self._actions.build(backend, [event], ids)) if self._actions else None
refs = await self._deliverer.send(message or wrap(plain), targets, extras)
for ref in refs:
for alert_id in ids:
await asyncio.to_thread(self._repo.add_delivery, alert_id, ref)