docker_api_exec_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package main
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os"
  10. "strings"
  11. "testing"
  12. "time"
  13. "github.com/docker/docker/api/types"
  14. "github.com/docker/docker/client"
  15. "github.com/docker/docker/integration-cli/checker"
  16. "github.com/docker/docker/integration-cli/cli"
  17. "github.com/docker/docker/testutil"
  18. "github.com/docker/docker/testutil/request"
  19. "gotest.tools/v3/assert"
  20. is "gotest.tools/v3/assert/cmp"
  21. "gotest.tools/v3/poll"
  22. )
  23. // Regression test for #9414
  24. func (s *DockerAPISuite) TestExecAPICreateNoCmd(c *testing.T) {
  25. name := "exec_test"
  26. cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
  27. res, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": nil}))
  28. assert.NilError(c, err)
  29. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  30. b, err := request.ReadBody(body)
  31. assert.NilError(c, err)
  32. assert.Assert(c, strings.Contains(getErrorMessage(c, b), "No exec command specified"), "Expected message when creating exec command with no Cmd specified")
  33. }
  34. func (s *DockerAPISuite) TestExecAPICreateNoValidContentType(c *testing.T) {
  35. name := "exec_test"
  36. cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
  37. jsonData := bytes.NewBuffer(nil)
  38. if err := json.NewEncoder(jsonData).Encode(map[string]interface{}{"Cmd": nil}); err != nil {
  39. c.Fatalf("Can not encode data to json %s", err)
  40. }
  41. res, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/containers/%s/exec", name), request.RawContent(io.NopCloser(jsonData)), request.ContentType("test/plain"))
  42. assert.NilError(c, err)
  43. assert.Equal(c, res.StatusCode, http.StatusBadRequest)
  44. b, err := request.ReadBody(body)
  45. assert.NilError(c, err)
  46. assert.Assert(c, is.Contains(getErrorMessage(c, b), "unsupported Content-Type header (test/plain): must be 'application/json'"))
  47. }
  48. func (s *DockerAPISuite) TestExecAPICreateContainerPaused(c *testing.T) {
  49. // Not relevant on Windows as Windows containers cannot be paused
  50. testRequires(c, DaemonIsLinux)
  51. name := "exec_create_test"
  52. cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
  53. cli.DockerCmd(c, "pause", name)
  54. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  55. assert.NilError(c, err)
  56. defer apiClient.Close()
  57. config := types.ExecConfig{
  58. Cmd: []string{"true"},
  59. }
  60. _, err = apiClient.ContainerExecCreate(testutil.GetContext(c), name, config)
  61. assert.ErrorContains(c, err, "Container "+name+" is paused, unpause the container before exec", "Expected message when creating exec command with Container %s is paused", name)
  62. }
  63. func (s *DockerAPISuite) TestExecAPIStart(c *testing.T) {
  64. testRequires(c, DaemonIsLinux) // Uses pause/unpause but bits may be salvageable to Windows to Windows CI
  65. cli.DockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
  66. id := createExec(c, "test")
  67. startExec(c, id, http.StatusOK)
  68. var execJSON struct{ PID int }
  69. inspectExec(testutil.GetContext(c), c, id, &execJSON)
  70. assert.Assert(c, execJSON.PID > 1)
  71. id = createExec(c, "test")
  72. cli.DockerCmd(c, "stop", "test")
  73. startExec(c, id, http.StatusNotFound)
  74. cli.DockerCmd(c, "start", "test")
  75. startExec(c, id, http.StatusNotFound)
  76. // make sure exec is created before pausing
  77. id = createExec(c, "test")
  78. cli.DockerCmd(c, "pause", "test")
  79. startExec(c, id, http.StatusConflict)
  80. cli.DockerCmd(c, "unpause", "test")
  81. startExec(c, id, http.StatusOK)
  82. }
  83. func (s *DockerAPISuite) TestExecAPIStartEnsureHeaders(c *testing.T) {
  84. testRequires(c, DaemonIsLinux)
  85. cli.DockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
  86. id := createExec(c, "test")
  87. resp, _, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
  88. assert.NilError(c, err)
  89. assert.Assert(c, resp.Header.Get("Server") != "")
  90. }
  91. // #19362
  92. func (s *DockerAPISuite) TestExecAPIStartMultipleTimesError(c *testing.T) {
  93. runSleepingContainer(c, "-d", "--name", "test")
  94. execID := createExec(c, "test")
  95. startExec(c, execID, http.StatusOK)
  96. waitForExec(testutil.GetContext(c), c, execID)
  97. startExec(c, execID, http.StatusConflict)
  98. }
  99. // #20638
  100. func (s *DockerAPISuite) TestExecAPIStartWithDetach(c *testing.T) {
  101. name := "foo"
  102. runSleepingContainer(c, "-d", "-t", "--name", name)
  103. ctx := testutil.GetContext(c)
  104. config := types.ExecConfig{
  105. Cmd: []string{"true"},
  106. AttachStderr: true,
  107. }
  108. apiClient, err := client.NewClientWithOpts(client.FromEnv)
  109. assert.NilError(c, err)
  110. defer apiClient.Close()
  111. createResp, err := apiClient.ContainerExecCreate(ctx, name, config)
  112. assert.NilError(c, err)
  113. _, body, err := request.Post(ctx, fmt.Sprintf("/exec/%s/start", createResp.ID), request.RawString(`{"Detach": true}`), request.JSON)
  114. assert.NilError(c, err)
  115. b, err := request.ReadBody(body)
  116. comment := fmt.Sprintf("response body: %s", b)
  117. assert.NilError(c, err, comment)
  118. resp, _, err := request.Get(ctx, "/_ping")
  119. assert.NilError(c, err)
  120. if resp.StatusCode != http.StatusOK {
  121. c.Fatal("daemon is down, it should alive")
  122. }
  123. }
  124. // #30311
  125. func (s *DockerAPISuite) TestExecAPIStartValidCommand(c *testing.T) {
  126. name := "exec_test"
  127. cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
  128. id := createExecCmd(c, name, "true")
  129. startExec(c, id, http.StatusOK)
  130. ctx := testutil.GetContext(c)
  131. waitForExec(ctx, c, id)
  132. var inspectJSON struct{ ExecIDs []string }
  133. inspectContainer(ctx, c, name, &inspectJSON)
  134. assert.Assert(c, inspectJSON.ExecIDs == nil)
  135. }
  136. // #30311
  137. func (s *DockerAPISuite) TestExecAPIStartInvalidCommand(c *testing.T) {
  138. name := "exec_test"
  139. cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
  140. id := createExecCmd(c, name, "invalid")
  141. startExec(c, id, http.StatusBadRequest)
  142. ctx := testutil.GetContext(c)
  143. waitForExec(ctx, c, id)
  144. var inspectJSON struct{ ExecIDs []string }
  145. inspectContainer(ctx, c, name, &inspectJSON)
  146. assert.Assert(c, inspectJSON.ExecIDs == nil)
  147. }
  148. func (s *DockerAPISuite) TestExecStateCleanup(c *testing.T) {
  149. testRequires(c, DaemonIsLinux, testEnv.IsLocalDaemon)
  150. // This test checks accidental regressions. Not part of stable API.
  151. name := "exec_cleanup"
  152. cid := cli.DockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh").Stdout()
  153. cid = strings.TrimSpace(cid)
  154. stateDir := "/var/run/docker/containerd/" + cid
  155. checkReadDir := func(c *testing.T) (interface{}, string) {
  156. fi, err := os.ReadDir(stateDir)
  157. assert.NilError(c, err)
  158. return len(fi), ""
  159. }
  160. fi, err := os.ReadDir(stateDir)
  161. assert.NilError(c, err)
  162. assert.Assert(c, len(fi) > 1)
  163. id := createExecCmd(c, name, "ls")
  164. startExec(c, id, http.StatusOK)
  165. ctx := testutil.GetContext(c)
  166. waitForExec(ctx, c, id)
  167. poll.WaitOn(c, pollCheck(c, checkReadDir, checker.Equals(len(fi))), poll.WithTimeout(5*time.Second))
  168. id = createExecCmd(c, name, "invalid")
  169. startExec(c, id, http.StatusBadRequest)
  170. waitForExec(ctx, c, id)
  171. poll.WaitOn(c, pollCheck(c, checkReadDir, checker.Equals(len(fi))), poll.WithTimeout(5*time.Second))
  172. cli.DockerCmd(c, "stop", name)
  173. _, err = os.Stat(stateDir)
  174. assert.ErrorContains(c, err, "")
  175. assert.Assert(c, os.IsNotExist(err))
  176. }
  177. func createExec(c *testing.T, name string) string {
  178. return createExecCmd(c, name, "true")
  179. }
  180. func createExecCmd(c *testing.T, name string, cmd string) string {
  181. _, reader, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/containers/%s/exec", name), request.JSONBody(map[string]interface{}{"Cmd": []string{cmd}}))
  182. assert.NilError(c, err)
  183. b, err := io.ReadAll(reader)
  184. assert.NilError(c, err)
  185. defer reader.Close()
  186. createResp := struct {
  187. ID string `json:"Id"`
  188. }{}
  189. assert.NilError(c, json.Unmarshal(b, &createResp), string(b))
  190. return createResp.ID
  191. }
  192. func startExec(c *testing.T, id string, code int) {
  193. resp, body, err := request.Post(testutil.GetContext(c), fmt.Sprintf("/exec/%s/start", id), request.RawString(`{"Detach": true}`), request.JSON)
  194. assert.NilError(c, err)
  195. b, err := request.ReadBody(body)
  196. assert.NilError(c, err, "response body: %s", b)
  197. assert.Equal(c, resp.StatusCode, code, "response body: %s", b)
  198. }
  199. func inspectExec(ctx context.Context, c *testing.T, id string, out interface{}) {
  200. resp, body, err := request.Get(ctx, fmt.Sprintf("/exec/%s/json", id))
  201. assert.NilError(c, err)
  202. defer body.Close()
  203. assert.Equal(c, resp.StatusCode, http.StatusOK)
  204. err = json.NewDecoder(body).Decode(out)
  205. assert.NilError(c, err)
  206. }
  207. func waitForExec(ctx context.Context, c *testing.T, id string) {
  208. timeout := time.After(60 * time.Second)
  209. var execJSON struct{ Running bool }
  210. for {
  211. select {
  212. case <-timeout:
  213. c.Fatal("timeout waiting for exec to start")
  214. default:
  215. }
  216. inspectExec(ctx, c, id, &execJSON)
  217. if !execJSON.Running {
  218. break
  219. }
  220. }
  221. }
  222. func inspectContainer(ctx context.Context, c *testing.T, id string, out interface{}) {
  223. resp, body, err := request.Get(ctx, "/containers/"+id+"/json")
  224. assert.NilError(c, err)
  225. defer body.Close()
  226. assert.Equal(c, resp.StatusCode, http.StatusOK)
  227. err = json.NewDecoder(body).Decode(out)
  228. assert.NilError(c, err)
  229. }