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 }