github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/content/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 "bytes" 21 "context" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "math/rand" 26 "os" 27 "runtime" 28 "sync/atomic" 29 "testing" 30 "time" 31 32 "github.com/containerd/containerd/content" 33 "github.com/containerd/containerd/errdefs" 34 "github.com/containerd/containerd/log/logtest" 35 "github.com/containerd/containerd/pkg/testutil" 36 digest "github.com/opencontainers/go-digest" 37 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 38 "github.com/pkg/errors" 39 "gotest.tools/v3/assert" 40 ) 41 42 const ( 43 emptyDigest = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 44 ) 45 46 // StoreInitFn initializes content store with given root and returns a function for 47 // destroying the content store 48 type StoreInitFn func(ctx context.Context, root string) (context.Context, content.Store, func() error, error) 49 50 // ContentSuite runs a test suite on the content store given a factory function. 51 func ContentSuite(t *testing.T, name string, storeFn StoreInitFn) { 52 t.Run("Writer", makeTest(t, name, storeFn, checkContentStoreWriter)) 53 t.Run("UpdateStatus", makeTest(t, name, storeFn, checkUpdateStatus)) 54 t.Run("CommitExists", makeTest(t, name, storeFn, checkCommitExists)) 55 t.Run("Resume", makeTest(t, name, storeFn, checkResumeWriter)) 56 t.Run("ResumeTruncate", makeTest(t, name, storeFn, checkResume(resumeTruncate))) 57 t.Run("ResumeDiscard", makeTest(t, name, storeFn, checkResume(resumeDiscard))) 58 t.Run("ResumeCopy", makeTest(t, name, storeFn, checkResume(resumeCopy))) 59 t.Run("ResumeCopySeeker", makeTest(t, name, storeFn, checkResume(resumeCopySeeker))) 60 t.Run("ResumeCopyReaderAt", makeTest(t, name, storeFn, checkResume(resumeCopyReaderAt))) 61 t.Run("SmallBlob", makeTest(t, name, storeFn, checkSmallBlob)) 62 t.Run("Labels", makeTest(t, name, storeFn, checkLabels)) 63 64 t.Run("CommitErrorState", makeTest(t, name, storeFn, checkCommitErrorState)) 65 } 66 67 // ContentCrossNSSharedSuite runs a test suite under shared content policy 68 func ContentCrossNSSharedSuite(t *testing.T, name string, storeFn StoreInitFn) { 69 t.Run("CrossNamespaceAppend", makeTest(t, name, storeFn, checkCrossNSAppend)) 70 t.Run("CrossNamespaceShare", makeTest(t, name, storeFn, checkCrossNSShare)) 71 } 72 73 // ContentCrossNSIsolatedSuite runs a test suite under isolated content policy 74 func ContentCrossNSIsolatedSuite(t *testing.T, name string, storeFn StoreInitFn) { 75 t.Run("CrossNamespaceIsolate", makeTest(t, name, storeFn, checkCrossNSIsolate)) 76 } 77 78 // ContextWrapper is used to decorate new context used inside the test 79 // before using the context on the content store. 80 // This can be used to support leasing and multiple namespaces tests. 81 type ContextWrapper func(ctx context.Context) (context.Context, func(context.Context) error, error) 82 83 type wrapperKey struct{} 84 85 // SetContextWrapper sets the wrapper on the context for deriving 86 // new test contexts from the context. 87 func SetContextWrapper(ctx context.Context, w ContextWrapper) context.Context { 88 return context.WithValue(ctx, wrapperKey{}, w) 89 } 90 91 type nameKey struct{} 92 93 // Name gets the test name from the context 94 func Name(ctx context.Context) string { 95 name, ok := ctx.Value(nameKey{}).(string) 96 if !ok { 97 return "" 98 } 99 return name 100 } 101 102 func makeTest(t *testing.T, name string, storeFn func(ctx context.Context, root string) (context.Context, content.Store, func() error, error), fn func(ctx context.Context, t *testing.T, cs content.Store)) func(t *testing.T) { 103 return func(t *testing.T) { 104 ctx := context.WithValue(context.Background(), nameKey{}, name) 105 ctx = logtest.WithT(ctx, t) 106 107 tmpDir, err := ioutil.TempDir("", "content-suite-"+name+"-") 108 if err != nil { 109 t.Fatal(err) 110 } 111 defer os.RemoveAll(tmpDir) 112 113 ctx, cs, cleanup, err := storeFn(ctx, tmpDir) 114 if err != nil { 115 t.Fatal(err) 116 } 117 defer func() { 118 if err := cleanup(); err != nil && !t.Failed() { 119 t.Fatalf("Cleanup failed: %+v", err) 120 } 121 }() 122 123 w, ok := ctx.Value(wrapperKey{}).(ContextWrapper) 124 if ok { 125 var done func(context.Context) error 126 ctx, done, err = w(ctx) 127 if err != nil { 128 t.Fatalf("Error wrapping context: %+v", err) 129 } 130 defer func() { 131 if err := done(ctx); err != nil && !t.Failed() { 132 t.Fatalf("Wrapper release failed: %+v", err) 133 } 134 }() 135 } 136 137 defer testutil.DumpDirOnFailure(t, tmpDir) 138 fn(ctx, t, cs) 139 } 140 } 141 142 var labels = map[string]string{ 143 "containerd.io/gc.root": time.Now().UTC().Format(time.RFC3339), 144 } 145 146 func checkContentStoreWriter(ctx context.Context, t *testing.T, cs content.Store) { 147 c1, d1 := createContent(256) 148 w1, err := cs.Writer(ctx, content.WithRef("c1")) 149 if err != nil { 150 t.Fatal(err) 151 } 152 defer w1.Close() 153 154 c2, d2 := createContent(256) 155 w2, err := cs.Writer(ctx, content.WithRef("c2"), content.WithDescriptor(ocispec.Descriptor{Size: int64(len(c2))})) 156 if err != nil { 157 t.Fatal(err) 158 } 159 defer w2.Close() 160 161 c3, d3 := createContent(256) 162 w3, err := cs.Writer(ctx, content.WithRef("c3"), content.WithDescriptor(ocispec.Descriptor{Digest: d3})) 163 if err != nil { 164 t.Fatal(err) 165 } 166 defer w3.Close() 167 168 c4, d4 := createContent(256) 169 w4, err := cs.Writer(ctx, content.WithRef("c4"), content.WithDescriptor(ocispec.Descriptor{Size: int64(len(c4)), Digest: d4})) 170 if err != nil { 171 t.Fatal(err) 172 } 173 defer w4.Close() 174 175 smallbuf := make([]byte, 32) 176 for _, s := range []struct { 177 content []byte 178 digest digest.Digest 179 writer content.Writer 180 }{ 181 { 182 content: c1, 183 digest: d1, 184 writer: w1, 185 }, 186 { 187 content: c2, 188 digest: d2, 189 writer: w2, 190 }, 191 { 192 content: c3, 193 digest: d3, 194 writer: w3, 195 }, 196 { 197 content: c4, 198 digest: d4, 199 writer: w4, 200 }, 201 } { 202 n, err := io.CopyBuffer(s.writer, bytes.NewReader(s.content), smallbuf) 203 if err != nil { 204 t.Fatal(err) 205 } 206 207 if n != int64(len(s.content)) { 208 t.Fatalf("Unexpected copy length %d, expected %d", n, len(s.content)) 209 } 210 211 preCommit := time.Now() 212 if err := s.writer.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil { 213 t.Fatal(err) 214 } 215 postCommit := time.Now() 216 217 if s.writer.Digest() != s.digest { 218 t.Fatalf("Unexpected commit digest %s, expected %s", s.writer.Digest(), s.digest) 219 } 220 221 info := content.Info{ 222 Digest: s.digest, 223 Size: int64(len(s.content)), 224 Labels: labels, 225 } 226 if err := checkInfo(ctx, cs, s.digest, info, preCommit, postCommit, preCommit, postCommit); err != nil { 227 t.Fatalf("Check info failed: %+v", err) 228 } 229 } 230 } 231 232 func checkResumeWriter(ctx context.Context, t *testing.T, cs content.Store) { 233 checkWrite := func(t *testing.T, w io.Writer, p []byte) { 234 t.Helper() 235 n, err := w.Write(p) 236 if err != nil { 237 t.Fatal(err) 238 } 239 240 if n != len(p) { 241 t.Fatal("short write to content store") 242 } 243 } 244 245 var ( 246 ref = "cb" 247 cb, dgst = createContent(256) 248 first, second = cb[:128], cb[128:] 249 ) 250 251 preStart := time.Now() 252 w1, err := cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: dgst})) 253 if err != nil { 254 t.Fatal(err) 255 } 256 postStart := time.Now() 257 preUpdate := postStart 258 259 checkWrite(t, w1, first) 260 postUpdate := time.Now() 261 262 dgstFirst := digest.FromBytes(first) 263 expected := content.Status{ 264 Ref: ref, 265 Offset: int64(len(first)), 266 Total: int64(len(cb)), 267 Expected: dgstFirst, 268 } 269 270 checkStatus(t, w1, expected, dgstFirst, preStart, postStart, preUpdate, postUpdate) 271 assert.NilError(t, w1.Close(), "close first writer") 272 273 w2, err := cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: dgst})) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 // status should be consistent with version before close. 279 checkStatus(t, w2, expected, dgstFirst, preStart, postStart, preUpdate, postUpdate) 280 281 preUpdate = time.Now() 282 checkWrite(t, w2, second) 283 postUpdate = time.Now() 284 285 expected.Offset = expected.Total 286 expected.Expected = dgst 287 checkStatus(t, w2, expected, dgst, preStart, postStart, preUpdate, postUpdate) 288 289 preCommit := time.Now() 290 if err := w2.Commit(ctx, 0, ""); err != nil { 291 t.Fatalf("commit failed: %+v", err) 292 } 293 postCommit := time.Now() 294 295 assert.NilError(t, w2.Close(), "close second writer") 296 info := content.Info{ 297 Digest: dgst, 298 Size: 256, 299 } 300 301 if err := checkInfo(ctx, cs, dgst, info, preCommit, postCommit, preCommit, postCommit); err != nil { 302 t.Fatalf("Check info failed: %+v", err) 303 } 304 } 305 306 func checkCommitExists(ctx context.Context, t *testing.T, cs content.Store) { 307 c1, d1 := createContent(256) 308 if err := content.WriteBlob(ctx, cs, "c1", bytes.NewReader(c1), ocispec.Descriptor{Digest: d1}); err != nil { 309 t.Fatal(err) 310 } 311 312 for i, tc := range []struct { 313 expected digest.Digest 314 }{ 315 { 316 expected: d1, 317 }, 318 {}, 319 } { 320 w, err := cs.Writer(ctx, content.WithRef(fmt.Sprintf("c1-commitexists-%d", i))) 321 if err != nil { 322 t.Fatal(err) 323 } 324 if _, err := w.Write(c1); err != nil { 325 w.Close() 326 t.Fatal(err) 327 } 328 err = w.Commit(ctx, int64(len(c1)), tc.expected) 329 w.Close() 330 if err == nil { 331 t.Errorf("(%d) Expected already exists error", i) 332 } else if !errdefs.IsAlreadyExists(err) { 333 t.Fatalf("(%d) Unexpected error: %+v", i, err) 334 } 335 } 336 } 337 338 func checkRefNotAvailable(ctx context.Context, t *testing.T, cs content.Store, ref string) { 339 t.Helper() 340 341 w, err := cs.Writer(ctx, content.WithRef(ref)) 342 if err == nil { 343 defer w.Close() 344 t.Fatal("writer created with ref, expected to be in use") 345 } 346 if !errdefs.IsUnavailable(err) { 347 t.Fatalf("Expected unavailable error, got %+v", err) 348 } 349 } 350 351 func checkCommitErrorState(ctx context.Context, t *testing.T, cs content.Store) { 352 c1, d1 := createContent(256) 353 _, d2 := createContent(256) 354 if err := content.WriteBlob(ctx, cs, "c1", bytes.NewReader(c1), ocispec.Descriptor{Digest: d1}); err != nil { 355 t.Fatal(err) 356 } 357 358 ref := "c1-commiterror-state" 359 w, err := cs.Writer(ctx, content.WithRef(ref)) 360 if err != nil { 361 t.Fatal(err) 362 } 363 if _, err := w.Write(c1); err != nil { 364 if err := w.Close(); err != nil { 365 t.Errorf("Close error: %+v", err) 366 } 367 t.Fatal(err) 368 } 369 370 checkRefNotAvailable(ctx, t, cs, ref) 371 372 // Check exists 373 err = w.Commit(ctx, int64(len(c1)), d1) 374 if err == nil { 375 t.Fatalf("Expected already exists error") 376 } else if !errdefs.IsAlreadyExists(err) { 377 if err := w.Close(); err != nil { 378 t.Errorf("Close error: %+v", err) 379 } 380 t.Fatalf("Unexpected error: %+v", err) 381 } 382 383 w, err = cs.Writer(ctx, content.WithRef(ref)) 384 if err != nil { 385 t.Fatal(err) 386 } 387 388 checkRefNotAvailable(ctx, t, cs, ref) 389 390 if _, err := w.Write(c1); err != nil { 391 if err := w.Close(); err != nil { 392 t.Errorf("close error: %+v", err) 393 } 394 t.Fatal(err) 395 } 396 397 // Check exists without providing digest 398 err = w.Commit(ctx, int64(len(c1)), "") 399 if err == nil { 400 t.Fatalf("Expected already exists error") 401 } else if !errdefs.IsAlreadyExists(err) { 402 if err := w.Close(); err != nil { 403 t.Errorf("Close error: %+v", err) 404 } 405 t.Fatalf("Unexpected error: %+v", err) 406 } 407 w.Close() 408 409 w, err = cs.Writer(ctx, content.WithRef(ref)) 410 if err != nil { 411 t.Fatal(err) 412 } 413 414 checkRefNotAvailable(ctx, t, cs, ref) 415 416 if _, err := w.Write(append(c1, []byte("more")...)); err != nil { 417 if err := w.Close(); err != nil { 418 t.Errorf("close error: %+v", err) 419 } 420 t.Fatal(err) 421 } 422 423 // Commit with the wrong digest should produce an error 424 err = w.Commit(ctx, int64(len(c1))+4, d2) 425 if err == nil { 426 t.Fatalf("Expected error from wrong digest") 427 } else if !errdefs.IsFailedPrecondition(err) { 428 t.Errorf("Unexpected error: %+v", err) 429 } 430 431 w.Close() 432 w, err = cs.Writer(ctx, content.WithRef(ref)) 433 if err != nil { 434 t.Fatal(err) 435 } 436 437 checkRefNotAvailable(ctx, t, cs, ref) 438 439 // Commit with wrong size should also produce an error 440 err = w.Commit(ctx, int64(len(c1)), "") 441 if err == nil { 442 t.Fatalf("Expected error from wrong size") 443 } else if !errdefs.IsFailedPrecondition(err) { 444 t.Errorf("Unexpected error: %+v", err) 445 } 446 447 w.Close() 448 w, err = cs.Writer(ctx, content.WithRef(ref)) 449 if err != nil { 450 t.Fatal(err) 451 } 452 453 checkRefNotAvailable(ctx, t, cs, ref) 454 455 // Now expect commit to succeed 456 if err := w.Commit(ctx, int64(len(c1))+4, ""); err != nil { 457 if err := w.Close(); err != nil { 458 t.Errorf("close error: %+v", err) 459 } 460 t.Fatalf("Failed to commit: %+v", err) 461 } 462 463 w.Close() 464 // Create another writer with same reference 465 w, err = cs.Writer(ctx, content.WithRef(ref)) 466 if err != nil { 467 t.Fatalf("Failed to open writer: %+v", err) 468 } 469 470 if _, err := w.Write(c1); err != nil { 471 if err := w.Close(); err != nil { 472 t.Errorf("close error: %+v", err) 473 } 474 t.Fatal(err) 475 } 476 477 checkRefNotAvailable(ctx, t, cs, ref) 478 479 // Commit should fail due to already exists 480 err = w.Commit(ctx, int64(len(c1)), d1) 481 if err == nil { 482 t.Fatalf("Expected already exists error") 483 } else if !errdefs.IsAlreadyExists(err) { 484 if err := w.Close(); err != nil { 485 t.Errorf("close error: %+v", err) 486 } 487 t.Fatalf("Unexpected error: %+v", err) 488 } 489 490 w.Close() 491 w, err = cs.Writer(ctx, content.WithRef(ref)) 492 if err != nil { 493 t.Fatal(err) 494 } 495 496 checkRefNotAvailable(ctx, t, cs, ref) 497 498 if err := w.Close(); err != nil { 499 t.Fatalf("Close failed: %+v", err) 500 } 501 502 // Create another writer with same reference to check available 503 w, err = cs.Writer(ctx, content.WithRef(ref)) 504 if err != nil { 505 t.Fatalf("Failed to open writer: %+v", err) 506 } 507 if err := w.Close(); err != nil { 508 t.Fatalf("Close failed: %+v", err) 509 } 510 } 511 512 func checkUpdateStatus(ctx context.Context, t *testing.T, cs content.Store) { 513 c1, d1 := createContent(256) 514 515 preStart := time.Now() 516 w1, err := cs.Writer(ctx, content.WithRef("c1"), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: d1})) 517 if err != nil { 518 t.Fatal(err) 519 } 520 defer w1.Close() 521 postStart := time.Now() 522 523 d := digest.FromBytes([]byte{}) 524 525 expected := content.Status{ 526 Ref: "c1", 527 Total: 256, 528 Expected: d1, 529 } 530 preUpdate := preStart 531 postUpdate := postStart 532 533 checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate) 534 535 // Write first 64 bytes 536 preUpdate = time.Now() 537 if _, err := w1.Write(c1[:64]); err != nil { 538 t.Fatalf("Failed to write: %+v", err) 539 } 540 postUpdate = time.Now() 541 expected.Offset = 64 542 d = digest.FromBytes(c1[:64]) 543 checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate) 544 545 // Write next 128 bytes 546 preUpdate = time.Now() 547 if _, err := w1.Write(c1[64:192]); err != nil { 548 t.Fatalf("Failed to write: %+v", err) 549 } 550 postUpdate = time.Now() 551 expected.Offset = 192 552 d = digest.FromBytes(c1[:192]) 553 checkStatus(t, w1, expected, d, preStart, postStart, preUpdate, postUpdate) 554 555 // Write last 64 bytes 556 preUpdate = time.Now() 557 if _, err := w1.Write(c1[192:]); err != nil { 558 t.Fatalf("Failed to write: %+v", err) 559 } 560 postUpdate = time.Now() 561 expected.Offset = 256 562 checkStatus(t, w1, expected, d1, preStart, postStart, preUpdate, postUpdate) 563 564 preCommit := time.Now() 565 if err := w1.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil { 566 t.Fatalf("Commit failed: %+v", err) 567 } 568 postCommit := time.Now() 569 570 info := content.Info{ 571 Digest: d1, 572 Size: 256, 573 Labels: labels, 574 } 575 576 if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preCommit, postCommit); err != nil { 577 t.Fatalf("Check info failed: %+v", err) 578 } 579 } 580 581 func checkLabels(ctx context.Context, t *testing.T, cs content.Store) { 582 c1, d1 := createContent(256) 583 584 w1, err := cs.Writer(ctx, content.WithRef("c1-checklabels"), content.WithDescriptor(ocispec.Descriptor{Size: 256, Digest: d1})) 585 if err != nil { 586 t.Fatal(err) 587 } 588 defer w1.Close() 589 590 if _, err := w1.Write(c1); err != nil { 591 t.Fatalf("Failed to write: %+v", err) 592 } 593 594 rootTime := time.Now().UTC().Format(time.RFC3339) 595 labels := map[string]string{ 596 "k1": "v1", 597 "k2": "v2", 598 599 "containerd.io/gc.root": rootTime, 600 } 601 602 preCommit := time.Now() 603 if err := w1.Commit(ctx, 0, "", content.WithLabels(labels)); err != nil { 604 t.Fatalf("Commit failed: %+v", err) 605 } 606 postCommit := time.Now() 607 608 info := content.Info{ 609 Digest: d1, 610 Size: 256, 611 Labels: labels, 612 } 613 614 if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preCommit, postCommit); err != nil { 615 t.Fatalf("Check info failed: %+v", err) 616 } 617 618 labels["k1"] = "newvalue" 619 delete(labels, "k2") 620 labels["k3"] = "v3" 621 622 info.Labels = labels 623 preUpdate := time.Now() 624 if _, err := cs.Update(ctx, info); err != nil { 625 t.Fatalf("Update failed: %+v", err) 626 } 627 postUpdate := time.Now() 628 629 if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preUpdate, postUpdate); err != nil { 630 t.Fatalf("Check info failed: %+v", err) 631 } 632 633 info.Labels = map[string]string{ 634 "k1": "v1", 635 636 "containerd.io/gc.root": rootTime, 637 } 638 preUpdate = time.Now() 639 if _, err := cs.Update(ctx, info, "labels.k3", "labels.k1"); err != nil { 640 t.Fatalf("Update failed: %+v", err) 641 } 642 postUpdate = time.Now() 643 644 if err := checkInfo(ctx, cs, d1, info, preCommit, postCommit, preUpdate, postUpdate); err != nil { 645 t.Fatalf("Check info failed: %+v", err) 646 } 647 648 } 649 650 func checkResume(rf func(context.Context, content.Writer, []byte, int64, int64, digest.Digest) error) func(ctx context.Context, t *testing.T, cs content.Store) { 651 return func(ctx context.Context, t *testing.T, cs content.Store) { 652 sizes := []int64{500, 5000, 50000} 653 truncations := []float64{0.0, 0.1, 0.5, 0.9, 1.0} 654 655 for i, size := range sizes { 656 for j, tp := range truncations { 657 b, d := createContent(size) 658 limit := int64(float64(size) * tp) 659 ref := fmt.Sprintf("ref-%d-%d", i, j) 660 661 w, err := cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d})) 662 if err != nil { 663 t.Fatal(err) 664 } 665 666 if _, err := w.Write(b[:limit]); err != nil { 667 w.Close() 668 t.Fatal(err) 669 } 670 671 if err := w.Close(); err != nil { 672 t.Fatal(err) 673 } 674 675 w, err = cs.Writer(ctx, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d})) 676 if err != nil { 677 t.Fatal(err) 678 } 679 680 st, err := w.Status() 681 if err != nil { 682 w.Close() 683 t.Fatal(err) 684 } 685 686 if st.Offset != limit { 687 w.Close() 688 t.Fatalf("Unexpected offset %d, expected %d", st.Offset, limit) 689 } 690 691 preCommit := time.Now() 692 if err := rf(ctx, w, b, limit, size, d); err != nil { 693 t.Fatalf("Resume failed: %+v", err) 694 } 695 postCommit := time.Now() 696 697 if err := w.Close(); err != nil { 698 t.Fatal(err) 699 } 700 701 info := content.Info{ 702 Digest: d, 703 Size: size, 704 } 705 706 if err := checkInfo(ctx, cs, d, info, preCommit, postCommit, preCommit, postCommit); err != nil { 707 t.Fatalf("Check info failed: %+v", err) 708 } 709 } 710 } 711 } 712 } 713 714 func resumeTruncate(ctx context.Context, w content.Writer, b []byte, written, size int64, dgst digest.Digest) error { 715 if err := w.Truncate(0); err != nil { 716 return errors.Wrap(err, "truncate failed") 717 } 718 719 if _, err := io.CopyBuffer(w, bytes.NewReader(b), make([]byte, 1024)); err != nil { 720 return errors.Wrap(err, "write failed") 721 } 722 723 return errors.Wrap(w.Commit(ctx, size, dgst), "commit failed") 724 } 725 726 func resumeDiscard(ctx context.Context, w content.Writer, b []byte, written, size int64, dgst digest.Digest) error { 727 if _, err := io.CopyBuffer(w, bytes.NewReader(b[written:]), make([]byte, 1024)); err != nil { 728 return errors.Wrap(err, "write failed") 729 } 730 return errors.Wrap(w.Commit(ctx, size, dgst), "commit failed") 731 } 732 733 func resumeCopy(ctx context.Context, w content.Writer, b []byte, _, size int64, dgst digest.Digest) error { 734 r := struct { 735 io.Reader 736 }{bytes.NewReader(b)} 737 return errors.Wrap(content.Copy(ctx, w, r, size, dgst), "copy failed") 738 } 739 740 func resumeCopySeeker(ctx context.Context, w content.Writer, b []byte, _, size int64, dgst digest.Digest) error { 741 r := struct { 742 io.ReadSeeker 743 }{bytes.NewReader(b)} 744 return errors.Wrap(content.Copy(ctx, w, r, size, dgst), "copy failed") 745 } 746 747 func resumeCopyReaderAt(ctx context.Context, w content.Writer, b []byte, _, size int64, dgst digest.Digest) error { 748 type readerAt interface { 749 io.Reader 750 io.ReaderAt 751 } 752 r := struct { 753 readerAt 754 }{bytes.NewReader(b)} 755 return errors.Wrap(content.Copy(ctx, w, r, size, dgst), "copy failed") 756 } 757 758 // checkSmallBlob tests reading a blob which is smaller than the read size. 759 func checkSmallBlob(ctx context.Context, t *testing.T, store content.Store) { 760 blob := []byte(`foobar`) 761 blobSize := int64(len(blob)) 762 blobDigest := digest.FromBytes(blob) 763 // test write 764 w, err := store.Writer(ctx, content.WithRef(t.Name()), content.WithDescriptor(ocispec.Descriptor{Size: blobSize, Digest: blobDigest})) 765 if err != nil { 766 t.Fatal(err) 767 } 768 if _, err := w.Write(blob); err != nil { 769 t.Fatal(err) 770 } 771 if err := w.Commit(ctx, blobSize, blobDigest); err != nil { 772 t.Fatal(err) 773 } 774 if err := w.Close(); err != nil { 775 t.Fatal(err) 776 } 777 // test read. 778 readSize := blobSize + 1 779 ra, err := store.ReaderAt(ctx, ocispec.Descriptor{Digest: blobDigest}) 780 if err != nil { 781 t.Fatal(err) 782 } 783 r := io.NewSectionReader(ra, 0, readSize) 784 b, err := ioutil.ReadAll(r) 785 if err != nil { 786 t.Fatal(err) 787 } 788 if err := ra.Close(); err != nil { 789 t.Fatal(err) 790 } 791 d := digest.FromBytes(b) 792 if blobDigest != d { 793 t.Fatalf("expected %s (%q), got %s (%q)", blobDigest, string(blob), 794 d, string(b)) 795 } 796 } 797 798 func checkCrossNSShare(ctx context.Context, t *testing.T, cs content.Store) { 799 wrap, ok := ctx.Value(wrapperKey{}).(ContextWrapper) 800 if !ok { 801 t.Skip("multiple contexts not supported") 802 } 803 804 var size int64 = 1000 805 b, d := createContent(size) 806 ref := fmt.Sprintf("ref-%d", size) 807 t1 := time.Now() 808 809 if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(b), ocispec.Descriptor{Size: size, Digest: d}); err != nil { 810 t.Fatal(err) 811 } 812 813 ctx2, done, err := wrap(context.Background()) 814 if err != nil { 815 t.Fatal(err) 816 } 817 defer done(ctx2) 818 819 w, err := cs.Writer(ctx2, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d})) 820 if err != nil { 821 t.Fatal(err) 822 } 823 t2 := time.Now() 824 825 checkStatus(t, w, content.Status{ 826 Ref: ref, 827 Offset: size, 828 Total: size, 829 }, d, t1, t2, t1, t2) 830 831 if err := w.Commit(ctx2, size, d); err != nil { 832 t.Fatal(err) 833 } 834 t3 := time.Now() 835 836 info := content.Info{ 837 Digest: d, 838 Size: size, 839 } 840 if err := checkContent(ctx, cs, d, info, t1, t3, t1, t3); err != nil { 841 t.Fatal(err) 842 } 843 844 if err := checkContent(ctx2, cs, d, info, t1, t3, t1, t3); err != nil { 845 t.Fatal(err) 846 } 847 } 848 849 func checkCrossNSAppend(ctx context.Context, t *testing.T, cs content.Store) { 850 wrap, ok := ctx.Value(wrapperKey{}).(ContextWrapper) 851 if !ok { 852 t.Skip("multiple contexts not supported") 853 } 854 855 var size int64 = 1000 856 b, d := createContent(size) 857 ref := fmt.Sprintf("ref-%d", size) 858 t1 := time.Now() 859 860 if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(b), ocispec.Descriptor{Size: size, Digest: d}); err != nil { 861 t.Fatal(err) 862 } 863 864 ctx2, done, err := wrap(context.Background()) 865 if err != nil { 866 t.Fatal(err) 867 } 868 defer done(ctx2) 869 870 extra := []byte("appended bytes") 871 size2 := size + int64(len(extra)) 872 b2 := make([]byte, size2) 873 copy(b2[:size], b) 874 copy(b2[size:], extra) 875 d2 := digest.FromBytes(b2) 876 877 w, err := cs.Writer(ctx2, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d})) 878 if err != nil { 879 t.Fatal(err) 880 } 881 t2 := time.Now() 882 883 checkStatus(t, w, content.Status{ 884 Ref: ref, 885 Offset: size, 886 Total: size, 887 }, d, t1, t2, t1, t2) 888 889 if _, err := w.Write(extra); err != nil { 890 t.Fatal(err) 891 } 892 893 if err := w.Commit(ctx2, size2, d2); err != nil { 894 t.Fatal(err) 895 } 896 t3 := time.Now() 897 898 info := content.Info{ 899 Digest: d, 900 Size: size, 901 } 902 if err := checkContent(ctx, cs, d, info, t1, t3, t1, t3); err != nil { 903 t.Fatal(err) 904 } 905 906 info2 := content.Info{ 907 Digest: d2, 908 Size: size2, 909 } 910 if err := checkContent(ctx2, cs, d2, info2, t1, t3, t1, t3); err != nil { 911 t.Fatal(err) 912 } 913 914 } 915 916 func checkCrossNSIsolate(ctx context.Context, t *testing.T, cs content.Store) { 917 wrap, ok := ctx.Value(wrapperKey{}).(ContextWrapper) 918 if !ok { 919 t.Skip("multiple contexts not supported") 920 } 921 922 var size int64 = 1000 923 b, d := createContent(size) 924 ref := fmt.Sprintf("ref-%d", size) 925 t1 := time.Now() 926 927 if err := content.WriteBlob(ctx, cs, ref, bytes.NewReader(b), ocispec.Descriptor{Size: size, Digest: d}); err != nil { 928 t.Fatal(err) 929 } 930 t2 := time.Now() 931 932 ctx2, done, err := wrap(context.Background()) 933 if err != nil { 934 t.Fatal(err) 935 } 936 defer done(ctx2) 937 938 t3 := time.Now() 939 w, err := cs.Writer(ctx2, content.WithRef(ref), content.WithDescriptor(ocispec.Descriptor{Size: size, Digest: d})) 940 if err != nil { 941 t.Fatal(err) 942 } 943 t4 := time.Now() 944 945 checkNewlyCreated(t, w, t1, t2, t3, t4) 946 } 947 948 func checkStatus(t *testing.T, w content.Writer, expected content.Status, d digest.Digest, preStart, postStart, preUpdate, postUpdate time.Time) { 949 t.Helper() 950 st, err := w.Status() 951 if err != nil { 952 t.Fatalf("failed to get status: %v", err) 953 } 954 955 wd := w.Digest() 956 if wd != d { 957 t.Fatalf("unexpected digest %v, expected %v", wd, d) 958 } 959 960 if st.Ref != expected.Ref { 961 t.Fatalf("unexpected ref %q, expected %q", st.Ref, expected.Ref) 962 } 963 964 if st.Offset != expected.Offset { 965 t.Fatalf("unexpected offset %d, expected %d", st.Offset, expected.Offset) 966 } 967 968 if st.Total != expected.Total { 969 t.Fatalf("unexpected total %d, expected %d", st.Total, expected.Total) 970 } 971 972 // TODO: Add this test once all implementations guarantee this value is held 973 //if st.Expected != expected.Expected { 974 // t.Fatalf("unexpected \"expected digest\" %q, expected %q", st.Expected, expected.Expected) 975 //} 976 977 // FIXME: broken on windows: unexpected updated at time 2017-11-14 13:43:22.178013 -0800 PST, 978 // expected between 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300 and 979 // 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300 980 if runtime.GOOS != "windows" { 981 if st.StartedAt.After(postStart) || st.StartedAt.Before(preStart) { 982 t.Fatalf("unexpected started at time %s, expected between %s and %s", st.StartedAt, preStart, postStart) 983 } 984 985 t.Logf("compare update %v against (%v, %v)", st.UpdatedAt, preUpdate, postUpdate) 986 if st.UpdatedAt.After(postUpdate) || st.UpdatedAt.Before(preUpdate) { 987 t.Fatalf("unexpected updated at time %s, expected between %s and %s", st.UpdatedAt, preUpdate, postUpdate) 988 } 989 } 990 } 991 992 func checkNewlyCreated(t *testing.T, w content.Writer, preStart, postStart, preUpdate, postUpdate time.Time) { 993 t.Helper() 994 st, err := w.Status() 995 if err != nil { 996 t.Fatalf("failed to get status: %v", err) 997 } 998 999 wd := w.Digest() 1000 if wd != emptyDigest { 1001 t.Fatalf("unexpected digest %v, expected %v", wd, emptyDigest) 1002 } 1003 1004 if st.Offset != 0 { 1005 t.Fatalf("unexpected offset %v", st.Offset) 1006 } 1007 1008 if runtime.GOOS != "windows" { 1009 if st.StartedAt.After(postUpdate) || st.StartedAt.Before(postStart) { 1010 t.Fatalf("unexpected started at time %s, expected between %s and %s", st.StartedAt, postStart, postUpdate) 1011 } 1012 } 1013 } 1014 1015 func checkInfo(ctx context.Context, cs content.Store, d digest.Digest, expected content.Info, c1, c2, u1, u2 time.Time) error { 1016 info, err := cs.Info(ctx, d) 1017 if err != nil { 1018 return errors.Wrap(err, "failed to get info") 1019 } 1020 1021 if info.Digest != d { 1022 return errors.Errorf("unexpected info digest %s, expected %s", info.Digest, d) 1023 } 1024 1025 if info.Size != expected.Size { 1026 return errors.Errorf("unexpected info size %d, expected %d", info.Size, expected.Size) 1027 } 1028 1029 if info.CreatedAt.After(c2) || info.CreatedAt.Before(c1) { 1030 return errors.Errorf("unexpected created at time %s, expected between %s and %s", info.CreatedAt, c1, c2) 1031 } 1032 // FIXME: broken on windows: unexpected updated at time 2017-11-14 13:43:22.178013 -0800 PST, 1033 // expected between 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300 and 1034 // 2017-11-14 13:43:22.1790195 -0800 PST m=+1.022137300 1035 if runtime.GOOS != "windows" && (info.UpdatedAt.After(u2) || info.UpdatedAt.Before(u1)) { 1036 return errors.Errorf("unexpected updated at time %s, expected between %s and %s", info.UpdatedAt, u1, u2) 1037 } 1038 1039 if len(info.Labels) != len(expected.Labels) { 1040 return errors.Errorf("mismatched number of labels\ngot:\n%#v\nexpected:\n%#v", info.Labels, expected.Labels) 1041 } 1042 1043 for k, v := range expected.Labels { 1044 actual := info.Labels[k] 1045 if v != actual { 1046 return errors.Errorf("unexpected value for label %q: %q, expected %q", k, actual, v) 1047 } 1048 } 1049 1050 return nil 1051 } 1052 func checkContent(ctx context.Context, cs content.Store, d digest.Digest, expected content.Info, c1, c2, u1, u2 time.Time) error { 1053 if err := checkInfo(ctx, cs, d, expected, c1, c2, u1, u2); err != nil { 1054 return err 1055 } 1056 1057 b, err := content.ReadBlob(ctx, cs, ocispec.Descriptor{Digest: d}) 1058 if err != nil { 1059 return errors.Wrap(err, "failed to read blob") 1060 } 1061 1062 if int64(len(b)) != expected.Size { 1063 return errors.Errorf("wrong blob size %d, expected %d", len(b), expected.Size) 1064 } 1065 1066 actual := digest.FromBytes(b) 1067 if actual != d { 1068 return errors.Errorf("wrong digest %s, expected %s", actual, d) 1069 } 1070 1071 return nil 1072 } 1073 1074 var contentSeed int64 1075 1076 func createContent(size int64) ([]byte, digest.Digest) { 1077 // each time we call this, we want to get a different seed, but it should 1078 // be related to the initialization order and fairly consistent between 1079 // test runs. An atomic integer works just good enough for this. 1080 seed := atomic.AddInt64(&contentSeed, 1) 1081 1082 b, err := ioutil.ReadAll(io.LimitReader(rand.New(rand.NewSource(seed)), size)) 1083 if err != nil { 1084 panic(err) 1085 } 1086 return b, digest.FromBytes(b) 1087 }