From 030d5dde1ee1adb95ae907e5a228ab7253225ad4 Mon Sep 17 00:00:00 2001 From: Stephen J Day Date: Mon, 12 Dec 2016 21:53:48 -0800 Subject: [PATCH] btrfs: add initial implementation of btrfs snapshot driver Signed-off-by: Stephen J Day --- snapshot/btrfs.go | 71 ++++++++++++++++++++++++++++++++++ snapshot/btrfs_test.go | 87 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 snapshot/btrfs.go create mode 100644 snapshot/btrfs_test.go diff --git a/snapshot/btrfs.go b/snapshot/btrfs.go new file mode 100644 index 0000000..32a40ff --- /dev/null +++ b/snapshot/btrfs.go @@ -0,0 +1,71 @@ +package snapshot + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/docker/containerd" + "github.com/stevvooe/go-btrfs" +) + +type Btrfs struct { + device string // maybe we can resolve it with path? + root string // root provides paths for internal storage. +} + +func NewBtrfs(device, root string) (*Btrfs, error) { + return &Btrfs{device: device, root: root}, nil +} + +func (lm *Btrfs) Prepare(key, parent string) ([]containerd.Mount, error) { + active := filepath.Join(lm.root, "active") + if err := os.MkdirAll(active, 0755); err != nil { + return nil, err + } + + dir := filepath.Join(active, hash(key)) + + fmt.Println("dir", dir) + if parent == "" { + // create new subvolume + // btrfs subvolume create /dir + if err := btrfs.SubvolCreate(dir); err != nil { + return nil, err + } + } else { + // btrfs subvolume snapshot /parent /subvol + if err := btrfs.SubvolSnapshot(dir, parent, false); err != nil { + return nil, err + } + } + + // get the subvolume id back out for the mount + info, err := btrfs.SubvolInfo(dir) + if err != nil { + return nil, err + } + + return []containerd.Mount{ + { + Type: "btrfs", + Source: lm.device, // device? + Target: key, + + // NOTE(stevvooe): While it would be nice to use to uuids for + // mounts, they don't work reliably if the uuids are missing. + Options: []string{fmt.Sprintf("subvolid=%d", info.ID)}, + }, + }, nil +} + +func (lm *Btrfs) Commit(name, key string) error { + dir := filepath.Join(lm.root, "active", hash(key)) + + fmt.Println("commit to", name) + if err := btrfs.SubvolSnapshot(name, dir, true); err != nil { + return err + } + + return btrfs.SubvolDelete(dir) +} diff --git a/snapshot/btrfs_test.go b/snapshot/btrfs_test.go new file mode 100644 index 0000000..2511bd0 --- /dev/null +++ b/snapshot/btrfs_test.go @@ -0,0 +1,87 @@ +package snapshot + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/docker/containerd" +) + +func TestBtrfs(t *testing.T) { + // SORRY(stevvooe): This is where I mount a btrfs loopback. We can probably + // set this up as part of the test. + root, err := ioutil.TempDir("/tmp/snapshots", "TestBtrfsPrepare-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + // TODO(stevvooe): Cleanup subvolumes + + sm, err := NewBtrfs("/dev/loop0", root) + if err != nil { + t.Fatal(err) + } + mounts, err := sm.Prepare(filepath.Join(root, "test"), "") + if err != nil { + t.Fatal(err) + } + t.Log(mounts) + + for _, mount := range mounts { + if mount.Type != "btrfs" { + t.Fatal("wrong mount type: %v != btrfs", mount.Type) + } + + // assumes the first, maybe incorrect in the future + if !strings.HasPrefix(mount.Options[0], "subvolid=") { + t.Fatal("no subvolid option in %v", mount.Options) + } + } + + if err := os.MkdirAll(mounts[0].Target, 0755); err != nil { + t.Fatal(err) + } + if err := containerd.MountAll(mounts...); err != nil { + t.Fatal(err) + } + + // write in some data + if err := ioutil.WriteFile(filepath.Join(mounts[0].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 := sm.Commit(filepath.Join(root, "snapshots/committed"), filepath.Join(root, "test")); err != nil { + t.Fatal(err) + } + + mounts, err = sm.Prepare(filepath.Join(root, "test2"), filepath.Join(root, "snapshots/committed")) + if err != nil { + t.Fatal(err) + } + + if err := os.MkdirAll(filepath.Join(root, "test2"), 0755); err != nil { + t.Fatal(err) + } + + if err := containerd.MountAll(mounts...); err != nil { + t.Fatal(err) + } + + // TODO(stevvooe): Verify contents of "foo" + if err := ioutil.WriteFile(filepath.Join(mounts[0].Target, "bar"), []byte("content"), 0777); err != nil { + t.Fatal(err) + } + + if err := sm.Commit(filepath.Join(root, "snapshots/committed2"), filepath.Join(root, "test2")); err != nil { + t.Fatal(err) + } +}