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

     1  // Package tools provides common tools and utilities for all unit and integration tests
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package tools
     6  
     7  import (
     8  	"bytes"
     9  	"io"
    10  	"math/rand"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/NVIDIA/aistore/api/apc"
    18  	"github.com/NVIDIA/aistore/cmn"
    19  	"github.com/NVIDIA/aistore/cmn/cos"
    20  	"github.com/NVIDIA/aistore/core"
    21  	"github.com/NVIDIA/aistore/core/meta"
    22  	"github.com/NVIDIA/aistore/core/mock"
    23  	"github.com/NVIDIA/aistore/fs"
    24  	"github.com/NVIDIA/aistore/tools/cryptorand"
    25  	"github.com/NVIDIA/aistore/tools/tassert"
    26  	"github.com/NVIDIA/aistore/tools/trand"
    27  )
    28  
    29  type (
    30  	DirTreeDesc struct {
    31  		InitDir string // Directory where the tree is created (can be empty).
    32  		Dirs    int    // Number of (initially empty) directories at each depth (we recurse into single directory at each depth).
    33  		Files   int    // Number of files at each depth.
    34  		Depth   int    // Depth of tree/nesting.
    35  		Empty   bool   // Determines if there is a file somewhere in the directories.
    36  	}
    37  
    38  	ContentTypeDesc struct {
    39  		Type       string
    40  		ContentCnt int
    41  	}
    42  
    43  	ObjectsDesc struct {
    44  		CTs           []ContentTypeDesc // Content types which are interesting for the test.
    45  		MountpathsCnt int               // Number of mountpaths to be created.
    46  		ObjectSize    int64
    47  	}
    48  
    49  	ObjectsOut struct {
    50  		Dir             string
    51  		Bck             cmn.Bck
    52  		FQNs            map[string][]string // ContentType => FQN
    53  		MpathObjectsCnt map[string]int      // mpath -> # objects on the mpath
    54  	}
    55  )
    56  
    57  func RandomObjDir(dirLen, maxDepth int) (dir string) {
    58  	depth := rand.Intn(maxDepth)
    59  	for range depth {
    60  		dir = filepath.Join(dir, trand.String(dirLen))
    61  	}
    62  	return
    63  }
    64  
    65  func SetXattrCksum(fqn string, bck cmn.Bck, cksum *cos.Cksum) error {
    66  	lom := &core.LOM{}
    67  	// NOTE: this is an intentional hack to go ahead and corrupt the checksum
    68  	//       - init and/or load errors are ignored on purpose
    69  	_ = lom.InitFQN(fqn, &bck)
    70  	_ = lom.LoadMetaFromFS()
    71  	lom.SetCksum(cksum)
    72  	return lom.Persist()
    73  }
    74  
    75  func CheckPathExists(t *testing.T, path string, dir bool) {
    76  	if fi, err := os.Stat(path); err != nil {
    77  		t.Fatal(err)
    78  	} else {
    79  		if dir && !fi.IsDir() {
    80  			t.Fatalf("expected path %q to be directory", path)
    81  		} else if !dir && fi.IsDir() {
    82  			t.Fatalf("expected path %q to not be directory", path)
    83  		}
    84  	}
    85  }
    86  
    87  func CheckPathNotExists(t *testing.T, path string) {
    88  	if err := cos.Stat(path); err == nil || !os.IsNotExist(err) {
    89  		t.Fatal(err)
    90  	}
    91  }
    92  
    93  func PrepareDirTree(tb testing.TB, desc DirTreeDesc) (string, []string) {
    94  	fileNames := make([]string, 0, 100)
    95  	topDirName, err := os.MkdirTemp(desc.InitDir, "")
    96  	tassert.CheckFatal(tb, err)
    97  
    98  	nestedDirectoryName := topDirName
    99  	for depth := 1; depth <= desc.Depth; depth++ {
   100  		names := make([]string, 0, desc.Dirs)
   101  		for i := 1; i <= desc.Dirs; i++ {
   102  			name, err := os.MkdirTemp(nestedDirectoryName, "")
   103  			tassert.CheckFatal(tb, err)
   104  			names = append(names, name)
   105  		}
   106  		for i := 1; i <= desc.Files; i++ {
   107  			f, err := os.CreateTemp(nestedDirectoryName, "")
   108  			tassert.CheckFatal(tb, err)
   109  			fileNames = append(fileNames, f.Name())
   110  			f.Close()
   111  		}
   112  		sort.Strings(names)
   113  		if desc.Dirs > 0 {
   114  			// We only recurse into last directory.
   115  			nestedDirectoryName = names[len(names)-1]
   116  		}
   117  	}
   118  
   119  	if !desc.Empty {
   120  		f, err := os.CreateTemp(nestedDirectoryName, "")
   121  		tassert.CheckFatal(tb, err)
   122  		fileNames = append(fileNames, f.Name())
   123  		f.Close()
   124  	}
   125  	return topDirName, fileNames
   126  }
   127  
   128  func PrepareObjects(t *testing.T, desc ObjectsDesc) *ObjectsOut {
   129  	var (
   130  		buf       = make([]byte, desc.ObjectSize)
   131  		fqns      = make(map[string][]string, len(desc.CTs))
   132  		mpathCnts = make(map[string]int, desc.MountpathsCnt)
   133  
   134  		bck = cmn.Bck{
   135  			Name:     trand.String(10),
   136  			Provider: apc.AIS,
   137  			Ns:       cmn.NsGlobal,
   138  			Props: &cmn.Bprops{
   139  				Cksum: cmn.CksumConf{Type: cos.ChecksumXXHash},
   140  				BID:   0xa5b6e7d8,
   141  			},
   142  		}
   143  		bmd = mock.NewBaseBownerMock((*meta.Bck)(&bck))
   144  	)
   145  
   146  	mios := mock.NewIOS()
   147  	fs.TestNew(mios)
   148  
   149  	fs.CSM.Reg(fs.WorkfileType, &fs.WorkfileContentResolver{}, true)
   150  	fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}, true)
   151  	fs.CSM.Reg(fs.ECSliceType, &fs.ECSliceContentResolver{}, true)
   152  	fs.CSM.Reg(fs.ECMetaType, &fs.ECMetaContentResolver{}, true)
   153  
   154  	dir := t.TempDir()
   155  
   156  	for range desc.MountpathsCnt {
   157  		mpath, err := os.MkdirTemp(dir, "")
   158  		tassert.CheckFatal(t, err)
   159  		mp, err := fs.Add(mpath, "daeID")
   160  		tassert.CheckFatal(t, err)
   161  		mpathCnts[mp.Path] = 0
   162  	}
   163  
   164  	if len(desc.CTs) == 0 {
   165  		return nil
   166  	}
   167  
   168  	core.T = mock.NewTarget(bmd) // a.k.a. tMock
   169  
   170  	errs := fs.CreateBucket(&bck, false /*nilbmd*/)
   171  	if len(errs) > 0 {
   172  		tassert.CheckFatal(t, errs[0])
   173  	}
   174  
   175  	for _, ct := range desc.CTs {
   176  		for range ct.ContentCnt {
   177  			fqn, _, err := core.HrwFQN(&bck, ct.Type, trand.String(15))
   178  			tassert.CheckFatal(t, err)
   179  
   180  			fqns[ct.Type] = append(fqns[ct.Type], fqn)
   181  
   182  			f, err := cos.CreateFile(fqn)
   183  			tassert.CheckFatal(t, err)
   184  			_, _ = cryptorand.Read(buf)
   185  			_, err = f.Write(buf)
   186  			f.Close()
   187  			tassert.CheckFatal(t, err)
   188  
   189  			var parsed fs.ParsedFQN
   190  			err = parsed.Init(fqn)
   191  			tassert.CheckFatal(t, err)
   192  			mpathCnts[parsed.Mountpath.Path]++
   193  
   194  			switch ct.Type {
   195  			case fs.ObjectType:
   196  				lom := &core.LOM{}
   197  				err = lom.InitFQN(fqn, nil)
   198  				tassert.CheckFatal(t, err)
   199  
   200  				lom.SetSize(desc.ObjectSize)
   201  				lom.SetAtimeUnix(time.Now().UnixNano())
   202  				err = lom.Persist()
   203  				tassert.CheckFatal(t, err)
   204  			case fs.WorkfileType, fs.ECSliceType, fs.ECMetaType:
   205  			default:
   206  				cos.AssertMsg(false, "non-implemented type")
   207  			}
   208  		}
   209  	}
   210  
   211  	return &ObjectsOut{
   212  		Dir:             dir,
   213  		Bck:             bck,
   214  		FQNs:            fqns,
   215  		MpathObjectsCnt: mpathCnts,
   216  	}
   217  }
   218  
   219  func PrepareMountPaths(t *testing.T, cnt int) fs.MPI {
   220  	PrepareObjects(t, ObjectsDesc{
   221  		MountpathsCnt: cnt,
   222  	})
   223  	AssertMountpathCount(t, cnt, 0)
   224  	return fs.GetAvail()
   225  }
   226  
   227  func RemoveMpaths(t *testing.T, mpaths fs.MPI) {
   228  	for _, mpath := range mpaths {
   229  		removedMP, err := fs.Remove(mpath.Path)
   230  		tassert.CheckError(t, err)
   231  		tassert.Errorf(t, removedMP != nil, "expected remove to be successful")
   232  		tassert.CheckError(t, fs.RemoveAll(mpath.Path))
   233  	}
   234  }
   235  
   236  func AddMpath(t *testing.T, path string) {
   237  	err := cos.CreateDir(path) // Create directory if not exists
   238  	tassert.CheckFatal(t, err)
   239  	t.Cleanup(func() {
   240  		fs.RemoveAll(path)
   241  	})
   242  	_, err = fs.Add(path, "daeID")
   243  	tassert.Errorf(t, err == nil, "Failed adding mountpath %q, err: %v", path, err)
   244  }
   245  
   246  func AssertMountpathCount(t *testing.T, availableCount, disabledCount int) {
   247  	availableMountpaths, disabledMountpaths := fs.Get()
   248  	if len(availableMountpaths) != availableCount ||
   249  		len(disabledMountpaths) != disabledCount {
   250  		t.Errorf(
   251  			"wrong mountpaths: %d/%d, %d/%d",
   252  			len(availableMountpaths), availableCount,
   253  			len(disabledMountpaths), disabledCount,
   254  		)
   255  	}
   256  }
   257  
   258  func CreateFileFromReader(t *testing.T, fileName string, r io.Reader) string {
   259  	filePath := filepath.Join(t.TempDir(), fileName)
   260  	f, err := os.Create(filePath)
   261  	tassert.CheckFatal(t, err)
   262  
   263  	_, err = io.Copy(f, r)
   264  	tassert.CheckFatal(t, err)
   265  
   266  	err = f.Close()
   267  	tassert.CheckFatal(t, err)
   268  
   269  	return filePath
   270  }
   271  
   272  func FilesEqual(file1, file2 string) (bool, error) {
   273  	f1, err := os.ReadFile(file1)
   274  	if err != nil {
   275  		return false, err
   276  	}
   277  	f2, err := os.ReadFile(file2)
   278  	if err != nil {
   279  		return false, err
   280  	}
   281  	return bytes.Equal(f1, f2), nil
   282  }