moby/integration-cli/docker_api_logs_test.go
Brian Goff e8dc902781 Wire up tests to support otel tracing
Integration tests will now configure clients to propagate traces as well
as create spans for all tests.

Some extra changes were needed (or desired for trace propagation) in the
test helpers to pass through tracing spans via context.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2023-09-07 18:38:22 +00:00

225 lines
6.6 KiB
Go

package main
import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"testing"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/request"
"gotest.tools/v3/assert"
)
func (s *DockerAPISuite) TestLogsAPIWithStdout(c *testing.T) {
out, _ := dockerCmd(c, "run", "-d", "-t", "busybox", "/bin/sh", "-c", "while true; do echo hello; sleep 1; done")
id := strings.TrimSpace(out)
assert.NilError(c, waitRun(id))
type logOut struct {
out string
err error
}
chLog := make(chan logOut, 1)
res, body, err := request.Get(testutil.GetContext(c), fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&timestamps=1", id))
assert.NilError(c, err)
assert.Equal(c, res.StatusCode, http.StatusOK)
go func() {
defer body.Close()
out, err := bufio.NewReader(body).ReadString('\n')
if err != nil {
chLog <- logOut{"", err}
return
}
chLog <- logOut{strings.TrimSpace(out), err}
}()
select {
case l := <-chLog:
assert.NilError(c, l.err)
if !strings.HasSuffix(l.out, "hello") {
c.Fatalf("expected log output to container 'hello', but it does not")
}
case <-time.After(30 * time.Second):
c.Fatal("timeout waiting for logs to exit")
}
}
func (s *DockerAPISuite) TestLogsAPINoStdoutNorStderr(c *testing.T) {
name := "logs_test"
dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh")
apiClient, err := client.NewClientWithOpts(client.FromEnv)
assert.NilError(c, err)
defer apiClient.Close()
_, err = apiClient.ContainerLogs(testutil.GetContext(c), name, types.ContainerLogsOptions{})
assert.ErrorContains(c, err, "Bad parameters: you must choose at least one stream")
}
// Regression test for #12704
func (s *DockerAPISuite) TestLogsAPIFollowEmptyOutput(c *testing.T) {
name := "logs_test"
t0 := time.Now()
dockerCmd(c, "run", "-d", "-t", "--name", name, "busybox", "sleep", "10")
_, body, err := request.Get(testutil.GetContext(c), fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&stderr=1&tail=all", name))
t1 := time.Now()
assert.NilError(c, err)
body.Close()
elapsed := t1.Sub(t0).Seconds()
if elapsed > 20.0 {
c.Fatalf("HTTP response was not immediate (elapsed %.1fs)", elapsed)
}
}
func (s *DockerAPISuite) TestLogsAPIContainerNotFound(c *testing.T) {
name := "nonExistentContainer"
resp, _, err := request.Get(testutil.GetContext(c), fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&stderr=1&tail=all", name))
assert.NilError(c, err)
assert.Equal(c, resp.StatusCode, http.StatusNotFound)
}
func (s *DockerAPISuite) TestLogsAPIUntilFutureFollow(c *testing.T) {
testRequires(c, DaemonIsLinux)
name := "logsuntilfuturefollow"
dockerCmd(c, "run", "-d", "--name", name, "busybox", "/bin/sh", "-c", "while true; do date +%s; sleep 1; done")
assert.NilError(c, waitRun(name))
untilSecs := 5
untilDur, err := time.ParseDuration(fmt.Sprintf("%ds", untilSecs))
assert.NilError(c, err)
until := daemonTime(c).Add(untilDur)
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
c.Fatal(err)
}
cfg := types.ContainerLogsOptions{Until: until.Format(time.RFC3339Nano), Follow: true, ShowStdout: true, Timestamps: true}
reader, err := client.ContainerLogs(testutil.GetContext(c), name, cfg)
assert.NilError(c, err)
type logOut struct {
out string
err error
}
chLog := make(chan logOut)
stop := make(chan struct{})
defer close(stop)
go func() {
bufReader := bufio.NewReader(reader)
defer reader.Close()
for i := 0; i < untilSecs; i++ {
out, _, err := bufReader.ReadLine()
if err != nil {
if err == io.EOF {
return
}
select {
case <-stop:
return
case chLog <- logOut{"", err}:
}
return
}
select {
case <-stop:
return
case chLog <- logOut{strings.TrimSpace(string(out)), err}:
}
}
}()
for i := 0; i < untilSecs; i++ {
select {
case l := <-chLog:
assert.NilError(c, l.err)
i, err := strconv.ParseInt(strings.Split(l.out, " ")[1], 10, 64)
assert.NilError(c, err)
assert.Assert(c, time.Unix(i, 0).UnixNano() <= until.UnixNano())
case <-time.After(20 * time.Second):
c.Fatal("timeout waiting for logs to exit")
}
}
}
func (s *DockerAPISuite) TestLogsAPIUntil(c *testing.T) {
testRequires(c, MinimumAPIVersion("1.34"))
name := "logsuntil"
dockerCmd(c, "run", "--name", name, "busybox", "/bin/sh", "-c", "for i in $(seq 1 3); do echo log$i; sleep 1; done")
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
c.Fatal(err)
}
extractBody := func(c *testing.T, cfg types.ContainerLogsOptions) []string {
reader, err := client.ContainerLogs(testutil.GetContext(c), name, cfg)
assert.NilError(c, err)
actualStdout := new(bytes.Buffer)
actualStderr := io.Discard
_, err = stdcopy.StdCopy(actualStdout, actualStderr, reader)
assert.NilError(c, err)
return strings.Split(actualStdout.String(), "\n")
}
// Get timestamp of second log line
allLogs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true})
assert.Assert(c, len(allLogs) >= 3)
t, err := time.Parse(time.RFC3339Nano, strings.Split(allLogs[1], " ")[0])
assert.NilError(c, err)
until := t.Format(time.RFC3339Nano)
// Get logs until the timestamp of second line, i.e. first two lines
logs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true, Until: until})
// Ensure log lines after cut-off are excluded
logsString := strings.Join(logs, "\n")
assert.Assert(c, !strings.Contains(logsString, "log3"), "unexpected log message returned, until=%v", until)
}
func (s *DockerAPISuite) TestLogsAPIUntilDefaultValue(c *testing.T) {
name := "logsuntildefaultval"
dockerCmd(c, "run", "--name", name, "busybox", "/bin/sh", "-c", "for i in $(seq 1 3); do echo log$i; done")
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
c.Fatal(err)
}
extractBody := func(c *testing.T, cfg types.ContainerLogsOptions) []string {
reader, err := client.ContainerLogs(testutil.GetContext(c), name, cfg)
assert.NilError(c, err)
actualStdout := new(bytes.Buffer)
actualStderr := io.Discard
_, err = stdcopy.StdCopy(actualStdout, actualStderr, reader)
assert.NilError(c, err)
return strings.Split(actualStdout.String(), "\n")
}
// Get timestamp of second log line
allLogs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true})
// Test with default value specified and parameter omitted
defaultLogs := extractBody(c, types.ContainerLogsOptions{Timestamps: true, ShowStdout: true, Until: "0"})
assert.DeepEqual(c, defaultLogs, allLogs)
}