commands_test.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. package docker
  2. import (
  3. "bufio"
  4. "fmt"
  5. "github.com/dotcloud/docker/utils"
  6. "io"
  7. "io/ioutil"
  8. "strings"
  9. "testing"
  10. "time"
  11. )
  12. func closeWrap(args ...io.Closer) error {
  13. e := false
  14. ret := fmt.Errorf("Error closing elements")
  15. for _, c := range args {
  16. if err := c.Close(); err != nil {
  17. e = true
  18. ret = fmt.Errorf("%s\n%s", ret, err)
  19. }
  20. }
  21. if e {
  22. return ret
  23. }
  24. return nil
  25. }
  26. func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
  27. c := make(chan bool)
  28. // Make sure we are not too long
  29. go func() {
  30. time.Sleep(d)
  31. c <- true
  32. }()
  33. go func() {
  34. f()
  35. c <- false
  36. }()
  37. if <-c && msg != "" {
  38. t.Fatal(msg)
  39. }
  40. }
  41. func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error {
  42. for i := 0; i < count; i++ {
  43. if _, err := w.Write([]byte(input)); err != nil {
  44. return err
  45. }
  46. o, err := bufio.NewReader(r).ReadString('\n')
  47. if err != nil {
  48. return err
  49. }
  50. if strings.Trim(o, " \r\n") != output {
  51. return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", output, o)
  52. }
  53. }
  54. return nil
  55. }
  56. // TestRunHostname checks that 'docker run -h' correctly sets a custom hostname
  57. func TestRunHostname(t *testing.T) {
  58. stdout, stdoutPipe := io.Pipe()
  59. cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  60. defer cleanup(globalRuntime)
  61. c := make(chan struct{})
  62. go func() {
  63. defer close(c)
  64. if err := cli.CmdRun("-h", "foobar", unitTestImageID, "hostname"); err != nil {
  65. t.Fatal(err)
  66. }
  67. }()
  68. setTimeout(t, "Reading command output time out", 2*time.Second, func() {
  69. cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
  70. if err != nil {
  71. t.Fatal(err)
  72. }
  73. if cmdOutput != "foobar\n" {
  74. t.Fatalf("'hostname' should display '%s', not '%s'", "foobar\n", cmdOutput)
  75. }
  76. })
  77. setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
  78. <-c
  79. })
  80. }
  81. func TestRunExit(t *testing.T) {
  82. stdin, stdinPipe := io.Pipe()
  83. stdout, stdoutPipe := io.Pipe()
  84. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  85. defer cleanup(globalRuntime)
  86. c1 := make(chan struct{})
  87. go func() {
  88. cli.CmdRun("-i", unitTestImageID, "/bin/cat")
  89. close(c1)
  90. }()
  91. setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
  92. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  93. t.Fatal(err)
  94. }
  95. })
  96. container := globalRuntime.List()[0]
  97. // Closing /bin/cat stdin, expect it to exit
  98. if err := stdin.Close(); err != nil {
  99. t.Fatal(err)
  100. }
  101. // as the process exited, CmdRun must finish and unblock. Wait for it
  102. setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
  103. <-c1
  104. go func() {
  105. cli.CmdWait(container.ID)
  106. }()
  107. if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
  108. t.Fatal(err)
  109. }
  110. })
  111. // Make sure that the client has been disconnected
  112. setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
  113. // Expecting pipe i/o error, just check that read does not block
  114. stdin.Read([]byte{})
  115. })
  116. // Cleanup pipes
  117. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  118. t.Fatal(err)
  119. }
  120. }
  121. // Expected behaviour: the process dies when the client disconnects
  122. func TestRunDisconnect(t *testing.T) {
  123. stdin, stdinPipe := io.Pipe()
  124. stdout, stdoutPipe := io.Pipe()
  125. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  126. defer cleanup(globalRuntime)
  127. c1 := make(chan struct{})
  128. go func() {
  129. // We're simulating a disconnect so the return value doesn't matter. What matters is the
  130. // fact that CmdRun returns.
  131. cli.CmdRun("-i", unitTestImageID, "/bin/cat")
  132. close(c1)
  133. }()
  134. setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
  135. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  136. t.Fatal(err)
  137. }
  138. })
  139. // Close pipes (simulate disconnect)
  140. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  141. t.Fatal(err)
  142. }
  143. // as the pipes are close, we expect the process to die,
  144. // therefore CmdRun to unblock. Wait for CmdRun
  145. setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
  146. <-c1
  147. })
  148. // Client disconnect after run -i should cause stdin to be closed, which should
  149. // cause /bin/cat to exit.
  150. setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
  151. container := globalRuntime.List()[0]
  152. container.Wait()
  153. if container.State.Running {
  154. t.Fatalf("/bin/cat is still running after closing stdin")
  155. }
  156. })
  157. }
  158. // Expected behaviour: the process dies when the client disconnects
  159. func TestRunDisconnectTty(t *testing.T) {
  160. stdin, stdinPipe := io.Pipe()
  161. stdout, stdoutPipe := io.Pipe()
  162. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  163. defer cleanup(globalRuntime)
  164. c1 := make(chan struct{})
  165. go func() {
  166. // We're simulating a disconnect so the return value doesn't matter. What matters is the
  167. // fact that CmdRun returns.
  168. if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
  169. utils.Debugf("Error CmdRun: %s\n", err)
  170. }
  171. close(c1)
  172. }()
  173. setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
  174. for {
  175. // Client disconnect after run -i should keep stdin out in TTY mode
  176. l := globalRuntime.List()
  177. if len(l) == 1 && l[0].State.Running {
  178. break
  179. }
  180. time.Sleep(10 * time.Millisecond)
  181. }
  182. })
  183. // Client disconnect after run -i should keep stdin out in TTY mode
  184. container := globalRuntime.List()[0]
  185. setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
  186. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  187. t.Fatal(err)
  188. }
  189. })
  190. // Close pipes (simulate disconnect)
  191. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  192. t.Fatal(err)
  193. }
  194. // In tty mode, we expect the process to stay alive even after client's stdin closes.
  195. // Do not wait for run to finish
  196. // Give some time to monitor to do his thing
  197. container.WaitTimeout(500 * time.Millisecond)
  198. if !container.State.Running {
  199. t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
  200. }
  201. }
  202. // TestAttachStdin checks attaching to stdin without stdout and stderr.
  203. // 'docker run -i -a stdin' should sends the client's stdin to the command,
  204. // then detach from it and print the container id.
  205. func TestRunAttachStdin(t *testing.T) {
  206. stdin, stdinPipe := io.Pipe()
  207. stdout, stdoutPipe := io.Pipe()
  208. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  209. defer cleanup(globalRuntime)
  210. ch := make(chan struct{})
  211. go func() {
  212. defer close(ch)
  213. cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat && sleep 5")
  214. }()
  215. // Send input to the command, close stdin
  216. setTimeout(t, "Write timed out", 10*time.Second, func() {
  217. if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
  218. t.Fatal(err)
  219. }
  220. if err := stdinPipe.Close(); err != nil {
  221. t.Fatal(err)
  222. }
  223. })
  224. container := globalRuntime.List()[0]
  225. // Check output
  226. setTimeout(t, "Reading command output time out", 10*time.Second, func() {
  227. cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
  228. if err != nil {
  229. t.Fatal(err)
  230. }
  231. if cmdOutput != container.ShortID()+"\n" {
  232. t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
  233. }
  234. })
  235. // wait for CmdRun to return
  236. setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
  237. <-ch
  238. })
  239. setTimeout(t, "Waiting for command to exit timed out", 10*time.Second, func() {
  240. container.Wait()
  241. })
  242. // Check logs
  243. if cmdLogs, err := container.ReadLog("json"); err != nil {
  244. t.Fatal(err)
  245. } else {
  246. if output, err := ioutil.ReadAll(cmdLogs); err != nil {
  247. t.Fatal(err)
  248. } else {
  249. expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
  250. for _, expectedLog := range expectedLogs {
  251. if !strings.Contains(string(output), expectedLog) {
  252. t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
  253. }
  254. }
  255. }
  256. }
  257. }
  258. // Expected behaviour, the process stays alive when the client disconnects
  259. func TestAttachDisconnect(t *testing.T) {
  260. stdin, stdinPipe := io.Pipe()
  261. stdout, stdoutPipe := io.Pipe()
  262. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  263. defer cleanup(globalRuntime)
  264. go func() {
  265. // Start a process in daemon mode
  266. if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
  267. utils.Debugf("Error CmdRun: %s\n", err)
  268. }
  269. }()
  270. setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
  271. if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
  272. t.Fatal(err)
  273. }
  274. })
  275. setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
  276. for {
  277. l := globalRuntime.List()
  278. if len(l) == 1 && l[0].State.Running {
  279. break
  280. }
  281. time.Sleep(10 * time.Millisecond)
  282. }
  283. })
  284. container := globalRuntime.List()[0]
  285. // Attach to it
  286. c1 := make(chan struct{})
  287. go func() {
  288. // We're simulating a disconnect so the return value doesn't matter. What matters is the
  289. // fact that CmdAttach returns.
  290. cli.CmdAttach(container.ID)
  291. close(c1)
  292. }()
  293. setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
  294. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  295. t.Fatal(err)
  296. }
  297. })
  298. // Close pipes (client disconnects)
  299. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  300. t.Fatal(err)
  301. }
  302. // Wait for attach to finish, the client disconnected, therefore, Attach finished his job
  303. setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
  304. <-c1
  305. })
  306. // We closed stdin, expect /bin/cat to still be running
  307. // Wait a little bit to make sure container.monitor() did his thing
  308. err := container.WaitTimeout(500 * time.Millisecond)
  309. if err == nil || !container.State.Running {
  310. t.Fatalf("/bin/cat is not running after closing stdin")
  311. }
  312. // Try to avoid the timeout in destroy. Best effort, don't check error
  313. cStdin, _ := container.StdinPipe()
  314. cStdin.Close()
  315. container.Wait()
  316. }