Skip to content

Commit

Permalink
btrfs: auto-detect device
Browse files Browse the repository at this point in the history
No longer need to set `plugins.snapshot-btrfs.device` manually

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
  • Loading branch information
AkihiroSuda committed May 6, 2017
1 parent e37c337 commit 4881aa9
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 19 deletions.
54 changes: 36 additions & 18 deletions snapshot/btrfs/btrfs.go
Expand Up @@ -7,51 +7,69 @@ import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/Sirupsen/logrus"
"github.com/containerd/btrfs"
"github.com/containerd/containerd"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/mountinfo"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/snapshot"
"github.com/containerd/containerd/snapshot/storage"
"github.com/pkg/errors"
)

type btrfsConfig struct {
Device string `toml:"device"`
}

func init() {
plugin.Register("snapshot-btrfs", &plugin.Registration{
Type: plugin.SnapshotPlugin,
Config: &btrfsConfig{},
Type: plugin.SnapshotPlugin,
Init: func(ic *plugin.InitContext) (interface{}, error) {
root := filepath.Join(ic.Root, "snapshot", "btrfs")
conf := ic.Config.(*btrfsConfig)
if conf.Device == "" {
// TODO: check device for root
return nil, errors.Errorf("btrfs requires \"device\" configuration")
}
return NewSnapshotter(conf.Device, root)
return NewSnapshotter(root)
},
})
}

type snapshotter struct {
device string // maybe we can resolve it with path?
device string // device of the root
root string // root provides paths for internal storage.
ms *storage.MetaStore
}

func getBtrfsDevice(root string, mounts []*mountinfo.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
// device and root directory for snapshots and stores the metadata in
// root directory for snapshots and stores the metadata in
// a file in the provided root.
func NewSnapshotter(device, root string) (snapshot.Snapshotter, error) {
if err := os.MkdirAll(root, 0700); err != nil {
// root needs to be a mount point of btrfs.
func NewSnapshotter(root string) (snapshot.Snapshotter, error) {
mounts, err := mountinfo.GetMounts()
if err != nil {
return nil, err
}
device, err := getBtrfsDevice(root, mounts)
if err != nil {
return nil, err
}

var (
active = filepath.Join(root, "active")
snapshots = filepath.Join(root, "snapshots")
Expand Down Expand Up @@ -193,7 +211,7 @@ func (b *snapshotter) mounts(dir string) ([]containerd.Mount, error) {
return []containerd.Mount{
{
Type: "btrfs",
Source: b.device, // device?
Source: b.device,
// NOTE(stevvooe): While it would be nice to use to uuids for
// mounts, they don't work reliably if the uuids are missing.
Options: options,
Expand Down
64 changes: 63 additions & 1 deletion snapshot/btrfs/btrfs_test.go
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

"github.com/containerd/containerd"
"github.com/containerd/containerd/mountinfo"
"github.com/containerd/containerd/snapshot"
"github.com/containerd/containerd/snapshot/testsuite"
"github.com/containerd/containerd/testutil"
Expand All @@ -24,7 +25,7 @@ const (
func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshot.Snapshotter, func(), error) {
return func(ctx context.Context, root string) (snapshot.Snapshotter, func(), error) {
device := setupBtrfsLoopbackDevice(t, root)
snapshotter, err := NewSnapshotter(device.deviceName, root)
snapshotter, err := NewSnapshotter(root)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -215,3 +216,64 @@ func (device *testDevice) remove(t *testing.T) {
t.Error(err)
}
}

func TestGetBtrfsDevice(t *testing.T) {
testCases := []struct {
expectedDevice string
expectedError string
root string
mounts []*mountinfo.Info
}{
{
expectedDevice: "/dev/loop0",
root: "/var/lib/containerd/snapshot/btrfs",
mounts: []*mountinfo.Info{
&mountinfo.Info{Root: "/", Mountpoint: "/", Fstype: "ext4", Source: "/dev/sda1"},
&mountinfo.Info{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: []*mountinfo.Info{
&mountinfo.Info{Root: "/", Mountpoint: "/", Fstype: "ext4", Source: "/dev/sda1"},
},
},
{
expectedDevice: "/dev/sda1",
root: "/var/lib/containerd/snapshot/btrfs",
mounts: []*mountinfo.Info{
&mountinfo.Info{Root: "/", Mountpoint: "/", Fstype: "btrfs", Source: "/dev/sda1"},
},
},
{
expectedDevice: "/dev/sda2",
root: "/var/lib/containerd/snapshot/btrfs",
mounts: []*mountinfo.Info{
&mountinfo.Info{Root: "/", Mountpoint: "/", Fstype: "btrfs", Source: "/dev/sda1"},
&mountinfo.Info{Root: "/", Mountpoint: "/var/lib/containerd/snapshot/btrfs", Fstype: "btrfs", Source: "/dev/sda2"},
},
},
{
expectedDevice: "/dev/sda2",
root: "/var/lib/containerd/snapshot/btrfs",
mounts: []*mountinfo.Info{
&mountinfo.Info{Root: "/", Mountpoint: "/var/lib/containerd/snapshot/btrfs", Fstype: "btrfs", Source: "/dev/sda2"},
&mountinfo.Info{Root: "/", Mountpoint: "/var/lib/foooooooooooooooooooo/baaaaaaaaaaaaaaaaaaaar", Fstype: "btrfs", Source: "/dev/sda3"}, // mountpoint length longer than /var/lib/containerd/snapshot/btrfs
&mountinfo.Info{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)
}
}
}

0 comments on commit 4881aa9

Please sign in to comment.