podman/libpod/define/healthchecks.go

359 lines
13 KiB
Go

package define
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containers/image/v5/manifest"
)
const (
// HealthCheckHealthy describes a healthy container
HealthCheckHealthy string = "healthy"
// HealthCheckUnhealthy describes an unhealthy container
HealthCheckUnhealthy string = "unhealthy"
// HealthCheckStarting describes the time between when the container starts
// and the start-period (time allowed for the container to start and application
// to be running) expires.
HealthCheckStarting string = "starting"
// HealthCheckReset describes reset of HealthCheck logs
HealthCheckReset string = "reset"
// HealthCheckStopped describes the time when container was stopped during HealthCheck
// and HealthCheck was terminated
HealthCheckStopped string = "stopped"
)
// HealthCheckStatus represents the current state of a container
type HealthCheckStatus int
const (
// HealthCheckSuccess means the health worked
HealthCheckSuccess HealthCheckStatus = iota
// HealthCheckFailure means the health ran and failed
HealthCheckFailure HealthCheckStatus = iota
// HealthCheckContainerStopped means the health check cannot
// be run because the container is stopped
HealthCheckContainerStopped HealthCheckStatus = iota
// HealthCheckContainerNotFound means the container could
// not be found in local store
HealthCheckContainerNotFound HealthCheckStatus = iota
// HealthCheckNotDefined means the container has no health
// check defined in it
HealthCheckNotDefined HealthCheckStatus = iota
// HealthCheckInternalError means some something failed obtaining or running
// a given health check
HealthCheckInternalError HealthCheckStatus = iota
// HealthCheckDefined means the healthcheck was found on the container
HealthCheckDefined HealthCheckStatus = iota
// HealthCheckStartup means the healthcheck was unhealthy, but is still
// either within the startup HC or the startup period of the healthcheck
HealthCheckStartup HealthCheckStatus = iota
)
func (s HealthCheckStatus) String() string {
switch s {
case HealthCheckSuccess:
return HealthCheckHealthy
case HealthCheckStartup:
return HealthCheckStarting
case HealthCheckContainerStopped:
return HealthCheckStopped
default:
return HealthCheckUnhealthy
}
}
// Healthcheck defaults. These are used both in the cli as well in
// libpod and were moved from cmd/podman/common
const (
// DefaultHealthCheckInterval default value
DefaultHealthCheckInterval = "30s"
// DefaultHealthCheckRetries default value
DefaultHealthCheckRetries uint = 3
// DefaultHealthCheckStartPeriod default value
DefaultHealthCheckStartPeriod = "0s"
// DefaultHealthCheckTimeout default value
DefaultHealthCheckTimeout = "30s"
// DefaultHealthMaxLogCount default value
DefaultHealthMaxLogCount uint = 5
// DefaultHealthMaxLogSize default value
DefaultHealthMaxLogSize uint = 500
// DefaultHealthCheckLocalDestination default value
DefaultHealthCheckLocalDestination string = "local"
)
const HealthCheckEventsLoggerDestination string = "events_logger"
// HealthConfig.Test options
const (
// HealthConfigTestNone disables healthcheck
HealthConfigTestNone = "NONE"
// HealthConfigTestCmd execs arguments directly
HealthConfigTestCmd = "CMD"
// HealthConfigTestCmdShell runs commands with the system's default shell
HealthConfigTestCmdShell = "CMD-SHELL"
)
// HealthCheckOnFailureAction defines how Podman reacts when a container's health
// status turns unhealthy.
type HealthCheckOnFailureAction int
// Healthcheck on-failure actions.
const (
// HealthCheckOnFailureActionNonce instructs Podman to not react on an unhealthy status.
HealthCheckOnFailureActionNone = iota // Must be first iota for backwards compatibility
// HealthCheckOnFailureActionInvalid denotes an invalid on-failure policy.
HealthCheckOnFailureActionInvalid = iota
// HealthCheckOnFailureActionNonce instructs Podman to kill the container on an unhealthy status.
HealthCheckOnFailureActionKill = iota
// HealthCheckOnFailureActionNonce instructs Podman to restart the container on an unhealthy status.
HealthCheckOnFailureActionRestart = iota
// HealthCheckOnFailureActionNonce instructs Podman to stop the container on an unhealthy status.
HealthCheckOnFailureActionStop = iota
)
// String representations for on-failure actions.
const (
strHealthCheckOnFailureActionNone = "none"
strHealthCheckOnFailureActionInvalid = "invalid"
strHealthCheckOnFailureActionKill = "kill"
strHealthCheckOnFailureActionRestart = "restart"
strHealthCheckOnFailureActionStop = "stop"
)
// SupportedHealthCheckOnFailureActions lists all supported healthcheck restart policies.
var SupportedHealthCheckOnFailureActions = []string{
strHealthCheckOnFailureActionNone,
strHealthCheckOnFailureActionKill,
strHealthCheckOnFailureActionRestart,
strHealthCheckOnFailureActionStop,
}
// String returns the string representation of the HealthCheckOnFailureAction.
func (h HealthCheckOnFailureAction) String() string {
switch h {
case HealthCheckOnFailureActionNone:
return strHealthCheckOnFailureActionNone
case HealthCheckOnFailureActionKill:
return strHealthCheckOnFailureActionKill
case HealthCheckOnFailureActionRestart:
return strHealthCheckOnFailureActionRestart
case HealthCheckOnFailureActionStop:
return strHealthCheckOnFailureActionStop
default:
return strHealthCheckOnFailureActionInvalid
}
}
// ParseHealthCheckOnFailureAction parses the specified string into a HealthCheckOnFailureAction.
// An error is returned for an invalid input.
func ParseHealthCheckOnFailureAction(s string) (HealthCheckOnFailureAction, error) {
switch s {
case "", strHealthCheckOnFailureActionNone:
return HealthCheckOnFailureActionNone, nil
case strHealthCheckOnFailureActionKill:
return HealthCheckOnFailureActionKill, nil
case strHealthCheckOnFailureActionRestart:
return HealthCheckOnFailureActionRestart, nil
case strHealthCheckOnFailureActionStop:
return HealthCheckOnFailureActionStop, nil
default:
err := fmt.Errorf("invalid on-failure action %q for health check: supported actions are %s", s, strings.Join(SupportedHealthCheckOnFailureActions, ","))
return HealthCheckOnFailureActionInvalid, err
}
}
// StartupHealthCheck is the configuration of a startup healthcheck.
type StartupHealthCheck struct {
manifest.Schema2HealthConfig
// Successes are the number of successes required to mark the startup HC
// as passed.
// If set to 0, a single success will mark the HC as passed.
Successes int `json:",omitempty"`
}
type UpdateHealthCheckConfig struct {
// HealthLogDestination set the destination of the HealthCheck log.
// Directory path, local or events_logger (local use container state file)
// Warning: Changing this setting may cause the loss of previous logs!
HealthLogDestination *string `json:"health_log_destination,omitempty"`
// HealthMaxLogSize set maximum length in characters of stored HealthCheck log.
// ('0' value means an infinite log length)
HealthMaxLogSize *uint `json:"health_max_log_size,omitempty"`
// HealthMaxLogCount set maximum number of attempts in the HealthCheck log file.
// ('0' value means an infinite number of attempts in the log file)
HealthMaxLogCount *uint `json:"health_max_log_count,omitempty"`
// HealthOnFailure set the action to take once the container turns unhealthy.
HealthOnFailure *string `json:"health_on_failure,omitempty"`
// Disable healthchecks on container.
NoHealthCheck *bool `json:"no_healthcheck,omitempty"`
// HealthCmd set a healthcheck command for the container. ('none' disables the existing healthcheck)
HealthCmd *string `json:"health_cmd,omitempty"`
// HealthInterval set an interval for the healthcheck.
// (a value of disable results in no automatic timer setup) Changing this setting resets timer.
HealthInterval *string `json:"health_interval,omitempty"`
// HealthRetries set the number of retries allowed before a healthcheck is considered to be unhealthy.
HealthRetries *uint `json:"health_retries,omitempty"`
// HealthTimeout set the maximum time allowed to complete the healthcheck before an interval is considered failed.
HealthTimeout *string `json:"health_timeout,omitempty"`
// HealthStartPeriod set the initialization time needed for a container to bootstrap.
HealthStartPeriod *string `json:"health_start_period,omitempty"`
// HealthStartupCmd set a startup healthcheck command for the container.
HealthStartupCmd *string `json:"health_startup_cmd,omitempty"`
// HealthStartupInterval set an interval for the startup healthcheck.
// Changing this setting resets the timer, depending on the state of the container.
HealthStartupInterval *string `json:"health_startup_interval,omitempty"`
// HealthStartupRetries set the maximum number of retries before the startup healthcheck will restart the container.
HealthStartupRetries *uint `json:"health_startup_retries,omitempty"`
// HealthStartupTimeout set the maximum amount of time that the startup healthcheck may take before it is considered failed.
HealthStartupTimeout *string `json:"health_startup_timeout,omitempty"`
// HealthStartupSuccess set the number of consecutive successes before the startup healthcheck is marked as successful
// and the normal healthcheck begins (0 indicates any success will start the regular healthcheck)
HealthStartupSuccess *uint `json:"health_startup_success,omitempty"`
}
func (u *UpdateHealthCheckConfig) IsStartupHealthCheckCommandSet(startupHealthCheck *StartupHealthCheck) bool {
containsStartupHealthCheckCmd := u.HealthStartupCmd != nil
containsFlags := (u.HealthStartupInterval != nil || u.HealthStartupRetries != nil ||
u.HealthStartupTimeout != nil || u.HealthStartupSuccess != nil)
return startupHealthCheck == nil && !containsStartupHealthCheckCmd && containsFlags
}
func (u *UpdateHealthCheckConfig) IsHealthCheckCommandSet(healthCheck *manifest.Schema2HealthConfig) bool {
containsStartupHealthCheckCmd := u.HealthCmd != nil
containsFlags := (u.HealthInterval != nil || u.HealthRetries != nil ||
u.HealthTimeout != nil || u.HealthStartPeriod != nil)
return healthCheck == nil && !containsStartupHealthCheckCmd && containsFlags
}
func (u *UpdateHealthCheckConfig) SetNewStartupHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool {
changed := false
if u.HealthStartupCmd != nil {
healthCheckOptions.Cmd = *u.HealthStartupCmd
changed = true
}
if u.HealthStartupInterval != nil {
healthCheckOptions.Interval = *u.HealthStartupInterval
changed = true
}
if u.HealthStartupRetries != nil {
healthCheckOptions.Retries = int(*u.HealthStartupRetries)
changed = true
}
if u.HealthStartupTimeout != nil {
healthCheckOptions.Timeout = *u.HealthStartupTimeout
changed = true
}
if u.HealthStartupSuccess != nil {
healthCheckOptions.Successes = int(*u.HealthStartupSuccess)
changed = true
}
healthCheckOptions.StartPeriod = "1s"
return changed
}
func (u *UpdateHealthCheckConfig) SetNewHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool {
changed := false
if u.HealthCmd != nil {
healthCheckOptions.Cmd = *u.HealthCmd
changed = true
}
if u.HealthInterval != nil {
healthCheckOptions.Interval = *u.HealthInterval
changed = true
}
if u.HealthRetries != nil {
healthCheckOptions.Retries = int(*u.HealthRetries)
changed = true
}
if u.HealthTimeout != nil {
healthCheckOptions.Timeout = *u.HealthTimeout
changed = true
}
if u.HealthStartPeriod != nil {
healthCheckOptions.StartPeriod = *u.HealthStartPeriod
changed = true
}
return changed
}
func GetValidHealthCheckDestination(destination string) (string, error) {
if destination == HealthCheckEventsLoggerDestination || destination == DefaultHealthCheckLocalDestination {
return destination, nil
}
fileInfo, err := os.Stat(destination)
if err != nil {
return "", fmt.Errorf("HealthCheck Log '%s' destination error: %w", destination, err)
}
mode := fileInfo.Mode()
if !mode.IsDir() {
return "", fmt.Errorf("HealthCheck Log '%s' destination must be directory", destination)
}
absPath, err := filepath.Abs(destination)
if err != nil {
return "", err
}
return absPath, nil
}
func (u *UpdateHealthCheckConfig) GetNewGlobalHealthCheck() (GlobalHealthCheckOptions, error) {
globalOptions := GlobalHealthCheckOptions{}
healthLogDestination := u.HealthLogDestination
if u.HealthLogDestination != nil {
dest, err := GetValidHealthCheckDestination(*u.HealthLogDestination)
if err != nil {
return GlobalHealthCheckOptions{}, err
}
healthLogDestination = &dest
}
globalOptions.HealthLogDestination = healthLogDestination
globalOptions.HealthMaxLogSize = u.HealthMaxLogSize
globalOptions.HealthMaxLogCount = u.HealthMaxLogCount
if u.HealthOnFailure != nil {
val, err := ParseHealthCheckOnFailureAction(*u.HealthOnFailure)
if err != nil {
return globalOptions, err
}
globalOptions.HealthCheckOnFailureAction = &val
}
return globalOptions, nil
}
type HealthCheckOptions struct {
Cmd string
Interval string
Retries int
Timeout string
StartPeriod string
Successes int
}
type GlobalHealthCheckOptions struct {
HealthLogDestination *string
HealthMaxLogCount *uint
HealthMaxLogSize *uint
HealthCheckOnFailureAction *HealthCheckOnFailureAction
}