github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/container/restart_test.go (about) 1 package container // import "github.com/Prakhar-Agarwal-byte/moby/integration/container" 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "testing" 8 "time" 9 10 "github.com/Prakhar-Agarwal-byte/moby/api/types" 11 "github.com/Prakhar-Agarwal-byte/moby/api/types/container" 12 "github.com/Prakhar-Agarwal-byte/moby/api/types/events" 13 "github.com/Prakhar-Agarwal-byte/moby/api/types/filters" 14 "github.com/Prakhar-Agarwal-byte/moby/client" 15 testContainer "github.com/Prakhar-Agarwal-byte/moby/integration/internal/container" 16 "github.com/Prakhar-Agarwal-byte/moby/testutil" 17 "github.com/Prakhar-Agarwal-byte/moby/testutil/daemon" 18 "gotest.tools/v3/assert" 19 is "gotest.tools/v3/assert/cmp" 20 "gotest.tools/v3/poll" 21 "gotest.tools/v3/skip" 22 ) 23 24 func TestDaemonRestartKillContainers(t *testing.T) { 25 skip.If(t, testEnv.IsRemoteDaemon, "cannot start daemon on remote test run") 26 skip.If(t, testEnv.DaemonInfo.OSType == "windows") 27 skip.If(t, testEnv.IsRootless, "rootless mode doesn't support live-restore") 28 29 ctx := testutil.StartSpan(baseContext, t) 30 31 type testCase struct { 32 desc string 33 config *container.Config 34 hostConfig *container.HostConfig 35 36 xRunning bool 37 xRunningLiveRestore bool 38 xStart bool 39 xHealthCheck bool 40 } 41 42 for _, tc := range []testCase{ 43 { 44 desc: "container without restart policy", 45 config: &container.Config{Image: "busybox", Cmd: []string{"top"}}, 46 xRunningLiveRestore: true, 47 xStart: true, 48 }, 49 { 50 desc: "container with restart=always", 51 config: &container.Config{Image: "busybox", Cmd: []string{"top"}}, 52 hostConfig: &container.HostConfig{RestartPolicy: container.RestartPolicy{Name: "always"}}, 53 xRunning: true, 54 xRunningLiveRestore: true, 55 xStart: true, 56 }, 57 { 58 desc: "container with restart=always and with healthcheck", 59 config: &container.Config{ 60 Image: "busybox", Cmd: []string{"top"}, 61 Healthcheck: &container.HealthConfig{ 62 Test: []string{"CMD-SHELL", "sleep 1"}, 63 Interval: time.Second, 64 }, 65 }, 66 hostConfig: &container.HostConfig{RestartPolicy: container.RestartPolicy{Name: "always"}}, 67 xRunning: true, 68 xRunningLiveRestore: true, 69 xStart: true, 70 xHealthCheck: true, 71 }, 72 { 73 desc: "container created should not be restarted", 74 config: &container.Config{Image: "busybox", Cmd: []string{"top"}}, 75 hostConfig: &container.HostConfig{RestartPolicy: container.RestartPolicy{Name: "always"}}, 76 }, 77 } { 78 for _, liveRestoreEnabled := range []bool{false, true} { 79 for fnName, stopDaemon := range map[string]func(*testing.T, *daemon.Daemon){ 80 "kill-daemon": func(t *testing.T, d *daemon.Daemon) { 81 err := d.Kill() 82 assert.NilError(t, err) 83 }, 84 "stop-daemon": func(t *testing.T, d *daemon.Daemon) { 85 d.Stop(t) 86 }, 87 } { 88 tc := tc 89 liveRestoreEnabled := liveRestoreEnabled 90 stopDaemon := stopDaemon 91 t.Run(fmt.Sprintf("live-restore=%v/%s/%s", liveRestoreEnabled, tc.desc, fnName), func(t *testing.T) { 92 t.Parallel() 93 94 ctx := testutil.StartSpan(ctx, t) 95 96 d := daemon.New(t) 97 apiClient := d.NewClientT(t) 98 99 args := []string{"--iptables=false"} 100 if liveRestoreEnabled { 101 args = append(args, "--live-restore") 102 } 103 104 d.StartWithBusybox(ctx, t, args...) 105 defer d.Stop(t) 106 107 resp, err := apiClient.ContainerCreate(ctx, tc.config, tc.hostConfig, nil, nil, "") 108 assert.NilError(t, err) 109 defer apiClient.ContainerRemove(ctx, resp.ID, container.RemoveOptions{Force: true}) 110 111 if tc.xStart { 112 err = apiClient.ContainerStart(ctx, resp.ID, container.StartOptions{}) 113 assert.NilError(t, err) 114 } 115 116 stopDaemon(t, d) 117 d.Start(t, args...) 118 119 expected := tc.xRunning 120 if liveRestoreEnabled { 121 expected = tc.xRunningLiveRestore 122 } 123 124 var running bool 125 for i := 0; i < 30; i++ { 126 inspect, err := apiClient.ContainerInspect(ctx, resp.ID) 127 assert.NilError(t, err) 128 129 running = inspect.State.Running 130 if running == expected { 131 break 132 } 133 time.Sleep(2 * time.Second) 134 } 135 assert.Equal(t, expected, running, "got unexpected running state, expected %v, got: %v", expected, running) 136 137 if tc.xHealthCheck { 138 startTime := time.Now() 139 ctxPoll, cancel := context.WithTimeout(ctx, 30*time.Second) 140 defer cancel() 141 poll.WaitOn(t, pollForNewHealthCheck(ctxPoll, apiClient, startTime, resp.ID), poll.WithDelay(100*time.Millisecond)) 142 } 143 // TODO(cpuguy83): test pause states... this seems to be rather undefined currently 144 }) 145 } 146 } 147 } 148 } 149 150 func pollForNewHealthCheck(ctx context.Context, client *client.Client, startTime time.Time, containerID string) func(log poll.LogT) poll.Result { 151 return func(log poll.LogT) poll.Result { 152 inspect, err := client.ContainerInspect(ctx, containerID) 153 if err != nil { 154 return poll.Error(err) 155 } 156 healthChecksTotal := len(inspect.State.Health.Log) 157 if healthChecksTotal > 0 { 158 if inspect.State.Health.Log[healthChecksTotal-1].Start.After(startTime) { 159 return poll.Success() 160 } 161 } 162 return poll.Continue("waiting for a new container healthcheck") 163 } 164 } 165 166 // Container started with --rm should be able to be restarted. 167 // It should be removed only if killed or stopped 168 func TestContainerWithAutoRemoveCanBeRestarted(t *testing.T) { 169 ctx := setupTest(t) 170 apiClient := testEnv.APIClient() 171 172 noWaitTimeout := 0 173 174 for _, tc := range []struct { 175 desc string 176 doSth func(ctx context.Context, containerID string) error 177 }{ 178 { 179 desc: "kill", 180 doSth: func(ctx context.Context, containerID string) error { 181 return apiClient.ContainerKill(ctx, containerID, "SIGKILL") 182 }, 183 }, 184 { 185 desc: "stop", 186 doSth: func(ctx context.Context, containerID string) error { 187 return apiClient.ContainerStop(ctx, containerID, container.StopOptions{Timeout: &noWaitTimeout}) 188 }, 189 }, 190 } { 191 tc := tc 192 t.Run(tc.desc, func(t *testing.T) { 193 testutil.StartSpan(ctx, t) 194 cID := testContainer.Run(ctx, t, apiClient, 195 testContainer.WithName("autoremove-restart-and-"+tc.desc), 196 testContainer.WithAutoRemove, 197 ) 198 defer func() { 199 err := apiClient.ContainerRemove(ctx, cID, container.RemoveOptions{Force: true}) 200 if t.Failed() && err != nil { 201 t.Logf("Cleaning up test container failed with error: %v", err) 202 } 203 }() 204 205 err := apiClient.ContainerRestart(ctx, cID, container.StopOptions{Timeout: &noWaitTimeout}) 206 assert.NilError(t, err) 207 208 inspect, err := apiClient.ContainerInspect(ctx, cID) 209 assert.NilError(t, err) 210 assert.Assert(t, inspect.State.Status != "removing", "Container should not be removing yet") 211 212 poll.WaitOn(t, testContainer.IsInState(ctx, apiClient, cID, "running")) 213 214 err = tc.doSth(ctx, cID) 215 assert.NilError(t, err) 216 217 poll.WaitOn(t, testContainer.IsRemoved(ctx, apiClient, cID)) 218 }) 219 } 220 } 221 222 // TestContainerRestartWithCancelledRequest verifies that cancelling a restart 223 // request does not cancel the restart operation, and still starts the container 224 // after it was stopped. 225 // 226 // Regression test for https://github.com/moby/moby/discussions/46682 227 func TestContainerRestartWithCancelledRequest(t *testing.T) { 228 ctx := setupTest(t) 229 apiClient := testEnv.APIClient() 230 231 testutil.StartSpan(ctx, t) 232 233 // Create a container that ignores SIGTERM and doesn't stop immediately, 234 // giving us time to cancel the request. 235 // 236 // Restarting a container is "stop" (and, if needed, "kill"), then "start" 237 // the container. We're trying to create the scenario where the "stop" is 238 // handled, but the request was cancelled and therefore the "start" not 239 // taking place. 240 cID := testContainer.Run(ctx, t, apiClient, testContainer.WithCmd("sh", "-c", "trap 'echo received TERM' TERM; while true; do usleep 10; done")) 241 defer func() { 242 err := apiClient.ContainerRemove(ctx, cID, container.RemoveOptions{Force: true}) 243 if t.Failed() && err != nil { 244 t.Logf("Cleaning up test container failed with error: %v", err) 245 } 246 }() 247 248 // Start listening for events. 249 messages, errs := apiClient.Events(ctx, types.EventsOptions{ 250 Filters: filters.NewArgs( 251 filters.Arg("container", cID), 252 filters.Arg("event", string(events.ActionRestart)), 253 ), 254 }) 255 256 // Make restart request, but cancel the request before the container 257 // is (forcibly) killed. 258 ctx2, cancel := context.WithTimeout(ctx, 100*time.Millisecond) 259 stopTimeout := 1 260 err := apiClient.ContainerRestart(ctx2, cID, container.StopOptions{ 261 Timeout: &stopTimeout, 262 }) 263 assert.Check(t, is.ErrorIs(err, context.DeadlineExceeded)) 264 cancel() 265 266 // Validate that the restart event occurred, which is emitted 267 // after the restart (stop (kill) start) finished. 268 // 269 // Note that we cannot use RestartCount for this, as that's only 270 // used for restart-policies. 271 restartTimeout := 2 * time.Second 272 if runtime.GOOS == "windows" { 273 // hcs can sometimes take a long time to stop container. 274 restartTimeout = StopContainerWindowsPollTimeout 275 } 276 select { 277 case m := <-messages: 278 assert.Check(t, is.Equal(m.Actor.ID, cID)) 279 assert.Check(t, is.Equal(m.Action, events.ActionRestart)) 280 case err := <-errs: 281 assert.NilError(t, err) 282 case <-time.After(restartTimeout): 283 t.Errorf("timeout waiting for restart event") 284 } 285 286 // Container should be restarted (running). 287 inspect, err := apiClient.ContainerInspect(ctx, cID) 288 assert.NilError(t, err) 289 assert.Check(t, is.Equal(inspect.State.Status, "running")) 290 }