From 0d73ee40b2e95ec7d50c7ee72fcb24cae0e190a7 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 1 Apr 2019 15:22:32 -0400 Subject: Add container restart policy to Libpod & Podman This initial version does not support restart count, but it works as advertised otherwise. Signed-off-by: Matthew Heon --- pkg/spec/createconfig.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'pkg') diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 90e7accf3..f1861dd19 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -108,6 +108,7 @@ type CreateConfig struct { ReadOnlyRootfs bool //read-only ReadOnlyTmpfs bool //read-only-tmpfs Resources CreateResourceConfig + RestartPolicy string Rm bool //rm StopSignal syscall.Signal // stop-signal StopTimeout uint // stop-timeout @@ -359,6 +360,26 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, libpod.WithCgroupParent(c.CgroupParent)) } + if c.RestartPolicy != "" { + if c.RestartPolicy == "unless-stopped" { + return nil, errors.Wrapf(libpod.ErrInvalidArg, "the unless-stopped restart policy is not supported") + } + + split := strings.Split(c.RestartPolicy, ":") + if len(split) > 1 { + numTries, err := strconv.Atoi(split[1]) + if err != nil { + return nil, errors.Wrapf(err, "%s is not a valid number of retries for restart policy", split[1]) + } + if numTries < 0 { + return nil, errors.Wrapf(libpod.ErrInvalidArg, "restart policy requires a positive number of retries") + } + options = append(options, libpod.WithRestartRetries(uint(numTries))) + } + + options = append(options, libpod.WithRestartPolicy(c.RestartPolicy)) + } + // Always use a cleanup process to clean up Podman after termination exitCmd, err := c.createExitCommand(runtime) if err != nil { -- cgit v1.2.3-54-g00ecf From f4db6d5cf61741f9b0de163b158ecdc2bcfa6669 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 1 Apr 2019 19:20:03 -0400 Subject: Add support for retry count with --restart flag The on-failure restart option supports restarting only a given number of times. To do this, we need one additional field in the DB to track restart count (which conveniently fills a field in Inspect we weren't populating), plus some plumbing logic. Signed-off-by: Matthew Heon --- libpod/container.go | 4 ++++ libpod/container_api.go | 18 +++++++++--------- libpod/container_inspect.go | 1 + libpod/container_internal.go | 34 +++++++++++++++++++++++----------- pkg/inspect/inspect.go | 2 +- pkg/spec/createconfig.go | 3 +-- 6 files changed, 39 insertions(+), 23 deletions(-) (limited to 'pkg') diff --git a/libpod/container.go b/libpod/container.go index 4236f2456..769c4f69c 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -185,6 +185,10 @@ type ContainerState struct { // RestartPolicyMatch indicates whether the conditions for restart // policy have been met. RestartPolicyMatch bool `json:"restartPolicyMatch,omitempty"` + // RestartCount is how many times the container was restarted by its + // restart policy. This is NOT incremented by normal container restarts + // (only by restart policy). + RestartCount uint `json:"restartCount,omitempty"` // ExtensionStageHooks holds hooks which will be executed by libpod // and not delegated to the OCI runtime. diff --git a/libpod/container_api.go b/libpod/container_api.go index 8b3dd4186..7132a82c2 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -57,11 +57,11 @@ func (c *Container) Init(ctx context.Context) (err error) { if c.state.State == ContainerStateStopped { // Reinitialize the container - return c.reinit(ctx) + return c.reinit(ctx, false) } // Initialize the container for the first time - return c.init(ctx) + return c.init(ctx, false) } // Start starts a container. @@ -605,13 +605,13 @@ func (c *Container) Cleanup(ctx context.Context) error { // restart the container. // However, perform a full validation of restart policy first. if c.state.RestartPolicyMatch { - // if restart policy is on-error and exit code is 0, we do - // nothing. - // TODO: if restart retries is set, handle that here. - if c.config.RestartRetries != 0 { - return errors.Wrapf(ErrNotImplemented, "restart retries not yet implemented") + if c.config.RestartPolicy == "on-failure" && c.state.ExitCode != 0 { + logrus.Debugf("Container %s restart policy trigger: on retry %d (of %d)", + c.ID(), c.state.RestartCount, c.config.RestartRetries) } - if (c.config.RestartPolicy == "on-error" && c.state.ExitCode == 0) || c.config.RestartPolicy == "always" { + if (c.config.RestartPolicy == "on-failure" && c.state.ExitCode != 0 && + (c.config.RestartRetries > 0 && c.state.RestartCount < c.config.RestartRetries)) || + c.config.RestartPolicy == "always" { // The container stopped. We need to restart it. return c.handleRestartPolicy(ctx) } @@ -780,7 +780,7 @@ func (c *Container) Refresh(ctx context.Context) error { if err := c.prepare(); err != nil { return err } - if err := c.init(ctx); err != nil { + if err := c.init(ctx, false); err != nil { return err } } diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index aa3a07888..a7369bfdd 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -95,6 +95,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data) LogPath: config.LogPath, ConmonPidFile: config.ConmonPidFile, Name: config.Name, + RestartCount: int32(runtimeInfo.RestartCount), Driver: driverData.Name, MountLabel: config.MountLabel, ProcessLabel: config.ProcessLabel, diff --git a/libpod/container_internal.go b/libpod/container_internal.go index f72d86c84..b1b7a090f 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -221,6 +221,13 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (err error) { return err } + // Increment restart count + c.state.RestartCount = c.state.RestartCount + 1 + logrus.Debugf("Container %s now on retry %d", c.ID(), c.state.RestartCount) + if err := c.save(); err != nil { + return err + } + defer func() { if err != nil { if err2 := c.cleanup(ctx); err2 != nil { @@ -234,13 +241,13 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (err error) { if c.state.State == ContainerStateStopped { // Reinitialize the container if we need to - if err := c.reinit(ctx); err != nil { + if err := c.reinit(ctx, true); err != nil { return err } } else if c.state.State == ContainerStateConfigured || c.state.State == ContainerStateExited { // Initialize the container - if err := c.init(ctx); err != nil { + if err := c.init(ctx, true); err != nil { return err } } @@ -423,6 +430,7 @@ func resetState(state *ContainerState) error { state.BindMounts = make(map[string]string) state.StoppedByUser = false state.RestartPolicyMatch = false + state.RestartCount = 0 return nil } @@ -616,13 +624,13 @@ func (c *Container) prepareToStart(ctx context.Context, recursive bool) (err err if c.state.State == ContainerStateStopped { // Reinitialize the container if we need to - if err := c.reinit(ctx); err != nil { + if err := c.reinit(ctx, false); err != nil { return err } } else if c.state.State == ContainerStateConfigured || c.state.State == ContainerStateExited { // Or initialize it if necessary - if err := c.init(ctx); err != nil { + if err := c.init(ctx, false); err != nil { return err } } @@ -810,7 +818,7 @@ func (c *Container) completeNetworkSetup() error { } // Initialize a container, creating it in the runtime -func (c *Container) init(ctx context.Context) error { +func (c *Container) init(ctx context.Context, retainRetries bool) error { span, _ := opentracing.StartSpanFromContext(ctx, "init") span.SetTag("struct", "container") defer span.Finish() @@ -839,6 +847,10 @@ func (c *Container) init(ctx context.Context) error { c.state.StoppedByUser = false c.state.RestartPolicyMatch = false + if !retainRetries { + c.state.RestartCount = 0 + } + if err := c.save(); err != nil { return err } @@ -900,7 +912,7 @@ func (c *Container) cleanupRuntime(ctx context.Context) error { // Should only be done on ContainerStateStopped containers. // Not necessary for ContainerStateExited - the container has already been // removed from the runtime, so init() can proceed freely. -func (c *Container) reinit(ctx context.Context) error { +func (c *Container) reinit(ctx context.Context, retainRetries bool) error { span, _ := opentracing.StartSpanFromContext(ctx, "reinit") span.SetTag("struct", "container") defer span.Finish() @@ -912,7 +924,7 @@ func (c *Container) reinit(ctx context.Context) error { } // Initialize the container again - return c.init(ctx) + return c.init(ctx, retainRetries) } // Initialize (if necessary) and start a container @@ -950,12 +962,12 @@ func (c *Container) initAndStart(ctx context.Context) (err error) { if c.state.State == ContainerStateStopped { logrus.Debugf("Recreating container %s in OCI runtime", c.ID()) - if err := c.reinit(ctx); err != nil { + if err := c.reinit(ctx, false); err != nil { return err } } else if c.state.State == ContainerStateConfigured || c.state.State == ContainerStateExited { - if err := c.init(ctx); err != nil { + if err := c.init(ctx, false); err != nil { return err } } @@ -1058,13 +1070,13 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e if c.state.State == ContainerStateStopped { // Reinitialize the container if we need to - if err := c.reinit(ctx); err != nil { + if err := c.reinit(ctx, false); err != nil { return err } } else if c.state.State == ContainerStateConfigured || c.state.State == ContainerStateExited { // Initialize the container - if err := c.init(ctx); err != nil { + if err := c.init(ctx, false); err != nil { return err } } diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index 6978370ef..693755aa8 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -161,7 +161,7 @@ type ContainerInspectData struct { LogPath string `json:"LogPath"` ConmonPidFile string `json:"ConmonPidFile"` Name string `json:"Name"` - RestartCount int32 `json:"RestartCount"` //TODO + RestartCount int32 `json:"RestartCount"` Driver string `json:"Driver"` MountLabel string `json:"MountLabel"` ProcessLabel string `json:"ProcessLabel"` diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index f1861dd19..9979e773c 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -376,8 +376,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l } options = append(options, libpod.WithRestartRetries(uint(numTries))) } - - options = append(options, libpod.WithRestartPolicy(c.RestartPolicy)) + options = append(options, libpod.WithRestartPolicy(split[0])) } // Always use a cleanup process to clean up Podman after termination -- cgit v1.2.3-54-g00ecf