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 }