Pulse/internal/cloudcp/admin/status.go
2026-03-18 16:06:30 +00:00

101 lines
3.3 KiB
Go

package admin
import (
"net/http"
"github.com/rcourtman/pulse-go-rewrite/internal/cloudcp/cpmetrics"
"github.com/rcourtman/pulse-go-rewrite/internal/cloudcp/registry"
"github.com/rs/zerolog/log"
)
type statusResponse struct {
Version string `json:"version"`
TotalTenants int `json:"total_tenants"`
Healthy int `json:"healthy"`
Unhealthy int `json:"unhealthy"`
ByState map[registry.TenantState]int `json:"by_state"`
}
// HandleHealthz returns 200 "ok" unconditionally (liveness probe).
func HandleHealthz(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte("ok")); err != nil {
log.Error().Err(err).Msg("cloudcp.admin: write /healthz response")
}
}
// HandleReadyz returns a handler that checks database connectivity (readiness probe).
func HandleReadyz(reg *registry.TenantRegistry) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if reg == nil {
log.Error().Msg("Control plane readiness check failed: registry dependency unavailable")
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusServiceUnavailable)
_, _ = w.Write([]byte("not ready"))
return
}
if err := reg.Ping(); err != nil {
log.Warn().Err(err).Msg("Control plane readiness check failed: registry ping error")
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusServiceUnavailable)
if _, writeErr := w.Write([]byte("not ready")); writeErr != nil {
log.Error().Err(writeErr).Msg("cloudcp.admin: write /readyz unavailable response")
}
return
}
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
if _, writeErr := w.Write([]byte("ready")); writeErr != nil {
log.Error().Err(writeErr).Msg("cloudcp.admin: write /readyz response")
}
}
}
// HandleStatus returns a handler that reports aggregate tenant status.
func HandleStatus(reg *registry.TenantRegistry, version string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if reg == nil {
log.Error().Msg("Control plane status check failed: registry dependency unavailable")
http.Error(w, "service unavailable", http.StatusServiceUnavailable)
return
}
counts, err := reg.CountByState()
if err != nil {
log.Error().Err(err).Msg("Control plane status check failed: count by state")
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
// Opportunistically sync gauges on status calls (in addition to the background updater).
for state, c := range counts {
cpmetrics.TenantsByState.WithLabelValues(string(state)).Set(float64(c))
}
total := 0
for _, c := range counts {
total += c
}
healthy, unhealthy, err := reg.HealthSummary()
if err != nil {
log.Error().Err(err).Msg("Control plane status check failed: health summary")
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
resp := statusResponse{
Version: version,
TotalTenants: total,
Healthy: healthy,
Unhealthy: unhealthy,
ByState: counts,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
encodeJSON(w, resp)
}
}