Skip to content

Commit

Permalink
Merge pull request #1262 from dmcgowan/more-snapshot-tests
Browse files Browse the repository at this point in the history
Add snapshot test cases for former issues
  • Loading branch information
stevvooe committed Jul 31, 2017
2 parents 20fa6ae + d28126f commit f86d5ae
Show file tree
Hide file tree
Showing 4 changed files with 303 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Expand Up @@ -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
Expand Down
120 changes: 120 additions & 0 deletions 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

}
172 changes: 172 additions & 0 deletions 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
8 changes: 8 additions & 0 deletions snapshot/testsuite/testsuite.go
Expand Up @@ -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) {
Expand Down

0 comments on commit f86d5ae

Please sign in to comment.