github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/snapshots/btrfs/btrfs_test.go (about) 1 // +build linux,!no_btrfs 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package btrfs 20 21 import ( 22 "context" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/containerd/containerd/mount" 32 "github.com/containerd/containerd/pkg/testutil" 33 "github.com/containerd/containerd/plugin" 34 "github.com/containerd/containerd/snapshots" 35 "github.com/containerd/containerd/snapshots/testsuite" 36 "github.com/containerd/continuity/testutil/loopback" 37 "github.com/pkg/errors" 38 "golang.org/x/sys/unix" 39 ) 40 41 func boltSnapshotter(t *testing.T) func(context.Context, string) (snapshots.Snapshotter, func() error, error) { 42 mkbtrfs, err := exec.LookPath("mkfs.btrfs") 43 if err != nil { 44 t.Skipf("could not find mkfs.btrfs: %v", err) 45 } 46 47 // TODO: Check for btrfs in /proc/module and skip if not loaded 48 49 return func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) { 50 51 loopbackSize := int64(128 << 20) // 128 MB 52 // mkfs.btrfs creates a fs which has a blocksize equal to the system default pagesize. If that pagesize 53 // is > 4KB, mounting the fs will fail unless we increase the size of the file used by mkfs.btrfs 54 if os.Getpagesize() > 4096 { 55 loopbackSize = int64(650 << 20) // 650 MB 56 } 57 loop, err := loopback.New(loopbackSize) 58 59 if err != nil { 60 return nil, nil, err 61 } 62 63 if out, err := exec.Command(mkbtrfs, loop.Device).CombinedOutput(); err != nil { 64 loop.Close() 65 return nil, nil, errors.Wrapf(err, "failed to make btrfs filesystem (out: %q)", out) 66 } 67 // sync after a mkfs on the loopback before trying to mount the device 68 unix.Sync() 69 70 var snapshotter snapshots.Snapshotter 71 for i := 0; i < 5; i++ { 72 if out, err := exec.Command("mount", loop.Device, root).CombinedOutput(); err != nil { 73 loop.Close() 74 return nil, nil, errors.Wrapf(err, "failed to mount device %s (out: %q)", loop.Device, out) 75 } 76 77 if i > 0 { 78 time.Sleep(10 * time.Duration(i) * time.Millisecond) 79 } 80 81 snapshotter, err = NewSnapshotter(root) 82 if err == nil { 83 break 84 } else if !errors.Is(err, plugin.ErrSkipPlugin) { 85 return nil, nil, err 86 } 87 88 t.Logf("Attempt %d to create btrfs snapshotter failed: %#v", i+1, err) 89 90 // unmount and try again 91 unix.Unmount(root, 0) 92 } 93 if snapshotter == nil { 94 return nil, nil, errors.Wrap(err, "failed to successfully create snapshotter after 5 attempts") 95 } 96 97 return snapshotter, func() error { 98 if err := snapshotter.Close(); err != nil { 99 return err 100 } 101 err := mount.UnmountAll(root, unix.MNT_DETACH) 102 if cerr := loop.Close(); cerr != nil { 103 err = errors.Wrap(cerr, "device cleanup failed") 104 } 105 return err 106 }, nil 107 } 108 } 109 110 func TestBtrfs(t *testing.T) { 111 testutil.RequiresRoot(t) 112 testsuite.SnapshotterSuite(t, "Btrfs", boltSnapshotter(t)) 113 } 114 115 func TestBtrfsMounts(t *testing.T) { 116 testutil.RequiresRoot(t) 117 ctx := context.Background() 118 119 // create temporary directory for mount point 120 mountPoint, err := ioutil.TempDir("", "containerd-btrfs-test") 121 if err != nil { 122 t.Fatal("could not create mount point for btrfs test", err) 123 } 124 defer os.RemoveAll(mountPoint) 125 t.Log("temporary mount point created", mountPoint) 126 127 root, err := ioutil.TempDir(mountPoint, "TestBtrfsPrepare-") 128 if err != nil { 129 t.Fatal(err) 130 } 131 defer os.RemoveAll(root) 132 133 b, c, err := boltSnapshotter(t)(ctx, root) 134 if err != nil { 135 t.Fatal(err) 136 } 137 defer c() 138 139 target := filepath.Join(root, "test") 140 mounts, err := b.Prepare(ctx, target, "") 141 if err != nil { 142 t.Fatal(err) 143 } 144 t.Log(mounts) 145 146 for _, mount := range mounts { 147 if mount.Type != "btrfs" { 148 t.Fatalf("wrong mount type: %v != btrfs", mount.Type) 149 } 150 151 // assumes the first, maybe incorrect in the future 152 if !strings.HasPrefix(mount.Options[0], "subvolid=") { 153 t.Fatalf("no subvolid option in %v", mount.Options) 154 } 155 } 156 157 if err := os.MkdirAll(target, 0755); err != nil { 158 t.Fatal(err) 159 } 160 if err := mount.All(mounts, target); err != nil { 161 t.Fatal(err) 162 } 163 defer testutil.Unmount(t, target) 164 165 // write in some data 166 if err := ioutil.WriteFile(filepath.Join(target, "foo"), []byte("content"), 0777); err != nil { 167 t.Fatal(err) 168 } 169 170 // TODO(stevvooe): We don't really make this with the driver, but that 171 // might prove annoying in practice. 172 if err := os.MkdirAll(filepath.Join(root, "snapshots"), 0755); err != nil { 173 t.Fatal(err) 174 } 175 176 if err := b.Commit(ctx, filepath.Join(root, "snapshots/committed"), filepath.Join(root, "test")); err != nil { 177 t.Fatal(err) 178 } 179 180 target = filepath.Join(root, "test2") 181 mounts, err = b.Prepare(ctx, target, filepath.Join(root, "snapshots/committed")) 182 if err != nil { 183 t.Fatal(err) 184 } 185 186 if err := os.MkdirAll(target, 0755); err != nil { 187 t.Fatal(err) 188 } 189 190 if err := mount.All(mounts, target); err != nil { 191 t.Fatal(err) 192 } 193 defer testutil.Unmount(t, target) 194 195 // TODO(stevvooe): Verify contents of "foo" 196 if err := ioutil.WriteFile(filepath.Join(target, "bar"), []byte("content"), 0777); err != nil { 197 t.Fatal(err) 198 } 199 200 if err := b.Commit(ctx, filepath.Join(root, "snapshots/committed2"), target); err != nil { 201 t.Fatal(err) 202 } 203 }