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 }