Merge pull request #854 from AkihiroSuda/lookup-mountinfo
mountinfo: add Lookup for ease of implementing snapshotters
This commit is contained in:
commit
99160a7ac0
6
Makefile
6
Makefile
@ -25,7 +25,7 @@ endif
|
|||||||
# Project packages.
|
# Project packages.
|
||||||
PACKAGES=$(shell go list ./... | grep -v /vendor/)
|
PACKAGES=$(shell go list ./... | grep -v /vendor/)
|
||||||
INTEGRATION_PACKAGE=${PKG}/integration
|
INTEGRATION_PACKAGE=${PKG}/integration
|
||||||
SNAPSHOT_PACKAGES=$(shell go list ./snapshot/...)
|
TEST_REQUIRES_ROOT_PACKAGES=$(shell for f in $$(git grep -l testutil.RequiresRoot | grep -v Makefile);do echo "${PKG}/$$(dirname $$f)"; done)
|
||||||
|
|
||||||
# Project binaries.
|
# Project binaries.
|
||||||
COMMANDS=ctr containerd protoc-gen-gogoctrd dist ctrd-protobuild
|
COMMANDS=ctr containerd protoc-gen-gogoctrd dist ctrd-protobuild
|
||||||
@ -121,7 +121,7 @@ test: ## run tests, except integration tests and tests that require root
|
|||||||
|
|
||||||
root-test: ## run tests, except integration tests
|
root-test: ## run tests, except integration tests
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
@go test ${TESTFLAGS} ${SNAPSHOT_PACKAGES} -test.root
|
@go test ${TESTFLAGS} ${TEST_REQUIRES_ROOT_PACKAGES} -test.root
|
||||||
|
|
||||||
integration: ## run integration tests
|
integration: ## run integration tests
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
@ -173,7 +173,7 @@ coverage: ## generate coverprofiles from the unit tests, except tests that requi
|
|||||||
|
|
||||||
root-coverage: ## generae coverage profiles for the unit tests
|
root-coverage: ## generae coverage profiles for the unit tests
|
||||||
@echo "$(WHALE) $@"
|
@echo "$(WHALE) $@"
|
||||||
@( for pkg in ${SNAPSHOT_PACKAGES}; do \
|
@( for pkg in ${TEST_REQUIRES_ROOT_PACKAGES}; do \
|
||||||
go test -i ${TESTFLAGS} -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg -test.root || exit; \
|
go test -i ${TESTFLAGS} -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg -test.root || exit; \
|
||||||
go test ${TESTFLAGS} -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg -test.root || exit; \
|
go test ${TESTFLAGS} -test.short -coverprofile="../../../$$pkg/coverage.txt" -covermode=atomic $$pkg -test.root || exit; \
|
||||||
done )
|
done )
|
||||||
|
@ -6,76 +6,40 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testSupportsDType(t *testing.T, expected bool, mkfsCommand string, mkfsArg ...string) {
|
func testSupportsDType(t *testing.T, expected bool, mkfs ...string) {
|
||||||
// check whether mkfs is installed
|
testutil.RequiresRoot(t)
|
||||||
if _, err := exec.LookPath(mkfsCommand); err != nil {
|
mnt, err := ioutil.TempDir("", "containerd-fs-test-supports-dtype")
|
||||||
t.Skipf("%s not installed: %v", mkfsCommand, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a sparse image
|
|
||||||
imageSize := int64(32 * 1024 * 1024)
|
|
||||||
imageFile, err := ioutil.TempFile("", "fsutils-image")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
imageFileName := imageFile.Name()
|
defer os.RemoveAll(mnt)
|
||||||
defer os.Remove(imageFileName)
|
|
||||||
if _, err = imageFile.Seek(imageSize-1, 0); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if _, err = imageFile.Write([]byte{0}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err = imageFile.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a mountpoint
|
deviceName, cleanupDevice := testutil.NewLoopback(t, 100<<20) // 100 MB
|
||||||
mountpoint, err := ioutil.TempDir("", "fsutils-mountpoint")
|
if out, err := exec.Command(mkfs[0], append(mkfs[1:], deviceName)...).CombinedOutput(); err != nil {
|
||||||
if err != nil {
|
// not fatal
|
||||||
t.Fatal(err)
|
t.Skipf("could not mkfs (%v) %s: %v (out: %q)", mkfs, deviceName, err, string(out))
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(mountpoint)
|
if out, err := exec.Command("mount", deviceName, mnt).CombinedOutput(); err != nil {
|
||||||
|
// not fatal
|
||||||
// format the image
|
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
||||||
args := append(mkfsArg, imageFileName)
|
|
||||||
t.Logf("Executing `%s %v`", mkfsCommand, args)
|
|
||||||
out, err := exec.Command(mkfsCommand, args...).CombinedOutput()
|
|
||||||
if len(out) > 0 {
|
|
||||||
t.Log(string(out))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Skip("skipping the test because %s failed. This is probably your %s is an unsupported version.", mkfsCommand, mkfsCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
// loopback-mount the image.
|
|
||||||
// for ease of setting up loopback device, we use os/exec rather than syscall.Mount
|
|
||||||
out, err = exec.Command("mount", "-o", "loop", imageFileName, mountpoint).CombinedOutput()
|
|
||||||
if len(out) > 0 {
|
|
||||||
t.Log(string(out))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Skip("skipping the test because mount failed")
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := syscall.Unmount(mountpoint, 0); err != nil {
|
testutil.Unmount(t, mnt)
|
||||||
t.Fatal(err)
|
cleanupDevice()
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// check whether it supports d_type
|
// check whether it supports d_type
|
||||||
result, err := SupportsDType(mountpoint)
|
result, err := SupportsDType(mnt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
t.Logf("Supports d_type: %v", result)
|
t.Logf("Supports d_type: %v", result)
|
||||||
if result != expected {
|
assert.Equal(t, expected, result)
|
||||||
t.Fatalf("expected %v, got %v", expected, result)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSupportsDTypeWithFType0XFS(t *testing.T) {
|
func TestSupportsDTypeWithFType0XFS(t *testing.T) {
|
||||||
|
3
mount/lookup_test/dummy.go
Normal file
3
mount/lookup_test/dummy.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package lookuptest
|
||||||
|
|
||||||
|
// FIXME: without this dummy file, `make build` fails with "no buildable Go source files" error
|
59
mount/lookup_test/lookup_linux_test.go
Normal file
59
mount/lookup_test/lookup_linux_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
// FIXME: we can't put this test to the mount package:
|
||||||
|
// import cycle not allowed in test
|
||||||
|
// package github.com/containerd/containerd/mount (test)
|
||||||
|
// imports github.com/containerd/containerd/testutil
|
||||||
|
// imports github.com/containerd/containerd/mount
|
||||||
|
//
|
||||||
|
// NOTE: we can't have this as lookup_test (compilation fails)
|
||||||
|
package lookuptest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/mount"
|
||||||
|
"github.com/containerd/containerd/testutil"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testLookup(t *testing.T, fsType string) {
|
||||||
|
testutil.RequiresRoot(t)
|
||||||
|
mnt, err := ioutil.TempDir("", "containerd-mountinfo-test-lookup")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(mnt)
|
||||||
|
|
||||||
|
deviceName, cleanupDevice := testutil.NewLoopback(t, 100<<20) // 100 MB
|
||||||
|
if out, err := exec.Command("mkfs", "-t", fsType, deviceName).CombinedOutput(); err != nil {
|
||||||
|
// not fatal
|
||||||
|
t.Skipf("could not mkfs (%s) %s: %v (out: %q)", fsType, deviceName, err, string(out))
|
||||||
|
}
|
||||||
|
if out, err := exec.Command("mount", deviceName, mnt).CombinedOutput(); err != nil {
|
||||||
|
// not fatal
|
||||||
|
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
testutil.Unmount(t, mnt)
|
||||||
|
cleanupDevice()
|
||||||
|
}()
|
||||||
|
assert.True(t, strings.HasPrefix(deviceName, "/dev/loop"))
|
||||||
|
info, err := mount.Lookup(mnt)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, fsType, info.FSType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupWithExt4(t *testing.T) {
|
||||||
|
testLookup(t, "ext4")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupWithXFS(t *testing.T) {
|
||||||
|
testLookup(t, "xfs")
|
||||||
|
}
|
37
mount/lookup_unix.go
Normal file
37
mount/lookup_unix.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lookup returns the mount info corresponds to the path.
|
||||||
|
func Lookup(dir string) (Info, error) {
|
||||||
|
var dirStat syscall.Stat_t
|
||||||
|
if err := syscall.Stat(dir, &dirStat); err != nil {
|
||||||
|
return Info{}, errors.Wrapf(err, "failed to access %q", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
mounts, err := Self()
|
||||||
|
if err != nil {
|
||||||
|
return Info{}, err
|
||||||
|
}
|
||||||
|
for _, m := range mounts {
|
||||||
|
// Note that m.{Major, Minor} are generally unreliable for our purpose here
|
||||||
|
// https://www.spinics.net/lists/linux-btrfs/msg58908.html
|
||||||
|
var st syscall.Stat_t
|
||||||
|
if err := syscall.Stat(m.Mountpoint, &st); err != nil {
|
||||||
|
// may fail; ignore err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if st.Dev == dirStat.Dev {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Info{}, fmt.Errorf("failed to find the mount info for %q", dir)
|
||||||
|
}
|
13
mount/lookup_unsupported.go
Normal file
13
mount/lookup_unsupported.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package mount
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lookup returns the mount info corresponds to the path.
|
||||||
|
func Lookup(dir string) (Info, error) {
|
||||||
|
return Info{}, fmt.Errorf("mount.Lookup is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/containerd/btrfs"
|
"github.com/containerd/btrfs"
|
||||||
@ -35,37 +34,15 @@ type snapshotter struct {
|
|||||||
ms *storage.MetaStore
|
ms *storage.MetaStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBtrfsDevice(root string, mounts []mount.Info) (string, error) {
|
|
||||||
device := ""
|
|
||||||
deviceMountpoint := ""
|
|
||||||
for _, info := range mounts {
|
|
||||||
if (info.Root == "/" || info.Root == "") && strings.HasPrefix(root, info.Mountpoint) {
|
|
||||||
if info.FSType == "btrfs" && len(info.Mountpoint) > len(deviceMountpoint) {
|
|
||||||
device = info.Source
|
|
||||||
deviceMountpoint = info.Mountpoint
|
|
||||||
}
|
|
||||||
if root == info.Mountpoint && info.FSType != "btrfs" {
|
|
||||||
return "", fmt.Errorf("%s needs to be btrfs, but seems %s", root, info.FSType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if device == "" {
|
|
||||||
// TODO: automatically mount loopback device here?
|
|
||||||
return "", fmt.Errorf("%s is not mounted as btrfs", root)
|
|
||||||
}
|
|
||||||
return device, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSnapshotter returns a Snapshotter using btrfs. Uses the provided
|
// NewSnapshotter returns a Snapshotter using btrfs. Uses the provided
|
||||||
// root directory for snapshots and stores the metadata in
|
// root directory for snapshots and stores the metadata in
|
||||||
// a file in the provided root.
|
// a file in the provided root.
|
||||||
// root needs to be a mount point of btrfs.
|
// root needs to be a mount point of btrfs.
|
||||||
func NewSnapshotter(root string) (snapshot.Snapshotter, error) {
|
func NewSnapshotter(root string) (snapshot.Snapshotter, error) {
|
||||||
mounts, err := mount.Self()
|
mnt, err := mount.Lookup(root)
|
||||||
if err != nil {
|
if mnt.FSType != "btrfs" {
|
||||||
return nil, err
|
return nil, fmt.Errorf("expected btrfs, got %s", mnt.FSType)
|
||||||
}
|
}
|
||||||
device, err := getBtrfsDevice(root, mounts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -88,7 +65,7 @@ func NewSnapshotter(root string) (snapshot.Snapshotter, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &snapshotter{
|
return &snapshotter{
|
||||||
device: device,
|
device: mnt.Source,
|
||||||
root: root,
|
root: root,
|
||||||
ms: ms,
|
ms: ms,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -18,20 +18,28 @@ import (
|
|||||||
"github.com/containerd/containerd/testutil"
|
"github.com/containerd/containerd/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
mib = 1024 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshot.Snapshotter, func(), error) {
|
func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshot.Snapshotter, func(), error) {
|
||||||
return func(ctx context.Context, root string) (snapshot.Snapshotter, func(), error) {
|
return func(ctx context.Context, root string) (snapshot.Snapshotter, func(), error) {
|
||||||
device := setupBtrfsLoopbackDevice(t, root)
|
|
||||||
|
deviceName, cleanupDevice := testutil.NewLoopback(t, 100<<20) // 100 MB
|
||||||
|
|
||||||
|
if out, err := exec.Command("mkfs.btrfs", deviceName).CombinedOutput(); err != nil {
|
||||||
|
// not fatal
|
||||||
|
t.Skipf("could not mkfs.btrfs %s: %v (out: %q)", deviceName, err, string(out))
|
||||||
|
}
|
||||||
|
if out, err := exec.Command("mount", deviceName, root).CombinedOutput(); err != nil {
|
||||||
|
// not fatal
|
||||||
|
t.Skipf("could not mount %s: %v (out: %q)", deviceName, err, string(out))
|
||||||
|
}
|
||||||
|
|
||||||
snapshotter, err := NewSnapshotter(root)
|
snapshotter, err := NewSnapshotter(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return snapshotter, func() {
|
return snapshotter, func() {
|
||||||
device.remove(t)
|
testutil.Unmount(t, root)
|
||||||
|
cleanupDevice()
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,150 +138,3 @@ func TestBtrfsMounts(t *testing.T) {
|
|||||||
t.Fatal(err)
|
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
|
|
||||||
if err := file.Truncate(100 << 20); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file.Close()
|
|
||||||
|
|
||||||
// create device
|
|
||||||
losetup := exec.Command("losetup", "--find", "--show", file.Name())
|
|
||||||
p, err := losetup.Output()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceName := strings.TrimSpace(string(p))
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetBtrfsDevice(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
expectedDevice string
|
|
||||||
expectedError string
|
|
||||||
root string
|
|
||||||
mounts []mount.Info
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
expectedDevice: "/dev/loop0",
|
|
||||||
root: "/var/lib/containerd/snapshot/btrfs",
|
|
||||||
mounts: []mount.Info{
|
|
||||||
{Root: "/", Mountpoint: "/", FSType: "ext4", Source: "/dev/sda1"},
|
|
||||||
{Root: "/", Mountpoint: "/var/lib/containerd/snapshot/btrfs", FSType: "btrfs", Source: "/dev/loop0"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expectedError: "/var/lib/containerd/snapshot/btrfs is not mounted as btrfs",
|
|
||||||
root: "/var/lib/containerd/snapshot/btrfs",
|
|
||||||
mounts: []mount.Info{
|
|
||||||
{Root: "/", Mountpoint: "/", FSType: "ext4", Source: "/dev/sda1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expectedDevice: "/dev/sda1",
|
|
||||||
root: "/var/lib/containerd/snapshot/btrfs",
|
|
||||||
mounts: []mount.Info{
|
|
||||||
{Root: "/", Mountpoint: "/", FSType: "btrfs", Source: "/dev/sda1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expectedDevice: "/dev/sda2",
|
|
||||||
root: "/var/lib/containerd/snapshot/btrfs",
|
|
||||||
mounts: []mount.Info{
|
|
||||||
{Root: "/", Mountpoint: "/", FSType: "btrfs", Source: "/dev/sda1"},
|
|
||||||
{Root: "/", Mountpoint: "/var/lib/containerd/snapshot/btrfs", FSType: "btrfs", Source: "/dev/sda2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expectedDevice: "/dev/sda2",
|
|
||||||
root: "/var/lib/containerd/snapshot/btrfs",
|
|
||||||
mounts: []mount.Info{
|
|
||||||
{Root: "/", Mountpoint: "/var/lib/containerd/snapshot/btrfs", FSType: "btrfs", Source: "/dev/sda2"},
|
|
||||||
{Root: "/", Mountpoint: "/var/lib/foooooooooooooooooooo/baaaaaaaaaaaaaaaaaaaar", FSType: "btrfs", Source: "/dev/sda3"}, // mountpoint length longer than /var/lib/containerd/snapshot/btrfs
|
|
||||||
{Root: "/", Mountpoint: "/", FSType: "btrfs", Source: "/dev/sda1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, tc := range testCases {
|
|
||||||
device, err := getBtrfsDevice(tc.root, tc.mounts)
|
|
||||||
if err != nil && tc.expectedError == "" {
|
|
||||||
t.Fatalf("%d: expected nil, got %v", i, err)
|
|
||||||
}
|
|
||||||
if err != nil && !strings.Contains(err.Error(), tc.expectedError) {
|
|
||||||
t.Fatalf("%d: expected %s, got %v", i, tc.expectedError, err)
|
|
||||||
}
|
|
||||||
if err == nil && device != tc.expectedDevice {
|
|
||||||
t.Fatalf("%d: expected %s, got %s", i, tc.expectedDevice, device)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
53
testutil/loopback_linux.go
Normal file
53
testutil/loopback_linux.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLoopback creates a loopback device, and returns its device name (/dev/loopX), and its clean-up function.
|
||||||
|
func NewLoopback(t *testing.T, size int64) (string, func()) {
|
||||||
|
// create temporary file for the disk image
|
||||||
|
file, err := ioutil.TempFile("", "containerd-test-loopback")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not create temporary file for loopback: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := file.Truncate(size); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
// create device
|
||||||
|
losetup := exec.Command("losetup", "--find", "--show", file.Name())
|
||||||
|
p, err := losetup.Output()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceName := strings.TrimSpace(string(p))
|
||||||
|
t.Logf("Created loop device %s (using %s)", deviceName, file.Name())
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
// detach device
|
||||||
|
t.Logf("Removing loop device %s", deviceName)
|
||||||
|
losetup := exec.Command("losetup", "--detach", deviceName)
|
||||||
|
err := losetup.Run()
|
||||||
|
if err != nil {
|
||||||
|
t.Error("Could not remove loop device", deviceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove file
|
||||||
|
t.Logf("Removing temporary file %s", file.Name())
|
||||||
|
if err = os.Remove(file.Name()); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceName, cleanup
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user