aeffd4f92c
We now include btrfs in the snapshot driver test suite. This includes the addition of parent links and name hashing into the btrfs driver. We'll probably endup replacing this with a common metadata store, as these relationships are generally identical between implementations. A small bug was discovered in the delete implementation in the course testing, so the btrfs package has been updated with a fix. The overlay driver was modified accordingly with the btrfs driver to use `Driver` as the exported type of each driver packge. Signed-off-by: Stephen J Day <stephen.day@docker.com>
216 lines
5.1 KiB
Go
216 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/snapshot"
|
|
"github.com/docker/containerd/testutil"
|
|
)
|
|
|
|
const (
|
|
mib = 1024 * 1024
|
|
)
|
|
|
|
func TestBtrfs(t *testing.T) {
|
|
testutil.RequiresRoot(t)
|
|
snapshot.DriverSuite(t, "Btrfs", func(root string) (snapshot.Driver, func(), error) {
|
|
device := setupBtrfsLoopbackDevice(t, root)
|
|
driver, err := NewDriver(device.deviceName, root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return driver, func() {
|
|
device.remove(t)
|
|
}, nil
|
|
})
|
|
}
|
|
|
|
func TestBtrfsMounts(t *testing.T) {
|
|
testutil.RequiresRoot(t)
|
|
|
|
// 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)
|
|
|
|
device := setupBtrfsLoopbackDevice(t, mountPoint)
|
|
defer device.remove(t)
|
|
|
|
root, err := ioutil.TempDir(device.mountPoint, "TestBtrfsPrepare-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(root)
|
|
|
|
target := filepath.Join(root, "test")
|
|
b, err := NewDriver(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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
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, mountPoint string) *testDevice {
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
|
|
// remove cleans up the test device, unmounting the loopback and disk image
|
|
// file.
|
|
func (device *testDevice) remove(t *testing.T) {
|
|
// 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)
|
|
}
|
|
}
|