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