github.com/demonoid81/containerd@v1.3.4/metadata/db_test.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 metadata 18 19 import ( 20 "context" 21 "encoding/binary" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "math/rand" 26 "os" 27 "path/filepath" 28 "runtime/pprof" 29 "strings" 30 "testing" 31 "time" 32 33 "github.com/containerd/containerd/containers" 34 "github.com/containerd/containerd/content" 35 "github.com/containerd/containerd/content/local" 36 "github.com/containerd/containerd/errdefs" 37 "github.com/containerd/containerd/gc" 38 "github.com/containerd/containerd/images" 39 "github.com/containerd/containerd/leases" 40 "github.com/containerd/containerd/log/logtest" 41 "github.com/containerd/containerd/namespaces" 42 "github.com/containerd/containerd/snapshots" 43 "github.com/containerd/containerd/snapshots/native" 44 "github.com/gogo/protobuf/types" 45 digest "github.com/opencontainers/go-digest" 46 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 47 "github.com/pkg/errors" 48 bolt "go.etcd.io/bbolt" 49 ) 50 51 func testDB(t *testing.T) (context.Context, *DB, func()) { 52 ctx, cancel := context.WithCancel(context.Background()) 53 ctx = namespaces.WithNamespace(ctx, "testing") 54 55 dirname, err := ioutil.TempDir("", strings.Replace(t.Name(), "/", "_", -1)+"-") 56 if err != nil { 57 t.Fatal(err) 58 } 59 60 snapshotter, err := native.NewSnapshotter(filepath.Join(dirname, "native")) 61 if err != nil { 62 t.Fatal(err) 63 } 64 65 cs, err := local.NewStore(filepath.Join(dirname, "content")) 66 if err != nil { 67 t.Fatal(err) 68 } 69 70 bdb, err := bolt.Open(filepath.Join(dirname, "metadata.db"), 0644, nil) 71 if err != nil { 72 t.Fatal(err) 73 } 74 75 db := NewDB(bdb, cs, map[string]snapshots.Snapshotter{"native": snapshotter}) 76 if err := db.Init(ctx); err != nil { 77 t.Fatal(err) 78 } 79 80 return ctx, db, func() { 81 bdb.Close() 82 if err := os.RemoveAll(dirname); err != nil { 83 t.Log("failed removing temp dir", err) 84 } 85 cancel() 86 } 87 } 88 89 func TestInit(t *testing.T) { 90 ctx, db, cancel := testEnv(t) 91 defer cancel() 92 93 if err := NewDB(db, nil, nil).Init(ctx); err != nil { 94 t.Fatal(err) 95 } 96 97 version, err := readDBVersion(db, bucketKeyVersion) 98 if err != nil { 99 t.Fatal(err) 100 } 101 if version != dbVersion { 102 t.Fatalf("Unexpected version %d, expected %d", version, dbVersion) 103 } 104 } 105 106 func TestMigrations(t *testing.T) { 107 testRefs := []struct { 108 ref string 109 bref string 110 }{ 111 { 112 ref: "k1", 113 bref: "bk1", 114 }, 115 { 116 ref: strings.Repeat("longerkey", 30), // 270 characters 117 bref: "short", 118 }, 119 { 120 ref: "short", 121 bref: strings.Repeat("longerkey", 30), // 270 characters 122 }, 123 { 124 ref: "emptykey", 125 bref: "", 126 }, 127 } 128 migrationTests := []struct { 129 name string 130 init func(*bolt.Tx) error 131 check func(*bolt.Tx) error 132 }{ 133 { 134 name: "ChildrenKey", 135 init: func(tx *bolt.Tx) error { 136 bkt, err := createSnapshotterBucket(tx, "testing", "testing") 137 if err != nil { 138 return err 139 } 140 141 snapshots := []struct { 142 key string 143 parent string 144 }{ 145 { 146 key: "k1", 147 parent: "", 148 }, 149 { 150 key: "k2", 151 parent: "k1", 152 }, 153 { 154 key: "k2a", 155 parent: "k1", 156 }, 157 { 158 key: "a1", 159 parent: "k2", 160 }, 161 } 162 163 for _, s := range snapshots { 164 sbkt, err := bkt.CreateBucket([]byte(s.key)) 165 if err != nil { 166 return err 167 } 168 if err := sbkt.Put(bucketKeyParent, []byte(s.parent)); err != nil { 169 return err 170 } 171 } 172 173 return nil 174 }, 175 check: func(tx *bolt.Tx) error { 176 bkt := getSnapshotterBucket(tx, "testing", "testing") 177 if bkt == nil { 178 return errors.Wrap(errdefs.ErrNotFound, "snapshots bucket not found") 179 } 180 snapshots := []struct { 181 key string 182 children []string 183 }{ 184 { 185 key: "k1", 186 children: []string{"k2", "k2a"}, 187 }, 188 { 189 key: "k2", 190 children: []string{"a1"}, 191 }, 192 { 193 key: "k2a", 194 children: []string{}, 195 }, 196 { 197 key: "a1", 198 children: []string{}, 199 }, 200 } 201 202 for _, s := range snapshots { 203 sbkt := bkt.Bucket([]byte(s.key)) 204 if sbkt == nil { 205 return errors.Wrap(errdefs.ErrNotFound, "key does not exist") 206 } 207 208 cbkt := sbkt.Bucket(bucketKeyChildren) 209 var cn int 210 if cbkt != nil { 211 cn = cbkt.Stats().KeyN 212 } 213 214 if cn != len(s.children) { 215 return errors.Errorf("unexpected number of children %d, expected %d", cn, len(s.children)) 216 } 217 218 for _, ch := range s.children { 219 if v := cbkt.Get([]byte(ch)); v == nil { 220 return errors.Errorf("missing child record for %s", ch) 221 } 222 } 223 } 224 225 return nil 226 }, 227 }, 228 { 229 name: "IngestUpdate", 230 init: func(tx *bolt.Tx) error { 231 bkt, err := createBucketIfNotExists(tx, bucketKeyVersion, []byte("testing"), bucketKeyObjectContent, deprecatedBucketKeyObjectIngest) 232 if err != nil { 233 return err 234 } 235 236 for _, s := range testRefs { 237 if err := bkt.Put([]byte(s.ref), []byte(s.bref)); err != nil { 238 return err 239 } 240 } 241 242 return nil 243 }, 244 check: func(tx *bolt.Tx) error { 245 bkt := getIngestsBucket(tx, "testing") 246 if bkt == nil { 247 return errors.Wrap(errdefs.ErrNotFound, "ingests bucket not found") 248 } 249 250 for _, s := range testRefs { 251 sbkt := bkt.Bucket([]byte(s.ref)) 252 if sbkt == nil { 253 return errors.Wrap(errdefs.ErrNotFound, "ref does not exist") 254 } 255 256 bref := string(sbkt.Get(bucketKeyRef)) 257 if bref != s.bref { 258 return errors.Errorf("unexpected reference key %q, expected %q", bref, s.bref) 259 } 260 } 261 262 dbkt := getBucket(tx, bucketKeyVersion, []byte("testing"), bucketKeyObjectContent, deprecatedBucketKeyObjectIngest) 263 if dbkt != nil { 264 return errors.New("deprecated ingest bucket still exists") 265 } 266 267 return nil 268 }, 269 }, 270 271 { 272 name: "NoOp", 273 init: func(tx *bolt.Tx) error { 274 return nil 275 }, 276 check: func(tx *bolt.Tx) error { 277 return nil 278 }, 279 }, 280 } 281 282 if len(migrationTests) != len(migrations) { 283 t.Fatal("Each migration must have a test case") 284 } 285 286 for i, mt := range migrationTests { 287 t.Run(mt.name, runMigrationTest(i, mt.init, mt.check)) 288 } 289 } 290 291 func runMigrationTest(i int, init, check func(*bolt.Tx) error) func(t *testing.T) { 292 return func(t *testing.T) { 293 _, db, cancel := testEnv(t) 294 defer cancel() 295 296 if err := db.Update(init); err != nil { 297 t.Fatal(err) 298 } 299 300 if err := db.Update(migrations[i].migrate); err != nil { 301 t.Fatal(err) 302 } 303 304 if err := db.View(check); err != nil { 305 t.Fatal(err) 306 } 307 } 308 } 309 310 func readDBVersion(db *bolt.DB, schema []byte) (int, error) { 311 var version int 312 if err := db.View(func(tx *bolt.Tx) error { 313 bkt := tx.Bucket(schema) 314 if bkt == nil { 315 return errors.Wrap(errdefs.ErrNotFound, "no version bucket") 316 } 317 vb := bkt.Get(bucketKeyDBVersion) 318 if vb == nil { 319 return errors.Wrap(errdefs.ErrNotFound, "no version value") 320 } 321 v, _ := binary.Varint(vb) 322 version = int(v) 323 return nil 324 }); err != nil { 325 return 0, err 326 } 327 return version, nil 328 } 329 330 func TestMetadataCollector(t *testing.T) { 331 mdb, cs, sn, cleanup := newStores(t) 332 defer cleanup() 333 334 var ( 335 ctx = logtest.WithT(context.Background(), t) 336 337 objects = []object{ 338 blob(bytesFor(1), true), 339 blob(bytesFor(2), false), 340 blob(bytesFor(3), true), 341 blob(bytesFor(4), false, "containerd.io/gc.root", time.Now().String()), 342 newSnapshot("1", "", false, false), 343 newSnapshot("2", "1", false, false), 344 newSnapshot("3", "2", false, false), 345 newSnapshot("4", "3", false, false), 346 newSnapshot("5", "3", false, true), 347 container("1", "4"), 348 image("image-1", digestFor(2)), 349 350 // Test lease preservation 351 blob(bytesFor(5), false, "containerd.io/gc.ref.content.0", digestFor(6).String()), 352 blob(bytesFor(6), false), 353 blob(bytesFor(7), false), 354 newSnapshot("6", "", false, false, "containerd.io/gc.ref.content.0", digestFor(7).String()), 355 lease("lease-1", []leases.Resource{ 356 { 357 ID: digestFor(5).String(), 358 Type: "content", 359 }, 360 { 361 ID: "6", 362 Type: "snapshots/native", 363 }, 364 }, false), 365 366 // Test flat lease 367 blob(bytesFor(8), false, "containerd.io/gc.ref.content.0", digestFor(9).String()), 368 blob(bytesFor(9), true), 369 blob(bytesFor(10), true), 370 newSnapshot("7", "", false, false, "containerd.io/gc.ref.content.0", digestFor(10).String()), 371 newSnapshot("8", "7", false, false), 372 newSnapshot("9", "8", false, false), 373 lease("lease-2", []leases.Resource{ 374 { 375 ID: digestFor(8).String(), 376 Type: "content", 377 }, 378 { 379 ID: "9", 380 Type: "snapshots/native", 381 }, 382 }, false, "containerd.io/gc.flat", time.Now().String()), 383 } 384 remaining []gc.Node 385 ) 386 387 if err := mdb.Update(func(tx *bolt.Tx) error { 388 for _, obj := range objects { 389 node, err := create(obj, tx, mdb, cs, sn) 390 if err != nil { 391 return err 392 } 393 if node != nil { 394 remaining = append(remaining, *node) 395 } 396 } 397 return nil 398 }); err != nil { 399 t.Fatalf("Creation failed: %+v", err) 400 } 401 402 if _, err := mdb.GarbageCollect(ctx); err != nil { 403 t.Fatal(err) 404 } 405 406 var actual []gc.Node 407 408 if err := mdb.View(func(tx *bolt.Tx) error { 409 scanFn := func(ctx context.Context, node gc.Node) error { 410 actual = append(actual, node) 411 return nil 412 } 413 return scanAll(ctx, tx, scanFn) 414 }); err != nil { 415 t.Fatal(err) 416 } 417 418 checkNodesEqual(t, actual, remaining) 419 } 420 421 func BenchmarkGarbageCollect(b *testing.B) { 422 b.Run("10-Sets", benchmarkTrigger(10)) 423 b.Run("100-Sets", benchmarkTrigger(100)) 424 b.Run("1000-Sets", benchmarkTrigger(1000)) 425 b.Run("10000-Sets", benchmarkTrigger(10000)) 426 } 427 428 func benchmarkTrigger(n int) func(b *testing.B) { 429 return func(b *testing.B) { 430 mdb, cs, sn, cleanup := newStores(b) 431 defer cleanup() 432 433 objects := []object{} 434 435 // TODO: Allow max to be configurable 436 for i := 0; i < n; i++ { 437 objects = append(objects, 438 blob(bytesFor(int64(i)), false), 439 image(fmt.Sprintf("image-%d", i), digestFor(int64(i))), 440 ) 441 lastSnapshot := 6 442 for j := 0; j <= lastSnapshot; j++ { 443 var parent string 444 key := fmt.Sprintf("snapshot-%d-%d", i, j) 445 if j > 0 { 446 parent = fmt.Sprintf("snapshot-%d-%d", i, j-1) 447 } 448 objects = append(objects, newSnapshot(key, parent, false, false)) 449 } 450 objects = append(objects, container(fmt.Sprintf("container-%d", i), fmt.Sprintf("snapshot-%d-%d", i, lastSnapshot))) 451 452 } 453 454 // TODO: Create set of objects for removal 455 456 var ( 457 ctx = context.Background() 458 459 remaining []gc.Node 460 ) 461 462 if err := mdb.Update(func(tx *bolt.Tx) error { 463 for _, obj := range objects { 464 node, err := create(obj, tx, mdb, cs, sn) 465 if err != nil { 466 return err 467 } 468 if node != nil { 469 remaining = append(remaining, *node) 470 } 471 } 472 return nil 473 }); err != nil { 474 b.Fatalf("Creation failed: %+v", err) 475 } 476 477 // TODO: reset benchmark 478 b.ResetTimer() 479 //b.StopTimer() 480 481 labels := pprof.Labels("worker", "trigger") 482 pprof.Do(ctx, labels, func(ctx context.Context) { 483 for i := 0; i < b.N; i++ { 484 485 // TODO: Add removal objects 486 487 //b.StartTimer() 488 489 if _, err := mdb.GarbageCollect(ctx); err != nil { 490 b.Fatal(err) 491 } 492 493 //b.StopTimer() 494 495 //var actual []gc.Node 496 497 //if err := db.View(func(tx *bolt.Tx) error { 498 // nodeC := make(chan gc.Node) 499 // var scanErr error 500 // go func() { 501 // defer close(nodeC) 502 // scanErr = scanAll(ctx, tx, nodeC) 503 // }() 504 // for node := range nodeC { 505 // actual = append(actual, node) 506 // } 507 // return scanErr 508 //}); err != nil { 509 // t.Fatal(err) 510 //} 511 512 //checkNodesEqual(t, actual, remaining) 513 } 514 }) 515 } 516 } 517 518 func bytesFor(i int64) []byte { 519 r := rand.New(rand.NewSource(i)) 520 var b [256]byte 521 _, err := r.Read(b[:]) 522 if err != nil { 523 panic(err) 524 } 525 return b[:] 526 } 527 528 func digestFor(i int64) digest.Digest { 529 r := rand.New(rand.NewSource(i)) 530 dgstr := digest.SHA256.Digester() 531 _, err := io.Copy(dgstr.Hash(), io.LimitReader(r, 256)) 532 if err != nil { 533 panic(err) 534 } 535 return dgstr.Digest() 536 } 537 538 type object struct { 539 data interface{} 540 removed bool 541 labels map[string]string 542 } 543 544 func create(obj object, tx *bolt.Tx, db *DB, cs content.Store, sn snapshots.Snapshotter) (*gc.Node, error) { 545 var ( 546 node *gc.Node 547 namespace = "test" 548 ctx = WithTransactionContext(namespaces.WithNamespace(context.Background(), namespace), tx) 549 ) 550 551 switch v := obj.data.(type) { 552 case testContent: 553 expected := digest.FromBytes(v.data) 554 w, err := cs.Writer(ctx, 555 content.WithRef("test-ref"), 556 content.WithDescriptor(ocispec.Descriptor{Size: int64(len(v.data)), Digest: expected})) 557 if err != nil { 558 return nil, errors.Wrap(err, "failed to create writer") 559 } 560 if _, err := w.Write(v.data); err != nil { 561 return nil, errors.Wrap(err, "write blob failed") 562 } 563 if err := w.Commit(ctx, int64(len(v.data)), expected, content.WithLabels(obj.labels)); err != nil { 564 return nil, errors.Wrap(err, "failed to commit blob") 565 } 566 if !obj.removed { 567 node = &gc.Node{ 568 Type: ResourceContent, 569 Namespace: namespace, 570 Key: expected.String(), 571 } 572 } 573 case testSnapshot: 574 if v.active { 575 _, err := sn.Prepare(ctx, v.key, v.parent, snapshots.WithLabels(obj.labels)) 576 if err != nil { 577 return nil, err 578 } 579 } else { 580 akey := fmt.Sprintf("%s-active", v.key) 581 _, err := sn.Prepare(ctx, akey, v.parent) 582 if err != nil { 583 return nil, err 584 } 585 if err := sn.Commit(ctx, v.key, akey, snapshots.WithLabels(obj.labels)); err != nil { 586 return nil, err 587 } 588 } 589 if !obj.removed { 590 node = &gc.Node{ 591 Type: ResourceSnapshot, 592 Namespace: namespace, 593 Key: fmt.Sprintf("native/%s", v.key), 594 } 595 } 596 case testImage: 597 image := images.Image{ 598 Name: v.name, 599 Target: v.target, 600 Labels: obj.labels, 601 } 602 603 _, err := NewImageStore(db).Create(ctx, image) 604 if err != nil { 605 return nil, errors.Wrap(err, "failed to create image") 606 } 607 case testContainer: 608 container := containers.Container{ 609 ID: v.id, 610 SnapshotKey: v.snapshot, 611 Snapshotter: "native", 612 Labels: obj.labels, 613 614 Runtime: containers.RuntimeInfo{ 615 Name: "testruntime", 616 }, 617 Spec: &types.Any{}, 618 } 619 _, err := NewContainerStore(db).Create(ctx, container) 620 if err != nil { 621 return nil, err 622 } 623 case testLease: 624 lm := NewLeaseManager(db) 625 626 l, err := lm.Create(ctx, leases.WithID(v.id), leases.WithLabels(obj.labels)) 627 if err != nil { 628 return nil, err 629 } 630 631 for _, ref := range v.refs { 632 if err := lm.AddResource(ctx, l, ref); err != nil { 633 return nil, err 634 } 635 } 636 637 if !obj.removed { 638 node = &gc.Node{ 639 Type: ResourceLease, 640 Namespace: namespace, 641 Key: v.id, 642 } 643 } 644 } 645 646 return node, nil 647 } 648 649 func blob(b []byte, r bool, l ...string) object { 650 return object{ 651 data: testContent{ 652 data: b, 653 }, 654 removed: r, 655 labels: labelmap(l...), 656 } 657 } 658 659 func image(n string, d digest.Digest, l ...string) object { 660 return object{ 661 data: testImage{ 662 name: n, 663 target: ocispec.Descriptor{ 664 MediaType: "irrelevant", 665 Digest: d, 666 Size: 256, 667 }, 668 }, 669 removed: false, 670 labels: labelmap(l...), 671 } 672 } 673 674 func newSnapshot(key, parent string, active, r bool, l ...string) object { 675 return object{ 676 data: testSnapshot{ 677 key: key, 678 parent: parent, 679 active: active, 680 }, 681 removed: r, 682 labels: labelmap(l...), 683 } 684 } 685 686 func container(id, s string, l ...string) object { 687 return object{ 688 data: testContainer{ 689 id: id, 690 snapshot: s, 691 }, 692 removed: false, 693 labels: labelmap(l...), 694 } 695 } 696 697 func lease(id string, refs []leases.Resource, r bool, l ...string) object { 698 return object{ 699 data: testLease{ 700 id: id, 701 refs: refs, 702 }, 703 removed: r, 704 labels: labelmap(l...), 705 } 706 } 707 708 type testContent struct { 709 data []byte 710 } 711 712 type testSnapshot struct { 713 key string 714 parent string 715 active bool 716 } 717 718 type testImage struct { 719 name string 720 target ocispec.Descriptor 721 } 722 723 type testContainer struct { 724 id string 725 snapshot string 726 } 727 728 type testLease struct { 729 id string 730 refs []leases.Resource 731 } 732 733 func newStores(t testing.TB) (*DB, content.Store, snapshots.Snapshotter, func()) { 734 td, err := ioutil.TempDir("", "gc-test-") 735 if err != nil { 736 t.Fatal(err) 737 } 738 db, err := bolt.Open(filepath.Join(td, "meta.db"), 0644, nil) 739 if err != nil { 740 t.Fatal(err) 741 } 742 743 nsn, err := native.NewSnapshotter(filepath.Join(td, "snapshots")) 744 if err != nil { 745 t.Fatal(err) 746 } 747 748 lcs, err := local.NewStore(filepath.Join(td, "content")) 749 if err != nil { 750 t.Fatal(err) 751 } 752 753 mdb := NewDB(db, lcs, map[string]snapshots.Snapshotter{"native": nsn}) 754 755 return mdb, mdb.ContentStore(), mdb.Snapshotter("native"), func() { 756 os.RemoveAll(td) 757 } 758 }