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  }