|
@@ -0,0 +1,111 @@
|
|
|
+package container
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "io"
|
|
|
+ "strings"
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "github.com/docker/docker/api/types"
|
|
|
+ "github.com/docker/docker/integration/internal/container"
|
|
|
+ "github.com/docker/docker/pkg/archive"
|
|
|
+ "github.com/docker/docker/pkg/dmesg"
|
|
|
+ "gotest.tools/v3/assert"
|
|
|
+ "gotest.tools/v3/skip"
|
|
|
+)
|
|
|
+
|
|
|
+func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) {
|
|
|
+ skip.If(t, testEnv.DaemonInfo.OSType != "linux", "overlayfs is only available on linux")
|
|
|
+ skip.If(t, testEnv.IsRemoteDaemon(), "local daemon is needed for kernel log access")
|
|
|
+ skip.If(t, testEnv.IsRootless(), "root is needed for reading kernel log")
|
|
|
+
|
|
|
+ defer setupTest(t)()
|
|
|
+ client := testEnv.APIClient()
|
|
|
+ ctx := context.Background()
|
|
|
+
|
|
|
+ cID := container.Run(ctx, t, client, container.WithCmd("sh", "-c", `while true; do echo $RANDOM >>/file; sleep 0.1; done`))
|
|
|
+
|
|
|
+ testCases := []struct {
|
|
|
+ name string
|
|
|
+ operation func(t *testing.T) error
|
|
|
+ }{
|
|
|
+ {name: "diff", operation: func(*testing.T) error {
|
|
|
+ _, err := client.ContainerDiff(ctx, cID)
|
|
|
+ return err
|
|
|
+ }},
|
|
|
+ {name: "export", operation: func(*testing.T) error {
|
|
|
+ rc, err := client.ContainerExport(ctx, cID)
|
|
|
+ if err == nil {
|
|
|
+ defer rc.Close()
|
|
|
+ _, err = io.Copy(io.Discard, rc)
|
|
|
+ }
|
|
|
+ return err
|
|
|
+ }},
|
|
|
+ {name: "cp to container", operation: func(t *testing.T) error {
|
|
|
+ archive, err := archive.Generate("new-file", "hello-world")
|
|
|
+ assert.NilError(t, err, "failed to create a temporary archive")
|
|
|
+ return client.CopyToContainer(ctx, cID, "/", archive, types.CopyToContainerOptions{})
|
|
|
+ }},
|
|
|
+ {name: "cp from container", operation: func(*testing.T) error {
|
|
|
+ rc, _, err := client.CopyFromContainer(ctx, cID, "/file")
|
|
|
+ if err == nil {
|
|
|
+ defer rc.Close()
|
|
|
+ _, err = io.Copy(io.Discard, rc)
|
|
|
+ }
|
|
|
+
|
|
|
+ return err
|
|
|
+ }},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tc := range testCases {
|
|
|
+ tc := tc
|
|
|
+ t.Run(tc.name, func(t *testing.T) {
|
|
|
+ prev := dmesgLines(256)
|
|
|
+
|
|
|
+ err := tc.operation(t)
|
|
|
+ assert.NilError(t, err)
|
|
|
+
|
|
|
+ after := dmesgLines(2048)
|
|
|
+
|
|
|
+ diff := diffDmesg(prev, after)
|
|
|
+ for _, line := range diff {
|
|
|
+ overlayfs := strings.Contains(line, "overlayfs: ")
|
|
|
+ lowerDirInUse := strings.Contains(line, "lowerdir is in-use as ")
|
|
|
+ upperDirInUse := strings.Contains(line, "upperdir is in-use as ")
|
|
|
+ workDirInuse := strings.Contains(line, "workdir is in-use as ")
|
|
|
+ undefinedBehavior := strings.Contains(line, "will result in undefined behavior")
|
|
|
+
|
|
|
+ if overlayfs && (lowerDirInUse || upperDirInUse || workDirInuse) && undefinedBehavior {
|
|
|
+ t.Errorf("%s caused overlayfs kernel warning: %s", tc.name, line)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func dmesgLines(bytes int) []string {
|
|
|
+ data := dmesg.Dmesg(bytes)
|
|
|
+ return strings.Split(strings.TrimSpace(string(data)), "\n")
|
|
|
+}
|
|
|
+
|
|
|
+func diffDmesg(prev, next []string) []string {
|
|
|
+ // All lines have a timestamp, so just take the last one from the previous
|
|
|
+ // log and find it in the new log.
|
|
|
+ lastPrev := prev[len(prev)-1]
|
|
|
+
|
|
|
+ for idx := len(next) - 1; idx >= 0; idx-- {
|
|
|
+ line := next[idx]
|
|
|
+
|
|
|
+ if line == lastPrev {
|
|
|
+ nextIdx := idx + 1
|
|
|
+ if nextIdx < len(next) {
|
|
|
+ return next[nextIdx:]
|
|
|
+ } else {
|
|
|
+ // Found at the last position, log is the same.
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return next
|
|
|
+}
|