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