From ccb179d3eff410bdd04b626934528298cd23b3a5 Mon Sep 17 00:00:00 2001 From: Shubharanshu Mahapatra Date: Mon, 17 Feb 2025 23:36:08 -0800 Subject: [PATCH] fix: logs updated on starting of a stopped container Signed-off-by: Shubharanshu Mahapatra --- cmd/nerdctl/container/container_logs_test.go | 97 ++++++++++++++++++++ pkg/taskutil/taskutil.go | 4 +- pkg/testutil/testutil_freebsd.go | 10 +- pkg/testutil/testutil_windows.go | 6 ++ 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/cmd/nerdctl/container/container_logs_test.go b/cmd/nerdctl/container/container_logs_test.go index e1ed851fa7d..bb7537da009 100644 --- a/cmd/nerdctl/container/container_logs_test.go +++ b/cmd/nerdctl/container/container_logs_test.go @@ -20,6 +20,7 @@ import ( "fmt" "os/exec" "runtime" + "strconv" "strings" "testing" "time" @@ -28,6 +29,8 @@ import ( "gotest.tools/v3/icmd" "github.com/containerd/nerdctl/v2/pkg/testutil" + "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" + "github.com/containerd/nerdctl/v2/pkg/testutil/test" ) func TestLogs(t *testing.T) { @@ -255,3 +258,97 @@ func TestTailFollowRotateLogs(t *testing.T) { } assert.Equal(t, true, len(tailLogs) > linesPerFile, logRun.Stderr()) } + +func TestLogsWithStartContainer(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("dual logging test is not supported on Windows") + } + + testCase := nerdtest.Setup() + testCase.SubTests = []*test.Case{ + { + Description: "Test logs are directed correctly for container start of a interactive container", + Require: test.Require( + test.Not(nerdtest.Docker), + test.Not(test.Windows), + ), + Setup: func(data test.Data, helpers test.Helpers) { + cmd := helpers.Command("run", "-it", "--name", data.Identifier(), testutil.CommonImage) + cmd.WithWrapper("unbuffer", "-p") + cmd.WithStdin(testutil.NewDelayOnceReader(strings.NewReader("echo foo\nexit\n"))) + cmd.Run(&test.Expected{ + ExitCode: 0, + }) + + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + cmd := helpers.Command("start", "-a", data.Identifier()) + cmd.WithWrapper("unbuffer", "-p") + cmd.WithStdin(testutil.NewDelayOnceReader(strings.NewReader("echo bar\nexit\n"))) + cmd.Run(&test.Expected{ + ExitCode: 0, + }) + + cmd = helpers.Command("logs", data.Identifier()) + + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Errors: []error{}, + Output: test.All( + func(stdout string, info string, t *testing.T) { + assert.Assert(t, strings.Contains(stdout, "foo")) + assert.Assert(t, strings.Contains(stdout, "bar")) + }, + ), + } + }, + }, + { + Description: "Test logs are captured after stopping and starting a non-interactive container and continue capturing new logs", + Require: test.Require( + test.Not(nerdtest.Docker), + test.Not(test.Windows), + ), + Setup: func(data test.Data, helpers test.Helpers) { + cmd := helpers.Command("run", "-d", "--name", data.Identifier(), testutil.CommonImage, "sh", "-c", "while true; do echo foo; sleep 1; done") + cmd.Run(&test.Expected{ + ExitCode: 0, + }) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier()) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + + helpers.Anyhow("stop", data.Identifier()) + initialLogs := helpers.Capture("logs", data.Identifier()) + initialFooCount := strings.Count(initialLogs, "foo") + data.Set("initialFooCount", strconv.Itoa(initialFooCount)) + helpers.Anyhow("start", data.Identifier()) + time.Sleep(5 * time.Second) + cmd := helpers.Command("logs", data.Identifier()) + return cmd + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + ExitCode: 0, + Errors: []error{}, + Output: test.All( + func(stdout string, info string, t *testing.T) { + finalLogsCount := strings.Count(stdout, "foo") + initialFooCount, _ := strconv.Atoi(data.Get("initialFooCount")) + assert.Assert(t, finalLogsCount > initialFooCount, "Expected 'foo' count to increase after restart") + }, + ), + } + }, + }, + } + testCase.Run(t) +} diff --git a/pkg/taskutil/taskutil.go b/pkg/taskutil/taskutil.go index 35ac3d8effc..cad23393d29 100644 --- a/pkg/taskutil/taskutil.go +++ b/pkg/taskutil/taskutil.go @@ -71,10 +71,10 @@ func NewTask(ctx context.Context, client *containerd.Client, container container if err != nil { return nil, err } - ioCreator = cio.NewCreator(cio.WithStreams(in, con, nil), cio.WithTerminal) + ioCreator = cioutil.NewContainerIO(namespace, logURI, true, in, con, nil) } else { streams := processAttachStreamsOpt(attachStreamOpt) - ioCreator = cio.NewCreator(cio.WithStreams(streams.stdIn, streams.stdOut, streams.stdErr)) + ioCreator = cioutil.NewContainerIO(namespace, logURI, false, streams.stdIn, streams.stdOut, streams.stdErr) } } else if flagT && flagD { diff --git a/pkg/testutil/testutil_freebsd.go b/pkg/testutil/testutil_freebsd.go index 63a79f07a02..f0974f0e60e 100644 --- a/pkg/testutil/testutil_freebsd.go +++ b/pkg/testutil/testutil_freebsd.go @@ -16,7 +16,10 @@ package testutil -import "fmt" +import ( + "fmt" + "io" +) const ( CommonImage = "docker.io/knast/freebsd:13-STABLE" @@ -40,3 +43,8 @@ func mirrorOf(s string) string { // plain mirror, NOT stargz-converted images return fmt.Sprintf("ghcr.io/stargz-containers/%s-org", s) } + +func NewDelayOnceReader(wrapped io.Reader) io.Reader { + // not implemented + return nil +} diff --git a/pkg/testutil/testutil_windows.go b/pkg/testutil/testutil_windows.go index 868e5df1cd8..631cc104e7f 100644 --- a/pkg/testutil/testutil_windows.go +++ b/pkg/testutil/testutil_windows.go @@ -17,6 +17,7 @@ package testutil import ( + "io" "os" "strconv" "strings" @@ -110,3 +111,8 @@ func HyperVContainer(inspect dockercompat.Container) (bool, error) { return hypervContainer, nil } + +func NewDelayOnceReader(wrapped io.Reader) io.Reader { + // not implemented + return nil +}