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 }