Merge pull request #16250 from cpuguy83/15487_exec_error_codes
Make exec start return proper error codes
This commit is contained in:
commit
5a43beda91
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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**:
|
||||||
|
|
|
||||||
|
|
@ -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**:
|
||||||
|
|
|
||||||
|
|
@ -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**:
|
||||||
|
|
|
||||||
|
|
@ -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**:
|
||||||
|
|
|
||||||
|
|
@ -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**:
|
||||||
|
|
|
||||||
|
|
@ -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**:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue