github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/renameworker.go (about)

     1  package sdk
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"mime/multipart"
     9  	"net/http"
    10  	"path/filepath"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/0chain/errors"
    15  	"github.com/google/uuid"
    16  
    17  	"github.com/0chain/gosdk/constants"
    18  	"github.com/0chain/gosdk/core/common"
    19  	"github.com/0chain/gosdk/zboxcore/client"
    20  	"github.com/0chain/gosdk/zboxcore/fileref"
    21  	"github.com/0chain/gosdk/zboxcore/logger"
    22  
    23  	"github.com/0chain/gosdk/zboxcore/allocationchange"
    24  	"github.com/0chain/gosdk/zboxcore/blockchain"
    25  	l "github.com/0chain/gosdk/zboxcore/logger"
    26  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    27  )
    28  
    29  type RenameRequest struct {
    30  	allocationObj  *Allocation
    31  	allocationID   string
    32  	allocationTx   string
    33  	sig            string
    34  	blobbers       []*blockchain.StorageNode
    35  	remotefilepath string
    36  	newName        string
    37  	ctx            context.Context
    38  	ctxCncl        context.CancelFunc
    39  	wg             *sync.WaitGroup
    40  	renameMask     zboxutil.Uint128
    41  	maskMU         *sync.Mutex
    42  	connectionID   string
    43  	consensus      Consensus
    44  	timestamp      int64
    45  }
    46  
    47  func (req *RenameRequest) getObjectTreeFromBlobber(blobber *blockchain.StorageNode) (fileref.RefEntity, error) {
    48  	return getObjectTreeFromBlobber(req.ctx, req.allocationID, req.allocationTx, req.sig, req.remotefilepath, blobber)
    49  }
    50  
    51  func (req *RenameRequest) renameBlobberObject(
    52  	blobber *blockchain.StorageNode, blobberIdx int) (refEntity fileref.RefEntity, err error) {
    53  
    54  	defer func() {
    55  		if err != nil {
    56  			req.maskMU.Lock()
    57  			req.renameMask = req.renameMask.And(zboxutil.NewUint128(1).Lsh(uint64(blobberIdx)).Not())
    58  			req.maskMU.Unlock()
    59  		}
    60  	}()
    61  
    62  	refEntity, err = req.getObjectTreeFromBlobber(req.blobbers[blobberIdx])
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	var (
    68  		resp             *http.Response
    69  		shouldContinue   bool
    70  		latestRespMsg    string
    71  		latestStatusCode int
    72  	)
    73  
    74  	for i := 0; i < 3; i++ {
    75  		err, shouldContinue = func() (err error, shouldContinue bool) {
    76  			body := new(bytes.Buffer)
    77  			formWriter := multipart.NewWriter(body)
    78  
    79  			err = formWriter.WriteField("connection_id", req.connectionID)
    80  			if err != nil {
    81  				return err, false
    82  			}
    83  
    84  			err = formWriter.WriteField("path", req.remotefilepath)
    85  			if err != nil {
    86  				return err, false
    87  			}
    88  
    89  			err = formWriter.WriteField("new_name", req.newName)
    90  			if err != nil {
    91  				return err, false
    92  			}
    93  
    94  			formWriter.Close()
    95  
    96  			var httpreq *http.Request
    97  			httpreq, err = zboxutil.NewRenameRequest(blobber.Baseurl, req.allocationID, req.allocationTx, req.sig, body)
    98  			if err != nil {
    99  				l.Logger.Error(blobber.Baseurl, "Error creating rename request", err)
   100  				return
   101  			}
   102  
   103  			httpreq.Header.Add("Content-Type", formWriter.FormDataContentType())
   104  			ctx, cncl := context.WithTimeout(req.ctx, DefaultUploadTimeOut)
   105  			resp, err = zboxutil.Client.Do(httpreq.WithContext(ctx))
   106  			defer cncl()
   107  
   108  			if err != nil {
   109  				logger.Logger.Error("Rename: ", err)
   110  				return
   111  			}
   112  
   113  			if resp.Body != nil {
   114  				defer resp.Body.Close()
   115  			}
   116  			var respBody []byte
   117  			respBody, err = ioutil.ReadAll(resp.Body)
   118  			if err != nil {
   119  				logger.Logger.Error("Error: Resp ", err)
   120  				return
   121  			}
   122  
   123  			latestRespMsg = string(respBody)
   124  			latestStatusCode = resp.StatusCode
   125  
   126  			if resp.StatusCode == http.StatusOK {
   127  				req.consensus.Done()
   128  				l.Logger.Info(blobber.Baseurl, " "+req.remotefilepath, " renamed.")
   129  				return
   130  			}
   131  
   132  			if resp.StatusCode == http.StatusTooManyRequests {
   133  				logger.Logger.Error("Got too many request error")
   134  				var r int
   135  				r, err = zboxutil.GetRateLimitValue(resp)
   136  				if err != nil {
   137  					logger.Logger.Error(err)
   138  					return
   139  				}
   140  				time.Sleep(time.Duration(r) * time.Second)
   141  				shouldContinue = true
   142  				return
   143  			}
   144  			l.Logger.Error(blobber.Baseurl, "Response: ", string(respBody))
   145  			err = errors.New("response_error", string(respBody))
   146  			return
   147  		}()
   148  
   149  		if err != nil {
   150  			return
   151  		}
   152  		if shouldContinue {
   153  			continue
   154  		}
   155  		return
   156  	}
   157  
   158  	err = errors.New("unknown_issue",
   159  		fmt.Sprintf("last status code: %d, last response message: %s", latestStatusCode, latestRespMsg))
   160  	return
   161  }
   162  
   163  func (req *RenameRequest) ProcessWithBlobbers() ([]fileref.RefEntity, []error) {
   164  	var pos uint64
   165  	numList := len(req.blobbers)
   166  	objectTreeRefs := make([]fileref.RefEntity, numList)
   167  	blobberErrors := make([]error, numList)
   168  	req.wg = &sync.WaitGroup{}
   169  	for i := req.renameMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
   170  		pos = uint64(i.TrailingZeros())
   171  		req.wg.Add(1)
   172  		go func(blobberIdx int) {
   173  			defer req.wg.Done()
   174  			refEntity, err := req.renameBlobberObject(req.blobbers[blobberIdx], blobberIdx)
   175  			if err != nil {
   176  				blobberErrors[blobberIdx] = err
   177  				l.Logger.Error(err.Error())
   178  				return
   179  			}
   180  			objectTreeRefs[blobberIdx] = refEntity
   181  		}(int(pos))
   182  	}
   183  	req.wg.Wait()
   184  	return objectTreeRefs, blobberErrors
   185  }
   186  
   187  func (req *RenameRequest) ProcessRename() error {
   188  	defer req.ctxCncl()
   189  
   190  	objectTreeRefs, blobberErrors := req.ProcessWithBlobbers()
   191  
   192  	if !req.consensus.isConsensusOk() {
   193  		err := zboxutil.MajorError(blobberErrors)
   194  		if err != nil {
   195  			return errors.New("rename_failed",
   196  				fmt.Sprintf("Rename failed. %s", err.Error()))
   197  		}
   198  
   199  		return errors.New("consensus_not_met",
   200  			fmt.Sprintf("Rename failed. Required consensus %d got %d",
   201  				req.consensus.consensusThresh, req.consensus.getConsensus()))
   202  	}
   203  
   204  	writeMarkerMutex, err := CreateWriteMarkerMutex(client.GetClient(), req.allocationObj)
   205  	if err != nil {
   206  		return fmt.Errorf("rename failed: %s", err.Error())
   207  	}
   208  
   209  	err = writeMarkerMutex.Lock(req.ctx, &req.renameMask,
   210  		req.maskMU, req.blobbers, &req.consensus, 0, time.Minute, req.connectionID)
   211  	if err != nil {
   212  		return fmt.Errorf("rename failed: %s", err.Error())
   213  	}
   214  	defer writeMarkerMutex.Unlock(req.ctx, req.renameMask, req.blobbers, time.Minute, req.connectionID) //nolint: errcheck
   215  
   216  	//Check if the allocation is to be repaired or rolled back
   217  	status, _, err := req.allocationObj.CheckAllocStatus()
   218  	if err != nil {
   219  		logger.Logger.Error("Error checking allocation status: ", err)
   220  		return fmt.Errorf("rename failed: %s", err.Error())
   221  	}
   222  
   223  	if status == Repair {
   224  		logger.Logger.Info("Repairing allocation")
   225  		//TODO: Need status callback to call repair allocation
   226  		// err = req.allocationObj.RepairAlloc()
   227  		// if err != nil {
   228  		// 	return err
   229  		// }
   230  	}
   231  	if status != Commit {
   232  		return ErrRetryOperation
   233  	}
   234  
   235  	req.consensus.Reset()
   236  	req.timestamp = int64(common.Now())
   237  	activeBlobbers := req.renameMask.CountOnes()
   238  	wg := &sync.WaitGroup{}
   239  	wg.Add(activeBlobbers)
   240  	commitReqs := make([]*CommitRequest, activeBlobbers)
   241  	var pos uint64
   242  	var counter int
   243  	for i := req.renameMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
   244  		pos = uint64(i.TrailingZeros())
   245  
   246  		newChange := &allocationchange.RenameFileChange{
   247  			NewName:    req.newName,
   248  			ObjectTree: objectTreeRefs[pos],
   249  		}
   250  		newChange.Operation = constants.FileOperationRename
   251  		newChange.Size = 0
   252  
   253  		commitReq := &CommitRequest{
   254  			allocationID: req.allocationID,
   255  			allocationTx: req.allocationTx,
   256  			sig:          req.sig,
   257  			blobber:      req.blobbers[pos],
   258  			connectionID: req.connectionID,
   259  			wg:           wg,
   260  			timestamp:    req.timestamp,
   261  		}
   262  		commitReq.changes = append(commitReq.changes, newChange)
   263  		commitReqs[counter] = commitReq
   264  
   265  		go AddCommitRequest(commitReq)
   266  
   267  		counter++
   268  	}
   269  
   270  	wg.Wait()
   271  
   272  	var errMessages string
   273  	for _, commitReq := range commitReqs {
   274  		if commitReq.result != nil {
   275  			if commitReq.result.Success {
   276  				l.Logger.Info("Commit success", commitReq.blobber.Baseurl)
   277  				req.consensus.Done()
   278  			} else {
   279  				errMessages += commitReq.result.ErrorMessage + "\t"
   280  				l.Logger.Info("Commit failed", commitReq.blobber.Baseurl, commitReq.result.ErrorMessage)
   281  			}
   282  		} else {
   283  			l.Logger.Info("Commit result not set", commitReq.blobber.Baseurl)
   284  		}
   285  	}
   286  
   287  	if !req.consensus.isConsensusOk() {
   288  		return errors.New("consensus_not_met",
   289  			fmt.Sprintf("Required consensus %d got %d. Error: %s",
   290  				req.consensus.consensusThresh, req.consensus.consensus, errMessages))
   291  	}
   292  	return nil
   293  }
   294  
   295  type RenameOperation struct {
   296  	remotefilepath string
   297  	ctx            context.Context
   298  	ctxCncl        context.CancelFunc
   299  	renameMask     zboxutil.Uint128
   300  	newName        string
   301  	maskMU         *sync.Mutex
   302  
   303  	consensus Consensus
   304  }
   305  
   306  func (ro *RenameOperation) Process(allocObj *Allocation, connectionID string) ([]fileref.RefEntity, zboxutil.Uint128, error) {
   307  	// make renameRequest object
   308  	rR := &RenameRequest{
   309  		allocationObj:  allocObj,
   310  		allocationID:   allocObj.ID,
   311  		allocationTx:   allocObj.Tx,
   312  		sig:            allocObj.sig,
   313  		connectionID:   connectionID,
   314  		blobbers:       allocObj.Blobbers,
   315  		remotefilepath: ro.remotefilepath,
   316  		newName:        ro.newName,
   317  		ctx:            ro.ctx,
   318  		ctxCncl:        ro.ctxCncl,
   319  		renameMask:     ro.renameMask,
   320  		maskMU:         ro.maskMU,
   321  		wg:             &sync.WaitGroup{},
   322  		consensus:      Consensus{RWMutex: &sync.RWMutex{}},
   323  	}
   324  	if filepath.Base(ro.remotefilepath) == ro.newName {
   325  		return nil, ro.renameMask, errors.New("invalid_operation", "Cannot rename to same name")
   326  	}
   327  	rR.consensus.fullconsensus = ro.consensus.fullconsensus
   328  	rR.consensus.consensusThresh = ro.consensus.consensusThresh
   329  
   330  	objectTreeRefs, blobberErrors := rR.ProcessWithBlobbers()
   331  
   332  	if !rR.consensus.isConsensusOk() {
   333  		err := zboxutil.MajorError(blobberErrors)
   334  		if err != nil {
   335  			return nil, rR.renameMask, errors.New("rename_failed", fmt.Sprintf("Renamed failed. %s", err.Error()))
   336  		}
   337  
   338  		return nil, rR.renameMask, errors.New("consensus_not_met",
   339  			fmt.Sprintf("Rename failed. Required consensus %d, got %d",
   340  				rR.consensus.consensusThresh, rR.consensus.consensus))
   341  	}
   342  	l.Logger.Info("Rename Processs Ended ")
   343  	return objectTreeRefs, rR.renameMask, nil
   344  }
   345  
   346  func (ro *RenameOperation) buildChange(refs []fileref.RefEntity, uid uuid.UUID) []allocationchange.AllocationChange {
   347  	changes := make([]allocationchange.AllocationChange, len(refs))
   348  
   349  	for idx, ref := range refs {
   350  		if ref == nil {
   351  			change := &allocationchange.EmptyFileChange{}
   352  			changes[idx] = change
   353  			continue
   354  		}
   355  		newChange := &allocationchange.RenameFileChange{
   356  			NewName:    ro.newName,
   357  			ObjectTree: ref,
   358  		}
   359  
   360  		newChange.Operation = constants.FileOperationRename
   361  		newChange.Size = 0
   362  		changes[idx] = newChange
   363  	}
   364  	return changes
   365  }
   366  
   367  func (ro *RenameOperation) Verify(a *Allocation) error {
   368  
   369  	if !a.CanRename() {
   370  		return constants.ErrFileOptionNotPermitted
   371  	}
   372  
   373  	if ro.remotefilepath == "" {
   374  		return errors.New("invalid_path", "Invalid path for the list")
   375  	}
   376  
   377  	if ro.remotefilepath == "/" {
   378  		return errors.New("invalid_operation", "cannot rename root path")
   379  	}
   380  
   381  	isabs := zboxutil.IsRemoteAbs(ro.remotefilepath)
   382  	if !isabs {
   383  		return errors.New("invalid_path", "Path should be valid and absolute")
   384  	}
   385  
   386  	err := ValidateRemoteFileName(ro.newName)
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	return nil
   392  }
   393  
   394  func (ro *RenameOperation) Completed(allocObj *Allocation) {
   395  
   396  }
   397  
   398  func (ro *RenameOperation) Error(allocObj *Allocation, consensus int, err error) {
   399  
   400  }
   401  
   402  func NewRenameOperation(remotePath string, destName string, renameMask zboxutil.Uint128, maskMU *sync.Mutex, consensusTh int, fullConsensus int, ctx context.Context) *RenameOperation {
   403  	ro := &RenameOperation{}
   404  	ro.remotefilepath = zboxutil.RemoteClean(remotePath)
   405  	ro.newName = destName
   406  	ro.renameMask = renameMask
   407  	ro.maskMU = maskMU
   408  	ro.consensus.consensusThresh = consensusTh
   409  	ro.consensus.fullconsensus = fullConsensus
   410  	ro.ctx, ro.ctxCncl = context.WithCancel(ctx)
   411  	return ro
   412  
   413  }