docker_api_exec_test.go 9.2 KB

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