68fd25221d
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>
212 lines
5.1 KiB
Go
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)
|
|
}
|
|
}
|