...

Source file src/github.com/docker/distribution/registry/storage/linkedblobstore_test.go

Documentation: github.com/docker/distribution/registry/storage

     1  package storage
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"reflect"
     8  	"strconv"
     9  	"testing"
    10  
    11  	"github.com/distribution/reference"
    12  	"github.com/docker/distribution"
    13  	"github.com/docker/distribution/testutil"
    14  	"github.com/opencontainers/go-digest"
    15  )
    16  
    17  func TestLinkedBlobStoreCreateWithMountFrom(t *testing.T) {
    18  	fooRepoName, _ := reference.WithName("nm/foo")
    19  	fooEnv := newManifestStoreTestEnv(t, fooRepoName, "thetag")
    20  	ctx := context.Background()
    21  	stats, err := mockRegistry(t, fooEnv.registry)
    22  	if err != nil {
    23  		t.Fatal(err)
    24  	}
    25  
    26  	// Build up some test layers and add them to the manifest, saving the
    27  	// readseekers for upload later.
    28  	testLayers := map[digest.Digest]io.ReadSeeker{}
    29  	for i := 0; i < 2; i++ {
    30  		rs, dgst, err := testutil.CreateRandomTarFile()
    31  		if err != nil {
    32  			t.Fatalf("unexpected error generating test layer file")
    33  		}
    34  
    35  		testLayers[dgst] = rs
    36  	}
    37  
    38  	// upload the layers to foo/bar
    39  	for dgst, rs := range testLayers {
    40  		wr, err := fooEnv.repository.Blobs(fooEnv.ctx).Create(fooEnv.ctx)
    41  		if err != nil {
    42  			t.Fatalf("unexpected error creating test upload: %v", err)
    43  		}
    44  
    45  		if _, err := io.Copy(wr, rs); err != nil {
    46  			t.Fatalf("unexpected error copying to upload: %v", err)
    47  		}
    48  
    49  		if _, err := wr.Commit(fooEnv.ctx, distribution.Descriptor{Digest: dgst}); err != nil {
    50  			t.Fatalf("unexpected error finishing upload: %v", err)
    51  		}
    52  	}
    53  
    54  	// create another repository nm/bar
    55  	barRepoName, _ := reference.WithName("nm/bar")
    56  	barRepo, err := fooEnv.registry.Repository(ctx, barRepoName)
    57  	if err != nil {
    58  		t.Fatalf("unexpected error getting repo: %v", err)
    59  	}
    60  
    61  	// cross-repo mount the test layers into a nm/bar
    62  	for dgst := range testLayers {
    63  		fooCanonical, _ := reference.WithDigest(fooRepoName, dgst)
    64  		option := WithMountFrom(fooCanonical)
    65  		// ensure we can instrospect it
    66  		createOpts := distribution.CreateOptions{}
    67  		if err := option.Apply(&createOpts); err != nil {
    68  			t.Fatalf("failed to apply MountFrom option: %v", err)
    69  		}
    70  		if !createOpts.Mount.ShouldMount || createOpts.Mount.From.String() != fooCanonical.String() {
    71  			t.Fatalf("unexpected create options: %#+v", createOpts.Mount)
    72  		}
    73  
    74  		_, err := barRepo.Blobs(ctx).Create(ctx, WithMountFrom(fooCanonical))
    75  		if err == nil {
    76  			t.Fatalf("unexpected non-error while mounting from %q: %v", fooRepoName.String(), err)
    77  		}
    78  		if _, ok := err.(distribution.ErrBlobMounted); !ok {
    79  			t.Fatalf("expected ErrMountFrom error, not %T: %v", err, err)
    80  		}
    81  	}
    82  	for dgst := range testLayers {
    83  		fooCanonical, _ := reference.WithDigest(fooRepoName, dgst)
    84  		count, exists := stats[fooCanonical.String()]
    85  		if !exists {
    86  			t.Errorf("expected entry %q not found among handled stat calls", fooCanonical.String())
    87  		} else if count != 1 {
    88  			t.Errorf("expected exactly one stat call for entry %q, not %d", fooCanonical.String(), count)
    89  		}
    90  	}
    91  
    92  	clearStats(stats)
    93  
    94  	// create yet another repository nm/baz
    95  	bazRepoName, _ := reference.WithName("nm/baz")
    96  	bazRepo, err := fooEnv.registry.Repository(ctx, bazRepoName)
    97  	if err != nil {
    98  		t.Fatalf("unexpected error getting repo: %v", err)
    99  	}
   100  
   101  	// cross-repo mount them into a nm/baz and provide a prepopulated blob descriptor
   102  	for dgst := range testLayers {
   103  		fooCanonical, _ := reference.WithDigest(fooRepoName, dgst)
   104  		size, err := strconv.ParseInt("0x"+dgst.Hex()[:8], 0, 64)
   105  		if err != nil {
   106  			t.Fatal(err)
   107  		}
   108  		prepolutatedDescriptor := distribution.Descriptor{
   109  			Digest:    dgst,
   110  			Size:      size,
   111  			MediaType: "application/octet-stream",
   112  		}
   113  		_, err = bazRepo.Blobs(ctx).Create(ctx, WithMountFrom(fooCanonical), &statCrossMountCreateOption{
   114  			desc: prepolutatedDescriptor,
   115  		})
   116  		blobMounted, ok := err.(distribution.ErrBlobMounted)
   117  		if !ok {
   118  			t.Errorf("expected ErrMountFrom error, not %T: %v", err, err)
   119  			continue
   120  		}
   121  		if !reflect.DeepEqual(blobMounted.Descriptor, prepolutatedDescriptor) {
   122  			t.Errorf("unexpected descriptor: %#+v != %#+v", blobMounted.Descriptor, prepolutatedDescriptor)
   123  		}
   124  	}
   125  	// this time no stat calls will be made
   126  	if len(stats) != 0 {
   127  		t.Errorf("unexpected number of stats made: %d != %d", len(stats), len(testLayers))
   128  	}
   129  }
   130  
   131  func clearStats(stats map[string]int) {
   132  	for k := range stats {
   133  		delete(stats, k)
   134  	}
   135  }
   136  
   137  // mockRegistry sets a mock blob descriptor service factory that overrides
   138  // statter's Stat method to note each attempt to stat a blob in any repository.
   139  // Returned stats map contains canonical references to blobs with a number of
   140  // attempts.
   141  func mockRegistry(t *testing.T, nm distribution.Namespace) (map[string]int, error) {
   142  	registry, ok := nm.(*registry)
   143  	if !ok {
   144  		return nil, fmt.Errorf("not an expected type of registry: %T", nm)
   145  	}
   146  	stats := make(map[string]int)
   147  
   148  	registry.blobDescriptorServiceFactory = &mockBlobDescriptorServiceFactory{
   149  		t:     t,
   150  		stats: stats,
   151  	}
   152  
   153  	return stats, nil
   154  }
   155  
   156  type mockBlobDescriptorServiceFactory struct {
   157  	t     *testing.T
   158  	stats map[string]int
   159  }
   160  
   161  func (f *mockBlobDescriptorServiceFactory) BlobAccessController(svc distribution.BlobDescriptorService) distribution.BlobDescriptorService {
   162  	return &mockBlobDescriptorService{
   163  		BlobDescriptorService: svc,
   164  		t:                     f.t,
   165  		stats:                 f.stats,
   166  	}
   167  }
   168  
   169  type mockBlobDescriptorService struct {
   170  	distribution.BlobDescriptorService
   171  	t     *testing.T
   172  	stats map[string]int
   173  }
   174  
   175  var _ distribution.BlobDescriptorService = &mockBlobDescriptorService{}
   176  
   177  func (bs *mockBlobDescriptorService) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
   178  	statter, ok := bs.BlobDescriptorService.(*linkedBlobStatter)
   179  	if !ok {
   180  		return distribution.Descriptor{}, fmt.Errorf("unexpected blob descriptor service: %T", bs.BlobDescriptorService)
   181  	}
   182  
   183  	name := statter.repository.Named()
   184  	canonical, err := reference.WithDigest(name, dgst)
   185  	if err != nil {
   186  		return distribution.Descriptor{}, fmt.Errorf("failed to make canonical reference: %v", err)
   187  	}
   188  
   189  	bs.stats[canonical.String()]++
   190  	bs.t.Logf("calling Stat on %s", canonical.String())
   191  
   192  	return bs.BlobDescriptorService.Stat(ctx, dgst)
   193  }
   194  
   195  // statCrossMountCreateOptions ensures the expected options type is passed, and optionally pre-fills the cross-mount stat info
   196  type statCrossMountCreateOption struct {
   197  	desc distribution.Descriptor
   198  }
   199  
   200  var _ distribution.BlobCreateOption = statCrossMountCreateOption{}
   201  
   202  func (f statCrossMountCreateOption) Apply(v interface{}) error {
   203  	opts, ok := v.(*distribution.CreateOptions)
   204  	if !ok {
   205  		return fmt.Errorf("Unexpected create options: %#v", v)
   206  	}
   207  
   208  	if !opts.Mount.ShouldMount {
   209  		return nil
   210  	}
   211  
   212  	opts.Mount.Stat = &f.desc
   213  
   214  	return nil
   215  }
   216  

View as plain text