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 }