github.com/npaton/distribution@v2.3.1-rc.0+incompatible/registry/proxy/proxymanifeststore_test.go (about)

     1  package proxy
     2  
     3  import (
     4  	"io"
     5  	"sync"
     6  	"testing"
     7  
     8  	"github.com/docker/distribution"
     9  	"github.com/docker/distribution/context"
    10  	"github.com/docker/distribution/digest"
    11  	"github.com/docker/distribution/manifest"
    12  	"github.com/docker/distribution/manifest/schema1"
    13  	"github.com/docker/distribution/reference"
    14  	"github.com/docker/distribution/registry/client/auth"
    15  	"github.com/docker/distribution/registry/proxy/scheduler"
    16  	"github.com/docker/distribution/registry/storage"
    17  	"github.com/docker/distribution/registry/storage/cache/memory"
    18  	"github.com/docker/distribution/registry/storage/driver/inmemory"
    19  	"github.com/docker/distribution/testutil"
    20  	"github.com/docker/libtrust"
    21  )
    22  
    23  type statsManifest struct {
    24  	manifests distribution.ManifestService
    25  	stats     map[string]int
    26  }
    27  
    28  type manifestStoreTestEnv struct {
    29  	manifestDigest digest.Digest // digest of the signed manifest in the local storage
    30  	manifests      proxyManifestStore
    31  }
    32  
    33  func (te manifestStoreTestEnv) LocalStats() *map[string]int {
    34  	ls := te.manifests.localManifests.(statsManifest).stats
    35  	return &ls
    36  }
    37  
    38  func (te manifestStoreTestEnv) RemoteStats() *map[string]int {
    39  	rs := te.manifests.remoteManifests.(statsManifest).stats
    40  	return &rs
    41  }
    42  
    43  func (sm statsManifest) Delete(ctx context.Context, dgst digest.Digest) error {
    44  	sm.stats["delete"]++
    45  	return sm.manifests.Delete(ctx, dgst)
    46  }
    47  
    48  func (sm statsManifest) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
    49  	sm.stats["exists"]++
    50  	return sm.manifests.Exists(ctx, dgst)
    51  }
    52  
    53  func (sm statsManifest) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
    54  	sm.stats["get"]++
    55  	return sm.manifests.Get(ctx, dgst)
    56  }
    57  
    58  func (sm statsManifest) Put(ctx context.Context, manifest distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
    59  	sm.stats["put"]++
    60  	return sm.manifests.Put(ctx, manifest)
    61  }
    62  
    63  /*func (sm statsManifest) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) {
    64  	sm.stats["enumerate"]++
    65  	return sm.manifests.Enumerate(ctx, manifests, last)
    66  }
    67  */
    68  
    69  type mockChallenger struct {
    70  	sync.Mutex
    71  	count int
    72  }
    73  
    74  // Called for remote operations only
    75  func (m *mockChallenger) tryEstablishChallenges(context.Context) error {
    76  	m.Lock()
    77  	defer m.Unlock()
    78  
    79  	m.count++
    80  	return nil
    81  }
    82  
    83  func (m *mockChallenger) credentialStore() auth.CredentialStore {
    84  	return nil
    85  }
    86  
    87  func (m *mockChallenger) challengeManager() auth.ChallengeManager {
    88  	return nil
    89  }
    90  
    91  func newManifestStoreTestEnv(t *testing.T, name, tag string) *manifestStoreTestEnv {
    92  	nameRef, err := reference.ParseNamed(name)
    93  	if err != nil {
    94  		t.Fatalf("unable to parse reference: %s", err)
    95  	}
    96  
    97  	ctx := context.Background()
    98  	truthRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()))
    99  	if err != nil {
   100  		t.Fatalf("error creating registry: %v", err)
   101  	}
   102  	truthRepo, err := truthRegistry.Repository(ctx, nameRef)
   103  	if err != nil {
   104  		t.Fatalf("unexpected error getting repo: %v", err)
   105  	}
   106  	tr, err := truthRepo.Manifests(ctx)
   107  	if err != nil {
   108  		t.Fatal(err.Error())
   109  	}
   110  	truthManifests := statsManifest{
   111  		manifests: tr,
   112  		stats:     make(map[string]int),
   113  	}
   114  
   115  	manifestDigest, err := populateRepo(t, ctx, truthRepo, name, tag)
   116  	if err != nil {
   117  		t.Fatalf(err.Error())
   118  	}
   119  
   120  	localRegistry, err := storage.NewRegistry(ctx, inmemory.New(), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption)
   121  	if err != nil {
   122  		t.Fatalf("error creating registry: %v", err)
   123  	}
   124  	localRepo, err := localRegistry.Repository(ctx, nameRef)
   125  	if err != nil {
   126  		t.Fatalf("unexpected error getting repo: %v", err)
   127  	}
   128  	lr, err := localRepo.Manifests(ctx)
   129  	if err != nil {
   130  		t.Fatal(err.Error())
   131  	}
   132  
   133  	localManifests := statsManifest{
   134  		manifests: lr,
   135  		stats:     make(map[string]int),
   136  	}
   137  
   138  	s := scheduler.New(ctx, inmemory.New(), "/scheduler-state.json")
   139  	return &manifestStoreTestEnv{
   140  		manifestDigest: manifestDigest,
   141  		manifests: proxyManifestStore{
   142  			ctx:             ctx,
   143  			localManifests:  localManifests,
   144  			remoteManifests: truthManifests,
   145  			scheduler:       s,
   146  			repositoryName:  nameRef,
   147  			authChallenger:  &mockChallenger{},
   148  		},
   149  	}
   150  }
   151  
   152  func populateRepo(t *testing.T, ctx context.Context, repository distribution.Repository, name, tag string) (digest.Digest, error) {
   153  	m := schema1.Manifest{
   154  		Versioned: manifest.Versioned{
   155  			SchemaVersion: 1,
   156  		},
   157  		Name: name,
   158  		Tag:  tag,
   159  	}
   160  
   161  	for i := 0; i < 2; i++ {
   162  		wr, err := repository.Blobs(ctx).Create(ctx)
   163  		if err != nil {
   164  			t.Fatalf("unexpected error creating test upload: %v", err)
   165  		}
   166  
   167  		rs, ts, err := testutil.CreateRandomTarFile()
   168  		if err != nil {
   169  			t.Fatalf("unexpected error generating test layer file")
   170  		}
   171  		dgst := digest.Digest(ts)
   172  		if _, err := io.Copy(wr, rs); err != nil {
   173  			t.Fatalf("unexpected error copying to upload: %v", err)
   174  		}
   175  
   176  		if _, err := wr.Commit(ctx, distribution.Descriptor{Digest: dgst}); err != nil {
   177  			t.Fatalf("unexpected error finishing upload: %v", err)
   178  		}
   179  	}
   180  
   181  	pk, err := libtrust.GenerateECP256PrivateKey()
   182  	if err != nil {
   183  		t.Fatalf("unexpected error generating private key: %v", err)
   184  	}
   185  
   186  	sm, err := schema1.Sign(&m, pk)
   187  	if err != nil {
   188  		t.Fatalf("error signing manifest: %v", err)
   189  	}
   190  
   191  	ms, err := repository.Manifests(ctx)
   192  	if err != nil {
   193  		t.Fatalf(err.Error())
   194  	}
   195  	dgst, err := ms.Put(ctx, sm)
   196  	if err != nil {
   197  		t.Fatalf("unexpected errors putting manifest: %v", err)
   198  	}
   199  
   200  	return dgst, nil
   201  }
   202  
   203  // TestProxyManifests contains basic acceptance tests
   204  // for the pull-through behavior
   205  func TestProxyManifests(t *testing.T) {
   206  	name := "foo/bar"
   207  	env := newManifestStoreTestEnv(t, name, "latest")
   208  
   209  	localStats := env.LocalStats()
   210  	remoteStats := env.RemoteStats()
   211  
   212  	ctx := context.Background()
   213  	// Stat - must check local and remote
   214  	exists, err := env.manifests.Exists(ctx, env.manifestDigest)
   215  	if err != nil {
   216  		t.Fatalf("Error checking existance")
   217  	}
   218  	if !exists {
   219  		t.Errorf("Unexpected non-existant manifest")
   220  	}
   221  
   222  	if (*localStats)["exists"] != 1 && (*remoteStats)["exists"] != 1 {
   223  		t.Errorf("Unexpected exists count : \n%v \n%v", localStats, remoteStats)
   224  	}
   225  
   226  	if env.manifests.authChallenger.(*mockChallenger).count != 1 {
   227  		t.Fatalf("Expected 1 auth challenge, got %#v", env.manifests.authChallenger)
   228  	}
   229  
   230  	// Get - should succeed and pull manifest into local
   231  	_, err = env.manifests.Get(ctx, env.manifestDigest)
   232  	if err != nil {
   233  		t.Fatal(err)
   234  	}
   235  
   236  	if (*localStats)["get"] != 1 && (*remoteStats)["get"] != 1 {
   237  		t.Errorf("Unexpected get count")
   238  	}
   239  
   240  	if (*localStats)["put"] != 1 {
   241  		t.Errorf("Expected local put")
   242  	}
   243  
   244  	if env.manifests.authChallenger.(*mockChallenger).count != 2 {
   245  		t.Fatalf("Expected 2 auth challenges, got %#v", env.manifests.authChallenger)
   246  	}
   247  
   248  	// Stat - should only go to local
   249  	exists, err = env.manifests.Exists(ctx, env.manifestDigest)
   250  	if err != nil {
   251  		t.Fatal(err)
   252  	}
   253  	if !exists {
   254  		t.Errorf("Unexpected non-existant manifest")
   255  	}
   256  
   257  	if (*localStats)["exists"] != 2 && (*remoteStats)["exists"] != 1 {
   258  		t.Errorf("Unexpected exists count")
   259  	}
   260  
   261  	if env.manifests.authChallenger.(*mockChallenger).count != 2 {
   262  		t.Fatalf("Expected 2 auth challenges, got %#v", env.manifests.authChallenger)
   263  	}
   264  
   265  	// Get proxied - won't require another authchallenge
   266  	_, err = env.manifests.Get(ctx, env.manifestDigest)
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  
   271  	if env.manifests.authChallenger.(*mockChallenger).count != 2 {
   272  		t.Fatalf("Expected 2 auth challenges, got %#v", env.manifests.authChallenger)
   273  	}
   274  
   275  }