Skip to content

Commit faab7cb

Browse files
committed
Fix unless-stopped containers not restarting after podman-restart.service stop them
When `podman-restart.service` stops containers, it marks them as "stopped by user" which breaks the `unless-stopped` restart policy. Add hidden `--not-stopped-by-user` flag to prevent this, allowing `unless-stopped` containers to restart on next boot. Fixes: #28152 Fixes: https://issues.redhat.com/browse/RUN-4357 Signed-off-by: Jan Rodák <hony.com@seznam.cz>
1 parent 5685ac5 commit faab7cb

File tree

8 files changed

+96
-10
lines changed

8 files changed

+96
-10
lines changed

cmd/podman/containers/stop.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,14 @@ func stopFlags(cmd *cobra.Command) {
7373
flags.StringArrayVarP(&filters, filterFlagName, "f", []string{}, "Filter output based on conditions given")
7474
_ = cmd.RegisterFlagCompletionFunc(filterFlagName, common.AutocompletePsFilters)
7575

76+
notStoppedByUserFlagName := "not-stopped-by-user"
77+
flags.BoolVar(&stopOptions.NotStoppedByUser, notStoppedByUserFlagName, false, "Do not mark container as stopped by user")
78+
_ = flags.MarkHidden(notStoppedByUserFlagName)
79+
7680
if registry.IsRemote() {
7781
_ = flags.MarkHidden("cidfile")
7882
_ = flags.MarkHidden("ignore")
83+
_ = flags.MarkHidden(notStoppedByUserFlagName)
7984
}
8085

8186
flags.SetNormalizeFunc(utils.TimeoutAliasFlags)

contrib/systemd/system/podman-restart.service.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Type=oneshot
1010
RemainAfterExit=true
1111
Environment=LOGGING="--log-level=info"
1212
ExecStart=@@PODMAN@@ $LOGGING start --all --filter should-start-on-boot=true
13-
ExecStop=@@PODMAN@@ $LOGGING stop --all --filter should-start-on-boot=true
13+
ExecStop=@@PODMAN@@ $LOGGING stop --not-stopped-by-user --all
1414

1515
[Install]
1616
WantedBy=default.target

libpod/container_api.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,13 @@ func (c *Container) Stop() error {
283283
// manually. If timeout is 0, SIGKILL will be used immediately to kill the
284284
// container.
285285
func (c *Container) StopWithTimeout(timeout uint) (finalErr error) {
286+
return c.StopWithArgs(timeout, false)
287+
}
288+
289+
// StopWithArgs is a version of Stop that allows a timeout to be specified manually
290+
// and controls whether to set the StoppedByUser state field. If timeout is 0,
291+
// SIGKILL will be used immediately to kill the container.
292+
func (c *Container) StopWithArgs(timeout uint, notStoppedByUser bool) (finalErr error) {
286293
// Have to lock the pod the container is a part of.
287294
// This prevents running `podman stop` at the same time a
288295
// `podman pod start` is running, which could lead to weird races.
@@ -320,8 +327,7 @@ func (c *Container) StopWithTimeout(timeout uint) (finalErr error) {
320327
return err
321328
}
322329
}
323-
324-
return c.stop(timeout)
330+
return c.stopInternal(timeout, notStoppedByUser)
325331
}
326332

327333
// Kill sends a signal to a container

libpod/container_internal.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1373,6 +1373,12 @@ func (c *Container) stopWithAll() bool {
13731373

13741374
// Internal, non-locking function to stop container
13751375
func (c *Container) stop(timeout uint) error {
1376+
return c.stopInternal(timeout, false)
1377+
}
1378+
1379+
// Internal, non-locking function to stop container
1380+
// notStoppedByUser controls whether to set the StoppedByUser state field.
1381+
func (c *Container) stopInternal(timeout uint, notStoppedByUser bool) error {
13761382
logrus.Debugf("Stopping ctr %s (timeout %d)", c.ID(), timeout)
13771383

13781384
all := c.stopWithAll()
@@ -1390,7 +1396,10 @@ func (c *Container) stop(timeout uint) error {
13901396
cannotStopErr = fmt.Errorf("can only stop created or running containers. %s is in state %s: %w", c.ID(), c.state.State.String(), define.ErrCtrStateInvalid)
13911397
}
13921398

1393-
c.state.StoppedByUser = true
1399+
if !notStoppedByUser {
1400+
c.state.StoppedByUser = true
1401+
}
1402+
13941403
if cannotStopErr == nil {
13951404
// Set the container state to "stopping" and unlock the container
13961405
// before handing it over to conmon to unblock other commands. #8501

pkg/domain/entities/containers.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ type StopOptions struct {
101101
Ignore bool
102102
Latest bool
103103
Timeout *uint
104+
// NotStoppedByUser specifies that the container should not be marked as
105+
// stopped by user. This is used internally by systemd units and should not
106+
// be exposed via the REST API. When set to true, the StoppedByUser field will
107+
// not be set when stopping the container.
108+
NotStoppedByUser bool
104109
}
105110

106111
type StopReport struct {

pkg/domain/infra/abi/containers.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,13 +318,12 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
318318
}
319319

320320
errMap, err := parallelctr.ContainerOp(ctx, libpodContainers, func(c *libpod.Container) error {
321-
var err error
321+
timeout := c.StopTimeout()
322322
if options.Timeout != nil {
323-
err = c.StopWithTimeout(*options.Timeout)
324-
} else {
325-
err = c.Stop()
323+
timeout = *options.Timeout
326324
}
327-
if err != nil {
325+
326+
if err := c.StopWithArgs(timeout, options.NotStoppedByUser); err != nil {
328327
switch {
329328
case errors.Is(err, define.ErrCtrStopped):
330329
logrus.Debugf("Container %s is already stopped", c.ID())
@@ -359,7 +358,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin
359358
}
360359
}
361360
} else {
362-
if err = c.Cleanup(ctx, false); err != nil {
361+
if err := c.Cleanup(ctx, false); err != nil {
363362
// The container could still have been removed, as we unlocked
364363
// after we stopped it.
365364
if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) {

test/e2e/ps_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,4 +1117,43 @@ var _ = Describe("Podman ps", func() {
11171117
Expect(output).ToNot(ContainSubstring("test-unless-stopped-exit-bad"))
11181118
Expect(output).ToNot(ContainSubstring("test-always-exit-bad"))
11191119
})
1120+
1121+
It("podman ps filter should-start-on-boot with --not-stopped-by-user", func() {
1122+
SkipIfRemote("--not-stopped-by-user flag is not supported on remote")
1123+
1124+
commands := [][]string{
1125+
{"create", "--restart", "unless-stopped", "--name", "test-unless-stopped-not-user-stop", ALPINE, "top"},
1126+
{"create", "--restart", "always", "--name", "test-always-not-user-stop", ALPINE, "top"},
1127+
{"create", "--restart", "no", "--name", "test-no-restart-not-user-stop", ALPINE, "top"},
1128+
{"create", "--restart", "on-failure", "--name", "test-onfailure-not-user-stop", ALPINE, "top"},
1129+
{"start", "test-unless-stopped-not-user-stop"},
1130+
{"start", "test-always-not-user-stop"},
1131+
{"start", "test-no-restart-not-user-stop"},
1132+
{"start", "test-onfailure-not-user-stop"},
1133+
{"stop", "--not-stopped-by-user", "test-unless-stopped-not-user-stop"},
1134+
{"stop", "--not-stopped-by-user", "test-always-not-user-stop"},
1135+
{"stop", "--not-stopped-by-user", "test-no-restart-not-user-stop"},
1136+
{"stop", "--not-stopped-by-user", "test-onfailure-not-user-stop"},
1137+
}
1138+
1139+
for _, cmd := range commands {
1140+
podmanTest.PodmanExitCleanly(cmd...)
1141+
}
1142+
1143+
session := podmanTest.PodmanExitCleanly("ps", "-a", "--filter", "should-start-on-boot=true", "--format", "{{.Names}}")
1144+
output := session.OutputToString()
1145+
1146+
Expect(output).To(ContainSubstring("test-unless-stopped-not-user-stop"))
1147+
Expect(output).To(ContainSubstring("test-always-not-user-stop"))
1148+
Expect(output).ToNot(ContainSubstring("test-no-restart-not-user-stop"))
1149+
Expect(output).ToNot(ContainSubstring("test-onfailure-not-user-stop"))
1150+
1151+
session = podmanTest.PodmanExitCleanly("ps", "-a", "--filter", "should-start-on-boot=false", "--format", "{{.Names}}")
1152+
output = session.OutputToString()
1153+
1154+
Expect(output).To(ContainSubstring("test-no-restart-not-user-stop"))
1155+
Expect(output).To(ContainSubstring("test-onfailure-not-user-stop"))
1156+
Expect(output).ToNot(ContainSubstring("test-unless-stopped-not-user-stop"))
1157+
Expect(output).ToNot(ContainSubstring("test-always-not-user-stop"))
1158+
})
11201159
})

test/e2e/stop_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,4 +407,27 @@ var _ = Describe("Podman stop", func() {
407407
Expect(session1).Should(ExitCleanly())
408408
Expect(session1.OutputToString()).To(BeEquivalentTo(cid2))
409409
})
410+
411+
It("podman stop --not-stopped-by-user sets StoppedByUser to false", func() {
412+
SkipIfRemote("--not-stopped-by-user flag is not supported on remote")
413+
containerName := "test-not-stopped-by-user"
414+
podmanTest.PodmanExitCleanly("run", "-d", "--name", containerName, ALPINE, "top")
415+
416+
podmanTest.PodmanExitCleanly("stop", "--not-stopped-by-user", containerName)
417+
418+
data := podmanTest.InspectContainer(containerName)
419+
Expect(data).To(HaveLen(1))
420+
Expect(data[0].State.StoppedByUser).To(BeFalse())
421+
})
422+
423+
It("podman stop without --not-stopped-by-user flag sets StoppedByUser to true", func() {
424+
containerName := "test-default-stop"
425+
podmanTest.PodmanExitCleanly("run", "-d", "--name", containerName, ALPINE, "top")
426+
427+
podmanTest.PodmanExitCleanly("stop", containerName)
428+
429+
data := podmanTest.InspectContainer(containerName)
430+
Expect(data).To(HaveLen(1))
431+
Expect(data[0].State.StoppedByUser).To(BeTrue())
432+
})
410433
})

0 commit comments

Comments
 (0)