github.com/containerd/Containerd@v1.4.13/metadata/containers_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 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "reflect" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/containerd/containerd/containers" 31 "github.com/containerd/containerd/errdefs" 32 "github.com/containerd/containerd/filters" 33 "github.com/containerd/containerd/log/logtest" 34 "github.com/containerd/containerd/namespaces" 35 "github.com/containerd/typeurl" 36 "github.com/gogo/protobuf/types" 37 specs "github.com/opencontainers/runtime-spec/specs-go" 38 "github.com/pkg/errors" 39 bolt "go.etcd.io/bbolt" 40 ) 41 42 func init() { 43 typeurl.Register(&specs.Spec{}, "types.containerd.io/opencontainers/runtime-spec", "v1", "Spec") 44 } 45 46 func TestContainersList(t *testing.T) { 47 ctx, db, cancel := testEnv(t) 48 defer cancel() 49 50 store := NewContainerStore(NewDB(db, nil, nil)) 51 52 spec := &specs.Spec{} 53 encoded, err := typeurl.MarshalAny(spec) 54 if err != nil { 55 t.Fatal(err) 56 } 57 58 testset := map[string]*containers.Container{} 59 for i := 0; i < 4; i++ { 60 id := "container-" + fmt.Sprint(i) 61 testset[id] = &containers.Container{ 62 ID: id, 63 Labels: map[string]string{ 64 "idlabel": id, 65 "even": fmt.Sprint(i%2 == 0), 66 "odd": fmt.Sprint(i%2 != 0), 67 }, 68 Spec: encoded, 69 SnapshotKey: "test-snapshot-key", 70 Snapshotter: "snapshotter", 71 Runtime: containers.RuntimeInfo{ 72 Name: "testruntime", 73 }, 74 Image: "test image", 75 } 76 77 if err := db.Update(func(tx *bolt.Tx) error { 78 now := time.Now() 79 result, err := store.Create(WithTransactionContext(ctx, tx), *testset[id]) 80 if err != nil { 81 return err 82 } 83 84 checkContainerTimestamps(t, &result, now, true) 85 testset[id].UpdatedAt, testset[id].CreatedAt = result.UpdatedAt, result.CreatedAt 86 checkContainersEqual(t, &result, testset[id], "ensure that containers were created as expected for list") 87 return nil 88 }); err != nil { 89 t.Fatal(err) 90 } 91 } 92 93 for _, testcase := range []struct { 94 name string 95 filters []string 96 }{ 97 { 98 name: "FullSet", 99 }, 100 { 101 name: "FullSetFiltered", // full set, but because we have OR filter 102 filters: []string{"labels.even==true", "labels.odd==true"}, 103 }, 104 { 105 name: "Even", 106 filters: []string{"labels.even==true"}, 107 }, 108 { 109 name: "Odd", 110 filters: []string{"labels.odd==true"}, 111 }, 112 { 113 name: "ByID", 114 filters: []string{"id==container-0"}, 115 }, 116 { 117 name: "ByIDLabelEven", 118 filters: []string{"labels.idlabel==container-0,labels.even==true"}, 119 }, 120 { 121 name: "ByRuntime", 122 filters: []string{"runtime.name==testruntime"}, 123 }, 124 } { 125 t.Run(testcase.name, func(t *testing.T) { 126 testset := testset 127 if len(testcase.filters) > 0 { 128 fs, err := filters.ParseAll(testcase.filters...) 129 if err != nil { 130 t.Fatal(err) 131 } 132 133 newtestset := make(map[string]*containers.Container, len(testset)) 134 for k, v := range testset { 135 if fs.Match(adaptContainer(*v)) { 136 newtestset[k] = v 137 } 138 } 139 testset = newtestset 140 } 141 142 results, err := store.List(ctx, testcase.filters...) 143 if err != nil { 144 t.Fatal(err) 145 } 146 147 if len(results) == 0 { // all tests return a non-empty result set 148 t.Fatalf("not results returned") 149 } 150 151 if len(results) != len(testset) { 152 t.Fatalf("length of result does not match testset: %v != %v", len(results), len(testset)) 153 } 154 155 for _, result := range results { 156 checkContainersEqual(t, &result, testset[result.ID], "list results did not match") 157 } 158 }) 159 } 160 161 // delete everything to test it 162 for id := range testset { 163 if err := store.Delete(ctx, id); err != nil { 164 t.Fatal(err) 165 } 166 167 // try it again, get NotFound 168 if err := store.Delete(ctx, id); err == nil { 169 t.Fatalf("expected error deleting non-existent container") 170 } else if !errdefs.IsNotFound(err) { 171 t.Fatalf("unexpected error %v", err) 172 } 173 } 174 } 175 176 // TestContainersUpdate ensures that updates are taken in an expected manner. 177 func TestContainersCreateUpdateDelete(t *testing.T) { 178 ctx, db, cancel := testEnv(t) 179 defer cancel() 180 181 store := NewContainerStore(NewDB(db, nil, nil)) 182 183 spec := &specs.Spec{} 184 encoded, err := typeurl.MarshalAny(spec) 185 if err != nil { 186 t.Fatal(err) 187 } 188 189 spec.Annotations = map[string]string{"updated": "true"} 190 encodedUpdated, err := typeurl.MarshalAny(spec) 191 if err != nil { 192 t.Fatal(err) 193 } 194 195 for _, testcase := range []struct { 196 name string 197 original containers.Container 198 createerr error 199 input containers.Container 200 fieldpaths []string 201 expected containers.Container 202 cause error 203 }{ 204 { 205 name: "UpdateIDFail", 206 original: containers.Container{ 207 Spec: encoded, 208 SnapshotKey: "test-snapshot-key", 209 Snapshotter: "snapshotter", 210 Runtime: containers.RuntimeInfo{ 211 Name: "testruntime", 212 }, 213 }, 214 input: containers.Container{ 215 ID: "newid", 216 Spec: encoded, 217 Runtime: containers.RuntimeInfo{ 218 Name: "testruntime", 219 }, 220 }, 221 fieldpaths: []string{"id"}, 222 cause: errdefs.ErrNotFound, 223 }, 224 { 225 name: "UpdateRuntimeFail", 226 original: containers.Container{ 227 SnapshotKey: "test-snapshot-key", 228 Snapshotter: "snapshotter", 229 Spec: encoded, 230 Runtime: containers.RuntimeInfo{ 231 Name: "testruntime", 232 }, 233 }, 234 input: containers.Container{ 235 Spec: encoded, 236 Runtime: containers.RuntimeInfo{ 237 Name: "testruntimedifferent", 238 }, 239 }, 240 fieldpaths: []string{"runtime"}, 241 cause: errdefs.ErrInvalidArgument, 242 }, 243 { 244 name: "UpdateRuntimeClearFail", 245 original: containers.Container{ 246 Spec: encoded, 247 SnapshotKey: "test-snapshot-key", 248 Snapshotter: "snapshotter", 249 Runtime: containers.RuntimeInfo{ 250 Name: "testruntime", 251 }, 252 }, 253 input: containers.Container{ 254 Spec: encoded, 255 }, 256 fieldpaths: []string{"runtime"}, 257 cause: errdefs.ErrInvalidArgument, 258 }, 259 { 260 name: "UpdateSpec", 261 original: containers.Container{ 262 Spec: encoded, 263 SnapshotKey: "test-snapshot-key", 264 Snapshotter: "snapshotter", 265 Runtime: containers.RuntimeInfo{ 266 Name: "testruntime", 267 }, 268 Image: "test image", 269 }, 270 input: containers.Container{ 271 Spec: encodedUpdated, 272 }, 273 fieldpaths: []string{"spec"}, 274 expected: containers.Container{ 275 Runtime: containers.RuntimeInfo{ 276 Name: "testruntime", 277 }, 278 Spec: encodedUpdated, 279 SnapshotKey: "test-snapshot-key", 280 Snapshotter: "snapshotter", 281 Image: "test image", 282 }, 283 }, 284 { 285 name: "UpdateSnapshot", 286 original: containers.Container{ 287 288 Spec: encoded, 289 SnapshotKey: "test-snapshot-key", 290 Snapshotter: "snapshotter", 291 Runtime: containers.RuntimeInfo{ 292 Name: "testruntime", 293 }, 294 Image: "test image", 295 }, 296 input: containers.Container{ 297 SnapshotKey: "test2-snapshot-key", 298 }, 299 fieldpaths: []string{"snapshotkey"}, 300 expected: containers.Container{ 301 302 Spec: encoded, 303 SnapshotKey: "test2-snapshot-key", 304 Snapshotter: "snapshotter", 305 Runtime: containers.RuntimeInfo{ 306 Name: "testruntime", 307 }, 308 Image: "test image", 309 }, 310 }, 311 { 312 name: "UpdateImage", 313 original: containers.Container{ 314 315 Spec: encoded, 316 SnapshotKey: "test-snapshot-key", 317 Snapshotter: "snapshotter", 318 Runtime: containers.RuntimeInfo{ 319 Name: "testruntime", 320 }, 321 Image: "test image", 322 }, 323 input: containers.Container{ 324 Image: "test2 image", 325 }, 326 fieldpaths: []string{"image"}, 327 expected: containers.Container{ 328 329 Spec: encoded, 330 SnapshotKey: "test-snapshot-key", 331 Snapshotter: "snapshotter", 332 Runtime: containers.RuntimeInfo{ 333 Name: "testruntime", 334 }, 335 Image: "test2 image", 336 }, 337 }, 338 { 339 name: "UpdateLabel", 340 original: containers.Container{ 341 Labels: map[string]string{ 342 "foo": "one", 343 "bar": "two", 344 }, 345 Spec: encoded, 346 SnapshotKey: "test-snapshot-key", 347 Snapshotter: "snapshotter", 348 Runtime: containers.RuntimeInfo{ 349 Name: "testruntime", 350 }, 351 Image: "test image", 352 }, 353 input: containers.Container{ 354 Labels: map[string]string{ 355 "bar": "baz", 356 }, 357 }, 358 fieldpaths: []string{"labels.bar"}, 359 expected: containers.Container{ 360 Labels: map[string]string{ 361 "foo": "one", 362 "bar": "baz", 363 }, 364 Spec: encoded, 365 SnapshotKey: "test-snapshot-key", 366 Snapshotter: "snapshotter", 367 Runtime: containers.RuntimeInfo{ 368 Name: "testruntime", 369 }, 370 Image: "test image", 371 }, 372 }, 373 { 374 name: "DeleteAllLabels", 375 original: containers.Container{ 376 Labels: map[string]string{ 377 "foo": "one", 378 "bar": "two", 379 }, 380 Spec: encoded, 381 SnapshotKey: "test-snapshot-key", 382 Snapshotter: "snapshotter", 383 Runtime: containers.RuntimeInfo{ 384 Name: "testruntime", 385 }, 386 Image: "test image", 387 }, 388 input: containers.Container{ 389 Labels: nil, 390 }, 391 fieldpaths: []string{"labels"}, 392 expected: containers.Container{ 393 Spec: encoded, 394 SnapshotKey: "test-snapshot-key", 395 Snapshotter: "snapshotter", 396 Runtime: containers.RuntimeInfo{ 397 Name: "testruntime", 398 }, 399 Image: "test image", 400 }, 401 }, 402 { 403 name: "DeleteLabel", 404 original: containers.Container{ 405 Labels: map[string]string{ 406 "foo": "one", 407 "bar": "two", 408 }, 409 Spec: encoded, 410 SnapshotKey: "test-snapshot-key", 411 Snapshotter: "snapshotter", 412 Runtime: containers.RuntimeInfo{ 413 Name: "testruntime", 414 }, 415 Image: "test image", 416 }, 417 input: containers.Container{ 418 Labels: map[string]string{ 419 "bar": "", 420 }, 421 }, 422 fieldpaths: []string{"labels.bar"}, 423 expected: containers.Container{ 424 Labels: map[string]string{ 425 "foo": "one", 426 }, 427 Spec: encoded, 428 SnapshotKey: "test-snapshot-key", 429 Snapshotter: "snapshotter", 430 Runtime: containers.RuntimeInfo{ 431 Name: "testruntime", 432 }, 433 Image: "test image", 434 }, 435 }, 436 { 437 name: "UpdateSnapshotKeyImmutable", 438 original: containers.Container{ 439 Spec: encoded, 440 SnapshotKey: "", 441 Snapshotter: "", 442 Runtime: containers.RuntimeInfo{ 443 Name: "testruntime", 444 }, 445 }, 446 input: containers.Container{ 447 SnapshotKey: "something", 448 Snapshotter: "something", 449 }, 450 fieldpaths: []string{"snapshotkey", "snapshotter"}, 451 cause: errdefs.ErrInvalidArgument, 452 }, 453 { 454 name: "SnapshotKeyWithoutSnapshot", 455 original: containers.Container{ 456 Spec: encoded, 457 SnapshotKey: "/nosnapshot", 458 Snapshotter: "", 459 Runtime: containers.RuntimeInfo{ 460 Name: "testruntime", 461 }, 462 }, 463 createerr: errdefs.ErrInvalidArgument, 464 }, 465 { 466 name: "UpdateExtensionsFull", 467 original: containers.Container{ 468 Spec: encoded, 469 Runtime: containers.RuntimeInfo{ 470 Name: "testruntime", 471 }, 472 Extensions: map[string]types.Any{ 473 "hello": { 474 TypeUrl: "test.update.extensions", 475 Value: []byte("hello"), 476 }, 477 }, 478 }, 479 input: containers.Container{ 480 Spec: encoded, 481 Runtime: containers.RuntimeInfo{ 482 Name: "testruntime", 483 }, 484 Extensions: map[string]types.Any{ 485 "hello": { 486 TypeUrl: "test.update.extensions", 487 Value: []byte("world"), 488 }, 489 }, 490 }, 491 expected: containers.Container{ 492 Spec: encoded, 493 Runtime: containers.RuntimeInfo{ 494 Name: "testruntime", 495 }, 496 Extensions: map[string]types.Any{ 497 "hello": { 498 TypeUrl: "test.update.extensions", 499 Value: []byte("world"), 500 }, 501 }, 502 }, 503 }, 504 { 505 name: "UpdateExtensionsNotInFieldpath", 506 original: containers.Container{ 507 Spec: encoded, 508 Runtime: containers.RuntimeInfo{ 509 Name: "testruntime", 510 }, 511 Extensions: map[string]types.Any{ 512 "hello": { 513 TypeUrl: "test.update.extensions", 514 Value: []byte("hello"), 515 }, 516 }, 517 }, 518 input: containers.Container{ 519 Spec: encoded, 520 Runtime: containers.RuntimeInfo{ 521 Name: "testruntime", 522 }, 523 Extensions: map[string]types.Any{ 524 "hello": { 525 TypeUrl: "test.update.extensions", 526 Value: []byte("world"), 527 }, 528 }, 529 }, 530 fieldpaths: []string{"labels"}, 531 expected: containers.Container{ 532 Spec: encoded, 533 Runtime: containers.RuntimeInfo{ 534 Name: "testruntime", 535 }, 536 Extensions: map[string]types.Any{ 537 "hello": { 538 TypeUrl: "test.update.extensions", 539 Value: []byte("hello"), 540 }, 541 }, 542 }, 543 }, 544 { 545 name: "UpdateExtensionsFieldPath", 546 original: containers.Container{ 547 Spec: encoded, 548 Runtime: containers.RuntimeInfo{ 549 Name: "testruntime", 550 }, 551 Extensions: map[string]types.Any{ 552 "hello": { 553 TypeUrl: "test.update.extensions", 554 Value: []byte("hello"), 555 }, 556 }, 557 }, 558 input: containers.Container{ 559 Labels: map[string]string{ 560 "foo": "one", 561 }, 562 Extensions: map[string]types.Any{ 563 "hello": { 564 TypeUrl: "test.update.extensions", 565 Value: []byte("world"), 566 }, 567 }, 568 }, 569 fieldpaths: []string{"extensions"}, 570 expected: containers.Container{ 571 Spec: encoded, 572 Runtime: containers.RuntimeInfo{ 573 Name: "testruntime", 574 }, 575 Extensions: map[string]types.Any{ 576 "hello": { 577 TypeUrl: "test.update.extensions", 578 Value: []byte("world"), 579 }, 580 }, 581 }, 582 }, 583 { 584 name: "UpdateExtensionsFieldPathIsolated", 585 original: containers.Container{ 586 Spec: encoded, 587 Runtime: containers.RuntimeInfo{ 588 Name: "testruntime", 589 }, 590 Extensions: map[string]types.Any{ 591 // leaves hello in place. 592 "hello": { 593 TypeUrl: "test.update.extensions", 594 Value: []byte("hello"), 595 }, 596 }, 597 }, 598 input: containers.Container{ 599 Extensions: map[string]types.Any{ 600 "hello": { 601 TypeUrl: "test.update.extensions", 602 Value: []byte("universe"), // this will be ignored 603 }, 604 "bar": { 605 TypeUrl: "test.update.extensions", 606 Value: []byte("foo"), // this will be added 607 }, 608 }, 609 }, 610 fieldpaths: []string{"extensions.bar"}, // 611 expected: containers.Container{ 612 Spec: encoded, 613 Runtime: containers.RuntimeInfo{ 614 Name: "testruntime", 615 }, 616 Extensions: map[string]types.Any{ 617 "hello": { 618 TypeUrl: "test.update.extensions", 619 Value: []byte("hello"), // remains as world 620 }, 621 "bar": { 622 TypeUrl: "test.update.extensions", 623 Value: []byte("foo"), // this will be added 624 }, 625 }, 626 }, 627 }, 628 } { 629 t.Run(testcase.name, func(t *testing.T) { 630 testcase.original.ID = testcase.name 631 if testcase.input.ID == "" { 632 testcase.input.ID = testcase.name 633 } 634 testcase.expected.ID = testcase.name 635 636 now := time.Now().UTC() 637 638 result, err := store.Create(ctx, testcase.original) 639 if !errors.Is(err, testcase.createerr) { 640 if testcase.createerr == nil { 641 t.Fatalf("unexpected error: %v", err) 642 } else { 643 t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.createerr) 644 } 645 } else if testcase.createerr != nil { 646 return 647 } 648 649 checkContainerTimestamps(t, &result, now, true) 650 651 // ensure that createdat is never tampered with 652 testcase.original.CreatedAt = result.CreatedAt 653 testcase.expected.CreatedAt = result.CreatedAt 654 testcase.original.UpdatedAt = result.UpdatedAt 655 testcase.expected.UpdatedAt = result.UpdatedAt 656 657 checkContainersEqual(t, &result, &testcase.original, "unexpected result on container update") 658 659 now = time.Now() 660 result, err = store.Update(ctx, testcase.input, testcase.fieldpaths...) 661 if !errors.Is(err, testcase.cause) { 662 if testcase.cause == nil { 663 t.Fatalf("unexpected error: %v", err) 664 } else { 665 t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.cause) 666 } 667 } else if testcase.cause != nil { 668 return 669 } 670 671 checkContainerTimestamps(t, &result, now, false) 672 testcase.expected.UpdatedAt = result.UpdatedAt 673 checkContainersEqual(t, &result, &testcase.expected, "updated failed to get expected result") 674 675 result, err = store.Get(ctx, testcase.original.ID) 676 if err != nil { 677 t.Fatal(err) 678 } 679 680 checkContainersEqual(t, &result, &testcase.expected, "get after failed to get expected result") 681 }) 682 } 683 } 684 685 func checkContainerTimestamps(t *testing.T, c *containers.Container, now time.Time, oncreate bool) { 686 if c.UpdatedAt.IsZero() || c.CreatedAt.IsZero() { 687 t.Fatalf("timestamps not set") 688 } 689 690 if oncreate { 691 if !c.CreatedAt.Equal(c.UpdatedAt) { 692 t.Fatal("timestamps should be equal on create") 693 } 694 695 } else { 696 // ensure that updatedat is always after createdat 697 if !c.UpdatedAt.After(c.CreatedAt) { 698 t.Fatalf("timestamp for updatedat not after createdat: %v <= %v", c.UpdatedAt, c.CreatedAt) 699 } 700 } 701 702 if c.UpdatedAt.Before(now) { 703 t.Fatal("createdat time incorrect should be after the start of the operation") 704 } 705 } 706 707 func checkContainersEqual(t *testing.T, a, b *containers.Container, format string, args ...interface{}) { 708 if !reflect.DeepEqual(a, b) { 709 t.Fatalf("containers not equal \n\t%v != \n\t%v: "+format, append([]interface{}{a, b}, args...)...) 710 } 711 } 712 713 func testEnv(t *testing.T) (context.Context, *bolt.DB, func()) { 714 ctx, cancel := context.WithCancel(context.Background()) 715 ctx = namespaces.WithNamespace(ctx, "testing") 716 ctx = logtest.WithT(ctx, t) 717 718 dirname, err := ioutil.TempDir("", strings.Replace(t.Name(), "/", "_", -1)+"-") 719 if err != nil { 720 t.Fatal(err) 721 } 722 723 db, err := bolt.Open(filepath.Join(dirname, "meta.db"), 0644, nil) 724 if err != nil { 725 t.Fatal(err) 726 } 727 728 return ctx, db, func() { 729 db.Close() 730 if err := os.RemoveAll(dirname); err != nil { 731 t.Log("failed removing temp dir", err) 732 } 733 cancel() 734 } 735 }