Files
fn_registry/functions/infra/parse_unibus_health.go
T
egutierrez 82f1f1bd58 feat(infra): parse_unibus_health — healthz del cluster unibus → []PromSample
Función del grupo fleet-metrics que convierte la respuesta JSON del endpoint /healthz
de un nodo unibus (membershipd) en series Prometheus (unibus_up, unibus_status_ok,
unibus_posture_enforce/acl/tls/cluster, unibus_store_kv) con labels node/instance.
Pura de transformación (impure solo por el error de unmarshal). La consume el daemon
unibus_exporter del project fleet_monitoring. Con tests golden/edge/error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:26:15 +02:00

68 lines
2.3 KiB
Go

package infra
import (
"encoding/json"
"fmt"
)
// unibusHealth refleja la respuesta JSON del endpoint /healthz de un nodo del
// cluster de mensajería unibus (membershipd). Forma verificada en producción:
//
// {"posture":{"enforce":true,"acl":true,"tls":true,"cluster":true,"store":"kv"},"status":"ok"}
type unibusHealth struct {
Status string `json:"status"`
Posture struct {
Enforce bool `json:"enforce"`
ACL bool `json:"acl"`
TLS bool `json:"tls"`
Cluster bool `json:"cluster"`
Store string `json:"store"`
} `json:"posture"`
}
// ParseUnibusHealth convierte la respuesta JSON del endpoint /healthz de un nodo
// del cluster de mensajería unibus en una serie de PromSample lista para empujar
// a VictoriaMetrics, sin instrumentar el bus (solo lee su endpoint de salud).
//
// node es el nombre lógico del nodo (p.ej. "magnus"); se adjunta a cada serie
// como las labels "node" e "instance" para distinguir los nodos cuando un único
// exporter scrapea varios. La función SOLO debe llamarse cuando el nodo
// respondió: el caso "no responde" (unibus_up=0) lo emite el llamador, no esta
// función, porque sin cuerpo no hay nada que parsear.
//
// Devuelve siete series por nodo:
// - unibus_up = 1 (si el body parseó, el nodo respondió)
// - unibus_status_ok = 1 si status=="ok", si no 0
// - unibus_posture_enforce / _acl / _tls / _cluster = 1/0 según el booleano
// - unibus_store_kv = 1 si posture.store=="kv", si no 0
//
// Si el body no es JSON válido con la forma esperada, devuelve (nil, error).
func ParseUnibusHealth(node string, body []byte) ([]PromSample, error) {
var h unibusHealth
if err := json.Unmarshal(body, &h); err != nil {
return nil, fmt.Errorf("parse unibus healthz for node %q: %w", node, err)
}
b2f := func(b bool) float64 {
if b {
return 1
}
return 0
}
mk := func(name string, v float64) PromSample {
return PromSample{
Name: name,
Labels: map[string]string{"node": node, "instance": node},
Value: v,
}
}
return []PromSample{
mk("unibus_up", 1),
mk("unibus_status_ok", b2f(h.Status == "ok")),
mk("unibus_posture_enforce", b2f(h.Posture.Enforce)),
mk("unibus_posture_acl", b2f(h.Posture.ACL)),
mk("unibus_posture_tls", b2f(h.Posture.TLS)),
mk("unibus_posture_cluster", b2f(h.Posture.Cluster)),
mk("unibus_store_kv", b2f(h.Posture.Store == "kv")),
}, nil
}