github.com/containerd/Containerd@v1.4.13/snapshots/overlay/overlay_test.go (about) 1 // +build linux 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 overlay 20 21 import ( 22 "context" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "syscall" 28 "testing" 29 30 "github.com/containerd/containerd/mount" 31 "github.com/containerd/containerd/pkg/testutil" 32 "github.com/containerd/containerd/snapshots" 33 "github.com/containerd/containerd/snapshots/storage" 34 "github.com/containerd/containerd/snapshots/testsuite" 35 ) 36 37 func newSnapshotterWithOpts(opts ...Opt) testsuite.SnapshotterFunc { 38 return func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) { 39 snapshotter, err := NewSnapshotter(root, opts...) 40 if err != nil { 41 return nil, nil, err 42 } 43 44 return snapshotter, func() error { return snapshotter.Close() }, nil 45 } 46 } 47 48 func TestOverlay(t *testing.T) { 49 testutil.RequiresRoot(t) 50 optTestCases := map[string][]Opt{ 51 "no opt": nil, 52 // default in init() 53 "AsynchronousRemove": {AsynchronousRemove}, 54 } 55 56 for optsName, opts := range optTestCases { 57 t.Run(optsName, func(t *testing.T) { 58 newSnapshotter := newSnapshotterWithOpts(opts...) 59 testsuite.SnapshotterSuite(t, "Overlay", newSnapshotter) 60 t.Run("TestOverlayMounts", func(t *testing.T) { 61 testOverlayMounts(t, newSnapshotter) 62 }) 63 t.Run("TestOverlayCommit", func(t *testing.T) { 64 testOverlayCommit(t, newSnapshotter) 65 }) 66 t.Run("TestOverlayOverlayMount", func(t *testing.T) { 67 testOverlayOverlayMount(t, newSnapshotter) 68 }) 69 t.Run("TestOverlayOverlayRead", func(t *testing.T) { 70 testOverlayOverlayRead(t, newSnapshotter) 71 }) 72 t.Run("TestOverlayView", func(t *testing.T) { 73 testOverlayView(t, newSnapshotter) 74 }) 75 }) 76 } 77 } 78 79 func testOverlayMounts(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) { 80 ctx := context.TODO() 81 root, err := ioutil.TempDir("", "overlay") 82 if err != nil { 83 t.Fatal(err) 84 } 85 defer os.RemoveAll(root) 86 o, _, err := newSnapshotter(ctx, root) 87 if err != nil { 88 t.Fatal(err) 89 } 90 mounts, err := o.Prepare(ctx, "/tmp/test", "") 91 if err != nil { 92 t.Fatal(err) 93 } 94 if len(mounts) != 1 { 95 t.Errorf("should only have 1 mount but received %d", len(mounts)) 96 } 97 m := mounts[0] 98 if m.Type != "bind" { 99 t.Errorf("mount type should be bind but received %q", m.Type) 100 } 101 expected := filepath.Join(root, "snapshots", "1", "fs") 102 if m.Source != expected { 103 t.Errorf("expected source %q but received %q", expected, m.Source) 104 } 105 if m.Options[0] != "rw" { 106 t.Errorf("expected mount option rw but received %q", m.Options[0]) 107 } 108 if m.Options[1] != "rbind" { 109 t.Errorf("expected mount option rbind but received %q", m.Options[1]) 110 } 111 } 112 113 func testOverlayCommit(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) { 114 ctx := context.TODO() 115 root, err := ioutil.TempDir("", "overlay") 116 if err != nil { 117 t.Fatal(err) 118 } 119 defer os.RemoveAll(root) 120 o, _, err := newSnapshotter(ctx, root) 121 if err != nil { 122 t.Fatal(err) 123 } 124 key := "/tmp/test" 125 mounts, err := o.Prepare(ctx, key, "") 126 if err != nil { 127 t.Fatal(err) 128 } 129 m := mounts[0] 130 if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil { 131 t.Fatal(err) 132 } 133 if err := o.Commit(ctx, "base", key); err != nil { 134 t.Fatal(err) 135 } 136 } 137 138 func testOverlayOverlayMount(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) { 139 ctx := context.TODO() 140 root, err := ioutil.TempDir("", "overlay") 141 if err != nil { 142 t.Fatal(err) 143 } 144 defer os.RemoveAll(root) 145 o, _, err := newSnapshotter(ctx, root) 146 if err != nil { 147 t.Fatal(err) 148 } 149 key := "/tmp/test" 150 if _, err = o.Prepare(ctx, key, ""); err != nil { 151 t.Fatal(err) 152 } 153 if err := o.Commit(ctx, "base", key); err != nil { 154 t.Fatal(err) 155 } 156 var mounts []mount.Mount 157 if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil { 158 t.Fatal(err) 159 } 160 if len(mounts) != 1 { 161 t.Errorf("should only have 1 mount but received %d", len(mounts)) 162 } 163 m := mounts[0] 164 if m.Type != "overlay" { 165 t.Errorf("mount type should be overlay but received %q", m.Type) 166 } 167 if m.Source != "overlay" { 168 t.Errorf("expected source %q but received %q", "overlay", m.Source) 169 } 170 var ( 171 bp = getBasePath(ctx, o, root, "/tmp/layer2") 172 work = "workdir=" + filepath.Join(bp, "work") 173 upper = "upperdir=" + filepath.Join(bp, "fs") 174 lower = "lowerdir=" + getParents(ctx, o, root, "/tmp/layer2")[0] 175 ) 176 177 expected := []string{ 178 "index=off", 179 } 180 if userxattr, err := NeedsUserXAttr(root); err != nil { 181 t.Fatal(err) 182 } else if userxattr { 183 expected = append(expected, "userxattr") 184 } 185 expected = append(expected, []string{ 186 work, 187 upper, 188 lower, 189 }...) 190 for i, v := range expected { 191 if m.Options[i] != v { 192 t.Errorf("expected %q but received %q", v, m.Options[i]) 193 } 194 } 195 } 196 197 func getBasePath(ctx context.Context, sn snapshots.Snapshotter, root, key string) string { 198 o := sn.(*snapshotter) 199 ctx, t, err := o.ms.TransactionContext(ctx, false) 200 if err != nil { 201 panic(err) 202 } 203 defer t.Rollback() 204 205 s, err := storage.GetSnapshot(ctx, key) 206 if err != nil { 207 panic(err) 208 } 209 210 return filepath.Join(root, "snapshots", s.ID) 211 } 212 213 func getParents(ctx context.Context, sn snapshots.Snapshotter, root, key string) []string { 214 o := sn.(*snapshotter) 215 ctx, t, err := o.ms.TransactionContext(ctx, false) 216 if err != nil { 217 panic(err) 218 } 219 defer t.Rollback() 220 s, err := storage.GetSnapshot(ctx, key) 221 if err != nil { 222 panic(err) 223 } 224 parents := make([]string, len(s.ParentIDs)) 225 for i := range s.ParentIDs { 226 parents[i] = filepath.Join(root, "snapshots", s.ParentIDs[i], "fs") 227 } 228 return parents 229 } 230 231 func testOverlayOverlayRead(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) { 232 testutil.RequiresRoot(t) 233 ctx := context.TODO() 234 root, err := ioutil.TempDir("", "overlay") 235 if err != nil { 236 t.Fatal(err) 237 } 238 defer os.RemoveAll(root) 239 o, _, err := newSnapshotter(ctx, root) 240 if err != nil { 241 t.Fatal(err) 242 } 243 key := "/tmp/test" 244 mounts, err := o.Prepare(ctx, key, "") 245 if err != nil { 246 t.Fatal(err) 247 } 248 m := mounts[0] 249 if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil { 250 t.Fatal(err) 251 } 252 if err := o.Commit(ctx, "base", key); err != nil { 253 t.Fatal(err) 254 } 255 if mounts, err = o.Prepare(ctx, "/tmp/layer2", "base"); err != nil { 256 t.Fatal(err) 257 } 258 dest := filepath.Join(root, "dest") 259 if err := os.Mkdir(dest, 0700); err != nil { 260 t.Fatal(err) 261 } 262 if err := mount.All(mounts, dest); err != nil { 263 t.Fatal(err) 264 } 265 defer syscall.Unmount(dest, 0) 266 data, err := ioutil.ReadFile(filepath.Join(dest, "foo")) 267 if err != nil { 268 t.Fatal(err) 269 } 270 if e := string(data); e != "hi" { 271 t.Fatalf("expected file contents hi but got %q", e) 272 } 273 } 274 275 func testOverlayView(t *testing.T, newSnapshotter testsuite.SnapshotterFunc) { 276 ctx := context.TODO() 277 root, err := ioutil.TempDir("", "overlay") 278 if err != nil { 279 t.Fatal(err) 280 } 281 defer os.RemoveAll(root) 282 o, _, err := newSnapshotter(ctx, root) 283 if err != nil { 284 t.Fatal(err) 285 } 286 key := "/tmp/base" 287 mounts, err := o.Prepare(ctx, key, "") 288 if err != nil { 289 t.Fatal(err) 290 } 291 m := mounts[0] 292 if err := ioutil.WriteFile(filepath.Join(m.Source, "foo"), []byte("hi"), 0660); err != nil { 293 t.Fatal(err) 294 } 295 if err := o.Commit(ctx, "base", key); err != nil { 296 t.Fatal(err) 297 } 298 299 key = "/tmp/top" 300 _, err = o.Prepare(ctx, key, "base") 301 if err != nil { 302 t.Fatal(err) 303 } 304 if err := ioutil.WriteFile(filepath.Join(getParents(ctx, o, root, "/tmp/top")[0], "foo"), []byte("hi, again"), 0660); err != nil { 305 t.Fatal(err) 306 } 307 if err := o.Commit(ctx, "top", key); err != nil { 308 t.Fatal(err) 309 } 310 311 mounts, err = o.View(ctx, "/tmp/view1", "base") 312 if err != nil { 313 t.Fatal(err) 314 } 315 if len(mounts) != 1 { 316 t.Fatalf("should only have 1 mount but received %d", len(mounts)) 317 } 318 m = mounts[0] 319 if m.Type != "bind" { 320 t.Errorf("mount type should be bind but received %q", m.Type) 321 } 322 expected := getParents(ctx, o, root, "/tmp/view1")[0] 323 if m.Source != expected { 324 t.Errorf("expected source %q but received %q", expected, m.Source) 325 } 326 if m.Options[0] != "ro" { 327 t.Errorf("expected mount option ro but received %q", m.Options[0]) 328 } 329 if m.Options[1] != "rbind" { 330 t.Errorf("expected mount option rbind but received %q", m.Options[1]) 331 } 332 333 mounts, err = o.View(ctx, "/tmp/view2", "top") 334 if err != nil { 335 t.Fatal(err) 336 } 337 if len(mounts) != 1 { 338 t.Fatalf("should only have 1 mount but received %d", len(mounts)) 339 } 340 m = mounts[0] 341 if m.Type != "overlay" { 342 t.Errorf("mount type should be overlay but received %q", m.Type) 343 } 344 if m.Source != "overlay" { 345 t.Errorf("mount source should be overlay but received %q", m.Source) 346 } 347 348 expectedOptions := 2 349 userxattr, err := NeedsUserXAttr(root) 350 if err != nil { 351 t.Fatal(err) 352 } 353 if userxattr { 354 expectedOptions++ 355 } 356 357 if len(m.Options) != expectedOptions { 358 t.Errorf("expected %d additional mount option but got %d", expectedOptions, len(m.Options)) 359 } 360 lowers := getParents(ctx, o, root, "/tmp/view2") 361 expected = fmt.Sprintf("lowerdir=%s:%s", lowers[0], lowers[1]) 362 optIdx := 1 363 if userxattr { 364 optIdx++ 365 } 366 if m.Options[optIdx] != expected { 367 t.Errorf("expected option %q but received %q", expected, m.Options[optIdx]) 368 } 369 }