github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/proxy/proxyblobstore.go (about) 1 package proxy 2 3 import ( 4 "io" 5 "net/http" 6 "strconv" 7 "sync" 8 "time" 9 10 "github.com/docker/distribution" 11 "github.com/docker/distribution/context" 12 "github.com/docker/distribution/digest" 13 "github.com/docker/distribution/registry/proxy/scheduler" 14 ) 15 16 // todo(richardscothern): from cache control header or config file 17 const blobTTL = time.Duration(24 * 7 * time.Hour) 18 19 type proxyBlobStore struct { 20 localStore distribution.BlobStore 21 remoteStore distribution.BlobService 22 scheduler *scheduler.TTLExpirationScheduler 23 } 24 25 var _ distribution.BlobStore = &proxyBlobStore{} 26 27 // inflight tracks currently downloading blobs 28 var inflight = make(map[digest.Digest]struct{}) 29 30 // mu protects inflight 31 var mu sync.Mutex 32 33 func setResponseHeaders(w http.ResponseWriter, length int64, mediaType string, digest digest.Digest) { 34 w.Header().Set("Content-Length", strconv.FormatInt(length, 10)) 35 w.Header().Set("Content-Type", mediaType) 36 w.Header().Set("Docker-Content-Digest", digest.String()) 37 w.Header().Set("Etag", digest.String()) 38 } 39 40 func (pbs *proxyBlobStore) copyContent(ctx context.Context, dgst digest.Digest, writer io.Writer) (distribution.Descriptor, error) { 41 desc, err := pbs.remoteStore.Stat(ctx, dgst) 42 if err != nil { 43 return distribution.Descriptor{}, err 44 } 45 46 if w, ok := writer.(http.ResponseWriter); ok { 47 setResponseHeaders(w, desc.Size, desc.MediaType, dgst) 48 } 49 50 remoteReader, err := pbs.remoteStore.Open(ctx, dgst) 51 if err != nil { 52 return distribution.Descriptor{}, err 53 } 54 55 _, err = io.CopyN(writer, remoteReader, desc.Size) 56 if err != nil { 57 return distribution.Descriptor{}, err 58 } 59 60 proxyMetrics.BlobPush(uint64(desc.Size)) 61 62 return desc, nil 63 } 64 65 func (pbs *proxyBlobStore) serveLocal(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) (bool, error) { 66 localDesc, err := pbs.localStore.Stat(ctx, dgst) 67 if err != nil { 68 // Stat can report a zero sized file here if it's checked between creation 69 // and population. Return nil error, and continue 70 return false, nil 71 } 72 73 if err == nil { 74 proxyMetrics.BlobPush(uint64(localDesc.Size)) 75 return true, pbs.localStore.ServeBlob(ctx, w, r, dgst) 76 } 77 78 return false, nil 79 80 } 81 82 func (pbs *proxyBlobStore) storeLocal(ctx context.Context, dgst digest.Digest) error { 83 defer func() { 84 mu.Lock() 85 delete(inflight, dgst) 86 mu.Unlock() 87 }() 88 89 var desc distribution.Descriptor 90 var err error 91 var bw distribution.BlobWriter 92 93 bw, err = pbs.localStore.Create(ctx) 94 if err != nil { 95 return err 96 } 97 98 desc, err = pbs.copyContent(ctx, dgst, bw) 99 if err != nil { 100 return err 101 } 102 103 _, err = bw.Commit(ctx, desc) 104 if err != nil { 105 return err 106 } 107 108 return nil 109 } 110 111 func (pbs *proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { 112 served, err := pbs.serveLocal(ctx, w, r, dgst) 113 if err != nil { 114 context.GetLogger(ctx).Errorf("Error serving blob from local storage: %s", err.Error()) 115 return err 116 } 117 118 if served { 119 return nil 120 } 121 122 mu.Lock() 123 _, ok := inflight[dgst] 124 if ok { 125 mu.Unlock() 126 _, err := pbs.copyContent(ctx, dgst, w) 127 return err 128 } 129 inflight[dgst] = struct{}{} 130 mu.Unlock() 131 132 go func(dgst digest.Digest) { 133 if err := pbs.storeLocal(ctx, dgst); err != nil { 134 context.GetLogger(ctx).Errorf("Error committing to storage: %s", err.Error()) 135 } 136 pbs.scheduler.AddBlob(dgst.String(), repositoryTTL) 137 }(dgst) 138 139 _, err = pbs.copyContent(ctx, dgst, w) 140 if err != nil { 141 return err 142 } 143 return nil 144 } 145 146 func (pbs *proxyBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 147 desc, err := pbs.localStore.Stat(ctx, dgst) 148 if err == nil { 149 return desc, err 150 } 151 152 if err != distribution.ErrBlobUnknown { 153 return distribution.Descriptor{}, err 154 } 155 156 return pbs.remoteStore.Stat(ctx, dgst) 157 } 158 159 // Unsupported functions 160 func (pbs *proxyBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { 161 return distribution.Descriptor{}, distribution.ErrUnsupported 162 } 163 164 func (pbs *proxyBlobStore) Create(ctx context.Context) (distribution.BlobWriter, error) { 165 return nil, distribution.ErrUnsupported 166 } 167 168 func (pbs *proxyBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { 169 return nil, distribution.ErrUnsupported 170 } 171 172 func (pbs *proxyBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { 173 return nil, distribution.ErrUnsupported 174 } 175 176 func (pbs *proxyBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { 177 return nil, distribution.ErrUnsupported 178 } 179 180 func (pbs *proxyBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { 181 return distribution.ErrUnsupported 182 }