github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/fs/content.go (about)

     1  // Package fs provides mountpath and FQN abstractions and methods to resolve/map stored content
     2  /*
     3   * Copyright (c) 2018-2021, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package fs
     6  
     7  import (
     8  	"fmt"
     9  	"path/filepath"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/NVIDIA/aistore/cmn"
    14  	"github.com/NVIDIA/aistore/cmn/cos"
    15  	"github.com/NVIDIA/aistore/cmn/debug"
    16  	"github.com/NVIDIA/aistore/cmn/nlog"
    17  )
    18  
    19  /*
    20   * Besides objects we must deal with additional files like: workfiles, dsort
    21   * intermediate files (used when spilling to disk) or EC slices. These files can
    22   * have different rules of rebalancing, evicting and other processing. Each
    23   * content type needs to implement ContentResolver to reflect the rules and
    24   * permission for different services. To see how the interface can be
    25   * implemented see: DefaultWorkfile implemention.
    26   *
    27   * When walking through the files we need to know if the file is an object or
    28   * other content. To do that we generate fqn with Gen. It adds short
    29   * prefix to the base name, which we believe is unique and will separate objects
    30   * from content files. We parse the file type to run ParseUniqueFQN (implemented
    31   * by this file type) on the rest of the base name.
    32   */
    33  
    34  const (
    35  	contentTypeLen = 2
    36  
    37  	ObjectType   = "ob"
    38  	WorkfileType = "wk"
    39  	ECSliceType  = "ec"
    40  	ECMetaType   = "mt"
    41  )
    42  
    43  type (
    44  	ContentResolver interface {
    45  		// When set to true, services like rebalance have permission to move
    46  		// content for example to another target because it is misplaced (HRW).
    47  		PermToMove() bool
    48  		// When set to true, services like LRU have permission to evict/delete content
    49  		PermToEvict() bool
    50  		// When set to true, content can be checksumed, shown or processed in other ways.
    51  		PermToProcess() bool
    52  
    53  		// Generates unique base name for original one. This function may add
    54  		// additional information to the base name.
    55  		// prefix - user-defined marker
    56  		GenUniqueFQN(base, prefix string) (ufqn string)
    57  		// Parses generated unique fqn to the original one.
    58  		ParseUniqueFQN(base string) (orig string, old, ok bool)
    59  	}
    60  
    61  	PartsFQN interface {
    62  		ObjectName() string
    63  		Bucket() *cmn.Bck
    64  		Mountpath() *Mountpath
    65  		CacheIdx() int
    66  	}
    67  
    68  	ContentInfo struct {
    69  		Dir  string // original directory
    70  		Base string // original basename
    71  		Type string // content type
    72  		Old  bool   // true if old (subj. to space cleanup)
    73  	}
    74  
    75  	contentSpecMgr struct {
    76  		m map[string]ContentResolver
    77  	}
    78  )
    79  
    80  var CSM *contentSpecMgr
    81  
    82  func (f *contentSpecMgr) Resolver(contentType string) ContentResolver {
    83  	r := f.m[contentType]
    84  	return r
    85  }
    86  
    87  // Reg registers new content type with a given content resolver.
    88  // NOTE: all content type registrations must happen at startup.
    89  func (f *contentSpecMgr) Reg(contentType string, spec ContentResolver, unitTest ...bool) {
    90  	err := f._reg(contentType, spec)
    91  	if err != nil && len(unitTest) == 0 {
    92  		debug.Assert(false)
    93  		cos.ExitLog(err)
    94  	}
    95  }
    96  
    97  func (f *contentSpecMgr) _reg(contentType string, spec ContentResolver) error {
    98  	if strings.ContainsRune(contentType, filepath.Separator) {
    99  		return fmt.Errorf("%s content type cannot contain %q", contentType, filepath.Separator)
   100  	}
   101  	if len(contentType) != contentTypeLen {
   102  		return fmt.Errorf("%s content type must have length %d", contentType, contentTypeLen)
   103  	}
   104  	if _, ok := f.m[contentType]; ok {
   105  		return fmt.Errorf("%s content type is already registered", contentType)
   106  	}
   107  	f.m[contentType] = spec
   108  	return nil
   109  }
   110  
   111  // Gen returns a new FQN generated from given parts.
   112  func (f *contentSpecMgr) Gen(parts PartsFQN, contentType, prefix string) (fqn string) {
   113  	var (
   114  		spec    = f.m[contentType]
   115  		objName = spec.GenUniqueFQN(parts.ObjectName(), prefix)
   116  	)
   117  	return parts.Mountpath().MakePathFQN(parts.Bucket(), contentType, objName)
   118  }
   119  
   120  // FileSpec returns the specification/attributes and information about the `fqn`
   121  // (which must be generated by the Gen)
   122  func (f *contentSpecMgr) FileSpec(fqn string) (resolver ContentResolver, info *ContentInfo) {
   123  	dir, base := filepath.Split(fqn)
   124  	if dir == "" || base == "" {
   125  		return
   126  	}
   127  	debug.Assert(cos.IsLastB(dir, filepath.Separator), dir)
   128  
   129  	var parsed ParsedFQN
   130  	if err := parsed.Init(fqn); err != nil {
   131  		return
   132  	}
   133  	spec, found := f.m[parsed.ContentType]
   134  	if !found {
   135  		nlog.Errorf("%q: unknown content type %s", fqn, parsed.ContentType)
   136  		return
   137  	}
   138  	origBase, old, ok := spec.ParseUniqueFQN(base)
   139  	if !ok {
   140  		return
   141  	}
   142  	resolver = spec
   143  	info = &ContentInfo{Dir: dir, Base: origBase, Old: old, Type: parsed.ContentType}
   144  	return
   145  }
   146  
   147  func (f *contentSpecMgr) PermToEvict(fqn string) (ok, isOld bool) {
   148  	spec, info := f.FileSpec(fqn)
   149  	if spec == nil {
   150  		return true, false
   151  	}
   152  
   153  	return spec.PermToEvict(), info.Old
   154  }
   155  
   156  func (f *contentSpecMgr) PermToMove(fqn string) (ok bool) {
   157  	spec, _ := f.FileSpec(fqn)
   158  	if spec == nil {
   159  		return false
   160  	}
   161  
   162  	return spec.PermToMove()
   163  }
   164  
   165  func (f *contentSpecMgr) PermToProcess(fqn string) (ok bool) {
   166  	spec, _ := f.FileSpec(fqn)
   167  	if spec == nil {
   168  		return false
   169  	}
   170  
   171  	return spec.PermToProcess()
   172  }
   173  
   174  // FIXME: This should be probably placed somewhere else \/
   175  
   176  type (
   177  	ObjectContentResolver   struct{}
   178  	WorkfileContentResolver struct{}
   179  	ECSliceContentResolver  struct{}
   180  	ECMetaContentResolver   struct{}
   181  )
   182  
   183  func (*ObjectContentResolver) PermToMove() bool                   { return true }
   184  func (*ObjectContentResolver) PermToEvict() bool                  { return true }
   185  func (*ObjectContentResolver) PermToProcess() bool                { return true }
   186  func (*ObjectContentResolver) GenUniqueFQN(base, _ string) string { return base }
   187  
   188  func (*ObjectContentResolver) ParseUniqueFQN(base string) (orig string, old, ok bool) {
   189  	return base, false, true
   190  }
   191  
   192  func (*WorkfileContentResolver) PermToMove() bool    { return false }
   193  func (*WorkfileContentResolver) PermToEvict() bool   { return true }
   194  func (*WorkfileContentResolver) PermToProcess() bool { return false }
   195  
   196  func (*WorkfileContentResolver) GenUniqueFQN(base, prefix string) string {
   197  	const (
   198  		contentSepa = "."
   199  	)
   200  	var (
   201  		dir, fname = filepath.Split(base)
   202  		tieBreaker = cos.GenTie()
   203  	)
   204  	fname = prefix + contentSepa + fname
   205  	base = filepath.Join(dir, fname)
   206  	return base + contentSepa + tieBreaker + contentSepa + spid
   207  }
   208  
   209  func (*WorkfileContentResolver) ParseUniqueFQN(base string) (orig string, old, ok bool) {
   210  	const (
   211  		contentSepa = '.'
   212  	)
   213  	// remove original content type
   214  	cntIndex := strings.IndexByte(base, contentSepa)
   215  	if cntIndex < 0 {
   216  		return "", false, false
   217  	}
   218  	base = base[cntIndex+1:]
   219  
   220  	pidIndex := strings.LastIndexByte(base, contentSepa) // pid
   221  	if pidIndex < 0 {
   222  		return "", false, false
   223  	}
   224  	tieIndex := strings.LastIndexByte(base[:pidIndex], contentSepa) // tie breaker
   225  	if tieIndex < 0 {
   226  		return "", false, false
   227  	}
   228  	filePID, err := strconv.ParseInt(base[pidIndex+1:], 16, 64)
   229  	if err != nil {
   230  		return "", false, false
   231  	}
   232  
   233  	return base[:tieIndex], filePID != pid, true
   234  }
   235  
   236  func (*ECSliceContentResolver) PermToMove() bool    { return true }
   237  func (*ECSliceContentResolver) PermToEvict() bool   { return true }
   238  func (*ECSliceContentResolver) PermToProcess() bool { return false }
   239  
   240  func (*ECSliceContentResolver) GenUniqueFQN(base, _ string) string { return base }
   241  
   242  func (*ECSliceContentResolver) ParseUniqueFQN(base string) (orig string, old, ok bool) {
   243  	return base, false, true
   244  }
   245  
   246  func (*ECMetaContentResolver) PermToMove() bool    { return true }
   247  func (*ECMetaContentResolver) PermToEvict() bool   { return true }
   248  func (*ECMetaContentResolver) PermToProcess() bool { return false }
   249  
   250  func (*ECMetaContentResolver) GenUniqueFQN(base, _ string) string { return base }
   251  
   252  func (*ECMetaContentResolver) ParseUniqueFQN(base string) (orig string, old, ok bool) {
   253  	return base, false, true
   254  }