--- name: parse_unibus_health kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func ParseUnibusHealth(node string, body []byte) ([]PromSample, error)" description: "Convierte la respuesta JSON del endpoint /healthz de un nodo del cluster de mensajería unibus (membershipd) en una serie de PromSample lista para empujar a VictoriaMetrics, sin instrumentar el bus: solo lee su endpoint de salud. Adjunta a cada serie las labels node e instance (= nombre lógico del nodo) para distinguir los nodos cuando un único exporter scrapea varios. Emite siete series por nodo: unibus_up, unibus_status_ok, unibus_posture_enforce/acl/tls/cluster y unibus_store_kv. Devuelve error si el body no es JSON válido con la forma esperada." tags: [prometheus, metrics, unibus, nats, healthz, posture, fleet-metrics, infra, monitoring] uses_functions: [] uses_types: ["PromSample_go_infra"] returns: [] returns_optional: false error_type: "error_go_core" imports: ["encoding/json", "fmt"] params: - name: node desc: "nombre lógico del nodo (p.ej. \"magnus\"); se adjunta como labels node e instance a cada serie" - name: body desc: "cuerpo JSON crudo devuelto por GET https://:8470/healthz, forma {\"posture\":{enforce,acl,tls,cluster bool; store string},\"status\":string}" output: "slice de 7 PromSample con labels {node,instance}: unibus_up=1, unibus_status_ok (1 si status==ok), unibus_posture_enforce/acl/tls/cluster (1/0), unibus_store_kv (1 si posture.store==kv). Error si el body no es JSON válido." tested: true test_file_path: "functions/infra/parse_unibus_health_test.go" tests: - "TestParseUnibusHealthGolden" - "TestParseUnibusHealthDegraded" - "TestParseUnibusHealthInvalid" --- # parse_unibus_health Función pura de transformación (clasificada `impure` solo porque devuelve `error` al fallar el unmarshal; no hace I/O ni red) que traduce la salud de un nodo del bus de mensajería **unibus** a métricas Prometheus. Pertenece al grupo de capacidad `fleet-metrics`: se compone con `format_prom_exposition_go_infra` (serializar) y `push_prom_remote_go_infra` (empujar a VictoriaMetrics). El endpoint `/healthz` de cada nodo (`membershipd`) responde, verificado en producción: ```json {"posture":{"enforce":true,"acl":true,"tls":true,"cluster":true,"store":"kv"},"status":"ok"} ``` ## Ejemplo ```go package main import ( "fmt" "time" "fn-registry/functions/infra" ) func main() { body := []byte(`{"posture":{"enforce":true,"acl":true,"tls":true,"cluster":true,"store":"kv"},"status":"ok"}`) samples, err := infra.ParseUnibusHealth("magnus", body) if err != nil { panic(err) } // Serializa y (en un exporter real) empuja a VictoriaMetrics. fmt.Print(infra.FormatPromExposition(samples, time.Now().UnixMilli())) // unibus_up{instance="magnus",node="magnus"} 1 ... // unibus_posture_enforce{instance="magnus",node="magnus"} 1 ... } ``` ## Cuando usarla Úsala dentro de un exporter que monitoriza el cluster unibus: tras hacer `GET https://:8470/healthz` con la CA del cluster, pasa el cuerpo a esta función para obtener las series del nodo. Llámala **solo cuando el nodo respondió**; si el GET falla (timeout, TLS, no-2xx), emite tú `unibus_up=0` para ese nodo, porque sin cuerpo no hay nada que parsear. ## Gotchas - No emite `unibus_up=0`: ese caso (nodo caído) es responsabilidad del llamador, que sabe si el GET falló. Esta función siempre emite `unibus_up=1` porque solo se la llama con un cuerpo recibido. - Las labels `node` e `instance` toman el mismo valor (el nombre lógico del nodo). El `push_prom_remote_go_infra` añadiría `instance` vía `extra_label` por igual a todas las series del body; por eso aquí ya se fija `instance` por-serie, para que cada nodo unibus conserve su identidad cuando un solo exporter empuja los de varios nodos en un único POST. - Solo lee la posture y el status que hoy expone `/healthz`. Métricas profundas de NATS/JetStream (msgs/s, conexiones, RAFT leader por stream) NO salen de aquí: requieren el monitoring embebido de NATS (puerto 8222), que en producción está cerrado.