github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/integration-cli/docker_cli_service_logs_test.go (about)

     1  // +build !windows
     2  
     3  package main
     4  
     5  import (
     6  	"bufio"
     7  	"fmt"
     8  	"io"
     9  	"os/exec"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/docker/docker/integration-cli/checker"
    14  	"github.com/docker/docker/integration-cli/daemon"
    15  	"github.com/go-check/check"
    16  	"github.com/gotestyourself/gotestyourself/icmd"
    17  )
    18  
    19  type logMessage struct {
    20  	err  error
    21  	data []byte
    22  }
    23  
    24  func (s *DockerSwarmSuite) TestServiceLogs(c *check.C) {
    25  	d := s.AddDaemon(c, true, true)
    26  
    27  	// we have multiple services here for detecting the goroutine issue #28915
    28  	services := map[string]string{
    29  		"TestServiceLogs1": "hello1",
    30  		"TestServiceLogs2": "hello2",
    31  	}
    32  
    33  	for name, message := range services {
    34  		out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox",
    35  			"sh", "-c", fmt.Sprintf("echo %s; tail -f /dev/null", message))
    36  		c.Assert(err, checker.IsNil)
    37  		c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
    38  	}
    39  
    40  	// make sure task has been deployed.
    41  	waitAndAssert(c, defaultReconciliationTimeout,
    42  		d.CheckRunningTaskImages, checker.DeepEquals,
    43  		map[string]int{"busybox:latest": len(services)})
    44  
    45  	for name, message := range services {
    46  		out, err := d.Cmd("service", "logs", name)
    47  		c.Assert(err, checker.IsNil)
    48  		c.Logf("log for %q: %q", name, out)
    49  		c.Assert(out, checker.Contains, message)
    50  	}
    51  }
    52  
    53  // countLogLines returns a closure that can be used with waitAndAssert to
    54  // verify that a minimum number of expected container log messages have been
    55  // output.
    56  func countLogLines(d *daemon.Swarm, name string) func(*check.C) (interface{}, check.CommentInterface) {
    57  	return func(c *check.C) (interface{}, check.CommentInterface) {
    58  		result := icmd.RunCmd(d.Command("service", "logs", "-t", "--raw", name))
    59  		result.Assert(c, icmd.Expected{})
    60  		// if this returns an emptystring, trying to split it later will return
    61  		// an array containing emptystring. a valid log line will NEVER be
    62  		// emptystring because we ask for the timestamp.
    63  		if result.Stdout() == "" {
    64  			return 0, check.Commentf("Empty stdout")
    65  		}
    66  		lines := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
    67  		return len(lines), check.Commentf("output, %q", string(result.Stdout()))
    68  	}
    69  }
    70  
    71  func (s *DockerSwarmSuite) TestServiceLogsCompleteness(c *check.C) {
    72  	d := s.AddDaemon(c, true, true)
    73  
    74  	name := "TestServiceLogsCompleteness"
    75  
    76  	// make a service that prints 6 lines
    77  	out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "sh", "-c", "for line in $(seq 0 5); do echo log test $line; done; sleep 100000")
    78  	c.Assert(err, checker.IsNil)
    79  	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
    80  
    81  	// make sure task has been deployed.
    82  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
    83  	// and make sure we have all the log lines
    84  	waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 6)
    85  
    86  	out, err = d.Cmd("service", "logs", name)
    87  	c.Assert(err, checker.IsNil)
    88  	lines := strings.Split(strings.TrimSpace(out), "\n")
    89  
    90  	// i have heard anecdotal reports that logs may come back from the engine
    91  	// mis-ordered. if this tests fails, consider the possibility that that
    92  	// might be occurring
    93  	for i, line := range lines {
    94  		c.Assert(line, checker.Contains, fmt.Sprintf("log test %v", i))
    95  	}
    96  }
    97  
    98  func (s *DockerSwarmSuite) TestServiceLogsTail(c *check.C) {
    99  	d := s.AddDaemon(c, true, true)
   100  
   101  	name := "TestServiceLogsTail"
   102  
   103  	// make a service that prints 6 lines
   104  	out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "sh", "-c", "for line in $(seq 1 6); do echo log test $line; done; sleep 100000")
   105  	c.Assert(err, checker.IsNil)
   106  	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
   107  
   108  	// make sure task has been deployed.
   109  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
   110  	waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 6)
   111  
   112  	out, err = d.Cmd("service", "logs", "--tail=2", name)
   113  	c.Assert(err, checker.IsNil)
   114  	lines := strings.Split(strings.TrimSpace(out), "\n")
   115  
   116  	for i, line := range lines {
   117  		// doing i+5 is hacky but not too fragile, it's good enough. if it flakes something else is wrong
   118  		c.Assert(line, checker.Contains, fmt.Sprintf("log test %v", i+5))
   119  	}
   120  }
   121  
   122  func (s *DockerSwarmSuite) TestServiceLogsSince(c *check.C) {
   123  	// See DockerSuite.TestLogsSince, which is where this comes from
   124  	d := s.AddDaemon(c, true, true)
   125  
   126  	name := "TestServiceLogsSince"
   127  
   128  	out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "sh", "-c", "for i in $(seq 1 3); do sleep .1; echo log$i; done; sleep 10000000")
   129  	c.Assert(err, checker.IsNil)
   130  	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
   131  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
   132  	// wait a sec for the logs to come in
   133  	waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 3)
   134  
   135  	out, err = d.Cmd("service", "logs", "-t", name)
   136  	c.Assert(err, checker.IsNil)
   137  
   138  	log2Line := strings.Split(strings.Split(out, "\n")[1], " ")
   139  	t, err := time.Parse(time.RFC3339Nano, log2Line[0]) // timestamp log2 is written
   140  	c.Assert(err, checker.IsNil)
   141  	u := t.Add(50 * time.Millisecond) // add .05s so log1 & log2 don't show up
   142  	since := u.Format(time.RFC3339Nano)
   143  
   144  	out, err = d.Cmd("service", "logs", "-t", fmt.Sprintf("--since=%v", since), name)
   145  	c.Assert(err, checker.IsNil)
   146  
   147  	unexpected := []string{"log1", "log2"}
   148  	expected := []string{"log3"}
   149  	for _, v := range unexpected {
   150  		c.Assert(out, checker.Not(checker.Contains), v, check.Commentf("unexpected log message returned, since=%v", u))
   151  	}
   152  	for _, v := range expected {
   153  		c.Assert(out, checker.Contains, v, check.Commentf("expected log message %v, was not present, since=%v", u))
   154  	}
   155  }
   156  
   157  func (s *DockerSwarmSuite) TestServiceLogsFollow(c *check.C) {
   158  	d := s.AddDaemon(c, true, true)
   159  
   160  	name := "TestServiceLogsFollow"
   161  
   162  	out, err := d.Cmd("service", "create", "--detach", "--no-resolve-image", "--name", name, "busybox", "sh", "-c", "while true; do echo log test; sleep 0.1; done")
   163  	c.Assert(err, checker.IsNil)
   164  	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
   165  
   166  	// make sure task has been deployed.
   167  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
   168  
   169  	args := []string{"service", "logs", "-f", name}
   170  	cmd := exec.Command(dockerBinary, d.PrependHostArg(args)...)
   171  	r, w := io.Pipe()
   172  	cmd.Stdout = w
   173  	cmd.Stderr = w
   174  	c.Assert(cmd.Start(), checker.IsNil)
   175  	go cmd.Wait()
   176  
   177  	// Make sure pipe is written to
   178  	ch := make(chan *logMessage)
   179  	done := make(chan struct{})
   180  	go func() {
   181  		reader := bufio.NewReader(r)
   182  		for {
   183  			msg := &logMessage{}
   184  			msg.data, _, msg.err = reader.ReadLine()
   185  			select {
   186  			case ch <- msg:
   187  			case <-done:
   188  				return
   189  			}
   190  		}
   191  	}()
   192  
   193  	for i := 0; i < 3; i++ {
   194  		msg := <-ch
   195  		c.Assert(msg.err, checker.IsNil)
   196  		c.Assert(string(msg.data), checker.Contains, "log test")
   197  	}
   198  	close(done)
   199  
   200  	c.Assert(cmd.Process.Kill(), checker.IsNil)
   201  }
   202  
   203  func (s *DockerSwarmSuite) TestServiceLogsTaskLogs(c *check.C) {
   204  	d := s.AddDaemon(c, true, true)
   205  
   206  	name := "TestServicelogsTaskLogs"
   207  	replicas := 2
   208  
   209  	result := icmd.RunCmd(d.Command(
   210  		// create a service with the name
   211  		"service", "create", "--detach", "--no-resolve-image", "--name", name,
   212  		// which has some number of replicas
   213  		fmt.Sprintf("--replicas=%v", replicas),
   214  		// which has this the task id as an environment variable templated in
   215  		"--env", "TASK={{.Task.ID}}",
   216  		// and runs this command to print exactly 6 logs lines
   217  		"busybox", "sh", "-c", "for line in $(seq 0 5); do echo $TASK log test $line; done; sleep 100000",
   218  	))
   219  	result.Assert(c, icmd.Expected{})
   220  	// ^^ verify that we get no error
   221  	// then verify that we have an id in stdout
   222  	id := strings.TrimSpace(result.Stdout())
   223  	c.Assert(id, checker.Not(checker.Equals), "")
   224  	// so, right here, we're basically inspecting by id and returning only
   225  	// the ID. if they don't match, the service doesn't exist.
   226  	result = icmd.RunCmd(d.Command("service", "inspect", "--format=\"{{.ID}}\"", id))
   227  	result.Assert(c, icmd.Expected{Out: id})
   228  
   229  	// make sure task has been deployed.
   230  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, replicas)
   231  	waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 6*replicas)
   232  
   233  	// get the task ids
   234  	result = icmd.RunCmd(d.Command("service", "ps", "-q", name))
   235  	result.Assert(c, icmd.Expected{})
   236  	// make sure we have two tasks
   237  	taskIDs := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
   238  	c.Assert(taskIDs, checker.HasLen, replicas)
   239  
   240  	for _, taskID := range taskIDs {
   241  		c.Logf("checking task %v", taskID)
   242  		result := icmd.RunCmd(d.Command("service", "logs", taskID))
   243  		result.Assert(c, icmd.Expected{})
   244  		lines := strings.Split(strings.TrimSpace(result.Stdout()), "\n")
   245  
   246  		c.Logf("checking messages for %v", taskID)
   247  		for i, line := range lines {
   248  			// make sure the message is in order
   249  			c.Assert(line, checker.Contains, fmt.Sprintf("log test %v", i))
   250  			// make sure it contains the task id
   251  			c.Assert(line, checker.Contains, taskID)
   252  		}
   253  	}
   254  }
   255  
   256  func (s *DockerSwarmSuite) TestServiceLogsTTY(c *check.C) {
   257  	d := s.AddDaemon(c, true, true)
   258  
   259  	name := "TestServiceLogsTTY"
   260  
   261  	result := icmd.RunCmd(d.Command(
   262  		// create a service
   263  		"service", "create", "--detach", "--no-resolve-image",
   264  		// name it $name
   265  		"--name", name,
   266  		// use a TTY
   267  		"-t",
   268  		// busybox image, shell string
   269  		"busybox", "sh", "-c",
   270  		// echo to stdout and stderr
   271  		"echo out; (echo err 1>&2); sleep 10000",
   272  	))
   273  
   274  	result.Assert(c, icmd.Expected{})
   275  	id := strings.TrimSpace(result.Stdout())
   276  	c.Assert(id, checker.Not(checker.Equals), "")
   277  	// so, right here, we're basically inspecting by id and returning only
   278  	// the ID. if they don't match, the service doesn't exist.
   279  	result = icmd.RunCmd(d.Command("service", "inspect", "--format=\"{{.ID}}\"", id))
   280  	result.Assert(c, icmd.Expected{Out: id})
   281  
   282  	// make sure task has been deployed.
   283  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
   284  	// and make sure we have all the log lines
   285  	waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 2)
   286  
   287  	cmd := d.Command("service", "logs", "--raw", name)
   288  	result = icmd.RunCmd(cmd)
   289  	// for some reason there is carriage return in the output. i think this is
   290  	// just expected.
   291  	result.Assert(c, icmd.Expected{Out: "out\r\nerr\r\n"})
   292  }
   293  
   294  func (s *DockerSwarmSuite) TestServiceLogsNoHangDeletedContainer(c *check.C) {
   295  	d := s.AddDaemon(c, true, true)
   296  
   297  	name := "TestServiceLogsNoHangDeletedContainer"
   298  
   299  	result := icmd.RunCmd(d.Command(
   300  		// create a service
   301  		"service", "create", "--detach", "--no-resolve-image",
   302  		// name it $name
   303  		"--name", name,
   304  		// busybox image, shell string
   305  		"busybox", "sh", "-c",
   306  		// echo to stdout and stderr
   307  		"while true; do echo line; sleep 2; done",
   308  	))
   309  
   310  	// confirm that the command succeeded
   311  	result.Assert(c, icmd.Expected{})
   312  	// get the service id
   313  	id := strings.TrimSpace(result.Stdout())
   314  	c.Assert(id, checker.Not(checker.Equals), "")
   315  
   316  	// make sure task has been deployed.
   317  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
   318  	// and make sure we have all the log lines
   319  	waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 2)
   320  
   321  	// now find and nuke the container
   322  	result = icmd.RunCmd(d.Command("ps", "-q"))
   323  	containerID := strings.TrimSpace(result.Stdout())
   324  	c.Assert(containerID, checker.Not(checker.Equals), "")
   325  	result = icmd.RunCmd(d.Command("stop", containerID))
   326  	result.Assert(c, icmd.Expected{Out: containerID})
   327  	result = icmd.RunCmd(d.Command("rm", containerID))
   328  	result.Assert(c, icmd.Expected{Out: containerID})
   329  
   330  	// run logs. use tail 2 to make sure we don't try to get a bunch of logs
   331  	// somehow and slow down execution time
   332  	cmd := d.Command("service", "logs", "--tail", "2", id)
   333  	// start the command and then wait for it to finish with a 3 second timeout
   334  	result = icmd.StartCmd(cmd)
   335  	result = icmd.WaitOnCmd(3*time.Second, result)
   336  
   337  	// then, assert that the result matches expected. if the command timed out,
   338  	// if the command is timed out, result.Timeout will be true, but the
   339  	// Expected defaults to false
   340  	result.Assert(c, icmd.Expected{})
   341  }
   342  
   343  func (s *DockerSwarmSuite) TestServiceLogsDetails(c *check.C) {
   344  	d := s.AddDaemon(c, true, true)
   345  
   346  	name := "TestServiceLogsDetails"
   347  
   348  	result := icmd.RunCmd(d.Command(
   349  		// create a service
   350  		"service", "create", "--detach", "--no-resolve-image",
   351  		// name it $name
   352  		"--name", name,
   353  		// add an environment variable
   354  		"--env", "asdf=test1",
   355  		// add a log driver (without explicitly setting a driver, log-opt doesn't work)
   356  		"--log-driver", "json-file",
   357  		// add a log option to print the environment variable
   358  		"--log-opt", "env=asdf",
   359  		// busybox image, shell string
   360  		"busybox", "sh", "-c",
   361  		// make a log line
   362  		"echo LogLine; while true; do sleep 1; done;",
   363  	))
   364  
   365  	result.Assert(c, icmd.Expected{})
   366  	id := strings.TrimSpace(result.Stdout())
   367  	c.Assert(id, checker.Not(checker.Equals), "")
   368  
   369  	// make sure task has been deployed
   370  	waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 1)
   371  	// and make sure we have all the log lines
   372  	waitAndAssert(c, defaultReconciliationTimeout, countLogLines(d, name), checker.Equals, 1)
   373  
   374  	// First, test without pretty printing
   375  	// call service logs with details. set raw to skip pretty printing
   376  	result = icmd.RunCmd(d.Command("service", "logs", "--raw", "--details", name))
   377  	// in this case, we should get details and we should get log message, but
   378  	// there will also be context as details (which will fall after the detail
   379  	// we inserted in alphabetical order
   380  	result.Assert(c, icmd.Expected{Out: "asdf=test1"})
   381  	result.Assert(c, icmd.Expected{Out: "LogLine"})
   382  
   383  	// call service logs with details. this time, don't pass raw
   384  	result = icmd.RunCmd(d.Command("service", "logs", "--details", id))
   385  	// in this case, we should get details space logmessage as well. the context
   386  	// is part of the pretty part of the logline
   387  	result.Assert(c, icmd.Expected{Out: "asdf=test1 LogLine"})
   388  }