Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[24.0 backport] liverestore: Don't remove --rm containers on restart #46869

Merged
merged 2 commits into from Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 6 additions & 3 deletions daemon/daemon.go
Expand Up @@ -472,9 +472,12 @@ func (daemon *Daemon) restore() error {
restartContainers[c] = make(chan struct{})
mapLock.Unlock()
} else if c.HostConfig != nil && c.HostConfig.AutoRemove {
mapLock.Lock()
removeContainers[c.ID] = c
mapLock.Unlock()
// Remove the container if live-restore is disabled or if the container has already exited.
if !daemon.configStore.LiveRestoreEnabled || !alive {
mapLock.Lock()
removeContainers[c.ID] = c
mapLock.Unlock()
}
}

c.Lock()
Expand Down
68 changes: 68 additions & 0 deletions integration/daemon/daemon_test.go
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/integration/internal/process"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/testutil/daemon"
"gotest.tools/v3/assert"
Expand Down Expand Up @@ -370,6 +371,73 @@ func TestLiveRestore(t *testing.T) {
skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows")

t.Run("volume references", testLiveRestoreVolumeReferences)
t.Run("autoremove", testLiveRestoreAutoRemove)
}

func testLiveRestoreAutoRemove(t *testing.T) {
skip.If(t, testEnv.IsRootless(), "restarted rootless daemon will have a new process namespace")

t.Parallel()
ctx := context.Background()

run := func(t *testing.T) (*daemon.Daemon, func(), string) {
d := daemon.New(t)
d.StartWithBusybox(t, "--live-restore", "--iptables=false")
t.Cleanup(func() {
d.Stop(t)
d.Cleanup(t)
})

tmpDir := t.TempDir()

apiClient := d.NewClientT(t)

cID := container.Run(ctx, t, apiClient,
container.WithBind(tmpDir, "/v"),
// Run until a 'stop' file is created.
container.WithCmd("sh", "-c", "while [ ! -f /v/stop ]; do sleep 0.1; done"),
container.WithAutoRemove)
t.Cleanup(func() { apiClient.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) })
finishContainer := func() {
file, err := os.Create(filepath.Join(tmpDir, "stop"))
assert.NilError(t, err, "Failed to create 'stop' file")
file.Close()
}
return d, finishContainer, cID
}

t.Run("engine restart shouldnt kill alive containers", func(t *testing.T) {
d, finishContainer, cID := run(t)

d.Restart(t, "--live-restore", "--iptables=false")

apiClient := d.NewClientT(t)
_, err := apiClient.ContainerInspect(ctx, cID)
assert.NilError(t, err, "Container shouldn't be removed after engine restart")

finishContainer()

poll.WaitOn(t, container.IsRemoved(ctx, apiClient, cID))
})
t.Run("engine restart should remove containers that exited", func(t *testing.T) {
d, finishContainer, cID := run(t)

apiClient := d.NewClientT(t)

// Get PID of the container process.
inspect, err := apiClient.ContainerInspect(ctx, cID)
assert.NilError(t, err)
pid := inspect.State.Pid

d.Stop(t)

finishContainer()
poll.WaitOn(t, process.NotAlive(pid))

d.Start(t, "--live-restore", "--iptables=false")

poll.WaitOn(t, container.IsRemoved(ctx, apiClient, cID))
})
}

func testLiveRestoreVolumeReferences(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions integration/internal/process/wait.go
@@ -0,0 +1,17 @@
package process

import (
procpkg "github.com/docker/docker/pkg/process"
"gotest.tools/v3/poll"
)

// NotAlive verifies the process doesn't exist (finished or never started).
func NotAlive(pid int) func(log poll.LogT) poll.Result {
return func(log poll.LogT) poll.Result {
if !procpkg.Alive(pid) {
return poll.Success()
}

return poll.Continue("waiting for process to finish")
}
}