2018-03-06 10:32:47 -05:00
// +build !windows
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
"unicode"
"github.com/docker/docker/integration-cli/checker"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/go-check/check"
"github.com/kr/pty"
"golang.org/x/sys/unix"
)
// #5979
func ( s * DockerSuite ) TestEventsRedirectStdout ( c * check . C ) {
since := daemonUnixTime ( c )
dockerCmd ( c , "run" , "busybox" , "true" )
file , err := ioutil . TempFile ( "" , "" )
c . Assert ( err , checker . IsNil , check . Commentf ( "could not create temp file" ) )
defer os . Remove ( file . Name ( ) )
command := fmt . Sprintf ( "%s events --since=%s --until=%s > %s" , dockerBinary , since , daemonUnixTime ( c ) , file . Name ( ) )
_ , tty , err := pty . Open ( )
c . Assert ( err , checker . IsNil , check . Commentf ( "Could not open pty" ) )
cmd := exec . Command ( "sh" , "-c" , command )
cmd . Stdin = tty
cmd . Stdout = tty
cmd . Stderr = tty
c . Assert ( cmd . Run ( ) , checker . IsNil , check . Commentf ( "run err for command %q" , command ) )
scanner := bufio . NewScanner ( file )
for scanner . Scan ( ) {
for _ , ch := range scanner . Text ( ) {
c . Assert ( unicode . IsControl ( ch ) , checker . False , check . Commentf ( "found control character %v" , [ ] byte ( string ( ch ) ) ) )
}
}
c . Assert ( scanner . Err ( ) , checker . IsNil , check . Commentf ( "Scan err for command %q" , command ) )
}
func ( s * DockerSuite ) TestEventsOOMDisableFalse ( c * check . C ) {
2018-03-11 11:52:12 -04:00
testRequires ( c , DaemonIsLinux , oomControl , memoryLimitSupport , swapMemorySupport , NotPpc64le )
2018-03-06 10:32:47 -05:00
errChan := make ( chan error )
go func ( ) {
defer close ( errChan )
out , exitCode , _ := dockerCmdWithError ( "run" , "--name" , "oomFalse" , "-m" , "10MB" , "busybox" , "sh" , "-c" , "x=a; while true; do x=$x$x$x$x; done" )
if expected := 137 ; exitCode != expected {
errChan <- fmt . Errorf ( "wrong exit code for OOM container: expected %d, got %d (output: %q)" , expected , exitCode , out )
}
} ( )
select {
case err := <- errChan :
c . Assert ( err , checker . IsNil )
case <- time . After ( 30 * time . Second ) :
c . Fatal ( "Timeout waiting for container to die on OOM" )
}
out , _ := dockerCmd ( c , "events" , "--since=0" , "-f" , "container=oomFalse" , "--until" , daemonUnixTime ( c ) )
events := strings . Split ( strings . TrimSuffix ( out , "\n" ) , "\n" )
nEvents := len ( events )
c . Assert ( nEvents , checker . GreaterOrEqualThan , 5 ) //Missing expected event
c . Assert ( parseEventAction ( c , events [ nEvents - 5 ] ) , checker . Equals , "create" )
c . Assert ( parseEventAction ( c , events [ nEvents - 4 ] ) , checker . Equals , "attach" )
c . Assert ( parseEventAction ( c , events [ nEvents - 3 ] ) , checker . Equals , "start" )
c . Assert ( parseEventAction ( c , events [ nEvents - 2 ] ) , checker . Equals , "oom" )
c . Assert ( parseEventAction ( c , events [ nEvents - 1 ] ) , checker . Equals , "die" )
}
func ( s * DockerSuite ) TestEventsOOMDisableTrue ( c * check . C ) {
2018-03-11 11:52:12 -04:00
testRequires ( c , DaemonIsLinux , oomControl , memoryLimitSupport , NotArm , swapMemorySupport , NotPpc64le )
2018-03-06 10:32:47 -05:00
errChan := make ( chan error )
observer , err := newEventObserver ( c )
c . Assert ( err , checker . IsNil )
err = observer . Start ( )
c . Assert ( err , checker . IsNil )
defer observer . Stop ( )
go func ( ) {
defer close ( errChan )
out , exitCode , _ := dockerCmdWithError ( "run" , "--oom-kill-disable=true" , "--name" , "oomTrue" , "-m" , "10MB" , "busybox" , "sh" , "-c" , "x=a; while true; do x=$x$x$x$x; done" )
if expected := 137 ; exitCode != expected {
errChan <- fmt . Errorf ( "wrong exit code for OOM container: expected %d, got %d (output: %q)" , expected , exitCode , out )
}
} ( )
c . Assert ( waitRun ( "oomTrue" ) , checker . IsNil )
defer dockerCmdWithResult ( "kill" , "oomTrue" )
containerID := inspectField ( c , "oomTrue" , "Id" )
testActions := map [ string ] chan bool {
"oom" : make ( chan bool ) ,
}
matcher := matchEventLine ( containerID , "container" , testActions )
processor := processEventMatch ( testActions )
go observer . Match ( matcher , processor )
select {
case <- time . After ( 20 * time . Second ) :
observer . CheckEventError ( c , containerID , "oom" , matcher )
case <- testActions [ "oom" ] :
// ignore, done
case errRun := <- errChan :
if errRun != nil {
c . Fatalf ( "%v" , errRun )
} else {
c . Fatalf ( "container should be still running but it's not" )
}
}
status := inspectField ( c , "oomTrue" , "State.Status" )
c . Assert ( strings . TrimSpace ( status ) , checker . Equals , "running" , check . Commentf ( "container should be still running" ) )
}
// #18453
func ( s * DockerSuite ) TestEventsContainerFilterByName ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
cOut , _ := dockerCmd ( c , "run" , "--name=foo" , "-d" , "busybox" , "top" )
c1 := strings . TrimSpace ( cOut )
waitRun ( "foo" )
cOut , _ = dockerCmd ( c , "run" , "--name=bar" , "-d" , "busybox" , "top" )
c2 := strings . TrimSpace ( cOut )
waitRun ( "bar" )
out , _ := dockerCmd ( c , "events" , "-f" , "container=foo" , "--since=0" , "--until" , daemonUnixTime ( c ) )
c . Assert ( out , checker . Contains , c1 , check . Commentf ( out ) )
c . Assert ( out , checker . Not ( checker . Contains ) , c2 , check . Commentf ( out ) )
}
// #18453
func ( s * DockerSuite ) TestEventsContainerFilterBeforeCreate ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
buf := & bytes . Buffer { }
cmd := exec . Command ( dockerBinary , "events" , "-f" , "container=foo" , "--since=0" )
cmd . Stdout = buf
c . Assert ( cmd . Start ( ) , check . IsNil )
defer cmd . Wait ( )
defer cmd . Process . Kill ( )
// Sleep for a second to make sure we are testing the case where events are listened before container starts.
time . Sleep ( time . Second )
id , _ := dockerCmd ( c , "run" , "--name=foo" , "-d" , "busybox" , "top" )
cID := strings . TrimSpace ( id )
for i := 0 ; ; i ++ {
out := buf . String ( )
if strings . Contains ( out , cID ) {
break
}
if i > 30 {
c . Fatalf ( "Missing event of container (foo, %v), got %q" , cID , out )
}
time . Sleep ( 500 * time . Millisecond )
}
}
func ( s * DockerSuite ) TestVolumeEvents ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
since := daemonUnixTime ( c )
// Observe create/mount volume actions
dockerCmd ( c , "volume" , "create" , "test-event-volume-local" )
dockerCmd ( c , "run" , "--name" , "test-volume-container" , "--volume" , "test-event-volume-local:/foo" , "-d" , "busybox" , "true" )
waitRun ( "test-volume-container" )
// Observe unmount/destroy volume actions
dockerCmd ( c , "rm" , "-f" , "test-volume-container" )
dockerCmd ( c , "volume" , "rm" , "test-event-volume-local" )
until := daemonUnixTime ( c )
out , _ := dockerCmd ( c , "events" , "--since" , since , "--until" , until )
events := strings . Split ( strings . TrimSpace ( out ) , "\n" )
c . Assert ( len ( events ) , checker . GreaterThan , 4 )
volumeEvents := eventActionsByIDAndType ( c , events , "test-event-volume-local" , "volume" )
c . Assert ( volumeEvents , checker . HasLen , 4 )
c . Assert ( volumeEvents [ 0 ] , checker . Equals , "create" )
c . Assert ( volumeEvents [ 1 ] , checker . Equals , "mount" )
c . Assert ( volumeEvents [ 2 ] , checker . Equals , "unmount" )
c . Assert ( volumeEvents [ 3 ] , checker . Equals , "destroy" )
}
func ( s * DockerSuite ) TestNetworkEvents ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
since := daemonUnixTime ( c )
// Observe create/connect network actions
dockerCmd ( c , "network" , "create" , "test-event-network-local" )
dockerCmd ( c , "run" , "--name" , "test-network-container" , "--net" , "test-event-network-local" , "-d" , "busybox" , "true" )
waitRun ( "test-network-container" )
// Observe disconnect/destroy network actions
dockerCmd ( c , "rm" , "-f" , "test-network-container" )
dockerCmd ( c , "network" , "rm" , "test-event-network-local" )
until := daemonUnixTime ( c )
out , _ := dockerCmd ( c , "events" , "--since" , since , "--until" , until )
events := strings . Split ( strings . TrimSpace ( out ) , "\n" )
c . Assert ( len ( events ) , checker . GreaterThan , 4 )
netEvents := eventActionsByIDAndType ( c , events , "test-event-network-local" , "network" )
c . Assert ( netEvents , checker . HasLen , 4 )
c . Assert ( netEvents [ 0 ] , checker . Equals , "create" )
c . Assert ( netEvents [ 1 ] , checker . Equals , "connect" )
c . Assert ( netEvents [ 2 ] , checker . Equals , "disconnect" )
c . Assert ( netEvents [ 3 ] , checker . Equals , "destroy" )
}
func ( s * DockerSuite ) TestEventsContainerWithMultiNetwork ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
// Observe create/connect network actions
dockerCmd ( c , "network" , "create" , "test-event-network-local-1" )
dockerCmd ( c , "network" , "create" , "test-event-network-local-2" )
dockerCmd ( c , "run" , "--name" , "test-network-container" , "--net" , "test-event-network-local-1" , "-td" , "busybox" , "sh" )
waitRun ( "test-network-container" )
dockerCmd ( c , "network" , "connect" , "test-event-network-local-2" , "test-network-container" )
since := daemonUnixTime ( c )
dockerCmd ( c , "stop" , "-t" , "1" , "test-network-container" )
until := daemonUnixTime ( c )
out , _ := dockerCmd ( c , "events" , "--since" , since , "--until" , until , "-f" , "type=network" )
netEvents := strings . Split ( strings . TrimSpace ( out ) , "\n" )
// received two network disconnect events
c . Assert ( len ( netEvents ) , checker . Equals , 2 )
c . Assert ( netEvents [ 0 ] , checker . Contains , "disconnect" )
c . Assert ( netEvents [ 1 ] , checker . Contains , "disconnect" )
//both networks appeared in the network event output
c . Assert ( out , checker . Contains , "test-event-network-local-1" )
c . Assert ( out , checker . Contains , "test-event-network-local-2" )
}
func ( s * DockerSuite ) TestEventsStreaming ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
observer , err := newEventObserver ( c )
c . Assert ( err , checker . IsNil )
err = observer . Start ( )
c . Assert ( err , checker . IsNil )
defer observer . Stop ( )
out , _ := dockerCmd ( c , "run" , "-d" , "busybox:latest" , "true" )
containerID := strings . TrimSpace ( out )
testActions := map [ string ] chan bool {
"create" : make ( chan bool , 1 ) ,
"start" : make ( chan bool , 1 ) ,
"die" : make ( chan bool , 1 ) ,
"destroy" : make ( chan bool , 1 ) ,
}
matcher := matchEventLine ( containerID , "container" , testActions )
processor := processEventMatch ( testActions )
go observer . Match ( matcher , processor )
select {
case <- time . After ( 5 * time . Second ) :
observer . CheckEventError ( c , containerID , "create" , matcher )
case <- testActions [ "create" ] :
// ignore, done
}
select {
case <- time . After ( 5 * time . Second ) :
observer . CheckEventError ( c , containerID , "start" , matcher )
case <- testActions [ "start" ] :
// ignore, done
}
select {
case <- time . After ( 5 * time . Second ) :
observer . CheckEventError ( c , containerID , "die" , matcher )
case <- testActions [ "die" ] :
// ignore, done
}
dockerCmd ( c , "rm" , containerID )
select {
case <- time . After ( 5 * time . Second ) :
observer . CheckEventError ( c , containerID , "destroy" , matcher )
case <- testActions [ "destroy" ] :
// ignore, done
}
}
func ( s * DockerSuite ) TestEventsImageUntagDelete ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
observer , err := newEventObserver ( c )
c . Assert ( err , checker . IsNil )
err = observer . Start ( )
c . Assert ( err , checker . IsNil )
defer observer . Stop ( )
name := "testimageevents"
buildImageSuccessfully ( c , name , build . WithDockerfile ( ` FROM scratch
MAINTAINER "docker" ` ) )
imageID := getIDByName ( c , name )
c . Assert ( deleteImages ( name ) , checker . IsNil )
testActions := map [ string ] chan bool {
"untag" : make ( chan bool , 1 ) ,
"delete" : make ( chan bool , 1 ) ,
}
matcher := matchEventLine ( imageID , "image" , testActions )
processor := processEventMatch ( testActions )
go observer . Match ( matcher , processor )
select {
case <- time . After ( 10 * time . Second ) :
observer . CheckEventError ( c , imageID , "untag" , matcher )
case <- testActions [ "untag" ] :
// ignore, done
}
select {
case <- time . After ( 10 * time . Second ) :
observer . CheckEventError ( c , imageID , "delete" , matcher )
case <- testActions [ "delete" ] :
// ignore, done
}
}
func ( s * DockerSuite ) TestEventsFilterVolumeAndNetworkType ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
since := daemonUnixTime ( c )
dockerCmd ( c , "network" , "create" , "test-event-network-type" )
dockerCmd ( c , "volume" , "create" , "test-event-volume-type" )
out , _ := dockerCmd ( c , "events" , "--filter" , "type=volume" , "--filter" , "type=network" , "--since" , since , "--until" , daemonUnixTime ( c ) )
events := strings . Split ( strings . TrimSpace ( out ) , "\n" )
c . Assert ( len ( events ) , checker . GreaterOrEqualThan , 2 , check . Commentf ( out ) )
networkActions := eventActionsByIDAndType ( c , events , "test-event-network-type" , "network" )
volumeActions := eventActionsByIDAndType ( c , events , "test-event-volume-type" , "volume" )
c . Assert ( volumeActions [ 0 ] , checker . Equals , "create" )
c . Assert ( networkActions [ 0 ] , checker . Equals , "create" )
}
func ( s * DockerSuite ) TestEventsFilterVolumeID ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
since := daemonUnixTime ( c )
dockerCmd ( c , "volume" , "create" , "test-event-volume-id" )
out , _ := dockerCmd ( c , "events" , "--filter" , "volume=test-event-volume-id" , "--since" , since , "--until" , daemonUnixTime ( c ) )
events := strings . Split ( strings . TrimSpace ( out ) , "\n" )
c . Assert ( events , checker . HasLen , 1 )
c . Assert ( events [ 0 ] , checker . Contains , "test-event-volume-id" )
c . Assert ( events [ 0 ] , checker . Contains , "driver=local" )
}
func ( s * DockerSuite ) TestEventsFilterNetworkID ( c * check . C ) {
testRequires ( c , DaemonIsLinux )
since := daemonUnixTime ( c )
dockerCmd ( c , "network" , "create" , "test-event-network-local" )
out , _ := dockerCmd ( c , "events" , "--filter" , "network=test-event-network-local" , "--since" , since , "--until" , daemonUnixTime ( c ) )
events := strings . Split ( strings . TrimSpace ( out ) , "\n" )
c . Assert ( events , checker . HasLen , 1 )
c . Assert ( events [ 0 ] , checker . Contains , "test-event-network-local" )
c . Assert ( events [ 0 ] , checker . Contains , "type=bridge" )
}
func ( s * DockerDaemonSuite ) TestDaemonEvents ( c * check . C ) {
testRequires ( c , SameHostDaemon , DaemonIsLinux )
// daemon config file
configFilePath := "test.json"
configFile , err := os . Create ( configFilePath )
c . Assert ( err , checker . IsNil )
defer os . Remove ( configFilePath )
daemonConfig := ` { "labels":["foo=bar"]} `
fmt . Fprintf ( configFile , "%s" , daemonConfig )
configFile . Close ( )
s . d . Start ( c , fmt . Sprintf ( "--config-file=%s" , configFilePath ) )
// Get daemon ID
out , err := s . d . Cmd ( "info" )
c . Assert ( err , checker . IsNil )
daemonID := ""
daemonName := ""
for _ , line := range strings . Split ( out , "\n" ) {
if strings . HasPrefix ( line , "ID: " ) {
daemonID = strings . TrimPrefix ( line , "ID: " )
} else if strings . HasPrefix ( line , "Name: " ) {
daemonName = strings . TrimPrefix ( line , "Name: " )
}
}
c . Assert ( daemonID , checker . Not ( checker . Equals ) , "" )
configFile , err = os . Create ( configFilePath )
c . Assert ( err , checker . IsNil )
daemonConfig = ` { "max-concurrent-downloads":1,"labels":["bar=foo"], "shutdown-timeout": 10} `
fmt . Fprintf ( configFile , "%s" , daemonConfig )
configFile . Close ( )
c . Assert ( s . d . Signal ( unix . SIGHUP ) , checker . IsNil )
time . Sleep ( 3 * time . Second )
out , err = s . d . Cmd ( "events" , "--since=0" , "--until" , daemonUnixTime ( c ) )
c . Assert ( err , checker . IsNil )
// only check for values known (daemon ID/name) or explicitly set above,
// otherwise just check for names being present.
expectedSubstrings := [ ] string {
" daemon reload " + daemonID + " " ,
"(allow-nondistributable-artifacts=[" ,
" cluster-advertise=, " ,
" cluster-store=, " ,
" cluster-store-opts={" ,
" debug=true, " ,
" default-ipc-mode=" ,
" default-runtime=" ,
" default-shm-size=" ,
" insecure-registries=[" ,
" labels=[\"bar=foo\"], " ,
" live-restore=" ,
" max-concurrent-downloads=1, " ,
" max-concurrent-uploads=5, " ,
" name=" + daemonName ,
" registry-mirrors=[" ,
" runtimes=" ,
" shutdown-timeout=10)" ,
}
for _ , s := range expectedSubstrings {
c . Assert ( out , checker . Contains , s )
}
}
func ( s * DockerDaemonSuite ) TestDaemonEventsWithFilters ( c * check . C ) {
testRequires ( c , SameHostDaemon , DaemonIsLinux )
// daemon config file
configFilePath := "test.json"
configFile , err := os . Create ( configFilePath )
c . Assert ( err , checker . IsNil )
defer os . Remove ( configFilePath )
daemonConfig := ` { "labels":["foo=bar"]} `
fmt . Fprintf ( configFile , "%s" , daemonConfig )
configFile . Close ( )
s . d . Start ( c , fmt . Sprintf ( "--config-file=%s" , configFilePath ) )
// Get daemon ID
out , err := s . d . Cmd ( "info" )
c . Assert ( err , checker . IsNil )
daemonID := ""
daemonName := ""
for _ , line := range strings . Split ( out , "\n" ) {
if strings . HasPrefix ( line , "ID: " ) {
daemonID = strings . TrimPrefix ( line , "ID: " )
} else if strings . HasPrefix ( line , "Name: " ) {
daemonName = strings . TrimPrefix ( line , "Name: " )
}
}
c . Assert ( daemonID , checker . Not ( checker . Equals ) , "" )
c . Assert ( s . d . Signal ( unix . SIGHUP ) , checker . IsNil )
time . Sleep ( 3 * time . Second )
out , err = s . d . Cmd ( "events" , "--since=0" , "--until" , daemonUnixTime ( c ) , "--filter" , fmt . Sprintf ( "daemon=%s" , daemonID ) )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , fmt . Sprintf ( "daemon reload %s" , daemonID ) )
out , err = s . d . Cmd ( "events" , "--since=0" , "--until" , daemonUnixTime ( c ) , "--filter" , fmt . Sprintf ( "daemon=%s" , daemonName ) )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , fmt . Sprintf ( "daemon reload %s" , daemonID ) )
out , err = s . d . Cmd ( "events" , "--since=0" , "--until" , daemonUnixTime ( c ) , "--filter" , "daemon=foo" )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Not ( checker . Contains ) , fmt . Sprintf ( "daemon reload %s" , daemonID ) )
out , err = s . d . Cmd ( "events" , "--since=0" , "--until" , daemonUnixTime ( c ) , "--filter" , "type=daemon" )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Contains , fmt . Sprintf ( "daemon reload %s" , daemonID ) )
out , err = s . d . Cmd ( "events" , "--since=0" , "--until" , daemonUnixTime ( c ) , "--filter" , "type=container" )
c . Assert ( err , checker . IsNil )
c . Assert ( out , checker . Not ( checker . Contains ) , fmt . Sprintf ( "daemon reload %s" , daemonID ) )
}