docker_api_exec_test.go 9.3 KB

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