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

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