From 0d73ee40b2e95ec7d50c7ee72fcb24cae0e190a7 Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
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 <matthew.heon@pm.me>
---
 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 <matthew.heon@pm.me>
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 <matthew.heon@pm.me>
---
 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