github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/proxy/proxyblobstore_test.go (about)

     1  package proxy
     2  
     3  import (
     4  	"io/ioutil"
     5  	"math/rand"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/docker/distribution"
    13  	"github.com/docker/distribution/context"
    14  	"github.com/docker/distribution/digest"
    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/filesystem"
    19  	"github.com/docker/distribution/registry/storage/driver/inmemory"
    20  )
    21  
    22  var sbsMu sync.Mutex
    23  
    24  type statsBlobStore struct {
    25  	stats map[string]int
    26  	blobs distribution.BlobStore
    27  }
    28  
    29  func (sbs statsBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) {
    30  	sbsMu.Lock()
    31  	sbs.stats["put"]++
    32  	sbsMu.Unlock()
    33  
    34  	return sbs.blobs.Put(ctx, mediaType, p)
    35  }
    36  
    37  func (sbs statsBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
    38  	sbsMu.Lock()
    39  	sbs.stats["get"]++
    40  	sbsMu.Unlock()
    41  
    42  	return sbs.blobs.Get(ctx, dgst)
    43  }
    44  
    45  func (sbs statsBlobStore) Create(ctx context.Context) (distribution.BlobWriter, error) {
    46  	sbsMu.Lock()
    47  	sbs.stats["create"]++
    48  	sbsMu.Unlock()
    49  
    50  	return sbs.blobs.Create(ctx)
    51  }
    52  
    53  func (sbs statsBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
    54  	sbsMu.Lock()
    55  	sbs.stats["resume"]++
    56  	sbsMu.Unlock()
    57  
    58  	return sbs.blobs.Resume(ctx, id)
    59  }
    60  
    61  func (sbs statsBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
    62  	sbsMu.Lock()
    63  	sbs.stats["open"]++
    64  	sbsMu.Unlock()
    65  
    66  	return sbs.blobs.Open(ctx, dgst)
    67  }
    68  
    69  func (sbs statsBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
    70  	sbsMu.Lock()
    71  	sbs.stats["serveblob"]++
    72  	sbsMu.Unlock()
    73  
    74  	return sbs.blobs.ServeBlob(ctx, w, r, dgst)
    75  }
    76  
    77  func (sbs statsBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
    78  
    79  	sbsMu.Lock()
    80  	sbs.stats["stat"]++
    81  	sbsMu.Unlock()
    82  
    83  	return sbs.blobs.Stat(ctx, dgst)
    84  }
    85  
    86  func (sbs statsBlobStore) Delete(ctx context.Context, dgst digest.Digest) error {
    87  	sbsMu.Lock()
    88  	sbs.stats["delete"]++
    89  	sbsMu.Unlock()
    90  
    91  	return sbs.blobs.Delete(ctx, dgst)
    92  }
    93  
    94  type testEnv struct {
    95  	numUnique int
    96  	inRemote  []distribution.Descriptor
    97  	store     proxyBlobStore
    98  	ctx       context.Context
    99  }
   100  
   101  func (te *testEnv) LocalStats() *map[string]int {
   102  	sbsMu.Lock()
   103  	ls := te.store.localStore.(statsBlobStore).stats
   104  	sbsMu.Unlock()
   105  	return &ls
   106  }
   107  
   108  func (te *testEnv) RemoteStats() *map[string]int {
   109  	sbsMu.Lock()
   110  	rs := te.store.remoteStore.(statsBlobStore).stats
   111  	sbsMu.Unlock()
   112  	return &rs
   113  }
   114  
   115  // Populate remote store and record the digests
   116  func makeTestEnv(t *testing.T, name string) *testEnv {
   117  	ctx := context.Background()
   118  
   119  	truthDir, err := ioutil.TempDir("", "truth")
   120  	if err != nil {
   121  		t.Fatalf("unable to create tempdir: %s", err)
   122  	}
   123  
   124  	cacheDir, err := ioutil.TempDir("", "cache")
   125  	if err != nil {
   126  		t.Fatalf("unable to create tempdir: %s", err)
   127  	}
   128  
   129  	// todo: create a tempfile area here
   130  	localRegistry, err := storage.NewRegistry(ctx, filesystem.New(truthDir), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()), storage.EnableRedirect, storage.DisableDigestResumption)
   131  	if err != nil {
   132  		t.Fatalf("error creating registry: %v", err)
   133  	}
   134  	localRepo, err := localRegistry.Repository(ctx, name)
   135  	if err != nil {
   136  		t.Fatalf("unexpected error getting repo: %v", err)
   137  	}
   138  
   139  	truthRegistry, err := storage.NewRegistry(ctx, filesystem.New(cacheDir), storage.BlobDescriptorCacheProvider(memory.NewInMemoryBlobDescriptorCacheProvider()))
   140  	if err != nil {
   141  		t.Fatalf("error creating registry: %v", err)
   142  	}
   143  	truthRepo, err := truthRegistry.Repository(ctx, name)
   144  	if err != nil {
   145  		t.Fatalf("unexpected error getting repo: %v", err)
   146  	}
   147  
   148  	truthBlobs := statsBlobStore{
   149  		stats: make(map[string]int),
   150  		blobs: truthRepo.Blobs(ctx),
   151  	}
   152  
   153  	localBlobs := statsBlobStore{
   154  		stats: make(map[string]int),
   155  		blobs: localRepo.Blobs(ctx),
   156  	}
   157  
   158  	s := scheduler.New(ctx, inmemory.New(), "/scheduler-state.json")
   159  
   160  	proxyBlobStore := proxyBlobStore{
   161  		remoteStore: truthBlobs,
   162  		localStore:  localBlobs,
   163  		scheduler:   s,
   164  	}
   165  
   166  	te := &testEnv{
   167  		store: proxyBlobStore,
   168  		ctx:   ctx,
   169  	}
   170  	return te
   171  }
   172  
   173  func makeBlob(size int) []byte {
   174  	blob := make([]byte, size, size)
   175  	for i := 0; i < size; i++ {
   176  		blob[i] = byte('A' + rand.Int()%48)
   177  	}
   178  	return blob
   179  }
   180  
   181  func init() {
   182  	rand.Seed(42)
   183  }
   184  
   185  func perm(m []distribution.Descriptor) []distribution.Descriptor {
   186  	for i := 0; i < len(m); i++ {
   187  		j := rand.Intn(i + 1)
   188  		tmp := m[i]
   189  		m[i] = m[j]
   190  		m[j] = tmp
   191  	}
   192  	return m
   193  }
   194  
   195  func populate(t *testing.T, te *testEnv, blobCount, size, numUnique int) {
   196  	var inRemote []distribution.Descriptor
   197  
   198  	for i := 0; i < numUnique; i++ {
   199  		bytes := makeBlob(size)
   200  		for j := 0; j < blobCount/numUnique; j++ {
   201  			desc, err := te.store.remoteStore.Put(te.ctx, "", bytes)
   202  			if err != nil {
   203  				t.Fatalf("Put in store")
   204  			}
   205  
   206  			inRemote = append(inRemote, desc)
   207  		}
   208  	}
   209  
   210  	te.inRemote = inRemote
   211  	te.numUnique = numUnique
   212  }
   213  
   214  func TestProxyStoreStat(t *testing.T) {
   215  	te := makeTestEnv(t, "foo/bar")
   216  
   217  	remoteBlobCount := 1
   218  	populate(t, te, remoteBlobCount, 10, 1)
   219  
   220  	localStats := te.LocalStats()
   221  	remoteStats := te.RemoteStats()
   222  
   223  	// Stat - touches both stores
   224  	for _, d := range te.inRemote {
   225  		_, err := te.store.Stat(te.ctx, d.Digest)
   226  		if err != nil {
   227  			t.Fatalf("Error stating proxy store")
   228  		}
   229  	}
   230  
   231  	if (*localStats)["stat"] != remoteBlobCount {
   232  		t.Errorf("Unexpected local stat count")
   233  	}
   234  
   235  	if (*remoteStats)["stat"] != remoteBlobCount {
   236  		t.Errorf("Unexpected remote stat count")
   237  	}
   238  }
   239  
   240  func TestProxyStoreServeHighConcurrency(t *testing.T) {
   241  	te := makeTestEnv(t, "foo/bar")
   242  	blobSize := 200
   243  	blobCount := 10
   244  	numUnique := 1
   245  	populate(t, te, blobCount, blobSize, numUnique)
   246  
   247  	numClients := 16
   248  	testProxyStoreServe(t, te, numClients)
   249  }
   250  
   251  func TestProxyStoreServeMany(t *testing.T) {
   252  	te := makeTestEnv(t, "foo/bar")
   253  	blobSize := 200
   254  	blobCount := 10
   255  	numUnique := 4
   256  	populate(t, te, blobCount, blobSize, numUnique)
   257  
   258  	numClients := 4
   259  	testProxyStoreServe(t, te, numClients)
   260  }
   261  
   262  // todo(richardscothern): blobCount must be smaller than num clients
   263  func TestProxyStoreServeBig(t *testing.T) {
   264  	te := makeTestEnv(t, "foo/bar")
   265  
   266  	blobSize := 2 << 20
   267  	blobCount := 4
   268  	numUnique := 2
   269  	populate(t, te, blobCount, blobSize, numUnique)
   270  
   271  	numClients := 4
   272  	testProxyStoreServe(t, te, numClients)
   273  }
   274  
   275  // testProxyStoreServe will create clients to consume all blobs
   276  // populated in the truth store
   277  func testProxyStoreServe(t *testing.T, te *testEnv, numClients int) {
   278  	localStats := te.LocalStats()
   279  	remoteStats := te.RemoteStats()
   280  
   281  	var wg sync.WaitGroup
   282  
   283  	for i := 0; i < numClients; i++ {
   284  		// Serveblob - pulls through blobs
   285  		wg.Add(1)
   286  		go func() {
   287  			defer wg.Done()
   288  			for _, remoteBlob := range te.inRemote {
   289  				w := httptest.NewRecorder()
   290  				r, err := http.NewRequest("GET", "", nil)
   291  				if err != nil {
   292  					t.Fatal(err)
   293  				}
   294  
   295  				err = te.store.ServeBlob(te.ctx, w, r, remoteBlob.Digest)
   296  				if err != nil {
   297  					t.Fatalf(err.Error())
   298  				}
   299  
   300  				bodyBytes := w.Body.Bytes()
   301  				localDigest := digest.FromBytes(bodyBytes)
   302  				if localDigest != remoteBlob.Digest {
   303  					t.Fatalf("Mismatching blob fetch from proxy")
   304  				}
   305  			}
   306  		}()
   307  	}
   308  
   309  	wg.Wait()
   310  
   311  	remoteBlobCount := len(te.inRemote)
   312  	if (*localStats)["stat"] != remoteBlobCount*numClients && (*localStats)["create"] != te.numUnique {
   313  		t.Fatal("Expected: stat:", remoteBlobCount*numClients, "create:", remoteBlobCount)
   314  	}
   315  
   316  	// Wait for any async storage goroutines to finish
   317  	time.Sleep(3 * time.Second)
   318  
   319  	remoteStatCount := (*remoteStats)["stat"]
   320  	remoteOpenCount := (*remoteStats)["open"]
   321  
   322  	// Serveblob - blobs come from local
   323  	for _, dr := range te.inRemote {
   324  		w := httptest.NewRecorder()
   325  		r, err := http.NewRequest("GET", "", nil)
   326  		if err != nil {
   327  			t.Fatal(err)
   328  		}
   329  
   330  		err = te.store.ServeBlob(te.ctx, w, r, dr.Digest)
   331  		if err != nil {
   332  			t.Fatalf(err.Error())
   333  		}
   334  
   335  		dl := digest.FromBytes(w.Body.Bytes())
   336  		if dl != dr.Digest {
   337  			t.Errorf("Mismatching blob fetch from proxy")
   338  		}
   339  	}
   340  
   341  	localStats = te.LocalStats()
   342  	remoteStats = te.RemoteStats()
   343  
   344  	// Ensure remote unchanged
   345  	if (*remoteStats)["stat"] != remoteStatCount && (*remoteStats)["open"] != remoteOpenCount {
   346  		t.Fatalf("unexpected remote stats: %#v", remoteStats)
   347  	}
   348  }