318 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// +build linux
 | 
						|
 | 
						|
/*
 | 
						|
   Copyright The containerd Authors.
 | 
						|
 | 
						|
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
   you may not use this file except in compliance with the License.
 | 
						|
   You may obtain a copy of the License at
 | 
						|
 | 
						|
       http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
   Unless required by applicable law or agreed to in writing, software
 | 
						|
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
   See the License for the specific language governing permissions and
 | 
						|
   limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package overlay
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"syscall"
 | 
						|
	"testing"
 | 
						|
 | 
						|
	"github.com/containerd/containerd/mount"
 | 
						|
	"github.com/containerd/containerd/pkg/testutil"
 | 
						|
	"github.com/containerd/containerd/snapshots"
 | 
						|
	"github.com/containerd/containerd/snapshots/storage"
 | 
						|
	"github.com/containerd/containerd/snapshots/testsuite"
 | 
						|
)
 | 
						|
 | 
						|
func newSnapshotter(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
 | 
						|
	snapshotter, err := NewSnapshotter(root)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return snapshotter, func() error { return snapshotter.Close() }, nil
 | 
						|
}
 | 
						|
 | 
						|
func TestOverlay(t *testing.T) {
 | 
						|
	testutil.RequiresRoot(t)
 | 
						|
	testsuite.SnapshotterSuite(t, "Overlay", newSnapshotter)
 | 
						|
}
 | 
						|
 | 
						|
func TestOverlayMounts(t *testing.T) {
 | 
						|
	ctx := context.TODO()
 | 
						|
	root, err := ioutil.TempDir("", "overlay")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer os.RemoveAll(root)
 | 
						|
	o, _, err := newSnapshotter(ctx, root)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	mounts, err := o.Prepare(ctx, "/tmp/test", "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(mounts) != 1 {
 | 
						|
		t.Errorf("should only have 1 mount but received %d", len(mounts))
 | 
						|
	}
 | 
						|
	m := mounts[0]
 | 
						|
	if m.Type != "bind" {
 | 
						|
		t.Errorf("mount type should be bind but received %q", m.Type)
 | 
						|
	}
 | 
						|
	expected := filepath.Join(root, "snapshots", "1", "fs")
 | 
						|
	if m.Source != expected {
 | 
						|
		t.Errorf("expected source %q but received %q", expected, m.Source)
 | 
						|
	}
 | 
						|
	if m.Options[0] != "rw" {
 | 
						|
		t.Errorf("expected mount option rw but received %q", m.Options[0])
 | 
						|
	}
 | 
						|
	if m.Options[1] != "rbind" {
 | 
						|
		t.Errorf("expected mount option rbind but received %q", m.Options[1])
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestOverlayCommit(t *testing.T) {
 | 
						|
	ctx := context.TODO()
 | 
						|
	root, err := ioutil.TempDir("", "overlay")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer os.RemoveAll(root)
 | 
						|
	o, _, err := newSnapshotter(ctx, root)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	key := "/tmp/test"
 | 
						|
	mounts, err := o.Prepare(ctx, key, "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	m := mounts[0]
 | 
						|
	if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if err := o.Commit(ctx, "base", key); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestOverlayOverlayMount(t *testing.T) {
 | 
						|
	ctx := context.TODO()
 | 
						|
	root, err := ioutil.TempDir("", "overlay")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer os.RemoveAll(root)
 | 
						|
	o, _, err := newSnapshotter(ctx, root)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	key := "/tmp/test"
 | 
						|
	if _, err = o.Prepare(ctx, key, ""); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if err := o.Commit(ctx, "base", key); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	var mounts []mount.Mount
 | 
						|
	if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(mounts) != 1 {
 | 
						|
		t.Errorf("should only have 1 mount but received %d", len(mounts))
 | 
						|
	}
 | 
						|
	m := mounts[0]
 | 
						|
	if m.Type != "overlay" {
 | 
						|
		t.Errorf("mount type should be overlay but received %q", m.Type)
 | 
						|
	}
 | 
						|
	if m.Source != "overlay" {
 | 
						|
		t.Errorf("expected source %q but received %q", "overlay", m.Source)
 | 
						|
	}
 | 
						|
	var (
 | 
						|
		bp    = getBasePath(ctx, o, root, "/tmp/layer2")
 | 
						|
		work  = "workdir=" + filepath.Join(bp, "work")
 | 
						|
		upper = "upperdir=" + filepath.Join(bp, "fs")
 | 
						|
		lower = "lowerdir=" + getParents(ctx, o, root, "/tmp/layer2")[0]
 | 
						|
	)
 | 
						|
	for i, v := range []string{
 | 
						|
		work,
 | 
						|
		upper,
 | 
						|
		lower,
 | 
						|
	} {
 | 
						|
		if m.Options[i] != v {
 | 
						|
			t.Errorf("expected %q but received %q", v, m.Options[i])
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func getBasePath(ctx context.Context, sn snapshots.Snapshotter, root, key string) string {
 | 
						|
	o := sn.(*snapshotter)
 | 
						|
	ctx, t, err := o.ms.TransactionContext(ctx, false)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	defer t.Rollback()
 | 
						|
 | 
						|
	s, err := storage.GetSnapshot(ctx, key)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return filepath.Join(root, "snapshots", s.ID)
 | 
						|
}
 | 
						|
 | 
						|
func getParents(ctx context.Context, sn snapshots.Snapshotter, root, key string) []string {
 | 
						|
	o := sn.(*snapshotter)
 | 
						|
	ctx, t, err := o.ms.TransactionContext(ctx, false)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	defer t.Rollback()
 | 
						|
	s, err := storage.GetSnapshot(ctx, key)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
	parents := make([]string, len(s.ParentIDs))
 | 
						|
	for i := range s.ParentIDs {
 | 
						|
		parents[i] = filepath.Join(root, "snapshots", s.ParentIDs[i], "fs")
 | 
						|
	}
 | 
						|
	return parents
 | 
						|
}
 | 
						|
 | 
						|
func TestOverlayOverlayRead(t *testing.T) {
 | 
						|
	testutil.RequiresRoot(t)
 | 
						|
	ctx := context.TODO()
 | 
						|
	root, err := ioutil.TempDir("", "overlay")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer os.RemoveAll(root)
 | 
						|
	o, _, err := newSnapshotter(ctx, root)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	key := "/tmp/test"
 | 
						|
	mounts, err := o.Prepare(ctx, key, "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	m := mounts[0]
 | 
						|
	if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if err := o.Commit(ctx, "base", key); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	dest := filepath.Join(root, "dest")
 | 
						|
	if err := os.Mkdir(dest, 0700); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if err := mount.All(mounts, dest); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer syscall.Unmount(dest, 0)
 | 
						|
	data, err := ioutil.ReadFile(filepath.Join(dest, "foo"))
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if e := string(data); e != "hi" {
 | 
						|
		t.Fatalf("expected file contents hi but got %q", e)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestOverlayView(t *testing.T) {
 | 
						|
	ctx := context.TODO()
 | 
						|
	root, err := ioutil.TempDir("", "overlay")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	defer os.RemoveAll(root)
 | 
						|
	o, _, err := newSnapshotter(ctx, root)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	key := "/tmp/base"
 | 
						|
	mounts, err := o.Prepare(ctx, key, "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	m := mounts[0]
 | 
						|
	if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if err := o.Commit(ctx, "base", key); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	key = "/tmp/top"
 | 
						|
	_, err = o.Prepare(ctx, key, "base")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if err := ioutil.WriteFile(filepath.Join(getParents(ctx, o, root, "/tmp/top")[0], "foo"), []byte("hi, again"), 0660); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if err := o.Commit(ctx, "top", key); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	mounts, err = o.View(ctx, "/tmp/view1", "base")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(mounts) != 1 {
 | 
						|
		t.Fatalf("should only have 1 mount but received %d", len(mounts))
 | 
						|
	}
 | 
						|
	m = mounts[0]
 | 
						|
	if m.Type != "bind" {
 | 
						|
		t.Errorf("mount type should be bind but received %q", m.Type)
 | 
						|
	}
 | 
						|
	expected := getParents(ctx, o, root, "/tmp/view1")[0]
 | 
						|
	if m.Source != expected {
 | 
						|
		t.Errorf("expected source %q but received %q", expected, m.Source)
 | 
						|
	}
 | 
						|
	if m.Options[0] != "ro" {
 | 
						|
		t.Errorf("expected mount option ro but received %q", m.Options[0])
 | 
						|
	}
 | 
						|
	if m.Options[1] != "rbind" {
 | 
						|
		t.Errorf("expected mount option rbind but received %q", m.Options[1])
 | 
						|
	}
 | 
						|
 | 
						|
	mounts, err = o.View(ctx, "/tmp/view2", "top")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(mounts) != 1 {
 | 
						|
		t.Fatalf("should only have 1 mount but received %d", len(mounts))
 | 
						|
	}
 | 
						|
	m = mounts[0]
 | 
						|
	if m.Type != "overlay" {
 | 
						|
		t.Errorf("mount type should be overlay but received %q", m.Type)
 | 
						|
	}
 | 
						|
	if m.Source != "overlay" {
 | 
						|
		t.Errorf("mount source should be overlay but received %q", m.Source)
 | 
						|
	}
 | 
						|
	if len(m.Options) != 1 {
 | 
						|
		t.Errorf("expected 1 mount option but got %d", len(m.Options))
 | 
						|
	}
 | 
						|
	lowers := getParents(ctx, o, root, "/tmp/view2")
 | 
						|
	expected = fmt.Sprintf("lowerdir=%s:%s", lowers[0], lowers[1])
 | 
						|
	if m.Options[0] != expected {
 | 
						|
		t.Errorf("expected option %q but received %q", expected, m.Options[0])
 | 
						|
	}
 | 
						|
}
 |