From 3fb52f4fbb04781e32f72888c9e509dea5d7b434 Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Mon, 1 Apr 2019 13:30:28 -0400
Subject: Add a StoppedByUser field to the DB

This field indicates that a container was explciitly stopped by
an API call, and did not exit naturally. It's used when
implementing restart policy for containers.

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 libpod/container.go          | 18 ++++++++++++++++++
 libpod/container_internal.go |  7 +++++++
 2 files changed, 25 insertions(+)

(limited to 'libpod')

diff --git a/libpod/container.go b/libpod/container.go
index 4bf9a1ba9..570110ca1 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -179,6 +179,9 @@ type ContainerState struct {
 	// This maps the path the file will be mounted to in the container to
 	// the path of the file on disk outside the container
 	BindMounts map[string]string `json:"bindMounts,omitempty"`
+	// StoppedByUser indicates whether the container was stopped by an
+	// explicit call to the Stop() API.
+	StoppedByUser bool
 
 	// ExtensionStageHooks holds hooks which will be executed by libpod
 	// and not delegated to the OCI runtime.
@@ -1003,6 +1006,21 @@ func (c *Container) BindMounts() (map[string]string, error) {
 	return newMap, nil
 }
 
+// StoppedByUser returns whether the container was last stopped by an explicit
+// call to the Stop() API, or whether it exited naturally.
+func (c *Container) StoppedByUser() (bool, error) {
+	if !c.batched {
+		c.lock.Lock()
+		defer c.lock.Unlock()
+
+		if err := c.syncContainer(); err != nil {
+			return false, err
+		}
+	}
+
+	return c.state.StoppedByUser, nil
+}
+
 // Misc Accessors
 // Most will require locking
 
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index a791df491..0b9c5a96a 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -376,6 +376,7 @@ func resetState(state *ContainerState) error {
 	state.ExecSessions = make(map[string]*ExecSession)
 	state.NetworkStatus = nil
 	state.BindMounts = make(map[string]string)
+	state.StoppedByUser = false
 
 	return nil
 }
@@ -789,6 +790,7 @@ func (c *Container) init(ctx context.Context) error {
 	c.state.ExitCode = 0
 	c.state.Exited = false
 	c.state.State = ContainerStateCreated
+	c.state.StoppedByUser = false
 
 	if err := c.save(); err != nil {
 		return err
@@ -950,6 +952,11 @@ func (c *Container) stop(timeout uint) error {
 		return err
 	}
 
+	c.state.StoppedByUser = true
+	if err := c.save(); err != nil {
+		return errors.Wrapf(err, "error saving container %s state after stopping", c.ID())
+	}
+
 	// Wait until we have an exit file, and sync once we do
 	return c.waitForExitFileAndSync()
 }
-- 
cgit v1.2.3-54-g00ecf


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>
---
 cmd/podman/common.go         |  2 +-
 cmd/podman/shared/create.go  | 34 +++++++++++++++-----------------
 libpod/container.go          | 27 ++++++++++++++++++++++++-
 libpod/container_api.go      | 19 ++++++++++++++++++
 libpod/container_internal.go | 47 ++++++++++++++++++++++++++++++++++++++++++++
 libpod/options.go            | 35 +++++++++++++++++++++++++++++++++
 pkg/spec/createconfig.go     | 21 ++++++++++++++++++++
 7 files changed, 165 insertions(+), 20 deletions(-)

(limited to 'libpod')

diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index 8aca08248..c0bcaa5c5 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -444,7 +444,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) {
 	)
 	createFlags.String(
 		"restart", "",
-		"Restart is not supported.  Please use a systemd unit file for restart",
+		"Restart policy to apply when a container exits",
 	)
 	createFlags.Bool(
 		"rm", false,
diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go
index 81566326b..1ef852cd2 100644
--- a/cmd/podman/shared/create.go
+++ b/cmd/podman/shared/create.go
@@ -279,9 +279,6 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
 		blkioWeight                                              uint16
 		namespaces                                               map[string]string
 	)
-	if c.IsSet("restart") {
-		return nil, errors.Errorf("--restart option is not supported.\nUse systemd unit files for restarting containers")
-	}
 
 	idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname"))
 	if err != nil {
@@ -676,21 +673,22 @@ func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.
 			PidsLimit:         c.Int64("pids-limit"),
 			Ulimit:            c.StringSlice("ulimit"),
 		},
-		Rm:          c.Bool("rm"),
-		StopSignal:  stopSignal,
-		StopTimeout: c.Uint("stop-timeout"),
-		Sysctl:      sysctl,
-		Systemd:     systemd,
-		Tmpfs:       c.StringSlice("tmpfs"),
-		Tty:         tty,
-		User:        user,
-		UsernsMode:  usernsMode,
-		MountsFlag:  c.StringArray("mount"),
-		Volumes:     c.StringArray("volume"),
-		WorkDir:     workDir,
-		Rootfs:      rootfs,
-		VolumesFrom: c.StringSlice("volumes-from"),
-		Syslog:      c.Bool("syslog"),
+		RestartPolicy: c.String("restart"),
+		Rm:            c.Bool("rm"),
+		StopSignal:    stopSignal,
+		StopTimeout:   c.Uint("stop-timeout"),
+		Sysctl:        sysctl,
+		Systemd:       systemd,
+		Tmpfs:         c.StringSlice("tmpfs"),
+		Tty:           tty,
+		User:          user,
+		UsernsMode:    usernsMode,
+		MountsFlag:    c.StringArray("mount"),
+		Volumes:       c.StringArray("volume"),
+		WorkDir:       workDir,
+		Rootfs:        rootfs,
+		VolumesFrom:   c.StringSlice("volumes-from"),
+		Syslog:        c.Bool("syslog"),
 	}
 
 	if config.Privileged {
diff --git a/libpod/container.go b/libpod/container.go
index 570110ca1..4236f2456 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -181,7 +181,10 @@ type ContainerState struct {
 	BindMounts map[string]string `json:"bindMounts,omitempty"`
 	// StoppedByUser indicates whether the container was stopped by an
 	// explicit call to the Stop() API.
-	StoppedByUser bool
+	StoppedByUser bool `json:"stoppedByUser,omitempty"`
+	// RestartPolicyMatch indicates whether the conditions for restart
+	// policy have been met.
+	RestartPolicyMatch bool `json:"restartPolicyMatch,omitempty"`
 
 	// ExtensionStageHooks holds hooks which will be executed by libpod
 	// and not delegated to the OCI runtime.
@@ -349,6 +352,17 @@ type ContainerConfig struct {
 	LogPath string `json:"logPath"`
 	// File containing the conmon PID
 	ConmonPidFile string `json:"conmonPidFile,omitempty"`
+	// RestartPolicy indicates what action the container will take upon
+	// exiting naturally.
+	// Allowed options are "no" (take no action), "on-failure" (restart on
+	// non-zero exit code, up an a maximum of RestartRetries times),
+	// and "always" (always restart the container on any exit code).
+	// The empty string is treated as the default ("no")
+	RestartPolicy string `json:"restart_policy,omitempty"`
+	// RestartRetries indicates the number of attempts that will be made to
+	// restart the container. Used only if RestartPolicy is set to
+	// "on-failure".
+	RestartRetries uint `json:"restart_retries,omitempty"`
 	// TODO log options for log drivers
 
 	PostConfigureNetNS bool `json:"postConfigureNetNS"`
@@ -732,6 +746,17 @@ func (c *Container) LogPath() string {
 	return c.config.LogPath
 }
 
+// RestartPolicy returns the container's restart policy.
+func (c *Container) RestartPolicy() string {
+	return c.config.RestartPolicy
+}
+
+// RestartRetries returns the number of retries that will be attempted when
+// using the "on-failure" restart policy
+func (c *Container) RestartRetries() uint {
+	return c.config.RestartRetries
+}
+
 // RuntimeName returns the name of the runtime
 func (c *Container) RuntimeName() string {
 	return c.runtime.ociRuntime.name
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 5bfd869b3..46c913e99 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -583,6 +583,7 @@ func (c *Container) Cleanup(ctx context.Context) error {
 	if !c.batched {
 		c.lock.Lock()
 		defer c.lock.Unlock()
+
 		if err := c.syncContainer(); err != nil {
 			return err
 		}
@@ -593,6 +594,24 @@ func (c *Container) Cleanup(ctx context.Context) error {
 		return errors.Wrapf(ErrCtrStateInvalid, "container %s is running or paused, refusing to clean up", c.ID())
 	}
 
+	// If we have a restart policy match when updating the state, we need to
+	// 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-error" && c.state.ExitCode == 0) || c.config.RestartPolicy == "always" {
+			// The container stopped. We need to restart it.
+			return c.handleRestartPolicy(ctx)
+		}
+	}
+
+	// If we aren't hitting restart policy, we perform a normal cleanup
+
 	// Check if we have active exec sessions
 	if len(c.state.ExecSessions) != 0 {
 		return errors.Wrapf(ErrCtrStateInvalid, "container %s has active exec sessions, refusing to clean up", c.ID())
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 0b9c5a96a..f72d86c84 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -210,6 +210,43 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error {
 	return nil
 }
 
+// Handle container restart policy.
+// This is called when a container has exited, and was not explicitly stopped by
+// an API call to stop the container or pod it is in.
+func (c *Container) handleRestartPolicy(ctx context.Context) (err error) {
+	logrus.Debugf("Restarting container %s due to restart policy %s", c.ID(), c.config.RestartPolicy)
+
+	// Need to check if dependencies are alive.
+	if err = c.checkDependenciesAndHandleError(ctx); err != nil {
+		return err
+	}
+
+	defer func() {
+		if err != nil {
+			if err2 := c.cleanup(ctx); err2 != nil {
+				logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2)
+			}
+		}
+	}()
+	if err := c.prepare(); err != nil {
+		return err
+	}
+
+	if c.state.State == ContainerStateStopped {
+		// Reinitialize the container if we need to
+		if err := c.reinit(ctx); err != nil {
+			return err
+		}
+	} else if c.state.State == ContainerStateConfigured ||
+		c.state.State == ContainerStateExited {
+		// Initialize the container
+		if err := c.init(ctx); err != nil {
+			return err
+		}
+	}
+	return c.start()
+}
+
 // Sync this container with on-disk state and runtime status
 // Should only be called with container lock held
 // This function should suffice to ensure a container's state is accurate and
@@ -230,6 +267,14 @@ func (c *Container) syncContainer() error {
 		}
 		// Only save back to DB if state changed
 		if c.state.State != oldState {
+			// Check for a restart policy match
+			if c.config.RestartPolicy != "" && c.config.RestartPolicy != "no" &&
+				(oldState == ContainerStateRunning || oldState == ContainerStatePaused) &&
+				(c.state.State == ContainerStateStopped || c.state.State == ContainerStateExited) &&
+				!c.state.StoppedByUser {
+				c.state.RestartPolicyMatch = true
+			}
+
 			if err := c.save(); err != nil {
 				return err
 			}
@@ -377,6 +422,7 @@ func resetState(state *ContainerState) error {
 	state.NetworkStatus = nil
 	state.BindMounts = make(map[string]string)
 	state.StoppedByUser = false
+	state.RestartPolicyMatch = false
 
 	return nil
 }
@@ -791,6 +837,7 @@ func (c *Container) init(ctx context.Context) error {
 	c.state.Exited = false
 	c.state.State = ContainerStateCreated
 	c.state.StoppedByUser = false
+	c.state.RestartPolicyMatch = false
 
 	if err := c.save(); err != nil {
 		return err
diff --git a/libpod/options.go b/libpod/options.go
index 86c04db09..e83515822 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1239,6 +1239,41 @@ func WithUseImageHosts() CtrCreateOption {
 	}
 }
 
+// WithRestartPolicy sets the container's restart policy. Valid values are
+// "no", "on-failure", and "always". The empty string is allowed, and will be
+// equivalent to "no".
+func WithRestartPolicy(policy string) CtrCreateOption {
+	return func(ctr *Container) error {
+		if ctr.valid {
+			return ErrCtrFinalized
+		}
+
+		switch policy {
+		case "", "no", "on-failure", "always":
+			ctr.config.RestartPolicy = policy
+		default:
+			return errors.Wrapf(ErrInvalidArg, "%q is not a valid restart policy", policy)
+		}
+
+		return nil
+	}
+}
+
+// WithRestartRetries sets the number of retries to use when restarting a
+// container with the "on-failure" restart policy.
+// 0 is an allowed value, and indicates infinite retries.
+func WithRestartRetries(tries uint) CtrCreateOption {
+	return func(ctr *Container) error {
+		if ctr.valid {
+			return ErrCtrFinalized
+		}
+
+		ctr.config.RestartRetries = tries
+
+		return nil
+	}
+}
+
 // withIsInfra sets the container to be an infra container. This means the container will be sometimes hidden
 // and expected to be the first container in the pod.
 func withIsInfra() CtrCreateOption {
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 dc42304f3804632d01345478ab9b1f122b48d516 Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Mon, 1 Apr 2019 18:17:16 -0400
Subject: Sending signals to containers prevents restart policy

Noticed this when testing some behavior with Docker.

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 libpod/container_api.go | 9 ++++++++-
 libpod/pod_api.go       | 7 +++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

(limited to 'libpod')

diff --git a/libpod/container_api.go b/libpod/container_api.go
index 46c913e99..8b3dd4186 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -199,8 +199,15 @@ func (c *Container) Kill(signal uint) error {
 	if c.state.State != ContainerStateRunning {
 		return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers")
 	}
+
 	defer c.newContainerEvent(events.Kill)
-	return c.runtime.ociRuntime.killContainer(c, signal)
+	if err := c.runtime.ociRuntime.killContainer(c, signal); err != nil {
+		return err
+	}
+
+	c.state.StoppedByUser = true
+
+	return c.save()
 }
 
 // Exec starts a new process inside the container
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index 9a6baf23e..9ed5c88eb 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -364,6 +364,13 @@ func (p *Pod) Kill(signal uint) (map[string]error, error) {
 		}
 
 		logrus.Debugf("Killed container %s with signal %d", ctr.ID(), signal)
+
+		ctr.state.StoppedByUser = true
+		if err := ctr.save(); err != nil {
+			ctrErrors[ctr.ID()] = err
+		}
+
+		ctr.lock.Unlock()
 	}
 
 	if len(ctrErrors) > 0 {
-- 
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 'libpod')

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


From 7ba1b609aad678f458951c978455ea44c5b4d3ec Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Mon, 1 Apr 2019 19:45:23 -0400
Subject: Move to using constants for valid restart policy types

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 libpod/container.go          | 14 ++++++++++++++
 libpod/container_api.go      |  6 +++---
 libpod/container_internal.go |  2 +-
 libpod/options.go            |  2 +-
 4 files changed, 19 insertions(+), 5 deletions(-)

(limited to 'libpod')

diff --git a/libpod/container.go b/libpod/container.go
index 769c4f69c..c07f4c78d 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -102,6 +102,20 @@ func (ns LinuxNS) String() string {
 	}
 }
 
+// Valid restart policy types.
+const (
+	// RestartPolicyNone indicates that no restart policy has been requested
+	// by a container.
+	RestartPolicyNone = ""
+	// RestartPolicyNo is identical in function to RestartPolicyNone.
+	RestartPolicyNo = "no"
+	// RestartPolicyAlways unconditionally restarts the container.
+	RestartPolicyAlways = "always"
+	// RestartPolicyOnFailure restarts the container on non-0 exit code,
+	// with an optional maximum number of retries.
+	RestartPolicyOnFailure = "on-failure"
+)
+
 // Container is a single OCI container.
 // All operations on a Container that access state must begin with a call to
 // syncContainer().
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 7132a82c2..faf01eccb 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -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 c.config.RestartPolicy == "on-failure" && c.state.ExitCode != 0 {
+		if c.config.RestartPolicy == RestartPolicyOnFailure && 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-failure" && c.state.ExitCode != 0 &&
+		if (c.config.RestartPolicy == RestartPolicyOnFailure && c.state.ExitCode != 0 &&
 			(c.config.RestartRetries > 0 && c.state.RestartCount < c.config.RestartRetries)) ||
-			c.config.RestartPolicy == "always" {
+			c.config.RestartPolicy == RestartPolicyAlways {
 			// The container stopped. We need to restart it.
 			return c.handleRestartPolicy(ctx)
 		}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index b1b7a090f..76de37a09 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -275,7 +275,7 @@ func (c *Container) syncContainer() error {
 		// Only save back to DB if state changed
 		if c.state.State != oldState {
 			// Check for a restart policy match
-			if c.config.RestartPolicy != "" && c.config.RestartPolicy != "no" &&
+			if c.config.RestartPolicy != RestartPolicyNone && c.config.RestartPolicy != RestartPolicyNo &&
 				(oldState == ContainerStateRunning || oldState == ContainerStatePaused) &&
 				(c.state.State == ContainerStateStopped || c.state.State == ContainerStateExited) &&
 				!c.state.StoppedByUser {
diff --git a/libpod/options.go b/libpod/options.go
index e83515822..7ec7dfe63 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1249,7 +1249,7 @@ func WithRestartPolicy(policy string) CtrCreateOption {
 		}
 
 		switch policy {
-		case "", "no", "on-failure", "always":
+		case RestartPolicyNone, RestartPolicyNo, RestartPolicyOnFailure, RestartPolicyAlways:
 			ctr.config.RestartPolicy = policy
 		default:
 			return errors.Wrapf(ErrInvalidArg, "%q is not a valid restart policy", policy)
-- 
cgit v1.2.3-54-g00ecf


From 56356d70276bed453bfe67fee068a14b7c727b2f Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Wed, 3 Apr 2019 10:24:35 -0400
Subject: Restart policy should not run if a container is running

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 libpod/container_internal.go | 8 ++++++++
 1 file changed, 8 insertions(+)

(limited to 'libpod')

diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 76de37a09..22ed34987 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -221,6 +221,14 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (err error) {
 		return err
 	}
 
+	// Is the container running again?
+	// If so, we don't have to do anything
+	if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused {
+		return nil
+	} else if c.state.State == ContainerStateUnknown {
+		return errors.Wrapf(ErrInternal, "invalid container state encountered in restart attempt!")
+	}
+
 	// Increment restart count
 	c.state.RestartCount = c.state.RestartCount + 1
 	logrus.Debugf("Container %s now on retry %d", c.ID(), c.state.RestartCount)
-- 
cgit v1.2.3-54-g00ecf


From cafb68e301ca808392aa31fda51370593fcabbed Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Wed, 3 Apr 2019 14:17:02 -0400
Subject: Add a restart event, and make one during restart policy

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 docs/podman-events.1.md      | 1 +
 libpod/container_internal.go | 4 ++++
 libpod/events/config.go      | 2 ++
 3 files changed, 7 insertions(+)

(limited to 'libpod')

diff --git a/docs/podman-events.1.md b/docs/podman-events.1.md
index da142c0fb..3ccecac28 100644
--- a/docs/podman-events.1.md
+++ b/docs/podman-events.1.md
@@ -28,6 +28,7 @@ The *container* event type will report the follow statuses:
  * pause
  * prune
  * remove
+ * restart
  * restore
  * start
  * stop
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 22ed34987..00f916d0f 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -229,6 +229,8 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (err error) {
 		return errors.Wrapf(ErrInternal, "invalid container state encountered in restart attempt!")
 	}
 
+	c.newContainerEvent(events.Restart)
+
 	// Increment restart count
 	c.state.RestartCount = c.state.RestartCount + 1
 	logrus.Debugf("Container %s now on retry %d", c.ID(), c.state.RestartCount)
@@ -1060,6 +1062,8 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e
 		return errors.Wrapf(ErrCtrStateInvalid, "unable to restart a container in a paused or unknown state")
 	}
 
+	c.newContainerEvent(events.Restart)
+
 	if c.state.State == ContainerStateRunning {
 		if err := c.stop(timeout); err != nil {
 			return err
diff --git a/libpod/events/config.go b/libpod/events/config.go
index 36387e835..810988205 100644
--- a/libpod/events/config.go
+++ b/libpod/events/config.go
@@ -134,6 +134,8 @@ const (
 	// Renumber indicates that lock numbers were reallocated at user
 	// request.
 	Renumber Status = "renumber"
+	// Restart indicates the target was restarted via an API call.
+	Restart Status = "restart"
 	// Restore ...
 	Restore Status = "restore"
 	// Save ...
-- 
cgit v1.2.3-54-g00ecf


From d7c367aa61850936249fc99461a92185cb6ba8aa Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Fri, 3 May 2019 10:35:48 -0400
Subject: Address review comments on restart policy

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 cmd/podman/shared/create.go  |  2 +-
 docs/podman-create.1.md      | 19 ++----------------
 docs/podman-run.1.md         |  4 ++--
 libpod/container_api.go      | 25 +++++++++--------------
 libpod/container_internal.go | 48 +++++++++++++++++++++++++++++++++++---------
 5 files changed, 54 insertions(+), 44 deletions(-)

(limited to 'libpod')

diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go
index d2bd23e3e..d1f704374 100644
--- a/cmd/podman/shared/create.go
+++ b/cmd/podman/shared/create.go
@@ -42,7 +42,7 @@ func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.
 		defer span.Finish()
 	}
 	if c.Bool("rm") && c.String("restart") != "" && c.String("restart") != "no" {
-		return nil, nil, errors.Errorf("the --rm flag conflicts with --restart")
+		return nil, nil, errors.Errorf("the --rm option conflicts with --restart")
 	}
 
 	rtc, err := runtime.GetConfig()
diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md
index abdfe9f8b..3d9b9ca7d 100644
--- a/docs/podman-create.1.md
+++ b/docs/podman-create.1.md
@@ -576,8 +576,8 @@ Valid values are:
 - `always`                   : Restart containers when they exit, regardless of status, retrying indefinitely
 
 Please note that restart will not restart containers after a system reboot.
-If you require this functionality, please add your Podman commands to a systemd unit file, or create an init script for your init system of choice.
-There is an example of restarting a container with systemd below.
+This this functionality is required in your environment, you can invoke Podman from a systemd unit file, or create an init script for whichever init system is in use.
+To generate systemd unit files, please see *podman generate systemd*
 
 **--rm**=*true*|*false*
 
@@ -865,21 +865,6 @@ the uids and gids from the host.
 $ podman create --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello
 ```
 
-### Running a podman container to restart inside of a systemd unit file
-
-
-```
-[Unit]
-Description=My App
-[Service]
-Restart=always
-ExecStart=/usr/bin/podman start -a my_app
-ExecStop=/usr/bin/podman stop -t 10 my_app
-KillMode=process
-[Install]
-WantedBy=multi-user.target
-```
-
 ### Rootless Containers
 
 Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils
diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md
index 078e388ea..f9dee4713 100644
--- a/docs/podman-run.1.md
+++ b/docs/podman-run.1.md
@@ -598,8 +598,8 @@ Valid values are:
 - `always`                   : Restart containers when they exit, regardless of status, retrying indefinitely
 
 Please note that restart will not restart containers after a system reboot.
-If you require this functionality, please add your Podman commands to a systemd unit file, or create an init script for your init system of choice.
-To manage container restart via systemd, see *podman generate systemd*.
+This this functionality is required in your environment, you can invoke Podman from a systemd unit file, or create an init script for whichever init system is in use.
+To generate systemd unit files, please see *podman generate systemd*
 
 **--rm**=*true*|*false*
 
diff --git a/libpod/container_api.go b/libpod/container_api.go
index faf01eccb..5bb610aab 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -601,23 +601,18 @@ func (c *Container) Cleanup(ctx context.Context) error {
 		return errors.Wrapf(ErrCtrStateInvalid, "container %s is running or paused, refusing to clean up", c.ID())
 	}
 
-	// If we have a restart policy match when updating the state, we need to
-	// restart the container.
-	// However, perform a full validation of restart policy first.
-	if c.state.RestartPolicyMatch {
-		if c.config.RestartPolicy == RestartPolicyOnFailure && 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 == RestartPolicyOnFailure && c.state.ExitCode != 0 &&
-			(c.config.RestartRetries > 0 && c.state.RestartCount < c.config.RestartRetries)) ||
-			c.config.RestartPolicy == RestartPolicyAlways {
-			// The container stopped. We need to restart it.
-			return c.handleRestartPolicy(ctx)
-		}
+	// Handle restart policy.
+	// Returns a bool indicating whether we actually restarted.
+	// If we did, don't proceed to cleanup - just exit.
+	didRestart, err := c.handleRestartPolicy(ctx)
+	if err != nil {
+		return err
+	}
+	if didRestart {
+		return nil
 	}
 
-	// If we aren't hitting restart policy, we perform a normal cleanup
+	// If we didn't restart, we perform a normal cleanup
 
 	// Check if we have active exec sessions
 	if len(c.state.ExecSessions) != 0 {
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 00f916d0f..0dadcd1ca 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -213,20 +213,47 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error {
 // Handle container restart policy.
 // This is called when a container has exited, and was not explicitly stopped by
 // an API call to stop the container or pod it is in.
-func (c *Container) handleRestartPolicy(ctx context.Context) (err error) {
+func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, err error) {
+	// If we did not get a restart policy match, exit immediately.
+	// Do the same if we're not a policy that restarts.
+	if !c.state.RestartPolicyMatch ||
+		c.config.RestartPolicy == RestartPolicyNo ||
+		c.config.RestartPolicy == RestartPolicyNone {
+		return false, nil
+	}
+
+	// If we're RestartPolicyOnFailure, we need to check retries and exit
+	// code.
+	if c.config.RestartPolicy == RestartPolicyOnFailure {
+		if c.state.ExitCode == 0 {
+			return false, nil
+		}
+
+		// If we don't have a max retries set, continue
+		if c.config.RestartRetries > 0 {
+			if c.state.RestartCount < c.config.RestartRetries {
+				logrus.Debugf("Container %s restart policy trigger: on retry %d (of %d)",
+					c.ID(), c.state.RestartCount, c.config.RestartRetries)
+			} else {
+				logrus.Debugf("Container %s restart policy trigger: retries exhausted")
+				return false, nil
+			}
+		}
+	}
+
 	logrus.Debugf("Restarting container %s due to restart policy %s", c.ID(), c.config.RestartPolicy)
 
 	// Need to check if dependencies are alive.
 	if err = c.checkDependenciesAndHandleError(ctx); err != nil {
-		return err
+		return false, err
 	}
 
 	// Is the container running again?
 	// If so, we don't have to do anything
 	if c.state.State == ContainerStateRunning || c.state.State == ContainerStatePaused {
-		return nil
+		return false, nil
 	} else if c.state.State == ContainerStateUnknown {
-		return errors.Wrapf(ErrInternal, "invalid container state encountered in restart attempt!")
+		return false, errors.Wrapf(ErrInternal, "invalid container state encountered in restart attempt!")
 	}
 
 	c.newContainerEvent(events.Restart)
@@ -235,7 +262,7 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (err error) {
 	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
+		return false, err
 	}
 
 	defer func() {
@@ -246,22 +273,25 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (err error) {
 		}
 	}()
 	if err := c.prepare(); err != nil {
-		return err
+		return false, err
 	}
 
 	if c.state.State == ContainerStateStopped {
 		// Reinitialize the container if we need to
 		if err := c.reinit(ctx, true); err != nil {
-			return err
+			return false, err
 		}
 	} else if c.state.State == ContainerStateConfigured ||
 		c.state.State == ContainerStateExited {
 		// Initialize the container
 		if err := c.init(ctx, true); err != nil {
-			return err
+			return false, err
 		}
 	}
-	return c.start()
+	if err := c.start(); err != nil {
+		return false, err
+	}
+	return true, nil
 }
 
 // Sync this container with on-disk state and runtime status
-- 
cgit v1.2.3-54-g00ecf


From 4d348d7839b7d018435cd4db6e82826269d8b399 Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Fri, 3 May 2019 10:43:14 -0400
Subject: Fix 'restart' event in journald

Ensure that we can decode the restart event with the new journald
events.

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 libpod/events/events.go | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'libpod')

diff --git a/libpod/events/events.go b/libpod/events/events.go
index 202c9db4e..650a47bfb 100644
--- a/libpod/events/events.go
+++ b/libpod/events/events.go
@@ -144,6 +144,8 @@ func StringToStatus(name string) (Status, error) {
 		return Remove, nil
 	case Renumber.String():
 		return Renumber, nil
+	case Restart.String():
+		return Restart, nil
 	case Restore.String():
 		return Restore, nil
 	case Save.String():
-- 
cgit v1.2.3-54-g00ecf


From 5c4fefa5332f56b2a5f0dd8b30bf495e44a479c0 Mon Sep 17 00:00:00 2001
From: Matthew Heon <matthew.heon@pm.me>
Date: Fri, 3 May 2019 11:42:34 -0400
Subject: Small code fix

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
---
 libpod/container_internal.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'libpod')

diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 0dadcd1ca..0b4e5763e 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -235,7 +235,7 @@ func (c *Container) handleRestartPolicy(ctx context.Context) (restarted bool, er
 				logrus.Debugf("Container %s restart policy trigger: on retry %d (of %d)",
 					c.ID(), c.state.RestartCount, c.config.RestartRetries)
 			} else {
-				logrus.Debugf("Container %s restart policy trigger: retries exhausted")
+				logrus.Debugf("Container %s restart policy trigger: retries exhausted", c.ID())
 				return false, nil
 			}
 		}
-- 
cgit v1.2.3-54-g00ecf