github.com/containerd/containerd/v2@v2.0.0-rc.2/core/snapshots/testsuite/testsuite.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package testsuite 18 19 import ( 20 "context" 21 //nolint:revive // go-digest needs the blank import. See https://github.com/opencontainers/go-digest#usage. 22 _ "crypto/sha256" 23 "fmt" 24 "os" 25 "path/filepath" 26 "runtime" 27 "sort" 28 "testing" 29 "time" 30 31 "github.com/containerd/containerd/v2/core/mount" 32 "github.com/containerd/containerd/v2/core/snapshots" 33 "github.com/containerd/containerd/v2/internal/randutil" 34 "github.com/containerd/containerd/v2/pkg/namespaces" 35 "github.com/containerd/containerd/v2/pkg/testutil" 36 "github.com/containerd/continuity/fs/fstest" 37 "github.com/containerd/errdefs" 38 "github.com/containerd/log/logtest" 39 "github.com/stretchr/testify/assert" 40 ) 41 42 // SnapshotterFunc is used in SnapshotterSuite 43 type SnapshotterFunc func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error) 44 45 // SnapshotterSuite runs a test suite on the snapshotter given a factory function. 46 func SnapshotterSuite(t *testing.T, name string, snapshotterFn SnapshotterFunc) { 47 restoreMask := clearMask() 48 defer restoreMask() 49 50 t.Run("Basic", makeTest(name, snapshotterFn, checkSnapshotterBasic)) 51 t.Run("StatActive", makeTest(name, snapshotterFn, checkSnapshotterStatActive)) 52 t.Run("StatComitted", makeTest(name, snapshotterFn, checkSnapshotterStatCommitted)) 53 t.Run("TransitivityTest", makeTest(name, snapshotterFn, checkSnapshotterTransitivity)) 54 t.Run("PreareViewFailingtest", makeTest(name, snapshotterFn, checkSnapshotterPrepareView)) 55 t.Run("Update", makeTest(name, snapshotterFn, checkUpdate)) 56 t.Run("Remove", makeTest(name, snapshotterFn, checkRemove)) 57 t.Run("Walk", makeTest(name, snapshotterFn, checkWalk)) 58 59 t.Run("LayerFileupdate", makeTest(name, snapshotterFn, checkLayerFileUpdate)) 60 t.Run("RemoveDirectoryInLowerLayer", makeTest(name, snapshotterFn, checkRemoveDirectoryInLowerLayer)) 61 t.Run("Chown", makeTest(name, snapshotterFn, checkChown)) 62 t.Run("DirectoryPermissionOnCommit", makeTest(name, snapshotterFn, checkDirectoryPermissionOnCommit)) 63 t.Run("RemoveIntermediateSnapshot", makeTest(name, snapshotterFn, checkRemoveIntermediateSnapshot)) 64 t.Run("DeletedFilesInChildSnapshot", makeTest(name, snapshotterFn, checkDeletedFilesInChildSnapshot)) 65 t.Run("MoveFileFromLowerLayer", makeTest(name, snapshotterFn, checkFileFromLowerLayer)) 66 67 t.Run("ViewReadonly", makeTest(name, snapshotterFn, checkSnapshotterViewReadonly)) 68 69 t.Run("StatInWalk", makeTest(name, snapshotterFn, checkStatInWalk)) 70 t.Run("CloseTwice", makeTest(name, snapshotterFn, closeTwice)) 71 t.Run("RootPermission", makeTest(name, snapshotterFn, checkRootPermission)) 72 73 // Different snapshotters behave slightly differently in the tests below. 74 t.Run("Rename", makeTest(name, snapshotterFn, checkRename(name))) 75 t.Run("128LayersMount", makeTest(name, snapshotterFn, check128LayersMount(name))) 76 } 77 78 func makeTest( 79 snapshotter string, 80 snapshotterFn func(ctx context.Context, root string) (snapshots.Snapshotter, func() error, error), 81 fn func(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string), 82 ) func(t *testing.T) { 83 return func(t *testing.T) { 84 t.Parallel() 85 86 ctx := logtest.WithT(context.Background(), t) 87 ctx = namespaces.WithNamespace(ctx, "testsuite") 88 // Make two directories: a snapshotter root and a play area for the tests: 89 // 90 // /tmp 91 // work/ -> passed to test functions 92 // root/ -> passed to snapshotter 93 // 94 tmpDir, err := os.MkdirTemp("", "snapshot-suite-"+snapshotter+"-") 95 if err != nil { 96 t.Fatal(err) 97 } 98 defer os.RemoveAll(tmpDir) 99 100 root := filepath.Join(tmpDir, "root") 101 if err := os.MkdirAll(root, 0777); err != nil { 102 t.Fatal(err) 103 } 104 105 snapshotter, cleanup, err := snapshotterFn(ctx, root) 106 if err != nil { 107 t.Fatalf("Failed to initialize snapshotter: %+v", err) 108 } 109 defer func() { 110 if cleanup != nil { 111 if err := cleanup(); err != nil { 112 t.Errorf("Cleanup failed: %v", err) 113 } 114 } 115 }() 116 117 work := filepath.Join(tmpDir, "work") 118 if err := os.MkdirAll(work, 0777); err != nil { 119 t.Fatal(err) 120 } 121 122 defer testutil.DumpDirOnFailure(t, tmpDir) 123 fn(ctx, t, snapshotter, work) 124 } 125 } 126 127 var opt = snapshots.WithLabels(map[string]string{ 128 "containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339), 129 }) 130 131 // checkSnapshotterBasic tests the basic workflow of a snapshot snapshotter. 132 func checkSnapshotterBasic(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 133 initialApplier := fstest.Apply( 134 fstest.CreateFile("/foo", []byte("foo\n"), 0777), 135 fstest.CreateDir("/a", 0755), 136 fstest.CreateDir("/a/b", 0755), 137 fstest.CreateDir("/a/b/c", 0755), 138 ) 139 140 diffApplier := fstest.Apply( 141 fstest.CreateFile("/bar", []byte("bar\n"), 0777), 142 // also, change content of foo to bar 143 fstest.CreateFile("/foo", []byte("bar\n"), 0777), 144 fstest.RemoveAll("/a/b"), 145 ) 146 147 preparing := filepath.Join(work, "preparing") 148 if err := os.MkdirAll(preparing, 0777); err != nil { 149 t.Fatalf("failure reason: %+v", err) 150 } 151 152 mounts, err := snapshotter.Prepare(ctx, preparing, "", opt) 153 if err != nil { 154 t.Fatalf("failure reason: %+v", err) 155 } 156 157 if len(mounts) < 1 { 158 t.Fatal("expected mounts to have entries") 159 } 160 161 if err := mount.All(mounts, preparing); err != nil { 162 t.Fatalf("failure reason: %+v", err) 163 } 164 165 if err := initialApplier.Apply(preparing); err != nil { 166 testutil.Unmount(t, preparing) 167 t.Fatalf("failure reason: %+v", err) 168 } 169 // unmount before commit 170 testutil.Unmount(t, preparing) 171 172 committed := filepath.Join(work, "committed") 173 if err := snapshotter.Commit(ctx, committed, preparing, opt); err != nil { 174 t.Fatalf("failure reason: %+v", err) 175 } 176 177 si, err := snapshotter.Stat(ctx, committed) 178 if err != nil { 179 t.Fatalf("failure reason: %+v", err) 180 } 181 182 assert.Empty(t, si.Parent) 183 assert.Equal(t, snapshots.KindCommitted, si.Kind) 184 185 _, err = snapshotter.Stat(ctx, preparing) 186 if err == nil { 187 t.Fatalf("%s should no longer be available after Commit", preparing) 188 } 189 190 next := filepath.Join(work, "nextlayer") 191 if err := os.MkdirAll(next, 0777); err != nil { 192 t.Fatalf("failure reason: %+v", err) 193 } 194 195 mounts, err = snapshotter.Prepare(ctx, next, committed, opt) 196 if err != nil { 197 t.Fatalf("failure reason: %+v", err) 198 } 199 if err := mount.All(mounts, next); err != nil { 200 t.Fatalf("failure reason: %+v", err) 201 } 202 203 if err := fstest.CheckDirectoryEqualWithApplier(next, initialApplier); err != nil { 204 testutil.Unmount(t, next) 205 t.Fatalf("failure reason: %+v", err) 206 } 207 208 if err := diffApplier.Apply(next); err != nil { 209 testutil.Unmount(t, next) 210 t.Fatalf("failure reason: %+v", err) 211 } 212 // unmount before commit 213 testutil.Unmount(t, next) 214 215 ni, err := snapshotter.Stat(ctx, next) 216 if err != nil { 217 t.Fatal(err) 218 } 219 220 assert.Equal(t, committed, ni.Parent) 221 assert.Equal(t, snapshots.KindActive, ni.Kind) 222 223 nextCommitted := filepath.Join(work, "committed-next") 224 if err := snapshotter.Commit(ctx, nextCommitted, next, opt); err != nil { 225 t.Fatalf("failure reason: %+v", err) 226 } 227 228 si2, err := snapshotter.Stat(ctx, nextCommitted) 229 if err != nil { 230 t.Fatalf("failure reason: %+v", err) 231 } 232 233 assert.Equal(t, committed, si2.Parent) 234 assert.Equal(t, snapshots.KindCommitted, si2.Kind) 235 236 _, err = snapshotter.Stat(ctx, next) 237 if err == nil { 238 t.Fatalf("%s should no longer be available after Commit", next) 239 } 240 241 expected := map[string]snapshots.Info{ 242 si.Name: si, 243 si2.Name: si2, 244 } 245 walked := map[string]snapshots.Info{} // walk is not ordered 246 assert.Nil(t, snapshotter.Walk(ctx, func(ctx context.Context, si snapshots.Info) error { 247 walked[si.Name] = si 248 return nil 249 })) 250 251 for ek, ev := range expected { 252 av, ok := walked[ek] 253 if !ok { 254 t.Errorf("Missing stat for %v", ek) 255 continue 256 } 257 assert.Equal(t, ev, av) 258 } 259 260 nextnext := filepath.Join(work, "nextnextlayer") 261 if err := os.MkdirAll(nextnext, 0777); err != nil { 262 t.Fatalf("failure reason: %+v", err) 263 } 264 265 mounts, err = snapshotter.View(ctx, nextnext, nextCommitted, opt) 266 if err != nil { 267 t.Fatalf("failure reason: %+v", err) 268 } 269 if err := mount.All(mounts, nextnext); err != nil { 270 t.Fatalf("failure reason: %+v", err) 271 } 272 273 if err := fstest.CheckDirectoryEqualWithApplier(nextnext, 274 fstest.Apply(initialApplier, diffApplier)); err != nil { 275 testutil.Unmount(t, nextnext) 276 t.Fatalf("failure reason: %+v", err) 277 } 278 279 testutil.Unmount(t, nextnext) 280 assert.Nil(t, snapshotter.Remove(ctx, nextnext)) 281 282 err = snapshotter.Remove(ctx, committed) 283 assert.NotNil(t, err) 284 if err != nil { 285 assert.Contains(t, err.Error(), "remove") 286 } 287 288 assert.Nil(t, snapshotter.Remove(ctx, nextCommitted)) 289 assert.Nil(t, snapshotter.Remove(ctx, committed)) 290 } 291 292 // Create a New Layer on top of base layer with Prepare, Stat on new layer, should return Active layer. 293 func checkSnapshotterStatActive(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 294 preparing := filepath.Join(work, "preparing") 295 if err := os.MkdirAll(preparing, 0777); err != nil { 296 t.Fatal(err) 297 } 298 299 mounts, err := snapshotter.Prepare(ctx, preparing, "", opt) 300 if err != nil { 301 t.Fatal(err) 302 } 303 304 if len(mounts) < 1 { 305 t.Fatal("expected mounts to have entries") 306 } 307 308 if err = mount.All(mounts, preparing); err != nil { 309 t.Fatal(err) 310 } 311 defer testutil.Unmount(t, preparing) 312 313 if err = os.WriteFile(filepath.Join(preparing, "foo"), []byte("foo\n"), 0777); err != nil { 314 t.Fatal(err) 315 } 316 317 si, err := snapshotter.Stat(ctx, preparing) 318 if err != nil { 319 t.Fatal(err) 320 } 321 assert.Equal(t, si.Name, preparing) 322 assert.Equal(t, snapshots.KindActive, si.Kind) 323 assert.Equal(t, "", si.Parent) 324 } 325 326 // Commit a New Layer on top of base layer with Prepare & Commit , Stat on new layer, should return Committed layer. 327 func checkSnapshotterStatCommitted(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 328 preparing := filepath.Join(work, "preparing") 329 if err := os.MkdirAll(preparing, 0777); err != nil { 330 t.Fatal(err) 331 } 332 333 mounts, err := snapshotter.Prepare(ctx, preparing, "", opt) 334 if err != nil { 335 t.Fatal(err) 336 } 337 338 if len(mounts) < 1 { 339 t.Fatal("expected mounts to have entries") 340 } 341 342 if err = mount.All(mounts, preparing); err != nil { 343 t.Fatal(err) 344 } 345 defer testutil.Unmount(t, preparing) 346 347 if err = os.WriteFile(filepath.Join(preparing, "foo"), []byte("foo\n"), 0777); err != nil { 348 t.Fatal(err) 349 } 350 351 committed := filepath.Join(work, "committed") 352 if err = snapshotter.Commit(ctx, committed, preparing, opt); err != nil { 353 t.Fatal(err) 354 } 355 356 si, err := snapshotter.Stat(ctx, committed) 357 if err != nil { 358 t.Fatal(err) 359 } 360 assert.Equal(t, si.Name, committed) 361 assert.Equal(t, snapshots.KindCommitted, si.Kind) 362 assert.Equal(t, "", si.Parent) 363 364 } 365 366 func snapshotterPrepareMount(ctx context.Context, snapshotter snapshots.Snapshotter, diffPathName string, parent string, work string) (string, error) { 367 preparing := filepath.Join(work, diffPathName) 368 if err := os.MkdirAll(preparing, 0777); err != nil { 369 return "", err 370 } 371 372 mounts, err := snapshotter.Prepare(ctx, preparing, parent, opt) 373 if err != nil { 374 return "", err 375 } 376 377 if len(mounts) < 1 { 378 return "", fmt.Errorf("expected mounts to have entries") 379 } 380 381 if err = mount.All(mounts, preparing); err != nil { 382 return "", err 383 } 384 return preparing, nil 385 } 386 387 // Given A <- B <- C, B is the parent of C and A is a transitive parent of C (in this case, a "grandparent") 388 func checkSnapshotterTransitivity(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 389 preparing, err := snapshotterPrepareMount(ctx, snapshotter, "preparing", "", work) 390 if err != nil { 391 t.Fatal(err) 392 } 393 394 if err = os.WriteFile(filepath.Join(preparing, "foo"), []byte("foo\n"), 0777); err != nil { 395 testutil.Unmount(t, preparing) 396 t.Fatal(err) 397 } 398 testutil.Unmount(t, preparing) 399 400 snapA := filepath.Join(work, "snapA") 401 if err = snapshotter.Commit(ctx, snapA, preparing, opt); err != nil { 402 t.Fatal(err) 403 } 404 405 next, err := snapshotterPrepareMount(ctx, snapshotter, "next", snapA, work) 406 if err != nil { 407 t.Fatal(err) 408 } 409 410 if err = os.WriteFile(filepath.Join(next, "foo"), []byte("foo bar\n"), 0777); err != nil { 411 testutil.Unmount(t, next) 412 t.Fatal(err) 413 } 414 testutil.Unmount(t, next) 415 416 snapB := filepath.Join(work, "snapB") 417 if err = snapshotter.Commit(ctx, snapB, next, opt); err != nil { 418 t.Fatal(err) 419 } 420 421 siA, err := snapshotter.Stat(ctx, snapA) 422 if err != nil { 423 t.Fatal(err) 424 } 425 426 siB, err := snapshotter.Stat(ctx, snapB) 427 if err != nil { 428 t.Fatal(err) 429 } 430 431 siParentB, err := snapshotter.Stat(ctx, siB.Parent) 432 if err != nil { 433 t.Fatal(err) 434 } 435 436 // Test the transivity 437 assert.Equal(t, "", siA.Parent) 438 assert.Equal(t, snapA, siB.Parent) 439 assert.Equal(t, "", siParentB.Parent) 440 441 } 442 443 // Creating two layers with Prepare or View with same key must fail. 444 func checkSnapshotterPrepareView(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 445 preparing, err := snapshotterPrepareMount(ctx, snapshotter, "preparing", "", work) 446 if err != nil { 447 t.Fatal(err) 448 } 449 testutil.Unmount(t, preparing) 450 451 snapA := filepath.Join(work, "snapA") 452 if err = snapshotter.Commit(ctx, snapA, preparing, opt); err != nil { 453 t.Fatal(err) 454 } 455 456 // Prepare & View with same key 457 newLayer := filepath.Join(work, "newlayer") 458 if err = os.MkdirAll(preparing, 0777); err != nil { 459 t.Fatal(err) 460 } 461 462 // Prepare & View with same key 463 _, err = snapshotter.Prepare(ctx, newLayer, snapA, opt) 464 if err != nil { 465 t.Fatal(err) 466 } 467 468 _, err = snapshotter.View(ctx, newLayer, snapA, opt) 469 assert.True(t, err != nil) 470 471 // Two Prepare with same key 472 prepLayer := filepath.Join(work, "prepLayer") 473 if err = os.MkdirAll(preparing, 0777); err != nil { 474 t.Fatal(err) 475 } 476 477 _, err = snapshotter.Prepare(ctx, prepLayer, snapA, opt) 478 if err != nil { 479 t.Fatal(err) 480 } 481 482 _, err = snapshotter.Prepare(ctx, prepLayer, snapA, opt) 483 assert.True(t, err != nil) 484 485 // Two View with same key 486 viewLayer := filepath.Join(work, "viewLayer") 487 if err = os.MkdirAll(preparing, 0777); err != nil { 488 t.Fatal(err) 489 } 490 491 _, err = snapshotter.View(ctx, viewLayer, snapA, opt) 492 if err != nil { 493 t.Fatal(err) 494 } 495 496 _, err = snapshotter.View(ctx, viewLayer, snapA, opt) 497 assert.True(t, err != nil) 498 499 } 500 501 // Deletion of files/folder of base layer in new layer, On Commit, those files should not be visible. 502 func checkDeletedFilesInChildSnapshot(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 503 504 l1Init := fstest.Apply( 505 fstest.CreateFile("/foo", []byte("foo\n"), 0777), 506 fstest.CreateFile("/foobar", []byte("foobar\n"), 0777), 507 ) 508 l2Init := fstest.Apply( 509 fstest.RemoveAll("/foobar"), 510 ) 511 l3Init := fstest.Apply() 512 513 if err := checkSnapshots(ctx, snapshotter, work, l1Init, l2Init, l3Init); err != nil { 514 t.Fatalf("Check snapshots failed: %+v", err) 515 } 516 517 } 518 519 // Create three layers. Deleting intermediate layer must fail. 520 func checkRemoveIntermediateSnapshot(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 521 522 base, err := snapshotterPrepareMount(ctx, snapshotter, "base", "", work) 523 if err != nil { 524 t.Fatal(err) 525 } 526 testutil.Unmount(t, base) 527 528 committedBase := filepath.Join(work, "committed-base") 529 if err = snapshotter.Commit(ctx, committedBase, base, opt); err != nil { 530 t.Fatal(err) 531 } 532 533 // Create intermediate layer 534 intermediate := filepath.Join(work, "intermediate") 535 if _, err = snapshotter.Prepare(ctx, intermediate, committedBase, opt); err != nil { 536 t.Fatal(err) 537 } 538 539 committedInter := filepath.Join(work, "committed-inter") 540 if err = snapshotter.Commit(ctx, committedInter, intermediate, opt); err != nil { 541 t.Fatal(err) 542 } 543 544 // Create top layer 545 topLayer := filepath.Join(work, "toplayer") 546 if _, err = snapshotter.Prepare(ctx, topLayer, committedInter, opt); err != nil { 547 t.Fatal(err) 548 } 549 550 // Deletion of intermediate layer must fail. 551 err = snapshotter.Remove(ctx, committedInter) 552 if err == nil { 553 t.Fatal("intermediate layer removal should fail.") 554 } 555 556 //Removal from toplayer to base should not fail. 557 err = snapshotter.Remove(ctx, topLayer) 558 if err != nil { 559 t.Fatal(err) 560 } 561 err = snapshotter.Remove(ctx, committedInter) 562 if err != nil { 563 t.Fatal(err) 564 } 565 err = snapshotter.Remove(ctx, committedBase) 566 if err != nil { 567 t.Fatal(err) 568 } 569 } 570 571 // baseTestSnapshots creates a base set of snapshots for tests, each snapshot is empty 572 // Tests snapshots: 573 // 574 // c1 - committed snapshot, no parent 575 // c2 - committed snapshot, c1 is parent 576 // a1 - active snapshot, c2 is parent 577 // a1 - active snapshot, no parent 578 // v1 - view snapshot, v1 is parent 579 // v2 - view snapshot, no parent 580 func baseTestSnapshots(ctx context.Context, snapshotter snapshots.Snapshotter) error { 581 if _, err := snapshotter.Prepare(ctx, "c1-a", "", opt); err != nil { 582 return err 583 } 584 if err := snapshotter.Commit(ctx, "c1", "c1-a", opt); err != nil { 585 return err 586 } 587 if _, err := snapshotter.Prepare(ctx, "c2-a", "c1", opt); err != nil { 588 return err 589 } 590 if err := snapshotter.Commit(ctx, "c2", "c2-a", opt); err != nil { 591 return err 592 } 593 if _, err := snapshotter.Prepare(ctx, "a1", "c2", opt); err != nil { 594 return err 595 } 596 if _, err := snapshotter.Prepare(ctx, "a2", "", opt); err != nil { 597 return err 598 } 599 if _, err := snapshotter.View(ctx, "v1", "c2", opt); err != nil { 600 return err 601 } 602 if _, err := snapshotter.View(ctx, "v2", "", opt); err != nil { 603 return err 604 } 605 return nil 606 } 607 608 func checkUpdate(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 609 t1 := time.Now().UTC() 610 if err := baseTestSnapshots(ctx, snapshotter); err != nil { 611 t.Fatalf("Failed to create base snapshots: %v", err) 612 } 613 t2 := time.Now().UTC() 614 testcases := []struct { 615 name string 616 kind snapshots.Kind 617 parent string 618 }{ 619 { 620 name: "c1", 621 kind: snapshots.KindCommitted, 622 }, 623 { 624 name: "c2", 625 kind: snapshots.KindCommitted, 626 parent: "c1", 627 }, 628 { 629 name: "a1", 630 kind: snapshots.KindActive, 631 parent: "c2", 632 }, 633 { 634 name: "a2", 635 kind: snapshots.KindActive, 636 }, 637 { 638 name: "v1", 639 kind: snapshots.KindView, 640 parent: "c2", 641 }, 642 { 643 name: "v2", 644 kind: snapshots.KindView, 645 }, 646 } 647 for _, tc := range testcases { 648 st, err := snapshotter.Stat(ctx, tc.name) 649 if err != nil { 650 t.Fatalf("Failed to stat %s: %v", tc.name, err) 651 } 652 if st.Created.Before(t1) || st.Created.After(t2) { 653 t.Errorf("(%s) wrong created time %s: expected between %s and %s", tc.name, st.Created, t1, t2) 654 continue 655 } 656 if st.Created != st.Updated { 657 t.Errorf("(%s) unexpected updated time %s: expected %s", tc.name, st.Updated, st.Created) 658 continue 659 } 660 if st.Kind != tc.kind { 661 t.Errorf("(%s) unexpected kind %s, expected %s", tc.name, st.Kind, tc.kind) 662 continue 663 } 664 if st.Parent != tc.parent { 665 t.Errorf("(%s) unexpected parent %q, expected %q", tc.name, st.Parent, tc.parent) 666 continue 667 } 668 if st.Name != tc.name { 669 t.Errorf("(%s) unexpected name %q, expected %q", tc.name, st.Name, tc.name) 670 continue 671 } 672 673 createdAt := st.Created 674 rootTime := time.Now().UTC().Format(time.RFC3339) 675 expected := map[string]string{ 676 "l1": "v1", 677 "l2": "v2", 678 "l3": "v3", 679 // Keep root label 680 "containerd.io/gc.root": rootTime, 681 } 682 st.Parent = "doesnotexist" 683 st.Labels = expected 684 u1 := time.Now().UTC() 685 st, err = snapshotter.Update(ctx, st) 686 if err != nil { 687 t.Fatalf("Failed to update %s: %v", tc.name, err) 688 } 689 u2 := time.Now().UTC() 690 691 if st.Created != createdAt { 692 t.Errorf("(%s) wrong created time %s: expected %s", tc.name, st.Created, createdAt) 693 continue 694 } 695 if st.Updated.Before(u1) || st.Updated.After(u2) { 696 t.Errorf("(%s) wrong updated time %s: expected between %s and %s", tc.name, st.Updated, u1, u2) 697 continue 698 } 699 if st.Kind != tc.kind { 700 t.Errorf("(%s) unexpected kind %s, expected %s", tc.name, st.Kind, tc.kind) 701 continue 702 } 703 if st.Parent != tc.parent { 704 t.Errorf("(%s) unexpected parent %q, expected %q", tc.name, st.Parent, tc.parent) 705 continue 706 } 707 if st.Name != tc.name { 708 t.Errorf("(%s) unexpected name %q, expected %q", tc.name, st.Name, tc.name) 709 continue 710 } 711 assertLabels(t, st.Labels, expected) 712 713 expected = map[string]string{ 714 "l1": "updated", 715 "l3": "v3", 716 717 "containerd.io/gc.root": rootTime, 718 } 719 st.Labels = map[string]string{ 720 "l1": "updated", 721 "l4": "v4", 722 } 723 st, err = snapshotter.Update(ctx, st, "labels.l1", "labels.l2") 724 if err != nil { 725 t.Fatalf("Failed to update %s: %v", tc.name, err) 726 } 727 assertLabels(t, st.Labels, expected) 728 729 expected = map[string]string{ 730 "l4": "v4", 731 732 "containerd.io/gc.root": rootTime, 733 } 734 st.Labels = expected 735 st, err = snapshotter.Update(ctx, st, "labels") 736 if err != nil { 737 t.Fatalf("Failed to update %s: %v", tc.name, err) 738 } 739 assertLabels(t, st.Labels, expected) 740 741 // Test failure received when providing immutable field path 742 st.Parent = "doesnotexist" 743 st, err = snapshotter.Update(ctx, st, "parent") 744 if err == nil { 745 t.Errorf("Expected error updating with immutable field path") 746 } else if !errdefs.IsInvalidArgument(err) { 747 t.Fatalf("Unexpected error updating %s: %+v", tc.name, err) 748 } 749 } 750 } 751 752 func assertLabels(t *testing.T, actual, expected map[string]string) { 753 if len(actual) != len(expected) { 754 t.Fatalf("Label size mismatch: %d vs %d\n\tActual: %#v\n\tExpected: %#v", len(actual), len(expected), actual, expected) 755 } 756 for k, v := range expected { 757 if a := actual[k]; v != a { 758 t.Errorf("Wrong label value for %s, got %q, expected %q", k, a, v) 759 } 760 } 761 if t.Failed() { 762 t.FailNow() 763 } 764 } 765 766 func checkRemove(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 767 if _, err := snapshotter.Prepare(ctx, "committed-a", "", opt); err != nil { 768 t.Fatal(err) 769 } 770 if err := snapshotter.Commit(ctx, "committed-1", "committed-a", opt); err != nil { 771 t.Fatal(err) 772 } 773 if _, err := snapshotter.Prepare(ctx, "reuse-1", "committed-1", opt); err != nil { 774 t.Fatal(err) 775 } 776 if err := snapshotter.Remove(ctx, "reuse-1"); err != nil { 777 t.Fatal(err) 778 } 779 if _, err := snapshotter.View(ctx, "reuse-1", "committed-1", opt); err != nil { 780 t.Fatal(err) 781 } 782 if err := snapshotter.Remove(ctx, "reuse-1"); err != nil { 783 t.Fatal(err) 784 } 785 if _, err := snapshotter.Prepare(ctx, "reuse-1", "", opt); err != nil { 786 t.Fatal(err) 787 } 788 if err := snapshotter.Remove(ctx, "committed-1"); err != nil { 789 t.Fatal(err) 790 } 791 if err := snapshotter.Commit(ctx, "committed-1", "reuse-1", opt); err != nil { 792 t.Fatal(err) 793 } 794 } 795 796 // checkSnapshotterViewReadonly ensures a KindView snapshot to be mounted as a read-only filesystem. 797 // This function is called only when WithTestViewReadonly is true. 798 func checkSnapshotterViewReadonly(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 799 preparing := filepath.Join(work, "preparing") 800 if _, err := snapshotter.Prepare(ctx, preparing, "", opt); err != nil { 801 t.Fatal(err) 802 } 803 committed := filepath.Join(work, "committed") 804 if err := snapshotter.Commit(ctx, committed, preparing, opt); err != nil { 805 t.Fatal(err) 806 } 807 view := filepath.Join(work, "view") 808 m, err := snapshotter.View(ctx, view, committed, opt) 809 if err != nil { 810 t.Fatal(err) 811 } 812 viewMountPoint := filepath.Join(work, "view-mount") 813 if err := os.MkdirAll(viewMountPoint, 0777); err != nil { 814 t.Fatal(err) 815 } 816 817 // Just checking the option string of m is not enough, we need to test real mount. (#1368) 818 if err := mount.All(m, viewMountPoint); err != nil { 819 t.Fatal(err) 820 } 821 822 testfile := filepath.Join(viewMountPoint, "testfile") 823 err = os.WriteFile(testfile, []byte("testcontent"), 0777) 824 testutil.Unmount(t, viewMountPoint) 825 if err != nil { 826 t.Logf("write to %q failed with %v (EROFS is expected but can be other error code)", testfile, err) 827 } else { 828 t.Fatalf("write to %q should fail (EROFS) but did not fail", testfile) 829 } 830 assert.Nil(t, snapshotter.Remove(ctx, view)) 831 assert.Nil(t, snapshotter.Remove(ctx, committed)) 832 } 833 834 // Move files from base layer to new location in intermediate layer. 835 // Verify if the file at source is deleted and copied to new location. 836 func checkFileFromLowerLayer(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 837 l1Init := fstest.Apply( 838 fstest.CreateDir("/dir1", 0700), 839 fstest.CreateFile("/dir1/f1", []byte("Hello"), 0644), 840 fstest.CreateDir("dir2", 0700), 841 fstest.CreateFile("dir2/f2", []byte("..."), 0644), 842 ) 843 l2Init := fstest.Apply( 844 fstest.CreateDir("/dir3", 0700), 845 fstest.CreateFile("/dir3/f1", []byte("Hello"), 0644), 846 fstest.RemoveAll("/dir1"), 847 fstest.Link("dir2/f2", "dir3/f2"), 848 fstest.RemoveAll("dir2/f2"), 849 ) 850 851 if err := checkSnapshots(ctx, snapshotter, work, l1Init, l2Init); err != nil { 852 t.Fatalf("Check snapshots failed: %+v", err) 853 } 854 } 855 856 func closeTwice(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 857 n := fmt.Sprintf("closeTwice-%d", randutil.Int()) 858 prepare := fmt.Sprintf("%s-prepare", n) 859 860 // do some dummy ops to modify the snapshotter internal state 861 if _, err := snapshotter.Prepare(ctx, prepare, "", opt); err != nil { 862 t.Fatal(err) 863 } 864 if err := snapshotter.Commit(ctx, n, prepare, opt); err != nil { 865 t.Fatal(err) 866 } 867 if err := snapshotter.Remove(ctx, n); err != nil { 868 t.Fatal(err) 869 } 870 if err := snapshotter.Close(); err != nil { 871 t.Fatalf("The first close failed: %+v", err) 872 } 873 if err := snapshotter.Close(); err != nil { 874 t.Fatalf("The second close failed: %+v", err) 875 } 876 } 877 878 func checkRootPermission(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 879 if runtime.GOOS == "windows" { 880 t.Skip("Filesystem permissions are not supported on Windows") 881 } 882 883 preparing, err := snapshotterPrepareMount(ctx, snapshotter, "preparing", "", work) 884 if err != nil { 885 t.Fatal(err) 886 } 887 defer testutil.Unmount(t, preparing) 888 st, err := os.Stat(preparing) 889 if err != nil { 890 t.Fatal(err) 891 } 892 if mode := st.Mode() & 0777; mode != 0755 { 893 t.Fatalf("expected 0755, got 0%o", mode) 894 } 895 } 896 897 func check128LayersMount(name string) func(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 898 return func(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 899 lowestApply := fstest.Apply( 900 fstest.CreateFile("/bottom", []byte("way at the bottom\n"), 0777), 901 fstest.CreateFile("/overwriteme", []byte("FIRST!\n"), 0777), 902 fstest.CreateDir("/addhere", 0755), 903 fstest.CreateDir("/onlyme", 0755), 904 fstest.CreateFile("/onlyme/bottom", []byte("bye!\n"), 0777), 905 ) 906 907 appliers := []fstest.Applier{lowestApply} 908 for i := 1; i <= 127; i++ { 909 appliers = append(appliers, fstest.Apply( 910 fstest.CreateFile("/overwriteme", []byte(fmt.Sprintf("%d WAS HERE!\n", i)), 0777), 911 fstest.CreateFile(fmt.Sprintf("/addhere/file-%d", i), []byte("same\n"), 0755), 912 fstest.RemoveAll("/onlyme"), 913 fstest.CreateDir("/onlyme", 0755), 914 fstest.CreateFile(fmt.Sprintf("/onlyme/file-%d", i), []byte("only me!\n"), 0777), 915 )) 916 } 917 918 flat := filepath.Join(work, "flat") 919 if err := os.MkdirAll(flat, 0777); err != nil { 920 t.Fatalf("failed to create flat dir(%s): %+v", flat, err) 921 } 922 923 // NOTE: add gc labels to avoid snapshots get removed by gc... 924 parent := "" 925 for i, applier := range appliers { 926 preparing := filepath.Join(work, fmt.Sprintf("prepare-layer-%d", i)) 927 if err := os.MkdirAll(preparing, 0777); err != nil { 928 t.Fatalf("[layer %d] failed to create preparing dir(%s): %+v", i, preparing, err) 929 } 930 931 mounts, err := snapshotter.Prepare(ctx, preparing, parent, opt) 932 if err != nil { 933 t.Fatalf("[layer %d] failed to get mount info: %+v", i, err) 934 } 935 936 if err := mount.All(mounts, preparing); err != nil { 937 t.Fatalf("[layer %d] failed to mount on the target(%s): %+v", i, preparing, err) 938 } 939 940 t.Log("mount", preparing) 941 942 if err := fstest.CheckDirectoryEqual(flat, preparing); err != nil { 943 testutil.Unmount(t, preparing) 944 t.Fatalf("[layer %d] preparing doesn't equal to flat before apply: %+v", i, err) 945 } 946 947 if err := applier.Apply(flat); err != nil { 948 testutil.Unmount(t, preparing) 949 t.Fatalf("[layer %d] failed to apply on flat dir: %+v", i, err) 950 } 951 952 if err = applier.Apply(preparing); err != nil { 953 testutil.Unmount(t, preparing) 954 t.Fatalf("[layer %d] failed to apply on preparing dir: %+v", i, err) 955 } 956 957 if err := fstest.CheckDirectoryEqual(flat, preparing); err != nil { 958 testutil.Unmount(t, preparing) 959 t.Fatalf("[layer %d] preparing doesn't equal to flat after apply: %+v", i, err) 960 } 961 962 testutil.Unmount(t, preparing) 963 964 parent = filepath.Join(work, fmt.Sprintf("committed-%d", i)) 965 if err := snapshotter.Commit(ctx, parent, preparing, opt); err != nil { 966 t.Fatalf("[layer %d] failed to commit the preparing: %+v", i, err) 967 } 968 969 } 970 971 view := filepath.Join(work, "fullview") 972 if err := os.MkdirAll(view, 0777); err != nil { 973 t.Fatalf("failed to create fullview dir(%s): %+v", view, err) 974 } 975 976 mounts, err := snapshotter.View(ctx, view, parent, opt) 977 if err != nil { 978 t.Fatalf("failed to get view's mount info: %+v", err) 979 } 980 981 if err := mount.All(mounts, view); err != nil { 982 t.Fatalf("failed to mount on the target(%s): %+v", view, err) 983 } 984 defer testutil.Unmount(t, view) 985 986 if err := fstest.CheckDirectoryEqual(flat, view); err != nil { 987 t.Fatalf("fullview should equal to flat: %+v", err) 988 } 989 } 990 } 991 992 func checkWalk(ctx context.Context, t *testing.T, snapshotter snapshots.Snapshotter, work string) { 993 opt := snapshots.WithLabels(map[string]string{ 994 "containerd.io/gc.root": "check-walk", 995 }) 996 997 // No parent active 998 if _, err := snapshotter.Prepare(ctx, "a-np", "", opt); err != nil { 999 t.Fatal(err) 1000 } 1001 1002 // Base parent 1003 if _, err := snapshotter.Prepare(ctx, "p-tmp", "", opt); err != nil { 1004 t.Fatal(err) 1005 } 1006 if err := snapshotter.Commit(ctx, "p", "p-tmp", opt); err != nil { 1007 t.Fatal(err) 1008 } 1009 1010 // Active 1011 if _, err := snapshotter.Prepare(ctx, "a", "p", opt); err != nil { 1012 t.Fatal(err) 1013 } 1014 1015 // View 1016 if _, err := snapshotter.View(ctx, "v", "p", opt); err != nil { 1017 t.Fatal(err) 1018 } 1019 1020 // Base parent with label=1 1021 if _, err := snapshotter.Prepare(ctx, "p-wl-tmp", "", opt); err != nil { 1022 t.Fatal(err) 1023 } 1024 if err := snapshotter.Commit(ctx, "p-wl", "p-wl-tmp", snapshots.WithLabels(map[string]string{ 1025 "l": "1", 1026 "containerd.io/gc.root": "check-walk", 1027 })); err != nil { 1028 t.Fatal(err) 1029 } 1030 1031 // active with label=2 1032 if _, err := snapshotter.Prepare(ctx, "a-wl", "p-wl", snapshots.WithLabels(map[string]string{ 1033 "l": "2", 1034 "containerd.io/gc.root": "check-walk", 1035 })); err != nil { 1036 t.Fatal(err) 1037 } 1038 1039 // view with label=3 1040 if _, err := snapshotter.View(ctx, "v-wl", "p-wl", snapshots.WithLabels(map[string]string{ 1041 "l": "3", 1042 "containerd.io/gc.root": "check-walk", 1043 })); err != nil { 1044 t.Fatal(err) 1045 } 1046 1047 // no parent active with label=2 1048 if _, err := snapshotter.Prepare(ctx, "a-np-wl", "", snapshots.WithLabels(map[string]string{ 1049 "l": "2", 1050 "containerd.io/gc.root": "check-walk", 1051 })); err != nil { 1052 t.Fatal(err) 1053 } 1054 1055 for i, tc := range []struct { 1056 matches []string 1057 filters []string 1058 }{ 1059 { 1060 matches: []string{"a-np", "p", "a", "v", "p-wl", "a-wl", "v-wl", "a-np-wl"}, 1061 filters: []string{"labels.\"containerd.io/gc.root\"==check-walk"}, 1062 }, 1063 { 1064 matches: []string{"a-np", "a", "a-wl", "a-np-wl"}, 1065 filters: []string{"kind==active,labels.\"containerd.io/gc.root\"==check-walk"}, 1066 }, 1067 { 1068 matches: []string{"v", "v-wl"}, 1069 filters: []string{"kind==view,labels.\"containerd.io/gc.root\"==check-walk"}, 1070 }, 1071 { 1072 matches: []string{"p", "p-wl"}, 1073 filters: []string{"kind==committed,labels.\"containerd.io/gc.root\"==check-walk"}, 1074 }, 1075 { 1076 matches: []string{"p", "a-np-wl"}, 1077 filters: []string{"name==p", "name==a-np-wl"}, 1078 }, 1079 { 1080 matches: []string{"a-wl"}, 1081 filters: []string{"name==a-wl,labels.l"}, 1082 }, 1083 { 1084 matches: []string{"a", "v"}, 1085 filters: []string{"parent==p"}, 1086 }, 1087 { 1088 matches: []string{"a", "v", "a-wl", "v-wl"}, 1089 filters: []string{"parent!=\"\",labels.\"containerd.io/gc.root\"==check-walk"}, 1090 }, 1091 { 1092 matches: []string{"p-wl", "a-wl", "v-wl", "a-np-wl"}, 1093 filters: []string{"labels.l"}, 1094 }, 1095 { 1096 matches: []string{"a-wl", "a-np-wl"}, 1097 filters: []string{"labels.l==2"}, 1098 }, 1099 } { 1100 actual := []string{} 1101 err := snapshotter.Walk(ctx, func(ctx context.Context, si snapshots.Info) error { 1102 actual = append(actual, si.Name) 1103 return nil 1104 }, tc.filters...) 1105 if err != nil { 1106 t.Fatal(err) 1107 } 1108 1109 sort.Strings(tc.matches) 1110 sort.Strings(actual) 1111 if len(actual) != len(tc.matches) { 1112 t.Errorf("[%d] Unexpected result (size):\nActual:\n\t%#v\nExpected:\n\t%#v", i, actual, tc.matches) 1113 continue 1114 } 1115 for j := range actual { 1116 if actual[j] != tc.matches[j] { 1117 t.Errorf("[%d] Unexpected result @%d:\nActual:\n\t%#vExpected:\n\t%#v", i, j, actual, tc.matches) 1118 break 1119 } 1120 } 1121 } 1122 }