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