Skip to content

Commit

Permalink
Add --health-max-log-count flag
Browse files Browse the repository at this point in the history
Signed-off-by: Jan Rodák <[email protected]>
  • Loading branch information
Honny1 committed Sep 10, 2024
1 parent f22f4cf commit 331ce42
Show file tree
Hide file tree
Showing 18 changed files with 192 additions and 4 deletions.
8 changes: 8 additions & 0 deletions cmd/podman/common/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(healthIntervalFlagName, completion.AutocompleteNone)

healthMaxLogCountFlagName := "health-max-log-count"
createFlags.UintVar(
&cf.HealthMaxLogCount,
healthMaxLogCountFlagName, define.DefaultHealthMaxLogCount,
"set the maximum number of attempts we keep in the healthcheck history file. ('0' value means no limit for stored logs)",
)
_ = cmd.RegisterFlagCompletionFunc(healthMaxLogCountFlagName, completion.AutocompleteNone)

healthRetriesFlagName := "health-retries"
createFlags.UintVar(
&cf.HealthRetries,
Expand Down
7 changes: 7 additions & 0 deletions docs/source/markdown/options/health-max-log-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
####> This option file is used in:
####> podman create, run
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-max-log-count**=*number of stored logs*

Set the maximum number of attempts we keep in the healthcheck history file. ('0' value means no limit for stored logs)
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-create.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ See [**Environment**](#environment) note below for precedence and examples.

@@option health-interval

@@option health-max-log-count

@@option health-on-failure

@@option health-retries
Expand Down
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-run.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ See [**Environment**](#environment) note below for precedence and examples.

@@option health-interval

@@option health-max-log-count

@@option health-on-failure

@@option health-retries
Expand Down
6 changes: 6 additions & 0 deletions docs/source/markdown/podman-systemd.unit.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ Valid options for `[Container]` are listed below:
| GroupAdd=keep-groups | --group-add=keep-groups |
| HealthCmd=/usr/bin/command | --health-cmd=/usr/bin/command |
| HealthInterval=2m | --health-interval=2m |
| HealthMaxLogCount=5 | --health-max-log-count=5 |
| HealthOnFailure=kill | --health-on-failure=kill |
| HealthRetries=5 | --health-retries=5 |
| HealthStartPeriod=1m | --health-start-period=period=1m |
Expand Down Expand Up @@ -515,6 +516,11 @@ Equivalent to the Podman `--health-cmd` option.
Set an interval for the healthchecks. An interval of disable results in no automatic timer setup.
Equivalent to the Podman `--health-interval` option.

### `HealthMaxLogCount=`

Set the maximum number of attempts we keep in the healthcheck history file. ('0' value means no limit for stored logs)
Equivalent to the Podman `--Health-max-log-count` option.

### `HealthOnFailure=`

Action to take once the container transitions to an unhealthy state.
Expand Down
3 changes: 3 additions & 0 deletions libpod/container_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ type ContainerMiscConfig struct {
HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"`
// HealthCheckOnFailureAction defines an action to take once the container turns unhealthy.
HealthCheckOnFailureAction define.HealthCheckOnFailureAction `json:"healthcheck_on_failure_action"`
// HealthMaxLogCount is the maximum number of attempts we keep
// in the healthcheck history file ("0" value means no limit)
HealthMaxLogCount uint `json:"healthMaxLogCount"`
// StartupHealthCheckConfig is the configuration of the startup
// healthcheck for the container. This will run before the regular HC
// runs, and when it passes the regular HC will be activated.
Expand Down
2 changes: 2 additions & 0 deletions libpod/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,8 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp

ctrConfig.HealthcheckOnFailureAction = c.config.HealthCheckOnFailureAction.String()

ctrConfig.HealthMaxLogCount = c.config.HealthMaxLogCount

ctrConfig.CreateCommand = c.config.CreateCommand

ctrConfig.Timezone = c.config.Timezone
Expand Down
3 changes: 3 additions & 0 deletions libpod/define/container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ type InspectContainerConfig struct {
Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"`
// HealthcheckOnFailureAction defines an action to take once the container turns unhealthy.
HealthcheckOnFailureAction string `json:"HealthcheckOnFailureAction,omitempty"`
// HealthMaxLogCount is the maximum number of attempts we keep
// in the healthcheck history file ("0" value means no limit)
HealthMaxLogCount uint `json:"HealthcheckMaxLogCount,omitempty"`
// CreateCommand is the full command plus arguments of the process the
// container has been created with.
CreateCommand []string `json:"CreateCommand,omitempty"`
Expand Down
2 changes: 2 additions & 0 deletions libpod/define/healthchecks.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ const (
DefaultHealthCheckStartPeriod = "0s"
// DefaultHealthCheckTimeout default value
DefaultHealthCheckTimeout = "30s"
// DefaultHealthMaxLogCount default value
DefaultHealthMaxLogCount uint = 5
)

// HealthConfig.Test options
Expand Down
5 changes: 1 addition & 4 deletions libpod/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import (
)

const (
// MaxHealthCheckNumberLogs is the maximum number of attempts we keep
// in the healthcheck history file
MaxHealthCheckNumberLogs int = 5
// MaxHealthCheckLogLength in characters
MaxHealthCheckLogLength = 500
)
Expand Down Expand Up @@ -398,7 +395,7 @@ func (c *Container) updateHealthCheckLog(hcl define.HealthCheckLog, inStartPerio
}
}
healthCheck.Log = append(healthCheck.Log, hcl)
if len(healthCheck.Log) > MaxHealthCheckNumberLogs {
if c.config.HealthMaxLogCount != 0 && len(healthCheck.Log) > int(c.config.HealthMaxLogCount) {
healthCheck.Log = healthCheck.Log[1:]
}
newResults, err := json.Marshal(healthCheck)
Expand Down
11 changes: 11 additions & 0 deletions libpod/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -1500,6 +1500,17 @@ func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption
}
}

// WithHealthCheckMaxLogCount adds the healthCheckMaxLogCount to the container config
func WithHealthCheckMaxLogCount(maxLogCount uint) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
}
ctr.config.HealthMaxLogCount = maxLogCount
return nil
}
}

// WithHealthCheckOnFailureAction adds an on-failure action to health-check config
func WithHealthCheckOnFailureAction(action define.HealthCheckOnFailureAction) CtrCreateOption {
return func(ctr *Container) error {
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ type ContainerCreateOptions struct {
HealthCmd string
HealthInterval string
HealthRetries uint
HealthMaxLogCount uint
HealthStartPeriod string
HealthTimeout string
HealthOnFailure string
Expand Down
4 changes: 4 additions & 0 deletions pkg/specgen/generate/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l
options = append(options, libpod.WithHealthCheckOnFailureAction(s.ContainerHealthCheckConfig.HealthCheckOnFailureAction))
}

if healthCheckSet {
options = append(options, libpod.WithHealthCheckMaxLogCount(s.ContainerHealthCheckConfig.HealthMaxLogCount))
}

if s.SdNotifyMode == define.SdNotifyModeHealthy && !healthCheckSet {
return nil, fmt.Errorf("%w: sdnotify policy %q requires a healthcheck to be set", define.ErrInvalidArg, s.SdNotifyMode)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/specgen/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,9 @@ type ContainerHealthCheckConfig struct {
// Requires that HealthConfig be set.
// Optional.
StartupHealthConfig *define.StartupHealthCheck `json:"startupHealthConfig,omitempty"`
// HealthMaxLogCount is the maximum number of attempts we keep
// in the healthcheck history file ("0" value means no limit)
HealthMaxLogCount uint `json:"healthMaxLogCount,omitempty"`
}

// SpecGenerator creates an OCI spec and Libpod configuration options to create
Expand Down
2 changes: 2 additions & 0 deletions pkg/specgenutil/specgen.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
}
s.HealthCheckOnFailureAction = onFailureAction

s.HealthMaxLogCount = c.HealthMaxLogCount

if c.StartupHCCmd != "" {
if c.NoHealthCheck {
return errors.New("cannot specify both --no-healthcheck and --health-startup-cmd")
Expand Down
3 changes: 3 additions & 0 deletions pkg/systemd/quadlet/quadlet.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const (
KeyGroupAdd = "GroupAdd"
KeyHealthCmd = "HealthCmd"
KeyHealthInterval = "HealthInterval"
KeyHealthMaxLogCount = "HealthMaxLogCount"
KeyHealthOnFailure = "HealthOnFailure"
KeyHealthRetries = "HealthRetries"
KeyHealthStartPeriod = "HealthStartPeriod"
Expand Down Expand Up @@ -214,6 +215,7 @@ var (
KeyHealthCmd: true,
KeyHealthInterval: true,
KeyHealthOnFailure: true,
KeyHealthMaxLogCount: true,
KeyHealthRetries: true,
KeyHealthStartPeriod: true,
KeyHealthStartupCmd: true,
Expand Down Expand Up @@ -2065,6 +2067,7 @@ func handleHealth(unitFile *parser.UnitFile, groupName string, podman *PodmanCmd
{KeyHealthCmd, "cmd"},
{KeyHealthInterval, "interval"},
{KeyHealthOnFailure, "on-failure"},
{KeyHealthMaxLogCount, "max-log-count"},
{KeyHealthRetries, "retries"},
{KeyHealthStartPeriod, "start-period"},
{KeyHealthTimeout, "timeout"},
Expand Down
52 changes: 52 additions & 0 deletions test/e2e/healthcheck_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,4 +390,56 @@ HEALTHCHECK CMD ls -l / 2>&1`, ALPINE)
Expect(ps.OutputToStringArray()).To(HaveLen(2))
Expect(ps.OutputToString()).To(ContainSubstring("hc"))
})

It("Healthcheck with max default value (5) of last execution log", func() {
countOfExecutions := 10
ctrName := "hc"
ctrRun := podmanTest.Podman([]string{"run", "-dt", "--name", ctrName, "--health-cmd", "echo hello", ALPINE, "top"})
ctrRun.WaitWithDefaultTimeout()
Expect(ctrRun).Should(ExitCleanly())

for i := 0; i < countOfExecutions; i++ {
hc := podmanTest.Podman([]string{"healthcheck", "run", ctrName})
hc.WaitWithDefaultTimeout()
Expect(hc).Should(ExitCleanly())
}

inspect := podmanTest.InspectContainer(ctrName)
Expect(inspect[0].State.Health.Log).To(HaveLen(5))
})

It("Healthcheck with max infinite value (0) of last execution log", func() {
countOfExecutions := 12
ctrName := "hc"
ctrRun := podmanTest.Podman([]string{"run", "-dt", "--name", ctrName, "--health-cmd", "echo hello", "--health-max-log-count", "0", ALPINE, "top"})
ctrRun.WaitWithDefaultTimeout()
Expect(ctrRun).Should(ExitCleanly())

for i := 0; i < countOfExecutions; i++ {
hc := podmanTest.Podman([]string{"healthcheck", "run", ctrName})
hc.WaitWithDefaultTimeout()
Expect(hc).Should(ExitCleanly())
}

inspect := podmanTest.InspectContainer(ctrName)
Expect(inspect[0].State.Health.Log).To(HaveLen(countOfExecutions))
})

It("Healthcheck with max 10 last execution log", func() {
countOfExecutions := 10
ctrName := "hc"
ctrRun := podmanTest.Podman([]string{"run", "-dt", "--name", ctrName, "--health-cmd", "echo hello", "--health-max-log-count", strconv.Itoa(countOfExecutions), ALPINE, "top"})
ctrRun.WaitWithDefaultTimeout()
Expect(ctrRun).Should(ExitCleanly())

for i := 0; i < countOfExecutions; i++ {
hc := podmanTest.Podman([]string{"healthcheck", "run", ctrName})
hc.WaitWithDefaultTimeout()
Expect(hc).Should(ExitCleanly())
}

inspect := podmanTest.InspectContainer(ctrName)
Expect(inspect[0].State.Health.Log).To(HaveLen(countOfExecutions))
})

})
80 changes: 80 additions & 0 deletions test/system/220-healthcheck.bats
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,84 @@ Log[-1].Output | \"Uh-oh on stdout!\\\nUh-oh on stderr!\\\n\"
done
}

@test "podman healthcheck --health-max-log-count default value (5)" {
local repeat_count=10
local msg="Hello, How are you?"
local ctrname="c-h-$(safename)"
run_podman run -d --name $ctrname \
--health-cmd "echo $msg" \
$IMAGE /home/podman/pause
cid="$output"

run_podman inspect $ctrname --format "{{.Config.HealthMaxLogCount}}"
is "$output" "5" "HealthMaxLogCount is set to 5"

for _ in $(seq 1 $repeat_count);
do
run_podman healthcheck run $ctrname
is "$output" "" "output from 'podman healthcheck run'"
done


run_podman inspect $ctrname --format "{{.State.Health.Log}}"
[ $(echo "$output" | grep -o "$msg" - | wc -l) -eq 5 ]

run_podman rm -t 0 -f $ctrname
}

@test "podman healthcheck --health-max-log-count infinite value (0)" {
local repeat_count=10
local msg="Hello, How are you?"
local ctrname="c-h-$(safename)"
run_podman run -d --name $ctrname \
--health-cmd "echo $msg" \
--health-max-log-count 0 \
$IMAGE /home/podman/pause
cid="$output"

run_podman inspect $ctrname --format "{{.Config.HealthMaxLogCount}}"
is "$output" "0" "HealthMaxLogCount is set to 0"

# This is run 11 times to check that the cap is working.
for _ in $(seq 0 $repeat_count);
do
run_podman healthcheck run $ctrname
is "$output" "" "output from 'podman healthcheck run'"
done


run_podman inspect $ctrname --format "{{.State.Health.Log}}"
[ $(echo "$output" | grep -o "$msg" - | wc -l) -eq 11 ]

run_podman rm -t 0 -f $ctrname
}


@test "podman healthcheck --health-max-log-count 10" {
local repeat_count=10
local msg="Hello, How are you?"
local ctrname="c-h-$(safename)"
run_podman run -d --name $ctrname \
--health-cmd "echo $msg" \
--health-max-log-count $repeat_count\
$IMAGE /home/podman/pause
cid="$output"

run_podman inspect $ctrname --format "{{.Config.HealthMaxLogCount}}"
is "$output" "10" "HealthMaxLogCount is set to 10"

# This is run 11 times to check that the cap is working.
for _ in $(seq 0 $repeat_count);
do
run_podman healthcheck run $ctrname
is "$output" "" "output from 'podman healthcheck run'"
done


run_podman inspect $ctrname --format "{{.State.Health.Log}}"
[ $(echo "$output" | grep -o "$msg" - | wc -l) -eq $repeat_count ]

run_podman rm -t 0 -f $ctrname
}

# vim: filetype=sh

0 comments on commit 331ce42

Please sign in to comment.