diff --git a/api.go b/api.go index 3155bf5513..dd33f40a66 100644 --- a/api.go +++ b/api.go @@ -660,7 +660,20 @@ func postContainersAttach(srv *Server, version float64, w http.ResponseWriter, r if err != nil { return err } - defer in.Close() + defer func() { + if tcpc, ok := in.(*net.TCPConn); ok { + tcpc.CloseWrite() + } else { + in.Close() + } + }() + defer func() { + if tcpc, ok := out.(*net.TCPConn); ok { + tcpc.CloseWrite() + } else if closer, ok := out.(io.Closer); ok { + closer.Close() + } + }() fmt.Fprintf(out, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") if err := srv.ContainerAttach(name, logs, stream, stdin, stdout, stderr, in, out); err != nil { diff --git a/commands.go b/commands.go index b84bea5be8..7923f84fc1 100644 --- a/commands.go +++ b/commands.go @@ -1279,8 +1279,10 @@ func (cli *DockerCli) CmdRun(args ...string) error { } if !config.AttachStdout && !config.AttachStderr { - fmt.Fprintf(cli.out, "%s\n", runResult.ID) + // Make this asynchrone in order to let the client write to stdin before having to read the ID + go fmt.Fprintf(cli.out, "%s\n", runResult.ID) } + if config.AttachStdin || config.AttachStdout || config.AttachStderr { if config.Tty { if err := cli.monitorTtySize(runResult.ID); err != nil { @@ -1301,6 +1303,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if config.AttachStderr { v.Set("stderr", "1") } + if err := cli.hijack("POST", "/containers/"+runResult.ID+"/attach?"+v.Encode(), config.Tty, cli.in, cli.out); err != nil { utils.Debugf("Error hijack: %s", err) return err @@ -1466,6 +1469,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } defer term.RestoreTerminal(cli.terminalFd, oldState) } + sendStdin := utils.Go(func() error { if in != nil { io.Copy(rwc, in) diff --git a/commands_test.go b/commands_test.go index 8628082169..87c4c02a52 100644 --- a/commands_test.go +++ b/commands_test.go @@ -3,8 +3,9 @@ package docker import ( "bufio" "fmt" + "github.com/dotcloud/docker/utils" "io" - _ "io/ioutil" + "io/ioutil" "strings" "testing" "time" @@ -59,20 +60,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error } /*TODO -func cmdWait(srv *Server, container *Container) error { - stdout, stdoutPipe := io.Pipe() - - go func() { - srv.CmdWait(nil, stdoutPipe, container.Id) - }() - - if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil { - return err - } - // Cleanup pipes - return closeWrap(stdout, stdoutPipe) -} - func cmdImages(srv *Server, args ...string) (string, error) { stdout, stdoutPipe := io.Pipe() @@ -144,41 +131,39 @@ func TestImages(t *testing.T) { // todo: add checks for -a } +*/ // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname func TestRunHostname(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } - defer nuke(runtime) - - srv := &Server{runtime: runtime} - - stdin, _ := io.Pipe() stdout, stdoutPipe := io.Pipe() + cli := NewDockerCli(nil, stdoutPipe, nil, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + c := make(chan struct{}) go func() { - if err := srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-h", "foobar", GetTestImage(runtime).Id, "hostname"); err != nil { + defer close(c) + if err := cli.CmdRun("-h", "foobar", unitTestImageId, "hostname"); err != nil { t.Fatal(err) } - close(c) }() - cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') - if err != nil { - t.Fatal(err) - } - if cmdOutput != "foobar\n" { - t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput) - } + utils.Debugf("--") + setTimeout(t, "Reading command output time out", 2*time.Second, func() { + cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if cmdOutput != "foobar\n" { + t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput) + } + }) - setTimeout(t, "CmdRun timed out", 2*time.Second, func() { + setTimeout(t, "CmdRun timed out", 5*time.Second, func() { <-c - cmdWait(srv, srv.runtime.List()[0]) }) } +/* func TestRunExit(t *testing.T) { runtime, err := newTestRuntime() if err != nil { @@ -335,33 +320,26 @@ func TestRunDisconnectTty(t *testing.T) { } } */ -/* + // TestAttachStdin checks attaching to stdin without stdout and stderr. // 'docker run -i -a stdin' should sends the client's stdin to the command, // then detach from it and print the container id. func TestRunAttachStdin(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } - defer nuke(runtime) - srv := &Server{runtime: runtime} - // enableCors: false, - // lock: &sync.Mutex{}, - // pullingPool: make(map[string]struct{}), - // pushingPool: make(map[string]struct{}), stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() + cli := NewDockerCli(stdin, stdoutPipe, nil, testDaemonProto, testDaemonAddr) + defer cleanup(globalRuntime) + ch := make(chan struct{}) go func() { - srv.CmdRun(stdin, rcli.NewDockerLocalConn(stdoutPipe), "-i", "-a", "stdin", GetTestImage(runtime).Id, "sh", "-c", "echo hello; cat") - close(ch) + defer close(ch) + cli.CmdRun("-i", "-a", "stdin", unitTestImageId, "sh", "-c", "echo hello && cat") }() // Send input to the command, close stdin - setTimeout(t, "Write timed out", 2*time.Second, func() { + setTimeout(t, "Write timed out", 10*time.Second, func() { if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil { t.Fatal(err) } @@ -370,23 +348,27 @@ func TestRunAttachStdin(t *testing.T) { } }) - container := runtime.List()[0] + container := globalRuntime.List()[0] // Check output - cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') - if err != nil { - t.Fatal(err) - } - if cmdOutput != container.ShortId()+"\n" { - t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortId()+"\n", cmdOutput) - } + setTimeout(t, "Reading command output time out", 10*time.Second, func() { + cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if cmdOutput != container.ShortID()+"\n" { + t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput) + } + }) // wait for CmdRun to return - setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() { + setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() { + // Unblock hijack end + stdout.Read([]byte{}) <-ch }) - setTimeout(t, "Waiting for command to exit timed out", 2*time.Second, func() { + setTimeout(t, "Waiting for command to exit timed out", 5*time.Second, func() { container.Wait() }) @@ -404,7 +386,7 @@ func TestRunAttachStdin(t *testing.T) { } } } -*/ + /* // Expected behaviour, the process stays alive when the client disconnects func TestAttachDisconnect(t *testing.T) { diff --git a/runtime_test.go b/runtime_test.go index d83808c78f..23b1be0257 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -24,6 +24,8 @@ const ( testDaemonProto = "tcp" ) +var globalRuntime *Runtime + func nuke(runtime *Runtime) error { var wg sync.WaitGroup for _, container := range runtime.List() { @@ -37,6 +39,23 @@ func nuke(runtime *Runtime) error { return os.RemoveAll(runtime.root) } +func cleanup(runtime *Runtime) error { + for _, container := range runtime.List() { + container.Kill() + runtime.Destroy(container) + } + images, err := runtime.graph.All() + if err != nil { + return err + } + for _, image := range images { + if image.ID != unitTestImageId { + runtime.graph.Delete(image.ID) + } + } + return nil +} + func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) @@ -64,6 +83,7 @@ func init() { if err != nil { panic(err) } + globalRuntime = runtime // Create the "Server" srv := &Server{ @@ -84,6 +104,9 @@ func init() { panic(err) } }() + + // Give some time to ListenAndServer to actually start + time.Sleep(time.Second) } // FIXME: test that ImagePull(json=true) send correct json output diff --git a/z_final_test.go b/z_final_test.go new file mode 100644 index 0000000000..78a7acf6e7 --- /dev/null +++ b/z_final_test.go @@ -0,0 +1,12 @@ +package docker + +import ( + "github.com/dotcloud/docker/utils" + "runtime" + "testing" +) + +func TestFinal(t *testing.T) { + cleanup(globalRuntime) + t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) +}