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 }