github.com/uber/kraken@v0.1.4/origin/blobserver/server.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 blobserver
    15  
    16  import (
    17  	"encoding/json"
    18  	"fmt"
    19  	"io"
    20  	"net/http"
    21  	_ "net/http/pprof" // Registers /debug/pprof endpoints in http.DefaultServeMux.
    22  	"os"
    23  	"strconv"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/uber/kraken/core"
    29  	"github.com/uber/kraken/lib/backend"
    30  	"github.com/uber/kraken/lib/backend/backenderrors"
    31  	"github.com/uber/kraken/lib/blobrefresh"
    32  	"github.com/uber/kraken/lib/hashring"
    33  	"github.com/uber/kraken/lib/metainfogen"
    34  	"github.com/uber/kraken/lib/middleware"
    35  	"github.com/uber/kraken/lib/persistedretry"
    36  	"github.com/uber/kraken/lib/persistedretry/writeback"
    37  	"github.com/uber/kraken/lib/store"
    38  	"github.com/uber/kraken/lib/store/metadata"
    39  	"github.com/uber/kraken/origin/blobclient"
    40  	"github.com/uber/kraken/utils/errutil"
    41  	"github.com/uber/kraken/utils/handler"
    42  	"github.com/uber/kraken/utils/httputil"
    43  	"github.com/uber/kraken/utils/listener"
    44  	"github.com/uber/kraken/utils/log"
    45  	"github.com/uber/kraken/utils/memsize"
    46  	"github.com/uber/kraken/utils/stringset"
    47  
    48  	"github.com/andres-erbsen/clock"
    49  	"github.com/pressly/chi"
    50  	"github.com/uber-go/tally"
    51  )
    52  
    53  const _uploadChunkSize = 16 * memsize.MB
    54  
    55  // Server defines a server that serves blob data for agent.
    56  type Server struct {
    57  	config            Config
    58  	stats             tally.Scope
    59  	clk               clock.Clock
    60  	addr              string
    61  	hashRing          hashring.Ring
    62  	cas               *store.CAStore
    63  	clientProvider    blobclient.Provider
    64  	clusterProvider   blobclient.ClusterProvider
    65  	backends          *backend.Manager
    66  	blobRefresher     *blobrefresh.Refresher
    67  	metaInfoGenerator *metainfogen.Generator
    68  	uploader          *uploader
    69  	writeBackManager  persistedretry.Manager
    70  
    71  	// This is an unfortunate coupling between the p2p client and the blob server.
    72  	// Tracker queries the origin cluster to discover which origins can seed
    73  	// a given torrent, however this requires blob server to understand the
    74  	// context of the p2p client running alongside it.
    75  	pctx core.PeerContext
    76  }
    77  
    78  // New initializes a new Server.
    79  func New(
    80  	config Config,
    81  	stats tally.Scope,
    82  	clk clock.Clock,
    83  	addr string,
    84  	hashRing hashring.Ring,
    85  	cas *store.CAStore,
    86  	clientProvider blobclient.Provider,
    87  	clusterProvider blobclient.ClusterProvider,
    88  	pctx core.PeerContext,
    89  	backends *backend.Manager,
    90  	blobRefresher *blobrefresh.Refresher,
    91  	metaInfoGenerator *metainfogen.Generator,
    92  	writeBackManager persistedretry.Manager) (*Server, error) {
    93  
    94  	config = config.applyDefaults()
    95  
    96  	stats = stats.Tagged(map[string]string{
    97  		"module": "blobserver",
    98  	})
    99  
   100  	return &Server{
   101  		config:            config,
   102  		stats:             stats,
   103  		clk:               clk,
   104  		addr:              addr,
   105  		hashRing:          hashRing,
   106  		cas:               cas,
   107  		clientProvider:    clientProvider,
   108  		clusterProvider:   clusterProvider,
   109  		backends:          backends,
   110  		blobRefresher:     blobRefresher,
   111  		metaInfoGenerator: metaInfoGenerator,
   112  		uploader:          newUploader(cas),
   113  		writeBackManager:  writeBackManager,
   114  		pctx:              pctx,
   115  	}, nil
   116  }
   117  
   118  // Addr returns the address the blob server is configured on.
   119  func (s *Server) Addr() string {
   120  	return s.addr
   121  }
   122  
   123  // Handler returns an http handler for the blob server.
   124  func (s *Server) Handler() http.Handler {
   125  	r := chi.NewRouter()
   126  
   127  	r.Use(middleware.StatusCounter(s.stats))
   128  	r.Use(middleware.LatencyTimer(s.stats))
   129  
   130  	// Public endpoints:
   131  
   132  	r.Get("/health", handler.Wrap(s.healthCheckHandler))
   133  
   134  	r.Get("/blobs/{digest}/locations", handler.Wrap(s.getLocationsHandler))
   135  
   136  	r.Post("/namespace/{namespace}/blobs/{digest}/uploads", handler.Wrap(s.startClusterUploadHandler))
   137  	r.Patch("/namespace/{namespace}/blobs/{digest}/uploads/{uid}", handler.Wrap(s.patchClusterUploadHandler))
   138  	r.Put("/namespace/{namespace}/blobs/{digest}/uploads/{uid}", handler.Wrap(s.commitClusterUploadHandler))
   139  
   140  	r.Get("/namespace/{namespace}/blobs/{digest}", handler.Wrap(s.downloadBlobHandler))
   141  
   142  	r.Post("/namespace/{namespace}/blobs/{digest}/remote/{remote}", handler.Wrap(s.replicateToRemoteHandler))
   143  
   144  	r.Post("/forcecleanup", handler.Wrap(s.forceCleanupHandler))
   145  
   146  	// Internal endpoints:
   147  
   148  	r.Post("/internal/blobs/{digest}/uploads", handler.Wrap(s.startTransferHandler))
   149  	r.Patch("/internal/blobs/{digest}/uploads/{uid}", handler.Wrap(s.patchTransferHandler))
   150  	r.Put("/internal/blobs/{digest}/uploads/{uid}", handler.Wrap(s.commitTransferHandler))
   151  
   152  	r.Delete("/internal/blobs/{digest}", handler.Wrap(s.deleteBlobHandler))
   153  
   154  	r.Post("/internal/blobs/{digest}/metainfo", handler.Wrap(s.overwriteMetaInfoHandler))
   155  
   156  	r.Get("/internal/peercontext", handler.Wrap(s.getPeerContextHandler))
   157  
   158  	r.Head("/internal/namespace/{namespace}/blobs/{digest}", handler.Wrap(s.statHandler))
   159  
   160  	r.Get("/internal/namespace/{namespace}/blobs/{digest}/metainfo", handler.Wrap(s.getMetaInfoHandler))
   161  
   162  	r.Put(
   163  		"/internal/duplicate/namespace/{namespace}/blobs/{digest}/uploads/{uid}",
   164  		handler.Wrap(s.duplicateCommitClusterUploadHandler))
   165  
   166  	r.Mount("/", http.DefaultServeMux) // Serves /debug/pprof endpoints.
   167  
   168  	return r
   169  }
   170  
   171  // ListenAndServe is a blocking call which runs s.
   172  func (s *Server) ListenAndServe(h http.Handler) error {
   173  	log.Infof("Starting blob server on %s", s.config.Listener)
   174  	return listener.Serve(s.config.Listener, h)
   175  }
   176  
   177  func (s *Server) healthCheckHandler(w http.ResponseWriter, r *http.Request) error {
   178  	fmt.Fprintln(w, "OK")
   179  	return nil
   180  }
   181  
   182  // statHandler returns blob info if it exists.
   183  func (s *Server) statHandler(w http.ResponseWriter, r *http.Request) error {
   184  	checkLocal, err := strconv.ParseBool(httputil.GetQueryArg(r, "local", "false"))
   185  	if err != nil {
   186  		return handler.Errorf("parse arg `local` as bool: %s", err)
   187  	}
   188  	namespace, err := httputil.ParseParam(r, "namespace")
   189  	if err != nil {
   190  		return err
   191  	}
   192  	d, err := httputil.ParseDigest(r, "digest")
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	bi, err := s.stat(namespace, d, checkLocal)
   198  	if os.IsNotExist(err) {
   199  		return handler.ErrorStatus(http.StatusNotFound)
   200  	} else if err != nil {
   201  		return fmt.Errorf("stat: %s", err)
   202  	}
   203  	w.Header().Set("Content-Length", strconv.FormatInt(bi.Size, 10))
   204  	log.Debugf("successfully check blob %s exists", d.Hex())
   205  	return nil
   206  }
   207  
   208  func (s *Server) stat(namespace string, d core.Digest, checkLocal bool) (*core.BlobInfo, error) {
   209  	fi, err := s.cas.GetCacheFileStat(d.Hex())
   210  	if err == nil {
   211  		return core.NewBlobInfo(fi.Size()), nil
   212  	} else if os.IsNotExist(err) {
   213  		if !checkLocal {
   214  			client, err := s.backends.GetClient(namespace)
   215  			if err != nil {
   216  				return nil, fmt.Errorf("get backend client: %s", err)
   217  			}
   218  			if bi, err := client.Stat(namespace, d.Hex()); err == nil {
   219  				return bi, nil
   220  			} else if err == backenderrors.ErrBlobNotFound {
   221  				return nil, os.ErrNotExist
   222  			} else {
   223  				return nil, fmt.Errorf("backend stat: %s", err)
   224  			}
   225  		}
   226  		return nil, err // os.ErrNotExist
   227  	}
   228  
   229  	return nil, fmt.Errorf("stat cache file: %s", err)
   230  }
   231  
   232  func (s *Server) downloadBlobHandler(w http.ResponseWriter, r *http.Request) error {
   233  	namespace, err := httputil.ParseParam(r, "namespace")
   234  	if err != nil {
   235  		return err
   236  	}
   237  	d, err := httputil.ParseDigest(r, "digest")
   238  	if err != nil {
   239  		return err
   240  	}
   241  	if err := s.downloadBlob(namespace, d, w); err != nil {
   242  		return err
   243  	}
   244  	setOctetStreamContentType(w)
   245  	return nil
   246  }
   247  
   248  func (s *Server) replicateToRemoteHandler(w http.ResponseWriter, r *http.Request) error {
   249  	namespace, err := httputil.ParseParam(r, "namespace")
   250  	if err != nil {
   251  		return err
   252  	}
   253  	d, err := httputil.ParseDigest(r, "digest")
   254  	if err != nil {
   255  		return err
   256  	}
   257  	remote, err := httputil.ParseParam(r, "remote")
   258  	if err != nil {
   259  		return err
   260  	}
   261  	return s.replicateToRemote(namespace, d, remote)
   262  }
   263  
   264  func (s *Server) replicateToRemote(namespace string, d core.Digest, remoteDNS string) error {
   265  	f, err := s.cas.GetCacheFileReader(d.Hex())
   266  	if err != nil {
   267  		if os.IsNotExist(err) {
   268  			return s.startRemoteBlobDownload(namespace, d, false)
   269  		}
   270  		return handler.Errorf("file store: %s", err)
   271  	}
   272  	defer f.Close()
   273  
   274  	remote, err := s.clusterProvider.Provide(remoteDNS)
   275  	if err != nil {
   276  		return handler.Errorf("remote cluster provider: %s", err)
   277  	}
   278  	return remote.UploadBlob(namespace, d, f)
   279  }
   280  
   281  // deleteBlobHandler deletes blob data.
   282  func (s *Server) deleteBlobHandler(w http.ResponseWriter, r *http.Request) error {
   283  	d, err := httputil.ParseDigest(r, "digest")
   284  	if err != nil {
   285  		return err
   286  	}
   287  	if err := s.deleteBlob(d); err != nil {
   288  		return err
   289  	}
   290  	setContentLength(w, 0)
   291  	w.WriteHeader(http.StatusAccepted)
   292  	log.Debugf("successfully delete blob %s", d.Hex())
   293  	return nil
   294  }
   295  
   296  func (s *Server) getLocationsHandler(w http.ResponseWriter, r *http.Request) error {
   297  	d, err := httputil.ParseDigest(r, "digest")
   298  	if err != nil {
   299  		return err
   300  	}
   301  	locs := s.hashRing.Locations(d)
   302  	w.Header().Set("Origin-Locations", strings.Join(locs, ","))
   303  	w.WriteHeader(http.StatusOK)
   304  	return nil
   305  }
   306  
   307  // getPeerContextHandler returns the Server's peer context as JSON.
   308  func (s *Server) getPeerContextHandler(w http.ResponseWriter, r *http.Request) error {
   309  	if err := json.NewEncoder(w).Encode(s.pctx); err != nil {
   310  		return handler.Errorf("error converting peer context to json: %s", err)
   311  	}
   312  	return nil
   313  }
   314  
   315  func (s *Server) getMetaInfoHandler(w http.ResponseWriter, r *http.Request) error {
   316  	namespace, err := httputil.ParseParam(r, "namespace")
   317  	if err != nil {
   318  		return err
   319  	}
   320  	d, err := httputil.ParseDigest(r, "digest")
   321  	if err != nil {
   322  		return err
   323  	}
   324  	raw, err := s.getMetaInfo(namespace, d)
   325  	if err != nil {
   326  		return err
   327  	}
   328  	w.Write(raw)
   329  	return nil
   330  }
   331  
   332  func (s *Server) overwriteMetaInfoHandler(w http.ResponseWriter, r *http.Request) error {
   333  	d, err := httputil.ParseDigest(r, "digest")
   334  	if err != nil {
   335  		return err
   336  	}
   337  	pieceLength, err := strconv.ParseInt(r.URL.Query().Get("piece_length"), 10, 64)
   338  	if err != nil {
   339  		return handler.Errorf("invalid piece_length argument: %s", err).Status(http.StatusBadRequest)
   340  	}
   341  	return s.overwriteMetaInfo(d, pieceLength)
   342  }
   343  
   344  // overwriteMetaInfo generates metainfo configured with pieceLength for d and
   345  // writes it to disk, overwriting any existing metainfo. Primarily intended for
   346  // benchmarking purposes.
   347  func (s *Server) overwriteMetaInfo(d core.Digest, pieceLength int64) error {
   348  	f, err := s.cas.GetCacheFileReader(d.Hex())
   349  	if err != nil {
   350  		return handler.Errorf("get cache file: %s", err)
   351  	}
   352  	mi, err := core.NewMetaInfo(d, f, pieceLength)
   353  	if err != nil {
   354  		return handler.Errorf("create metainfo: %s", err)
   355  	}
   356  	if _, err := s.cas.SetCacheFileMetadata(d.Hex(), metadata.NewTorrentMeta(mi)); err != nil {
   357  		return handler.Errorf("set metainfo: %s", err)
   358  	}
   359  	return nil
   360  }
   361  
   362  // getMetaInfo returns metainfo for d. If no blob exists under d, a download of
   363  // the blob from the storage backend configured for namespace will be initiated.
   364  // This download is asynchronous and getMetaInfo will immediately return a
   365  // "202 Accepted" server error.
   366  func (s *Server) getMetaInfo(namespace string, d core.Digest) ([]byte, error) {
   367  	var tm metadata.TorrentMeta
   368  	if err := s.cas.GetCacheFileMetadata(d.Hex(), &tm); os.IsNotExist(err) {
   369  		return nil, s.startRemoteBlobDownload(namespace, d, true)
   370  	} else if err != nil {
   371  		return nil, handler.Errorf("get cache metadata: %s", err)
   372  	}
   373  	return tm.Serialize()
   374  }
   375  
   376  type localReplicationHook struct {
   377  	server *Server
   378  }
   379  
   380  func (h *localReplicationHook) Run(d core.Digest) {
   381  	timer := h.server.stats.Timer("replicate_blob").Start()
   382  	if err := h.server.replicateBlobLocally(d); err != nil {
   383  		// Don't return error here as we only want to cache storage backend errors.
   384  		log.With("blob", d.Hex()).Errorf("Error replicating remote blob: %s", err)
   385  		h.server.stats.Counter("replicate_blob_errors").Inc(1)
   386  		return
   387  	}
   388  	timer.Stop()
   389  }
   390  
   391  func (s *Server) startRemoteBlobDownload(
   392  	namespace string, d core.Digest, replicateLocally bool) error {
   393  
   394  	var hooks []blobrefresh.PostHook
   395  	if replicateLocally {
   396  		hooks = append(hooks, &localReplicationHook{s})
   397  	}
   398  	err := s.blobRefresher.Refresh(namespace, d, hooks...)
   399  	switch err {
   400  	case blobrefresh.ErrPending, nil:
   401  		return handler.ErrorStatus(http.StatusAccepted)
   402  	case blobrefresh.ErrNotFound:
   403  		return handler.ErrorStatus(http.StatusNotFound)
   404  	case blobrefresh.ErrWorkersBusy:
   405  		return handler.ErrorStatus(http.StatusServiceUnavailable)
   406  	default:
   407  		return err
   408  	}
   409  }
   410  
   411  func (s *Server) replicateBlobLocally(d core.Digest) error {
   412  	return s.applyToReplicas(d, func(i int, client blobclient.Client) error {
   413  		f, err := s.cas.GetCacheFileReader(d.Hex())
   414  		if err != nil {
   415  			return fmt.Errorf("get cache reader: %s", err)
   416  		}
   417  		if err := client.TransferBlob(d, f); err != nil {
   418  			return fmt.Errorf("transfer blob: %s", err)
   419  		}
   420  		return nil
   421  	})
   422  }
   423  
   424  // applyToReplicas applies f to the replicas of d concurrently in random order,
   425  // not including the current origin. Passes the index of the iteration to f.
   426  func (s *Server) applyToReplicas(d core.Digest, f func(i int, c blobclient.Client) error) error {
   427  	replicas := stringset.FromSlice(s.hashRing.Locations(d))
   428  	replicas.Remove(s.addr)
   429  
   430  	var mu sync.Mutex
   431  	var errs []error
   432  
   433  	var wg sync.WaitGroup
   434  	var i int
   435  	for replica := range replicas {
   436  		wg.Add(1)
   437  		go func(i int, replica string) {
   438  			defer wg.Done()
   439  			if err := f(i, s.clientProvider.Provide(replica)); err != nil {
   440  				mu.Lock()
   441  				errs = append(errs, err)
   442  				mu.Unlock()
   443  			}
   444  		}(i, replica)
   445  		i++
   446  	}
   447  	wg.Wait()
   448  
   449  	return errutil.Join(errs)
   450  }
   451  
   452  // downloadBlob downloads blob for d into dst. If no blob exists under d, a
   453  // download of the blob from the storage backend configured for namespace will
   454  // be initiated. This download is asynchronous and downloadBlob will immediately
   455  // return a "202 Accepted" handler error.
   456  func (s *Server) downloadBlob(namespace string, d core.Digest, dst io.Writer) error {
   457  	f, err := s.cas.GetCacheFileReader(d.Hex())
   458  	if os.IsNotExist(err) {
   459  		return s.startRemoteBlobDownload(namespace, d, true)
   460  	} else if err != nil {
   461  		return handler.Errorf("get cache file: %s", err)
   462  	}
   463  	defer f.Close()
   464  
   465  	if _, err := io.Copy(dst, f); err != nil {
   466  		return handler.Errorf("copy blob: %s", err)
   467  	}
   468  	return nil
   469  }
   470  
   471  func (s *Server) deleteBlob(d core.Digest) error {
   472  	if err := s.cas.DeleteCacheFile(d.Hex()); err != nil {
   473  		if os.IsNotExist(err) {
   474  			return handler.ErrorStatus(http.StatusNotFound)
   475  		}
   476  		return handler.Errorf("cannot delete blob data for digest %q: %s", d, err)
   477  	}
   478  	return nil
   479  }
   480  
   481  // startTransferHandler initializes an upload for internal blob transfers.
   482  func (s *Server) startTransferHandler(w http.ResponseWriter, r *http.Request) error {
   483  	d, err := httputil.ParseDigest(r, "digest")
   484  	if err != nil {
   485  		return err
   486  	}
   487  	if ok, err := blobExists(s.cas, d); err != nil {
   488  		return handler.Errorf("check blob: %s", err)
   489  	} else if ok {
   490  		return handler.ErrorStatus(http.StatusConflict)
   491  	}
   492  	uid, err := s.uploader.start(d)
   493  	if err != nil {
   494  		return err
   495  	}
   496  	setUploadLocation(w, uid)
   497  	w.WriteHeader(http.StatusOK)
   498  	return nil
   499  }
   500  
   501  // patchTransferHandler uploads a chunk of a blob for internal uploads.
   502  func (s *Server) patchTransferHandler(w http.ResponseWriter, r *http.Request) error {
   503  	d, err := httputil.ParseDigest(r, "digest")
   504  	if err != nil {
   505  		return err
   506  	}
   507  	uid, err := httputil.ParseParam(r, "uid")
   508  	if err != nil {
   509  		return err
   510  	}
   511  	start, end, err := parseContentRange(r.Header)
   512  	if err != nil {
   513  		return err
   514  	}
   515  	return s.uploader.patch(d, uid, r.Body, start, end)
   516  }
   517  
   518  // commitTransferHandler commits the upload of an internal blob transfer.
   519  // Internal blob transfers are not replicated to the rest of the cluster.
   520  func (s *Server) commitTransferHandler(w http.ResponseWriter, r *http.Request) error {
   521  	d, err := httputil.ParseDigest(r, "digest")
   522  	if err != nil {
   523  		return err
   524  	}
   525  	uid, err := httputil.ParseParam(r, "uid")
   526  	if err != nil {
   527  		return err
   528  	}
   529  	if err := s.uploader.verify(d, uid); err != nil {
   530  		return err
   531  	}
   532  	if err := s.uploader.commit(d, uid); err != nil {
   533  		return err
   534  	}
   535  	if err := s.metaInfoGenerator.Generate(d); err != nil {
   536  		return handler.Errorf("generate metainfo: %s", err)
   537  	}
   538  	return nil
   539  }
   540  
   541  func (s *Server) handleUploadConflict(err error, namespace string, d core.Digest) error {
   542  	if herr, ok := err.(*handler.Error); ok && herr.GetStatus() == http.StatusConflict {
   543  		// Even if the blob was already uploaded and committed to cache, it's
   544  		// still possible that adding the write-back task failed. Clients short
   545  		// circuit on conflict and return success, so we must make sure that if we
   546  		// tell a client to stop before commit, the blob has been written back.
   547  		if err := s.writeBack(namespace, d, 0); err != nil {
   548  			return err
   549  		}
   550  	}
   551  	return err
   552  }
   553  
   554  // startClusterUploadHandler initializes an upload for external uploads.
   555  func (s *Server) startClusterUploadHandler(w http.ResponseWriter, r *http.Request) error {
   556  	d, err := httputil.ParseDigest(r, "digest")
   557  	if err != nil {
   558  		return err
   559  	}
   560  	namespace, err := httputil.ParseParam(r, "namespace")
   561  	if err != nil {
   562  		return err
   563  	}
   564  	uid, err := s.uploader.start(d)
   565  	if err != nil {
   566  		return s.handleUploadConflict(err, namespace, d)
   567  	}
   568  	setUploadLocation(w, uid)
   569  	w.WriteHeader(http.StatusOK)
   570  	return nil
   571  }
   572  
   573  // patchClusterUploadHandler uploads a chunk of a blob for external uploads.
   574  func (s *Server) patchClusterUploadHandler(w http.ResponseWriter, r *http.Request) error {
   575  	d, err := httputil.ParseDigest(r, "digest")
   576  	if err != nil {
   577  		return err
   578  	}
   579  	namespace, err := httputil.ParseParam(r, "namespace")
   580  	if err != nil {
   581  		return err
   582  	}
   583  	uid, err := httputil.ParseParam(r, "uid")
   584  	if err != nil {
   585  		return err
   586  	}
   587  	start, end, err := parseContentRange(r.Header)
   588  	if err != nil {
   589  		return err
   590  	}
   591  	if err := s.uploader.patch(d, uid, r.Body, start, end); err != nil {
   592  		return s.handleUploadConflict(err, namespace, d)
   593  	}
   594  	return nil
   595  }
   596  
   597  // commitClusterUploadHandler commits an external blob upload asynchronously,
   598  // meaning the blob will be written back to remote storage in a non-blocking
   599  // fashion.
   600  func (s *Server) commitClusterUploadHandler(w http.ResponseWriter, r *http.Request) error {
   601  	d, err := httputil.ParseDigest(r, "digest")
   602  	if err != nil {
   603  		return err
   604  	}
   605  	namespace, err := httputil.ParseParam(r, "namespace")
   606  	if err != nil {
   607  		return err
   608  	}
   609  	uid, err := httputil.ParseParam(r, "uid")
   610  	if err != nil {
   611  		return err
   612  	}
   613  
   614  	if err := s.uploader.verify(d, uid); err != nil {
   615  		return err
   616  	}
   617  	if err := s.uploader.commit(d, uid); err != nil {
   618  		return s.handleUploadConflict(err, namespace, d)
   619  	}
   620  	if err := s.writeBack(namespace, d, 0); err != nil {
   621  		return err
   622  	}
   623  	err = s.applyToReplicas(d, func(i int, client blobclient.Client) error {
   624  		delay := s.config.DuplicateWriteBackStagger * time.Duration(i+1)
   625  		f, err := s.cas.GetCacheFileReader(d.Hex())
   626  		if err != nil {
   627  			return fmt.Errorf("get cache file: %s", err)
   628  		}
   629  		if err := client.DuplicateUploadBlob(namespace, d, f, delay); err != nil {
   630  			return fmt.Errorf("duplicate upload: %s", err)
   631  		}
   632  		return nil
   633  	})
   634  	if err != nil {
   635  		s.stats.Counter("duplicate_write_back_errors").Inc(1)
   636  		log.Errorf("Error duplicating write-back task to replicas: %s", err)
   637  	}
   638  	return nil
   639  }
   640  
   641  // duplicateCommitClusterUploadHandler commits a duplicate blob upload, which
   642  // will attempt to write-back after the requested delay.
   643  func (s *Server) duplicateCommitClusterUploadHandler(w http.ResponseWriter, r *http.Request) error {
   644  	d, err := httputil.ParseDigest(r, "digest")
   645  	if err != nil {
   646  		return err
   647  	}
   648  	namespace, err := httputil.ParseParam(r, "namespace")
   649  	if err != nil {
   650  		return err
   651  	}
   652  	uid, err := httputil.ParseParam(r, "uid")
   653  	if err != nil {
   654  		return err
   655  	}
   656  
   657  	var dr blobclient.DuplicateCommitUploadRequest
   658  	if err := json.NewDecoder(r.Body).Decode(&dr); err != nil {
   659  		return handler.Errorf("decode body: %s", err)
   660  	}
   661  	delay := dr.Delay
   662  
   663  	if err := s.uploader.verify(d, uid); err != nil {
   664  		return err
   665  	}
   666  	if err := s.uploader.commit(d, uid); err != nil {
   667  		return err
   668  	}
   669  	return s.writeBack(namespace, d, delay)
   670  }
   671  
   672  func (s *Server) writeBack(namespace string, d core.Digest, delay time.Duration) error {
   673  	if _, err := s.cas.SetCacheFileMetadata(d.Hex(), metadata.NewPersist(true)); err != nil {
   674  		return handler.Errorf("set persist metadata: %s", err)
   675  	}
   676  	task := writeback.NewTask(namespace, d.Hex(), delay)
   677  	if err := s.writeBackManager.Add(task); err != nil {
   678  		return handler.Errorf("add write-back task: %s", err)
   679  	}
   680  	if err := s.metaInfoGenerator.Generate(d); err != nil {
   681  		return handler.Errorf("generate metainfo: %s", err)
   682  	}
   683  	return nil
   684  }
   685  
   686  func (s *Server) forceCleanupHandler(w http.ResponseWriter, r *http.Request) error {
   687  	// Note, this API is intended to be executed manually (i.e. curl), hence the
   688  	// query arguments, usage of hours instead of nanoseconds, and JSON response
   689  	// enumerating deleted files / errors.
   690  
   691  	rawTTLHr := r.URL.Query().Get("ttl_hr")
   692  	if rawTTLHr == "" {
   693  		return handler.Errorf("query arg ttl_hr required").Status(http.StatusBadRequest)
   694  	}
   695  	ttlHr, err := strconv.Atoi(rawTTLHr)
   696  	if err != nil {
   697  		return handler.Errorf("invalid ttl_hr: %s", err).Status(http.StatusBadRequest)
   698  	}
   699  	ttl := time.Duration(ttlHr) * time.Hour
   700  
   701  	names, err := s.cas.ListCacheFiles()
   702  	if err != nil {
   703  		return err
   704  	}
   705  	var errs, deleted []string
   706  	for _, name := range names {
   707  		if ok, err := s.maybeDelete(name, ttl); err != nil {
   708  			errs = append(errs, fmt.Sprintf("%s: %s", name, err))
   709  		} else if ok {
   710  			deleted = append(deleted, name)
   711  		}
   712  	}
   713  	return json.NewEncoder(w).Encode(map[string]interface{}{
   714  		"deleted": deleted,
   715  		"errors":  errs,
   716  	})
   717  }
   718  
   719  func (s *Server) maybeDelete(name string, ttl time.Duration) (deleted bool, err error) {
   720  	d, err := core.NewSHA256DigestFromHex(name)
   721  	if err != nil {
   722  		return false, fmt.Errorf("parse digest: %s", err)
   723  	}
   724  	info, err := s.cas.GetCacheFileStat(name)
   725  	if err != nil {
   726  		return false, fmt.Errorf("store: %s", err)
   727  	}
   728  	expired := s.clk.Now().Sub(info.ModTime()) > ttl
   729  	owns := stringset.FromSlice(s.hashRing.Locations(d)).Has(s.addr)
   730  	if expired || !owns {
   731  		// Ensure file is backed up properly before deleting.
   732  		var pm metadata.Persist
   733  		if err := s.cas.GetCacheFileMetadata(name, &pm); err != nil && !os.IsNotExist(err) {
   734  			return false, fmt.Errorf("store: %s", err)
   735  		}
   736  		if pm.Value {
   737  			// Note: It is possible that no writeback tasks exist, but the file
   738  			// is persisted. We classify this as a leaked file which is safe to
   739  			// delete.
   740  			tasks, err := s.writeBackManager.Find(writeback.NewNameQuery(name))
   741  			if err != nil {
   742  				return false, fmt.Errorf("find writeback tasks: %s", err)
   743  			}
   744  			for _, task := range tasks {
   745  				if err := s.writeBackManager.SyncExec(task); err != nil {
   746  					return false, fmt.Errorf("writeback: %s", err)
   747  				}
   748  			}
   749  			if err := s.cas.DeleteCacheFileMetadata(name, &metadata.Persist{}); err != nil {
   750  				return false, fmt.Errorf("delete persist: %s", err)
   751  			}
   752  		}
   753  		if err := s.cas.DeleteCacheFile(name); err != nil {
   754  			return false, fmt.Errorf("delete: %s", err)
   755  		}
   756  		return true, nil
   757  	}
   758  	return false, nil
   759  }