github.com/jingleWang/moby@v1.13.1/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/pkg/integration/checker" 16 "github.com/docker/docker/pkg/stdcopy" 17 "github.com/go-check/check" 18 "golang.org/x/net/websocket" 19 ) 20 21 func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) { 22 testRequires(c, DaemonIsLinux) 23 out, _ := dockerCmd(c, "run", "-dit", "busybox", "cat") 24 25 rwc, err := sockConn(time.Duration(10*time.Second), "") 26 c.Assert(err, checker.IsNil) 27 28 cleanedContainerID := strings.TrimSpace(out) 29 config, err := websocket.NewConfig( 30 "/containers/"+cleanedContainerID+"/attach/ws?stream=1&stdin=1&stdout=1&stderr=1", 31 "http://localhost", 32 ) 33 c.Assert(err, checker.IsNil) 34 35 ws, err := websocket.NewClient(config, rwc) 36 c.Assert(err, checker.IsNil) 37 defer ws.Close() 38 39 expected := []byte("hello") 40 actual := make([]byte, len(expected)) 41 42 outChan := make(chan error) 43 go func() { 44 _, err := io.ReadFull(ws, actual) 45 outChan <- err 46 close(outChan) 47 }() 48 49 inChan := make(chan error) 50 go func() { 51 _, err := ws.Write(expected) 52 inChan <- err 53 close(inChan) 54 }() 55 56 select { 57 case err := <-inChan: 58 c.Assert(err, checker.IsNil) 59 case <-time.After(5 * time.Second): 60 c.Fatal("Timeout writing to ws") 61 } 62 63 select { 64 case err := <-outChan: 65 c.Assert(err, checker.IsNil) 66 case <-time.After(5 * time.Second): 67 c.Fatal("Timeout reading from ws") 68 } 69 70 c.Assert(actual, checker.DeepEquals, expected, check.Commentf("Websocket didn't return the expected data")) 71 } 72 73 // regression gh14320 74 func (s *DockerSuite) TestPostContainersAttachContainerNotFound(c *check.C) { 75 req, client, err := newRequestClient("POST", "/containers/doesnotexist/attach", nil, "", "") 76 c.Assert(err, checker.IsNil) 77 78 resp, err := client.Do(req) 79 // connection will shutdown, err should be "persistent connection closed" 80 c.Assert(err, checker.NotNil) // Server shutdown connection 81 82 body, err := readBody(resp.Body) 83 c.Assert(err, checker.IsNil) 84 c.Assert(resp.StatusCode, checker.Equals, http.StatusNotFound) 85 expected := "No such container: doesnotexist\r\n" 86 c.Assert(string(body), checker.Equals, expected) 87 } 88 89 func (s *DockerSuite) TestGetContainersWsAttachContainerNotFound(c *check.C) { 90 status, body, err := sockRequest("GET", "/containers/doesnotexist/attach/ws", nil) 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 := sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain") 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 = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain") 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 = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain") 156 c.Assert(err, checker.IsNil) 157 expectSuccess(conn, br, "stderr", false) 158 conn, br, err = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain") 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 = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stdout=1", nil, "text/plain") 167 c.Assert(err, checker.IsNil) 168 expectSuccess(conn, br, "stdout", true) 169 170 // Attach without stdout stream. 171 conn, br, err = sockRequestHijack("POST", "/containers/"+cid+"/attach?stream=1&stdin=1&stderr=1", nil, "text/plain") 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 }