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  }