2018-10-06 03:32:43 +00:00
|
|
|
package docklog
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2022-07-28 18:46:56 +00:00
|
|
|
"context"
|
2019-03-14 20:23:11 +00:00
|
|
|
"errors"
|
2018-10-06 03:32:43 +00:00
|
|
|
"fmt"
|
2019-01-14 13:16:23 +00:00
|
|
|
"runtime"
|
2018-10-06 03:32:43 +00:00
|
|
|
"testing"
|
2019-03-14 20:23:11 +00:00
|
|
|
"time"
|
2018-10-06 03:32:43 +00:00
|
|
|
|
|
|
|
docker "github.com/fsouza/go-dockerclient"
|
2022-07-28 18:46:56 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
2022-03-15 12:42:43 +00:00
|
|
|
"github.com/hashicorp/nomad/ci"
|
2019-01-07 12:59:13 +00:00
|
|
|
ctu "github.com/hashicorp/nomad/client/testutil"
|
2018-10-06 03:32:43 +00:00
|
|
|
"github.com/hashicorp/nomad/helper/testlog"
|
|
|
|
"github.com/hashicorp/nomad/testutil"
|
|
|
|
)
|
|
|
|
|
2019-01-16 14:06:39 +00:00
|
|
|
func testContainerDetails() (image string, imageName string, imageTag string) {
|
2020-11-02 14:28:02 +00:00
|
|
|
name := "busybox"
|
|
|
|
tag := "1"
|
|
|
|
|
2019-01-14 13:16:23 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
2022-06-08 19:06:00 +00:00
|
|
|
name = "hashicorpdev/busybox-windows"
|
2020-11-02 14:28:02 +00:00
|
|
|
tag = "server2016-0.1"
|
|
|
|
}
|
|
|
|
|
|
|
|
if testutil.IsCI() {
|
|
|
|
// In CI, use HashiCorp Mirror to avoid DockerHub rate limiting
|
|
|
|
name = "docker.mirror.hashicorp.services/" + name
|
2019-01-14 13:16:23 +00:00
|
|
|
}
|
|
|
|
|
2020-11-02 14:28:02 +00:00
|
|
|
return name + ":" + tag, name, tag
|
2019-01-14 13:16:23 +00:00
|
|
|
}
|
|
|
|
|
2019-03-14 20:23:11 +00:00
|
|
|
func TestDockerLogger_Success(t *testing.T) {
|
2022-03-15 12:42:43 +00:00
|
|
|
ci.Parallel(t)
|
2019-01-07 13:27:06 +00:00
|
|
|
ctu.DockerCompatible(t)
|
2019-01-07 12:59:13 +00:00
|
|
|
|
2018-10-06 03:32:43 +00:00
|
|
|
require := require.New(t)
|
|
|
|
|
2019-01-14 13:16:23 +00:00
|
|
|
containerImage, containerImageName, containerImageTag := testContainerDetails()
|
|
|
|
|
2018-10-06 03:32:43 +00:00
|
|
|
client, err := docker.NewClientFromEnv()
|
|
|
|
if err != nil {
|
|
|
|
t.Skip("docker unavailable:", err)
|
|
|
|
}
|
|
|
|
|
2019-01-10 15:04:23 +00:00
|
|
|
if img, err := client.InspectImage(containerImage); err != nil || img == nil {
|
2018-11-13 02:39:23 +00:00
|
|
|
t.Log("image not found locally, downloading...")
|
|
|
|
err = client.PullImage(docker.PullImageOptions{
|
2019-01-10 15:04:23 +00:00
|
|
|
Repository: containerImageName,
|
|
|
|
Tag: containerImageTag,
|
2018-11-13 02:39:23 +00:00
|
|
|
}, docker.AuthConfiguration{})
|
2019-03-14 20:23:11 +00:00
|
|
|
require.NoError(err, "failed to pull image")
|
2018-11-06 07:41:20 +00:00
|
|
|
}
|
|
|
|
|
2018-10-06 03:32:43 +00:00
|
|
|
containerConf := docker.CreateContainerOptions{
|
|
|
|
Config: &docker.Config{
|
|
|
|
Cmd: []string{
|
2019-01-10 15:04:23 +00:00
|
|
|
"sh", "-c", "touch ~/docklog; tail -f ~/docklog",
|
2018-10-06 03:32:43 +00:00
|
|
|
},
|
2019-01-10 15:04:23 +00:00
|
|
|
Image: containerImage,
|
2018-10-06 03:32:43 +00:00
|
|
|
},
|
|
|
|
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)
|
|
|
|
|
2018-10-16 17:49:50 +00:00
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
2018-10-06 03:32:43 +00:00
|
|
|
container, err = client.InspectContainer(container.ID)
|
2018-10-16 17:49:50 +00:00
|
|
|
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)
|
|
|
|
})
|
2018-10-06 03:32:43 +00:00
|
|
|
|
2018-10-16 17:49:50 +00:00
|
|
|
stdout := &noopCloser{bytes.NewBuffer(nil)}
|
|
|
|
stderr := &noopCloser{bytes.NewBuffer(nil)}
|
2018-10-06 03:32:43 +00:00
|
|
|
|
2018-10-16 17:49:50 +00:00
|
|
|
dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger)
|
2018-10-06 03:32:43 +00:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-25 02:00:00 +00:00
|
|
|
func TestDockerLogger_Success_TTY(t *testing.T) {
|
2022-03-15 12:42:43 +00:00
|
|
|
ci.Parallel(t)
|
2019-04-25 02:00:00 +00:00
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-10-06 03:32:43 +00:00
|
|
|
func echoToContainer(t *testing.T, client *docker.Client, id string, line string) {
|
|
|
|
op := docker.CreateExecOptions{
|
|
|
|
Container: id,
|
|
|
|
Cmd: []string{
|
2019-03-14 20:23:11 +00:00
|
|
|
"/bin/sh", "-c",
|
2019-01-10 15:04:23 +00:00
|
|
|
fmt.Sprintf("echo %s >>~/docklog", line),
|
2018-10-06 03:32:43 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
exec, err := client.CreateExec(op)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, client.StartExec(exec.ID, docker.StartExecOptions{Detach: true}))
|
|
|
|
}
|
|
|
|
|
2019-03-14 20:23:11 +00:00
|
|
|
func TestDockerLogger_LoggingNotSupported(t *testing.T) {
|
2022-03-15 12:42:43 +00:00
|
|
|
ci.Parallel(t)
|
2019-03-14 20:23:11 +00:00
|
|
|
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{})
|
2022-04-19 15:24:35 +00:00
|
|
|
require.NoError(t, err, "failed to pull image")
|
2019-03-14 20:23:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
containerConf := docker.CreateContainerOptions{
|
|
|
|
Config: &docker.Config{
|
|
|
|
Cmd: []string{
|
|
|
|
"sh", "-c", "touch ~/docklog; tail -f ~/docklog",
|
|
|
|
},
|
|
|
|
Image: containerImage,
|
|
|
|
},
|
|
|
|
HostConfig: &docker.HostConfig{
|
|
|
|
LogConfig: docker.LogConfig{
|
2022-04-19 15:24:35 +00:00
|
|
|
Type: "none",
|
|
|
|
Config: map[string]string{},
|
2019-03-14 20:23:11 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Context: context.Background(),
|
|
|
|
}
|
|
|
|
|
|
|
|
container, err := client.CreateContainer(containerConf)
|
2022-04-19 15:24:35 +00:00
|
|
|
require.NoError(t, err)
|
2019-03-14 20:23:11 +00:00
|
|
|
|
|
|
|
defer client.RemoveContainer(docker.RemoveContainerOptions{
|
|
|
|
ID: container.ID,
|
|
|
|
Force: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
err = client.StartContainer(container.ID, nil)
|
2022-04-19 15:24:35 +00:00
|
|
|
require.NoError(t, err)
|
2019-03-14 20:23:11 +00:00
|
|
|
|
|
|
|
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) {
|
2022-04-19 15:24:35 +00:00
|
|
|
require.NoError(t, err)
|
2019-03-14 20:23:11 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
stdout := &noopCloser{bytes.NewBuffer(nil)}
|
|
|
|
stderr := &noopCloser{bytes.NewBuffer(nil)}
|
|
|
|
|
|
|
|
dl := NewDockerLogger(testlog.HCLogger(t)).(*dockerLogger)
|
|
|
|
dl.stdout = stdout
|
|
|
|
dl.stderr = stderr
|
2022-04-19 15:24:35 +00:00
|
|
|
require.NoError(t, dl.Start(&StartOpts{
|
2019-03-14 20:23:11 +00:00
|
|
|
ContainerID: container.ID,
|
|
|
|
}))
|
|
|
|
|
|
|
|
select {
|
|
|
|
case <-dl.doneCh:
|
|
|
|
case <-time.After(10 * time.Second):
|
2022-04-19 15:24:35 +00:00
|
|
|
require.Fail(t, "timeout while waiting for docker_logging to terminate")
|
2019-03-14 20:23:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-06 03:32:43 +00:00
|
|
|
type noopCloser struct {
|
|
|
|
*bytes.Buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*noopCloser) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
2019-03-14 20:23:11 +00:00
|
|
|
|
|
|
|
func TestNextBackoff(t *testing.T) {
|
2022-03-15 12:42:43 +00:00
|
|
|
ci.Parallel(t)
|
|
|
|
|
2019-03-14 20:23:11 +00:00
|
|
|
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) {
|
2022-03-15 12:42:43 +00:00
|
|
|
ci.Parallel(t)
|
|
|
|
|
2019-03-14 20:23:11 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|