Skip to content

Commit

Permalink
Allow containers to --restart on-failure with --rm
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel J Walsh <[email protected]>
  • Loading branch information
rhatdan committed Nov 20, 2020
1 parent 864fe21 commit dc8996e
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 25 deletions.
2 changes: 1 addition & 1 deletion cmd/podman/common/createparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
// by validate must not need any state information on the flag (i.e. changed)
func (c *ContainerCLIOpts) validate() error {
var ()
if c.Rm && c.Restart != "" && c.Restart != "no" {
if c.Rm && (c.Restart != "" && c.Restart != "no" && c.Restart != "on-failure") {
return errors.Errorf(`the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"`)
}

Expand Down
14 changes: 14 additions & 0 deletions libpod/container_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -714,3 +714,17 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti
defer c.newContainerEvent(events.Restore)
return c.restore(ctx, options)
}

// Indicate whether or not the container should restart
func (c *Container) ShouldRestart(ctx context.Context) bool {
logrus.Debugf("Checking if container %s should restart", c.ID())
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()

if err := c.syncContainer(); err != nil {
return false
}
}
return c.shouldRestart()
}
28 changes: 15 additions & 13 deletions libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,37 +206,39 @@ 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) (_ bool, retErr error) {
// If we did not get a restart policy match, exit immediately.
func (c *Container) shouldRestart() bool {
// If we did not get a restart policy match, return false
// 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
return false
}

// 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
return false
}

// 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", c.ID())
return false, nil
if c.state.RestartCount >= c.config.RestartRetries {
return false
}
}
}
return true
}

// 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) (_ bool, retErr error) {
if !c.shouldRestart() {
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.
Expand Down
24 changes: 24 additions & 0 deletions pkg/api/handlers/libpod/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,27 @@ func InitContainer(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusNoContent, "")
}

func ShouldRestart(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
// Now use the ABI implementation to prevent us from having duplicate
// code.
containerEngine := abi.ContainerEngine{Libpod: runtime}

name := utils.GetName(r)
report, err := containerEngine.ShouldRestart(r.Context(), name)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.ContainerNotFound(w, name, err)
return
}
utils.InternalServerError(w, err)
return

}
if report.Value {
utils.WriteResponse(w, http.StatusNoContent, "")
} else {
utils.ContainerNotFound(w, name, define.ErrNoSuchCtr)
}
}
12 changes: 12 additions & 0 deletions pkg/bindings/containers/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,15 @@ func ContainerInit(ctx context.Context, nameOrID string) error {
}
return response.Process(nil)
}

func ShouldRestart(ctx context.Context, nameOrID string) (bool, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return false, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/shouldrestart", nil, nil, nameOrID)
if err != nil {
return false, err
}
return response.IsSuccess(), nil
}
15 changes: 13 additions & 2 deletions pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
} else {
report.ExitCode = int(ecode)
}
if opts.Rm {
if opts.Rm && !ctr.ShouldRestart(ctx) {
if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr ||
errors.Cause(err) == define.ErrCtrRemoved {
Expand Down Expand Up @@ -992,7 +992,7 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st
return []*entities.ContainerCleanupReport{}, nil
}

if options.Remove {
if options.Remove && !ctr.ShouldRestart(ctx) {
err = ic.Libpod.RemoveContainer(ctx, ctr, false, true)
if err != nil {
report.RmErr = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID())
Expand All @@ -1015,6 +1015,7 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st
_, err = ic.Libpod.RemoveImage(ctx, ctrImage, false)
report.RmiErr = err
}

reports = append(reports, &report)
}
return reports, nil
Expand Down Expand Up @@ -1314,3 +1315,13 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri

return statsChan, nil
}

// ShouldRestart returns whether the container should be restarted
func (ic *ContainerEngine) ShouldRestart(ctx context.Context, nameOrID string) (*entities.BoolReport, error) {
ctr, err := ic.Libpod.LookupContainer(nameOrID)
if err != nil {
return nil, err
}

return &entities.BoolReport{Value: ctr.ShouldRestart(ctx)}, nil
}
25 changes: 19 additions & 6 deletions pkg/domain/infra/tunnel/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,12 +595,20 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
// Defer the removal, so we can return early if needed and
// de-spaghetti the code.
defer func() {
if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil {
if errorhandling.Contains(err, define.ErrNoSuchCtr) ||
errorhandling.Contains(err, define.ErrCtrRemoved) {
logrus.Warnf("Container %s does not exist: %v", con.ID, err)
} else {
logrus.Errorf("Error removing container %s: %v", con.ID, err)
shouldRestart, err := containers.ShouldRestart(ic.ClientCxt, con.ID)
if err != nil {
logrus.Errorf("Failed to check if %s should restart: %v", con.ID, err)
return
}

if !shouldRestart {
if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil {
if errorhandling.Contains(err, define.ErrNoSuchCtr) ||
errorhandling.Contains(err, define.ErrCtrRemoved) {
logrus.Warnf("Container %s does not exist: %v", con.ID, err)
} else {
logrus.Errorf("Error removing container %s: %v", con.ID, err)
}
}
}
}()
Expand Down Expand Up @@ -737,3 +745,8 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
}
return containers.Stats(ic.ClientCxt, namesOrIds, &options.Stream)
}

// ShouldRestart reports back whether the containre will restart
func (ic *ContainerEngine) ShouldRestart(_ context.Context, id string) (bool, error) {
return containers.ShouldRestart(ic.ClientCxt, id)
}
4 changes: 1 addition & 3 deletions test/e2e/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,9 @@ var _ = Describe("Podman run", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))

// the --rm option conflicts with --restart, when the restartPolicy is not "" and "no"
// so the exitCode should not equal 0
session = podmanTest.Podman([]string{"run", "--rm", "--restart", "on-failure", ALPINE})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
Expect(session.ExitCode()).To(Equal(0))

session = podmanTest.Podman([]string{"run", "--rm", "--restart", "always", ALPINE})
session.WaitWithDefaultTimeout()
Expand Down

0 comments on commit dc8996e

Please sign in to comment.