github.com/rawahars/moby@v24.0.4+incompatible/integration/container/stop_linux_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "strconv" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/docker/docker/api/types" 14 containertypes "github.com/docker/docker/api/types/container" 15 "github.com/docker/docker/client" 16 "github.com/docker/docker/errdefs" 17 "github.com/docker/docker/integration/internal/container" 18 "github.com/docker/docker/pkg/stdcopy" 19 "gotest.tools/v3/assert" 20 is "gotest.tools/v3/assert/cmp" 21 "gotest.tools/v3/icmd" 22 "gotest.tools/v3/poll" 23 "gotest.tools/v3/skip" 24 ) 25 26 // TestStopContainerWithTimeout checks that ContainerStop with 27 // a timeout works as documented, i.e. in case of negative timeout 28 // waiting is not limited (issue #35311). 29 func TestStopContainerWithTimeout(t *testing.T) { 30 defer setupTest(t)() 31 client := testEnv.APIClient() 32 ctx := context.Background() 33 34 testCmd := container.WithCmd("sh", "-c", "sleep 2 && exit 42") 35 testData := []struct { 36 doc string 37 timeout int 38 expectedExitCode int 39 }{ 40 // In case container is forcefully killed, 137 is returned, 41 // otherwise the exit code from the above script 42 { 43 "zero timeout: expect forceful container kill", 44 0, 137, 45 }, 46 { 47 "too small timeout: expect forceful container kill", 48 1, 137, 49 }, 50 { 51 "big enough timeout: expect graceful container stop", 52 3, 42, 53 }, 54 { 55 "unlimited timeout: expect graceful container stop", 56 -1, 42, 57 }, 58 } 59 60 for _, d := range testData { 61 d := d 62 t.Run(strconv.Itoa(d.timeout), func(t *testing.T) { 63 t.Parallel() 64 id := container.Run(ctx, t, client, testCmd) 65 66 err := client.ContainerStop(ctx, id, containertypes.StopOptions{Timeout: &d.timeout}) 67 assert.NilError(t, err) 68 69 poll.WaitOn(t, container.IsStopped(ctx, client, id), 70 poll.WithDelay(100*time.Millisecond)) 71 72 inspect, err := client.ContainerInspect(ctx, id) 73 assert.NilError(t, err) 74 assert.Equal(t, inspect.State.ExitCode, d.expectedExitCode) 75 }) 76 } 77 } 78 79 func TestDeleteDevicemapper(t *testing.T) { 80 skip.If(t, testEnv.DaemonInfo.Driver != "devicemapper") 81 skip.If(t, testEnv.IsRemoteDaemon) 82 83 defer setupTest(t)() 84 client := testEnv.APIClient() 85 ctx := context.Background() 86 87 id := container.Run(ctx, t, client, container.WithName("foo-"+t.Name()), container.WithCmd("echo")) 88 89 poll.WaitOn(t, container.IsStopped(ctx, client, id), poll.WithDelay(100*time.Millisecond)) 90 91 inspect, err := client.ContainerInspect(ctx, id) 92 assert.NilError(t, err) 93 94 deviceID := inspect.GraphDriver.Data["DeviceId"] 95 96 // Find pool name from device name 97 deviceName := inspect.GraphDriver.Data["DeviceName"] 98 devicePrefix := deviceName[:strings.LastIndex(deviceName, "-")] 99 devicePool := fmt.Sprintf("/dev/mapper/%s-pool", devicePrefix) 100 101 result := icmd.RunCommand("dmsetup", "message", devicePool, "0", fmt.Sprintf("delete %s", deviceID)) 102 result.Assert(t, icmd.Success) 103 104 err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{}) 105 assert.NilError(t, err) 106 } 107 108 // TestStopContainerWithTimeoutCancel checks that ContainerStop is not cancelled 109 // if the request is cancelled. 110 // See issue https://github.com/moby/moby/issues/45731 111 func TestStopContainerWithTimeoutCancel(t *testing.T) { 112 t.Parallel() 113 defer setupTest(t)() 114 apiClient := testEnv.APIClient() 115 t.Cleanup(func() { _ = apiClient.Close() }) 116 117 ctx := context.Background() 118 id := container.Run(ctx, t, apiClient, 119 container.WithCmd("sh", "-c", "trap 'echo received TERM' TERM; while true; do usleep 10; done"), 120 ) 121 poll.WaitOn(t, container.IsInState(ctx, apiClient, id, "running")) 122 123 ctxCancel, cancel := context.WithCancel(context.Background()) 124 t.Cleanup(cancel) 125 const stopTimeout = 3 126 127 stoppedCh := make(chan error) 128 go func() { 129 sto := stopTimeout 130 stoppedCh <- apiClient.ContainerStop(ctxCancel, id, containertypes.StopOptions{Timeout: &sto}) 131 }() 132 133 poll.WaitOn(t, logsContains(ctx, apiClient, id, "received TERM")) 134 135 // Cancel the context once we verified the container was signaled, and check 136 // that the container is not killed immediately 137 cancel() 138 139 select { 140 case stoppedErr := <-stoppedCh: 141 assert.Check(t, is.ErrorType(stoppedErr, errdefs.IsCancelled)) 142 case <-time.After(5 * time.Second): 143 t.Fatal("timeout waiting for stop request to be cancelled") 144 } 145 inspect, err := apiClient.ContainerInspect(ctx, id) 146 assert.Check(t, err) 147 assert.Check(t, inspect.State.Running) 148 149 // container should be stopped after stopTimeout is reached. The daemon.containerStop 150 // code is rather convoluted, and waits another 2 seconds for the container to 151 // terminate after signaling it; 152 // https://github.com/moby/moby/blob/97455cc31ffa08078db6591f018256ed59c35bbc/daemon/stop.go#L101-L112 153 // 154 // Adding 3 seconds to the specified stopTimeout to take this into account, 155 // and add another second margin to try to avoid flakiness. 156 poll.WaitOn(t, container.IsStopped(ctx, apiClient, id), poll.WithTimeout((3+stopTimeout)*time.Second)) 157 } 158 159 // logsContains verifies the container contains the given text in the log's stdout. 160 func logsContains(ctx context.Context, client client.APIClient, containerID string, logString string) func(log poll.LogT) poll.Result { 161 return func(log poll.LogT) poll.Result { 162 logs, err := client.ContainerLogs(ctx, containerID, types.ContainerLogsOptions{ 163 ShowStdout: true, 164 }) 165 if err != nil { 166 return poll.Error(err) 167 } 168 defer logs.Close() 169 170 var stdout bytes.Buffer 171 _, err = stdcopy.StdCopy(&stdout, io.Discard, logs) 172 if err != nil { 173 return poll.Error(err) 174 } 175 if strings.Contains(stdout.String(), logString) { 176 return poll.Success() 177 } 178 return poll.Continue("waiting for logstring '%s' in container", logString) 179 } 180 }