github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ext/dload/diff_resolver.go (about) 1 // Package dload implements functionality to download resources into AIS cluster from external source. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package dload 6 7 import ( 8 "github.com/NVIDIA/aistore/cmn" 9 "github.com/NVIDIA/aistore/cmn/atomic" 10 "github.com/NVIDIA/aistore/cmn/cos" 11 "github.com/NVIDIA/aistore/cmn/debug" 12 "github.com/NVIDIA/aistore/core" 13 "github.com/NVIDIA/aistore/fs" 14 ) 15 16 const ( 17 DiffResolverSend = iota 18 DiffResolverRecv 19 DiffResolverDelete 20 DiffResolverSkip 21 DiffResolverErr 22 DiffResolverEOF 23 ) 24 25 type ( 26 DiffResolverCtx interface { 27 CompareObjects(*core.LOM, *DstElement) (bool, error) 28 IsObjFromRemote(*core.LOM) (bool, error) 29 } 30 31 defaultDiffResolverCtx struct{} 32 33 // DiffResolver is entity that computes difference between two streams 34 // of objects. The streams are expected to be in sorted order. 35 DiffResolver struct { 36 ctx DiffResolverCtx 37 srcCh chan *core.LOM 38 dstCh chan *DstElement 39 resultCh chan DiffResolverResult 40 err cos.Errs 41 stopped atomic.Bool 42 } 43 44 BackendResource struct { 45 ObjName string 46 } 47 48 WebResource struct { 49 ObjName string 50 Link string 51 } 52 53 DstElement struct { 54 ObjName string 55 Version string 56 Link string 57 } 58 59 DiffResolverResult struct { 60 Err error 61 Src *core.LOM 62 Dst *DstElement 63 Action uint8 64 } 65 ) 66 67 ////////////////// 68 // DiffResolver // 69 ////////////////// 70 71 // TODO: configurable burst size of the channels, plus `chanFull` check 72 func NewDiffResolver(ctx DiffResolverCtx) *DiffResolver { 73 return &DiffResolver{ 74 ctx: ctx, 75 srcCh: make(chan *core.LOM, 128), 76 dstCh: make(chan *DstElement, 128), 77 resultCh: make(chan DiffResolverResult, 128), 78 } 79 } 80 81 func (dr *DiffResolver) Start() { 82 defer close(dr.resultCh) 83 src, srcOk := <-dr.srcCh 84 dst, dstOk := <-dr.dstCh 85 for { 86 if !srcOk && !dstOk { 87 dr.resultCh <- DiffResolverResult{ 88 Action: DiffResolverEOF, 89 } 90 return 91 } 92 93 switch { 94 case !srcOk || (dstOk && src.ObjName > dst.ObjName): 95 dr.resultCh <- DiffResolverResult{ 96 Action: DiffResolverRecv, 97 Dst: dst, 98 } 99 dst, dstOk = <-dr.dstCh 100 case !dstOk || (srcOk && src.ObjName < dst.ObjName): 101 remote, err := dr.ctx.IsObjFromRemote(src) 102 if err != nil { 103 dr.resultCh <- DiffResolverResult{ 104 Action: DiffResolverErr, 105 Src: src, 106 Dst: dst, 107 Err: err, 108 } 109 return 110 } 111 if remote { 112 debug.Assert(!dstOk || dst.Link == "") // destination must be remote as well 113 dr.resultCh <- DiffResolverResult{ 114 Action: DiffResolverDelete, 115 Src: src, 116 } 117 } else { 118 dr.resultCh <- DiffResolverResult{ 119 Action: DiffResolverSend, 120 Src: src, 121 } 122 } 123 src, srcOk = <-dr.srcCh 124 default: /* s.ObjName == d.ObjName */ 125 equal, err := dr.ctx.CompareObjects(src, dst) 126 if err != nil { 127 dr.resultCh <- DiffResolverResult{ 128 Action: DiffResolverErr, 129 Src: src, 130 Dst: dst, 131 Err: err, 132 } 133 return 134 } 135 if equal { 136 dr.resultCh <- DiffResolverResult{ 137 Action: DiffResolverSkip, 138 Src: src, 139 Dst: dst, 140 } 141 } else { 142 dr.resultCh <- DiffResolverResult{ 143 Action: DiffResolverRecv, 144 Dst: dst, 145 } 146 } 147 src, srcOk = <-dr.srcCh 148 dst, dstOk = <-dr.dstCh 149 } 150 } 151 } 152 153 func (dr *DiffResolver) PushSrc(v any) { 154 switch x := v.(type) { 155 case *core.LOM: 156 dr.srcCh <- x 157 default: 158 debug.FailTypeCast(v) 159 } 160 } 161 162 func (dr *DiffResolver) CloseSrc() { close(dr.srcCh) } 163 164 func (dr *DiffResolver) PushDst(v any) { 165 var d *DstElement 166 switch x := v.(type) { 167 case *BackendResource: 168 d = &DstElement{ 169 ObjName: x.ObjName, 170 } 171 case *WebResource: 172 d = &DstElement{ 173 ObjName: x.ObjName, 174 Link: x.Link, 175 } 176 default: 177 debug.FailTypeCast(v) 178 } 179 180 dr.dstCh <- d 181 } 182 183 func (dr *DiffResolver) CloseDst() { close(dr.dstCh) } 184 185 func (dr *DiffResolver) Next() (DiffResolverResult, error) { 186 if cnt, err := dr.err.JoinErr(); cnt > 0 { 187 return DiffResolverResult{}, err 188 } 189 r, ok := <-dr.resultCh 190 if !ok { 191 return DiffResolverResult{Action: DiffResolverEOF}, nil 192 } 193 return r, nil 194 } 195 196 func (dr *DiffResolver) Stop() { dr.stopped.Store(true) } 197 func (dr *DiffResolver) Stopped() bool { return dr.stopped.Load() } 198 func (dr *DiffResolver) Abort(err error) { dr.err.Add(err) } 199 200 func (dr *DiffResolver) walk(job jobif) { 201 defer dr.CloseSrc() 202 opts := &fs.WalkBckOpts{ 203 WalkOpts: fs.WalkOpts{CTs: []string{fs.ObjectType}, Sorted: true}, 204 } 205 opts.WalkOpts.Bck.Copy(job.Bck()) 206 opts.Callback = func(fqn string, _ fs.DirEntry) error { return dr.cb(fqn, job) } 207 208 err := fs.WalkBck(opts) 209 if err != nil && !cmn.IsErrAborted(err) { 210 dr.Abort(err) 211 } 212 } 213 214 func (dr *DiffResolver) cb(fqn string, job jobif) error { 215 if dr.Stopped() { 216 return cmn.NewErrAborted(job.String(), "diff-resolver stopped", nil) 217 } 218 lom := &core.LOM{} 219 if err := lom.InitFQN(fqn, job.Bck()); err != nil { 220 return err 221 } 222 if job.checkObj(lom.ObjName) { 223 dr.PushSrc(lom) 224 } 225 return nil 226 } 227 228 func (dr *DiffResolver) push(job jobif, d *dispatcher) { 229 defer func() { 230 dr.CloseDst() 231 if !job.Sync() { 232 dr.CloseSrc() 233 } 234 }() 235 236 for { 237 objs, ok, err := job.genNext() 238 if err != nil { 239 dr.Abort(err) 240 return 241 } 242 if !ok || dr.Stopped() { 243 return 244 } 245 for _, obj := range objs { 246 if d.checkAborted() { 247 err := cmn.NewErrAborted(job.String(), "", nil) 248 dr.Abort(err) 249 return 250 } 251 if d.checkAbortedJob(job) { 252 dr.Stop() 253 return 254 } 255 if !job.Sync() { 256 // When it is not a sync job, push LOM for a given object 257 // because we need to check if it exists. 258 lom := &core.LOM{ObjName: obj.objName} 259 if err := lom.InitBck(job.Bck()); err != nil { 260 dr.Abort(err) 261 return 262 } 263 dr.PushSrc(lom) 264 } 265 if obj.link != "" { 266 dr.PushDst(&WebResource{ 267 ObjName: obj.objName, 268 Link: obj.link, 269 }) 270 } else { 271 dr.PushDst(&BackendResource{ 272 ObjName: obj.objName, 273 }) 274 } 275 } 276 } 277 } 278 279 //////////////////////////// 280 // defaultDiffResolverCtx // 281 //////////////////////////// 282 283 func (*defaultDiffResolverCtx) CompareObjects(src *core.LOM, dst *DstElement) (bool, error) { 284 src.Lock(false) 285 defer src.Unlock(false) 286 if err := src.Load(true /*cache it*/, true /*locked*/); err != nil { 287 if cos.IsNotExist(err, 0) { 288 return false, nil 289 } 290 return false, err 291 } 292 return CompareObjects(src, dst) 293 } 294 295 func (*defaultDiffResolverCtx) IsObjFromRemote(src *core.LOM) (bool, error) { 296 if err := src.Load(true /*cache it*/, false /*locked*/); err != nil { 297 if cos.IsNotExist(err, 0) { 298 return false, nil 299 } 300 return false, err 301 } 302 objSrc, ok := src.GetCustomKey(cmn.SourceObjMD) 303 if !ok { 304 return false, nil 305 } 306 return objSrc != cmn.WebObjMD, nil 307 }