docker_api_exec_test.go 9.6 KB

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