mirror of
https://github.com/containers/podman.git
synced 2025-06-20 17:13:43 +08:00
Fix remote integration for healthchecks
the one remaining test that is still skipped do to missing exec function Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
@ -1169,7 +1169,7 @@ func (c *Container) start() error {
|
|||||||
c.state.State = define.ContainerStateRunning
|
c.state.State = define.ContainerStateRunning
|
||||||
|
|
||||||
if c.config.HealthCheckConfig != nil {
|
if c.config.HealthCheckConfig != nil {
|
||||||
if err := c.updateHealthStatus(HealthCheckStarting); err != nil {
|
if err := c.updateHealthStatus(define.HealthCheckStarting); err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
}
|
}
|
||||||
if err := c.startTimer(); err != nil {
|
if err := c.startTimer(); err != nil {
|
||||||
|
36
libpod/define/healthchecks.go
Normal file
36
libpod/define/healthchecks.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package define
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
)
|
@ -14,43 +14,12 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HealthCheckStatus represents the current state of a container
|
|
||||||
type HealthCheckStatus int
|
|
||||||
|
|
||||||
const (
|
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
|
|
||||||
|
|
||||||
// MaxHealthCheckNumberLogs is the maximum number of attempts we keep
|
// MaxHealthCheckNumberLogs is the maximum number of attempts we keep
|
||||||
// in the healthcheck history file
|
// in the healthcheck history file
|
||||||
MaxHealthCheckNumberLogs int = 5
|
MaxHealthCheckNumberLogs int = 5
|
||||||
// MaxHealthCheckLogLength in characters
|
// MaxHealthCheckLogLength in characters
|
||||||
MaxHealthCheckLogLength = 500
|
MaxHealthCheckLogLength = 500
|
||||||
|
|
||||||
// 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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// hcWriteCloser allows us to use bufio as a WriteCloser
|
// hcWriteCloser allows us to use bufio as a WriteCloser
|
||||||
@ -65,10 +34,10 @@ func (hcwc hcWriteCloser) Close() error {
|
|||||||
|
|
||||||
// HealthCheck verifies the state and validity of the healthcheck configuration
|
// HealthCheck verifies the state and validity of the healthcheck configuration
|
||||||
// on the container and then executes the healthcheck
|
// on the container and then executes the healthcheck
|
||||||
func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) {
|
func (r *Runtime) HealthCheck(name string) (define.HealthCheckStatus, error) {
|
||||||
container, err := r.LookupContainer(name)
|
container, err := r.LookupContainer(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name)
|
return define.HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name)
|
||||||
}
|
}
|
||||||
hcStatus, err := checkHealthCheckCanBeRun(container)
|
hcStatus, err := checkHealthCheckCanBeRun(container)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -78,7 +47,7 @@ func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runHealthCheck runs the health check as defined by the container
|
// runHealthCheck runs the health check as defined by the container
|
||||||
func (c *Container) runHealthCheck() (HealthCheckStatus, error) {
|
func (c *Container) runHealthCheck() (define.HealthCheckStatus, error) {
|
||||||
var (
|
var (
|
||||||
newCommand []string
|
newCommand []string
|
||||||
returnCode int
|
returnCode int
|
||||||
@ -87,11 +56,11 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) {
|
|||||||
)
|
)
|
||||||
hcCommand := c.HealthCheckConfig().Test
|
hcCommand := c.HealthCheckConfig().Test
|
||||||
if len(hcCommand) < 1 {
|
if len(hcCommand) < 1 {
|
||||||
return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
||||||
}
|
}
|
||||||
switch hcCommand[0] {
|
switch hcCommand[0] {
|
||||||
case "", "NONE":
|
case "", "NONE":
|
||||||
return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
||||||
case "CMD":
|
case "CMD":
|
||||||
newCommand = hcCommand[1:]
|
newCommand = hcCommand[1:]
|
||||||
case "CMD-SHELL":
|
case "CMD-SHELL":
|
||||||
@ -102,7 +71,7 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) {
|
|||||||
newCommand = hcCommand
|
newCommand = hcCommand
|
||||||
}
|
}
|
||||||
if len(newCommand) < 1 || newCommand[0] == "" {
|
if len(newCommand) < 1 || newCommand[0] == "" {
|
||||||
return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
||||||
}
|
}
|
||||||
captureBuffer := bufio.NewWriter(&capture)
|
captureBuffer := bufio.NewWriter(&capture)
|
||||||
hcw := hcWriteCloser{
|
hcw := hcWriteCloser{
|
||||||
@ -120,13 +89,13 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) {
|
|||||||
|
|
||||||
logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID())
|
logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID())
|
||||||
timeStart := time.Now()
|
timeStart := time.Now()
|
||||||
hcResult := HealthCheckSuccess
|
hcResult := define.HealthCheckSuccess
|
||||||
config := new(ExecConfig)
|
config := new(ExecConfig)
|
||||||
config.Command = newCommand
|
config.Command = newCommand
|
||||||
_, hcErr := c.Exec(config, streams, nil)
|
_, hcErr := c.Exec(config, streams, nil)
|
||||||
if hcErr != nil {
|
if hcErr != nil {
|
||||||
errCause := errors.Cause(hcErr)
|
errCause := errors.Cause(hcErr)
|
||||||
hcResult = HealthCheckFailure
|
hcResult = define.HealthCheckFailure
|
||||||
if errCause == define.ErrOCIRuntimeNotFound ||
|
if errCause == define.ErrOCIRuntimeNotFound ||
|
||||||
errCause == define.ErrOCIRuntimePermissionDenied ||
|
errCause == define.ErrOCIRuntimePermissionDenied ||
|
||||||
errCause == define.ErrOCIRuntime {
|
errCause == define.ErrOCIRuntime {
|
||||||
@ -154,7 +123,7 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) {
|
|||||||
|
|
||||||
if timeEnd.Sub(timeStart) > c.HealthCheckConfig().Timeout {
|
if timeEnd.Sub(timeStart) > c.HealthCheckConfig().Timeout {
|
||||||
returnCode = -1
|
returnCode = -1
|
||||||
hcResult = HealthCheckFailure
|
hcResult = define.HealthCheckFailure
|
||||||
hcErr = errors.Errorf("healthcheck command exceeded timeout of %s", c.HealthCheckConfig().Timeout.String())
|
hcErr = errors.Errorf("healthcheck command exceeded timeout of %s", c.HealthCheckConfig().Timeout.String())
|
||||||
}
|
}
|
||||||
hcl := newHealthCheckLog(timeStart, timeEnd, returnCode, eventLog)
|
hcl := newHealthCheckLog(timeStart, timeEnd, returnCode, eventLog)
|
||||||
@ -164,18 +133,18 @@ func (c *Container) runHealthCheck() (HealthCheckStatus, error) {
|
|||||||
return hcResult, hcErr
|
return hcResult, hcErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkHealthCheckCanBeRun(c *Container) (HealthCheckStatus, error) {
|
func checkHealthCheckCanBeRun(c *Container) (define.HealthCheckStatus, error) {
|
||||||
cstate, err := c.State()
|
cstate, err := c.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return HealthCheckInternalError, err
|
return define.HealthCheckInternalError, err
|
||||||
}
|
}
|
||||||
if cstate != define.ContainerStateRunning {
|
if cstate != define.ContainerStateRunning {
|
||||||
return HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID())
|
return define.HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID())
|
||||||
}
|
}
|
||||||
if !c.HasHealthCheck() {
|
if !c.HasHealthCheck() {
|
||||||
return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
return define.HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
|
||||||
}
|
}
|
||||||
return HealthCheckDefined, nil
|
return define.HealthCheckDefined, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHealthCheckLog(start, end time.Time, exitCode int, log string) define.HealthCheckLog {
|
func newHealthCheckLog(start, end time.Time, exitCode int, log string) define.HealthCheckLog {
|
||||||
@ -210,18 +179,18 @@ func (c *Container) updateHealthCheckLog(hcl define.HealthCheckLog, inStartPerio
|
|||||||
}
|
}
|
||||||
if hcl.ExitCode == 0 {
|
if hcl.ExitCode == 0 {
|
||||||
// set status to healthy, reset failing state to 0
|
// set status to healthy, reset failing state to 0
|
||||||
healthCheck.Status = HealthCheckHealthy
|
healthCheck.Status = define.HealthCheckHealthy
|
||||||
healthCheck.FailingStreak = 0
|
healthCheck.FailingStreak = 0
|
||||||
} else {
|
} else {
|
||||||
if len(healthCheck.Status) < 1 {
|
if len(healthCheck.Status) < 1 {
|
||||||
healthCheck.Status = HealthCheckHealthy
|
healthCheck.Status = define.HealthCheckHealthy
|
||||||
}
|
}
|
||||||
if !inStartPeriod {
|
if !inStartPeriod {
|
||||||
// increment failing streak
|
// increment failing streak
|
||||||
healthCheck.FailingStreak += 1
|
healthCheck.FailingStreak += 1
|
||||||
// if failing streak > retries, then status to unhealthy
|
// if failing streak > retries, then status to unhealthy
|
||||||
if healthCheck.FailingStreak >= c.HealthCheckConfig().Retries {
|
if healthCheck.FailingStreak >= c.HealthCheckConfig().Retries {
|
||||||
healthCheck.Status = HealthCheckUnhealthy
|
healthCheck.Status = define.HealthCheckUnhealthy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/pkg/api/handlers/utils"
|
"github.com/containers/libpod/pkg/api/handlers/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,32 +13,27 @@ func RunHealthCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
name := utils.GetName(r)
|
name := utils.GetName(r)
|
||||||
status, err := runtime.HealthCheck(name)
|
status, err := runtime.HealthCheck(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if status == libpod.HealthCheckContainerNotFound {
|
if status == define.HealthCheckContainerNotFound {
|
||||||
utils.ContainerNotFound(w, name, err)
|
utils.ContainerNotFound(w, name, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status == libpod.HealthCheckNotDefined {
|
if status == define.HealthCheckNotDefined {
|
||||||
utils.Error(w, "no healthcheck defined", http.StatusConflict, err)
|
utils.Error(w, "no healthcheck defined", http.StatusConflict, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status == libpod.HealthCheckContainerStopped {
|
if status == define.HealthCheckContainerStopped {
|
||||||
utils.Error(w, "container not running", http.StatusConflict, err)
|
utils.Error(w, "container not running", http.StatusConflict, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctr, err := runtime.LookupContainer(name)
|
hcStatus := define.HealthCheckUnhealthy
|
||||||
if err != nil {
|
if status == define.HealthCheckSuccess {
|
||||||
utils.InternalServerError(w, err)
|
hcStatus = define.HealthCheckHealthy
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
report := define.HealthCheckResults{
|
||||||
hcLog, err := ctr.GetHealthCheckLog()
|
Status: hcStatus,
|
||||||
if err != nil {
|
|
||||||
utils.InternalServerError(w, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
utils.WriteResponse(w, http.StatusOK, report)
|
||||||
utils.WriteResponse(w, http.StatusOK, hcLog)
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package abi
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/containers/libpod/libpod"
|
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/pkg/domain/entities"
|
"github.com/containers/libpod/pkg/domain/entities"
|
||||||
)
|
)
|
||||||
@ -13,9 +12,9 @@ func (ic *ContainerEngine) HealthCheckRun(ctx context.Context, nameOrId string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hcStatus := "unhealthy"
|
hcStatus := define.HealthCheckUnhealthy
|
||||||
if status == libpod.HealthCheckSuccess {
|
if status == define.HealthCheckSuccess {
|
||||||
hcStatus = "healthy"
|
hcStatus = define.HealthCheckHealthy
|
||||||
}
|
}
|
||||||
report := define.HealthCheckResults{
|
report := define.HealthCheckResults{
|
||||||
Status: hcStatus,
|
Status: hcStatus,
|
||||||
|
@ -901,12 +901,12 @@ func (i *VarlinkAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.Exec
|
|||||||
// HealthCheckRun executes defined container's healthcheck command and returns the container's health status.
|
// HealthCheckRun executes defined container's healthcheck command and returns the container's health status.
|
||||||
func (i *VarlinkAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error {
|
func (i *VarlinkAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error {
|
||||||
hcStatus, err := i.Runtime.HealthCheck(nameOrID)
|
hcStatus, err := i.Runtime.HealthCheck(nameOrID)
|
||||||
if err != nil && hcStatus != libpod.HealthCheckFailure {
|
if err != nil && hcStatus != define.HealthCheckFailure {
|
||||||
return call.ReplyErrorOccurred(err.Error())
|
return call.ReplyErrorOccurred(err.Error())
|
||||||
}
|
}
|
||||||
status := libpod.HealthCheckUnhealthy
|
status := define.HealthCheckUnhealthy
|
||||||
if hcStatus == libpod.HealthCheckSuccess {
|
if hcStatus == define.HealthCheckSuccess {
|
||||||
status = libpod.HealthCheckHealthy
|
status = define.HealthCheckHealthy
|
||||||
}
|
}
|
||||||
return call.ReplyHealthCheckRun(status)
|
return call.ReplyHealthCheckRun(status)
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,6 @@ var _ = Describe("Podman healthcheck run", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("podman healthcheck that should fail", func() {
|
It("podman healthcheck that should fail", func() {
|
||||||
Skip(v2remotefail)
|
|
||||||
session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "docker.io/libpod/badhealthcheck:latest"})
|
session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "docker.io/libpod/badhealthcheck:latest"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
@ -122,7 +121,6 @@ var _ = Describe("Podman healthcheck run", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("podman healthcheck failed checks in start-period should not change status", func() {
|
It("podman healthcheck failed checks in start-period should not change status", func() {
|
||||||
Skip(v2remotefail)
|
|
||||||
session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-start-period", "2m", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"})
|
session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-start-period", "2m", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
@ -144,7 +142,6 @@ var _ = Describe("Podman healthcheck run", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("podman healthcheck failed checks must reach retries before unhealthy ", func() {
|
It("podman healthcheck failed checks must reach retries before unhealthy ", func() {
|
||||||
Skip(v2remotefail)
|
|
||||||
session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"})
|
session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Reference in New Issue
Block a user