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