commands_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  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. // TestRunWorkdir checks that 'docker run -w' correctly sets a custom working directory
  82. func TestRunWorkdir(t *testing.T) {
  83. stdout, stdoutPipe := io.Pipe()
  84. cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  85. defer cleanup(globalRuntime)
  86. c := make(chan struct{})
  87. go func() {
  88. defer close(c)
  89. if err := cli.CmdRun("-w", "/foo/bar", unitTestImageID, "pwd"); err != nil {
  90. t.Fatal(err)
  91. }
  92. }()
  93. setTimeout(t, "Reading command output time out", 2*time.Second, func() {
  94. cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
  95. if err != nil {
  96. t.Fatal(err)
  97. }
  98. if cmdOutput != "/foo/bar\n" {
  99. t.Fatalf("'pwd' should display '%s', not '%s'", "/foo/bar\n", cmdOutput)
  100. }
  101. })
  102. setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
  103. <-c
  104. })
  105. }
  106. // TestRunWorkdirExists checks that 'docker run -w' correctly sets a custom working directory, even if it exists
  107. func TestRunWorkdirExists(t *testing.T) {
  108. stdout, stdoutPipe := io.Pipe()
  109. cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  110. defer cleanup(globalRuntime)
  111. c := make(chan struct{})
  112. go func() {
  113. defer close(c)
  114. if err := cli.CmdRun("-w", "/proc", unitTestImageID, "pwd"); err != nil {
  115. t.Fatal(err)
  116. }
  117. }()
  118. setTimeout(t, "Reading command output time out", 2*time.Second, func() {
  119. cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
  120. if err != nil {
  121. t.Fatal(err)
  122. }
  123. if cmdOutput != "/proc\n" {
  124. t.Fatalf("'pwd' should display '%s', not '%s'", "/proc\n", cmdOutput)
  125. }
  126. })
  127. setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
  128. <-c
  129. })
  130. }
  131. func TestRunExit(t *testing.T) {
  132. stdin, stdinPipe := io.Pipe()
  133. stdout, stdoutPipe := io.Pipe()
  134. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  135. defer cleanup(globalRuntime)
  136. c1 := make(chan struct{})
  137. go func() {
  138. cli.CmdRun("-i", unitTestImageID, "/bin/cat")
  139. close(c1)
  140. }()
  141. setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
  142. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  143. t.Fatal(err)
  144. }
  145. })
  146. container := globalRuntime.List()[0]
  147. // Closing /bin/cat stdin, expect it to exit
  148. if err := stdin.Close(); err != nil {
  149. t.Fatal(err)
  150. }
  151. // as the process exited, CmdRun must finish and unblock. Wait for it
  152. setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
  153. <-c1
  154. go func() {
  155. cli.CmdWait(container.ID)
  156. }()
  157. if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
  158. t.Fatal(err)
  159. }
  160. })
  161. // Make sure that the client has been disconnected
  162. setTimeout(t, "The client should have been disconnected once the remote process exited.", 2*time.Second, func() {
  163. // Expecting pipe i/o error, just check that read does not block
  164. stdin.Read([]byte{})
  165. })
  166. // Cleanup pipes
  167. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  168. t.Fatal(err)
  169. }
  170. }
  171. // Expected behaviour: the process dies when the client disconnects
  172. func TestRunDisconnect(t *testing.T) {
  173. stdin, stdinPipe := io.Pipe()
  174. stdout, stdoutPipe := io.Pipe()
  175. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  176. defer cleanup(globalRuntime)
  177. c1 := make(chan struct{})
  178. go func() {
  179. // We're simulating a disconnect so the return value doesn't matter. What matters is the
  180. // fact that CmdRun returns.
  181. cli.CmdRun("-i", unitTestImageID, "/bin/cat")
  182. close(c1)
  183. }()
  184. setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
  185. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  186. t.Fatal(err)
  187. }
  188. })
  189. // Close pipes (simulate disconnect)
  190. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  191. t.Fatal(err)
  192. }
  193. // as the pipes are close, we expect the process to die,
  194. // therefore CmdRun to unblock. Wait for CmdRun
  195. setTimeout(t, "Waiting for CmdRun timed out", 2*time.Second, func() {
  196. <-c1
  197. })
  198. // Client disconnect after run -i should cause stdin to be closed, which should
  199. // cause /bin/cat to exit.
  200. setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
  201. container := globalRuntime.List()[0]
  202. container.Wait()
  203. if container.State.Running {
  204. t.Fatalf("/bin/cat is still running after closing stdin")
  205. }
  206. })
  207. }
  208. // Expected behaviour: the process dies when the client disconnects
  209. func TestRunDisconnectTty(t *testing.T) {
  210. stdin, stdinPipe := io.Pipe()
  211. stdout, stdoutPipe := io.Pipe()
  212. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  213. defer cleanup(globalRuntime)
  214. c1 := make(chan struct{})
  215. go func() {
  216. // We're simulating a disconnect so the return value doesn't matter. What matters is the
  217. // fact that CmdRun returns.
  218. if err := cli.CmdRun("-i", "-t", unitTestImageID, "/bin/cat"); err != nil {
  219. utils.Debugf("Error CmdRun: %s\n", err)
  220. }
  221. close(c1)
  222. }()
  223. setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
  224. for {
  225. // Client disconnect after run -i should keep stdin out in TTY mode
  226. l := globalRuntime.List()
  227. if len(l) == 1 && l[0].State.Running {
  228. break
  229. }
  230. time.Sleep(10 * time.Millisecond)
  231. }
  232. })
  233. // Client disconnect after run -i should keep stdin out in TTY mode
  234. container := globalRuntime.List()[0]
  235. setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
  236. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  237. t.Fatal(err)
  238. }
  239. })
  240. // Close pipes (simulate disconnect)
  241. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  242. t.Fatal(err)
  243. }
  244. // In tty mode, we expect the process to stay alive even after client's stdin closes.
  245. // Do not wait for run to finish
  246. // Give some time to monitor to do his thing
  247. container.WaitTimeout(500 * time.Millisecond)
  248. if !container.State.Running {
  249. t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
  250. }
  251. }
  252. // TestAttachStdin checks attaching to stdin without stdout and stderr.
  253. // 'docker run -i -a stdin' should sends the client's stdin to the command,
  254. // then detach from it and print the container id.
  255. func TestRunAttachStdin(t *testing.T) {
  256. stdin, stdinPipe := io.Pipe()
  257. stdout, stdoutPipe := io.Pipe()
  258. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  259. defer cleanup(globalRuntime)
  260. ch := make(chan struct{})
  261. go func() {
  262. defer close(ch)
  263. cli.CmdRun("-i", "-a", "stdin", unitTestImageID, "sh", "-c", "echo hello && cat && sleep 5")
  264. }()
  265. // Send input to the command, close stdin
  266. setTimeout(t, "Write timed out", 10*time.Second, func() {
  267. if _, err := stdinPipe.Write([]byte("hi there\n")); err != nil {
  268. t.Fatal(err)
  269. }
  270. if err := stdinPipe.Close(); err != nil {
  271. t.Fatal(err)
  272. }
  273. })
  274. container := globalRuntime.List()[0]
  275. // Check output
  276. setTimeout(t, "Reading command output time out", 10*time.Second, func() {
  277. cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
  278. if err != nil {
  279. t.Fatal(err)
  280. }
  281. if cmdOutput != container.ShortID()+"\n" {
  282. t.Fatalf("Wrong output: should be '%s', not '%s'\n", container.ShortID()+"\n", cmdOutput)
  283. }
  284. })
  285. // wait for CmdRun to return
  286. setTimeout(t, "Waiting for CmdRun timed out", 5*time.Second, func() {
  287. <-ch
  288. })
  289. setTimeout(t, "Waiting for command to exit timed out", 10*time.Second, func() {
  290. container.Wait()
  291. })
  292. // Check logs
  293. if cmdLogs, err := container.ReadLog("json"); err != nil {
  294. t.Fatal(err)
  295. } else {
  296. if output, err := ioutil.ReadAll(cmdLogs); err != nil {
  297. t.Fatal(err)
  298. } else {
  299. expectedLogs := []string{"{\"log\":\"hello\\n\",\"stream\":\"stdout\"", "{\"log\":\"hi there\\n\",\"stream\":\"stdout\""}
  300. for _, expectedLog := range expectedLogs {
  301. if !strings.Contains(string(output), expectedLog) {
  302. t.Fatalf("Unexpected logs: should contains '%s', it is not '%s'\n", expectedLog, output)
  303. }
  304. }
  305. }
  306. }
  307. }
  308. // TestRunDetach checks attaching and detaching with the escape sequence.
  309. func TestRunDetach(t *testing.T) {
  310. stdin, stdinPipe := io.Pipe()
  311. stdout, stdoutPipe := io.Pipe()
  312. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  313. defer cleanup(globalRuntime)
  314. ch := make(chan struct{})
  315. go func() {
  316. defer close(ch)
  317. cli.CmdRun("-i", "-t", unitTestImageID, "cat")
  318. }()
  319. setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
  320. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  321. t.Fatal(err)
  322. }
  323. })
  324. container := globalRuntime.List()[0]
  325. setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
  326. stdinPipe.Write([]byte{'', ''})
  327. if err := stdinPipe.Close(); err != nil {
  328. t.Fatal(err)
  329. }
  330. })
  331. // wait for CmdRun to return
  332. setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() {
  333. <-ch
  334. })
  335. time.Sleep(500 * time.Millisecond)
  336. if !container.State.Running {
  337. t.Fatal("The detached container should be still running")
  338. }
  339. setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() {
  340. container.Kill()
  341. container.Wait()
  342. })
  343. }
  344. // TestAttachDetach checks that attach in tty mode can be detached
  345. func TestAttachDetach(t *testing.T) {
  346. stdin, stdinPipe := io.Pipe()
  347. stdout, stdoutPipe := io.Pipe()
  348. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  349. defer cleanup(globalRuntime)
  350. go stdout.Read(make([]byte, 1024))
  351. setTimeout(t, "Starting container timed out", 2*time.Second, func() {
  352. if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil {
  353. t.Fatal(err)
  354. }
  355. })
  356. container := globalRuntime.List()[0]
  357. stdin, stdinPipe = io.Pipe()
  358. stdout, stdoutPipe = io.Pipe()
  359. cli = NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  360. ch := make(chan struct{})
  361. go func() {
  362. defer close(ch)
  363. if err := cli.CmdAttach(container.ShortID()); err != nil {
  364. t.Fatal(err)
  365. }
  366. }()
  367. setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
  368. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  369. t.Fatal(err)
  370. }
  371. })
  372. setTimeout(t, "Escape sequence timeout", 5*time.Second, func() {
  373. stdinPipe.Write([]byte{'', ''})
  374. if err := stdinPipe.Close(); err != nil {
  375. t.Fatal(err)
  376. }
  377. })
  378. // wait for CmdRun to return
  379. setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() {
  380. <-ch
  381. })
  382. time.Sleep(500 * time.Millisecond)
  383. if !container.State.Running {
  384. t.Fatal("The detached container should be still running")
  385. }
  386. setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() {
  387. container.Kill()
  388. container.Wait()
  389. })
  390. }
  391. // Expected behaviour, the process stays alive when the client disconnects
  392. func TestAttachDisconnect(t *testing.T) {
  393. stdin, stdinPipe := io.Pipe()
  394. stdout, stdoutPipe := io.Pipe()
  395. cli := NewDockerCli(stdin, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  396. defer cleanup(globalRuntime)
  397. go func() {
  398. // Start a process in daemon mode
  399. if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil {
  400. utils.Debugf("Error CmdRun: %s\n", err)
  401. }
  402. }()
  403. setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() {
  404. if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil {
  405. t.Fatal(err)
  406. }
  407. })
  408. setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
  409. for {
  410. l := globalRuntime.List()
  411. if len(l) == 1 && l[0].State.Running {
  412. break
  413. }
  414. time.Sleep(10 * time.Millisecond)
  415. }
  416. })
  417. container := globalRuntime.List()[0]
  418. // Attach to it
  419. c1 := make(chan struct{})
  420. go func() {
  421. // We're simulating a disconnect so the return value doesn't matter. What matters is the
  422. // fact that CmdAttach returns.
  423. cli.CmdAttach(container.ID)
  424. close(c1)
  425. }()
  426. setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() {
  427. if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
  428. t.Fatal(err)
  429. }
  430. })
  431. // Close pipes (client disconnects)
  432. if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil {
  433. t.Fatal(err)
  434. }
  435. // Wait for attach to finish, the client disconnected, therefore, Attach finished his job
  436. setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() {
  437. <-c1
  438. })
  439. // We closed stdin, expect /bin/cat to still be running
  440. // Wait a little bit to make sure container.monitor() did his thing
  441. err := container.WaitTimeout(500 * time.Millisecond)
  442. if err == nil || !container.State.Running {
  443. t.Fatalf("/bin/cat is not running after closing stdin")
  444. }
  445. // Try to avoid the timeout in destroy. Best effort, don't check error
  446. cStdin, _ := container.StdinPipe()
  447. cStdin.Close()
  448. container.Wait()
  449. }
  450. // Expected behaviour: container gets deleted automatically after exit
  451. func TestRunAutoRemove(t *testing.T) {
  452. t.Skip("Fixme. Skipping test for now, race condition")
  453. stdout, stdoutPipe := io.Pipe()
  454. cli := NewDockerCli(nil, stdoutPipe, ioutil.Discard, testDaemonProto, testDaemonAddr)
  455. defer cleanup(globalRuntime)
  456. c := make(chan struct{})
  457. go func() {
  458. defer close(c)
  459. if err := cli.CmdRun("-rm", unitTestImageID, "hostname"); err != nil {
  460. t.Fatal(err)
  461. }
  462. }()
  463. var temporaryContainerID string
  464. setTimeout(t, "Reading command output time out", 2*time.Second, func() {
  465. cmdOutput, err := bufio.NewReader(stdout).ReadString('\n')
  466. if err != nil {
  467. t.Fatal(err)
  468. }
  469. temporaryContainerID = cmdOutput
  470. if err := closeWrap(stdout, stdoutPipe); err != nil {
  471. t.Fatal(err)
  472. }
  473. })
  474. setTimeout(t, "CmdRun timed out", 5*time.Second, func() {
  475. <-c
  476. })
  477. time.Sleep(500 * time.Millisecond)
  478. if len(globalRuntime.List()) > 0 {
  479. t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID)
  480. }
  481. }