From 2bd089dadb9a66829aa1835ad674d652244c5c97 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 20 Sep 2013 11:31:00 -0700 Subject: [PATCH] Fix attach issue --- commands.go | 37 +++++++++++++++---- commands_test.go | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ container.go | 7 ++-- 3 files changed, 129 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index 62d42b9874..5c373bf8b0 100644 --- a/commands.go +++ b/commands.go @@ -6,6 +6,7 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" "flag" "fmt" "github.com/dotcloud/docker/auth" @@ -36,6 +37,10 @@ var ( VERSION string ) +var ( + ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?") +) + func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { methodName := "Cmd" + strings.ToUpper(name[:1]) + strings.ToLower(name[1:]) return reflect.TypeOf(cli).MethodByName(methodName) @@ -1256,7 +1261,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { if container.Config.Tty { if err := cli.monitorTtySize(cmd.Arg(0)); err != nil { - return err + utils.Debugf("Error monitoring tty size: %s", err) } } @@ -1565,12 +1570,12 @@ func (cli *DockerCli) CmdRun(args ...string) error { // Detached mode <-wait } else { - status, err := waitForExit(cli, runResult.ID) + status, err := getExitCode(cli, runResult.ID) if err != nil { return err } if status != 0 { - return &utils.StatusError{status} + return &utils.StatusError{Status: status} } } @@ -1636,7 +1641,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, dial, err := net.Dial(cli.proto, cli.addr) if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + return nil, -1, ErrConnectionRefused } return nil, -1, err } @@ -1645,7 +1650,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, defer clientconn.Close() if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?") + return nil, -1, ErrConnectionRefused } return nil, -1, err } @@ -1864,7 +1869,11 @@ func (cli *DockerCli) LoadConfigFile() (err error) { func waitForExit(cli *DockerCli, containerId string) (int, error) { body, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil) if err != nil { - return -1, err + // If we can't connect, then the daemon probably died. + if err != ErrConnectionRefused { + return -1, err + } + return -1, nil } var out APIWait @@ -1874,6 +1883,22 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) { return out.StatusCode, nil } +func getExitCode(cli *DockerCli, containerId string) (int, error) { + body, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil) + if err != nil { + // If we can't connect, then the daemon probably died. + if err != ErrConnectionRefused { + return -1, err + } + return -1, nil + } + c := &Container{} + if err := json.Unmarshal(body, c); err != nil { + return -1, err + } + return c.State.ExitCode, nil +} + func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { var ( isTerminal = false diff --git a/commands_test.go b/commands_test.go index 2946da8792..7b94c30b3a 100644 --- a/commands_test.go +++ b/commands_test.go @@ -369,6 +369,100 @@ func TestRunAttachStdin(t *testing.T) { } } +// TestRunDetach checks attaching and detaching with the escape sequence. +func TestRunDetach(t *testing.T) { + + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + ch := make(chan struct{}) + go func() { + defer close(ch) + cli.CmdRun("-i", "-t", unitTestImageID, "cat") + }() + + setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + container := globalRuntime.List()[0] + + setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { + stdinPipe.Write([]byte{'', ''}) + if err := stdinPipe.Close(); err != nil { + t.Fatal(err) + } + }) + + // wait for CmdRun to return + setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() { + <-ch + }) + + setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { + container.Kill() + container.Wait() + }) +} + +// TestAttachDetach checks that attach in tty mode can be detached +func TestAttachDetach(t *testing.T) { + stdin, stdinPipe := io.Pipe() + stdout, stdoutPipe := io.Pipe() + + cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + + go stdout.Read(make([]byte, 1024)) + setTimeout(t, "Starting container timed out", 2*time.Second, func() { + if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { + t.Fatal(err) + } + }) + + container := globalRuntime.List()[0] + + stdin, stdinPipe = io.Pipe() + stdout, stdoutPipe = io.Pipe() + cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr) + + ch := make(chan struct{}) + go func() { + defer close(ch) + if err := cli.CmdAttach(container.ShortID()); err != nil { + t.Fatal(err) + } + }() + + setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { + if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil { + t.Fatal(err) + } + }) + + setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { + stdinPipe.Write([]byte{'', ''}) + if err := stdinPipe.Close(); err != nil { + t.Fatal(err) + } + }) + + // wait for CmdRun to return + setTimeout(t, "Waiting for CmdAttach timed out", 5*time.Second, func() { + <-ch + }) + + setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { + container.Kill() + container.Wait() + }) +} + // Expected behaviour, the process stays alive when the client disconnects func TestAttachDisconnect(t *testing.T) { stdin, stdinPipe := io.Pipe() diff --git a/container.go b/container.go index 84b2efd526..fd158afb70 100644 --- a/container.go +++ b/container.go @@ -956,6 +956,7 @@ func (container *Container) monitor() { } } utils.Debugf("Process finished") + if container.runtime != nil && container.runtime.srv != nil { container.runtime.srv.LogEvent("die", container.ShortID(), container.runtime.repositories.ImageName(container.Image)) } @@ -964,6 +965,9 @@ func (container *Container) monitor() { exitCode = container.cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus() } + // Report status back + container.State.setStopped(exitCode) + // Cleanup container.releaseNetwork() if container.Config.OpenStdin { @@ -993,9 +997,6 @@ func (container *Container) monitor() { container.stdin, container.stdinPipe = io.Pipe() } - // Report status back - container.State.setStopped(exitCode) - // Release the lock close(container.waitLock)