Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[24.0 backport] c8d integration: Use refcount mounter for diff and export #46266

Merged
merged 3 commits into from Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 3 additions & 8 deletions daemon/containerd/image_changes.go
Expand Up @@ -58,15 +58,10 @@ func (i *ImageService) Changes(ctx context.Context, container *container.Contain
}
}()

mounts, err := snapshotter.Mounts(ctx, container.ID)
if err != nil {
return nil, err
}

var changes []archive.Change
err = mount.WithReadonlyTempMount(ctx, mounts, func(fs string) error {
return mount.WithTempMount(ctx, parent, func(root string) error {
changes, err = archive.ChangesDirs(fs, root)
err = i.PerformWithBaseFS(ctx, container, func(containerRootfs string) error {
return mount.WithReadonlyTempMount(ctx, parent, func(parentRootfs string) error {
changes, err = archive.ChangesDirs(containerRootfs, parentRootfs)
return err
})
})
Expand Down
9 changes: 7 additions & 2 deletions daemon/containerd/image_exporter.go
Expand Up @@ -11,7 +11,6 @@ import (
containerdimages "github.com/containerd/containerd/images"
"github.com/containerd/containerd/images/archive"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/mount"
cplatforms "github.com/containerd/containerd/platforms"
"github.com/docker/distribution/reference"
"github.com/docker/docker/container"
Expand All @@ -30,7 +29,13 @@ func (i *ImageService) PerformWithBaseFS(ctx context.Context, c *container.Conta
if err != nil {
return err
}
return mount.WithTempMount(ctx, mounts, fn)
path, err := i.refCountMounter.Mount(mounts, c.ID)
if err != nil {
return err
}
defer i.refCountMounter.Unmount(path)

return fn(path)
}

// ExportImage exports a list of images to the given output stream. The
Expand Down
111 changes: 111 additions & 0 deletions integration/container/overlayfs_linux_test.go
@@ -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
}