diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 72a47db619..ae18cee3d0 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -168,78 +168,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(groupAddFlagName, completion.AutocompleteNone) - healthCmdFlagName := "health-cmd" - createFlags.StringVar( - &cf.HealthCmd, - healthCmdFlagName, "", - "set a healthcheck command for the container ('none' disables the existing healthcheck)", - ) - _ = cmd.RegisterFlagCompletionFunc(healthCmdFlagName, completion.AutocompleteNone) - - healthIntervalFlagName := "health-interval" - createFlags.StringVar( - &cf.HealthInterval, - healthIntervalFlagName, define.DefaultHealthCheckInterval, - "set an interval for the healthcheck (a value of disable results in no automatic timer setup)", - ) - _ = cmd.RegisterFlagCompletionFunc(healthIntervalFlagName, completion.AutocompleteNone) - - healthLogDestinationFlagName := "health-log-destination" - createFlags.StringVar( - &cf.HealthLogDestination, - healthLogDestinationFlagName, define.DefaultHealthCheckLocalDestination, - "set the destination of the HealthCheck log. Directory path, local or events_logger (local use container state file)", - ) - _ = cmd.RegisterFlagCompletionFunc(healthLogDestinationFlagName, completion.AutocompleteNone) - - healthMaxLogCountFlagName := "health-max-log-count" - createFlags.UintVar( - &cf.HealthMaxLogCount, - healthMaxLogCountFlagName, define.DefaultHealthMaxLogCount, - "set maximum number of attempts in the HealthCheck log file. ('0' value means an infinite number of attempts in the log file)", - ) - _ = cmd.RegisterFlagCompletionFunc(healthMaxLogCountFlagName, completion.AutocompleteNone) - - healthMaxLogSizeFlagName := "health-max-log-size" - createFlags.UintVar( - &cf.HealthMaxLogSize, - healthMaxLogSizeFlagName, define.DefaultHealthMaxLogSize, - "set maximum length in characters of stored HealthCheck log. ('0' value means an infinite log length)", - ) - _ = cmd.RegisterFlagCompletionFunc(healthMaxLogSizeFlagName, completion.AutocompleteNone) - - healthRetriesFlagName := "health-retries" - createFlags.UintVar( - &cf.HealthRetries, - healthRetriesFlagName, define.DefaultHealthCheckRetries, - "the number of retries allowed before a healthcheck is considered to be unhealthy", - ) - _ = cmd.RegisterFlagCompletionFunc(healthRetriesFlagName, completion.AutocompleteNone) - - healthStartPeriodFlagName := "health-start-period" - createFlags.StringVar( - &cf.HealthStartPeriod, - healthStartPeriodFlagName, define.DefaultHealthCheckStartPeriod, - "the initialization time needed for a container to bootstrap", - ) - _ = cmd.RegisterFlagCompletionFunc(healthStartPeriodFlagName, completion.AutocompleteNone) - - healthTimeoutFlagName := "health-timeout" - createFlags.StringVar( - &cf.HealthTimeout, - healthTimeoutFlagName, define.DefaultHealthCheckTimeout, - "the maximum time allowed to complete the healthcheck before an interval is considered failed", - ) - _ = cmd.RegisterFlagCompletionFunc(healthTimeoutFlagName, completion.AutocompleteNone) - - healthOnFailureFlagName := "health-on-failure" - createFlags.StringVar( - &cf.HealthOnFailure, - healthOnFailureFlagName, "none", - "action to take once the container turns unhealthy", - ) - _ = cmd.RegisterFlagCompletionFunc(healthOnFailureFlagName, AutocompleteHealthOnFailure) - createFlags.BoolVar( &cf.HTTPProxy, "http-proxy", podmanConfig.ContainersConfDefaultsRO.Containers.HTTPProxy, @@ -311,11 +239,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(logOptFlagName, AutocompleteLogOpt) - createFlags.BoolVar( - &cf.NoHealthCheck, - "no-healthcheck", false, - "Disable healthchecks on container", - ) createFlags.BoolVar( &cf.OOMKillDisable, "oom-kill-disable", false, @@ -452,46 +375,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(secretFlagName, AutocompleteSecrets) - startupHCCmdFlagName := "health-startup-cmd" - createFlags.StringVar( - &cf.StartupHCCmd, - startupHCCmdFlagName, "", - "Set a startup healthcheck command for the container", - ) - _ = cmd.RegisterFlagCompletionFunc(startupHCCmdFlagName, completion.AutocompleteNone) - - startupHCIntervalFlagName := "health-startup-interval" - createFlags.StringVar( - &cf.StartupHCInterval, - startupHCIntervalFlagName, define.DefaultHealthCheckInterval, - "Set an interval for the startup healthcheck", - ) - _ = cmd.RegisterFlagCompletionFunc(startupHCIntervalFlagName, completion.AutocompleteNone) - - startupHCRetriesFlagName := "health-startup-retries" - createFlags.UintVar( - &cf.StartupHCRetries, - startupHCRetriesFlagName, 0, - "Set the maximum number of retries before the startup healthcheck will restart the container", - ) - _ = cmd.RegisterFlagCompletionFunc(startupHCRetriesFlagName, completion.AutocompleteNone) - - startupHCSuccessesFlagName := "health-startup-success" - createFlags.UintVar( - &cf.StartupHCSuccesses, - startupHCSuccessesFlagName, 0, - "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)", - ) - _ = cmd.RegisterFlagCompletionFunc(startupHCSuccessesFlagName, completion.AutocompleteNone) - - startupHCTimeoutFlagName := "health-startup-timeout" - createFlags.StringVar( - &cf.StartupHCTimeout, - startupHCTimeoutFlagName, define.DefaultHealthCheckTimeout, - "Set the maximum amount of time that the startup healthcheck may take before it is considered failed", - ) - _ = cmd.RegisterFlagCompletionFunc(startupHCTimeoutFlagName, completion.AutocompleteNone) - stopSignalFlagName := "stop-signal" createFlags.StringVar( &cf.StopSignal, @@ -665,6 +548,140 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, `If a container with the same name exists, replace it`, ) } + if mode == entities.CreateMode || mode == entities.UpdateMode { + createFlags.BoolVar( + &cf.NoHealthCheck, + "no-healthcheck", false, + "Disable healthchecks on container", + ) + + healthCmdFlagName := "health-cmd" + createFlags.StringVar( + &cf.HealthCmd, + healthCmdFlagName, "", + "set a healthcheck command for the container ('none' disables the existing healthcheck)", + ) + _ = cmd.RegisterFlagCompletionFunc(healthCmdFlagName, completion.AutocompleteNone) + + info := "" + if mode == entities.UpdateMode { + info = "Changing this setting resets timer." + } + healthIntervalFlagName := "health-interval" + createFlags.StringVar( + &cf.HealthInterval, + healthIntervalFlagName, define.DefaultHealthCheckInterval, + "set an interval for the healthcheck. (a value of disable results in no automatic timer setup) "+info, + ) + _ = cmd.RegisterFlagCompletionFunc(healthIntervalFlagName, completion.AutocompleteNone) + + warning := "" + if mode == entities.UpdateMode { + warning = "Warning: Changing this setting may cause the loss of previous logs!" + } + healthLogDestinationFlagName := "health-log-destination" + createFlags.StringVar( + &cf.HealthLogDestination, + healthLogDestinationFlagName, define.DefaultHealthCheckLocalDestination, + "set the destination of the HealthCheck log. Directory path, local or events_logger (local use container state file) "+warning, + ) + _ = cmd.RegisterFlagCompletionFunc(healthLogDestinationFlagName, completion.AutocompleteNone) + + healthMaxLogCountFlagName := "health-max-log-count" + createFlags.UintVar( + &cf.HealthMaxLogCount, + healthMaxLogCountFlagName, define.DefaultHealthMaxLogCount, + "set maximum number of attempts in the HealthCheck log file. ('0' value means an infinite number of attempts in the log file)", + ) + _ = cmd.RegisterFlagCompletionFunc(healthMaxLogCountFlagName, completion.AutocompleteNone) + + healthMaxLogSizeFlagName := "health-max-log-size" + createFlags.UintVar( + &cf.HealthMaxLogSize, + healthMaxLogSizeFlagName, define.DefaultHealthMaxLogSize, + "set maximum length in characters of stored HealthCheck log. ('0' value means an infinite log length)", + ) + _ = cmd.RegisterFlagCompletionFunc(healthMaxLogSizeFlagName, completion.AutocompleteNone) + + healthRetriesFlagName := "health-retries" + createFlags.UintVar( + &cf.HealthRetries, + healthRetriesFlagName, define.DefaultHealthCheckRetries, + "the number of retries allowed before a healthcheck is considered to be unhealthy", + ) + _ = cmd.RegisterFlagCompletionFunc(healthRetriesFlagName, completion.AutocompleteNone) + + healthStartPeriodFlagName := "health-start-period" + createFlags.StringVar( + &cf.HealthStartPeriod, + healthStartPeriodFlagName, define.DefaultHealthCheckStartPeriod, + "the initialization time needed for a container to bootstrap", + ) + _ = cmd.RegisterFlagCompletionFunc(healthStartPeriodFlagName, completion.AutocompleteNone) + + healthTimeoutFlagName := "health-timeout" + createFlags.StringVar( + &cf.HealthTimeout, + healthTimeoutFlagName, define.DefaultHealthCheckTimeout, + "the maximum time allowed to complete the healthcheck before an interval is considered failed", + ) + _ = cmd.RegisterFlagCompletionFunc(healthTimeoutFlagName, completion.AutocompleteNone) + + healthOnFailureFlagName := "health-on-failure" + createFlags.StringVar( + &cf.HealthOnFailure, + healthOnFailureFlagName, "none", + "action to take once the container turns unhealthy", + ) + _ = cmd.RegisterFlagCompletionFunc(healthOnFailureFlagName, AutocompleteHealthOnFailure) + + // Startup HealthCheck + + startupHCCmdFlagName := "health-startup-cmd" + createFlags.StringVar( + &cf.StartupHCCmd, + startupHCCmdFlagName, "", + "Set a startup healthcheck command for the container", + ) + _ = cmd.RegisterFlagCompletionFunc(startupHCCmdFlagName, completion.AutocompleteNone) + + info = "" + if mode == entities.UpdateMode { + info = "Changing this setting resets the timer, depending on the state of the container." + } + startupHCIntervalFlagName := "health-startup-interval" + createFlags.StringVar( + &cf.StartupHCInterval, + startupHCIntervalFlagName, define.DefaultHealthCheckInterval, + "Set an interval for the startup healthcheck. "+info, + ) + _ = cmd.RegisterFlagCompletionFunc(startupHCIntervalFlagName, completion.AutocompleteNone) + + startupHCRetriesFlagName := "health-startup-retries" + createFlags.UintVar( + &cf.StartupHCRetries, + startupHCRetriesFlagName, 0, + "Set the maximum number of retries before the startup healthcheck will restart the container", + ) + _ = cmd.RegisterFlagCompletionFunc(startupHCRetriesFlagName, completion.AutocompleteNone) + + startupHCSuccessesFlagName := "health-startup-success" + createFlags.UintVar( + &cf.StartupHCSuccesses, + startupHCSuccessesFlagName, 0, + "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)", + ) + _ = cmd.RegisterFlagCompletionFunc(startupHCSuccessesFlagName, completion.AutocompleteNone) + + startupHCTimeoutFlagName := "health-startup-timeout" + createFlags.StringVar( + &cf.StartupHCTimeout, + startupHCTimeoutFlagName, define.DefaultHealthCheckTimeout, + "Set the maximum amount of time that the startup healthcheck may take before it is considered failed", + ) + _ = cmd.RegisterFlagCompletionFunc(startupHCTimeoutFlagName, completion.AutocompleteNone) + } + // Restart is allowed for created, updated, and infra ctr if mode == entities.InfraMode || mode == entities.CreateMode || mode == entities.UpdateMode { restartFlagName := "restart" diff --git a/cmd/podman/containers/update.go b/cmd/podman/containers/update.go index 9e8e28070a..673aa91acd 100644 --- a/cmd/podman/containers/update.go +++ b/cmd/podman/containers/update.go @@ -17,7 +17,7 @@ import ( ) var ( - updateDescription = `Updates the cgroup configuration of a given container` + updateDescription = `Updates the configuration of an existing container, allowing changes to resource limits and healthchecks` updateCommand = &cobra.Command{ Use: "update [options] CONTAINER", @@ -89,9 +89,15 @@ func update(cmd *cobra.Command, args []string) error { return err } + healthCheckConfig := specgenutil.GetChangedHealthCheckConfiguration(cmd) + if err != nil { + return err + } + opts := &entities.ContainerUpdateOptions{ - NameOrID: strings.TrimPrefix(args[0], "/"), - Specgen: s, + NameOrID: strings.TrimPrefix(args[0], "/"), + Specgen: s, + ChangedHealthCheckConfiguration: &healthCheckConfig, } rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts) if err != nil { diff --git a/docs/source/markdown/options/health-cmd.md b/docs/source/markdown/options/health-cmd.md index a135a2c435..4e09430849 100644 --- a/docs/source/markdown/options/health-cmd.md +++ b/docs/source/markdown/options/health-cmd.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-cmd**=*"command"* | *'["command", "arg1", ...]'* diff --git a/docs/source/markdown/options/health-interval.md b/docs/source/markdown/options/health-interval.md index 9aa86dcd76..0b25517cfa 100644 --- a/docs/source/markdown/options/health-interval.md +++ b/docs/source/markdown/options/health-interval.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-interval**=*interval* diff --git a/docs/source/markdown/options/health-log-destination.md b/docs/source/markdown/options/health-log-destination.md index 16b99ecc4c..91e91e269c 100644 --- a/docs/source/markdown/options/health-log-destination.md +++ b/docs/source/markdown/options/health-log-destination.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-log-destination**=*directory_path* diff --git a/docs/source/markdown/options/health-max-log-count.md b/docs/source/markdown/options/health-max-log-count.md index 96a7d60861..137d470830 100644 --- a/docs/source/markdown/options/health-max-log-count.md +++ b/docs/source/markdown/options/health-max-log-count.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-max-log-count**=*number of stored logs* diff --git a/docs/source/markdown/options/health-max-log-size.md b/docs/source/markdown/options/health-max-log-size.md index 96cc399e4a..1c3169c7f4 100644 --- a/docs/source/markdown/options/health-max-log-size.md +++ b/docs/source/markdown/options/health-max-log-size.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-max-log-size**=*size of stored logs* diff --git a/docs/source/markdown/options/health-on-failure.md b/docs/source/markdown/options/health-on-failure.md index 4075556a2d..6c539e7332 100644 --- a/docs/source/markdown/options/health-on-failure.md +++ b/docs/source/markdown/options/health-on-failure.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-on-failure**=*action* diff --git a/docs/source/markdown/options/health-retries.md b/docs/source/markdown/options/health-retries.md index 224bd0d552..13d68afb95 100644 --- a/docs/source/markdown/options/health-retries.md +++ b/docs/source/markdown/options/health-retries.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-retries**=*retries* diff --git a/docs/source/markdown/options/health-start-period.md b/docs/source/markdown/options/health-start-period.md index 5b1fde4bd5..4391338e46 100644 --- a/docs/source/markdown/options/health-start-period.md +++ b/docs/source/markdown/options/health-start-period.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-start-period**=*period* diff --git a/docs/source/markdown/options/health-startup-cmd.md b/docs/source/markdown/options/health-startup-cmd.md index b3792a584f..67b2db32ad 100644 --- a/docs/source/markdown/options/health-startup-cmd.md +++ b/docs/source/markdown/options/health-startup-cmd.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-startup-cmd**=*"command"* | *'["command", "arg1", ...]'* diff --git a/docs/source/markdown/options/health-startup-interval.md b/docs/source/markdown/options/health-startup-interval.md index dbba969a55..052d703a78 100644 --- a/docs/source/markdown/options/health-startup-interval.md +++ b/docs/source/markdown/options/health-startup-interval.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-startup-interval**=*interval* diff --git a/docs/source/markdown/options/health-startup-retries.md b/docs/source/markdown/options/health-startup-retries.md index db213dcf97..2a0f9fdf90 100644 --- a/docs/source/markdown/options/health-startup-retries.md +++ b/docs/source/markdown/options/health-startup-retries.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-startup-retries**=*retries* diff --git a/docs/source/markdown/options/health-startup-success.md b/docs/source/markdown/options/health-startup-success.md index c8c85e1bfb..e1f911b215 100644 --- a/docs/source/markdown/options/health-startup-success.md +++ b/docs/source/markdown/options/health-startup-success.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-startup-success**=*retries* diff --git a/docs/source/markdown/options/health-startup-timeout.md b/docs/source/markdown/options/health-startup-timeout.md index f6b8c75e07..deffa14025 100644 --- a/docs/source/markdown/options/health-startup-timeout.md +++ b/docs/source/markdown/options/health-startup-timeout.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-startup-timeout**=*timeout* diff --git a/docs/source/markdown/options/health-timeout.md b/docs/source/markdown/options/health-timeout.md index 1324628008..0540a48db3 100644 --- a/docs/source/markdown/options/health-timeout.md +++ b/docs/source/markdown/options/health-timeout.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--health-timeout**=*timeout* diff --git a/docs/source/markdown/options/no-healthcheck.md b/docs/source/markdown/options/no-healthcheck.md index 14704db8aa..9fab16a563 100644 --- a/docs/source/markdown/options/no-healthcheck.md +++ b/docs/source/markdown/options/no-healthcheck.md @@ -1,5 +1,5 @@ ####> This option file is used in: -####> podman create, run +####> podman create, run, update ####> If file is edited, make sure the changes ####> are applicable to all of those. #### **--no-healthcheck** diff --git a/docs/source/markdown/podman-update.1.md.in b/docs/source/markdown/podman-update.1.md.in index 9cce804aa9..3a010b028c 100644 --- a/docs/source/markdown/podman-update.1.md.in +++ b/docs/source/markdown/podman-update.1.md.in @@ -10,8 +10,7 @@ podman\-update - Update the configuration of a given container ## DESCRIPTION -Updates the configuration of an already existing container, allowing different resource limits to be set. -The currently supported options are a subset of the podman create/run resource limit options. +Updates the configuration of an existing container, allowing changes to resource limits and healthchecks. ## OPTIONS @@ -43,6 +42,40 @@ The currently supported options are a subset of the podman create/run resource l @@option device-write-iops +@@option health-cmd + +@@option health-interval + +Changing this setting resets timer. + +@@option health-log-destination + +Warning: Changing this setting may cause the loss of previous logs. + +@@option health-max-log-count + +@@option health-max-log-size + +@@option health-on-failure + +@@option health-retries + +@@option health-start-period + +@@option health-startup-cmd + +@@option health-startup-interval + +Changing this setting resets the timer, depending on the state of the container. + +@@option health-startup-retries + +@@option health-startup-success + +@@option health-startup-timeout + +@@option health-timeout + @@option memory @@option memory-reservation @@ -51,6 +84,8 @@ The currently supported options are a subset of the podman create/run resource l @@option memory-swappiness +@@option no-healthcheck + @@option pids-limit @@option restart diff --git a/libpod/container_api.go b/libpod/container_api.go index cae6817ec3..e2edd8210c 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -12,6 +12,7 @@ import ( "time" "github.com/containers/common/pkg/resize" + "github.com/containers/image/v5/manifest" "github.com/containers/podman/v5/libpod/define" "github.com/containers/podman/v5/libpod/events" "github.com/containers/storage/pkg/archive" @@ -136,6 +137,61 @@ func (c *Container) Update(resources *spec.LinuxResources, restartPolicy *string return c.update(resources, restartPolicy, restartRetries) } +// UpdateHealthCheckConfig updates HealthCheck configuration the given container. +func (c *Container) UpdateHealthCheckConfig(healthCheckConfig *manifest.Schema2HealthConfig, changedTimer bool, noHealthCheck bool) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.ensureState(define.ContainerStateRemoving) { + return fmt.Errorf("container %s is being removed, cannot update: %w", c.ID(), define.ErrCtrStateInvalid) + } + + return c.updateHealthCheckConfiguration(healthCheckConfig, changedTimer, noHealthCheck) +} + +// UpdateStartupHealthCheckConfig updates startup HealthCheck configuration the given container. +func (c *Container) UpdateStartupHealthCheckConfig(startupHealthCheckConfig *define.StartupHealthCheck, changedStartupTimer bool, noHealthCheck bool) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.ensureState(define.ContainerStateRemoving) { + return fmt.Errorf("container %s is being removed, cannot update: %w", c.ID(), define.ErrCtrStateInvalid) + } + + return c.updateStartupHealthCheckConfiguration(startupHealthCheckConfig, changedStartupTimer, noHealthCheck) +} + +// UpdateGlobalHealthCheckConfig updates global HealthCheck configuration the given container. +// If value is nil then value will be not changed. +func (c *Container) UpdateGlobalHealthCheckConfig(healthLogDestination *string, healthMaxLogCount *uint, healthMaxLogSize *uint, healthCheckOnFailureAction *define.HealthCheckOnFailureAction) error { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + if c.ensureState(define.ContainerStateRemoving) { + return fmt.Errorf("container %s is being removed, cannot update: %w", c.ID(), define.ErrCtrStateInvalid) + } + + return c.updateGlobalHealthCheckConfiguration(healthLogDestination, healthMaxLogCount, healthMaxLogSize, healthCheckOnFailureAction) +} + // Attach to a container. // The last parameter "start" can be used to also start the container. // This will then Start and Attach APIs, ensuring proper diff --git a/libpod/container_internal.go b/libpod/container_internal.go index c7efd18e4b..0d692ab442 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -30,6 +30,7 @@ import ( "github.com/containers/common/pkg/hooks/exec" "github.com/containers/common/pkg/timezone" cutil "github.com/containers/common/pkg/util" + "github.com/containers/image/v5/manifest" "github.com/containers/podman/v5/libpod/define" "github.com/containers/podman/v5/libpod/events" "github.com/containers/podman/v5/libpod/shutdown" @@ -2738,3 +2739,122 @@ func (c *Container) update(resources *spec.LinuxResources, restartPolicy *string return nil } + +func (c *Container) resetHealthCheckTimers(noHealthCheck bool, changedTimer bool, isStartup bool) error { + if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning) { + return nil + } + + switch { + case noHealthCheck: + if err := c.removeTransientFiles(context.Background(), + c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed, + c.state.HCUnitName); err != nil { + return err + } + case isStartup && changedTimer && c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed: + c.state.StartupHCPassed = false + c.state.StartupHCSuccessCount = 0 + c.state.StartupHCFailureCount = 0 + if err := c.save(); err != nil { + return err + } + c.recreateHealthCheckTimer(context.Background(), true, true) + case changedTimer && c.config.HealthCheckConfig != nil: + c.recreateHealthCheckTimer(context.Background(), false, false) + } + return nil +} + +func (c *Container) updateHealthCheckConfiguration(healthCheckConfig *manifest.Schema2HealthConfig, changedTimer bool, noHealthCheck bool) error { + oldHealthCheck := &manifest.Schema2HealthConfig{} + if c.config.HealthCheckConfig != nil { + if err := JSONDeepCopy(c.config.HealthCheckConfig, oldHealthCheck); err != nil { + return err + } + } + + c.config.HealthCheckConfig = healthCheckConfig + + if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + // Assume DB write failed, revert to old resources block + c.config.HealthCheckConfig = oldHealthCheck + return err + } + + err := c.resetHealthCheckTimers(noHealthCheck, changedTimer, false) + if err != nil { + return err + } + + logrus.Debugf("HealthCheck configuration updated for container %s", c.ID()) + c.newContainerEvent(events.UpdateHealthCheckConfig) + return nil +} + +func (c *Container) updateStartupHealthCheckConfiguration(startupHealthCheckConfig *define.StartupHealthCheck, changedStartupTimer bool, noHealthCheck bool) error { + oldStartupHealthCheck := &define.StartupHealthCheck{} + if c.config.StartupHealthCheckConfig != nil { + if err := JSONDeepCopy(c.config.StartupHealthCheckConfig, oldStartupHealthCheck); err != nil { + return err + } + } + + c.config.StartupHealthCheckConfig = startupHealthCheckConfig + + if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + // Assume DB write failed, revert to old resources block + c.config.StartupHealthCheckConfig = oldStartupHealthCheck + return err + } + + err := c.resetHealthCheckTimers(noHealthCheck, changedStartupTimer, true) + if err != nil { + return err + } + + logrus.Debugf("Startup HealthCheck configuration updated for container %s", c.ID()) + c.newContainerEvent(events.UpdateHealthCheckConfig) + return nil +} + +func (c *Container) updateGlobalHealthCheckConfiguration(healthLogDestination *string, healthMaxLogCount *uint, healthMaxLogSize *uint, healthCheckOnFailureAction *define.HealthCheckOnFailureAction) error { + oldHealthCheckOnFailureAction := c.config.HealthCheckOnFailureAction + oldHealthLogDestination := c.config.HealthLogDestination + oldHealthMaxLogCount := c.config.HealthMaxLogCount + oldHealthMaxLogSize := c.config.HealthMaxLogSize + + if healthCheckOnFailureAction != nil { + c.config.HealthCheckOnFailureAction = *healthCheckOnFailureAction + } + + if healthMaxLogCount != nil { + c.config.HealthMaxLogCount = *healthMaxLogCount + } + + if healthMaxLogSize != nil { + c.config.HealthMaxLogSize = *healthMaxLogSize + } + + if healthLogDestination != nil { + c.valid = false + err := WithHealthCheckLogDestination(*healthLogDestination)(c) + if err != nil { + return err + } + c.valid = true + } + + if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { + // Assume DB write failed, revert to old resources block + c.config.HealthCheckOnFailureAction = oldHealthCheckOnFailureAction + c.config.HealthLogDestination = oldHealthLogDestination + c.config.HealthMaxLogCount = oldHealthMaxLogCount + c.config.HealthMaxLogSize = oldHealthMaxLogSize + return err + } + + logrus.Debugf("Global HealthCheck configuration updated for container %s", c.ID()) + c.newContainerEvent(events.UpdateHealthCheckConfig) + return nil +} diff --git a/libpod/define/healthchecks.go b/libpod/define/healthchecks.go index 4fec277555..6a8fe52b07 100644 --- a/libpod/define/healthchecks.go +++ b/libpod/define/healthchecks.go @@ -155,3 +155,43 @@ type StartupHealthCheck struct { // 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 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"` +} diff --git a/libpod/events/config.go b/libpod/events/config.go index 6337e4c155..a612f049b2 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -217,6 +217,8 @@ const ( Untag Status = "untag" // Update indicates that a container's configuration has been modified. Update Status = "update" + // Update indicates that a container's HealthCheck configuration has been modified. + UpdateHealthCheckConfig Status = "update-HealthCheck-config" ) // EventFilter for filtering events diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go index ada4e004d3..093f11bd65 100644 --- a/libpod/healthcheck.go +++ b/libpod/healthcheck.go @@ -258,33 +258,42 @@ func (c *Container) incrementStartupHCSuccessCounter(ctx context.Context) { } if recreateTimer { - logrus.Infof("Startup healthcheck for container %s passed, recreating timer", c.ID()) + c.recreateHealthCheckTimer(ctx, false, true) + } +} - oldUnit := c.state.HCUnitName - // Create the new, standard healthcheck timer first. - if err := c.createTimer(c.HealthCheckConfig().Interval.String(), false); err != nil { - logrus.Errorf("Error recreating container %s healthcheck: %v", c.ID(), err) - return - } - if err := c.startTimer(false); err != nil { - logrus.Errorf("Error restarting container %s healthcheck timer: %v", c.ID(), err) - } +func (c *Container) recreateHealthCheckTimer(ctx context.Context, isStartup bool, isStartupRemoved bool) { + logrus.Infof("Startup healthcheck for container %s passed, recreating timer", c.ID()) - // This kills the process the healthcheck is running. - // Which happens to be us. - // So this has to be last - after this, systemd serves us a - // SIGTERM and we exit. - // Special case, via SIGTERM we exit(1) which means systemd logs a failure in the unit. - // We do not want this as the unit will be leaked on failure states unless "reset-failed" - // is called. Fundamentally this is expected so switch it to exit 0. - // NOTE: This is only safe while being called from "podman healthcheck run" which we know - // is the case here as we should not alter the exit code of another process that just - // happened to call this. - shutdown.SetExitCode(0) - if err := c.removeTransientFiles(ctx, true, oldUnit); err != nil { - logrus.Errorf("Error removing container %s healthcheck: %v", c.ID(), err) - return - } + oldUnit := c.state.HCUnitName + // Create the new, standard healthcheck timer first. + interval := c.HealthCheckConfig().Interval.String() + if isStartup { + interval = c.config.StartupHealthCheckConfig.StartInterval.String() + } + + if err := c.createTimer(interval, isStartup); err != nil { + logrus.Errorf("Error recreating container %s (isStartup: %t) healthcheck: %v", c.ID(), isStartup, err) + return + } + if err := c.startTimer(isStartup); err != nil { + logrus.Errorf("Error restarting container %s (isStartup: %t) healthcheck timer: %v", c.ID(), isStartup, err) + } + + // This kills the process the healthcheck is running. + // Which happens to be us. + // So this has to be last - after this, systemd serves us a + // SIGTERM and we exit. + // Special case, via SIGTERM we exit(1) which means systemd logs a failure in the unit. + // We do not want this as the unit will be leaked on failure states unless "reset-failed" + // is called. Fundamentally this is expected so switch it to exit 0. + // NOTE: This is only safe while being called from "podman healthcheck run" which we know + // is the case here as we should not alter the exit code of another process that just + // happened to call this. + shutdown.SetExitCode(0) + if err := c.removeTransientFiles(ctx, isStartupRemoved, oldUnit); err != nil { + logrus.Errorf("Error removing container %s healthcheck: %v", c.ID(), err) + return } } diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 726c30b2e7..57849999fe 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -18,9 +18,9 @@ import ( api "github.com/containers/podman/v5/pkg/api/types" "github.com/containers/podman/v5/pkg/domain/entities" "github.com/containers/podman/v5/pkg/domain/infra/abi" + "github.com/containers/podman/v5/pkg/specgenutil" "github.com/containers/podman/v5/pkg/util" "github.com/gorilla/schema" - "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -443,12 +443,52 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) { return } - options := &handlers.UpdateEntities{Resources: &specs.LinuxResources{}} - if err := json.NewDecoder(r.Body).Decode(&options.Resources); err != nil { + options := &handlers.UpdateEntities{} + if err := json.NewDecoder(r.Body).Decode(&options); err != nil { utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err)) return } - err = ctr.Update(options.Resources, restartPolicy, restartRetries) + + noHealthCheck := false + if options.UpdateHealthCheckConfig.NoHealthCheck != nil { + noHealthCheck = *options.UpdateHealthCheckConfig.NoHealthCheck + } + + healthCheckConfig, changedHealthCheck, changedTimer, err := specgenutil.GetNewHealthCheckConfig(ctr.HealthCheckConfig(), options.UpdateHealthCheckConfig) + if err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("GetNewHealthCheckConfig(): %w", err)) + return + } + if changedHealthCheck { + if err := ctr.UpdateHealthCheckConfig(healthCheckConfig, changedTimer, noHealthCheck); err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("UpdateHealthCheckConfig(): %w", err)) + return + } + } + + startupHealthCheckConfig, changedStartupHealthCheck, changedStartupTimer, err := specgenutil.GetNewStartupHealthCheckConfig(ctr.Config().StartupHealthCheckConfig, options.UpdateHealthCheckConfig) + if err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("GetNewStartupHealthCheckConfig(): %w", err)) + return + } + if changedStartupHealthCheck { + if err := ctr.UpdateStartupHealthCheckConfig(startupHealthCheckConfig, changedStartupTimer, noHealthCheck); err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("UpdateStartupHealthCheckConfig(): %w", err)) + return + } + } + + healthLogDestination, healthMaxLogCount, healthMaxLogSize, healthCheckOnFailureAction, err := specgenutil.GetNewGlobalHealthCheck(options.UpdateHealthCheckConfig) + if err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("GetNewGlobalHealthCheck(): %w", err)) + return + } + if err := ctr.UpdateGlobalHealthCheckConfig(healthLogDestination, healthMaxLogCount, healthMaxLogSize, healthCheckOnFailureAction); err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("UpdateGlobalHealthCheckConfig(): %w", err)) + return + } + + err = ctr.Update(&options.LinuxResources, restartPolicy, restartRetries) // &options.UpdateHealthCheckConfig) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/swagger/models.go b/pkg/api/handlers/swagger/models.go index 56fd5d8b56..fb8471a94c 100644 --- a/pkg/api/handlers/swagger/models.go +++ b/pkg/api/handlers/swagger/models.go @@ -54,4 +54,21 @@ type networkUpdateRequestLibpod entities.NetworkUpdateOptions // Container update // swagger:model -type containerUpdateRequest container.UpdateConfig +type containerUpdateRequest struct { + container.UpdateConfig + HealthLogDestination string + HealthMaxLogSize string + HealthMaxLogCount string + HealthOnFailure string + HoHealthCheck string + HealthCmd string + HealthInterval string + HealthRetries string + HealthTimeout string + HealthStartPeriod string + HealthStartupCmd string + HealthStartupInterval string + HealthStartupRetries string + HealthStartupTimeout string + HealthStartupSuccess string +} diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 5a13530b90..d461344a35 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -1,6 +1,7 @@ package handlers import ( + "github.com/containers/podman/v5/libpod/define" "github.com/containers/podman/v5/pkg/domain/entities" docker "github.com/docker/docker/api/types" dockerBackend "github.com/docker/docker/api/types/backend" @@ -73,7 +74,8 @@ type LibpodContainersRmReport struct { // UpdateEntities used to wrap the oci resource spec in a swagger model // swagger:model type UpdateEntities struct { - Resources *specs.LinuxResources + specs.LinuxResources + define.UpdateHealthCheckConfig } type Info struct { diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index cc1cc5e74d..3adfb2a90f 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -1778,8 +1778,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // --- // tags: // - containers - // summary: Update an existing containers cgroup configuration - // description: Update an existing containers cgroup configuration. + // summary: Updates the configuration of an existing container, allowing changes to resource limits and healthchecks + // description: Updates the configuration of an existing container, allowing changes to resource limits and healthchecks. // parameters: // - in: path // name: name diff --git a/pkg/bindings/containers/update.go b/pkg/bindings/containers/update.go index 37cf74426e..8091158029 100644 --- a/pkg/bindings/containers/update.go +++ b/pkg/bindings/containers/update.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + "github.com/containers/podman/v5/pkg/api/handlers" "github.com/containers/podman/v5/pkg/bindings" "github.com/containers/podman/v5/pkg/domain/entities/types" jsoniter "github.com/json-iterator/go" @@ -25,12 +26,15 @@ func Update(ctx context.Context, options *types.ContainerUpdateOptions) (string, params.Set("restartRetries", strconv.Itoa(int(*options.Specgen.RestartRetries))) } } - - resources, err := jsoniter.MarshalToString(options.Specgen.ResourceLimits) + updateEntities := &handlers.UpdateEntities{ + LinuxResources: *options.Specgen.ResourceLimits, + UpdateHealthCheckConfig: *options.ChangedHealthCheckConfiguration, + } + requestData, err := jsoniter.MarshalToString(updateEntities) if err != nil { return "", err } - stringReader := strings.NewReader(resources) + stringReader := strings.NewReader(requestData) response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/%s/update", params, nil, options.NameOrID) if err != nil { return "", err diff --git a/pkg/domain/entities/types/containers.go b/pkg/domain/entities/types/containers.go index f9d922e229..0e1f563931 100644 --- a/pkg/domain/entities/types/containers.go +++ b/pkg/domain/entities/types/containers.go @@ -36,6 +36,7 @@ type ContainerStatsReport struct { } type ContainerUpdateOptions struct { - NameOrID string - Specgen *specgen.SpecGenerator + NameOrID string + Specgen *specgen.SpecGenerator + ChangedHealthCheckConfiguration *define.UpdateHealthCheckConfig } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 0a0dbe3357..8de34f1957 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1806,13 +1806,47 @@ func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *e if len(containers) != 1 { return "", fmt.Errorf("container not found") } + container := containers[0].Container var restartPolicy *string if updateOptions.Specgen.RestartPolicy != "" { restartPolicy = &updateOptions.Specgen.RestartPolicy } - if err = containers[0].Update(updateOptions.Specgen.ResourceLimits, restartPolicy, updateOptions.Specgen.RestartRetries); err != nil { + noHealthCheck := false + if updateOptions.ChangedHealthCheckConfiguration.NoHealthCheck != nil { + noHealthCheck = *updateOptions.ChangedHealthCheckConfiguration.NoHealthCheck + } + + healthCheckConfig, changedHealthCheck, changedTimer, err := specgenutil.GetNewHealthCheckConfig(container.HealthCheckConfig(), *updateOptions.ChangedHealthCheckConfiguration) + if err != nil { + return "", err + } + if changedHealthCheck { + if err := container.UpdateHealthCheckConfig(healthCheckConfig, changedTimer, noHealthCheck); err != nil { + return "", err + } + } + + startupHealthCheckConfig, changedStartupHealthCheck, changedStartupTimer, err := specgenutil.GetNewStartupHealthCheckConfig(container.Config().StartupHealthCheckConfig, *updateOptions.ChangedHealthCheckConfiguration) + if err != nil { + return "", err + } + if changedStartupHealthCheck { + if err := container.UpdateStartupHealthCheckConfig(startupHealthCheckConfig, changedStartupTimer, noHealthCheck); err != nil { + return "", err + } + } + + healthLogDestination, healthMaxLogCount, healthMaxLogSize, healthCheckOnFailureAction, err := specgenutil.GetNewGlobalHealthCheck(*updateOptions.ChangedHealthCheckConfiguration) + if err != nil { + return "", err + } + if err := container.UpdateGlobalHealthCheckConfig(healthLogDestination, healthMaxLogCount, healthMaxLogSize, healthCheckOnFailureAction); err != nil { + return "", err + } + + if err = container.Update(updateOptions.Specgen.ResourceLimits, restartPolicy, updateOptions.Specgen.RestartRetries); err != nil { return "", err } return containers[0].ID(), nil diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 0bc9b419a7..9627cec25f 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "strconv" "strings" "time" @@ -22,6 +23,9 @@ import ( "github.com/docker/go-units" "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/pflag" ) const ( @@ -354,7 +358,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if c.NoHealthCheck { return errors.New("cannot specify both --no-healthcheck and --health-cmd") } - s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod, false) + s.HealthConfig, err = MakeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod, false) if err != nil { return err } @@ -383,7 +387,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions // The hardcoded "1s" will be discarded, as the startup // healthcheck does not have a period. So just hardcode // something that parses correctly. - tmpHcConfig, err := makeHealthCheckFromCli(c.StartupHCCmd, c.StartupHCInterval, c.StartupHCRetries, c.StartupHCTimeout, "1s", true) + tmpHcConfig, err := MakeHealthCheckFromCli(c.StartupHCCmd, c.StartupHCInterval, c.StartupHCRetries, c.StartupHCTimeout, "1s", true) if err != nil { return err } @@ -948,7 +952,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions return nil } -func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string, isStartup bool) (*manifest.Schema2HealthConfig, error) { +func MakeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string, isStartup bool) (*manifest.Schema2HealthConfig, error) { cmdArr := []string{} isArr := true err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling @@ -1017,7 +1021,6 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") } hc.StartPeriod = startPeriodDuration - return &hc, nil } @@ -1297,3 +1300,292 @@ func GetResources(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) } return s.ResourceLimits, nil } + +func parseUint(val string) (uint, error) { + valUint, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return 0, err + } + return uint(valUint), nil +} + +func GetChangedHealthCheckConfiguration(cmd *cobra.Command) define.UpdateHealthCheckConfig { + updateHealthCheckConfig := define.UpdateHealthCheckConfig{} + cmd.Flags().Visit(func(f *pflag.Flag) { + name := strings.ToLower(f.Name) + if strings.Contains(name, "health") { + valStr := f.Value.String() + switch name { + case "health-log-destination": + updateHealthCheckConfig.HealthLogDestination = &valStr + case "health-max-log-size": + val, err := parseUint(valStr) + if err != nil { + logrus.Error(err) + } + updateHealthCheckConfig.HealthMaxLogSize = &val + case "health-max-log-count": + val, err := parseUint(valStr) + if err != nil { + logrus.Error(err) + } + updateHealthCheckConfig.HealthMaxLogCount = &val + case "health-on-failure": + updateHealthCheckConfig.HealthOnFailure = &valStr + case "no-healthcheck": + val, err := strconv.ParseBool(valStr) + if err != nil { + logrus.Error(err) + } + updateHealthCheckConfig.NoHealthCheck = &val + case "health-cmd": + updateHealthCheckConfig.HealthCmd = &valStr + case "health-interval": + updateHealthCheckConfig.HealthInterval = &valStr + case "health-retries": + val, err := parseUint(valStr) + if err != nil { + logrus.Error(err) + } + updateHealthCheckConfig.HealthRetries = &val + case "health-timeout": + updateHealthCheckConfig.HealthTimeout = &valStr + case "health-start-period": + updateHealthCheckConfig.HealthStartPeriod = &valStr + case "health-startup-cmd": + updateHealthCheckConfig.HealthStartupCmd = &valStr + case "health-startup-interval": + updateHealthCheckConfig.HealthStartupInterval = &valStr + case "health-startup-retries": + val, err := parseUint(valStr) + if err != nil { + logrus.Error(err) + } + updateHealthCheckConfig.HealthStartupRetries = &val + case "health-startup-timeout": + updateHealthCheckConfig.HealthStartupTimeout = &valStr + case "health-startup-success": + val, err := parseUint(valStr) + if err != nil { + logrus.Error(err) + } + updateHealthCheckConfig.HealthStartupSuccess = &val + default: + logrus.Debug("Unexpected HealthCheck flag.") + } + } + }) + return updateHealthCheckConfig +} + +func GetNewHealthCheckConfig(originalHealthCheckConfig *manifest.Schema2HealthConfig, changedHealthCheckConfig define.UpdateHealthCheckConfig) (*manifest.Schema2HealthConfig, bool, bool, error) { + containsHealthCheckCmd := changedHealthCheckConfig.HealthCmd != nil + containsFlags := (changedHealthCheckConfig.HealthInterval != nil || + changedHealthCheckConfig.HealthRetries != nil || + changedHealthCheckConfig.HealthTimeout != nil || + changedHealthCheckConfig.HealthStartPeriod != nil) + if originalHealthCheckConfig == nil && !containsHealthCheckCmd && containsFlags { + return nil, false, false, errors.New("healthcheck command is not set") + } + + cmd := "" + interval := define.DefaultHealthCheckInterval + retries := int(define.DefaultHealthCheckRetries) + timeout := define.DefaultHealthCheckTimeout + startPeriod := define.DefaultHealthCheckStartPeriod + + if originalHealthCheckConfig != nil { + cmd = strings.Join(originalHealthCheckConfig.Test, " ") + interval = originalHealthCheckConfig.Interval.String() + retries = originalHealthCheckConfig.Retries + timeout = originalHealthCheckConfig.Timeout.String() + startPeriod = originalHealthCheckConfig.StartPeriod.String() + } + + changed := false + changedTimer := false + + noHealthCheck := false + if changedHealthCheckConfig.NoHealthCheck != nil { + noHealthCheck = *changedHealthCheckConfig.NoHealthCheck + } + + if changedHealthCheckConfig.HealthCmd != nil { + cmd = *changedHealthCheckConfig.HealthCmd + changed = true + } + + if changedHealthCheckConfig.HealthInterval != nil { + interval = *changedHealthCheckConfig.HealthInterval + changed = true + changedTimer = true + } + + if changedHealthCheckConfig.HealthRetries != nil { + retries = int(*changedHealthCheckConfig.HealthRetries) + changed = true + } + + if changedHealthCheckConfig.HealthTimeout != nil { + timeout = *changedHealthCheckConfig.HealthTimeout + changed = true + } + + if changedHealthCheckConfig.HealthStartPeriod != nil { + startPeriod = *changedHealthCheckConfig.HealthStartPeriod + changed = true + } + + var newHealthCheckConfig *manifest.Schema2HealthConfig + switch { + case noHealthCheck && changed: + return nil, false, false, errors.New("cannot specify both --no-healthcheck and other HealthCheck flags") + case noHealthCheck: + newHealthCheckConfig = &manifest.Schema2HealthConfig{ + Test: []string{"NONE"}, + } + changed = true + case changed: + newHealthCheckConfig_, err := MakeHealthCheckFromCli( + cmd, + interval, + uint(retries), + timeout, + startPeriod, + false, + ) + if err != nil { + return nil, false, false, err + } + newHealthCheckConfig = newHealthCheckConfig_ + } + return newHealthCheckConfig, changed, changedTimer, nil +} + +func GetNewStartupHealthCheckConfig(originalStartupHealthCheckConfig *define.StartupHealthCheck, changedHealthCheckConfig define.UpdateHealthCheckConfig) (*define.StartupHealthCheck, bool, bool, error) { + containsStartupHealthCheckCmd := changedHealthCheckConfig.HealthStartupCmd != nil + containsFlags := (changedHealthCheckConfig.HealthStartupInterval != nil || + changedHealthCheckConfig.HealthStartupRetries != nil || + changedHealthCheckConfig.HealthStartupTimeout != nil || + changedHealthCheckConfig.HealthStartupSuccess != nil) + if originalStartupHealthCheckConfig == nil && !containsStartupHealthCheckCmd && containsFlags { + return nil, false, false, errors.New("startup healthcheck command is not set") + } + + cmd := "" + interval := define.DefaultHealthCheckInterval + retries := 0 + timeout := define.DefaultHealthCheckTimeout + successes := 0 + + if originalStartupHealthCheckConfig != nil { + cmd = strings.Join(originalStartupHealthCheckConfig.Test, " ") + interval = originalStartupHealthCheckConfig.Interval.String() + retries = originalStartupHealthCheckConfig.Retries + timeout = originalStartupHealthCheckConfig.Timeout.String() + successes = originalStartupHealthCheckConfig.Successes + } + + changed := false + changedTimer := false + + noHealthCheck := false + if changedHealthCheckConfig.NoHealthCheck != nil { + noHealthCheck = *changedHealthCheckConfig.NoHealthCheck + } + + if changedHealthCheckConfig.HealthStartupCmd != nil { + cmd = *changedHealthCheckConfig.HealthStartupCmd + changed = true + } + + if changedHealthCheckConfig.HealthStartupInterval != nil { + interval = *changedHealthCheckConfig.HealthStartupInterval + changed = true + changedTimer = true + } + + if changedHealthCheckConfig.HealthStartupRetries != nil { + retries = int(*changedHealthCheckConfig.HealthStartupRetries) + changed = true + } + + if changedHealthCheckConfig.HealthStartupTimeout != nil { + timeout = *changedHealthCheckConfig.HealthStartupTimeout + changed = true + } + + if changedHealthCheckConfig.HealthStartupSuccess != nil { + successes = int(*changedHealthCheckConfig.HealthStartupSuccess) + changed = true + } + + var newStartupHealthCheckConfig *define.StartupHealthCheck + switch { + case noHealthCheck && changed: + return nil, false, false, errors.New("cannot specify both --no-healthcheck and other HealthCheck flags") + case noHealthCheck: + newStartupHealthCheckConfig = nil + changed = true + case changed: + tmpHcConfig, err := MakeHealthCheckFromCli( + cmd, + interval, + uint(retries), + timeout, + "1s", + true, + ) + if err != nil { + return nil, false, false, err + } + newStartupHealthCheckConfig = new(define.StartupHealthCheck) + newStartupHealthCheckConfig.Test = tmpHcConfig.Test + newStartupHealthCheckConfig.Interval = tmpHcConfig.Interval + newStartupHealthCheckConfig.Timeout = tmpHcConfig.Timeout + newStartupHealthCheckConfig.Retries = tmpHcConfig.Retries + newStartupHealthCheckConfig.Successes = successes + } + return newStartupHealthCheckConfig, changed, changedTimer, nil +} + +func GetNewGlobalHealthCheck(changedHealthCheckConfig define.UpdateHealthCheckConfig) (*string, *uint, *uint, *define.HealthCheckOnFailureAction, error) { + healthLogDestination := changedHealthCheckConfig.HealthLogDestination + if changedHealthCheckConfig.HealthLogDestination != nil { + switch *changedHealthCheckConfig.HealthLogDestination { + case define.HealthCheckEventsLoggerDestination, define.DefaultHealthCheckLocalDestination: + healthLogDestination = changedHealthCheckConfig.HealthLogDestination + default: + fileInfo, err := os.Stat(*changedHealthCheckConfig.HealthLogDestination) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("HealthCheck Log '%s' destination error: %w", *changedHealthCheckConfig.HealthLogDestination, err) + } + + mode := fileInfo.Mode() + if !mode.IsDir() { + return nil, nil, nil, nil, fmt.Errorf("HealthCheck Log '%s' destination must be directory", *changedHealthCheckConfig.HealthLogDestination) + } + + absPath, err := filepath.Abs(*changedHealthCheckConfig.HealthLogDestination) + if err != nil { + return nil, nil, nil, nil, err + } + healthLogDestination = &absPath + } + } + + healthMaxLogSize := changedHealthCheckConfig.HealthMaxLogSize + + healthMaxLogCount := changedHealthCheckConfig.HealthMaxLogCount + + var healthCheckOnFailureAction *define.HealthCheckOnFailureAction + if changedHealthCheckConfig.HealthOnFailure != nil { + val, err := define.ParseHealthCheckOnFailureAction(*changedHealthCheckConfig.HealthOnFailure) + if err != nil { + return nil, nil, nil, nil, err + } + healthCheckOnFailureAction = &val + } + + return healthLogDestination, healthMaxLogCount, healthMaxLogSize, healthCheckOnFailureAction, nil +} diff --git a/test/system/280-update.bats b/test/system/280-update.bats index 6013aa17e7..8020e4c4d4 100644 --- a/test/system/280-update.bats +++ b/test/system/280-update.bats @@ -161,4 +161,552 @@ device-write-iops = /dev/zero:4000 | - | - run_podman rm -f -t0 testctr } +function _create_container_check_change_of_HealthCheck_configuration_with_podman_update(){ + local ctrname="$1" + local msg="$2" + local format="$3" + local flag="$4" + local value="$5" + local expect="$6" + local expect_msg="$7" + local expect_exit_code="$8" + local is_startup="$9" + local no_hc="${10}" + + if [[ $is_startup = "yes" ]]; then + run_podman run -d --name $ctrname \ + --health-cmd "echo $msg" \ + --health-startup-cmd "echo $msg" \ + $IMAGE /home/podman/pause + cid="$output" + else + if [[ $no_hc = "yes" ]]; then + run_podman run -d --name $ctrname \ + $IMAGE /home/podman/pause + cid="$output" + else + run_podman run -d --name $ctrname \ + --health-cmd "echo $msg" \ + $IMAGE /home/podman/pause + cid="$output" + fi + fi + + + if [ -n "${value}" ]; then + run_podman $expect_exit_code update $ctrname $flag "$value" + else + run_podman $expect_exit_code update $ctrname $flag + fi + + if [[ $expect_exit_code = 0 ]]; then + run_podman inspect $ctrname --format $format + assert "$output" == "$expect" "$expect_msg" + fi + + output=$cid +} + +# HealthCheck configuration + +@test "podman update - --health-cmd" { + local msg="healthmsg-$(random_string)" + local new_msg="healthmsg-new-msg" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Test}}" \ + "--health-cmd" "echo $new_msg" \ + "[CMD-SHELL echo $new_msg]" \ + ".Config.Healthcheck.Test" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-interval" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Interval}}"\ + "--health-interval" "10s" \ + "10s" \ + ".Config.Healthcheck.Interval" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-log-destination" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + local TMP_DIR_HEALTHCHECK="$PODMAN_TMPDIR/healthcheck" + mkdir $TMP_DIR_HEALTHCHECK + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.HealthLogDestination}}" \ + "--health-log-destination" "$TMP_DIR_HEALTHCHECK" \ + "$TMP_DIR_HEALTHCHECK" \ + ".Config.HealthLogDestination" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-max-log-count" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.HealthMaxLogCount}}" \ + "--health-max-log-count" "20" \ + "20" \ + ".Config.HealthMaxLogCount" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-max-log-size" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.HealthMaxLogSize}}" \ + "--health-max-log-size" "10" \ + "10" \ + ".Config.HealthMaxLogSize" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-on-failure" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.HealthcheckOnFailureAction}}" \ + "--health-on-failure" "restart" \ + "restart" \ + ".Config.Healthcheck.HealthcheckOnFailureAction" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-retries" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Retries}}" \ + "--health-retries" "5" \ + "5" \ + ".Config.Healthcheck.Retries" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-timeout" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Timeout}}" \ + "--health-timeout" "10s" \ + "10s" \ + ".Config.Healthcheck.Timeout" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --no-healthcheck" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Test}}" \ + "--no-healthcheck" "" \ + "[NONE]" \ + "HealthCheck command is disabled" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + + +@test "podman update - --health-start-period" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.StartPeriod}}" \ + "--health-start-period" "10s" \ + "10s" \ + ".Config.Healthcheck.StartPeriod" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no hc command --health-interval" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Interval}}"\ + "--health-interval" "10s" \ + "10s" \ + ".Config.Healthcheck.Interval" \ + 125 "no" "yes" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no hc command --health-retries" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Retries}}" \ + "--health-retries" "5" \ + "5" \ + ".Config.Healthcheck.Retries" \ + 125 "no" "yes" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no hc command --health-timeout" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.Timeout}}" \ + "--health-timeout" "10s" \ + "10s" \ + ".Config.Healthcheck.Timeout" \ + 125 "no" "yes" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no hc command --health-start-period" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.Healthcheck.StartPeriod}}" \ + "--health-start-period" "10s" \ + "10s" \ + ".Config.Healthcheck.StartPeriod" \ + 125 "no" "yes" + + run_podman rm -t 0 -f $ctrname +} + +# Startup HealthCheck Configuration + +@test "podman update - --health-startup-cmd" { + local msg="healthmsg-$(random_string)" + local new_msg="healthmsg-new-msg" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Test}}" \ + "--health-startup-cmd" "echo $new_msg" \ + "[CMD-SHELL echo $new_msg]" \ + ".Config.StartupHealthCheck.Test" \ + 0 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-startup-interval" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Interval}}" \ + "--health-startup-interval" "10s" \ + "10s" \ + ".Config.StartupHealthCheck.Interval" \ + 0 "yes" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-startup-retries" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Retries}}" \ + "--health-startup-retries" "5" \ + "5" \ + ".Config.StartupHealthCheck.Retries" \ + 0 "yes" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-startup-success" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Successes}}" \ + "--health-startup-success " "10" \ + "10" \ + ".Config.StartupHealthCheck.Successes" \ + 0 "yes" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - --health-startup-timeout" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Timeout}}" \ + "--health-startup-timeout" "10s" \ + "10s" \ + ".Config.StartupHealthCheck.Timeout" \ + 0 "yes" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no startup hc command --health-startup-interval" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Interval}}" \ + "--health-startup-interval" "10s" \ + "10s" \ + ".Config.StartupHealthCheck.Interval" \ + 125 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no startup hc command --health-startup-retries" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Retries}}" \ + "--health-startup-retries" "5" \ + "5" \ + ".Config.StartupHealthCheck.Retries" \ + 125 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no startup hc command --health-startup-success" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Successes}}" \ + "--health-startup-success" "10" \ + "10" \ + ".Config.StartupHealthCheck.Successes" \ + 125 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - no startup hc command --health-startup-timeout" { + local msg="healthmsg-$(random_string)" + local ctrname="c-h-$(safename)" + + _create_container_check_change_of_HealthCheck_configuration_with_podman_update \ + $ctrname \ + $msg \ + "{{.Config.StartupHealthCheck.Timeout}}" \ + "--health-startup-timeout" "10s" \ + "10s" \ + ".Config.StartupHealthCheck.Timeout" \ + 125 "no" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - check behavior - change cmd and destination healthcheck" { + local TMP_DIR_HEALTHCHECK="$PODMAN_TMPDIR/healthcheck" + mkdir $TMP_DIR_HEALTHCHECK + local ctrname="c-h-$(safename)" + local msg="healthmsg-$(random_string)" + + run_podman run -d --name $ctrname \ + --health-cmd "echo $msg" \ + $IMAGE /home/podman/pause + cid="$output" + + run_podman healthcheck run $ctrname + is "$output" "" "output from 'podman healthcheck run'" + + run_podman update $ctrname --health-cmd "echo healthmsg-new" --health-log-destination $TMP_DIR_HEALTHCHECK + + run_podman healthcheck run $ctrname + is "$output" "" "output from 'podman healthcheck run'" + + healthcheck_log_path="${TMP_DIR_HEALTHCHECK}/${cid}-healthcheck.log" + # The healthcheck is triggered by the podman when the container is started, but its execution depends on systemd. + # And since `run_podman healthcheck run` is also run manually, it will result in two runs. + count=$(grep -co "healthmsg-new" $healthcheck_log_path) + assert "$count" -ge 1 "Number of matching health log messages" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - check behavior - change healthcheck interval" { + local ctrname="c-h-$(safename)" + local msg="healthmsg-$(random_string)" + + run_podman run -d --name $ctrname \ + --health-cmd "echo $msg" \ + --health-interval 2s \ + $IMAGE top + cid="$output" + + sleep 2s + + run_podman update $ctrname --health-interval 1s + + sleep 5s + + run_podman inspect $ctrname --format "{{.State.Health.Log}}" + count=$(grep -co "$msg" <<< "$output") + assert "$count" -ge 3 "Number of matching health log messages" + + run_podman rm -t 0 -f $ctrname +} + + +@test "podman update - check behavior - set healthcheck interval" { + local ctrname="c-h-$(safename)" + local msg="healthmsg-$(random_string)" + + run_podman run -d --name $ctrname \ + --health-cmd "echo $msg" \ + --health-interval disable \ + $IMAGE top + cid="$output" + + run_podman update $ctrname --health-interval 1s + + sleep 5s + + run_podman inspect $ctrname --format "{{.State.Health.Log}}" + count=$(grep -co "$msg" <<< "$output") + assert "$count" -ge 3 "Number of matching health log messages" + + run_podman rm -t 0 -f $ctrname +} + +@test "podman update - check behavior - change healthcheck startup interval" { + local ctrname="c-h-$(safename)" + local msg="healthmsg-$(random_string)" + + run_podman run -d --name $ctrname \ + --health-cmd "echo normal$msg" \ + --health-startup-cmd "echo startup$msg" \ + --health-startup-interval 30s \ + --health-startup-success 3 \ + $IMAGE top + cid="$output" + + sleep 1s + + run_podman update $ctrname --health-startup-interval 1s + + sleep 5s + + run_podman inspect $ctrname --format "{{.State.Health.Log}}" + count=$(grep -co "startup$msg" <<< "$output") + assert "$count" -ge 3 "Number of matching startup health log messages" + + count=$(grep -co "normal$msg" <<< "$output") + assert "$count" -ge 1 "Number of matching health log messages" + + run_podman rm -t 0 -f $ctrname +} + + +@test "podman update - check behavior - set healthcheck startup interval" { + local ctrname="c-h-$(safename)" + local msg="healthmsg-$(random_string)" + + run_podman run -d --name $ctrname \ + --health-cmd "echo normal$msg" \ + --health-startup-cmd "echo startup$msg" \ + --health-startup-interval disable \ + $IMAGE top + cid="$output" + + sleep 1s + + run_podman update $ctrname --health-startup-interval 1s + + sleep 5s + + run_podman inspect $ctrname --format "{{.State.Health.Log}}" + count=$(grep -co "$msg" <<< "$output") + assert "$count" -ge 1 "Number of matching health log messages" + + run_podman rm -t 0 -f $ctrname +} # vim: filetype=sh