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  }