docker_cli_service_logs_test.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. // +build !windows
  2. package main
  3. import (
  4. "bufio"
  5. "fmt"
  6. "io"
  7. "os/exec"
  8. "strings"
  9. "time"
  10. "github.com/docker/docker/integration-cli/checker"
  11. "github.com/docker/docker/integration-cli/daemon"
  12. icmd "github.com/docker/docker/pkg/testutil/cmd"
  13. "github.com/go-check/check"
  14. )
  15. type logMessage struct {
  16. err error
  17. data []byte
  18. }
  19. func (s *DockerSwarmSuite) TestServiceLogs(c *check.C) {
  20. d := s.AddDaemon(c, true, true)
  21. // we have multiple services here for detecting the goroutine issue #28915
  22. services := map[string]string{
  23. "TestServiceLogs1": "hello1",
  24. "TestServiceLogs2": "hello2",
  25. }
  26. for name, message := range services {
  27. out, err := d.Cmd("service", "create", "--name", name, "busybox",
  28. "sh", "-c", fmt.Sprintf("echo %s; tail -f /dev/null", message))
  29. c.Assert(err, checker.IsNil)
  30. c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
  31. }
  32. // make sure task has been deployed.
  33. waitAndAssert(c, defaultReconciliationTimeout,
  34. d.CheckRunningTaskImages, checker.DeepEquals,
  35. map[string]int{"busybox": len(services)})
  36. for name, message := range services {
  37. out, err := d.Cmd("service", "logs", name)
  38. c.Assert(err, checker.IsNil)
  39. c.Logf("log for %q: %q", name, out)
  40. c.Assert(out, checker.Contains, message)
  41. }
  42. }
  43. // countLogLines returns a closure that can be used with waitAndAssert to
  44. // verify that a minimum number of expected container log messages have been
  45. // output.
  46. func countLogLines(d *daemon.Swarm, name string) func(*check.C) (interface{}, check.CommentInterface) {
  47. return func(c *check.C) (interface{}, check.CommentInterface) {
  48. result := icmd.RunCmd(d.Command("service", "logs", "-t", name))
  49. result.Assert(c, icmd.Expected{})
  50. lines := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
  51. return len(lines), check.Commentf("output, %q", string(result.Stdout()))
  52. }
  53. }
  54. func (s *DockerSwarmSuite) TestServiceLogsCompleteness(c *check.C) {
  55. d := s.AddDaemon(c, true, true)
  56. name := "TestServiceLogsCompleteness"
  57. // make a service that prints 6 lines
  58. out, err := d.Cmd("service", "create", "--name", name, "busybox", "sh", "-c", "for line in $(seq 0 5); do echo log test $line; done; sleep 100000")
  59. c.Assert(err, checker.IsNil)
  60. c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
  61. // make sure task has been deployed.
  62. waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
  63. // and make sure we have all the log lines
  64. waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 6)
  65. out, err = d.Cmd("service", "logs", name)
  66. c.Assert(err, checker.IsNil)
  67. lines := strings.Split(strings.TrimSpace(out), "\n")
  68. // i have heard anecdotal reports that logs may come back from the engine
  69. // mis-ordered. if this tests fails, consider the possibility that that
  70. // might be occurring
  71. for i, line := range lines {
  72. c.Assert(line, checker.Contains, fmt.Sprintf("log test %v", i))
  73. }
  74. }
  75. func (s *DockerSwarmSuite) TestServiceLogsTail(c *check.C) {
  76. d := s.AddDaemon(c, true, true)
  77. name := "TestServiceLogsTail"
  78. // make a service that prints 6 lines
  79. out, err := d.Cmd("service", "create", "--name", name, "busybox", "sh", "-c", "for line in $(seq 1 6); do echo log test $line; done; sleep 100000")
  80. c.Assert(err, checker.IsNil)
  81. c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
  82. // make sure task has been deployed.
  83. waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
  84. waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 6)
  85. out, err = d.Cmd("service", "logs", "--tail=2", name)
  86. c.Assert(err, checker.IsNil)
  87. lines := strings.Split(strings.TrimSpace(out), "\n")
  88. for i, line := range lines {
  89. // doing i+5 is hacky but not too fragile, it's good enough. if it flakes something else is wrong
  90. c.Assert(line, checker.Contains, fmt.Sprintf("log test %v", i+5))
  91. }
  92. }
  93. func (s *DockerSwarmSuite) TestServiceLogsSince(c *check.C) {
  94. // See DockerSuite.TestLogsSince, which is where this comes from
  95. d := s.AddDaemon(c, true, true)
  96. name := "TestServiceLogsSince"
  97. out, err := d.Cmd("service", "create", "--name", name, "busybox", "sh", "-c", "for i in $(seq 1 3); do sleep .1; echo log$i; done; sleep 10000000")
  98. c.Assert(err, checker.IsNil)
  99. c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
  100. waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
  101. // wait a sec for the logs to come in
  102. waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 3)
  103. out, err = d.Cmd("service", "logs", "-t", name)
  104. c.Assert(err, checker.IsNil)
  105. log2Line := strings.Split(strings.Split(out, "\n")[1], " ")
  106. t, err := time.Parse(time.RFC3339Nano, log2Line[0]) // timestamp log2 is written
  107. c.Assert(err, checker.IsNil)
  108. u := t.Add(50 * time.Millisecond) // add .05s so log1 & log2 don't show up
  109. since := u.Format(time.RFC3339Nano)
  110. out, err = d.Cmd("service", "logs", "-t", fmt.Sprintf("--since=%v", since), name)
  111. c.Assert(err, checker.IsNil)
  112. unexpected := []string{"log1", "log2"}
  113. expected := []string{"log3"}
  114. for _, v := range unexpected {
  115. c.Assert(out, checker.Not(checker.Contains), v, check.Commentf("unexpected log message returned, since=%v", u))
  116. }
  117. for _, v := range expected {
  118. c.Assert(out, checker.Contains, v, check.Commentf("expected log message %v, was not present, since=%v", u))
  119. }
  120. }
  121. func (s *DockerSwarmSuite) TestServiceLogsFollow(c *check.C) {
  122. d := s.AddDaemon(c, true, true)
  123. name := "TestServiceLogsFollow"
  124. out, err := d.Cmd("service", "create", "--name", name, "busybox", "sh", "-c", "while true; do echo log test; sleep 0.1; done")
  125. c.Assert(err, checker.IsNil)
  126. c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
  127. // make sure task has been deployed.
  128. waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
  129. args := []string{"service", "logs", "-f", name}
  130. cmd := exec.Command(dockerBinary, d.PrependHostArg(args)...)
  131. r, w := io.Pipe()
  132. cmd.Stdout = w
  133. cmd.Stderr = w
  134. c.Assert(cmd.Start(), checker.IsNil)
  135. // Make sure pipe is written to
  136. ch := make(chan *logMessage)
  137. done := make(chan struct{})
  138. go func() {
  139. reader := bufio.NewReader(r)
  140. for {
  141. msg := &logMessage{}
  142. msg.data, _, msg.err = reader.ReadLine()
  143. select {
  144. case ch <- msg:
  145. case <-done:
  146. return
  147. }
  148. }
  149. }()
  150. for i := 0; i < 3; i++ {
  151. msg := <-ch
  152. c.Assert(msg.err, checker.IsNil)
  153. c.Assert(string(msg.data), checker.Contains, "log test")
  154. }
  155. close(done)
  156. c.Assert(cmd.Process.Kill(), checker.IsNil)
  157. }
  158. func (s *DockerSwarmSuite) TestServiceLogsTaskLogs(c *check.C) {
  159. d := s.AddDaemon(c, true, true)
  160. name := "TestServicelogsTaskLogs"
  161. replicas := 2
  162. result := icmd.RunCmd(d.Command(
  163. // create a service with the name
  164. "service", "create", "--name", name,
  165. // which has some number of replicas
  166. fmt.Sprintf("--replicas=%v", replicas),
  167. // which has this the task id as an environment variable templated in
  168. "--env", "TASK={{.Task.ID}}",
  169. // and runs this command to print exaclty 6 logs lines
  170. "busybox", "sh", "-c", "for line in $(seq 0 5); do echo $TASK log test $line; done; sleep 100000",
  171. ))
  172. result.Assert(c, icmd.Expected{})
  173. // ^^ verify that we get no error
  174. // then verify that we have an id in stdout
  175. id := strings.TrimSpace(result.Stdout())
  176. c.Assert(id, checker.Not(checker.Equals), "")
  177. // so, right here, we're basically inspecting by id and returning only
  178. // the ID. if they don't match, the service doesn't exist.
  179. result = icmd.RunCmd(d.Command("service", "inspect", "--format=\"{{.ID}}\"", id))
  180. result.Assert(c, icmd.Expected{Out: id})
  181. // make sure task has been deployed.
  182. waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, replicas)
  183. waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 6*replicas)
  184. // get the task ids
  185. result = icmd.RunCmd(d.Command("service", "ps", "-q", name))
  186. result.Assert(c, icmd.Expected{})
  187. // make sure we have two tasks
  188. taskIDs := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
  189. c.Assert(taskIDs, checker.HasLen, replicas)
  190. for _, taskID := range taskIDs {
  191. c.Logf("checking task %v", taskID)
  192. result := icmd.RunCmd(d.Command("service", "logs", taskID))
  193. result.Assert(c, icmd.Expected{})
  194. lines := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
  195. c.Logf("checking messages for %v", taskID)
  196. for i, line := range lines {
  197. // make sure the message is in order
  198. c.Assert(line, checker.Contains, fmt.Sprintf("log test %v", i))
  199. // make sure it contains the task id
  200. c.Assert(line, checker.Contains, taskID)
  201. }
  202. }
  203. }
  204. func (s *DockerSwarmSuite) TestServiceLogsTTY(c *check.C) {
  205. d := s.AddDaemon(c, true, true)
  206. name := "TestServiceLogsTTY"
  207. result := icmd.RunCmd(d.Command(
  208. // create a service
  209. "service", "create",
  210. // name it $name
  211. "--name", name,
  212. // use a TTY
  213. "-t",
  214. // busybox image, shell string
  215. "busybox", "sh", "-c",
  216. // echo to stdout and stderr
  217. "echo out; (echo err 1>&2); sleep 10000",
  218. ))
  219. result.Assert(c, icmd.Expected{})
  220. id := strings.TrimSpace(result.Stdout())
  221. c.Assert(id, checker.Not(checker.Equals), "")
  222. // so, right here, we're basically inspecting by id and returning only
  223. // the ID. if they don't match, the service doesn't exist.
  224. result = icmd.RunCmd(d.Command("service", "inspect", "--format=\"{{.ID}}\"", id))
  225. result.Assert(c, icmd.Expected{Out: id})
  226. // make sure task has been deployed.
  227. waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
  228. // and make sure we have all the log lines
  229. waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 2)
  230. cmd := d.Command("service", "logs", name)
  231. result = icmd.RunCmd(cmd)
  232. // for some reason there is carriage return in the output. i think this is
  233. // just expected.
  234. c.Assert(result, icmd.Matches, icmd.Expected{Out: "out\r\nerr\r\n"})
  235. }