github.com/0chain/gosdk@v1.17.11/zboxcore/sdk/dirworker.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"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/0chain/errors"
    16  	"github.com/0chain/gosdk/core/common"
    17  	"github.com/0chain/gosdk/core/util"
    18  	"github.com/0chain/gosdk/zboxcore/allocationchange"
    19  	"github.com/0chain/gosdk/zboxcore/blockchain"
    20  	"github.com/0chain/gosdk/zboxcore/client"
    21  	"github.com/0chain/gosdk/zboxcore/fileref"
    22  	"github.com/0chain/gosdk/zboxcore/logger"
    23  	l "github.com/0chain/gosdk/zboxcore/logger"
    24  	"github.com/0chain/gosdk/zboxcore/zboxutil"
    25  	"github.com/google/uuid"
    26  )
    27  
    28  const (
    29  	DirectoryExists = "directory_exists"
    30  )
    31  
    32  type DirRequest struct {
    33  	allocationObj *Allocation
    34  	allocationID  string
    35  	allocationTx  string
    36  	sig           string
    37  	remotePath    string
    38  	blobbers      []*blockchain.StorageNode
    39  	ctx           context.Context
    40  	ctxCncl       context.CancelFunc
    41  	wg            *sync.WaitGroup
    42  	dirMask       zboxutil.Uint128
    43  	mu            *sync.Mutex
    44  	connectionID  string
    45  	timestamp     int64
    46  	alreadyExists map[uint64]bool
    47  	customMeta    string
    48  	Consensus
    49  }
    50  
    51  func (req *DirRequest) ProcessWithBlobbers(a *Allocation) int {
    52  	var pos uint64
    53  	var existingDirCount int
    54  	countMu := &sync.Mutex{}
    55  	for i := req.dirMask; !i.Equals64(0); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
    56  		pos = uint64(i.TrailingZeros())
    57  
    58  		req.wg.Add(1)
    59  		go func(pos uint64) {
    60  			defer req.wg.Done()
    61  
    62  			err, alreadyExists := req.createDirInBlobber(a.Blobbers[pos], pos)
    63  			if err != nil {
    64  				l.Logger.Error(err.Error())
    65  				return
    66  			}
    67  			if alreadyExists {
    68  				countMu.Lock()
    69  				req.alreadyExists[pos] = true
    70  				existingDirCount++
    71  				countMu.Unlock()
    72  			}
    73  		}(pos)
    74  	}
    75  
    76  	req.wg.Wait()
    77  	return existingDirCount
    78  }
    79  
    80  func (req *DirRequest) ProcessDir(a *Allocation) error {
    81  	l.Logger.Info("Start creating dir for blobbers")
    82  
    83  	defer req.ctxCncl()
    84  	existingDirCount := req.ProcessWithBlobbers(a)
    85  	if !req.isConsensusOk() {
    86  		return errors.New("consensus_not_met", "directory creation failed due to consensus not met")
    87  	}
    88  
    89  	writeMarkerMU, err := CreateWriteMarkerMutex(client.GetClient(), a)
    90  	if err != nil {
    91  		return fmt.Errorf("directory creation failed. Err: %s", err.Error())
    92  	}
    93  	err = writeMarkerMU.Lock(
    94  		req.ctx, &req.dirMask, req.mu,
    95  		req.blobbers, &req.Consensus, existingDirCount, time.Minute, req.connectionID)
    96  	if err != nil {
    97  		return fmt.Errorf("directory creation failed. Err: %s", err.Error())
    98  	}
    99  	defer writeMarkerMU.Unlock(req.ctx, req.dirMask,
   100  		a.Blobbers, time.Minute, req.connectionID) //nolint: errcheck
   101  
   102  	return req.commitRequest(existingDirCount)
   103  }
   104  
   105  func (req *DirRequest) commitRequest(existingDirCount int) error {
   106  	req.Consensus.Reset()
   107  	req.timestamp = int64(common.Now())
   108  	req.consensus = existingDirCount
   109  	wg := &sync.WaitGroup{}
   110  	activeBlobbersNum := req.dirMask.CountOnes()
   111  	wg.Add(activeBlobbersNum)
   112  
   113  	commitReqs := make([]*CommitRequest, activeBlobbersNum)
   114  	var pos uint64
   115  	var c int
   116  
   117  	uid := util.GetNewUUID()
   118  
   119  	for i := req.dirMask; !i.Equals(zboxutil.NewUint128(0)); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
   120  		pos = uint64(i.TrailingZeros())
   121  		commitReq := &CommitRequest{}
   122  		commitReq.allocationID = req.allocationID
   123  		commitReq.allocationTx = req.allocationTx
   124  		commitReq.blobber = req.blobbers[pos]
   125  		commitReq.sig = req.sig
   126  		newChange := &allocationchange.DirCreateChange{
   127  			RemotePath: req.remotePath,
   128  			Uuid:       uid,
   129  			Timestamp:  common.Timestamp(req.timestamp),
   130  		}
   131  
   132  		commitReq.changes = append(commitReq.changes, newChange)
   133  		commitReq.connectionID = req.connectionID
   134  		commitReq.wg = wg
   135  		commitReq.timestamp = req.timestamp
   136  		commitReqs[c] = commitReq
   137  		c++
   138  		go AddCommitRequest(commitReq)
   139  	}
   140  	wg.Wait()
   141  
   142  	for _, commitReq := range commitReqs {
   143  		if commitReq.result != nil {
   144  			if commitReq.result.Success {
   145  				l.Logger.Info("Commit success", commitReq.blobber.Baseurl)
   146  				req.Consensus.Done()
   147  			} else {
   148  				l.Logger.Info("Commit failed", commitReq.blobber.Baseurl, commitReq.result.ErrorMessage)
   149  			}
   150  		} else {
   151  			l.Logger.Info("Commit result not set", commitReq.blobber.Baseurl)
   152  		}
   153  	}
   154  
   155  	if !req.isConsensusOk() {
   156  		return errors.New("consensus_not_met", "directory creation failed due consensus not met")
   157  	}
   158  	return nil
   159  }
   160  
   161  func (req *DirRequest) createDirInBlobber(blobber *blockchain.StorageNode, pos uint64) (err error, alreadyExists bool) {
   162  	defer func() {
   163  		if err != nil {
   164  			req.mu.Lock()
   165  			req.dirMask = req.dirMask.And(zboxutil.NewUint128(1).Lsh(pos).Not())
   166  			req.mu.Unlock()
   167  		}
   168  	}()
   169  
   170  	body := new(bytes.Buffer)
   171  	formWriter := multipart.NewWriter(body)
   172  	err = formWriter.WriteField("connection_id", req.connectionID)
   173  	if err != nil {
   174  		return err, false
   175  	}
   176  
   177  	err = formWriter.WriteField("dir_path", req.remotePath)
   178  	if err != nil {
   179  		return err, false
   180  	}
   181  
   182  	if req.customMeta != "" {
   183  		err = formWriter.WriteField("custom_meta", req.customMeta)
   184  		if err != nil {
   185  			return err, false
   186  		}
   187  	}
   188  
   189  	formWriter.Close()
   190  	httpreq, err := zboxutil.NewCreateDirRequest(blobber.Baseurl, req.allocationID, req.allocationTx, req.sig, body)
   191  	if err != nil {
   192  		l.Logger.Error(blobber.Baseurl, "Error creating dir request", err)
   193  		return err, false
   194  	}
   195  
   196  	httpreq.Header.Add("Content-Type", formWriter.FormDataContentType())
   197  
   198  	var (
   199  		resp             *http.Response
   200  		shouldContinue   bool
   201  		latestRespMsg    string
   202  		latestStatusCode int
   203  	)
   204  
   205  	for i := 0; i < 3; i++ {
   206  		err, shouldContinue = func() (err error, shouldContinue bool) {
   207  			ctx, cncl := context.WithTimeout(req.ctx, (time.Second * 10))
   208  			resp, err = zboxutil.Client.Do(httpreq.WithContext(ctx))
   209  			cncl()
   210  			if err != nil {
   211  				return err, false
   212  			}
   213  
   214  			if resp.Body != nil {
   215  				defer resp.Body.Close()
   216  			}
   217  
   218  			var (
   219  				respBody []byte
   220  				msg      string
   221  			)
   222  			if resp.StatusCode == http.StatusOK {
   223  				l.Logger.Info("Successfully created directory ", req.remotePath)
   224  				req.Consensus.Done()
   225  				return
   226  			}
   227  
   228  			if resp.StatusCode == http.StatusTooManyRequests {
   229  				var r int
   230  				r, err = zboxutil.GetRateLimitValue(resp)
   231  				if err != nil {
   232  					return
   233  				}
   234  				l.Logger.Debug(fmt.Sprintf("Got too many request error. Retrying after %d seconds", r))
   235  				time.Sleep(time.Duration(r) * time.Second)
   236  				shouldContinue = true
   237  				return
   238  			}
   239  
   240  			respBody, err = ioutil.ReadAll(resp.Body)
   241  			if err != nil {
   242  				l.Logger.Error(err)
   243  				return
   244  			}
   245  
   246  			latestRespMsg = string(respBody)
   247  			latestStatusCode = resp.StatusCode
   248  
   249  			msg = string(respBody)
   250  			if strings.Contains(msg, DirectoryExists) {
   251  				req.Consensus.Done()
   252  				alreadyExists = true
   253  				return
   254  			}
   255  			l.Logger.Error(blobber.Baseurl, " Response: ", msg)
   256  
   257  			err = errors.New("response_error", msg)
   258  			return
   259  		}()
   260  
   261  		if err != nil {
   262  			logger.Logger.Error(err)
   263  			return
   264  		}
   265  		if shouldContinue {
   266  			continue
   267  		}
   268  		return
   269  
   270  	}
   271  
   272  	return errors.New("dir_creation_failed",
   273  		fmt.Sprintf("Directory creation failed with latest status: %d and "+
   274  			"latest message: %s", latestStatusCode, latestRespMsg)), false
   275  }
   276  
   277  type DirOperation struct {
   278  	remotePath    string
   279  	ctx           context.Context
   280  	ctxCncl       context.CancelFunc
   281  	dirMask       zboxutil.Uint128
   282  	maskMU        *sync.Mutex
   283  	customMeta    string
   284  	alreadyExists map[uint64]bool
   285  
   286  	Consensus
   287  }
   288  
   289  func (dirOp *DirOperation) Process(allocObj *Allocation, connectionID string) ([]fileref.RefEntity, zboxutil.Uint128, error) {
   290  	refs := make([]fileref.RefEntity, len(allocObj.Blobbers))
   291  	dR := &DirRequest{
   292  		allocationID:  allocObj.ID,
   293  		allocationTx:  allocObj.Tx,
   294  		connectionID:  connectionID,
   295  		sig:           allocObj.sig,
   296  		blobbers:      allocObj.Blobbers,
   297  		remotePath:    dirOp.remotePath,
   298  		ctx:           dirOp.ctx,
   299  		ctxCncl:       dirOp.ctxCncl,
   300  		dirMask:       dirOp.dirMask,
   301  		mu:            dirOp.maskMU,
   302  		wg:            &sync.WaitGroup{},
   303  		alreadyExists: make(map[uint64]bool),
   304  		customMeta:    dirOp.customMeta,
   305  	}
   306  	dR.Consensus = Consensus{
   307  		RWMutex:         &sync.RWMutex{},
   308  		consensusThresh: dR.consensusThresh,
   309  		fullconsensus:   dR.fullconsensus,
   310  	}
   311  
   312  	_ = dR.ProcessWithBlobbers(allocObj)
   313  	dirOp.alreadyExists = dR.alreadyExists
   314  
   315  	if !dR.isConsensusOk() {
   316  		return nil, dR.dirMask, errors.New("consensus_not_met", "directory creation failed due to consensus not met")
   317  	}
   318  	return refs, dR.dirMask, nil
   319  
   320  }
   321  
   322  func (dirOp *DirOperation) buildChange(refs []fileref.RefEntity, uid uuid.UUID) []allocationchange.AllocationChange {
   323  
   324  	var pos uint64
   325  	changes := make([]allocationchange.AllocationChange, len(refs))
   326  	for i := dirOp.dirMask; !i.Equals(zboxutil.NewUint128(0)); i = i.And(zboxutil.NewUint128(1).Lsh(pos).Not()) {
   327  		pos = uint64(i.TrailingZeros())
   328  		if dirOp.alreadyExists[pos] {
   329  			newChange := &allocationchange.EmptyFileChange{}
   330  			changes[pos] = newChange
   331  		} else {
   332  			newChange := &allocationchange.DirCreateChange{
   333  				RemotePath: dirOp.remotePath,
   334  				Uuid:       uid,
   335  				Timestamp:  common.Now(),
   336  			}
   337  			changes[pos] = newChange
   338  		}
   339  	}
   340  	return changes
   341  }
   342  
   343  func (dirOp *DirOperation) Verify(a *Allocation) error {
   344  	if dirOp.remotePath == "" {
   345  		return errors.New("invalid_name", "Invalid name for dir")
   346  	}
   347  
   348  	if !path.IsAbs(dirOp.remotePath) {
   349  		return errors.New("invalid_path", "Path is not absolute")
   350  	}
   351  	return nil
   352  }
   353  
   354  func (dirOp *DirOperation) Completed(allocObj *Allocation) {
   355  
   356  }
   357  
   358  func (dirOp *DirOperation) Error(allocObj *Allocation, consensus int, err error) {
   359  
   360  }
   361  
   362  func NewDirOperation(remotePath, customMeta string, dirMask zboxutil.Uint128, maskMU *sync.Mutex, consensusTh int, fullConsensus int, ctx context.Context) *DirOperation {
   363  	dirOp := &DirOperation{}
   364  	dirOp.remotePath = zboxutil.RemoteClean(remotePath)
   365  	dirOp.dirMask = dirMask
   366  	dirOp.maskMU = maskMU
   367  	dirOp.consensusThresh = consensusTh
   368  	dirOp.fullconsensus = fullConsensus
   369  	dirOp.customMeta = customMeta
   370  	dirOp.ctx, dirOp.ctxCncl = context.WithCancel(ctx)
   371  	dirOp.alreadyExists = make(map[uint64]bool)
   372  	return dirOp
   373  }