Files
training-software/api/views.py
Paperclip CTO 3d541d818a feat(TRA-249): M5 observability, SLOs, backup, and release readiness
- Add prometheus-client to base requirements; sentry-sdk to prod
- api/metrics.py: define HTTP latency histogram, request/error counters, in-flight gauge
- api/middleware.py: extend SecurityAuditMiddleware to observe all four Prometheus collectors per request; low-cardinality path_template label via URL resolver
- api/views.py: /metrics/ endpoint (gated by METRICS_ENABLED setting)
- api/urls.py: wire /metrics/ route
- config/settings/prod.py: METRICS_ENABLED flag; optional Sentry SDK init via SENTRY_DSN env var
- ops/prometheus/alerts.yml: Prometheus alert rules for p95 latency SLO (≤500 ms), error rate SLO (<1%), availability, and saturation
- ops/prometheus/prometheus.yml: scrape config for app + blackbox healthcheck probe
- ops/scripts/backup.sh: pg_dump → S3 STANDARD_IA with retention metadata
- ops/scripts/restore.sh: pg_restore from S3 or local file with interactive confirmation guard
- ops/scripts/synthetic-check.sh: post-deploy smoke test (healthz, metrics gate, schema, 404 shape)
- docs/TRA-249-observability-slos.md: SLO table, PromQL reference queries, alert routing
- docs/TRA-249-backup-restore.md: RPO/RTO targets, drill procedure, restore validation steps
- docs/TRA-249-release-checklist.md: pre/post-deploy checklist
- docs/TRA-249-rollback-runbook.md: decision matrix, app rollback, migration revert, DB restore path

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:14:18 +02:00

60 lines
1.9 KiB
Python

import time
from django.conf import settings
from django.db import connection, OperationalError
from django.core.cache import cache
from django.http import HttpResponse
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import AllowAny, IsAdminUser
from rest_framework.response import Response
from rest_framework import status
@api_view(["GET"])
@authentication_classes([])
@permission_classes([AllowAny])
def healthcheck(request):
"""Liveness + readiness probe endpoint. Returns 200 when all dependencies are reachable."""
checks = {}
overall_ok = True
# Database
try:
connection.ensure_connection()
checks["db"] = "ok"
except OperationalError as exc:
checks["db"] = f"error: {exc}"
overall_ok = False
# Cache / Redis
try:
key = "_healthcheck"
cache.set(key, "1", timeout=5)
assert cache.get(key) == "1"
checks["cache"] = "ok"
except Exception as exc:
checks["cache"] = f"error: {exc}"
overall_ok = False
http_status = status.HTTP_200_OK if overall_ok else status.HTTP_503_SERVICE_UNAVAILABLE
return Response(
{"status": "ok" if overall_ok else "degraded", "checks": checks},
status=http_status,
)
@api_view(["GET"])
@authentication_classes([])
@permission_classes([AllowAny])
def metrics(request):
"""Prometheus metrics scrape endpoint.
Restricted to internal networks via METRICS_ALLOWED_HOSTS setting (checked
at the reverse-proxy level in prod). Returns 403 when the feature flag is
off so the endpoint is a no-op in environments where it is not configured.
"""
if not getattr(settings, "METRICS_ENABLED", False):
return HttpResponse(status=403)
from prometheus_client import generate_latest, CONTENT_TYPE_LATEST
return HttpResponse(generate_latest(), content_type=CONTENT_TYPE_LATEST)