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) } }