Browse code

integration: Add test for not breaking overlayfs

Check that operations that could potentially perform overlayfs mounts
that could cause undefined behaviors.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2023/08/11 02:13:38
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,111 @@
0
+package container
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"strings"
6
+	"testing"
7
+
8
+	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/integration/internal/container"
10
+	"github.com/docker/docker/pkg/archive"
11
+	"github.com/docker/docker/pkg/dmesg"
12
+	"gotest.tools/v3/assert"
13
+	"gotest.tools/v3/skip"
14
+)
15
+
16
+func TestNoOverlayfsWarningsAboutUndefinedBehaviors(t *testing.T) {
17
+	skip.If(t, testEnv.DaemonInfo.OSType != "linux", "overlayfs is only available on linux")
18
+	skip.If(t, testEnv.IsRemoteDaemon(), "local daemon is needed for kernel log access")
19
+	skip.If(t, testEnv.IsRootless(), "root is needed for reading kernel log")
20
+
21
+	defer setupTest(t)()
22
+	client := testEnv.APIClient()
23
+	ctx := context.Background()
24
+
25
+	cID := container.Run(ctx, t, client, container.WithCmd("sh", "-c", `while true; do echo $RANDOM >>/file; sleep 0.1; done`))
26
+
27
+	testCases := []struct {
28
+		name      string
29
+		operation func(t *testing.T) error
30
+	}{
31
+		{name: "diff", operation: func(*testing.T) error {
32
+			_, err := client.ContainerDiff(ctx, cID)
33
+			return err
34
+		}},
35
+		{name: "export", operation: func(*testing.T) error {
36
+			rc, err := client.ContainerExport(ctx, cID)
37
+			if err == nil {
38
+				defer rc.Close()
39
+				_, err = io.Copy(io.Discard, rc)
40
+			}
41
+			return err
42
+		}},
43
+		{name: "cp to container", operation: func(t *testing.T) error {
44
+			archive, err := archive.Generate("new-file", "hello-world")
45
+			assert.NilError(t, err, "failed to create a temporary archive")
46
+			return client.CopyToContainer(ctx, cID, "/", archive, types.CopyToContainerOptions{})
47
+		}},
48
+		{name: "cp from container", operation: func(*testing.T) error {
49
+			rc, _, err := client.CopyFromContainer(ctx, cID, "/file")
50
+			if err == nil {
51
+				defer rc.Close()
52
+				_, err = io.Copy(io.Discard, rc)
53
+			}
54
+
55
+			return err
56
+		}},
57
+	}
58
+
59
+	for _, tc := range testCases {
60
+		tc := tc
61
+		t.Run(tc.name, func(t *testing.T) {
62
+			prev := dmesgLines(256)
63
+
64
+			err := tc.operation(t)
65
+			assert.NilError(t, err)
66
+
67
+			after := dmesgLines(2048)
68
+
69
+			diff := diffDmesg(prev, after)
70
+			for _, line := range diff {
71
+				overlayfs := strings.Contains(line, "overlayfs: ")
72
+				lowerDirInUse := strings.Contains(line, "lowerdir is in-use as ")
73
+				upperDirInUse := strings.Contains(line, "upperdir is in-use as ")
74
+				workDirInuse := strings.Contains(line, "workdir is in-use as ")
75
+				undefinedBehavior := strings.Contains(line, "will result in undefined behavior")
76
+
77
+				if overlayfs && (lowerDirInUse || upperDirInUse || workDirInuse) && undefinedBehavior {
78
+					t.Errorf("%s caused overlayfs kernel warning: %s", tc.name, line)
79
+				}
80
+			}
81
+		})
82
+	}
83
+}
84
+
85
+func dmesgLines(bytes int) []string {
86
+	data := dmesg.Dmesg(bytes)
87
+	return strings.Split(strings.TrimSpace(string(data)), "\n")
88
+}
89
+
90
+func diffDmesg(prev, next []string) []string {
91
+	// All lines have a timestamp, so just take the last one from the previous
92
+	// log and find it in the new log.
93
+	lastPrev := prev[len(prev)-1]
94
+
95
+	for idx := len(next) - 1; idx >= 0; idx-- {
96
+		line := next[idx]
97
+
98
+		if line == lastPrev {
99
+			nextIdx := idx + 1
100
+			if nextIdx < len(next) {
101
+				return next[nextIdx:]
102
+			} else {
103
+				// Found at the last position, log is the same.
104
+				return nil
105
+			}
106
+		}
107
+	}
108
+
109
+	return next
110
+}