github.com/ethersphere/bee/v2@v2.2.0/pkg/api/pin.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package api
     6  
     7  import (
     8  	"encoding/json"
     9  	"errors"
    10  	"net/http"
    11  	"sync"
    12  
    13  	"github.com/ethersphere/bee/v2/pkg/jsonhttp"
    14  	"github.com/ethersphere/bee/v2/pkg/storage"
    15  	storer "github.com/ethersphere/bee/v2/pkg/storer"
    16  	"github.com/ethersphere/bee/v2/pkg/swarm"
    17  	"github.com/ethersphere/bee/v2/pkg/traversal"
    18  	"github.com/gorilla/mux"
    19  	"golang.org/x/sync/semaphore"
    20  )
    21  
    22  // pinRootHash pins root hash of given reference. This method is idempotent.
    23  func (s *Service) pinRootHash(w http.ResponseWriter, r *http.Request) {
    24  	logger := s.logger.WithName("post_pin").Build()
    25  
    26  	paths := struct {
    27  		Reference swarm.Address `map:"reference" validate:"required"`
    28  	}{}
    29  	if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
    30  		response("invalid path params", logger, w)
    31  		return
    32  	}
    33  
    34  	has, err := s.storer.HasPin(paths.Reference)
    35  	if err != nil {
    36  		logger.Debug("pin root hash: has pin failed", "chunk_address", paths.Reference, "error", err)
    37  		logger.Error(nil, "pin root hash: has pin failed")
    38  		jsonhttp.InternalServerError(w, "pin root hash: checking of tracking pin failed")
    39  		return
    40  	}
    41  	if has {
    42  		jsonhttp.OK(w, nil)
    43  		return
    44  	}
    45  
    46  	putter, err := s.storer.NewCollection(r.Context())
    47  	if err != nil {
    48  		logger.Debug("pin root hash: failed to create collection", "error", err)
    49  		logger.Error(nil, "pin root hash: failed to create collection")
    50  		jsonhttp.InternalServerError(w, "pin root hash: create collection failed")
    51  		return
    52  	}
    53  
    54  	getter := s.storer.Download(true)
    55  	traverser := traversal.New(getter, s.storer.Cache())
    56  
    57  	sem := semaphore.NewWeighted(100)
    58  	var errTraverse error
    59  	var mtxErr sync.Mutex
    60  	var wg sync.WaitGroup
    61  
    62  	err = traverser.Traverse(
    63  		r.Context(),
    64  		paths.Reference,
    65  		func(address swarm.Address) error {
    66  			mtxErr.Lock()
    67  			if errTraverse != nil {
    68  				mtxErr.Unlock()
    69  				return errTraverse
    70  			}
    71  			mtxErr.Unlock()
    72  			if err := sem.Acquire(r.Context(), 1); err != nil {
    73  				return err
    74  			}
    75  			wg.Add(1)
    76  			go func() {
    77  				var err error
    78  				defer func() {
    79  					sem.Release(1)
    80  					wg.Done()
    81  					if err != nil {
    82  						mtxErr.Lock()
    83  						errTraverse = errors.Join(errTraverse, err)
    84  						mtxErr.Unlock()
    85  					}
    86  				}()
    87  				chunk, err := getter.Get(r.Context(), address)
    88  				if err != nil {
    89  					return
    90  				}
    91  				err = putter.Put(r.Context(), chunk)
    92  			}()
    93  			return nil
    94  		},
    95  	)
    96  
    97  	wg.Wait()
    98  
    99  	if err := errors.Join(err, errTraverse); err != nil {
   100  		logger.Error(errors.Join(err, putter.Cleanup()), "pin collection failed")
   101  		if errors.Is(err, storage.ErrNotFound) {
   102  			jsonhttp.NotFound(w, "pin collection failed")
   103  			return
   104  		}
   105  		jsonhttp.InternalServerError(w, "pin collection failed")
   106  		return
   107  	}
   108  
   109  	err = putter.Done(paths.Reference)
   110  	if err != nil {
   111  		logger.Debug("pin collection failed on done", "error", err)
   112  		logger.Error(nil, "pin collection failed")
   113  		jsonhttp.InternalServerError(w, "pin collection failed")
   114  		return
   115  	}
   116  
   117  	jsonhttp.Created(w, nil)
   118  }
   119  
   120  // unpinRootHash unpin's an already pinned root hash. This method is idempotent.
   121  func (s *Service) unpinRootHash(w http.ResponseWriter, r *http.Request) {
   122  	logger := s.logger.WithName("delete_pin").Build()
   123  
   124  	paths := struct {
   125  		Reference swarm.Address `map:"reference" validate:"required"`
   126  	}{}
   127  	if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
   128  		response("invalid path params", logger, w)
   129  		return
   130  	}
   131  
   132  	has, err := s.storer.HasPin(paths.Reference)
   133  	if err != nil {
   134  		logger.Debug("unpin root hash: has pin failed", "chunk_address", paths.Reference, "error", err)
   135  		logger.Error(nil, "unpin root hash: has pin failed")
   136  		jsonhttp.InternalServerError(w, "pin root hash: checking of tracking pin")
   137  		return
   138  	}
   139  	if !has {
   140  		jsonhttp.NotFound(w, nil)
   141  		return
   142  	}
   143  
   144  	if err := s.storer.DeletePin(r.Context(), paths.Reference); err != nil {
   145  		logger.Debug("unpin root hash: delete pin failed", "chunk_address", paths.Reference, "error", err)
   146  		logger.Error(nil, "unpin root hash: delete pin failed")
   147  		jsonhttp.InternalServerError(w, "unpin root hash: deletion of pin failed")
   148  		return
   149  	}
   150  
   151  	jsonhttp.OK(w, nil)
   152  }
   153  
   154  // getPinnedRootHash returns back the given reference if its root hash is pinned.
   155  func (s *Service) getPinnedRootHash(w http.ResponseWriter, r *http.Request) {
   156  	logger := s.logger.WithName("get_pin").Build()
   157  
   158  	paths := struct {
   159  		Reference swarm.Address `map:"reference" validate:"required"`
   160  	}{}
   161  	if response := s.mapStructure(mux.Vars(r), &paths); response != nil {
   162  		response("invalid path params", logger, w)
   163  		return
   164  	}
   165  
   166  	has, err := s.storer.HasPin(paths.Reference)
   167  	if err != nil {
   168  		logger.Debug("pinned root hash: has pin failed", "chunk_address", paths.Reference, "error", err)
   169  		logger.Error(nil, "pinned root hash: has pin failed")
   170  		jsonhttp.InternalServerError(w, "pinned root hash: check reference failed")
   171  		return
   172  	}
   173  
   174  	if !has {
   175  		jsonhttp.NotFound(w, nil)
   176  		return
   177  	}
   178  
   179  	jsonhttp.OK(w, struct {
   180  		Reference swarm.Address `json:"reference"`
   181  	}{
   182  		Reference: paths.Reference,
   183  	})
   184  }
   185  
   186  // listPinnedRootHashes lists all the references of the pinned root hashes.
   187  func (s *Service) listPinnedRootHashes(w http.ResponseWriter, r *http.Request) {
   188  	logger := s.logger.WithName("get_pins").Build()
   189  
   190  	pinned, err := s.storer.Pins()
   191  	if err != nil {
   192  		logger.Debug("list pinned root references: unable to list references", "error", err)
   193  		logger.Error(nil, "list pinned root references: unable to list references")
   194  		jsonhttp.InternalServerError(w, "list pinned root references failed")
   195  		return
   196  	}
   197  
   198  	jsonhttp.OK(w, struct {
   199  		References []swarm.Address `json:"references"`
   200  	}{
   201  		References: pinned,
   202  	})
   203  }
   204  
   205  type PinIntegrityResponse struct {
   206  	Reference swarm.Address `json:"reference"`
   207  	Total     int           `json:"total"`
   208  	Missing   int           `json:"missing"`
   209  	Invalid   int           `json:"invalid"`
   210  }
   211  
   212  func (s *Service) pinIntegrityHandler(w http.ResponseWriter, r *http.Request) {
   213  	logger := s.logger.WithName("get_pin_integrity").Build()
   214  
   215  	querie := struct {
   216  		Ref swarm.Address `map:"ref"`
   217  	}{}
   218  
   219  	if response := s.mapStructure(r.URL.Query(), &querie); response != nil {
   220  		response("invalid query params", logger, w)
   221  		return
   222  	}
   223  
   224  	out := make(chan storer.PinStat)
   225  
   226  	go s.pinIntegrity.Check(r.Context(), logger, querie.Ref.String(), out)
   227  
   228  	flusher, ok := w.(http.Flusher)
   229  	if !ok {
   230  		http.NotFound(w, r)
   231  		return
   232  	}
   233  
   234  	w.Header().Set("Transfer-Encoding", "chunked")
   235  	w.Header().Set("Content-Type", "application/json; charset=utf-8")
   236  	w.WriteHeader(http.StatusOK)
   237  	flusher.Flush()
   238  
   239  	enc := json.NewEncoder(w)
   240  
   241  	for v := range out {
   242  		resp := PinIntegrityResponse{
   243  			Reference: v.Ref,
   244  			Total:     v.Total,
   245  			Missing:   v.Missing,
   246  			Invalid:   v.Invalid,
   247  		}
   248  		if err := enc.Encode(resp); err != nil {
   249  			break
   250  		}
   251  		flusher.Flush()
   252  	}
   253  }