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  }