github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/copyworker.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  	"github.com/google/uuid"
    16  
    17  	"github.com/0chain/gosdk/constants"
    18  	"github.com/0chain/gosdk/core/common"
    19  	"github.com/0chain/gosdk/core/util"
    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 CopyRequest 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  	copyMask       zboxutil.Uint128
    41  	maskMU         *sync.Mutex
    42  	connectionID   string
    43  	timestamp      int64
    44  	Consensus
    45  }
    46  
    47  func (req *CopyRequest) 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 *CopyRequest) copyBlobberObject(
    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.copyMask = req.copyMask.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  			err = formWriter.Close()
    92  			if err != nil {
    93  				return err, false
    94  			}
    95  
    96  			var (
    97  				httpreq  *http.Request
    98  				respBody []byte
    99  				ctx      context.Context
   100  				cncl     context.CancelFunc
   101  			)
   102  
   103  			httpreq, err = zboxutil.NewCopyRequest(blobber.Baseurl, req.allocationID, req.allocationTx, req.sig, body)
   104  			if err != nil {
   105  				l.Logger.Error(blobber.Baseurl, "Error creating rename request", err)
   106  				return
   107  			}
   108  
   109  			httpreq.Header.Add("Content-Type", formWriter.FormDataContentType())
   110  			l.Logger.Info(httpreq.URL.Path)
   111  			ctx, cncl = context.WithTimeout(req.ctx, DefaultUploadTimeOut)
   112  			resp, err = zboxutil.Client.Do(httpreq.WithContext(ctx))
   113  			defer cncl()
   114  
   115  			if err != nil {
   116  				logger.Logger.Error("Copy: ", err)
   117  				return
   118  			}
   119  
   120  			if resp.Body != nil {
   121  				defer resp.Body.Close()
   122  			}
   123  			respBody, err = ioutil.ReadAll(resp.Body)
   124  			if err != nil {
   125  				logger.Logger.Error("Error: Resp ", err)
   126  				return
   127  			}
   128  
   129  			if resp.StatusCode == http.StatusOK {
   130  				l.Logger.Info(blobber.Baseurl, " "+req.remotefilepath, " copied.")
   131  				req.Consensus.Done()
   132  				return
   133  			}
   134  
   135  			latestRespMsg = string(respBody)
   136  			latestStatusCode = resp.StatusCode
   137  
   138  			if resp.StatusCode == http.StatusTooManyRequests {
   139  				logger.Logger.Error("Got too many request error")
   140  				var r int
   141  				r, err = zboxutil.GetRateLimitValue(resp)
   142  				if err != nil {
   143  					logger.Logger.Error(err)
   144  					return
   145  				}
   146  				time.Sleep(time.Duration(r) * time.Second)
   147  				shouldContinue = true
   148  				return
   149  			}
   150  			l.Logger.Error(blobber.Baseurl, "Response: ", string(respBody))
   151  			err = errors.New("response_error", string(respBody))
   152  			return
   153  		}()
   154  
   155  		if err != nil {
   156  			return
   157  		}
   158  		if shouldContinue {
   159  			continue
   160  		}
   161  		return
   162  	}
   163  	return nil, errors.New("unknown_issue",
   164  		fmt.Sprintf("last status code: %d, last response message: %s", latestStatusCode, latestRespMsg))
   165  }
   166  
   167  func (req *CopyRequest) ProcessWithBlobbers() ([]fileref.RefEntity, []error) {
   168  	var pos uint64
   169  	numList := len(req.blobbers)
   170  	objectTreeRefs := make([]fileref.RefEntity, numList)
   171  	blobberErrors := make([]error, numList)
   172  
   173  	wg := &sync.WaitGroup{}
   174  	for i := req.copyMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
   175  		pos = uint64(i.TrailingZeros())
   176  		wg.Add(1)
   177  		go func(blobberIdx int) {
   178  			defer wg.Done()
   179  			refEntity, err := req.copyBlobberObject(req.blobbers[blobberIdx], blobberIdx)
   180  			if err != nil {
   181  				blobberErrors[blobberIdx] = err
   182  				l.Logger.Debug(err.Error())
   183  				return
   184  			}
   185  			objectTreeRefs[blobberIdx] = refEntity
   186  		}(int(pos))
   187  	}
   188  	wg.Wait()
   189  	return objectTreeRefs, blobberErrors
   190  }
   191  
   192  func (req *CopyRequest) ProcessCopy() error {
   193  	defer req.ctxCncl()
   194  
   195  	wg := &sync.WaitGroup{}
   196  	var pos uint64
   197  
   198  	objectTreeRefs, blobberErrors := req.ProcessWithBlobbers()
   199  
   200  	if !req.isConsensusOk() {
   201  		err := zboxutil.MajorError(blobberErrors)
   202  		if err != nil {
   203  			return errors.New("copy_failed", fmt.Sprintf("Copy failed. %s", err.Error()))
   204  		}
   205  
   206  		return errors.New("consensus_not_met",
   207  			fmt.Sprintf("Copy failed. Required consensus %d, got %d",
   208  				req.Consensus.consensusThresh, req.Consensus.consensus))
   209  	}
   210  
   211  	writeMarkerMutex, err := CreateWriteMarkerMutex(client.GetClient(), req.allocationObj)
   212  	if err != nil {
   213  		return fmt.Errorf("Copy failed: %s", err.Error())
   214  	}
   215  	err = writeMarkerMutex.Lock(req.ctx, &req.copyMask, req.maskMU,
   216  		req.blobbers, &req.Consensus, 0, time.Minute, req.connectionID)
   217  	if err != nil {
   218  		return fmt.Errorf("Copy failed: %s", err.Error())
   219  	}
   220  	defer writeMarkerMutex.Unlock(req.ctx, req.copyMask, req.blobbers, time.Minute, req.connectionID) //nolint: errcheck
   221  
   222  	//Check if the allocation is to be repaired or rolled back
   223  	status, _, err := req.allocationObj.CheckAllocStatus()
   224  	if err != nil {
   225  		logger.Logger.Error("Error checking allocation status: ", err)
   226  		return fmt.Errorf("Copy failed: %s", err.Error())
   227  	}
   228  
   229  	if status == Repair {
   230  		logger.Logger.Info("Repairing allocation")
   231  		// // TODO: Need status callback to call repair allocation
   232  		// err = req.allocationObj.RepairAlloc()
   233  		// if err != nil {
   234  		// 	return err
   235  		// }
   236  	}
   237  	if status != Commit {
   238  		return ErrRetryOperation
   239  	}
   240  
   241  	req.Consensus.Reset()
   242  	activeBlobbers := req.copyMask.CountOnes()
   243  	wg.Add(activeBlobbers)
   244  	commitReqs := make([]*CommitRequest, activeBlobbers)
   245  	req.timestamp = int64(common.Now())
   246  	uid := util.GetNewUUID()
   247  	var c int
   248  	for i := req.copyMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
   249  		pos = uint64(i.TrailingZeros())
   250  
   251  		newChange := &allocationchange.CopyFileChange{
   252  			DestPath:   req.destPath,
   253  			Uuid:       uid,
   254  			ObjectTree: objectTreeRefs[pos],
   255  		}
   256  		newChange.NumBlocks = 0
   257  		newChange.Operation = constants.FileOperationCopy
   258  		newChange.Size = 0
   259  		commitReq := &CommitRequest{
   260  			allocationID: req.allocationID,
   261  			allocationTx: req.allocationTx,
   262  			sig:          req.sig,
   263  			blobber:      req.blobbers[pos],
   264  			connectionID: req.connectionID,
   265  			wg:           wg,
   266  			timestamp:    req.timestamp,
   267  		}
   268  
   269  		commitReq.changes = append(commitReq.changes, newChange)
   270  		commitReqs[c] = commitReq
   271  		go AddCommitRequest(commitReq)
   272  		c++
   273  	}
   274  	wg.Wait()
   275  
   276  	for _, commitReq := range commitReqs {
   277  		if commitReq.result != nil {
   278  			if commitReq.result.Success {
   279  				l.Logger.Info("Commit success", commitReq.blobber.Baseurl)
   280  				req.consensus++
   281  			} else {
   282  				l.Logger.Info("Commit failed", commitReq.blobber.Baseurl, commitReq.result.ErrorMessage)
   283  			}
   284  		} else {
   285  			l.Logger.Info("Commit result not set", commitReq.blobber.Baseurl)
   286  		}
   287  	}
   288  
   289  	if !req.isConsensusOk() {
   290  		return errors.New("consensus_not_met",
   291  			fmt.Sprintf("Commit on copy failed. Required consensus %d, got %d",
   292  				req.Consensus.consensusThresh, req.Consensus.consensus))
   293  	}
   294  	return nil
   295  }
   296  
   297  type CopyOperation struct {
   298  	remotefilepath string
   299  	destPath       string
   300  	ctx            context.Context
   301  	ctxCncl        context.CancelFunc
   302  	copyMask       zboxutil.Uint128
   303  	maskMU         *sync.Mutex
   304  
   305  	Consensus
   306  }
   307  
   308  func (co *CopyOperation) Process(allocObj *Allocation, connectionID string) ([]fileref.RefEntity, zboxutil.Uint128, error) {
   309  	// make copyRequest object
   310  	cR := &CopyRequest{
   311  		allocationObj:  allocObj,
   312  		allocationID:   allocObj.ID,
   313  		allocationTx:   allocObj.Tx,
   314  		sig:            allocObj.sig,
   315  		connectionID:   connectionID,
   316  		blobbers:       allocObj.Blobbers,
   317  		remotefilepath: co.remotefilepath,
   318  		destPath:       co.destPath,
   319  		ctx:            co.ctx,
   320  		ctxCncl:        co.ctxCncl,
   321  		copyMask:       co.copyMask,
   322  		maskMU:         co.maskMU,
   323  		Consensus:      Consensus{RWMutex: &sync.RWMutex{}},
   324  	}
   325  
   326  	cR.consensusThresh = co.consensusThresh
   327  	cR.fullconsensus = co.fullconsensus
   328  
   329  	objectTreeRefs, blobberErrors := cR.ProcessWithBlobbers()
   330  
   331  	if !cR.isConsensusOk() {
   332  		l.Logger.Error("copy failed: ", cR.remotefilepath, cR.destPath)
   333  		err := zboxutil.MajorError(blobberErrors)
   334  		if err != nil {
   335  			return nil, cR.copyMask, errors.New("copy_failed", fmt.Sprintf("Copy failed. %s", err.Error()))
   336  		}
   337  
   338  		return nil, cR.copyMask, errors.New("consensus_not_met",
   339  			fmt.Sprintf("Copy failed. Required consensus %d, got %d",
   340  				cR.Consensus.consensusThresh, cR.Consensus.consensus))
   341  	}
   342  	return objectTreeRefs, cR.copyMask, nil
   343  
   344  }
   345  
   346  func (co *CopyOperation) buildChange(refs []fileref.RefEntity, uid uuid.UUID) []allocationchange.AllocationChange {
   347  
   348  	changes := make([]allocationchange.AllocationChange, len(refs))
   349  
   350  	for idx, ref := range refs {
   351  		if ref == nil {
   352  			change := &allocationchange.EmptyFileChange{}
   353  			changes[idx] = change
   354  			continue
   355  		}
   356  		newChange := &allocationchange.CopyFileChange{
   357  			DestPath:   co.destPath,
   358  			Uuid:       uid,
   359  			ObjectTree: ref,
   360  		}
   361  		newChange.Operation = constants.FileOperationCopy
   362  		changes[idx] = newChange
   363  	}
   364  	return changes
   365  }
   366  
   367  func (co *CopyOperation) Verify(a *Allocation) error {
   368  
   369  	if !a.CanCopy() {
   370  		return constants.ErrFileOptionNotPermitted
   371  	}
   372  
   373  	if co.remotefilepath == "" || co.destPath == "" {
   374  		return errors.New("invalid_path", "Invalid path for copy")
   375  	}
   376  	isabs := zboxutil.IsRemoteAbs(co.remotefilepath)
   377  	if !isabs {
   378  		return errors.New("invalid_path", "Path should be valid and absolute")
   379  	}
   380  
   381  	err := ValidateRemoteFileName(co.destPath)
   382  	if err != nil {
   383  		return err
   384  	}
   385  	return nil
   386  }
   387  
   388  func (co *CopyOperation) Completed(allocObj *Allocation) {
   389  
   390  }
   391  
   392  func (co *CopyOperation) Error(allocObj *Allocation, consensus int, err error) {
   393  
   394  }
   395  
   396  func NewCopyOperation(remotePath string, destPath string, copyMask zboxutil.Uint128, maskMU *sync.Mutex, consensusTh int, fullConsensus int, ctx context.Context) *CopyOperation {
   397  	co := &CopyOperation{}
   398  	co.remotefilepath = zboxutil.RemoteClean(remotePath)
   399  	co.copyMask = copyMask
   400  	co.maskMU = maskMU
   401  	co.consensusThresh = consensusTh
   402  	co.fullconsensus = fullConsensus
   403  	if destPath != "/" {
   404  		destPath = strings.TrimSuffix(destPath, "/")
   405  	}
   406  	co.destPath = destPath
   407  	co.ctx, co.ctxCncl = context.WithCancel(ctx)
   408  	return co
   409  
   410  }