107 lines
3.3 KiB
Go
107 lines
3.3 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"files-service/internal/storage"
|
|
)
|
|
|
|
type HealthHandler struct {
|
|
pool *pgxpool.Pool
|
|
store *storage.Storage
|
|
}
|
|
|
|
func NewHealthHandler(pool *pgxpool.Pool, store *storage.Storage) *HealthHandler {
|
|
return &HealthHandler{pool: pool, store: store}
|
|
}
|
|
|
|
func (h *HealthHandler) Healthz(w http.ResponseWriter, _ *http.Request) {
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
|
|
}
|
|
|
|
func (h *HealthHandler) Readyz(w http.ResponseWriter, r *http.Request) {
|
|
if err := h.pool.Ping(r.Context()); err != nil {
|
|
writeInternalError(w, r, err, "database unavailable")
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "ready"})
|
|
}
|
|
|
|
func (h *HealthHandler) Detail(w http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
components := []componentProbe{
|
|
h.probePostgres(ctx),
|
|
h.probeStorage(ctx),
|
|
h.probeTrash(ctx),
|
|
h.probeStorageMetadata(ctx),
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"components": components})
|
|
}
|
|
|
|
type componentProbe struct {
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
LatencyMs int64 `json:"latency_ms"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
func (h *HealthHandler) probePostgres(ctx context.Context) componentProbe {
|
|
start := time.Now()
|
|
if err := h.pool.Ping(ctx); err != nil {
|
|
return componentProbe{Name: "postgres", Status: "down", LatencyMs: time.Since(start).Milliseconds(), Error: err.Error()}
|
|
}
|
|
return componentProbe{Name: "postgres", Status: "ok", LatencyMs: time.Since(start).Milliseconds()}
|
|
}
|
|
|
|
func (h *HealthHandler) probeStorage(ctx context.Context) componentProbe {
|
|
start := time.Now()
|
|
if err := h.store.Check(ctx); err != nil {
|
|
return componentProbe{Name: "minio_storage", Status: "down", LatencyMs: time.Since(start).Milliseconds(), Error: err.Error()}
|
|
}
|
|
return componentProbe{Name: "minio_storage", Status: "ok", LatencyMs: time.Since(start).Milliseconds()}
|
|
}
|
|
|
|
func (h *HealthHandler) probeTrash(ctx context.Context) componentProbe {
|
|
start := time.Now()
|
|
var due int
|
|
if err := h.pool.QueryRow(ctx, `
|
|
SELECT COUNT(*)::int
|
|
FROM files_nodes
|
|
WHERE purge_after IS NOT NULL
|
|
AND purge_after <= now()
|
|
`).Scan(&due); err != nil {
|
|
return componentProbe{Name: "trash_purger", Status: "down", LatencyMs: time.Since(start).Milliseconds(), Error: err.Error()}
|
|
}
|
|
if due > 0 {
|
|
return componentProbe{Name: "trash_purger", Status: "down", LatencyMs: time.Since(start).Milliseconds(), Error: "purge_due=" + strconv.Itoa(due)}
|
|
}
|
|
return componentProbe{Name: "trash_purger", Status: "ok", LatencyMs: time.Since(start).Milliseconds()}
|
|
}
|
|
|
|
func (h *HealthHandler) probeStorageMetadata(ctx context.Context) componentProbe {
|
|
start := time.Now()
|
|
var activeObjects int
|
|
if err := h.pool.QueryRow(ctx, `
|
|
SELECT COUNT(*)::int
|
|
FROM files_nodes
|
|
WHERE deleted_at IS NULL
|
|
AND storage_key IS NOT NULL
|
|
`).Scan(&activeObjects); err != nil {
|
|
return componentProbe{Name: "storage_metadata", Status: "down", LatencyMs: time.Since(start).Milliseconds(), Error: err.Error()}
|
|
}
|
|
return componentProbe{
|
|
Name: "storage_metadata",
|
|
Status: "ok",
|
|
LatencyMs: time.Since(start).Milliseconds(),
|
|
}
|
|
}
|