github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/deleteworker.go (about) 1 package sdk 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "github.com/0chain/errors" 15 thrown "github.com/0chain/errors" 16 "github.com/google/uuid" 17 18 "github.com/0chain/gosdk/constants" 19 "github.com/0chain/gosdk/core/common" 20 "github.com/0chain/gosdk/zboxcore/allocationchange" 21 "github.com/0chain/gosdk/zboxcore/blockchain" 22 "github.com/0chain/gosdk/zboxcore/client" 23 "github.com/0chain/gosdk/zboxcore/fileref" 24 "github.com/0chain/gosdk/zboxcore/logger" 25 l "github.com/0chain/gosdk/zboxcore/logger" 26 "github.com/0chain/gosdk/zboxcore/zboxutil" 27 ) 28 29 type DeleteRequest struct { 30 allocationObj *Allocation 31 allocationID string 32 allocationTx string 33 sig string 34 blobbers []*blockchain.StorageNode 35 remotefilepath string 36 ctx context.Context 37 ctxCncl context.CancelFunc 38 wg *sync.WaitGroup 39 deleteMask zboxutil.Uint128 40 maskMu *sync.Mutex 41 connectionID string 42 consensus Consensus 43 timestamp int64 44 } 45 46 func (req *DeleteRequest) deleteBlobberFile( 47 blobber *blockchain.StorageNode, blobberIdx int) error { 48 49 var err error 50 51 defer func() { 52 if err != nil { 53 logger.Logger.Error(err) 54 req.maskMu.Lock() 55 req.deleteMask = req.deleteMask.And(zboxutil.NewUint128(1).Lsh(uint64(blobberIdx)).Not()) 56 req.maskMu.Unlock() 57 } 58 }() 59 60 query := &url.Values{} 61 62 query.Add("connection_id", req.connectionID) 63 query.Add("path", req.remotefilepath) 64 65 httpreq, err := zboxutil.NewDeleteRequest(blobber.Baseurl, req.allocationID, req.allocationTx, req.sig, query) 66 if err != nil { 67 l.Logger.Error(blobber.Baseurl, "Error creating delete request", err) 68 return err 69 } 70 71 var ( 72 resp *http.Response 73 shouldContinue bool 74 ) 75 76 for i := 0; i < 3; i++ { 77 err, shouldContinue = func() (err error, shouldContinue bool) { 78 ctx, cncl := context.WithTimeout(req.ctx, 2*time.Minute) 79 resp, err = zboxutil.Client.Do(httpreq.WithContext(ctx)) 80 defer cncl() 81 82 if err != nil { 83 if err == context.Canceled { 84 logger.Logger.Error("context was cancelled") 85 shouldContinue = true 86 return 87 } 88 if err == io.EOF { 89 shouldContinue = true 90 return 91 } 92 logger.Logger.Error(blobber.Baseurl, "Delete: ", err) 93 return 94 } 95 96 if resp.Body != nil { 97 defer resp.Body.Close() 98 } 99 var respBody []byte 100 101 if resp.StatusCode == http.StatusOK { 102 req.consensus.Done() 103 l.Logger.Debug(blobber.Baseurl, " "+req.remotefilepath, " deleted.") 104 return 105 } 106 if resp.StatusCode == http.StatusBadRequest { 107 body, err := ioutil.ReadAll(resp.Body) 108 if err!= nil { 109 logger.Logger.Error("Failed to read response body", err) 110 } 111 112 // Check for the specific content in the response body 113 if string(body) == "file was deleted" { 114 req.consensus.Done() 115 l.Logger.Debug(blobber.Baseurl, " ", req.remotefilepath, " deleted.") 116 } 117 } 118 119 if resp.StatusCode == http.StatusTooManyRequests { 120 logger.Logger.Error("Got too many request error") 121 var r int 122 r, err = zboxutil.GetRateLimitValue(resp) 123 if err != nil { 124 logger.Logger.Error(err) 125 return 126 } 127 time.Sleep(time.Duration(r) * time.Second) 128 shouldContinue = true 129 return 130 } 131 132 if resp.StatusCode == http.StatusNoContent { 133 req.consensus.Done() 134 l.Logger.Info(blobber.Baseurl, " "+req.remotefilepath, " not available in blobber.") 135 return 136 } 137 138 respBody, err = ioutil.ReadAll(resp.Body) 139 if err != nil { 140 l.Logger.Error(blobber.Baseurl, "Response: ", string(respBody)) 141 return 142 } 143 144 err = errors.New("response_error", fmt.Sprintf("unexpected response with status code %d, message: %s", 145 resp.StatusCode, string(respBody))) 146 return 147 }() 148 149 if err != nil { 150 return err 151 } 152 153 if shouldContinue { 154 continue 155 } 156 return nil 157 } 158 return errors.New("unknown_issue", 159 fmt.Sprintf("latest response code: %d", resp.StatusCode)) 160 } 161 162 func (req *DeleteRequest) getObjectTreeFromBlobber(pos uint64) ( 163 fRefEntity fileref.RefEntity, err error) { 164 165 defer func() { 166 if err != nil { 167 req.maskMu.Lock() 168 req.deleteMask = req.deleteMask.And(zboxutil.NewUint128(1).Lsh(pos).Not()) 169 req.maskMu.Unlock() 170 } 171 }() 172 173 fRefEntity, err = getObjectTreeFromBlobber( 174 req.ctx, req.allocationID, req.allocationTx, req.sig, 175 req.remotefilepath, req.blobbers[pos]) 176 return 177 } 178 179 func (req *DeleteRequest) getFileMetaFromBlobber(pos uint64) (fileRef *fileref.FileRef, err error) { 180 defer func() { 181 if err != nil { 182 req.maskMu.Lock() 183 req.deleteMask = req.deleteMask.And(zboxutil.NewUint128(1).Lsh(pos).Not()) 184 req.maskMu.Unlock() 185 } 186 }() 187 listReq := &ListRequest{ 188 allocationID: req.allocationID, 189 allocationTx: req.allocationTx, 190 blobbers: req.blobbers, 191 remotefilepath: req.remotefilepath, 192 ctx: req.ctx, 193 } 194 respChan := make(chan *fileMetaResponse) 195 go listReq.getFileMetaInfoFromBlobber(req.blobbers[pos], int(pos), respChan) 196 refRes := <-respChan 197 if refRes.err != nil { 198 err = refRes.err 199 return 200 } 201 fileRef = refRes.fileref 202 return 203 } 204 205 func (req *DeleteRequest) ProcessDelete() (err error) { 206 defer req.ctxCncl() 207 208 objectTreeRefs := make([]fileref.RefEntity, len(req.blobbers)) 209 var deleteMutex sync.Mutex 210 removedNum := 0 211 req.wg = &sync.WaitGroup{} 212 213 var pos uint64 214 for i := req.deleteMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 215 req.wg.Add(1) 216 pos = uint64(i.TrailingZeros()) 217 go func(blobberIdx uint64) { 218 defer req.wg.Done() 219 refEntity, err := req.getFileMetaFromBlobber(blobberIdx) 220 if err == nil { 221 req.consensus.Done() 222 objectTreeRefs[blobberIdx] = refEntity 223 return 224 } 225 //it was removed from the blobber 226 if errors.Is(err, constants.ErrNotFound) { 227 req.consensus.Done() 228 deleteMutex.Lock() 229 removedNum++ 230 deleteMutex.Unlock() 231 return 232 } 233 234 l.Logger.Error(err.Error()) 235 }(pos) 236 } 237 req.wg.Wait() 238 239 req.consensus.consensus = removedNum 240 241 var errCount int32 242 wgErrors := make(chan error) 243 wgDone := make(chan bool) 244 245 for i := req.deleteMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 246 req.wg.Add(1) 247 pos = uint64(i.TrailingZeros()) 248 go func(blobberIdx uint64) { 249 defer req.wg.Done() 250 err = req.deleteBlobberFile(req.blobbers[blobberIdx], int(blobberIdx)) 251 if err != nil { 252 logger.Logger.Error("error during deleteBlobberFile", err) 253 errC := atomic.AddInt32(&errCount, 1) 254 if errC > int32(req.consensus.fullconsensus-req.consensus.consensusThresh) { 255 wgErrors <- err 256 } 257 } 258 }(pos) 259 } 260 261 go func() { 262 req.wg.Wait() 263 close(wgDone) 264 }() 265 266 select { 267 case <-wgDone: 268 break 269 case err := <-wgErrors: 270 return thrown.New("delete_failed", fmt.Sprintf("Delete failed. %s", err.Error())) 271 } 272 273 if !req.consensus.isConsensusOk() { 274 return errors.New("consensus_not_met", 275 fmt.Sprintf("Consensus on delete failed. Required consensus %d got %d", 276 req.consensus.consensusThresh, req.consensus.getConsensus())) 277 } 278 279 writeMarkerMutex, err := CreateWriteMarkerMutex(client.GetClient(), req.allocationObj) 280 if err != nil { 281 return fmt.Errorf("Delete failed: %s", err.Error()) 282 } 283 err = writeMarkerMutex.Lock( 284 req.ctx, &req.deleteMask, req.maskMu, 285 req.blobbers, &req.consensus, removedNum, time.Minute, req.connectionID) 286 287 if err != nil { 288 return fmt.Errorf("Delete failed: %s", err.Error()) 289 } 290 defer writeMarkerMutex.Unlock(req.ctx, req.deleteMask, req.blobbers, time.Minute, req.connectionID) //nolint: errcheck 291 292 req.consensus.consensus = removedNum 293 req.timestamp = int64(common.Now()) 294 wg := &sync.WaitGroup{} 295 activeBlobbers := req.deleteMask.CountOnes() 296 wg.Add(activeBlobbers) 297 commitReqs := make([]*CommitRequest, activeBlobbers) 298 var c int 299 for i := req.deleteMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 300 pos = uint64(i.TrailingZeros()) 301 newChange := &allocationchange.DeleteFileChange{} 302 newChange.FileMetaRef = objectTreeRefs[pos] 303 newChange.NumBlocks = newChange.FileMetaRef.GetNumBlocks() 304 newChange.Operation = constants.FileOperationDelete 305 newChange.Size = newChange.FileMetaRef.GetSize() 306 commitReq := &CommitRequest{ 307 allocationID: req.allocationID, 308 allocationTx: req.allocationTx, 309 sig: req.sig, 310 blobber: req.blobbers[pos], 311 connectionID: req.connectionID, 312 wg: wg, 313 timestamp: req.timestamp, 314 } 315 316 commitReq.changes = append(commitReq.changes, newChange) 317 commitReqs[c] = commitReq 318 go AddCommitRequest(commitReq) 319 c++ 320 } 321 wg.Wait() 322 323 for _, commitReq := range commitReqs { 324 if commitReq.result != nil { 325 if commitReq.result.Success { 326 l.Logger.Info("Commit success", commitReq.blobber.Baseurl) 327 req.consensus.Done() 328 } else { 329 l.Logger.Info("Commit failed", commitReq.blobber.Baseurl, commitReq.result.ErrorMessage) 330 } 331 } else { 332 l.Logger.Info("Commit result not set", commitReq.blobber.Baseurl) 333 } 334 } 335 336 if !req.consensus.isConsensusOk() { 337 return errors.New("consensus_not_met", 338 fmt.Sprintf("Consensus on commit not met. Required %d, got %d", 339 req.consensus.consensusThresh, req.consensus.getConsensus())) 340 } 341 return nil 342 } 343 344 type DeleteOperation struct { 345 remotefilepath string 346 ctx context.Context 347 ctxCncl context.CancelFunc 348 deleteMask zboxutil.Uint128 349 maskMu *sync.Mutex 350 consensus Consensus 351 } 352 353 func (dop *DeleteOperation) Process(allocObj *Allocation, connectionID string) ([]fileref.RefEntity, zboxutil.Uint128, error) { 354 l.Logger.Info("Started Delete Process with Connection Id", connectionID) 355 deleteReq := &DeleteRequest{ 356 allocationObj: allocObj, 357 allocationID: allocObj.ID, 358 allocationTx: allocObj.Tx, 359 sig: allocObj.sig, 360 connectionID: connectionID, 361 blobbers: allocObj.Blobbers, 362 remotefilepath: dop.remotefilepath, 363 ctx: dop.ctx, 364 ctxCncl: dop.ctxCncl, 365 deleteMask: dop.deleteMask, 366 maskMu: dop.maskMu, 367 wg: &sync.WaitGroup{}, 368 consensus: Consensus{RWMutex: &sync.RWMutex{}}, 369 } 370 deleteReq.consensus.fullconsensus = dop.consensus.fullconsensus 371 deleteReq.consensus.consensusThresh = dop.consensus.consensusThresh 372 373 numList := len(deleteReq.blobbers) 374 objectTreeRefs := make([]fileref.RefEntity, numList) 375 blobberErrors := make([]error, numList) 376 377 var pos uint64 378 379 for i := deleteReq.deleteMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) { 380 pos = uint64(i.TrailingZeros()) 381 deleteReq.wg.Add(1) 382 go func(blobberIdx int) { 383 defer deleteReq.wg.Done() 384 refEntity, err := deleteReq.getFileMetaFromBlobber(uint64(blobberIdx)) 385 if errors.Is(err, constants.ErrNotFound) { 386 deleteReq.consensus.Done() 387 return 388 } else if err != nil { 389 blobberErrors[blobberIdx] = err 390 l.Logger.Error(err.Error()) 391 return 392 } 393 err = deleteReq.deleteBlobberFile(deleteReq.blobbers[blobberIdx], blobberIdx) 394 if err != nil { 395 blobberErrors[blobberIdx] = err 396 } 397 if singleClientMode { 398 lookuphash := fileref.GetReferenceLookup(deleteReq.allocationID, deleteReq.remotefilepath) 399 cacheKey := fileref.GetCacheKey(lookuphash, deleteReq.blobbers[blobberIdx].ID) 400 fileref.DeleteFileRef(cacheKey) 401 } 402 objectTreeRefs[blobberIdx] = refEntity 403 }(int(pos)) 404 } 405 deleteReq.wg.Wait() 406 407 if !deleteReq.consensus.isConsensusOk() { 408 err := zboxutil.MajorError(blobberErrors) 409 if err != nil { 410 return nil, deleteReq.deleteMask, thrown.New("delete_failed", fmt.Sprintf("Delete failed. %s", err.Error())) 411 } 412 413 return nil, deleteReq.deleteMask, thrown.New("consensus_not_met", 414 fmt.Sprintf("Delete failed. Required consensus %d, got %d", 415 deleteReq.consensus.consensusThresh, deleteReq.consensus.consensus)) 416 } 417 l.Logger.Debug("Delete Process Ended ") 418 return objectTreeRefs, deleteReq.deleteMask, nil 419 } 420 421 func (do *DeleteOperation) buildChange(refs []fileref.RefEntity, uid uuid.UUID) []allocationchange.AllocationChange { 422 423 changes := make([]allocationchange.AllocationChange, len(refs)) 424 for idx, ref := range refs { 425 if ref == nil { 426 newChange := &allocationchange.EmptyFileChange{} 427 changes[idx] = newChange 428 } else { 429 newChange := &allocationchange.DeleteFileChange{} 430 newChange.FileMetaRef = ref 431 newChange.NumBlocks = newChange.FileMetaRef.GetNumBlocks() 432 newChange.Operation = constants.FileOperationDelete 433 newChange.Size = newChange.FileMetaRef.GetSize() 434 changes[idx] = newChange 435 } 436 } 437 return changes 438 } 439 440 func (dop *DeleteOperation) Verify(a *Allocation) error { 441 442 if !a.CanDelete() { 443 return constants.ErrFileOptionNotPermitted 444 } 445 446 if dop.remotefilepath == "" { 447 return errors.New("invalid_path", "Invalid path for the list") 448 } 449 isabs := zboxutil.IsRemoteAbs(dop.remotefilepath) 450 if !isabs { 451 return errors.New("invalid_path", "Path should be valid and absolute") 452 } 453 return nil 454 } 455 456 func (dop *DeleteOperation) Completed(allocObj *Allocation) { 457 458 } 459 460 func (dop *DeleteOperation) Error(allocObj *Allocation, consensus int, err error) { 461 462 } 463 464 func NewDeleteOperation(remotePath string, deleteMask zboxutil.Uint128, maskMu *sync.Mutex, consensusTh int, fullConsensus int, ctx context.Context) *DeleteOperation { 465 dop := &DeleteOperation{} 466 dop.remotefilepath = zboxutil.RemoteClean(remotePath) 467 dop.deleteMask = deleteMask 468 dop.maskMu = maskMu 469 dop.consensus.consensusThresh = consensusTh 470 dop.consensus.fullconsensus = fullConsensus 471 dop.ctx, dop.ctxCncl = context.WithCancel(ctx) 472 return dop 473 }