docker_api_exec_test.go 9.3 KB

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