btrfs: auto-detect device

No longer need to set `plugins.snapshot-btrfs.device` manually

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
This commit is contained in:
Akihiro Suda 2017-05-06 16:46:58 +00:00
parent e37c337a3b
commit d5707d3ac7
2 changed files with 99 additions and 19 deletions

View File

@ -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{},
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
}
// NewSnapshotter returns a Snapshotter using btrfs. Uses the provided
// device and 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 {
return nil, err
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
// root directory for snapshots and stores the metadata in
// a file in the provided root.
// 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")
@ -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,

View File

@ -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"
@ -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)
}
@ -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{
{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: []*mountinfo.Info{
{Root: "/", Mountpoint: "/", Fstype: "ext4", Source: "/dev/sda1"},
},
},
{
expectedDevice: "/dev/sda1",
root: "/var/lib/containerd/snapshot/btrfs",
mounts: []*mountinfo.Info{
{Root: "/", Mountpoint: "/", Fstype: "btrfs", Source: "/dev/sda1"},
},
},
{
expectedDevice: "/dev/sda2",
root: "/var/lib/containerd/snapshot/btrfs",
mounts: []*mountinfo.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: []*mountinfo.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)
}
}
}