greentic_runner_host/http/
health.rs1use std::sync::atomic::{AtomicBool, Ordering};
2
3use anyhow::Error;
4use axum::Json;
5use axum::extract::State;
6use axum::response::IntoResponse;
7use time::OffsetDateTime;
8use time::format_description::well_known::Rfc3339;
9
10use crate::runner::ServerState;
11
12#[derive(Default)]
13pub struct HealthState {
14 telemetry_ready: AtomicBool,
15 secrets_ready: AtomicBool,
16 meta: parking_lot::Mutex<HealthMeta>,
17}
18
19#[derive(Default, Clone)]
20struct HealthMeta {
21 last_reload: Option<OffsetDateTime>,
22 last_error: Option<String>,
23}
24
25impl HealthState {
26 pub fn new() -> Self {
27 Self {
28 telemetry_ready: AtomicBool::new(false),
29 secrets_ready: AtomicBool::new(false),
30 meta: parking_lot::Mutex::new(HealthMeta::default()),
31 }
32 }
33
34 pub fn mark_telemetry_ready(&self) {
35 self.telemetry_ready.store(true, Ordering::SeqCst);
36 }
37
38 pub fn mark_secrets_ready(&self) {
39 self.secrets_ready.store(true, Ordering::SeqCst);
40 }
41
42 pub fn set_ready(&self) {
44 self.mark_telemetry_ready();
45 self.mark_secrets_ready();
46 }
47
48 pub fn record_reload_success(&self) {
49 let mut meta = self.meta.lock();
50 meta.last_reload = Some(OffsetDateTime::now_utc());
51 meta.last_error = None;
52 }
53
54 pub fn record_reload_error(&self, err: &Error) {
55 let mut meta = self.meta.lock();
56 meta.last_error = Some(err.to_string());
57 }
58
59 pub fn snapshot(&self) -> HealthSnapshot {
60 let meta = self.meta.lock().clone();
61 HealthSnapshot {
62 telemetry_ready: self.telemetry_ready.load(Ordering::SeqCst),
63 secrets_ready: self.secrets_ready.load(Ordering::SeqCst),
64 last_reload: meta.last_reload,
65 last_error: meta.last_error,
66 }
67 }
68}
69
70pub struct HealthSnapshot {
71 pub telemetry_ready: bool,
72 pub secrets_ready: bool,
73 pub last_reload: Option<OffsetDateTime>,
74 pub last_error: Option<String>,
75}
76
77pub async fn handler(State(state): State<ServerState>) -> impl IntoResponse {
78 let snapshot = state.health.snapshot();
79 let packs = state.active.len();
80 let status = if snapshot.telemetry_ready && snapshot.secrets_ready && packs > 0 {
81 "ok"
82 } else {
83 "degraded"
84 };
85 let last_reload = snapshot.last_reload.and_then(|ts| ts.format(&Rfc3339).ok());
86 Json(serde_json::json!({
87 "status": status,
88 "telemetry_ready": snapshot.telemetry_ready,
89 "secrets_ready": snapshot.secrets_ready,
90 "active_packs": packs,
91 "last_reload": last_reload,
92 "last_error": snapshot.last_error,
93 }))
94}