瀏覽代碼

Fix attach issue

Guillaume J. Charmes 11 年之前
父節點
當前提交
2bd089dadb
共有 3 個文件被更改,包括 129 次插入9 次删除
  1. 31 6
      commands.go
  2. 94 0
      commands_test.go
  3. 4 3
      container.go

+ 31 - 6
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

+ 94 - 0
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()

+ 4 - 3
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)