github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/snapshots/btrfs/btrfs_test.go (about)

     1  // +build linux,!no_btrfs
     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 btrfs
    20  
    21  import (
    22  	"context"
    23  	"io/ioutil"
    24  	"os"
    25  	"os/exec"
    26  	"path/filepath"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/containerd/containerd/mount"
    32  	"github.com/containerd/containerd/pkg/testutil"
    33  	"github.com/containerd/containerd/plugin"
    34  	"github.com/containerd/containerd/snapshots"
    35  	"github.com/containerd/containerd/snapshots/testsuite"
    36  	"github.com/containerd/continuity/testutil/loopback"
    37  	"github.com/pkg/errors"
    38  	"golang.org/x/sys/unix"
    39  )
    40  
    41  func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshots.Snapshotter, func() error, error) {
    42  	mkbtrfs, err := exec.LookPath("mkfs.btrfs")
    43  	if err != nil {
    44  		t.Skipf("could not find mkfs.btrfs: %v", err)
    45  	}
    46  
    47  	// TODO: Check for btrfs in /proc/module and skip if not loaded
    48  
    49  	return func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) {
    50  
    51  		loopbackSize := int64(128 << 20) // 128 MB
    52  		// mkfs.btrfs creates a fs which has a blocksize equal to the system default pagesize. If that pagesize
    53  		// is > 4KB, mounting the fs will fail unless we increase the size of the file used by mkfs.btrfs
    54  		if os.Getpagesize() > 4096 {
    55  			loopbackSize = int64(650 << 20) // 650 MB
    56  		}
    57  		loop, err := loopback.New(loopbackSize)
    58  
    59  		if err != nil {
    60  			return nil, nil, err
    61  		}
    62  
    63  		if out, err := exec.Command(mkbtrfs, loop.Device).CombinedOutput(); err != nil {
    64  			loop.Close()
    65  			return nil, nil, errors.Wrapf(err, "failed to make btrfs filesystem (out: %q)", out)
    66  		}
    67  		// sync after a mkfs on the loopback before trying to mount the device
    68  		unix.Sync()
    69  
    70  		var snapshotter snapshots.Snapshotter
    71  		for i := 0; i < 5; i++ {
    72  			if out, err := exec.Command("mount", loop.Device, root).CombinedOutput(); err != nil {
    73  				loop.Close()
    74  				return nil, nil, errors.Wrapf(err, "failed to mount device %s (out: %q)", loop.Device, out)
    75  			}
    76  
    77  			if i > 0 {
    78  				time.Sleep(10 * time.Duration(i) * time.Millisecond)
    79  			}
    80  
    81  			snapshotter, err = NewSnapshotter(root)
    82  			if err == nil {
    83  				break
    84  			} else if !errors.Is(err, plugin.ErrSkipPlugin) {
    85  				return nil, nil, err
    86  			}
    87  
    88  			t.Logf("Attempt %d to create btrfs snapshotter failed: %#v", i+1, err)
    89  
    90  			// unmount and try again
    91  			unix.Unmount(root, 0)
    92  		}
    93  		if snapshotter == nil {
    94  			return nil, nil, errors.Wrap(err, "failed to successfully create snapshotter after 5 attempts")
    95  		}
    96  
    97  		return snapshotter, func() error {
    98  			if err := snapshotter.Close(); err != nil {
    99  				return err
   100  			}
   101  			err := mount.UnmountAll(root, unix.MNT_DETACH)
   102  			if cerr := loop.Close(); cerr != nil {
   103  				err = errors.Wrap(cerr, "device cleanup failed")
   104  			}
   105  			return err
   106  		}, nil
   107  	}
   108  }
   109  
   110  func TestBtrfs(t *testing.T) {
   111  	testutil.RequiresRoot(t)
   112  	testsuite.SnapshotterSuite(t, "Btrfs", boltSnapshotter(t))
   113  }
   114  
   115  func TestBtrfsMounts(t *testing.T) {
   116  	testutil.RequiresRoot(t)
   117  	ctx := context.Background()
   118  
   119  	// create temporary directory for mount point
   120  	mountPoint, err := ioutil.TempDir("", "containerd-btrfs-test")
   121  	if err != nil {
   122  		t.Fatal("could not create mount point for btrfs test", err)
   123  	}
   124  	defer os.RemoveAll(mountPoint)
   125  	t.Log("temporary mount point created", mountPoint)
   126  
   127  	root, err := ioutil.TempDir(mountPoint, "TestBtrfsPrepare-")
   128  	if err != nil {
   129  		t.Fatal(err)
   130  	}
   131  	defer os.RemoveAll(root)
   132  
   133  	b, c, err := boltSnapshotter(t)(ctx, root)
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  	defer c()
   138  
   139  	target := filepath.Join(root, "test")
   140  	mounts, err := b.Prepare(ctx, target, "")
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  	t.Log(mounts)
   145  
   146  	for _, mount := range mounts {
   147  		if mount.Type != "btrfs" {
   148  			t.Fatalf("wrong mount type: %v != btrfs", mount.Type)
   149  		}
   150  
   151  		// assumes the first, maybe incorrect in the future
   152  		if !strings.HasPrefix(mount.Options[0], "subvolid=") {
   153  			t.Fatalf("no subvolid option in %v", mount.Options)
   154  		}
   155  	}
   156  
   157  	if err := os.MkdirAll(target, 0755); err != nil {
   158  		t.Fatal(err)
   159  	}
   160  	if err := mount.All(mounts, target); err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	defer testutil.Unmount(t, target)
   164  
   165  	// write in some data
   166  	if err := ioutil.WriteFile(filepath.Join(target, "foo"), []byte("content"), 0777); err != nil {
   167  		t.Fatal(err)
   168  	}
   169  
   170  	// TODO(stevvooe): We don't really make this with the driver, but that
   171  	// might prove annoying in practice.
   172  	if err := os.MkdirAll(filepath.Join(root, "snapshots"), 0755); err != nil {
   173  		t.Fatal(err)
   174  	}
   175  
   176  	if err := b.Commit(ctx, filepath.Join(root, "snapshots/committed"), filepath.Join(root, "test")); err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	target = filepath.Join(root, "test2")
   181  	mounts, err = b.Prepare(ctx, target, filepath.Join(root, "snapshots/committed"))
   182  	if err != nil {
   183  		t.Fatal(err)
   184  	}
   185  
   186  	if err := os.MkdirAll(target, 0755); err != nil {
   187  		t.Fatal(err)
   188  	}
   189  
   190  	if err := mount.All(mounts, target); err != nil {
   191  		t.Fatal(err)
   192  	}
   193  	defer testutil.Unmount(t, target)
   194  
   195  	// TODO(stevvooe): Verify contents of "foo"
   196  	if err := ioutil.WriteFile(filepath.Join(target, "bar"), []byte("content"), 0777); err != nil {
   197  		t.Fatal(err)
   198  	}
   199  
   200  	if err := b.Commit(ctx, filepath.Join(root, "snapshots/committed2"), target); err != nil {
   201  		t.Fatal(err)
   202  	}
   203  }