github.com/npaton/distribution@v2.3.1-rc.0+incompatible/registry/storage/blob_test.go (about) 1 package storage 2 3 import ( 4 "bytes" 5 "crypto/sha256" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "testing" 11 12 "github.com/docker/distribution" 13 "github.com/docker/distribution/context" 14 "github.com/docker/distribution/digest" 15 "github.com/docker/distribution/reference" 16 "github.com/docker/distribution/registry/storage/cache/memory" 17 "github.com/docker/distribution/registry/storage/driver/inmemory" 18 "github.com/docker/distribution/testutil" 19 ) 20 21 // TestSimpleBlobUpload covers the blob upload process, exercising common 22 // error paths that might be seen during an upload. 23 func TestSimpleBlobUpload(t *testing.T) { 24 randomDataReader, dgst, err := testutil.CreateRandomTarFile() 25 if err != nil { 26 t.Fatalf("error creating random reader: %v", err) 27 } 28 29 ctx := context.Background() 30 imageName, _ := reference.ParseNamed("foo/bar") 31 driver := inmemory.New() 32 registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect) 33 if err != nil { 34 t.Fatalf("error creating registry: %v", err) 35 } 36 repository, err := registry.Repository(ctx, imageName) 37 if err != nil { 38 t.Fatalf("unexpected error getting repo: %v", err) 39 } 40 bs := repository.Blobs(ctx) 41 42 h := sha256.New() 43 rd := io.TeeReader(randomDataReader, h) 44 45 blobUpload, err := bs.Create(ctx) 46 47 if err != nil { 48 t.Fatalf("unexpected error starting layer upload: %s", err) 49 } 50 51 // Cancel the upload then restart it 52 if err := blobUpload.Cancel(ctx); err != nil { 53 t.Fatalf("unexpected error during upload cancellation: %v", err) 54 } 55 56 // Do a resume, get unknown upload 57 blobUpload, err = bs.Resume(ctx, blobUpload.ID()) 58 if err != distribution.ErrBlobUploadUnknown { 59 t.Fatalf("unexpected error resuming upload, should be unknown: %v", err) 60 } 61 62 // Restart! 63 blobUpload, err = bs.Create(ctx) 64 if err != nil { 65 t.Fatalf("unexpected error starting layer upload: %s", err) 66 } 67 68 // Get the size of our random tarfile 69 randomDataSize, err := seekerSize(randomDataReader) 70 if err != nil { 71 t.Fatalf("error getting seeker size of random data: %v", err) 72 } 73 74 nn, err := io.Copy(blobUpload, rd) 75 if err != nil { 76 t.Fatalf("unexpected error uploading layer data: %v", err) 77 } 78 79 if nn != randomDataSize { 80 t.Fatalf("layer data write incomplete") 81 } 82 83 offset, err := blobUpload.Seek(0, os.SEEK_CUR) 84 if err != nil { 85 t.Fatalf("unexpected error seeking layer upload: %v", err) 86 } 87 88 if offset != nn { 89 t.Fatalf("blobUpload not updated with correct offset: %v != %v", offset, nn) 90 } 91 blobUpload.Close() 92 93 // Do a resume, for good fun 94 blobUpload, err = bs.Resume(ctx, blobUpload.ID()) 95 if err != nil { 96 t.Fatalf("unexpected error resuming upload: %v", err) 97 } 98 99 sha256Digest := digest.NewDigest("sha256", h) 100 desc, err := blobUpload.Commit(ctx, distribution.Descriptor{Digest: dgst}) 101 if err != nil { 102 t.Fatalf("unexpected error finishing layer upload: %v", err) 103 } 104 105 // After finishing an upload, it should no longer exist. 106 if _, err := bs.Resume(ctx, blobUpload.ID()); err != distribution.ErrBlobUploadUnknown { 107 t.Fatalf("expected layer upload to be unknown, got %v", err) 108 } 109 110 // Test for existence. 111 statDesc, err := bs.Stat(ctx, desc.Digest) 112 if err != nil { 113 t.Fatalf("unexpected error checking for existence: %v, %#v", err, bs) 114 } 115 116 if statDesc != desc { 117 t.Fatalf("descriptors not equal: %v != %v", statDesc, desc) 118 } 119 120 rc, err := bs.Open(ctx, desc.Digest) 121 if err != nil { 122 t.Fatalf("unexpected error opening blob for read: %v", err) 123 } 124 defer rc.Close() 125 126 h.Reset() 127 nn, err = io.Copy(h, rc) 128 if err != nil { 129 t.Fatalf("error reading layer: %v", err) 130 } 131 132 if nn != randomDataSize { 133 t.Fatalf("incorrect read length") 134 } 135 136 if digest.NewDigest("sha256", h) != sha256Digest { 137 t.Fatalf("unexpected digest from uploaded layer: %q != %q", digest.NewDigest("sha256", h), sha256Digest) 138 } 139 140 // Delete a blob 141 err = bs.Delete(ctx, desc.Digest) 142 if err != nil { 143 t.Fatalf("Unexpected error deleting blob") 144 } 145 146 d, err := bs.Stat(ctx, desc.Digest) 147 if err == nil { 148 t.Fatalf("unexpected non-error stating deleted blob: %v", d) 149 } 150 151 switch err { 152 case distribution.ErrBlobUnknown: 153 break 154 default: 155 t.Errorf("Unexpected error type stat-ing deleted manifest: %#v", err) 156 } 157 158 _, err = bs.Open(ctx, desc.Digest) 159 if err == nil { 160 t.Fatalf("unexpected success opening deleted blob for read") 161 } 162 163 switch err { 164 case distribution.ErrBlobUnknown: 165 break 166 default: 167 t.Errorf("Unexpected error type getting deleted manifest: %#v", err) 168 } 169 170 // Re-upload the blob 171 randomBlob, err := ioutil.ReadAll(randomDataReader) 172 if err != nil { 173 t.Fatalf("Error reading all of blob %s", err.Error()) 174 } 175 expectedDigest := digest.FromBytes(randomBlob) 176 simpleUpload(t, bs, randomBlob, expectedDigest) 177 178 d, err = bs.Stat(ctx, expectedDigest) 179 if err != nil { 180 t.Errorf("unexpected error stat-ing blob") 181 } 182 if d.Digest != expectedDigest { 183 t.Errorf("Mismatching digest with restored blob") 184 } 185 186 _, err = bs.Open(ctx, expectedDigest) 187 if err != nil { 188 t.Errorf("Unexpected error opening blob") 189 } 190 191 // Reuse state to test delete with a delete-disabled registry 192 registry, err = NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableRedirect) 193 if err != nil { 194 t.Fatalf("error creating registry: %v", err) 195 } 196 repository, err = registry.Repository(ctx, imageName) 197 if err != nil { 198 t.Fatalf("unexpected error getting repo: %v", err) 199 } 200 bs = repository.Blobs(ctx) 201 err = bs.Delete(ctx, desc.Digest) 202 if err == nil { 203 t.Errorf("Unexpected success deleting while disabled") 204 } 205 } 206 207 // TestSimpleBlobRead just creates a simple blob file and ensures that basic 208 // open, read, seek, read works. More specific edge cases should be covered in 209 // other tests. 210 func TestSimpleBlobRead(t *testing.T) { 211 ctx := context.Background() 212 imageName, _ := reference.ParseNamed("foo/bar") 213 driver := inmemory.New() 214 registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect) 215 if err != nil { 216 t.Fatalf("error creating registry: %v", err) 217 } 218 repository, err := registry.Repository(ctx, imageName) 219 if err != nil { 220 t.Fatalf("unexpected error getting repo: %v", err) 221 } 222 bs := repository.Blobs(ctx) 223 224 randomLayerReader, dgst, err := testutil.CreateRandomTarFile() // TODO(stevvooe): Consider using just a random string. 225 if err != nil { 226 t.Fatalf("error creating random data: %v", err) 227 } 228 229 // Test for existence. 230 desc, err := bs.Stat(ctx, dgst) 231 if err != distribution.ErrBlobUnknown { 232 t.Fatalf("expected not found error when testing for existence: %v", err) 233 } 234 235 rc, err := bs.Open(ctx, dgst) 236 if err != distribution.ErrBlobUnknown { 237 t.Fatalf("expected not found error when opening non-existent blob: %v", err) 238 } 239 240 randomLayerSize, err := seekerSize(randomLayerReader) 241 if err != nil { 242 t.Fatalf("error getting seeker size for random layer: %v", err) 243 } 244 245 descBefore := distribution.Descriptor{Digest: dgst, MediaType: "application/octet-stream", Size: randomLayerSize} 246 t.Logf("desc: %v", descBefore) 247 248 desc, err = addBlob(ctx, bs, descBefore, randomLayerReader) 249 if err != nil { 250 t.Fatalf("error adding blob to blobservice: %v", err) 251 } 252 253 if desc.Size != randomLayerSize { 254 t.Fatalf("committed blob has incorrect length: %v != %v", desc.Size, randomLayerSize) 255 } 256 257 rc, err = bs.Open(ctx, desc.Digest) // note that we are opening with original digest. 258 if err != nil { 259 t.Fatalf("error opening blob with %v: %v", dgst, err) 260 } 261 defer rc.Close() 262 263 // Now check the sha digest and ensure its the same 264 h := sha256.New() 265 nn, err := io.Copy(h, rc) 266 if err != nil { 267 t.Fatalf("unexpected error copying to hash: %v", err) 268 } 269 270 if nn != randomLayerSize { 271 t.Fatalf("stored incorrect number of bytes in blob: %d != %d", nn, randomLayerSize) 272 } 273 274 sha256Digest := digest.NewDigest("sha256", h) 275 if sha256Digest != desc.Digest { 276 t.Fatalf("fetched digest does not match: %q != %q", sha256Digest, desc.Digest) 277 } 278 279 // Now seek back the blob, read the whole thing and check against randomLayerData 280 offset, err := rc.Seek(0, os.SEEK_SET) 281 if err != nil { 282 t.Fatalf("error seeking blob: %v", err) 283 } 284 285 if offset != 0 { 286 t.Fatalf("seek failed: expected 0 offset, got %d", offset) 287 } 288 289 p, err := ioutil.ReadAll(rc) 290 if err != nil { 291 t.Fatalf("error reading all of blob: %v", err) 292 } 293 294 if len(p) != int(randomLayerSize) { 295 t.Fatalf("blob data read has different length: %v != %v", len(p), randomLayerSize) 296 } 297 298 // Reset the randomLayerReader and read back the buffer 299 _, err = randomLayerReader.Seek(0, os.SEEK_SET) 300 if err != nil { 301 t.Fatalf("error resetting layer reader: %v", err) 302 } 303 304 randomLayerData, err := ioutil.ReadAll(randomLayerReader) 305 if err != nil { 306 t.Fatalf("random layer read failed: %v", err) 307 } 308 309 if !bytes.Equal(p, randomLayerData) { 310 t.Fatalf("layer data not equal") 311 } 312 } 313 314 // TestBlobMount covers the blob mount process, exercising common 315 // error paths that might be seen during a mount. 316 func TestBlobMount(t *testing.T) { 317 randomDataReader, dgst, err := testutil.CreateRandomTarFile() 318 if err != nil { 319 t.Fatalf("error creating random reader: %v", err) 320 } 321 322 ctx := context.Background() 323 imageName, _ := reference.ParseNamed("foo/bar") 324 sourceImageName, _ := reference.ParseNamed("foo/source") 325 driver := inmemory.New() 326 registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect) 327 if err != nil { 328 t.Fatalf("error creating registry: %v", err) 329 } 330 331 repository, err := registry.Repository(ctx, imageName) 332 if err != nil { 333 t.Fatalf("unexpected error getting repo: %v", err) 334 } 335 sourceRepository, err := registry.Repository(ctx, sourceImageName) 336 if err != nil { 337 t.Fatalf("unexpected error getting repo: %v", err) 338 } 339 340 sbs := sourceRepository.Blobs(ctx) 341 342 blobUpload, err := sbs.Create(ctx) 343 344 if err != nil { 345 t.Fatalf("unexpected error starting layer upload: %s", err) 346 } 347 348 // Get the size of our random tarfile 349 randomDataSize, err := seekerSize(randomDataReader) 350 if err != nil { 351 t.Fatalf("error getting seeker size of random data: %v", err) 352 } 353 354 nn, err := io.Copy(blobUpload, randomDataReader) 355 if err != nil { 356 t.Fatalf("unexpected error uploading layer data: %v", err) 357 } 358 359 desc, err := blobUpload.Commit(ctx, distribution.Descriptor{Digest: dgst}) 360 if err != nil { 361 t.Fatalf("unexpected error finishing layer upload: %v", err) 362 } 363 364 // Test for existence. 365 statDesc, err := sbs.Stat(ctx, desc.Digest) 366 if err != nil { 367 t.Fatalf("unexpected error checking for existence: %v, %#v", err, sbs) 368 } 369 370 if statDesc != desc { 371 t.Fatalf("descriptors not equal: %v != %v", statDesc, desc) 372 } 373 374 bs := repository.Blobs(ctx) 375 // Test destination for existence. 376 statDesc, err = bs.Stat(ctx, desc.Digest) 377 if err == nil { 378 t.Fatalf("unexpected non-error stating unmounted blob: %v", desc) 379 } 380 381 canonicalRef, err := reference.WithDigest(sourceRepository.Name(), desc.Digest) 382 if err != nil { 383 t.Fatal(err) 384 } 385 386 bw, err := bs.Create(ctx, WithMountFrom(canonicalRef)) 387 if bw != nil { 388 t.Fatal("unexpected blobwriter returned from Create call, should mount instead") 389 } 390 391 ebm, ok := err.(distribution.ErrBlobMounted) 392 if !ok { 393 t.Fatalf("unexpected error mounting layer: %v", err) 394 } 395 396 if ebm.Descriptor != desc { 397 t.Fatalf("descriptors not equal: %v != %v", ebm.Descriptor, desc) 398 } 399 400 // Test for existence. 401 statDesc, err = bs.Stat(ctx, desc.Digest) 402 if err != nil { 403 t.Fatalf("unexpected error checking for existence: %v, %#v", err, bs) 404 } 405 406 if statDesc != desc { 407 t.Fatalf("descriptors not equal: %v != %v", statDesc, desc) 408 } 409 410 rc, err := bs.Open(ctx, desc.Digest) 411 if err != nil { 412 t.Fatalf("unexpected error opening blob for read: %v", err) 413 } 414 defer rc.Close() 415 416 h := sha256.New() 417 nn, err = io.Copy(h, rc) 418 if err != nil { 419 t.Fatalf("error reading layer: %v", err) 420 } 421 422 if nn != randomDataSize { 423 t.Fatalf("incorrect read length") 424 } 425 426 if digest.NewDigest("sha256", h) != dgst { 427 t.Fatalf("unexpected digest from uploaded layer: %q != %q", digest.NewDigest("sha256", h), dgst) 428 } 429 430 // Delete the blob from the source repo 431 err = sbs.Delete(ctx, desc.Digest) 432 if err != nil { 433 t.Fatalf("Unexpected error deleting blob") 434 } 435 436 d, err := bs.Stat(ctx, desc.Digest) 437 if err != nil { 438 t.Fatalf("unexpected error stating blob deleted from source repository: %v", err) 439 } 440 441 d, err = sbs.Stat(ctx, desc.Digest) 442 if err == nil { 443 t.Fatalf("unexpected non-error stating deleted blob: %v", d) 444 } 445 446 switch err { 447 case distribution.ErrBlobUnknown: 448 break 449 default: 450 t.Errorf("Unexpected error type stat-ing deleted manifest: %#v", err) 451 } 452 453 // Delete the blob from the dest repo 454 err = bs.Delete(ctx, desc.Digest) 455 if err != nil { 456 t.Fatalf("Unexpected error deleting blob") 457 } 458 459 d, err = bs.Stat(ctx, desc.Digest) 460 if err == nil { 461 t.Fatalf("unexpected non-error stating deleted blob: %v", d) 462 } 463 464 switch err { 465 case distribution.ErrBlobUnknown: 466 break 467 default: 468 t.Errorf("Unexpected error type stat-ing deleted manifest: %#v", err) 469 } 470 } 471 472 // TestLayerUploadZeroLength uploads zero-length 473 func TestLayerUploadZeroLength(t *testing.T) { 474 ctx := context.Background() 475 imageName, _ := reference.ParseNamed("foo/bar") 476 driver := inmemory.New() 477 registry, err := NewRegistry(ctx, driver, BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), EnableDelete, EnableRedirect) 478 if err != nil { 479 t.Fatalf("error creating registry: %v", err) 480 } 481 repository, err := registry.Repository(ctx, imageName) 482 if err != nil { 483 t.Fatalf("unexpected error getting repo: %v", err) 484 } 485 bs := repository.Blobs(ctx) 486 487 simpleUpload(t, bs, []byte{}, digest.DigestSha256EmptyTar) 488 } 489 490 func simpleUpload(t *testing.T, bs distribution.BlobIngester, blob []byte, expectedDigest digest.Digest) { 491 ctx := context.Background() 492 wr, err := bs.Create(ctx) 493 if err != nil { 494 t.Fatalf("unexpected error starting upload: %v", err) 495 } 496 497 nn, err := io.Copy(wr, bytes.NewReader(blob)) 498 if err != nil { 499 t.Fatalf("error copying into blob writer: %v", err) 500 } 501 502 if nn != 0 { 503 t.Fatalf("unexpected number of bytes copied: %v > 0", nn) 504 } 505 506 dgst, err := digest.FromReader(bytes.NewReader(blob)) 507 if err != nil { 508 t.Fatalf("error getting digest: %v", err) 509 } 510 511 if dgst != expectedDigest { 512 // sanity check on zero digest 513 t.Fatalf("digest not as expected: %v != %v", dgst, expectedDigest) 514 } 515 516 desc, err := wr.Commit(ctx, distribution.Descriptor{Digest: dgst}) 517 if err != nil { 518 t.Fatalf("unexpected error committing write: %v", err) 519 } 520 521 if desc.Digest != dgst { 522 t.Fatalf("unexpected digest: %v != %v", desc.Digest, dgst) 523 } 524 } 525 526 // seekerSize seeks to the end of seeker, checks the size and returns it to 527 // the original state, returning the size. The state of the seeker should be 528 // treated as unknown if an error is returned. 529 func seekerSize(seeker io.ReadSeeker) (int64, error) { 530 current, err := seeker.Seek(0, os.SEEK_CUR) 531 if err != nil { 532 return 0, err 533 } 534 535 end, err := seeker.Seek(0, os.SEEK_END) 536 if err != nil { 537 return 0, err 538 } 539 540 resumed, err := seeker.Seek(current, os.SEEK_SET) 541 if err != nil { 542 return 0, err 543 } 544 545 if resumed != current { 546 return 0, fmt.Errorf("error returning seeker to original state, could not seek back to original location") 547 } 548 549 return end, nil 550 } 551 552 // addBlob simply consumes the reader and inserts into the blob service, 553 // returning a descriptor on success. 554 func addBlob(ctx context.Context, bs distribution.BlobIngester, desc distribution.Descriptor, rd io.Reader) (distribution.Descriptor, error) { 555 wr, err := bs.Create(ctx) 556 if err != nil { 557 return distribution.Descriptor{}, err 558 } 559 defer wr.Cancel(ctx) 560 561 if nn, err := io.Copy(wr, rd); err != nil { 562 return distribution.Descriptor{}, err 563 } else if nn != desc.Size { 564 return distribution.Descriptor{}, fmt.Errorf("incorrect number of bytes copied: %v != %v", nn, desc.Size) 565 } 566 567 return wr.Commit(ctx, desc) 568 }