github.com/demonoid81/containerd@v1.3.4/metadata/images_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 "fmt" 21 "reflect" 22 "testing" 23 "time" 24 25 "github.com/containerd/containerd/errdefs" 26 "github.com/containerd/containerd/filters" 27 "github.com/containerd/containerd/images" 28 digest "github.com/opencontainers/go-digest" 29 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 30 "github.com/pkg/errors" 31 ) 32 33 func TestImagesList(t *testing.T) { 34 ctx, db, cancel := testEnv(t) 35 defer cancel() 36 store := NewImageStore(NewDB(db, nil, nil)) 37 38 testset := map[string]*images.Image{} 39 for i := 0; i < 4; i++ { 40 id := "image-" + fmt.Sprint(i) 41 testset[id] = &images.Image{ 42 Name: id, 43 Labels: map[string]string{ 44 "namelabel": id, 45 "even": fmt.Sprint(i%2 == 0), 46 "odd": fmt.Sprint(i%2 != 0), 47 }, 48 Target: ocispec.Descriptor{ 49 Size: 10, 50 MediaType: "application/vnd.containerd.test", 51 Digest: digest.FromString(id), 52 Annotations: map[string]string{ 53 "foo": "bar", 54 }, 55 }, 56 } 57 58 now := time.Now() 59 result, err := store.Create(ctx, *testset[id]) 60 if err != nil { 61 t.Fatal(err) 62 } 63 64 checkImageTimestamps(t, &result, now, true) 65 testset[id].UpdatedAt, testset[id].CreatedAt = result.UpdatedAt, result.CreatedAt 66 checkImagesEqual(t, &result, testset[id], "ensure that containers were created as expected for list") 67 } 68 69 for _, testcase := range []struct { 70 name string 71 filters []string 72 }{ 73 { 74 name: "FullSet", 75 }, 76 { 77 name: "FullSetFiltered", // full set, but because we have OR filter 78 filters: []string{"labels.even==true", "labels.odd==true"}, 79 }, 80 { 81 name: "Even", 82 filters: []string{"labels.even==true"}, 83 }, 84 { 85 name: "Odd", 86 filters: []string{"labels.odd==true"}, 87 }, 88 { 89 name: "ByName", 90 filters: []string{"name==image-0"}, 91 }, 92 { 93 name: "ByNameLabelEven", 94 filters: []string{"labels.namelabel==image-0,labels.even==true"}, 95 }, 96 { 97 name: "ByMediaType", 98 filters: []string{"target.mediatype~=application/vnd.*"}, 99 }, 100 } { 101 t.Run(testcase.name, func(t *testing.T) { 102 testset := testset 103 if len(testcase.filters) > 0 { 104 fs, err := filters.ParseAll(testcase.filters...) 105 if err != nil { 106 t.Fatal(err) 107 } 108 109 newtestset := make(map[string]*images.Image, len(testset)) 110 for k, v := range testset { 111 if fs.Match(adaptImage(*v)) { 112 newtestset[k] = v 113 } 114 } 115 testset = newtestset 116 } 117 118 results, err := store.List(ctx, testcase.filters...) 119 if err != nil { 120 t.Fatal(err) 121 } 122 123 if len(results) == 0 { // all tests return a non-empty result set 124 t.Fatalf("no results returned") 125 } 126 127 if len(results) != len(testset) { 128 t.Fatalf("length of result does not match testset: %v != %v", len(results), len(testset)) 129 } 130 131 for _, result := range results { 132 checkImagesEqual(t, &result, testset[result.Name], "list results did not match") 133 } 134 }) 135 } 136 137 // delete everything to test it 138 for id := range testset { 139 if err := store.Delete(ctx, id); err != nil { 140 t.Fatal(err) 141 } 142 143 // try it again, get NotFound 144 if err := store.Delete(ctx, id); errors.Cause(err) != errdefs.ErrNotFound { 145 t.Fatalf("unexpected error %v", err) 146 } 147 } 148 } 149 func TestImagesCreateUpdateDelete(t *testing.T) { 150 ctx, db, cancel := testEnv(t) 151 defer cancel() 152 store := NewImageStore(NewDB(db, nil, nil)) 153 154 for _, testcase := range []struct { 155 name string 156 original images.Image 157 createerr error 158 input images.Image 159 fieldpaths []string 160 expected images.Image 161 cause error 162 }{ 163 { 164 name: "Touch", 165 original: imageBase(), 166 input: images.Image{ 167 Labels: map[string]string{ 168 "foo": "bar", 169 "baz": "boo", 170 }, 171 Target: ocispec.Descriptor{ 172 Size: 10, 173 MediaType: "application/vnd.oci.blab", 174 Annotations: map[string]string{ 175 "foo": "bar", 176 }, 177 }, 178 }, 179 expected: images.Image{ 180 Labels: map[string]string{ 181 "foo": "bar", 182 "baz": "boo", 183 }, 184 Target: ocispec.Descriptor{ 185 Size: 10, 186 MediaType: "application/vnd.oci.blab", 187 Annotations: map[string]string{ 188 "foo": "bar", 189 }, 190 }, 191 }, 192 }, 193 { 194 name: "NoTarget", 195 original: images.Image{ 196 Labels: map[string]string{ 197 "foo": "bar", 198 "baz": "boo", 199 }, 200 Target: ocispec.Descriptor{}, 201 }, 202 createerr: errdefs.ErrInvalidArgument, 203 }, 204 { 205 name: "ReplaceLabels", 206 original: imageBase(), 207 input: images.Image{ 208 Labels: map[string]string{ 209 "for": "bar", 210 "boo": "boo", 211 }, 212 Target: ocispec.Descriptor{ 213 Size: 10, 214 MediaType: "application/vnd.oci.blab", 215 Annotations: map[string]string{ 216 "foo": "bar", 217 }, 218 }, 219 }, 220 expected: images.Image{ 221 Labels: map[string]string{ 222 "for": "bar", 223 "boo": "boo", 224 }, 225 Target: ocispec.Descriptor{ 226 Size: 10, 227 MediaType: "application/vnd.oci.blab", 228 Annotations: map[string]string{ 229 "foo": "bar", 230 }, 231 }, 232 }, 233 }, 234 { 235 name: "ReplaceLabelsFieldPath", 236 original: imageBase(), 237 input: images.Image{ 238 Labels: map[string]string{ 239 "for": "bar", 240 "boo": "boo", 241 }, 242 Target: ocispec.Descriptor{ 243 Size: 20, // ignored 244 MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored 245 Annotations: map[string]string{ 246 "not": "bar", 247 "new": "boo", 248 }, 249 }, 250 }, 251 fieldpaths: []string{"labels"}, 252 expected: images.Image{ 253 Labels: map[string]string{ 254 "for": "bar", 255 "boo": "boo", 256 }, 257 Target: ocispec.Descriptor{ 258 Size: 10, 259 MediaType: "application/vnd.oci.blab", 260 Annotations: map[string]string{ 261 "foo": "bar", 262 "baz": "boo", 263 }, 264 }, 265 }, 266 }, 267 { 268 name: "ReplaceLabelsAnnotationsFieldPath", 269 original: imageBase(), 270 input: images.Image{ 271 Labels: map[string]string{ 272 "for": "bar", 273 "boo": "boo", 274 }, 275 Target: ocispec.Descriptor{ 276 Size: 20, // ignored 277 MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored 278 Annotations: map[string]string{ 279 "foo": "boo", 280 }, 281 }, 282 }, 283 fieldpaths: []string{"annotations", "labels"}, 284 expected: images.Image{ 285 Labels: map[string]string{ 286 "for": "bar", 287 "boo": "boo", 288 }, 289 Target: ocispec.Descriptor{ 290 Size: 10, 291 MediaType: "application/vnd.oci.blab", 292 Annotations: map[string]string{ 293 "foo": "boo", 294 }, 295 }, 296 }, 297 }, 298 { 299 name: "ReplaceLabel", 300 original: imageBase(), 301 input: images.Image{ 302 Labels: map[string]string{ 303 "foo": "baz", 304 "baz": "bunk", 305 }, 306 Target: ocispec.Descriptor{ 307 Size: 20, // ignored 308 MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored 309 Annotations: map[string]string{ 310 "foo": "bar", 311 }, 312 }, 313 }, 314 fieldpaths: []string{"labels.foo"}, 315 expected: images.Image{ 316 Labels: map[string]string{ 317 "foo": "baz", 318 "baz": "boo", 319 }, 320 Target: ocispec.Descriptor{ 321 Size: 10, 322 MediaType: "application/vnd.oci.blab", 323 Annotations: map[string]string{ 324 "foo": "bar", 325 "baz": "boo", 326 }, 327 }, 328 }, 329 }, 330 { 331 name: "ReplaceAnnotation", 332 original: imageBase(), 333 input: images.Image{ 334 Labels: map[string]string{ 335 "foo": "baz", 336 "baz": "bunk", 337 }, 338 Target: ocispec.Descriptor{ 339 Size: 20, // ignored 340 MediaType: "application/vnd.oci.blab+ignored", // make sure other stuff is ignored 341 Annotations: map[string]string{ 342 "foo": "baz", 343 "baz": "bunk", 344 }, 345 }, 346 }, 347 fieldpaths: []string{"annotations.foo"}, 348 expected: images.Image{ 349 Labels: map[string]string{ 350 "foo": "bar", 351 "baz": "boo", 352 }, 353 Target: ocispec.Descriptor{ 354 Size: 10, 355 MediaType: "application/vnd.oci.blab", 356 Annotations: map[string]string{ 357 "foo": "baz", 358 "baz": "boo", 359 }, 360 }, 361 }, 362 }, 363 { 364 name: "ReplaceTarget", // target must be updated as a unit 365 original: imageBase(), 366 input: images.Image{ 367 Target: ocispec.Descriptor{ 368 Size: 10, 369 MediaType: "application/vnd.oci.blab+replaced", 370 Annotations: map[string]string{ 371 "fox": "dog", 372 }, 373 }, 374 }, 375 fieldpaths: []string{"target"}, 376 expected: images.Image{ 377 Labels: map[string]string{ 378 "foo": "bar", 379 "baz": "boo", 380 }, 381 Target: ocispec.Descriptor{ 382 Size: 10, 383 MediaType: "application/vnd.oci.blab+replaced", 384 Annotations: map[string]string{ 385 "fox": "dog", 386 }, 387 }, 388 }, 389 }, 390 { 391 name: "EmptySize", 392 original: imageBase(), 393 input: images.Image{ 394 Labels: map[string]string{ 395 "foo": "bar", 396 "baz": "boo", 397 }, 398 Target: ocispec.Descriptor{ 399 Size: 0, 400 MediaType: "application/vnd.oci.blab", 401 Annotations: map[string]string{ 402 "foo": "bar", 403 }, 404 }, 405 }, 406 cause: errdefs.ErrInvalidArgument, 407 }, 408 { 409 name: "EmptySizeOnCreate", 410 original: images.Image{ 411 Labels: map[string]string{ 412 "foo": "bar", 413 "baz": "boo", 414 }, 415 Target: ocispec.Descriptor{ 416 MediaType: "application/vnd.oci.blab", 417 Annotations: map[string]string{ 418 "foo": "bar", 419 }, 420 }, 421 }, 422 createerr: errdefs.ErrInvalidArgument, 423 }, 424 { 425 name: "EmptyMediaType", 426 original: imageBase(), 427 input: images.Image{ 428 Labels: map[string]string{ 429 "foo": "bar", 430 "baz": "boo", 431 }, 432 Target: ocispec.Descriptor{ 433 Size: 10, 434 }, 435 }, 436 cause: errdefs.ErrInvalidArgument, 437 }, 438 { 439 name: "EmptySizeOnCreate", 440 original: images.Image{ 441 Labels: map[string]string{ 442 "foo": "bar", 443 "baz": "boo", 444 }, 445 Target: ocispec.Descriptor{ 446 Size: 10, 447 }, 448 }, 449 createerr: errdefs.ErrInvalidArgument, 450 }, 451 { 452 name: "TryUpdateNameFail", 453 original: images.Image{ 454 Labels: map[string]string{ 455 "foo": "bar", 456 "baz": "boo", 457 }, 458 Target: ocispec.Descriptor{ 459 Size: 10, 460 MediaType: "application/vnd.oci.blab", 461 Annotations: map[string]string{ 462 "foo": "bar", 463 }, 464 }, 465 }, 466 input: images.Image{ 467 Name: "test should fail", 468 Labels: map[string]string{ 469 "foo": "bar", 470 "baz": "boo", 471 }, 472 Target: ocispec.Descriptor{ 473 Size: 10, 474 MediaType: "application/vnd.oci.blab", 475 Annotations: map[string]string{ 476 "foo": "bar", 477 }, 478 }, 479 }, 480 cause: errdefs.ErrNotFound, 481 }, 482 } { 483 t.Run(testcase.name, func(t *testing.T) { 484 testcase.original.Name = testcase.name 485 if testcase.input.Name == "" { 486 testcase.input.Name = testcase.name 487 } 488 testcase.expected.Name = testcase.name 489 490 if testcase.original.Target.Digest == "" { 491 testcase.original.Target.Digest = digest.FromString(testcase.name) 492 testcase.input.Target.Digest = testcase.original.Target.Digest 493 testcase.expected.Target.Digest = testcase.original.Target.Digest 494 } 495 496 // Create 497 now := time.Now() 498 created, err := store.Create(ctx, testcase.original) 499 if errors.Cause(err) != testcase.createerr { 500 if testcase.createerr == nil { 501 t.Fatalf("unexpected error: %v", err) 502 } else { 503 t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.createerr) 504 } 505 } else if testcase.createerr != nil { 506 return 507 } 508 509 checkImageTimestamps(t, &created, now, true) 510 511 testcase.original.CreatedAt = created.CreatedAt 512 testcase.expected.CreatedAt = created.CreatedAt 513 testcase.original.UpdatedAt = created.UpdatedAt 514 testcase.expected.UpdatedAt = created.UpdatedAt 515 516 checkImagesEqual(t, &created, &testcase.original, "unexpected image on creation") 517 518 // Update 519 now = time.Now() 520 updated, err := store.Update(ctx, testcase.input, testcase.fieldpaths...) 521 if errors.Cause(err) != testcase.cause { 522 if testcase.cause == nil { 523 t.Fatalf("unexpected error: %v", err) 524 } else { 525 t.Fatalf("cause of %v (cause: %v) != %v", err, errors.Cause(err), testcase.cause) 526 } 527 } else if testcase.cause != nil { 528 return 529 } 530 531 checkImageTimestamps(t, &updated, now, false) 532 testcase.expected.UpdatedAt = updated.UpdatedAt 533 checkImagesEqual(t, &updated, &testcase.expected, "updated failed to get expected result") 534 535 // Get 536 result, err := store.Get(ctx, testcase.original.Name) 537 if err != nil { 538 t.Fatal(err) 539 } 540 541 checkImagesEqual(t, &result, &testcase.expected, "get after failed to get expected result") 542 }) 543 } 544 } 545 546 func imageBase() images.Image { 547 return images.Image{ 548 Labels: map[string]string{ 549 "foo": "bar", 550 "baz": "boo", 551 }, 552 Target: ocispec.Descriptor{ 553 Size: 10, 554 MediaType: "application/vnd.oci.blab", 555 Annotations: map[string]string{ 556 "foo": "bar", 557 "baz": "boo", 558 }, 559 }, 560 } 561 } 562 563 func checkImageTimestamps(t *testing.T, im *images.Image, now time.Time, oncreate bool) { 564 t.Helper() 565 if im.UpdatedAt.IsZero() || im.CreatedAt.IsZero() { 566 t.Fatalf("timestamps not set") 567 } 568 569 if oncreate { 570 if !im.CreatedAt.Equal(im.UpdatedAt) { 571 t.Fatal("timestamps should be equal on create") 572 } 573 574 } else { 575 // ensure that updatedat is always after createdat 576 if !im.UpdatedAt.After(im.CreatedAt) { 577 t.Fatalf("timestamp for updatedat not after createdat: %v <= %v", im.UpdatedAt, im.CreatedAt) 578 } 579 } 580 581 if im.UpdatedAt.Before(now) { 582 t.Fatal("createdat time incorrect should be after the start of the operation") 583 } 584 } 585 586 func checkImagesEqual(t *testing.T, a, b *images.Image, format string, args ...interface{}) { 587 t.Helper() 588 if !reflect.DeepEqual(a, b) { 589 t.Fatalf("images not equal \n\t%v != \n\t%v: "+format, append([]interface{}{a, b}, args...)...) 590 } 591 }