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