diff --git a/.travis.yml b/.travis.yml index 786d2a62a188..7df354b88665 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,9 @@ env: - TRAVIS_GOOS=linux TRAVIS_CGO_ENABLED=1 - TRAVIS_GOOS=darwin TRAVIS_CGO_ENABLED=0 +before_install: + - uname -r + install: - if [ "$TRAVIS_GOOS" = "windows" ] ; then sudo apt-get install -y gcc-multilib gcc-mingw-w64; export CC=x86_64-w64-mingw32-gcc ; export CXX=x86_64-w64-mingw32-g++ ; fi - wget https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-linux-x86_64.zip -O /tmp/protoc-3.1.0-linux-x86_64.zip diff --git a/snapshot/testsuite/helpers.go b/snapshot/testsuite/helpers.go new file mode 100644 index 000000000000..a0be722d1a16 --- /dev/null +++ b/snapshot/testsuite/helpers.go @@ -0,0 +1,120 @@ +package testsuite + +import ( + "context" + "fmt" + "io/ioutil" + "math/rand" + "os" + + "github.com/containerd/containerd/fs/fstest" + "github.com/containerd/containerd/mount" + "github.com/containerd/containerd/snapshot" + "github.com/pkg/errors" +) + +func applyToMounts(m []mount.Mount, work string, a fstest.Applier) (err error) { + td, err := ioutil.TempDir(work, "prepare") + if err != nil { + return errors.Wrap(err, "failed to create temp dir") + } + defer os.RemoveAll(td) + + if err := mount.MountAll(m, td); err != nil { + return err + } + defer func() { + if err1 := mount.UnmountAll(td, 0); err == nil { + err = err1 + } + }() + + return a.Apply(td) +} + +// createSnapshot creates a new snapshot in the snapshotter +// given an applier to run on top of the given parent. +func createSnapshot(ctx context.Context, sn snapshot.Snapshotter, parent, work string, a fstest.Applier) (string, error) { + n := fmt.Sprintf("%p-%d", a, rand.Int()) + prepare := fmt.Sprintf("%s-prepare", n) + + m, err := sn.Prepare(ctx, prepare, parent) + if err != nil { + return "", err + } + + if err := applyToMounts(m, work, a); err != nil { + return "", err + } + + if err := sn.Commit(ctx, n, prepare); err != nil { + return "", err + } + + return n, nil +} + +func checkSnapshot(ctx context.Context, sn snapshot.Snapshotter, work, name, check string) error { + td, err := ioutil.TempDir(work, "check") + if err != nil { + return errors.Wrap(err, "failed to create temp dir") + } + defer os.RemoveAll(td) + + view := fmt.Sprintf("%s-view", name) + m, err := sn.View(ctx, view, name) + if err != nil { + return errors.Wrap(err, "failed to create view") + } + defer func() { + if err1 := sn.Remove(ctx, view); err == nil { + err = errors.Wrap(err1, "failed to remove view") + } + }() + + if err := mount.MountAll(m, td); err != nil { + return errors.Wrap(err, "failed to unmount") + } + defer func() { + if err1 := mount.UnmountAll(td, 0); err == nil { + err = errors.Wrap(err1, "failed to unmount view") + } + }() + + if err := fstest.CheckDirectoryEqual(check, td); err != nil { + return errors.Wrap(err, "check directory failed") + } + + return nil +} + +// checkSnapshots creates a new chain of snapshots in the given snapshotter +// using the provided appliers, checking each snapshot created in a view +// against the changes applied to a single directory. +func checkSnapshots(ctx context.Context, sn snapshot.Snapshotter, work string, as ...fstest.Applier) error { + td, err := ioutil.TempDir(work, "flat") + if err != nil { + return errors.Wrap(err, "failed to create temp dir") + } + defer os.RemoveAll(td) + + var parentID string + for i, a := range as { + s, err := createSnapshot(ctx, sn, parentID, work, a) + if err != nil { + return errors.Wrapf(err, "failed to create snapshot %d", i+1) + } + + if err := a.Apply(td); err != nil { + return errors.Wrapf(err, "failed to apply to check directory on %d", i+1) + } + + if err := checkSnapshot(ctx, sn, work, s, td); err != nil { + return errors.Wrapf(err, "snapshot check failed on snapshot %d", i+1) + } + + parentID = s + } + return nil + +} diff --git a/snapshot/testsuite/issues.go b/snapshot/testsuite/issues.go new file mode 100644 index 000000000000..a41c401f0843 --- /dev/null +++ b/snapshot/testsuite/issues.go @@ -0,0 +1,172 @@ +package testsuite + +import ( + "context" + "testing" + "time" + + "github.com/containerd/containerd/fs/fstest" + "github.com/containerd/containerd/snapshot" +) + +// Checks which cover former issues found in older layering models. +// +// NOTE: In older models, applying with tar was used to create read only layers, +// however with the snapshot model read only layers are created just using +// mounts and commits. Read write layers are a separate type of snapshot which +// is not committed, avoiding any confusion in the snapshotter about whether +// a snapshot will be mutated in the future. + +// checkLayerFileUpdate tests the update of a single file in an upper layer +// Cause of issue was originally related to tar, snapshot should be able to +// avoid such issues by not relying on tar to create layers. +// See https://github.com/docker/docker/issues/21555 +func checkLayerFileUpdate(ctx context.Context, t *testing.T, sn snapshot.Snapshotter, work string) { + l1Init := fstest.Apply( + fstest.CreateDir("/etc", 0700), + fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644), + fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644), + ) + l2Init := fstest.Apply( + fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.2"), 0644), + fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0666), + fstest.CreateDir("/root", 0700), + fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644), + ) + + var sleepTime time.Duration + + // run 5 times to account for sporadic failure + for i := 0; i < 5; i++ { + time.Sleep(sleepTime) + + if err := checkSnapshots(ctx, sn, work, l1Init, l2Init); err != nil { + t.Fatalf("Check snapshots failed: %+v", err) + } + + // Sleep until next second boundary before running again + nextTime := time.Now() + sleepTime = time.Unix(nextTime.Unix()+1, 0).Sub(nextTime) + } +} + +// checkRemoveDirectoryInLowerLayer +// See https://github.com/docker/docker/issues/25244 +func checkRemoveDirectoryInLowerLayer(ctx context.Context, t *testing.T, sn snapshot.Snapshotter, work string) { + l1Init := fstest.Apply( + fstest.CreateDir("/lib", 0700), + fstest.CreateFile("/lib/hidden", []byte{}, 0644), + ) + l2Init := fstest.Apply( + fstest.RemoveAll("/lib"), + fstest.CreateDir("/lib", 0700), + fstest.CreateFile("/lib/not-hidden", []byte{}, 0644), + ) + l3Init := fstest.Apply( + fstest.CreateFile("/lib/newfile", []byte{}, 0644), + ) + + if err := checkSnapshots(ctx, sn, work, l1Init, l2Init, l3Init); err != nil { + t.Fatalf("Check snapshots failed: %+v", err) + } +} + +// checkChown +// See https://github.com/docker/docker/issues/20240 aufs +// See https://github.com/docker/docker/issues/24913 overlay +// see https://github.com/docker/docker/issues/28391 overlay2 +func checkChown(ctx context.Context, t *testing.T, sn snapshot.Snapshotter, work string) { + l1Init := fstest.Apply( + fstest.CreateDir("/opt", 0700), + fstest.CreateDir("/opt/a", 0700), + fstest.CreateDir("/opt/a/b", 0700), + fstest.CreateFile("/opt/a/b/file.txt", []byte("hello"), 0644), + ) + l2Init := fstest.Apply( + fstest.Chown("/opt", 1, 1), + fstest.Chown("/opt/a", 1, 1), + fstest.Chown("/opt/a/b", 1, 1), + fstest.Chown("/opt/a/b/file.txt", 1, 1), + ) + + if err := checkSnapshots(ctx, sn, work, l1Init, l2Init); err != nil { + t.Fatalf("Check snapshots failed: %+v", err) + } +} + +// checkRename +// https://github.com/docker/docker/issues/25409 +func checkRename(ctx context.Context, t *testing.T, sn snapshot.Snapshotter, work string) { + l1Init := fstest.Apply( + fstest.CreateDir("/dir1", 0700), + fstest.CreateDir("/somefiles", 0700), + fstest.CreateFile("/somefiles/f1", []byte("was here first!"), 0644), + fstest.CreateFile("/somefiles/f2", []byte("nothing interesting"), 0644), + ) + l2Init := fstest.Apply( + fstest.Rename("/dir1", "/dir2"), + fstest.CreateFile("/somefiles/f1-overwrite", []byte("new content 1"), 0644), + fstest.Rename("/somefiles/f1-overwrite", "/somefiles/f1"), + fstest.Rename("/somefiles/f2", "/somefiles/f3"), + ) + + if err := checkSnapshots(ctx, sn, work, l1Init, l2Init); err != nil { + t.Fatalf("Check snapshots failed: %+v", err) + } +} + +// checkDirectoryPermissionOnCommit +// https://github.com/docker/docker/issues/27298 +func checkDirectoryPermissionOnCommit(ctx context.Context, t *testing.T, sn snapshot.Snapshotter, work string) { + l1Init := fstest.Apply( + fstest.CreateDir("/dir1", 0700), + fstest.CreateDir("/dir2", 0700), + fstest.CreateDir("/dir3", 0700), + fstest.CreateDir("/dir4", 0700), + fstest.CreateFile("/dir4/f1", []byte("..."), 0644), + fstest.CreateDir("/dir5", 0700), + fstest.CreateFile("/dir5/f1", []byte("..."), 0644), + fstest.Chown("/dir1", 1, 1), + fstest.Chown("/dir2", 1, 1), + fstest.Chown("/dir3", 1, 1), + fstest.Chown("/dir5", 1, 1), + fstest.Chown("/dir5/f1", 1, 1), + ) + l2Init := fstest.Apply( + fstest.Chown("/dir2", 0, 0), + fstest.RemoveAll("/dir3"), + fstest.Chown("/dir4", 1, 1), + fstest.Chown("/dir4/f1", 1, 1), + ) + l3Init := fstest.Apply( + fstest.CreateDir("/dir3", 0700), + fstest.Chown("/dir3", 1, 1), + fstest.RemoveAll("/dir5"), + fstest.CreateDir("/dir5", 0700), + fstest.Chown("/dir5", 1, 1), + ) + + if err := checkSnapshots(ctx, sn, work, l1Init, l2Init, l3Init); err != nil { + t.Fatalf("Check snapshots failed: %+v", err) + } +} + +// More issues to test +// +// checkRemoveAfterCommit +// See https://github.com/docker/docker/issues/24309 +// +// checkUnixDomainSockets +// See https://github.com/docker/docker/issues/12080 +// +// checkDirectoryInodeStability +// See https://github.com/docker/docker/issues/19647 +// +// checkOpenFileInodeStability +// See https://github.com/docker/docker/issues/12327 +// +// checkGetCWD +// See https://github.com/docker/docker/issues/19082 +// +// checkChmod +// See https://github.com/docker/machine/issues/3327 diff --git a/snapshot/testsuite/testsuite.go b/snapshot/testsuite/testsuite.go index 2f85c5a6d0a4..04825a2d13c5 100644 --- a/snapshot/testsuite/testsuite.go +++ b/snapshot/testsuite/testsuite.go @@ -23,6 +23,14 @@ func SnapshotterSuite(t *testing.T, name string, snapshotterFn func(ctx context. t.Run("StatComitted", makeTest(t, name, snapshotterFn, checkSnapshotterStatCommitted)) t.Run("TransitivityTest", makeTest(t, name, snapshotterFn, checkSnapshotterTransitivity)) t.Run("PreareViewFailingtest", makeTest(t, name, snapshotterFn, checkSnapshotterPrepareView)) + + t.Run("LayerFileupdate", makeTest(t, name, snapshotterFn, checkLayerFileUpdate)) + t.Run("RemoveDirectoryInLowerLayer", makeTest(t, name, snapshotterFn, checkRemoveDirectoryInLowerLayer)) + t.Run("Chown", makeTest(t, name, snapshotterFn, checkChown)) + t.Run("DirectoryPermissionOnCommit", makeTest(t, name, snapshotterFn, checkDirectoryPermissionOnCommit)) + + // Rename test still fails on some kernels with overlay + //t.Run("Rename", makeTest(t, name, snapshotterFn, checkRename)) } func makeTest(t *testing.T, name string, snapshotterFn func(ctx context.Context, root string) (snapshot.Snapshotter, func(), error), fn func(ctx context.Context, t *testing.T, snapshotter snapshot.Snapshotter, work string)) func(t *testing.T) {