stop_linux_test.go 5.5 KB

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