2016-10-26 04:19:32 -04:00
// +build !windows
package main
import (
"bufio"
2016-11-18 15:38:36 -05:00
"fmt"
2016-10-26 04:19:32 -04:00
"io"
"os/exec"
"strings"
2017-03-01 19:37:25 -05:00
"time"
2016-10-26 04:19:32 -04:00
2016-12-30 12:23:00 -05:00
"github.com/docker/docker/integration-cli/checker"
2017-03-01 19:37:25 -05:00
"github.com/docker/docker/integration-cli/daemon"
2017-03-21 14:35:55 -04:00
icmd "github.com/docker/docker/pkg/testutil/cmd"
2016-10-26 04:19:32 -04:00
"github.com/go-check/check"
)
type logMessage struct {
err error
data [ ] byte
}
func ( s * DockerSwarmSuite ) TestServiceLogs ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
2016-11-30 00:48:44 -05:00
// we have multiple services here for detecting the goroutine issue #28915
services := map [ string ] string {
"TestServiceLogs1" : "hello1" ,
"TestServiceLogs2" : "hello2" ,
}
for name , message := range services {
2017-05-18 15:23:28 -04:00
out , err := d . Cmd ( "service" , "create" , "--no-resolve-image" , "--name" , name , "busybox" ,
2016-11-30 00:48:44 -05:00
"sh" , "-c" , fmt . Sprintf ( "echo %s; tail -f /dev/null" , message ) )
c . Assert ( err , checker . IsNil )
c . Assert ( strings . TrimSpace ( out ) , checker . Not ( checker . Equals ) , "" )
}
2016-11-18 15:38:36 -05:00
// make sure task has been deployed.
2016-11-30 00:48:44 -05:00
waitAndAssert ( c , defaultReconciliationTimeout ,
2017-03-03 19:24:15 -05:00
d . CheckRunningTaskImages , checker . DeepEquals ,
map [ string ] int { "busybox" : len ( services ) } )
2016-11-30 00:48:44 -05:00
for name , message := range services {
out , err := d . Cmd ( "service" , "logs" , name )
c . Assert ( err , checker . IsNil )
c . Logf ( "log for %q: %q" , name , out )
c . Assert ( out , checker . Contains , message )
}
2016-11-18 15:38:36 -05:00
}
2017-03-01 19:37:25 -05:00
// countLogLines returns a closure that can be used with waitAndAssert to
// verify that a minimum number of expected container log messages have been
// output.
func countLogLines ( d * daemon . Swarm , name string ) func ( * check . C ) ( interface { } , check . CommentInterface ) {
return func ( c * check . C ) ( interface { } , check . CommentInterface ) {
2017-04-04 18:52:19 -04:00
result := icmd . RunCmd ( d . Command ( "service" , "logs" , "-t" , "--raw" , name ) )
2017-03-21 14:35:55 -04:00
result . Assert ( c , icmd . Expected { } )
2017-04-04 18:52:19 -04:00
// if this returns an emptystring, trying to split it later will return
// an array containing emptystring. a valid log line will NEVER be
// emptystring because we ask for the timestamp.
if result . Stdout ( ) == "" {
return 0 , check . Commentf ( "Empty stdout" )
}
2017-03-21 14:35:55 -04:00
lines := strings . Split ( strings . TrimSpace ( result . Stdout ( ) ) , "\n" )
return len ( lines ) , check . Commentf ( "output, %q" , string ( result . Stdout ( ) ) )
2017-03-01 19:37:25 -05:00
}
}
func ( s * DockerSwarmSuite ) TestServiceLogsCompleteness ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
name := "TestServiceLogsCompleteness"
// make a service that prints 6 lines
2017-05-18 15:23:28 -04:00
out , err := d . Cmd ( "service" , "create" , "--no-resolve-image" , "--name" , name , "busybox" , "sh" , "-c" , "for line in $(seq 0 5); do echo log test $line; done; sleep 100000" )
2017-03-01 19:37:25 -05:00
c . Assert ( err , checker . IsNil )
c . Assert ( strings . TrimSpace ( out ) , checker . Not ( checker . Equals ) , "" )
// make sure task has been deployed.
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , 1 )
// and make sure we have all the log lines
waitAndAssert ( c , defaultReconciliationTimeout , countLogLines ( d , name ) , checker . Equals , 6 )
2017-03-21 14:35:55 -04:00
out , err = d . Cmd ( "service" , "logs" , name )
c . Assert ( err , checker . IsNil )
lines := strings . Split ( strings . TrimSpace ( out ) , "\n" )
2017-03-01 19:37:25 -05:00
// i have heard anecdotal reports that logs may come back from the engine
// mis-ordered. if this tests fails, consider the possibility that that
// might be occurring
2017-03-21 14:35:55 -04:00
for i , line := range lines {
c . Assert ( line , checker . Contains , fmt . Sprintf ( "log test %v" , i ) )
2017-03-01 19:37:25 -05:00
}
}
func ( s * DockerSwarmSuite ) TestServiceLogsTail ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
name := "TestServiceLogsTail"
// make a service that prints 6 lines
2017-05-18 15:23:28 -04:00
out , err := d . Cmd ( "service" , "create" , "--no-resolve-image" , "--name" , name , "busybox" , "sh" , "-c" , "for line in $(seq 1 6); do echo log test $line; done; sleep 100000" )
2017-03-01 19:37:25 -05:00
c . Assert ( err , checker . IsNil )
c . Assert ( strings . TrimSpace ( out ) , checker . Not ( checker . Equals ) , "" )
// make sure task has been deployed.
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , 1 )
waitAndAssert ( c , defaultReconciliationTimeout , countLogLines ( d , name ) , checker . Equals , 6 )
2017-03-21 14:35:55 -04:00
out , err = d . Cmd ( "service" , "logs" , "--tail=2" , name )
c . Assert ( err , checker . IsNil )
lines := strings . Split ( strings . TrimSpace ( out ) , "\n" )
2017-03-01 19:37:25 -05:00
2017-03-21 14:35:55 -04:00
for i , line := range lines {
// doing i+5 is hacky but not too fragile, it's good enough. if it flakes something else is wrong
c . Assert ( line , checker . Contains , fmt . Sprintf ( "log test %v" , i + 5 ) )
2017-03-01 19:37:25 -05:00
}
}
func ( s * DockerSwarmSuite ) TestServiceLogsSince ( c * check . C ) {
// See DockerSuite.TestLogsSince, which is where this comes from
d := s . AddDaemon ( c , true , true )
name := "TestServiceLogsSince"
2017-05-18 15:23:28 -04:00
out , err := d . Cmd ( "service" , "create" , "--no-resolve-image" , "--name" , name , "busybox" , "sh" , "-c" , "for i in $(seq 1 3); do sleep .1; echo log$i; done; sleep 10000000" )
2017-03-01 19:37:25 -05:00
c . Assert ( err , checker . IsNil )
c . Assert ( strings . TrimSpace ( out ) , checker . Not ( checker . Equals ) , "" )
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , 1 )
// wait a sec for the logs to come in
waitAndAssert ( c , defaultReconciliationTimeout , countLogLines ( d , name ) , checker . Equals , 3 )
out , err = d . Cmd ( "service" , "logs" , "-t" , name )
c . Assert ( err , checker . IsNil )
log2Line := strings . Split ( strings . Split ( out , "\n" ) [ 1 ] , " " )
t , err := time . Parse ( time . RFC3339Nano , log2Line [ 0 ] ) // timestamp log2 is written
c . Assert ( err , checker . IsNil )
u := t . Add ( 50 * time . Millisecond ) // add .05s so log1 & log2 don't show up
since := u . Format ( time . RFC3339Nano )
out , err = d . Cmd ( "service" , "logs" , "-t" , fmt . Sprintf ( "--since=%v" , since ) , name )
c . Assert ( err , checker . IsNil )
unexpected := [ ] string { "log1" , "log2" }
expected := [ ] string { "log3" }
for _ , v := range unexpected {
c . Assert ( out , checker . Not ( checker . Contains ) , v , check . Commentf ( "unexpected log message returned, since=%v" , u ) )
}
for _ , v := range expected {
c . Assert ( out , checker . Contains , v , check . Commentf ( "expected log message %v, was not present, since=%v" , u ) )
}
}
2016-11-18 15:38:36 -05:00
func ( s * DockerSwarmSuite ) TestServiceLogsFollow ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
name := "TestServiceLogsFollow"
2017-05-18 15:23:28 -04:00
out , err := d . Cmd ( "service" , "create" , "--no-resolve-image" , "--name" , name , "busybox" , "sh" , "-c" , "while true; do echo log test; sleep 0.1; done" )
2016-10-26 04:19:32 -04:00
c . Assert ( err , checker . IsNil )
c . Assert ( strings . TrimSpace ( out ) , checker . Not ( checker . Equals ) , "" )
// make sure task has been deployed.
2016-12-09 04:17:53 -05:00
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , 1 )
2016-10-26 04:19:32 -04:00
args := [ ] string { "service" , "logs" , "-f" , name }
2016-12-09 04:17:53 -05:00
cmd := exec . Command ( dockerBinary , d . PrependHostArg ( args ) ... )
2016-10-26 04:19:32 -04:00
r , w := io . Pipe ( )
cmd . Stdout = w
cmd . Stderr = w
c . Assert ( cmd . Start ( ) , checker . IsNil )
// Make sure pipe is written to
ch := make ( chan * logMessage )
2016-11-18 15:38:36 -05:00
done := make ( chan struct { } )
2016-10-26 04:19:32 -04:00
go func ( ) {
reader := bufio . NewReader ( r )
2016-11-18 15:38:36 -05:00
for {
msg := & logMessage { }
msg . data , _ , msg . err = reader . ReadLine ( )
select {
case ch <- msg :
case <- done :
return
}
}
2016-10-26 04:19:32 -04:00
} ( )
2016-11-18 15:38:36 -05:00
for i := 0 ; i < 3 ; i ++ {
msg := <- ch
c . Assert ( msg . err , checker . IsNil )
c . Assert ( string ( msg . data ) , checker . Contains , "log test" )
}
close ( done )
2016-10-26 04:19:32 -04:00
c . Assert ( cmd . Process . Kill ( ) , checker . IsNil )
}
2017-03-21 14:35:55 -04:00
func ( s * DockerSwarmSuite ) TestServiceLogsTaskLogs ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
name := "TestServicelogsTaskLogs"
replicas := 2
result := icmd . RunCmd ( d . Command (
// create a service with the name
2017-05-18 15:23:28 -04:00
"service" , "create" , "--no-resolve-image" , "--name" , name ,
2017-03-21 14:35:55 -04:00
// which has some number of replicas
fmt . Sprintf ( "--replicas=%v" , replicas ) ,
// which has this the task id as an environment variable templated in
"--env" , "TASK={{.Task.ID}}" ,
2017-05-21 19:24:07 -04:00
// and runs this command to print exactly 6 logs lines
2017-03-21 14:35:55 -04:00
"busybox" , "sh" , "-c" , "for line in $(seq 0 5); do echo $TASK log test $line; done; sleep 100000" ,
) )
result . Assert ( c , icmd . Expected { } )
// ^^ verify that we get no error
// then verify that we have an id in stdout
id := strings . TrimSpace ( result . Stdout ( ) )
c . Assert ( id , checker . Not ( checker . Equals ) , "" )
// so, right here, we're basically inspecting by id and returning only
// the ID. if they don't match, the service doesn't exist.
result = icmd . RunCmd ( d . Command ( "service" , "inspect" , "--format=\"{{.ID}}\"" , id ) )
result . Assert ( c , icmd . Expected { Out : id } )
// make sure task has been deployed.
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , replicas )
waitAndAssert ( c , defaultReconciliationTimeout , countLogLines ( d , name ) , checker . Equals , 6 * replicas )
// get the task ids
result = icmd . RunCmd ( d . Command ( "service" , "ps" , "-q" , name ) )
result . Assert ( c , icmd . Expected { } )
// make sure we have two tasks
taskIDs := strings . Split ( strings . TrimSpace ( result . Stdout ( ) ) , "\n" )
c . Assert ( taskIDs , checker . HasLen , replicas )
for _ , taskID := range taskIDs {
c . Logf ( "checking task %v" , taskID )
result := icmd . RunCmd ( d . Command ( "service" , "logs" , taskID ) )
result . Assert ( c , icmd . Expected { } )
lines := strings . Split ( strings . TrimSpace ( result . Stdout ( ) ) , "\n" )
c . Logf ( "checking messages for %v" , taskID )
for i , line := range lines {
// make sure the message is in order
c . Assert ( line , checker . Contains , fmt . Sprintf ( "log test %v" , i ) )
// make sure it contains the task id
c . Assert ( line , checker . Contains , taskID )
}
}
}
2017-03-20 13:07:04 -04:00
func ( s * DockerSwarmSuite ) TestServiceLogsTTY ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
name := "TestServiceLogsTTY"
result := icmd . RunCmd ( d . Command (
// create a service
2017-05-18 15:23:28 -04:00
"service" , "create" , "--no-resolve-image" ,
2017-03-20 13:07:04 -04:00
// name it $name
"--name" , name ,
// use a TTY
"-t" ,
// busybox image, shell string
"busybox" , "sh" , "-c" ,
// echo to stdout and stderr
"echo out; (echo err 1>&2); sleep 10000" ,
) )
result . Assert ( c , icmd . Expected { } )
id := strings . TrimSpace ( result . Stdout ( ) )
c . Assert ( id , checker . Not ( checker . Equals ) , "" )
// so, right here, we're basically inspecting by id and returning only
// the ID. if they don't match, the service doesn't exist.
result = icmd . RunCmd ( d . Command ( "service" , "inspect" , "--format=\"{{.ID}}\"" , id ) )
result . Assert ( c , icmd . Expected { Out : id } )
// make sure task has been deployed.
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , 1 )
// and make sure we have all the log lines
waitAndAssert ( c , defaultReconciliationTimeout , countLogLines ( d , name ) , checker . Equals , 2 )
2017-04-04 18:52:19 -04:00
cmd := d . Command ( "service" , "logs" , "--raw" , name )
2017-03-20 13:07:04 -04:00
result = icmd . RunCmd ( cmd )
// for some reason there is carriage return in the output. i think this is
// just expected.
2017-06-07 14:29:33 -04:00
c . Assert ( result , icmd . Matches , icmd . Expected { Out : "out\r\nerr\r\n" } )
2017-03-20 13:07:04 -04:00
}
2017-05-03 19:04:51 -04:00
func ( s * DockerSwarmSuite ) TestServiceLogsNoHangDeletedContainer ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
name := "TestServiceLogsNoHangDeletedContainer"
result := icmd . RunCmd ( d . Command (
// create a service
2017-05-18 15:23:28 -04:00
"service" , "create" , "--no-resolve-image" ,
2017-05-03 19:04:51 -04:00
// name it $name
"--name" , name ,
// busybox image, shell string
"busybox" , "sh" , "-c" ,
// echo to stdout and stderr
"while true; do echo line; sleep 2; done" ,
) )
// confirm that the command succeeded
c . Assert ( result , icmd . Matches , icmd . Expected { } )
// get the service id
id := strings . TrimSpace ( result . Stdout ( ) )
c . Assert ( id , checker . Not ( checker . Equals ) , "" )
// make sure task has been deployed.
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , 1 )
// and make sure we have all the log lines
waitAndAssert ( c , defaultReconciliationTimeout , countLogLines ( d , name ) , checker . Equals , 2 )
// now find and nuke the container
result = icmd . RunCmd ( d . Command ( "ps" , "-q" ) )
containerID := strings . TrimSpace ( result . Stdout ( ) )
c . Assert ( containerID , checker . Not ( checker . Equals ) , "" )
result = icmd . RunCmd ( d . Command ( "stop" , containerID ) )
c . Assert ( result , icmd . Matches , icmd . Expected { Out : containerID } )
result = icmd . RunCmd ( d . Command ( "rm" , containerID ) )
c . Assert ( result , icmd . Matches , icmd . Expected { Out : containerID } )
// run logs. use tail 2 to make sure we don't try to get a bunch of logs
// somehow and slow down execution time
cmd := d . Command ( "service" , "logs" , "--tail" , "2" , id )
// start the command and then wait for it to finish with a 3 second timeout
result = icmd . StartCmd ( cmd )
result = icmd . WaitOnCmd ( 3 * time . Second , result )
// then, assert that the result matches expected. if the command timed out,
// if the command is timed out, result.Timeout will be true, but the
// Expected defaults to false
c . Assert ( result , icmd . Matches , icmd . Expected { } )
}
2017-04-04 18:52:19 -04:00
func ( s * DockerSwarmSuite ) TestServiceLogsDetails ( c * check . C ) {
d := s . AddDaemon ( c , true , true )
name := "TestServiceLogsDetails"
result := icmd . RunCmd ( d . Command (
// create a service
2017-05-18 15:23:28 -04:00
"service" , "create" , "--no-resolve-image" ,
2017-04-04 18:52:19 -04:00
// name it $name
"--name" , name ,
// add an environment variable
"--env" , "asdf=test1" ,
// add a log driver (without explicitly setting a driver, log-opt doesn't work)
"--log-driver" , "json-file" ,
// add a log option to print the environment variable
"--log-opt" , "env=asdf" ,
// busybox image, shell string
"busybox" , "sh" , "-c" ,
// make a log line
"echo LogLine; while true; do sleep 1; done;" ,
) )
result . Assert ( c , icmd . Expected { } )
id := strings . TrimSpace ( result . Stdout ( ) )
c . Assert ( id , checker . Not ( checker . Equals ) , "" )
// make sure task has been deployed
waitAndAssert ( c , defaultReconciliationTimeout , d . CheckActiveContainerCount , checker . Equals , 1 )
// and make sure we have all the log lines
waitAndAssert ( c , defaultReconciliationTimeout , countLogLines ( d , name ) , checker . Equals , 1 )
// First, test without pretty printing
// call service logs with details. set raw to skip pretty printing
result = icmd . RunCmd ( d . Command ( "service" , "logs" , "--raw" , "--details" , name ) )
// in this case, we should get details and we should get log message, but
// there will also be context as details (which will fall after the detail
// we inserted in alphabetical order
c . Assert ( result , icmd . Matches , icmd . Expected { Out : "asdf=test1" } )
c . Assert ( result , icmd . Matches , icmd . Expected { Out : "LogLine" } )
// call service logs with details. this time, don't pass raw
result = icmd . RunCmd ( d . Command ( "service" , "logs" , "--details" , id ) )
// in this case, we should get details space logmessage as well. the context
// is part of the pretty part of the logline
c . Assert ( result , icmd . Matches , icmd . Expected { Out : "asdf=test1 LogLine" } )
}