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  }