16cab10346
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"
355 lines
8.4 KiB
Go
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)
|
|
}
|
|
}
|