containerd/snapshot/btrfs/btrfs_test.go
Stephen J Day 68fd25221d
snapshot: define the snapshot driver interface
We now define the `snapshot.Driver` interface based on earlier work.
Many details of the model are worked out, such as snapshot lifecycle and
parentage of commits against "Active" snapshots.

The impetus of this change is to provide a snapshot POC that does a
complete push/pull workflow. The beginnings of a test suite for snapshot
drivers is included that we can use to verify the assumptions of
drivers. The intent is to port the existing tests over to this test
suite and start scaling contributions and test to the snapshot driver
subsystem.

There are still some details that need to be worked out, such as listing
and metadata access. We can do this activity as we further integrate
with tooling.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
2017-02-02 18:06:02 -08:00

212 lines
5.1 KiB
Go

package btrfs
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/docker/containerd"
"github.com/docker/containerd/testutil"
btrfs "github.com/stevvooe/go-btrfs"
)
const (
mib = 1024 * 1024
)
func TestBtrfs(t *testing.T) {
testutil.RequiresRoot(t)
device := setupBtrfsLoopbackDevice(t)
defer removeBtrfsLoopbackDevice(t, device)
root, err := ioutil.TempDir(device.mountPoint, "TestBtrfsPrepare-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)
target := filepath.Join(root, "test")
b, err := NewBtrfs(device.deviceName, root)
if err != nil {
t.Fatal(err)
}
mounts, err := b.Prepare(target, "")
if err != nil {
t.Fatal(err)
}
t.Log(mounts)
for _, mount := range mounts {
if mount.Type != "btrfs" {
t.Fatalf("wrong mount type: %v != btrfs", mount.Type)
}
// assumes the first, maybe incorrect in the future
if !strings.HasPrefix(mount.Options[0], "subvolid=") {
t.Fatalf("no subvolid option in %v", mount.Options)
}
}
if err := os.MkdirAll(target, 0755); err != nil {
t.Fatal(err)
}
if err := containerd.MountAll(mounts, target); err != nil {
t.Fatal(err)
}
defer testutil.Unmount(t, target)
// write in some data
if err := ioutil.WriteFile(filepath.Join(target, "foo"), []byte("content"), 0777); err != nil {
t.Fatal(err)
}
// TODO(stevvooe): We don't really make this with the driver, but that
// might prove annoying in practice.
if err := os.MkdirAll(filepath.Join(root, "snapshots"), 0755); err != nil {
t.Fatal(err)
}
if err := b.Commit(filepath.Join(root, "snapshots/committed"), filepath.Join(root, "test")); err != nil {
t.Fatal(err)
}
defer func() {
t.Log("Delete snapshot 1")
err := btrfs.SubvolDelete(filepath.Join(root, "snapshots/committed"))
if err != nil {
t.Error("snapshot delete failed", err)
}
}()
target = filepath.Join(root, "test2")
mounts, err = b.Prepare(target, filepath.Join(root, "snapshots/committed"))
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(target, 0755); err != nil {
t.Fatal(err)
}
if err := containerd.MountAll(mounts, target); err != nil {
t.Fatal(err)
}
defer testutil.Unmount(t, target)
// TODO(stevvooe): Verify contents of "foo"
if err := ioutil.WriteFile(filepath.Join(target, "bar"), []byte("content"), 0777); err != nil {
t.Fatal(err)
}
if err := b.Commit(filepath.Join(root, "snapshots/committed2"), target); err != nil {
t.Fatal(err)
}
defer func() {
t.Log("Delete snapshot 2")
err := btrfs.SubvolDelete(filepath.Join(root, "snapshots/committed2"))
if err != nil {
t.Error("snapshot delete failed", err)
}
}()
}
type testDevice struct {
mountPoint string
fileName string
deviceName string
}
// setupBtrfsLoopbackDevice creates a file, mounts it as a loopback device, and
// formats it as btrfs. The device should be cleaned up by calling
// removeBtrfsLoopbackDevice.
func setupBtrfsLoopbackDevice(t *testing.T) *testDevice {
// create temporary directory for mount point
mountPoint, err := ioutil.TempDir("", "containerd-btrfs-test")
if err != nil {
t.Fatal("Could not create mount point for btrfs test", err)
}
t.Log("Temporary mount point created", mountPoint)
// create temporary file for the disk image
file, err := ioutil.TempFile("", "containerd-btrfs-test")
if err != nil {
t.Fatal("Could not create temporary file for btrfs test", err)
}
t.Log("Temporary file created", file.Name())
// initialize file with 100 MiB
zero := [mib]byte{}
for i := 0; i < 100; i++ {
_, err = file.Write(zero[:])
if err != nil {
t.Fatal("Could not write to btrfs file", err)
}
}
file.Close()
// create device
losetup := exec.Command("losetup", "--find", "--show", file.Name())
var stdout, stderr bytes.Buffer
losetup.Stdout = &stdout
losetup.Stderr = &stderr
err = losetup.Run()
if err != nil {
t.Log(stderr.String())
t.Fatal("Could not run losetup", err)
}
deviceName := strings.TrimSpace(stdout.String())
t.Log("Created loop device", deviceName)
// format
t.Log("Creating btrfs filesystem")
mkfs := exec.Command("mkfs.btrfs", deviceName)
err = mkfs.Run()
if err != nil {
t.Fatal("Could not run mkfs.btrfs", err)
}
// mount
t.Logf("Mounting %s at %s", deviceName, mountPoint)
mount := exec.Command("mount", deviceName, mountPoint)
err = mount.Run()
if err != nil {
t.Fatal("Could not mount", err)
}
return &testDevice{
mountPoint: mountPoint,
fileName: file.Name(),
deviceName: deviceName,
}
}
// removeBtrfsLoopbackDevice unmounts the loopback device and deletes the
// file holding the disk image.
func removeBtrfsLoopbackDevice(t *testing.T, device *testDevice) {
// unmount
testutil.Unmount(t, device.mountPoint)
// detach device
t.Log("Removing loop device")
losetup := exec.Command("losetup", "--detach", device.deviceName)
err := losetup.Run()
if err != nil {
t.Error("Could not remove loop device", device.deviceName, err)
}
// remove file
t.Log("Removing temporary file")
err = os.Remove(device.fileName)
if err != nil {
t.Error(err)
}
// remove mount point
t.Log("Removing temporary mount point")
err = os.RemoveAll(device.mountPoint)
if err != nil {
t.Error(err)
}
}