github.com/containerd/Containerd@v1.4.13/snapshots/overlay/overlay_test.go (about)

     1  // +build linux
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package overlay
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"syscall"
    28  	"testing"
    29  
    30  	"github.com/containerd/containerd/mount"
    31  	"github.com/containerd/containerd/pkg/testutil"
    32  	"github.com/containerd/containerd/snapshots"
    33  	"github.com/containerd/containerd/snapshots/storage"
    34  	"github.com/containerd/containerd/snapshots/testsuite"
    35  )
    36  
    37  func newSnapshotterWithOpts(opts ...Opt) testsuite.SnapshotterFunc {
    38  	return func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
    39  		snapshotter, err := NewSnapshotter(root, opts...)
    40  		if err != nil {
    41  			return nil, nil, err
    42  		}
    43  
    44  		return snapshotter, func() error { return snapshotter.Close() }, nil
    45  	}
    46  }
    47  
    48  func TestOverlay(t *testing.T) {
    49  	testutil.RequiresRoot(t)
    50  	optTestCases := map[string][]Opt{
    51  		"no opt": nil,
    52  		// default in init()
    53  		"AsynchronousRemove": {AsynchronousRemove},
    54  	}
    55  
    56  	for optsName, opts := range optTestCases {
    57  		t.Run(optsName, func(t *testing.T) {
    58  			newSnapshotter := newSnapshotterWithOpts(opts...)
    59  			testsuite.SnapshotterSuite(t, "Overlay", newSnapshotter)
    60  			t.Run("TestOverlayMounts", func(t *testing.T) {
    61  				testOverlayMounts(t, newSnapshotter)
    62  			})
    63  			t.Run("TestOverlayCommit", func(t *testing.T) {
    64  				testOverlayCommit(t, newSnapshotter)
    65  			})
    66  			t.Run("TestOverlayOverlayMount", func(t *testing.T) {
    67  				testOverlayOverlayMount(t, newSnapshotter)
    68  			})
    69  			t.Run("TestOverlayOverlayRead", func(t *testing.T) {
    70  				testOverlayOverlayRead(t, newSnapshotter)
    71  			})
    72  			t.Run("TestOverlayView", func(t *testing.T) {
    73  				testOverlayView(t, newSnapshotter)
    74  			})
    75  		})
    76  	}
    77  }
    78  
    79  func testOverlayMounts(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) {
    80  	ctx := context.TODO()
    81  	root, err := ioutil.TempDir("", "overlay")
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	defer os.RemoveAll(root)
    86  	o, _, err := newSnapshotter(ctx, root)
    87  	if err != nil {
    88  		t.Fatal(err)
    89  	}
    90  	mounts, err := o.Prepare(ctx, "/tmp/test", "")
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	if len(mounts) != 1 {
    95  		t.Errorf("should only have 1 mount but received %d", len(mounts))
    96  	}
    97  	m := mounts[0]
    98  	if m.Type != "bind" {
    99  		t.Errorf("mount type should be bind but received %q", m.Type)
   100  	}
   101  	expected := filepath.Join(root, "snapshots", "1", "fs")
   102  	if m.Source != expected {
   103  		t.Errorf("expected source %q but received %q", expected, m.Source)
   104  	}
   105  	if m.Options[0] != "rw" {
   106  		t.Errorf("expected mount option rw but received %q", m.Options[0])
   107  	}
   108  	if m.Options[1] != "rbind" {
   109  		t.Errorf("expected mount option rbind but received %q", m.Options[1])
   110  	}
   111  }
   112  
   113  func testOverlayCommit(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) {
   114  	ctx := context.TODO()
   115  	root, err := ioutil.TempDir("", "overlay")
   116  	if err != nil {
   117  		t.Fatal(err)
   118  	}
   119  	defer os.RemoveAll(root)
   120  	o, _, err := newSnapshotter(ctx, root)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	key := "/tmp/test"
   125  	mounts, err := o.Prepare(ctx, key, "")
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	m := mounts[0]
   130  	if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	if err := o.Commit(ctx, "base", key); err != nil {
   134  		t.Fatal(err)
   135  	}
   136  }
   137  
   138  func testOverlayOverlayMount(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) {
   139  	ctx := context.TODO()
   140  	root, err := ioutil.TempDir("", "overlay")
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	defer os.RemoveAll(root)
   145  	o, _, err := newSnapshotter(ctx, root)
   146  	if err != nil {
   147  		t.Fatal(err)
   148  	}
   149  	key := "/tmp/test"
   150  	if _, err = o.Prepare(ctx, key, ""); err != nil {
   151  		t.Fatal(err)
   152  	}
   153  	if err := o.Commit(ctx, "base", key); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	var mounts []mount.Mount
   157  	if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil {
   158  		t.Fatal(err)
   159  	}
   160  	if len(mounts) != 1 {
   161  		t.Errorf("should only have 1 mount but received %d", len(mounts))
   162  	}
   163  	m := mounts[0]
   164  	if m.Type != "overlay" {
   165  		t.Errorf("mount type should be overlay but received %q", m.Type)
   166  	}
   167  	if m.Source != "overlay" {
   168  		t.Errorf("expected source %q but received %q", "overlay", m.Source)
   169  	}
   170  	var (
   171  		bp    = getBasePath(ctx, o, root, "/tmp/layer2")
   172  		work  = "workdir=" + filepath.Join(bp, "work")
   173  		upper = "upperdir=" + filepath.Join(bp, "fs")
   174  		lower = "lowerdir=" + getParents(ctx, o, root, "/tmp/layer2")[0]
   175  	)
   176  
   177  	expected := []string{
   178  		"index=off",
   179  	}
   180  	if userxattr, err := NeedsUserXAttr(root); err != nil {
   181  		t.Fatal(err)
   182  	} else if userxattr {
   183  		expected = append(expected, "userxattr")
   184  	}
   185  	expected = append(expected, []string{
   186  		work,
   187  		upper,
   188  		lower,
   189  	}...)
   190  	for i, v := range expected {
   191  		if m.Options[i] != v {
   192  			t.Errorf("expected %q but received %q", v, m.Options[i])
   193  		}
   194  	}
   195  }
   196  
   197  func getBasePath(ctx context.Context, sn snapshots.Snapshotter, root, key string) string {
   198  	o := sn.(*snapshotter)
   199  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   200  	if err != nil {
   201  		panic(err)
   202  	}
   203  	defer t.Rollback()
   204  
   205  	s, err := storage.GetSnapshot(ctx, key)
   206  	if err != nil {
   207  		panic(err)
   208  	}
   209  
   210  	return filepath.Join(root, "snapshots", s.ID)
   211  }
   212  
   213  func getParents(ctx context.Context, sn snapshots.Snapshotter, root, key string) []string {
   214  	o := sn.(*snapshotter)
   215  	ctx, t, err := o.ms.TransactionContext(ctx, false)
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  	defer t.Rollback()
   220  	s, err := storage.GetSnapshot(ctx, key)
   221  	if err != nil {
   222  		panic(err)
   223  	}
   224  	parents := make([]string, len(s.ParentIDs))
   225  	for i := range s.ParentIDs {
   226  		parents[i] = filepath.Join(root, "snapshots", s.ParentIDs[i], "fs")
   227  	}
   228  	return parents
   229  }
   230  
   231  func testOverlayOverlayRead(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) {
   232  	testutil.RequiresRoot(t)
   233  	ctx := context.TODO()
   234  	root, err := ioutil.TempDir("", "overlay")
   235  	if err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	defer os.RemoveAll(root)
   239  	o, _, err := newSnapshotter(ctx, root)
   240  	if err != nil {
   241  		t.Fatal(err)
   242  	}
   243  	key := "/tmp/test"
   244  	mounts, err := o.Prepare(ctx, key, "")
   245  	if err != nil {
   246  		t.Fatal(err)
   247  	}
   248  	m := mounts[0]
   249  	if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil {
   250  		t.Fatal(err)
   251  	}
   252  	if err := o.Commit(ctx, "base", key); err != nil {
   253  		t.Fatal(err)
   254  	}
   255  	if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil {
   256  		t.Fatal(err)
   257  	}
   258  	dest := filepath.Join(root, "dest")
   259  	if err := os.Mkdir(dest, 0700); err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	if err := mount.All(mounts, dest); err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	defer syscall.Unmount(dest, 0)
   266  	data, err := ioutil.ReadFile(filepath.Join(dest, "foo"))
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	if e := string(data); e != "hi" {
   271  		t.Fatalf("expected file contents hi but got %q", e)
   272  	}
   273  }
   274  
   275  func testOverlayView(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) {
   276  	ctx := context.TODO()
   277  	root, err := ioutil.TempDir("", "overlay")
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  	defer os.RemoveAll(root)
   282  	o, _, err := newSnapshotter(ctx, root)
   283  	if err != nil {
   284  		t.Fatal(err)
   285  	}
   286  	key := "/tmp/base"
   287  	mounts, err := o.Prepare(ctx, key, "")
   288  	if err != nil {
   289  		t.Fatal(err)
   290  	}
   291  	m := mounts[0]
   292  	if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	if err := o.Commit(ctx, "base", key); err != nil {
   296  		t.Fatal(err)
   297  	}
   298  
   299  	key = "/tmp/top"
   300  	_, err = o.Prepare(ctx, key, "base")
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  	if err := ioutil.WriteFile(filepath.Join(getParents(ctx, o, root, "/tmp/top")[0], "foo"), []byte("hi, again"), 0660); err != nil {
   305  		t.Fatal(err)
   306  	}
   307  	if err := o.Commit(ctx, "top", key); err != nil {
   308  		t.Fatal(err)
   309  	}
   310  
   311  	mounts, err = o.View(ctx, "/tmp/view1", "base")
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  	if len(mounts) != 1 {
   316  		t.Fatalf("should only have 1 mount but received %d", len(mounts))
   317  	}
   318  	m = mounts[0]
   319  	if m.Type != "bind" {
   320  		t.Errorf("mount type should be bind but received %q", m.Type)
   321  	}
   322  	expected := getParents(ctx, o, root, "/tmp/view1")[0]
   323  	if m.Source != expected {
   324  		t.Errorf("expected source %q but received %q", expected, m.Source)
   325  	}
   326  	if m.Options[0] != "ro" {
   327  		t.Errorf("expected mount option ro but received %q", m.Options[0])
   328  	}
   329  	if m.Options[1] != "rbind" {
   330  		t.Errorf("expected mount option rbind but received %q", m.Options[1])
   331  	}
   332  
   333  	mounts, err = o.View(ctx, "/tmp/view2", "top")
   334  	if err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	if len(mounts) != 1 {
   338  		t.Fatalf("should only have 1 mount but received %d", len(mounts))
   339  	}
   340  	m = mounts[0]
   341  	if m.Type != "overlay" {
   342  		t.Errorf("mount type should be overlay but received %q", m.Type)
   343  	}
   344  	if m.Source != "overlay" {
   345  		t.Errorf("mount source should be overlay but received %q", m.Source)
   346  	}
   347  
   348  	expectedOptions := 2
   349  	userxattr, err := NeedsUserXAttr(root)
   350  	if err != nil {
   351  		t.Fatal(err)
   352  	}
   353  	if userxattr {
   354  		expectedOptions++
   355  	}
   356  
   357  	if len(m.Options) != expectedOptions {
   358  		t.Errorf("expected %d additional mount option but got %d", expectedOptions, len(m.Options))
   359  	}
   360  	lowers := getParents(ctx, o, root, "/tmp/view2")
   361  	expected = fmt.Sprintf("lowerdir=%s:%s", lowers[0], lowers[1])
   362  	optIdx := 1
   363  	if userxattr {
   364  		optIdx++
   365  	}
   366  	if m.Options[optIdx] != expected {
   367  		t.Errorf("expected option %q but received %q", expected, m.Options[optIdx])
   368  	}
   369  }