Merge pull request #16250 from cpuguy83/15487_exec_error_codes

Make exec start return proper error codes
This commit is contained in:
Jess Frazelle 2015-10-02 12:55:17 -07:00
commit 5a43beda91
12 changed files with 102 additions and 43 deletions

View File

@ -75,6 +75,10 @@ func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWrit
return err return err
} }
if exists, err := s.daemon.ExecExists(execName); !exists {
return err
}
if !execStartCheck.Detach { if !execStartCheck.Detach {
var err error var err error
// Setting up the streaming http interface. // Setting up the streaming http interface.
@ -102,6 +106,9 @@ func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWrit
// Now run the user process in container. // Now run the user process in container.
if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil { if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
if execStartCheck.Detach {
return err
}
fmt.Fprintf(outStream, "Error running exec in container: %v\n", err) fmt.Fprintf(outStream, "Error running exec in container: %v\n", err)
} }
return nil return nil

View File

@ -92,8 +92,19 @@ func (d *Daemon) registerExecCommand(ExecConfig *ExecConfig) {
d.execCommands.Add(ExecConfig.ID, ExecConfig) d.execCommands.Add(ExecConfig.ID, ExecConfig)
} }
// ExecExists looks up the exec instance and returns a bool if it exists or not.
// It will also return the error produced by `getExecConfig`
func (d *Daemon) ExecExists(name string) (bool, error) {
if _, err := d.getExecConfig(name); err != nil {
return false, err
}
return true, nil
}
// getExecConfig looks up the exec instance by name. If the container associated
// with the exec instance is stopped or paused, it will return an error.
func (d *Daemon) getExecConfig(name string) (*ExecConfig, error) { func (d *Daemon) getExecConfig(name string) (*ExecConfig, error) {
ExecConfig := d.execCommands.Get(name) ec := d.execCommands.Get(name)
// If the exec is found but its container is not in the daemon's list of // If the exec is found but its container is not in the daemon's list of
// containers then it must have been delete, in which case instead of // containers then it must have been delete, in which case instead of
@ -101,12 +112,14 @@ func (d *Daemon) getExecConfig(name string) (*ExecConfig, error) {
// the user sees the same error now that they will after the // the user sees the same error now that they will after the
// 5 minute clean-up loop is run which erases old/dead execs. // 5 minute clean-up loop is run which erases old/dead execs.
if ExecConfig != nil && d.containers.Get(ExecConfig.Container.ID) != nil { if ec != nil && d.containers.Get(ec.Container.ID) != nil {
if !ec.Container.IsRunning() {
if !ExecConfig.Container.IsRunning() { return nil, derr.ErrorCodeContainerNotRunning.WithArgs(ec.Container.ID, ec.Container.State.String())
return nil, derr.ErrorCodeContainerNotRunning.WithArgs(ExecConfig.Container.ID)
} }
return ExecConfig, nil if ec.Container.isPaused() {
return nil, derr.ErrorCodeExecPaused.WithArgs(ec.Container.ID)
}
return ec, nil
} }
return nil, derr.ErrorCodeNoExecID.WithArgs(name) return nil, derr.ErrorCodeNoExecID.WithArgs(name)
@ -181,35 +194,30 @@ func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, erro
// ContainerExecStart starts a previously set up exec instance. The // ContainerExecStart starts a previously set up exec instance. The
// std streams are set up. // std streams are set up.
func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error { func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error {
var ( var (
cStdin io.ReadCloser cStdin io.ReadCloser
cStdout, cStderr io.Writer cStdout, cStderr io.Writer
) )
ExecConfig, err := d.getExecConfig(execName) ec, err := d.getExecConfig(name)
if err != nil { if err != nil {
return err return derr.ErrorCodeNoExecID.WithArgs(name)
} }
func() { ec.Lock()
ExecConfig.Lock() if ec.Running {
defer ExecConfig.Unlock() ec.Unlock()
if ExecConfig.Running { return derr.ErrorCodeExecRunning.WithArgs(ec.ID)
err = derr.ErrorCodeExecRunning.WithArgs(execName)
}
ExecConfig.Running = true
}()
if err != nil {
return err
} }
ec.Running = true
ec.Unlock()
logrus.Debugf("starting exec command %s in container %s", ExecConfig.ID, ExecConfig.Container.ID) logrus.Debugf("starting exec command %s in container %s", ec.ID, ec.Container.ID)
container := ExecConfig.Container container := ec.Container
container.logEvent("exec_start: " + ec.ProcessConfig.Entrypoint + " " + strings.Join(ec.ProcessConfig.Arguments, " "))
container.logEvent("exec_start: " + ExecConfig.ProcessConfig.Entrypoint + " " + strings.Join(ExecConfig.ProcessConfig.Arguments, " ")) if ec.OpenStdin {
if ExecConfig.OpenStdin {
r, w := io.Pipe() r, w := io.Pipe()
go func() { go func() {
defer w.Close() defer w.Close()
@ -218,23 +226,23 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout
}() }()
cStdin = r cStdin = r
} }
if ExecConfig.OpenStdout { if ec.OpenStdout {
cStdout = stdout cStdout = stdout
} }
if ExecConfig.OpenStderr { if ec.OpenStderr {
cStderr = stderr cStderr = stderr
} }
ExecConfig.streamConfig.stderr = broadcastwriter.New() ec.streamConfig.stderr = broadcastwriter.New()
ExecConfig.streamConfig.stdout = broadcastwriter.New() ec.streamConfig.stdout = broadcastwriter.New()
// Attach to stdin // Attach to stdin
if ExecConfig.OpenStdin { if ec.OpenStdin {
ExecConfig.streamConfig.stdin, ExecConfig.streamConfig.stdinPipe = io.Pipe() ec.streamConfig.stdin, ec.streamConfig.stdinPipe = io.Pipe()
} else { } else {
ExecConfig.streamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin ec.streamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin
} }
attachErr := attach(&ExecConfig.streamConfig, ExecConfig.OpenStdin, true, ExecConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr) attachErr := attach(&ec.streamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr)
execErr := make(chan error) execErr := make(chan error)
@ -243,8 +251,8 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout
// the exitStatus) even after the cmd is done running. // the exitStatus) even after the cmd is done running.
go func() { go func() {
if err := container.exec(ExecConfig); err != nil { if err := container.exec(ec); err != nil {
execErr <- derr.ErrorCodeExecCantRun.WithArgs(execName, container.ID, err) execErr <- derr.ErrorCodeExecCantRun.WithArgs(ec.ID, container.ID, err)
} }
}() }()
select { select {

View File

@ -90,6 +90,7 @@ list of DNS options to be used in the container.
* `GET /events` now includes a `timenano` field, in addition to the existing `time` field. * `GET /events` now includes a `timenano` field, in addition to the existing `time` field.
* `GET /info` now lists engine version information. * `GET /info` now lists engine version information.
* `GET /containers/json` will return `ImageID` of the image used by container. * `GET /containers/json` will return `ImageID` of the image used by container.
* `POST /exec/(name)/start` will now return an HTTP 409 when the container is either stopped or paused.
### v1.20 API changes ### v1.20 API changes

View File

@ -1677,7 +1677,7 @@ Json Parameters:
Status Codes: Status Codes:
- **201** no error - **200** no error
- **404** no such exec instance - **404** no such exec instance
**Stream details**: **Stream details**:

View File

@ -1638,7 +1638,7 @@ Json Parameters:
Status Codes: Status Codes:
- **201** no error - **200** no error
- **404** no such exec instance - **404** no such exec instance
**Stream details**: **Stream details**:

View File

@ -1801,7 +1801,7 @@ Json Parameters:
Status Codes: Status Codes:
- **201** no error - **200** no error
- **404** no such exec instance - **404** no such exec instance
**Stream details**: **Stream details**:

View File

@ -1922,7 +1922,7 @@ Json Parameters:
Status Codes: Status Codes:
- **201** no error - **200** no error
- **404** no such exec instance - **404** no such exec instance
**Stream details**: **Stream details**:

View File

@ -1984,7 +1984,7 @@ Json Parameters:
Status Codes: Status Codes:
- **201** no error - **200** no error
- **404** no such exec instance - **404** no such exec instance
**Stream details**: **Stream details**:

View File

@ -2126,7 +2126,7 @@ Json Parameters:
Status Codes: Status Codes:
- **201** no error - **200** no error
- **404** no such exec instance - **404** no such exec instance
**Stream details**: **Stream details**:

View File

@ -2226,8 +2226,9 @@ Json Parameters:
Status Codes: Status Codes:
- **201** no error - **200** no error
- **404** no such exec instance - **404** no such exec instance
- **409** - container is stopped or paused
**Stream details**: **Stream details**:
Similar to the stream behavior of `POST /container/(id)/attach` API Similar to the stream behavior of `POST /container/(id)/attach` API

View File

@ -654,7 +654,7 @@ var (
Value: "CONTAINERNOTRUNNING", Value: "CONTAINERNOTRUNNING",
Message: "Container %s is not running: %s", Message: "Container %s is not running: %s",
Description: "An attempt was made to retrieve the information about an 'exec' but the container is not running", Description: "An attempt was made to retrieve the information about an 'exec' but the container is not running",
HTTPStatusCode: http.StatusInternalServerError, HTTPStatusCode: http.StatusConflict,
}) })
// ErrorCodeNoExecID is generated when we try to get the info // ErrorCodeNoExecID is generated when we try to get the info
@ -672,7 +672,7 @@ var (
Value: "EXECPAUSED", Value: "EXECPAUSED",
Message: "Container %s is paused, unpause the container before exec", Message: "Container %s is paused, unpause the container before exec",
Description: "An attempt to start an 'exec' was made, but the owning container is paused", Description: "An attempt to start an 'exec' was made, but the owning container is paused",
HTTPStatusCode: http.StatusInternalServerError, HTTPStatusCode: http.StatusConflict,
}) })
// ErrorCodeExecRunning is generated when we try to start an exec // ErrorCodeExecRunning is generated when we try to start an exec

View File

@ -7,6 +7,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"github.com/go-check/check" "github.com/go-check/check"
) )
@ -47,3 +48,44 @@ func (s *DockerSuite) TestExecApiCreateNoValidContentType(c *check.C) {
c.Fatalf("Expected message when creating exec command with invalid Content-Type specified") c.Fatalf("Expected message when creating exec command with invalid Content-Type specified")
} }
} }
func (s *DockerSuite) TestExecAPIStart(c *check.C) {
dockerCmd(c, "run", "-d", "--name", "test", "busybox", "top")
createExec := func() string {
_, b, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", "test"), map[string]interface{}{"Cmd": []string{"true"}})
c.Assert(err, check.IsNil, check.Commentf(string(b)))
createResp := struct {
ID string `json:"Id"`
}{}
c.Assert(json.Unmarshal(b, &createResp), check.IsNil, check.Commentf(string(b)))
return createResp.ID
}
startExec := func(id string, code int) {
resp, body, err := sockRequestRaw("POST", fmt.Sprintf("/exec/%s/start", id), strings.NewReader(`{"Detach": true}`), "application/json")
c.Assert(err, check.IsNil)
b, err := readBody(body)
c.Assert(err, check.IsNil, check.Commentf(string(b)))
c.Assert(resp.StatusCode, check.Equals, code, check.Commentf(string(b)))
}
startExec(createExec(), http.StatusOK)
id := createExec()
dockerCmd(c, "stop", "test")
startExec(id, http.StatusNotFound)
dockerCmd(c, "start", "test")
startExec(id, http.StatusNotFound)
// make sure exec is created before pausing
id = createExec()
dockerCmd(c, "pause", "test")
startExec(id, http.StatusConflict)
dockerCmd(c, "unpause", "test")
startExec(id, http.StatusOK)
}