github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/integration-cli/docker_api_attach_test.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "io" 8 "net" 9 "net/http" 10 "strings" 11 "time" 12 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/client" 15 "github.com/docker/docker/integration-cli/checker" 16 "github.com/docker/docker/integration-cli/request" 17 "github.com/docker/docker/pkg/stdcopy" 18 "github.com/docker/docker/pkg/testutil" 19 "github.com/go-check/check" 20 "golang.org/x/net/websocket" 21 ) 22 23 func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) { 24 testRequires(c, DaemonIsLinux) 25 out, _ := dockerCmd(c, "run", "-dit", "busybox", "cat") 26 27 rwc, err := request.SockConn(time.Duration(10*time.Second), daemonHost()) 28 c.Assert(err, checker.IsNil) 29 30 cleanedContainerID := strings.TrimSpace(out) 31 config, err := websocket.NewConfig( 32 "/containers/"+cleanedContainerID+"/attach/ws?stream=1&stdin=1&stdout=1&stderr=1", 33 "http://localhost", 34 ) 35 c.Assert(err, checker.IsNil) 36 37 ws, err := websocket.NewClient(config, rwc) 38 c.Assert(err, checker.IsNil) 39 defer ws.Close() 40 41 expected := []byte("hello") 42 actual := make([]byte, len(expected)) 43 44 outChan := make(chan error) 45 go func() { 46 _, err := io.ReadFull(ws, actual) 47 outChan <- err 48 close(outChan) 49 }() 50 51 inChan := make(chan error) 52 go func() { 53 _, err := ws.Write(expected) 54 inChan <- err 55 close(inChan) 56 }() 57 58 select { 59 case err := <-inChan: 60 c.Assert(err, checker.IsNil) 61 case <-time.After(5 * time.Second): 62 c.Fatal("Timeout writing to ws") 63 } 64 65 select { 66 case err := <-outChan: 67 c.Assert(err, checker.IsNil) 68 case <-time.After(5 * time.Second): 69 c.Fatal("Timeout reading from ws") 70 } 71 72 c.Assert(actual, checker.DeepEquals, expected, check.Commentf("Websocket didn't return the expected data")) 73 } 74 75 // regression gh14320 76 func (s *DockerSuite) TestPostContainersAttachContainerNotFound(c *check.C) { 77 client, err := request.NewHTTPClient(daemonHost()) 78 c.Assert(err, checker.IsNil) 79 req, err := request.New(daemonHost(), "/containers/doesnotexist/attach", request.Method(http.MethodPost)) 80 resp, err := client.Do(req) 81 // connection will shutdown, err should be "persistent connection closed" 82 c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) 83 content, err := testutil.ReadBody(resp.Body) 84 c.Assert(err, checker.IsNil) 85 expected := "No such container: doesnotexist\r\n" 86 c.Assert(string(content), checker.Equals, expected) 87 } 88 89 func (s *DockerSuite) TestGetContainersWsAttachContainerNotFound(c *check.C) { 90 status, body, err := request.SockRequest("GET", "/containers/doesnotexist/attach/ws", nil, daemonHost()) 91 c.Assert(status, checker.Equals, http.StatusNotFound) 92 c.Assert(err, checker.IsNil) 93 expected := "No such container: doesnotexist" 94 c.Assert(getErrorMessage(c, body), checker.Contains, expected) 95 } 96 97 func (s *DockerSuite) TestPostContainersAttach(c *check.C) { 98 testRequires(c, DaemonIsLinux) 99 100 expectSuccess := func(conn net.Conn, br *bufio.Reader, stream string, tty bool) { 101 defer conn.Close() 102 expected := []byte("success") 103 _, err := conn.Write(expected) 104 c.Assert(err, checker.IsNil) 105 106 conn.SetReadDeadline(time.Now().Add(time.Second)) 107 lenHeader := 0 108 if !tty { 109 lenHeader = 8 110 } 111 actual := make([]byte, len(expected)+lenHeader) 112 _, err = io.ReadFull(br, actual) 113 c.Assert(err, checker.IsNil) 114 if !tty { 115 fdMap := map[string]byte{ 116 "stdin": 0, 117 "stdout": 1, 118 "stderr": 2, 119 } 120 c.Assert(actual[0], checker.Equals, fdMap[stream]) 121 } 122 c.Assert(actual[lenHeader:], checker.DeepEquals, expected, check.Commentf("Attach didn't return the expected data from %s", stream)) 123 } 124 125 expectTimeout := func(conn net.Conn, br *bufio.Reader, stream string) { 126 defer conn.Close() 127 _, err := conn.Write([]byte{'t'}) 128 c.Assert(err, checker.IsNil) 129 130 conn.SetReadDeadline(time.Now().Add(time.Second)) 131 actual := make([]byte, 1) 132 _, err = io.ReadFull(br, actual) 133 opErr, ok := err.(*net.OpError) 134 c.Assert(ok, checker.Equals, true, check.Commentf("Error is expected to be *net.OpError, got %v", err)) 135 c.Assert(opErr.Timeout(), checker.Equals, true, check.Commentf("Read from %s is expected to timeout", stream)) 136 } 137 138 // Create a container that only emits stdout. 139 cid, _ := dockerCmd(c, "run", "-di", "busybox", "cat") 140 cid = strings.TrimSpace(cid) 141 // Attach to the container's stdout stream. 142 conn, br, err := request.SockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain", daemonHost()) 143 c.Assert(err, checker.IsNil) 144 // Check if the data from stdout can be received. 145 expectSuccess(conn, br, "stdout", false) 146 // Attach to the container's stderr stream. 147 conn, br, err = request.SockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain", daemonHost()) 148 c.Assert(err, checker.IsNil) 149 // Since the container only emits stdout, attaching to stderr should return nothing. 150 expectTimeout(conn, br, "stdout") 151 152 // Test the similar functions of the stderr stream. 153 cid, _ = dockerCmd(c, "run", "-di", "busybox", "/bin/sh", "-c", "cat >&2") 154 cid = strings.TrimSpace(cid) 155 conn, br, err = request.SockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain", daemonHost()) 156 c.Assert(err, checker.IsNil) 157 expectSuccess(conn, br, "stderr", false) 158 conn, br, err = request.SockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain", daemonHost()) 159 c.Assert(err, checker.IsNil) 160 expectTimeout(conn, br, "stderr") 161 162 // Test with tty. 163 cid, _ = dockerCmd(c, "run", "-dit", "busybox", "/bin/sh", "-c", "cat >&2") 164 cid = strings.TrimSpace(cid) 165 // Attach to stdout only. 166 conn, br, err = request.SockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain", daemonHost()) 167 c.Assert(err, checker.IsNil) 168 expectSuccess(conn, br, "stdout", true) 169 170 // Attach without stdout stream. 171 conn, br, err = request.SockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain", daemonHost()) 172 c.Assert(err, checker.IsNil) 173 // Nothing should be received because both the stdout and stderr of the container will be 174 // sent to the client as stdout when tty is enabled. 175 expectTimeout(conn, br, "stdout") 176 177 // Test the client API 178 // Make sure we don't see "hello" if Logs is false 179 client, err := client.NewEnvClient() 180 c.Assert(err, checker.IsNil) 181 182 cid, _ = dockerCmd(c, "run", "-di", "busybox", "/bin/sh", "-c", "echo hello; cat") 183 cid = strings.TrimSpace(cid) 184 185 attachOpts := types.ContainerAttachOptions{ 186 Stream: true, 187 Stdin: true, 188 Stdout: true, 189 } 190 191 resp, err := client.ContainerAttach(context.Background(), cid, attachOpts) 192 c.Assert(err, checker.IsNil) 193 expectSuccess(resp.Conn, resp.Reader, "stdout", false) 194 195 // Make sure we do see "hello" if Logs is true 196 attachOpts.Logs = true 197 resp, err = client.ContainerAttach(context.Background(), cid, attachOpts) 198 c.Assert(err, checker.IsNil) 199 200 defer resp.Conn.Close() 201 resp.Conn.SetReadDeadline(time.Now().Add(time.Second)) 202 203 _, err = resp.Conn.Write([]byte("success")) 204 c.Assert(err, checker.IsNil) 205 206 actualStdout := new(bytes.Buffer) 207 actualStderr := new(bytes.Buffer) 208 stdcopy.StdCopy(actualStdout, actualStderr, resp.Reader) 209 c.Assert(actualStdout.Bytes(), checker.DeepEquals, []byte("hello\nsuccess"), check.Commentf("Attach didn't return the expected data from stdout")) 210 }