github.com/uber/kraken@v0.1.4/proxy/proxyserver/preheat.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package proxyserver
    15  
    16  import (
    17  	"bytes"
    18  	"encoding/json"
    19  	"fmt"
    20  	"net/http"
    21  	_ "net/http/pprof" // Registers /debug/pprof endpoints in http.DefaultServeMux.
    22  	"regexp"
    23  	"time"
    24  
    25  	"github.com/docker/distribution"
    26  	"github.com/uber/kraken/core"
    27  	"github.com/uber/kraken/origin/blobclient"
    28  	"github.com/uber/kraken/utils/dockerutil"
    29  	"github.com/uber/kraken/utils/handler"
    30  	"github.com/uber/kraken/utils/httputil"
    31  	"github.com/uber/kraken/utils/log"
    32  )
    33  
    34  var _manifestRegexp = regexp.MustCompile(`^application/vnd.docker.distribution.manifest.v\d\+(json|prettyjws)`)
    35  
    36  // PreheatHandler defines the handler of preheat.
    37  type PreheatHandler struct {
    38  	clusterClient blobclient.ClusterClient
    39  }
    40  
    41  // NewPreheatHandler creates a new preheat handler.
    42  func NewPreheatHandler(client blobclient.ClusterClient) *PreheatHandler {
    43  	return &PreheatHandler{client}
    44  }
    45  
    46  // Handle notifies origins to cache the blob related to the image.
    47  func (ph *PreheatHandler) Handle(w http.ResponseWriter, r *http.Request) error {
    48  	var notification Notification
    49  	if err := json.NewDecoder(r.Body).Decode(&notification); err != nil {
    50  		return handler.Errorf("decode body: %s", err)
    51  	}
    52  
    53  	events := filterEvents(&notification)
    54  	for _, event := range events {
    55  		repo := event.Target.Repository
    56  		digest := event.Target.Digest
    57  
    58  		log.With("repo", repo, "digest", digest).Infof("deal push image event")
    59  		err := ph.process(repo, digest)
    60  		if err != nil {
    61  			log.With("repo", repo, "digest", digest).Errorf("handle preheat: %s", err)
    62  		}
    63  	}
    64  	return nil
    65  }
    66  
    67  func (ph *PreheatHandler) process(repo, digest string) error {
    68  	manifest, err := ph.fetchManifest(repo, digest)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	for _, desc := range manifest.References() {
    73  		d, err := core.ParseSHA256Digest(string(desc.Digest))
    74  		if err != nil {
    75  			log.With("repo", repo, "digest", string(desc.Digest)).Errorf("parse digest: %s", err)
    76  			continue
    77  		}
    78  		go func() {
    79  			log.With("repo", repo).Debugf("trigger origin cache: %+v", d)
    80  			_, err = ph.clusterClient.GetMetaInfo(repo, d)
    81  			if err != nil && !httputil.IsAccepted(err) {
    82  				log.With("repo", repo, "digest", digest).Errorf("notify origin cache: %s", err)
    83  			}
    84  		}()
    85  	}
    86  	return nil
    87  }
    88  
    89  func (ph *PreheatHandler) fetchManifest(repo, digest string) (distribution.Manifest, error) {
    90  	d, err := core.ParseSHA256Digest(digest)
    91  	if err != nil {
    92  		return nil, fmt.Errorf("Error parse digest: %s ", err)
    93  	}
    94  
    95  	buf := &bytes.Buffer{}
    96  	// there may be a gap between registry finish uploading manifest and send notification.
    97  	// see https://github.com/docker/distribution/issues/2625.
    98  	interval := 100 * time.Millisecond
    99  	for i := 0; i < 4; i++ {
   100  		if i != 0 {
   101  			time.Sleep(interval)
   102  			interval = interval * 2
   103  		}
   104  		if err := ph.clusterClient.DownloadBlob(repo, d, buf); err == nil {
   105  			break
   106  		} else if err == blobclient.ErrBlobNotFound {
   107  			continue
   108  		} else {
   109  			return nil, fmt.Errorf("download manifest: %s", err)
   110  		}
   111  	}
   112  	if buf.Len() == 0 {
   113  		return nil, fmt.Errorf("manifest not found")
   114  	}
   115  
   116  	manifest, _, err := dockerutil.ParseManifestV2(buf)
   117  	if err != nil {
   118  		return nil, fmt.Errorf("parse manifest: %s", err)
   119  	}
   120  	return manifest, nil
   121  }
   122  
   123  // filterEvents pick out the push manifest events.
   124  func filterEvents(notification *Notification) []*Event {
   125  	events := []*Event{}
   126  
   127  	for _, event := range notification.Events {
   128  		isManifest := _manifestRegexp.MatchString(event.Target.MediaType)
   129  		if !isManifest {
   130  			continue
   131  		}
   132  
   133  		if event.Action == "push" {
   134  			events = append(events, &event)
   135  			continue
   136  		}
   137  	}
   138  	return events
   139  }