222 lines
5.9 KiB
Go
222 lines
5.9 KiB
Go
package health
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"slices"
|
|
"time"
|
|
)
|
|
|
|
// Status of the service readiness.
|
|
type ReadinessStatus string
|
|
|
|
// String returns the string representation of the readiness status.
|
|
func (s ReadinessStatus) String() string {
|
|
return string(s)
|
|
}
|
|
|
|
// HTTPStatus returns the corresponding HTTP status code for the readiness status.
|
|
func (s ReadinessStatus) HTTPStatus() int {
|
|
switch s {
|
|
case ReadinessStatusReady:
|
|
return http.StatusOK
|
|
case ReadinessStatusDegraded:
|
|
return http.StatusOK // 206 could also be suitable, but let's avoid false alarms
|
|
case ReadinessStatusFatal:
|
|
return http.StatusServiceUnavailable
|
|
case ReadinessStatusNotReady:
|
|
return http.StatusOK // treat as ok to avoid false alarms
|
|
default:
|
|
return http.StatusOK
|
|
}
|
|
}
|
|
|
|
const (
|
|
// Service is ready to handle requests.
|
|
ReadinessStatusReady ReadinessStatus = "ready"
|
|
// Service is not ready to handle requests.
|
|
ReadinessStatusNotReady ReadinessStatus = "not_ready"
|
|
// Service experienced a fatal error and cannot handle requests.
|
|
ReadinessStatusFatal ReadinessStatus = "fatal"
|
|
// Service is degraded but can still handle requests.
|
|
ReadinessStatusDegraded ReadinessStatus = "degraded"
|
|
)
|
|
|
|
// Returns the worst readiness status from a list of statuses.
|
|
func worstStatus(statuses []ReadinessStatus) ReadinessStatus {
|
|
if slices.Contains(statuses, ReadinessStatusFatal) {
|
|
return ReadinessStatusFatal
|
|
}
|
|
if slices.Contains(statuses, ReadinessStatusNotReady) {
|
|
return ReadinessStatusNotReady
|
|
}
|
|
if slices.Contains(statuses, ReadinessStatusDegraded) {
|
|
return ReadinessStatusDegraded
|
|
}
|
|
return ReadinessStatusReady
|
|
}
|
|
|
|
// Readiness check function return type.
|
|
type ReadinessReturn struct {
|
|
// Status of the readiness check.
|
|
Status ReadinessStatus
|
|
// Optional message providing additional information about the readiness status.
|
|
Message *string
|
|
}
|
|
|
|
// Function that performs a readiness check.
|
|
type ReadinessCheckFn func() ReadinessReturn
|
|
|
|
// Individual readiness check.
|
|
type ReadinessCheck struct {
|
|
// Name of the readiness check.
|
|
Name string
|
|
// Function that performs the readiness check.
|
|
Fn ReadinessCheckFn
|
|
// Timeout for the readiness check (default: 5 seconds)
|
|
Timeout time.Duration
|
|
}
|
|
|
|
// System-generated result from an individual readiness check.
|
|
type ReadinessCheckResult struct {
|
|
// Name of the readiness check.
|
|
Name string
|
|
// Status of the readiness check.
|
|
Status ReadinessStatus
|
|
// Optional message providing additional information about the readiness status.
|
|
Message *string
|
|
// Duration of the readiness check in milliseconds.
|
|
Duration int64
|
|
}
|
|
|
|
// System readiness status.
|
|
type Readiness struct {
|
|
// Status of the service readiness. Aggregated as the worst status from
|
|
// all individual readiness checks. If no checks have been performed
|
|
// yet, the status is ReadinessStatusNotReady.
|
|
Status ReadinessStatus
|
|
// Start time of the readiness check in milliseconds since Unix epoch.
|
|
Start int64
|
|
// Duration of the readiness check in milliseconds.
|
|
Duration int64
|
|
// Individual readiness check results.
|
|
Details map[string]ReadinessCheckResult
|
|
}
|
|
|
|
// Performs a full system readiness check given a list of individual checks.
|
|
func CheckReadiness(checks []ReadinessCheck) Readiness {
|
|
start := time.Now()
|
|
details := make(map[string]ReadinessCheckResult)
|
|
var statuses []ReadinessStatus
|
|
|
|
for _, check := range checks {
|
|
checkt0 := time.Now()
|
|
result := checkWithTimeout(check)
|
|
duration := time.Since(checkt0).Milliseconds()
|
|
|
|
details[check.Name] = ReadinessCheckResult{
|
|
Name: check.Name,
|
|
Status: result.Status,
|
|
Message: result.Message,
|
|
Duration: duration,
|
|
}
|
|
statuses = append(statuses, result.Status)
|
|
}
|
|
|
|
return Readiness{
|
|
Status: worstStatus(statuses),
|
|
Start: start.UnixMilli(),
|
|
Duration: time.Since(start).Milliseconds(),
|
|
Details: details,
|
|
}
|
|
}
|
|
|
|
// Creates a handler function for readiness HTTP requests.
|
|
func ReadinessHandler(checks []ReadinessCheck) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
respondWithResult(w, CheckReadiness(checks))
|
|
}
|
|
}
|
|
|
|
// Runs a readiness check with a timeout.
|
|
func checkWithTimeout(check ReadinessCheck) ReadinessReturn {
|
|
timeout := check.Timeout
|
|
if timeout == 0 {
|
|
timeout = 5 * time.Second
|
|
}
|
|
|
|
resultCh := make(chan ReadinessReturn, 1)
|
|
|
|
go func() {
|
|
resultCh <- check.Fn()
|
|
}()
|
|
|
|
select {
|
|
case result := <-resultCh:
|
|
return result
|
|
case <-time.After(timeout):
|
|
msg := "readiness check timed out"
|
|
return ReadinessReturn{
|
|
Status: ReadinessStatusFatal,
|
|
Message: &msg,
|
|
}
|
|
}
|
|
}
|
|
|
|
func respondWithResult(w http.ResponseWriter, result Readiness) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(result.Status.HTTPStatus())
|
|
json.NewEncoder(w).Encode(result)
|
|
}
|
|
|
|
// ScheduledReadiness performs periodic readiness checks and stores the latest result.
|
|
type ScheduledReadiness struct {
|
|
checks []ReadinessCheck
|
|
interval time.Duration
|
|
currentState Readiness
|
|
ticker *time.Ticker
|
|
quit chan struct{}
|
|
}
|
|
|
|
// NewScheduledReadiness creates a new ScheduledReadiness instance.
|
|
func NewScheduledReadiness(checks []ReadinessCheck, interval time.Duration) *ScheduledReadiness {
|
|
return &ScheduledReadiness{
|
|
checks: checks,
|
|
interval: interval,
|
|
quit: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// Start begins the periodic readiness checks.
|
|
func (sr *ScheduledReadiness) Start() {
|
|
sr.ticker = time.NewTicker(sr.interval)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-sr.ticker.C:
|
|
sr.currentState = CheckReadiness(sr.checks)
|
|
case <-sr.quit:
|
|
sr.ticker.Stop()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Stop halts the periodic readiness checks.
|
|
func (sr *ScheduledReadiness) Stop() {
|
|
close(sr.quit)
|
|
}
|
|
|
|
// GetCurrentState returns the latest readiness state.
|
|
func (sr *ScheduledReadiness) GetCurrentState() Readiness {
|
|
return sr.currentState
|
|
}
|
|
|
|
// Creates a handler function for scheduled readiness HTTP requests.
|
|
func (sr *ScheduledReadiness) ReadinessHandler() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
respondWithResult(w, sr.GetCurrentState())
|
|
}
|
|
}
|