2015-07-29 18:12:01 +00:00
package proxy
import (
2017-08-11 22:31:16 +00:00
"context"
2022-11-10 16:15:53 +00:00
"io"
2015-09-18 23:11:35 +00:00
"math/rand"
2015-07-29 18:12:01 +00:00
"net/http"
"net/http/httptest"
2015-09-18 23:11:35 +00:00
"sync"
2015-07-29 18:12:01 +00:00
"testing"
2015-09-18 23:11:35 +00:00
"time"
2015-07-29 18:12:01 +00:00
2020-08-24 11:18:39 +00:00
"github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/registry/proxy/scheduler"
"github.com/distribution/distribution/v3/registry/storage"
"github.com/distribution/distribution/v3/registry/storage/cache/memory"
"github.com/distribution/distribution/v3/registry/storage/driver/filesystem"
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
2023-08-30 15:50:56 +00:00
"github.com/distribution/reference"
2016-12-17 00:28:34 +00:00
"github.com/opencontainers/go-digest"
2015-07-29 18:12:01 +00:00
)
2015-09-18 23:11:35 +00:00
var sbsMu sync . Mutex
2023-08-27 10:06:16 +00:00
var randSource rand . Rand
2015-09-18 23:11:35 +00:00
2015-07-29 18:12:01 +00:00
type statsBlobStore struct {
stats map [ string ] int
blobs distribution . BlobStore
}
func ( sbs statsBlobStore ) Put ( ctx context . Context , mediaType string , p [ ] byte ) ( distribution . Descriptor , error ) {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "put" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return sbs . blobs . Put ( ctx , mediaType , p )
}
func ( sbs statsBlobStore ) Get ( ctx context . Context , dgst digest . Digest ) ( [ ] byte , error ) {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "get" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return sbs . blobs . Get ( ctx , dgst )
}
2016-01-13 19:44:42 +00:00
func ( sbs statsBlobStore ) Create ( ctx context . Context , options ... distribution . BlobCreateOption ) ( distribution . BlobWriter , error ) {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "create" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2016-01-13 19:44:42 +00:00
return sbs . blobs . Create ( ctx , options ... )
2015-07-29 18:12:01 +00:00
}
func ( sbs statsBlobStore ) Resume ( ctx context . Context , id string ) ( distribution . BlobWriter , error ) {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "resume" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return sbs . blobs . Resume ( ctx , id )
}
2022-11-10 16:15:53 +00:00
func ( sbs statsBlobStore ) Open ( ctx context . Context , dgst digest . Digest ) ( io . ReadSeekCloser , error ) {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "open" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return sbs . blobs . Open ( ctx , dgst )
}
func ( sbs statsBlobStore ) ServeBlob ( ctx context . Context , w http . ResponseWriter , r * http . Request , dgst digest . Digest ) error {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "serveblob" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return sbs . blobs . ServeBlob ( ctx , w , r , dgst )
}
func ( sbs statsBlobStore ) Stat ( ctx context . Context , dgst digest . Digest ) ( distribution . Descriptor , error ) {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "stat" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return sbs . blobs . Stat ( ctx , dgst )
}
func ( sbs statsBlobStore ) Delete ( ctx context . Context , dgst digest . Digest ) error {
2015-09-18 23:11:35 +00:00
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
sbs . stats [ "delete" ] ++
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return sbs . blobs . Delete ( ctx , dgst )
}
type testEnv struct {
2015-09-18 23:11:35 +00:00
numUnique int
inRemote [ ] distribution . Descriptor
store proxyBlobStore
ctx context . Context
2015-07-29 18:12:01 +00:00
}
2015-09-18 23:11:35 +00:00
func ( te * testEnv ) LocalStats ( ) * map [ string ] int {
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
ls := te . store . localStore . ( statsBlobStore ) . stats
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return & ls
}
2015-09-18 23:11:35 +00:00
func ( te * testEnv ) RemoteStats ( ) * map [ string ] int {
sbsMu . Lock ( )
2015-07-29 18:12:01 +00:00
rs := te . store . remoteStore . ( statsBlobStore ) . stats
2015-09-18 23:11:35 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
return & rs
}
// Populate remote store and record the digests
2015-09-18 23:11:35 +00:00
func makeTestEnv ( t * testing . T , name string ) * testEnv {
2022-11-02 21:55:22 +00:00
t . Helper ( )
2017-01-14 01:06:03 +00:00
nameRef , err := reference . WithName ( name )
2015-12-15 22:35:23 +00:00
if err != nil {
t . Fatalf ( "unable to parse reference: %s" , err )
}
2015-07-29 18:12:01 +00:00
ctx := context . Background ( )
2022-11-02 21:55:22 +00:00
truthDir := t . TempDir ( )
cacheDir := t . TempDir ( )
2015-09-18 23:11:35 +00:00
2016-04-26 21:36:38 +00:00
localDriver , err := filesystem . FromParameters ( map [ string ] interface { } {
"rootdirectory" : truthDir ,
} )
if err != nil {
t . Fatalf ( "unable to create filesystem driver: %s" , err )
}
2015-09-18 23:11:35 +00:00
// todo: create a tempfile area here
2022-07-13 00:42:48 +00:00
localRegistry , err := storage . NewRegistry ( ctx , localDriver , storage . BlobDescriptorCacheProvider ( memory . NewInMemoryBlobDescriptorCacheProvider ( memory . UnlimitedSize ) ) , storage . EnableRedirect , storage . DisableDigestResumption )
2015-08-18 17:56:27 +00:00
if err != nil {
t . Fatalf ( "error creating registry: %v" , err )
}
2015-12-15 22:35:23 +00:00
localRepo , err := localRegistry . Repository ( ctx , nameRef )
2015-07-29 18:12:01 +00:00
if err != nil {
t . Fatalf ( "unexpected error getting repo: %v" , err )
}
2016-04-26 21:36:38 +00:00
cacheDriver , err := filesystem . FromParameters ( map [ string ] interface { } {
"rootdirectory" : cacheDir ,
} )
if err != nil {
t . Fatalf ( "unable to create filesystem driver: %s" , err )
}
2022-07-13 00:42:48 +00:00
truthRegistry , err := storage . NewRegistry ( ctx , cacheDriver , storage . BlobDescriptorCacheProvider ( memory . NewInMemoryBlobDescriptorCacheProvider ( memory . UnlimitedSize ) ) )
2015-08-18 17:56:27 +00:00
if err != nil {
t . Fatalf ( "error creating registry: %v" , err )
}
2015-12-15 22:35:23 +00:00
truthRepo , err := truthRegistry . Repository ( ctx , nameRef )
2015-07-29 18:12:01 +00:00
if err != nil {
t . Fatalf ( "unexpected error getting repo: %v" , err )
}
truthBlobs := statsBlobStore {
stats : make ( map [ string ] int ) ,
blobs : truthRepo . Blobs ( ctx ) ,
}
localBlobs := statsBlobStore {
stats : make ( map [ string ] int ) ,
blobs : localRepo . Blobs ( ctx ) ,
}
s := scheduler . New ( ctx , inmemory . New ( ) , "/scheduler-state.json" )
proxyBlobStore := proxyBlobStore {
2016-01-27 00:42:10 +00:00
repositoryName : nameRef ,
remoteStore : truthBlobs ,
localStore : localBlobs ,
scheduler : s ,
2016-02-11 02:07:28 +00:00
authChallenger : & mockChallenger { } ,
2015-07-29 18:12:01 +00:00
}
2015-09-18 23:11:35 +00:00
te := & testEnv {
2015-07-29 18:12:01 +00:00
store : proxyBlobStore ,
ctx : ctx ,
}
return te
}
2015-09-18 23:11:35 +00:00
func makeBlob ( size int ) [ ] byte {
2019-02-05 00:01:04 +00:00
blob := make ( [ ] byte , size )
2015-09-18 23:11:35 +00:00
for i := 0 ; i < size ; i ++ {
2023-08-27 10:06:16 +00:00
blob [ i ] = byte ( 'A' + randSource . Int ( ) % 48 )
2015-09-18 23:11:35 +00:00
}
return blob
}
func init ( ) {
2023-08-27 10:06:16 +00:00
randSource = * rand . New ( rand . NewSource ( 42 ) )
2015-09-18 23:11:35 +00:00
}
func populate ( t * testing . T , te * testEnv , blobCount , size , numUnique int ) {
2015-07-29 18:12:01 +00:00
var inRemote [ ] distribution . Descriptor
2015-09-18 23:11:35 +00:00
for i := 0 ; i < numUnique ; i ++ {
bytes := makeBlob ( size )
for j := 0 ; j < blobCount / numUnique ; j ++ {
desc , err := te . store . remoteStore . Put ( te . ctx , "" , bytes )
if err != nil {
t . Fatalf ( "Put in store" )
}
inRemote = append ( inRemote , desc )
2015-07-29 18:12:01 +00:00
}
}
te . inRemote = inRemote
2015-09-18 23:11:35 +00:00
te . numUnique = numUnique
2015-07-29 18:12:01 +00:00
}
2022-11-02 21:05:45 +00:00
2016-02-18 00:32:23 +00:00
func TestProxyStoreGet ( t * testing . T ) {
te := makeTestEnv ( t , "foo/bar" )
localStats := te . LocalStats ( )
remoteStats := te . RemoteStats ( )
populate ( t , te , 1 , 10 , 1 )
_ , err := te . store . Get ( te . ctx , te . inRemote [ 0 ] . Digest )
if err != nil {
t . Fatal ( err )
}
if ( * localStats ) [ "get" ] != 1 && ( * localStats ) [ "put" ] != 1 {
t . Errorf ( "Unexpected local counts" )
}
if ( * remoteStats ) [ "get" ] != 1 {
t . Errorf ( "Unexpected remote get count" )
}
_ , err = te . store . Get ( te . ctx , te . inRemote [ 0 ] . Digest )
if err != nil {
t . Fatal ( err )
}
if ( * localStats ) [ "get" ] != 2 && ( * localStats ) [ "put" ] != 1 {
t . Errorf ( "Unexpected local counts" )
}
if ( * remoteStats ) [ "get" ] != 1 {
t . Errorf ( "Unexpected remote get count" )
}
}
2015-07-29 18:12:01 +00:00
2020-08-28 00:07:35 +00:00
func TestProxyStoreGetWithoutScheduler ( t * testing . T ) {
te := makeTestEnv ( t , "foo/bar" )
te . store . scheduler = nil
populate ( t , te , 1 , 10 , 1 )
_ , err := te . store . Get ( te . ctx , te . inRemote [ 0 ] . Digest )
if err != nil {
t . Fatal ( err )
}
}
2015-07-29 18:12:01 +00:00
func TestProxyStoreStat ( t * testing . T ) {
te := makeTestEnv ( t , "foo/bar" )
2015-09-18 23:11:35 +00:00
2015-07-29 18:12:01 +00:00
remoteBlobCount := 1
2015-09-18 23:11:35 +00:00
populate ( t , te , remoteBlobCount , 10 , 1 )
2015-07-29 18:12:01 +00:00
localStats := te . LocalStats ( )
remoteStats := te . RemoteStats ( )
// Stat - touches both stores
for _ , d := range te . inRemote {
_ , err := te . store . Stat ( te . ctx , d . Digest )
if err != nil {
t . Fatalf ( "Error stating proxy store" )
}
}
if ( * localStats ) [ "stat" ] != remoteBlobCount {
t . Errorf ( "Unexpected local stat count" )
}
if ( * remoteStats ) [ "stat" ] != remoteBlobCount {
t . Errorf ( "Unexpected remote stat count" )
}
2016-02-11 02:07:28 +00:00
if te . store . authChallenger . ( * mockChallenger ) . count != len ( te . inRemote ) {
t . Fatalf ( "Unexpected auth challenge count, got %#v" , te . store . authChallenger )
}
2015-07-29 18:12:01 +00:00
}
2015-09-18 23:11:35 +00:00
func TestProxyStoreServeHighConcurrency ( t * testing . T ) {
2015-07-29 18:12:01 +00:00
te := makeTestEnv ( t , "foo/bar" )
2015-09-18 23:11:35 +00:00
blobSize := 200
blobCount := 10
numUnique := 1
populate ( t , te , blobCount , blobSize , numUnique )
numClients := 16
testProxyStoreServe ( t , te , numClients )
}
2015-07-29 18:12:01 +00:00
2015-09-18 23:11:35 +00:00
func TestProxyStoreServeMany ( t * testing . T ) {
te := makeTestEnv ( t , "foo/bar" )
blobSize := 200
blobCount := 10
numUnique := 4
populate ( t , te , blobCount , blobSize , numUnique )
numClients := 4
testProxyStoreServe ( t , te , numClients )
}
// todo(richardscothern): blobCount must be smaller than num clients
func TestProxyStoreServeBig ( t * testing . T ) {
te := makeTestEnv ( t , "foo/bar" )
2023-10-31 21:53:02 +00:00
blobSize := 2 * 1024 * 1024
2015-09-18 23:11:35 +00:00
blobCount := 4
numUnique := 2
populate ( t , te , blobCount , blobSize , numUnique )
numClients := 4
testProxyStoreServe ( t , te , numClients )
}
2023-09-05 13:47:54 +00:00
func TestProxyStoreServeMetrics ( t * testing . T ) {
te := makeTestEnv ( t , "foo/bar" )
blobSize := 200
blobCount := 10
numUnique := 10
populate ( t , te , blobCount , blobSize , numUnique )
numClients := 1
proxyMetrics = & proxyMetricsCollector { }
testProxyStoreServe ( t , te , numClients )
expected := & proxyMetricsCollector {
blobMetrics : Metrics {
Requests : uint64 ( blobCount * numClients + blobCount ) ,
Hits : uint64 ( blobCount ) ,
Misses : uint64 ( blobCount ) ,
BytesPushed : uint64 ( blobSize * blobCount * numClients + blobSize * blobCount ) ,
BytesPulled : uint64 ( blobSize * blobCount ) ,
} ,
}
if proxyMetrics . blobMetrics . Requests != expected . blobMetrics . Requests {
t . Errorf ( "Expected blobMetrics.Requests %d but got %d" , expected . blobMetrics . Requests , proxyMetrics . blobMetrics . Requests )
}
if proxyMetrics . blobMetrics . Hits != expected . blobMetrics . Hits {
t . Errorf ( "Expected blobMetrics.Hits %d but got %d" , expected . blobMetrics . Hits , proxyMetrics . blobMetrics . Hits )
}
if proxyMetrics . blobMetrics . Misses != expected . blobMetrics . Misses {
t . Errorf ( "Expected blobMetrics.Misses %d but got %d" , expected . blobMetrics . Misses , proxyMetrics . blobMetrics . Misses )
}
if proxyMetrics . blobMetrics . BytesPushed != expected . blobMetrics . BytesPushed {
t . Errorf ( "Expected blobMetrics.BytesPushed %d but got %d" , expected . blobMetrics . BytesPushed , proxyMetrics . blobMetrics . BytesPushed )
}
if proxyMetrics . blobMetrics . BytesPulled != expected . blobMetrics . BytesPulled {
t . Errorf ( "Expected blobMetrics.BytesPulled %d but got %d" , expected . blobMetrics . BytesPulled , proxyMetrics . blobMetrics . BytesPulled )
}
}
func TestProxyStoreServeMetricsConcurrent ( t * testing . T ) {
te := makeTestEnv ( t , "foo/bar" )
blobSize := 200
blobCount := 10
numUnique := 10
populate ( t , te , blobCount , blobSize , numUnique )
numClients := 4
proxyMetrics = & proxyMetricsCollector { }
testProxyStoreServe ( t , te , numClients )
expected := & proxyMetricsCollector {
blobMetrics : Metrics {
Requests : uint64 ( blobCount * numClients + blobCount ) ,
Hits : uint64 ( blobCount ) ,
Misses : uint64 ( blobCount ) ,
BytesPushed : uint64 ( blobSize * blobCount * numClients + blobSize * blobCount ) ,
BytesPulled : uint64 ( blobSize * blobCount ) ,
} ,
}
if proxyMetrics . blobMetrics . Requests != expected . blobMetrics . Requests {
t . Errorf ( "Expected blobMetrics.Requests %d but got %d" , expected . blobMetrics . Requests , proxyMetrics . blobMetrics . Requests )
}
if proxyMetrics . blobMetrics . Hits + proxyMetrics . blobMetrics . Misses != expected . blobMetrics . Requests {
t . Errorf ( "Expected blobMetrics.Hits + blobMetrics.Misses %d but got %d" , expected . blobMetrics . Requests , proxyMetrics . blobMetrics . Hits + proxyMetrics . blobMetrics . Misses )
}
if proxyMetrics . blobMetrics . Hits < expected . blobMetrics . Hits {
t . Errorf ( "Expect blobMetrics.Hits %d to be >= %d" , proxyMetrics . blobMetrics . Hits , expected . blobMetrics . Hits )
}
if proxyMetrics . blobMetrics . Misses < expected . blobMetrics . Misses {
t . Errorf ( "Expect blobMetrics.Misses %d to be >= %d" , proxyMetrics . blobMetrics . Misses , expected . blobMetrics . Misses )
}
if proxyMetrics . blobMetrics . BytesPushed != expected . blobMetrics . BytesPushed {
t . Errorf ( "Expected blobMetrics.BytesPushed %d but got %d" , expected . blobMetrics . BytesPushed , proxyMetrics . blobMetrics . BytesPushed )
}
if proxyMetrics . blobMetrics . BytesPulled < expected . blobMetrics . BytesPulled {
t . Errorf ( "Expect blobMetrics.BytesPulled %d to be >= %d" , proxyMetrics . blobMetrics . BytesPulled , expected . blobMetrics . BytesPulled )
}
if proxyMetrics . blobMetrics . BytesPulled > expected . blobMetrics . BytesPushed - expected . blobMetrics . BytesPulled {
t . Errorf ( "Expect blobMetrics.BytesPulled %d to be <= %d" , proxyMetrics . blobMetrics . BytesPulled , expected . blobMetrics . BytesPushed - expected . blobMetrics . BytesPulled )
}
}
2015-09-18 23:11:35 +00:00
// testProxyStoreServe will create clients to consume all blobs
// populated in the truth store
func testProxyStoreServe ( t * testing . T , te * testEnv , numClients int ) {
2015-07-29 18:12:01 +00:00
localStats := te . LocalStats ( )
remoteStats := te . RemoteStats ( )
2015-09-18 23:11:35 +00:00
var wg sync . WaitGroup
2022-01-20 03:52:34 +00:00
var descHitMap = map [ digest . Digest ] bool { }
var hitLock sync . Mutex
for _ , remoteBlob := range te . inRemote {
descHitMap [ remoteBlob . Digest ] = true
}
2015-09-18 23:11:35 +00:00
for i := 0 ; i < numClients ; i ++ {
// Serveblob - pulls through blobs
wg . Add ( 1 )
go func ( ) {
defer wg . Done ( )
for _ , remoteBlob := range te . inRemote {
w := httptest . NewRecorder ( )
2022-11-02 22:31:23 +00:00
r , err := http . NewRequest ( http . MethodGet , "" , nil )
2015-09-18 23:11:35 +00:00
if err != nil {
2018-08-06 21:34:15 +00:00
t . Error ( err )
return
2015-09-18 23:11:35 +00:00
}
err = te . store . ServeBlob ( te . ctx , w , r , remoteBlob . Digest )
if err != nil {
2018-08-06 21:34:15 +00:00
t . Errorf ( err . Error ( ) )
return
2015-09-18 23:11:35 +00:00
}
bodyBytes := w . Body . Bytes ( )
2015-12-14 22:30:51 +00:00
localDigest := digest . FromBytes ( bodyBytes )
2015-09-18 23:11:35 +00:00
if localDigest != remoteBlob . Digest {
2018-08-06 21:34:15 +00:00
t . Errorf ( "Mismatching blob fetch from proxy" )
return
2015-09-18 23:11:35 +00:00
}
2022-01-20 03:52:34 +00:00
desc , err := te . store . localStore . Stat ( te . ctx , remoteBlob . Digest )
if err != nil {
continue
}
hitLock . Lock ( )
delete ( descHitMap , desc . Digest )
hitLock . Unlock ( )
2015-09-18 23:11:35 +00:00
}
} ( )
}
2015-07-29 18:12:01 +00:00
2015-09-18 23:11:35 +00:00
wg . Wait ( )
2018-08-06 21:34:15 +00:00
if t . Failed ( ) {
t . FailNow ( )
}
2015-07-29 18:12:01 +00:00
2022-01-20 03:52:34 +00:00
if len ( descHitMap ) > 0 {
t . Errorf ( "Expected hit cache at least once, but it turns out that no caches was hit" )
t . FailNow ( )
}
2015-09-18 23:11:35 +00:00
remoteBlobCount := len ( te . inRemote )
2016-08-29 20:39:24 +00:00
sbsMu . Lock ( )
2022-01-20 03:52:34 +00:00
if ( * localStats ) [ "stat" ] != remoteBlobCount * numClients * 2 && ( * localStats ) [ "create" ] != te . numUnique {
2016-08-29 20:39:24 +00:00
sbsMu . Unlock ( )
2022-01-20 03:52:34 +00:00
t . Fatal ( "Expected: stat:" , remoteBlobCount * numClients , "create:" , remoteBlobCount , "Got: stat:" , ( * localStats ) [ "stat" ] , "create:" , ( * localStats ) [ "create" ] )
2015-07-29 18:12:01 +00:00
}
2016-08-29 20:39:24 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
2015-09-18 23:11:35 +00:00
// Wait for any async storage goroutines to finish
time . Sleep ( 3 * time . Second )
2016-08-29 20:39:24 +00:00
sbsMu . Lock ( )
2015-09-18 23:11:35 +00:00
remoteStatCount := ( * remoteStats ) [ "stat" ]
remoteOpenCount := ( * remoteStats ) [ "open" ]
2016-08-29 20:39:24 +00:00
sbsMu . Unlock ( )
2015-07-29 18:12:01 +00:00
// Serveblob - blobs come from local
for _ , dr := range te . inRemote {
w := httptest . NewRecorder ( )
2022-11-02 22:31:23 +00:00
r , err := http . NewRequest ( http . MethodGet , "" , nil )
2015-07-29 18:12:01 +00:00
if err != nil {
t . Fatal ( err )
}
err = te . store . ServeBlob ( te . ctx , w , r , dr . Digest )
if err != nil {
t . Fatalf ( err . Error ( ) )
}
2015-12-14 22:30:51 +00:00
dl := digest . FromBytes ( w . Body . Bytes ( ) )
2015-07-29 18:12:01 +00:00
if dl != dr . Digest {
t . Errorf ( "Mismatching blob fetch from proxy" )
}
}
2015-09-18 23:11:35 +00:00
remoteStats = te . RemoteStats ( )
2015-07-29 18:12:01 +00:00
2015-09-18 23:11:35 +00:00
// Ensure remote unchanged
2016-08-29 20:39:24 +00:00
sbsMu . Lock ( )
defer sbsMu . Unlock ( )
2015-09-18 23:11:35 +00:00
if ( * remoteStats ) [ "stat" ] != remoteStatCount && ( * remoteStats ) [ "open" ] != remoteOpenCount {
t . Fatalf ( "unexpected remote stats: %#v" , remoteStats )
2015-07-29 18:12:01 +00:00
}
}