docker_api_exec_test.go 9.9 KB

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