Browse code

Make exec start return proper error codes

Exec start was sending HTTP 500 for every error.

Fixed an error where pausing a container and then calling exec start
caused the daemon to freeze.

Updated API docs which incorrectly showed that a successful exec start
was an HTTP 201, in reality it is HTTP 200.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2015/09/12 11:50:21
Showing 12 changed files
... ...
@@ -75,6 +75,10 @@ func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWrit
75 75
 		return err
76 76
 	}
77 77
 
78
+	if exists, err := s.daemon.ExecExists(execName); !exists {
79
+		return err
80
+	}
81
+
78 82
 	if !execStartCheck.Detach {
79 83
 		var err error
80 84
 		// Setting up the streaming http interface.
... ...
@@ -102,6 +106,9 @@ func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWrit
102 102
 
103 103
 	// Now run the user process in container.
104 104
 	if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
105
+		if execStartCheck.Detach {
106
+			return err
107
+		}
105 108
 		fmt.Fprintf(outStream, "Error running exec in container: %v\n", err)
106 109
 	}
107 110
 	return nil
... ...
@@ -92,8 +92,19 @@ func (d *Daemon) registerExecCommand(ExecConfig *ExecConfig) {
92 92
 	d.execCommands.Add(ExecConfig.ID, ExecConfig)
93 93
 }
94 94
 
95
+// ExecExists looks up the exec instance and returns a bool if it exists or not.
96
+// It will also return the error produced by `getExecConfig`
97
+func (d *Daemon) ExecExists(name string) (bool, error) {
98
+	if _, err := d.getExecConfig(name); err != nil {
99
+		return false, err
100
+	}
101
+	return true, nil
102
+}
103
+
104
+// getExecConfig looks up the exec instance by name. If the container associated
105
+// with the exec instance is stopped or paused, it will return an error.
95 106
 func (d *Daemon) getExecConfig(name string) (*ExecConfig, error) {
96
-	ExecConfig := d.execCommands.Get(name)
107
+	ec := d.execCommands.Get(name)
97 108
 
98 109
 	// If the exec is found but its container is not in the daemon's list of
99 110
 	// containers then it must have been delete, in which case instead of
... ...
@@ -101,12 +112,14 @@ func (d *Daemon) getExecConfig(name string) (*ExecConfig, error) {
101 101
 	// the user sees the same error now that they will after the
102 102
 	// 5 minute clean-up loop is run which erases old/dead execs.
103 103
 
104
-	if ExecConfig != nil && d.containers.Get(ExecConfig.Container.ID) != nil {
105
-
106
-		if !ExecConfig.Container.IsRunning() {
107
-			return nil, derr.ErrorCodeContainerNotRunning.WithArgs(ExecConfig.Container.ID)
104
+	if ec != nil && d.containers.Get(ec.Container.ID) != nil {
105
+		if !ec.Container.IsRunning() {
106
+			return nil, derr.ErrorCodeContainerNotRunning.WithArgs(ec.Container.ID, ec.Container.State.String())
107
+		}
108
+		if ec.Container.isPaused() {
109
+			return nil, derr.ErrorCodeExecPaused.WithArgs(ec.Container.ID)
108 110
 		}
109
-		return ExecConfig, nil
111
+		return ec, nil
110 112
 	}
111 113
 
112 114
 	return nil, derr.ErrorCodeNoExecID.WithArgs(name)
... ...
@@ -181,35 +194,30 @@ func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, erro
181 181
 
182 182
 // ContainerExecStart starts a previously set up exec instance. The
183 183
 // std streams are set up.
184
-func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error {
184
+func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error {
185 185
 	var (
186 186
 		cStdin           io.ReadCloser
187 187
 		cStdout, cStderr io.Writer
188 188
 	)
189 189
 
190
-	ExecConfig, err := d.getExecConfig(execName)
190
+	ec, err := d.getExecConfig(name)
191 191
 	if err != nil {
192
-		return err
192
+		return derr.ErrorCodeNoExecID.WithArgs(name)
193 193
 	}
194 194
 
195
-	func() {
196
-		ExecConfig.Lock()
197
-		defer ExecConfig.Unlock()
198
-		if ExecConfig.Running {
199
-			err = derr.ErrorCodeExecRunning.WithArgs(execName)
200
-		}
201
-		ExecConfig.Running = true
202
-	}()
203
-	if err != nil {
204
-		return err
195
+	ec.Lock()
196
+	if ec.Running {
197
+		ec.Unlock()
198
+		return derr.ErrorCodeExecRunning.WithArgs(ec.ID)
205 199
 	}
200
+	ec.Running = true
201
+	ec.Unlock()
206 202
 
207
-	logrus.Debugf("starting exec command %s in container %s", ExecConfig.ID, ExecConfig.Container.ID)
208
-	container := ExecConfig.Container
209
-
210
-	container.logEvent("exec_start: " + ExecConfig.ProcessConfig.Entrypoint + " " + strings.Join(ExecConfig.ProcessConfig.Arguments, " "))
203
+	logrus.Debugf("starting exec command %s in container %s", ec.ID, ec.Container.ID)
204
+	container := ec.Container
205
+	container.logEvent("exec_start: " + ec.ProcessConfig.Entrypoint + " " + strings.Join(ec.ProcessConfig.Arguments, " "))
211 206
 
212
-	if ExecConfig.OpenStdin {
207
+	if ec.OpenStdin {
213 208
 		r, w := io.Pipe()
214 209
 		go func() {
215 210
 			defer w.Close()
... ...
@@ -218,23 +226,23 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout
218 218
 		}()
219 219
 		cStdin = r
220 220
 	}
221
-	if ExecConfig.OpenStdout {
221
+	if ec.OpenStdout {
222 222
 		cStdout = stdout
223 223
 	}
224
-	if ExecConfig.OpenStderr {
224
+	if ec.OpenStderr {
225 225
 		cStderr = stderr
226 226
 	}
227 227
 
228
-	ExecConfig.streamConfig.stderr = broadcastwriter.New()
229
-	ExecConfig.streamConfig.stdout = broadcastwriter.New()
228
+	ec.streamConfig.stderr = broadcastwriter.New()
229
+	ec.streamConfig.stdout = broadcastwriter.New()
230 230
 	// Attach to stdin
231
-	if ExecConfig.OpenStdin {
232
-		ExecConfig.streamConfig.stdin, ExecConfig.streamConfig.stdinPipe = io.Pipe()
231
+	if ec.OpenStdin {
232
+		ec.streamConfig.stdin, ec.streamConfig.stdinPipe = io.Pipe()
233 233
 	} else {
234
-		ExecConfig.streamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
234
+		ec.streamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
235 235
 	}
236 236
 
237
-	attachErr := attach(&ExecConfig.streamConfig, ExecConfig.OpenStdin, true, ExecConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr)
237
+	attachErr := attach(&ec.streamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr)
238 238
 
239 239
 	execErr := make(chan error)
240 240
 
... ...
@@ -243,8 +251,8 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout
243 243
 	// the exitStatus) even after the cmd is done running.
244 244
 
245 245
 	go func() {
246
-		if err := container.exec(ExecConfig); err != nil {
247
-			execErr <- derr.ErrorCodeExecCantRun.WithArgs(execName, container.ID, err)
246
+		if err := container.exec(ec); err != nil {
247
+			execErr <- derr.ErrorCodeExecCantRun.WithArgs(ec.ID, container.ID, err)
248 248
 		}
249 249
 	}()
250 250
 	select {
... ...
@@ -90,6 +90,7 @@ list of DNS options to be used in the container.
90 90
 * `GET /events` now includes a `timenano` field, in addition to the existing `time` field.
91 91
 * `GET /info` now lists engine version information.
92 92
 * `GET /containers/json` will return `ImageID` of the image used by container.
93
+* `POST /exec/(name)/start` will now return an HTTP 409 when the container is either stopped or paused.
93 94
 
94 95
 ### v1.20 API changes
95 96
 
... ...
@@ -1677,7 +1677,7 @@ Json Parameters:
1677 1677
 
1678 1678
 Status Codes:
1679 1679
 
1680
--   **201** – no error
1680
+-   **200** – no error
1681 1681
 -   **404** – no such exec instance
1682 1682
 
1683 1683
     **Stream details**:
... ...
@@ -1638,7 +1638,7 @@ Json Parameters:
1638 1638
 
1639 1639
 Status Codes:
1640 1640
 
1641
--   **201** – no error
1641
+-   **200** – no error
1642 1642
 -   **404** – no such exec instance
1643 1643
 
1644 1644
     **Stream details**:
... ...
@@ -1801,7 +1801,7 @@ Json Parameters:
1801 1801
 
1802 1802
 Status Codes:
1803 1803
 
1804
--   **201** – no error
1804
+-   **200** – no error
1805 1805
 -   **404** – no such exec instance
1806 1806
 
1807 1807
     **Stream details**:
... ...
@@ -1922,7 +1922,7 @@ Json Parameters:
1922 1922
 
1923 1923
 Status Codes:
1924 1924
 
1925
--   **201** – no error
1925
+-   **200** – no error
1926 1926
 -   **404** – no such exec instance
1927 1927
 
1928 1928
     **Stream details**:
... ...
@@ -1984,7 +1984,7 @@ Json Parameters:
1984 1984
 
1985 1985
 Status Codes:
1986 1986
 
1987
--   **201** – no error
1987
+-   **200** – no error
1988 1988
 -   **404** – no such exec instance
1989 1989
 
1990 1990
     **Stream details**:
... ...
@@ -2126,7 +2126,7 @@ Json Parameters:
2126 2126
 
2127 2127
 Status Codes:
2128 2128
 
2129
--   **201** – no error
2129
+-   **200** – no error
2130 2130
 -   **404** – no such exec instance
2131 2131
 
2132 2132
     **Stream details**:
... ...
@@ -2226,8 +2226,9 @@ Json Parameters:
2226 2226
 
2227 2227
 Status Codes:
2228 2228
 
2229
--   **201** – no error
2229
+-   **200** – no error
2230 2230
 -   **404** – no such exec instance
2231
+-   **409** - container is stopped or paused
2231 2232
 
2232 2233
     **Stream details**:
2233 2234
     Similar to the stream behavior of `POST /container/(id)/attach` API
... ...
@@ -654,7 +654,7 @@ var (
654 654
 		Value:          "CONTAINERNOTRUNNING",
655 655
 		Message:        "Container %s is not running: %s",
656 656
 		Description:    "An attempt was made to retrieve the information about an 'exec' but the container is not running",
657
-		HTTPStatusCode: http.StatusInternalServerError,
657
+		HTTPStatusCode: http.StatusConflict,
658 658
 	})
659 659
 
660 660
 	// ErrorCodeNoExecID is generated when we try to get the info
... ...
@@ -672,7 +672,7 @@ var (
672 672
 		Value:          "EXECPAUSED",
673 673
 		Message:        "Container %s is paused, unpause the container before exec",
674 674
 		Description:    "An attempt to start an 'exec' was made, but the owning container is paused",
675
-		HTTPStatusCode: http.StatusInternalServerError,
675
+		HTTPStatusCode: http.StatusConflict,
676 676
 	})
677 677
 
678 678
 	// ErrorCodeExecRunning is generated when we try to start an exec
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"encoding/json"
8 8
 	"fmt"
9 9
 	"net/http"
10
+	"strings"
10 11
 
11 12
 	"github.com/go-check/check"
12 13
 )
... ...
@@ -47,3 +48,44 @@ func (s *DockerSuite) TestExecApiCreateNoValidContentType(c *check.C) {
47 47
 		c.Fatalf("Expected message when creating exec command with invalid Content-Type specified")
48 48
 	}
49 49
 }
50
+
51
+func (s *DockerSuite) TestExecAPIStart(c *check.C) {
52
+	dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
53
+
54
+	createExec := func() string {
55
+		_, b, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", "test"), map[string]interface{}{"Cmd": []string{"true"}})
56
+		c.Assert(err, check.IsNil, check.Commentf(string(b)))
57
+
58
+		createResp := struct {
59
+			ID string `json:"Id"`
60
+		}{}
61
+		c.Assert(json.Unmarshal(b, &createResp), check.IsNil, check.Commentf(string(b)))
62
+		return createResp.ID
63
+	}
64
+
65
+	startExec := func(id string, code int) {
66
+		resp, body, err := sockRequestRaw("POST", fmt.Sprintf("/exec/%s/start", id), strings.NewReader(`{"Detach": true}`), "application/json")
67
+		c.Assert(err, check.IsNil)
68
+
69
+		b, err := readBody(body)
70
+		c.Assert(err, check.IsNil, check.Commentf(string(b)))
71
+		c.Assert(resp.StatusCode, check.Equals, code, check.Commentf(string(b)))
72
+	}
73
+
74
+	startExec(createExec(), http.StatusOK)
75
+
76
+	id := createExec()
77
+	dockerCmd(c, "stop", "test")
78
+
79
+	startExec(id, http.StatusNotFound)
80
+
81
+	dockerCmd(c, "start", "test")
82
+	startExec(id, http.StatusNotFound)
83
+
84
+	// make sure exec is created before pausing
85
+	id = createExec()
86
+	dockerCmd(c, "pause", "test")
87
+	startExec(id, http.StatusConflict)
88
+	dockerCmd(c, "unpause", "test")
89
+	startExec(id, http.StatusOK)
90
+}