open-nomad/drivers/docker/docklog/docker_logger_test.go
Seth Hoenig 16cab10346 ci: fix docker logger not supported test
This test checks for behavior when asking for logs of a docker task
configured with a log driver that does not support streaming logs.

Previously this was using the 'gelf' log driver, but it seems that no
longer returns an error as expected. Instead we can just use the 'none'
log driver, which has the desired effect

2022-04-19T10:23:19.129-0500 [ERROR] docklog/docker_logger.go:133: log streaming ended with terminal error: error="API error (501): configured logging driver does not support reading"
2022-04-19 10:27:01 -05:00

355 lines
8.4 KiB
Go

package docklog
import (
"bytes"
"errors"
"fmt"
"runtime"
"testing"
"time"
docker "github.com/fsouza/go-dockerclient"
"github.com/hashicorp/nomad/ci"
ctu "github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func testContainerDetails() (image string, imageName string, imageTag string) {
name := "busybox"
tag := "1"
if runtime.GOOS == "windows" {
name = "hashicorpnomad/busybox-windows"
tag = "server2016-0.1"
}
if testutil.IsCI() {
// In CI, use HashiCorp Mirror to avoid DockerHub rate limiting
name = "docker.mirror.hashicorp.services/" + name
}
return name + ":" + tag, name, tag
}
func TestDockerLogger_Success(t *testing.T) {
ci.Parallel(t)
ctu.DockerCompatible(t)
require := require.New(t)
containerImage, containerImageName, containerImageTag := testContainerDetails()
client, err := docker.NewClientFromEnv()
if err != nil {
t.Skip("docker unavailable:", err)
}
if img, err := client.InspectImage(containerImage); err != nil || img == nil {
t.Log("image not found locally, downloading...")
err = client.PullImage(docker.PullImageOptions{
Repository: containerImageName,
Tag: containerImageTag,
}, docker.AuthConfiguration{})
require.NoError(err, "failed to pull image")
}
containerConf := docker.CreateContainerOptions{
Config: &docker.Config{
Cmd: []string{
"sh", "-c", "touch ~/docklog; tail -f ~/docklog",
},
Image: containerImage,
},
Context: context.Background(),
}
container, err := client.CreateContainer(containerConf)
require.NoError(err)
defer client.RemoveContainer(docker.RemoveContainerOptions{
ID: container.ID,
Force: true,
})
err = client.StartContainer(container.ID, nil)
require.NoError(err)
testutil.WaitForResult(func() (bool, error) {
container, err = client.InspectContainer(container.ID)
if err != nil {
return false, err
}
if !container.State.Running {
return false, fmt.Errorf("container not running")
}
return true, nil
}, func(err error) {
require.NoError(err)
})
stdout := &noopCloser{bytes.NewBuffer(nil)}
stderr := &noopCloser{bytes.NewBuffer(nil)}
dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger)
dl.stdout = stdout
dl.stderr = stderr
require.NoError(dl.Start(&StartOpts{
ContainerID: container.ID,
}))
echoToContainer(t, client, container.ID, "abc")
echoToContainer(t, client, container.ID, "123")
testutil.WaitForResult(func() (bool, error) {
act := stdout.String()
if "abc\n123\n" != act {
return false, fmt.Errorf("expected abc\\n123\\n for stdout but got %s", act)
}
return true, nil
}, func(err error) {
require.NoError(err)
})
}
func TestDockerLogger_Success_TTY(t *testing.T) {
ci.Parallel(t)
ctu.DockerCompatible(t)
require := require.New(t)
containerImage, containerImageName, containerImageTag := testContainerDetails()
client, err := docker.NewClientFromEnv()
if err != nil {
t.Skip("docker unavailable:", err)
}
if img, err := client.InspectImage(containerImage); err != nil || img == nil {
t.Log("image not found locally, downloading...")
err = client.PullImage(docker.PullImageOptions{
Repository: containerImageName,
Tag: containerImageTag,
}, docker.AuthConfiguration{})
require.NoError(err, "failed to pull image")
}
containerConf := docker.CreateContainerOptions{
Config: &docker.Config{
Cmd: []string{
"sh", "-c", "touch ~/docklog; tail -f ~/docklog",
},
Image: containerImage,
Tty: true,
},
Context: context.Background(),
}
container, err := client.CreateContainer(containerConf)
require.NoError(err)
defer client.RemoveContainer(docker.RemoveContainerOptions{
ID: container.ID,
Force: true,
})
err = client.StartContainer(container.ID, nil)
require.NoError(err)
testutil.WaitForResult(func() (bool, error) {
container, err = client.InspectContainer(container.ID)
if err != nil {
return false, err
}
if !container.State.Running {
return false, fmt.Errorf("container not running")
}
return true, nil
}, func(err error) {
require.NoError(err)
})
stdout := &noopCloser{bytes.NewBuffer(nil)}
stderr := &noopCloser{bytes.NewBuffer(nil)}
dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger)
dl.stdout = stdout
dl.stderr = stderr
require.NoError(dl.Start(&StartOpts{
ContainerID: container.ID,
TTY: true,
}))
echoToContainer(t, client, container.ID, "abc")
echoToContainer(t, client, container.ID, "123")
testutil.WaitForResult(func() (bool, error) {
act := stdout.String()
if "abc\r\n123\r\n" != act {
return false, fmt.Errorf("expected abc\\n123\\n for stdout but got %s", act)
}
return true, nil
}, func(err error) {
require.NoError(err)
})
}
func echoToContainer(t *testing.T, client *docker.Client, id string, line string) {
op := docker.CreateExecOptions{
Container: id,
Cmd: []string{
"/bin/sh", "-c",
fmt.Sprintf("echo %s >>~/docklog", line),
},
}
exec, err := client.CreateExec(op)
require.NoError(t, err)
require.NoError(t, client.StartExec(exec.ID, docker.StartExecOptions{Detach: true}))
}
func TestDockerLogger_LoggingNotSupported(t *testing.T) {
ci.Parallel(t)
ctu.DockerCompatible(t)
containerImage, containerImageName, containerImageTag := testContainerDetails()
client, err := docker.NewClientFromEnv()
if err != nil {
t.Skip("docker unavailable:", err)
}
if img, err := client.InspectImage(containerImage); err != nil || img == nil {
t.Log("image not found locally, downloading...")
err = client.PullImage(docker.PullImageOptions{
Repository: containerImageName,
Tag: containerImageTag,
}, docker.AuthConfiguration{})
require.NoError(t, err, "failed to pull image")
}
containerConf := docker.CreateContainerOptions{
Config: &docker.Config{
Cmd: []string{
"sh", "-c", "touch ~/docklog; tail -f ~/docklog",
},
Image: containerImage,
},
HostConfig: &docker.HostConfig{
LogConfig: docker.LogConfig{
Type: "none",
Config: map[string]string{},
},
},
Context: context.Background(),
}
container, err := client.CreateContainer(containerConf)
require.NoError(t, err)
defer client.RemoveContainer(docker.RemoveContainerOptions{
ID: container.ID,
Force: true,
})
err = client.StartContainer(container.ID, nil)
require.NoError(t, err)
testutil.WaitForResult(func() (bool, error) {
container, err = client.InspectContainer(container.ID)
if err != nil {
return false, err
}
if !container.State.Running {
return false, fmt.Errorf("container not running")
}
return true, nil
}, func(err error) {
require.NoError(t, err)
})
stdout := &noopCloser{bytes.NewBuffer(nil)}
stderr := &noopCloser{bytes.NewBuffer(nil)}
dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger)
dl.stdout = stdout
dl.stderr = stderr
require.NoError(t, dl.Start(&StartOpts{
ContainerID: container.ID,
}))
select {
case <-dl.doneCh:
case <-time.After(10 * time.Second):
require.Fail(t, "timeout while waiting for docker_logging to terminate")
}
}
type noopCloser struct {
*bytes.Buffer
}
func (*noopCloser) Close() error {
return nil
}
func TestNextBackoff(t *testing.T) {
ci.Parallel(t)
cases := []struct {
currentBackoff float64
min float64
max float64
}{
{0.0, 0.5, 1.15},
{5.0, 5.0, 16},
{120, 120, 120},
}
for _, c := range cases {
t.Run(fmt.Sprintf("case %v", c.currentBackoff), func(t *testing.T) {
next := nextBackoff(c.currentBackoff)
t.Logf("computed backoff(%v) = %v", c.currentBackoff, next)
require.True(t, next >= c.min, "next backoff is smaller than expected")
require.True(t, next <= c.max, "next backoff is larger than expected")
})
}
}
func TestIsLoggingTerminalError(t *testing.T) {
ci.Parallel(t)
terminalErrs := []error{
errors.New("docker returned: configured logging driver does not support reading"),
&docker.Error{
Status: 501,
Message: "configured logging driver does not support reading",
},
&docker.Error{
Status: 501,
Message: "not implemented",
},
}
for _, err := range terminalErrs {
require.Truef(t, isLoggingTerminalError(err), "error should be terminal: %v", err)
}
nonTerminalErrs := []error{
errors.New("not expected"),
&docker.Error{
Status: 503,
Message: "Service Unavailable",
},
}
for _, err := range nonTerminalErrs {
require.Falsef(t, isLoggingTerminalError(err), "error should be terminal: %v", err)
}
}