github.com/dougm/docker@v1.5.0/integration/commands_test.go (about) 1 package docker 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "strings" 9 "testing" 10 "time" 11 12 log "github.com/Sirupsen/logrus" 13 "github.com/docker/docker/api/client" 14 "github.com/docker/docker/daemon" 15 "github.com/docker/docker/pkg/term" 16 "github.com/docker/docker/utils" 17 "github.com/kr/pty" 18 ) 19 20 func closeWrap(args ...io.Closer) error { 21 e := false 22 ret := fmt.Errorf("Error closing elements") 23 for _, c := range args { 24 if err := c.Close(); err != nil { 25 e = true 26 ret = fmt.Errorf("%s\n%s", ret, err) 27 } 28 } 29 if e { 30 return ret 31 } 32 return nil 33 } 34 35 func setRaw(t *testing.T, c *daemon.Container) *term.State { 36 pty, err := c.GetPtyMaster() 37 if err != nil { 38 t.Fatal(err) 39 } 40 state, err := term.MakeRaw(pty.Fd()) 41 if err != nil { 42 t.Fatal(err) 43 } 44 return state 45 } 46 47 func unsetRaw(t *testing.T, c *daemon.Container, state *term.State) { 48 pty, err := c.GetPtyMaster() 49 if err != nil { 50 t.Fatal(err) 51 } 52 term.RestoreTerminal(pty.Fd(), state) 53 } 54 55 func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container { 56 var container *daemon.Container 57 58 setTimeout(t, "Waiting for the container to be started timed out", timeout, func() { 59 for { 60 l := globalDaemon.List() 61 if len(l) == 1 && l[0].IsRunning() { 62 container = l[0] 63 break 64 } 65 time.Sleep(10 * time.Millisecond) 66 } 67 }) 68 69 if container == nil { 70 t.Fatal("An error occured while waiting for the container to start") 71 } 72 73 return container 74 } 75 76 func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { 77 c := make(chan bool) 78 79 // Make sure we are not too long 80 go func() { 81 time.Sleep(d) 82 c <- true 83 }() 84 go func() { 85 f() 86 c <- false 87 }() 88 if <-c && msg != "" { 89 t.Fatal(msg) 90 } 91 } 92 93 func expectPipe(expected string, r io.Reader) error { 94 o, err := bufio.NewReader(r).ReadString('\n') 95 if err != nil { 96 return err 97 } 98 if strings.Trim(o, " \r\n") != expected { 99 return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", expected, o) 100 } 101 return nil 102 } 103 104 func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error { 105 for i := 0; i < count; i++ { 106 if _, err := w.Write([]byte(input)); err != nil { 107 return err 108 } 109 if err := expectPipe(output, r); err != nil { 110 return err 111 } 112 } 113 return nil 114 } 115 116 // TestRunDetach checks attaching and detaching with the escape sequence. 117 func TestRunDetach(t *testing.T) { 118 stdout, stdoutPipe := io.Pipe() 119 cpty, tty, err := pty.Open() 120 if err != nil { 121 t.Fatal(err) 122 } 123 124 cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) 125 defer cleanup(globalEngine, t) 126 127 ch := make(chan struct{}) 128 go func() { 129 defer close(ch) 130 cli.CmdRun("-i", "-t", unitTestImageID, "cat") 131 }() 132 133 container := waitContainerStart(t, 10*time.Second) 134 135 state := setRaw(t, container) 136 defer unsetRaw(t, container, state) 137 138 setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { 139 if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { 140 t.Fatal(err) 141 } 142 }) 143 144 setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { 145 cpty.Write([]byte{16}) 146 time.Sleep(100 * time.Millisecond) 147 cpty.Write([]byte{17}) 148 }) 149 150 // wait for CmdRun to return 151 setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() { 152 <-ch 153 }) 154 closeWrap(cpty, stdout, stdoutPipe) 155 156 time.Sleep(500 * time.Millisecond) 157 if !container.IsRunning() { 158 t.Fatal("The detached container should be still running") 159 } 160 161 setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() { 162 container.Kill() 163 }) 164 } 165 166 // TestAttachDetach checks that attach in tty mode can be detached using the long container ID 167 func TestAttachDetach(t *testing.T) { 168 stdout, stdoutPipe := io.Pipe() 169 cpty, tty, err := pty.Open() 170 if err != nil { 171 t.Fatal(err) 172 } 173 174 cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) 175 defer cleanup(globalEngine, t) 176 177 ch := make(chan struct{}) 178 go func() { 179 defer close(ch) 180 if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { 181 t.Fatal(err) 182 } 183 }() 184 185 container := waitContainerStart(t, 10*time.Second) 186 187 setTimeout(t, "Reading container's id timed out", 10*time.Second, func() { 188 buf := make([]byte, 1024) 189 n, err := stdout.Read(buf) 190 if err != nil { 191 t.Fatal(err) 192 } 193 194 if strings.Trim(string(buf[:n]), " \r\n") != container.ID { 195 t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n]) 196 } 197 }) 198 setTimeout(t, "Starting container timed out", 10*time.Second, func() { 199 <-ch 200 }) 201 202 state := setRaw(t, container) 203 defer unsetRaw(t, container, state) 204 205 stdout, stdoutPipe = io.Pipe() 206 cpty, tty, err = pty.Open() 207 if err != nil { 208 t.Fatal(err) 209 } 210 211 cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) 212 213 ch = make(chan struct{}) 214 go func() { 215 defer close(ch) 216 if err := cli.CmdAttach(container.ID); err != nil { 217 if err != io.ErrClosedPipe { 218 t.Fatal(err) 219 } 220 } 221 }() 222 223 setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { 224 if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { 225 if err != io.ErrClosedPipe { 226 t.Fatal(err) 227 } 228 } 229 }) 230 231 setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { 232 cpty.Write([]byte{16}) 233 time.Sleep(100 * time.Millisecond) 234 cpty.Write([]byte{17}) 235 }) 236 237 // wait for CmdRun to return 238 setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { 239 <-ch 240 }) 241 242 closeWrap(cpty, stdout, stdoutPipe) 243 244 time.Sleep(500 * time.Millisecond) 245 if !container.IsRunning() { 246 t.Fatal("The detached container should be still running") 247 } 248 249 setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { 250 container.Kill() 251 }) 252 } 253 254 // TestAttachDetachTruncatedID checks that attach in tty mode can be detached 255 func TestAttachDetachTruncatedID(t *testing.T) { 256 stdout, stdoutPipe := io.Pipe() 257 cpty, tty, err := pty.Open() 258 if err != nil { 259 t.Fatal(err) 260 } 261 262 cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) 263 defer cleanup(globalEngine, t) 264 265 // Discard the CmdRun output 266 go stdout.Read(make([]byte, 1024)) 267 setTimeout(t, "Starting container timed out", 2*time.Second, func() { 268 if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { 269 t.Fatal(err) 270 } 271 }) 272 273 container := waitContainerStart(t, 10*time.Second) 274 275 state := setRaw(t, container) 276 defer unsetRaw(t, container, state) 277 278 stdout, stdoutPipe = io.Pipe() 279 cpty, tty, err = pty.Open() 280 if err != nil { 281 t.Fatal(err) 282 } 283 284 cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) 285 286 ch := make(chan struct{}) 287 go func() { 288 defer close(ch) 289 if err := cli.CmdAttach(utils.TruncateID(container.ID)); err != nil { 290 if err != io.ErrClosedPipe { 291 t.Fatal(err) 292 } 293 } 294 }() 295 296 setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { 297 if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { 298 if err != io.ErrClosedPipe { 299 t.Fatal(err) 300 } 301 } 302 }) 303 304 setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { 305 cpty.Write([]byte{16}) 306 time.Sleep(100 * time.Millisecond) 307 cpty.Write([]byte{17}) 308 }) 309 310 // wait for CmdRun to return 311 setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { 312 <-ch 313 }) 314 closeWrap(cpty, stdout, stdoutPipe) 315 316 time.Sleep(500 * time.Millisecond) 317 if !container.IsRunning() { 318 t.Fatal("The detached container should be still running") 319 } 320 321 setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { 322 container.Kill() 323 }) 324 } 325 326 // Expected behaviour, the process stays alive when the client disconnects 327 func TestAttachDisconnect(t *testing.T) { 328 stdout, stdoutPipe := io.Pipe() 329 cpty, tty, err := pty.Open() 330 if err != nil { 331 t.Fatal(err) 332 } 333 334 cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) 335 defer cleanup(globalEngine, t) 336 337 go func() { 338 // Start a process in daemon mode 339 if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil { 340 log.Debugf("Error CmdRun: %s", err) 341 } 342 }() 343 344 setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() { 345 if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil { 346 t.Fatal(err) 347 } 348 }) 349 350 setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { 351 for { 352 l := globalDaemon.List() 353 if len(l) == 1 && l[0].IsRunning() { 354 break 355 } 356 time.Sleep(10 * time.Millisecond) 357 } 358 }) 359 360 container := globalDaemon.List()[0] 361 362 // Attach to it 363 c1 := make(chan struct{}) 364 go func() { 365 // We're simulating a disconnect so the return value doesn't matter. What matters is the 366 // fact that CmdAttach returns. 367 cli.CmdAttach(container.ID) 368 close(c1) 369 }() 370 371 setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { 372 if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { 373 t.Fatal(err) 374 } 375 }) 376 // Close pipes (client disconnects) 377 if err := closeWrap(cpty, stdout, stdoutPipe); err != nil { 378 t.Fatal(err) 379 } 380 381 // Wait for attach to finish, the client disconnected, therefore, Attach finished his job 382 setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { 383 <-c1 384 }) 385 386 // We closed stdin, expect /bin/cat to still be running 387 // Wait a little bit to make sure container.monitor() did his thing 388 _, err = container.WaitStop(500 * time.Millisecond) 389 if err == nil || !container.IsRunning() { 390 t.Fatalf("/bin/cat is not running after closing stdin") 391 } 392 393 // Try to avoid the timeout in destroy. Best effort, don't check error 394 cStdin := container.StdinPipe() 395 cStdin.Close() 396 container.WaitStop(-1 * time.Second) 397 } 398 399 // Expected behaviour: container gets deleted automatically after exit 400 func TestRunAutoRemove(t *testing.T) { 401 t.Skip("Fixme. Skipping test for now, race condition") 402 stdout, stdoutPipe := io.Pipe() 403 404 cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) 405 defer cleanup(globalEngine, t) 406 407 c := make(chan struct{}) 408 go func() { 409 defer close(c) 410 if err := cli.CmdRun("--rm", unitTestImageID, "hostname"); err != nil { 411 t.Fatal(err) 412 } 413 }() 414 415 var temporaryContainerID string 416 setTimeout(t, "Reading command output time out", 2*time.Second, func() { 417 cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') 418 if err != nil { 419 t.Fatal(err) 420 } 421 temporaryContainerID = cmdOutput 422 if err := closeWrap(stdout, stdoutPipe); err != nil { 423 t.Fatal(err) 424 } 425 }) 426 427 setTimeout(t, "CmdRun timed out", 10*time.Second, func() { 428 <-c 429 }) 430 431 time.Sleep(500 * time.Millisecond) 432 433 if len(globalDaemon.List()) > 0 { 434 t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID) 435 } 436 }