github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/space/space_test.go (about) 1 // Package space_test is a unit test for the package. 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package space_test 6 7 import ( 8 "crypto/rand" 9 "fmt" 10 "os" 11 "path" 12 "testing" 13 "time" 14 15 "github.com/NVIDIA/aistore/api/apc" 16 "github.com/NVIDIA/aistore/cmn" 17 "github.com/NVIDIA/aistore/cmn/cos" 18 "github.com/NVIDIA/aistore/core" 19 "github.com/NVIDIA/aistore/core/meta" 20 "github.com/NVIDIA/aistore/core/mock" 21 "github.com/NVIDIA/aistore/fs" 22 "github.com/NVIDIA/aistore/hk" 23 "github.com/NVIDIA/aistore/space" 24 "github.com/NVIDIA/aistore/tools/trand" 25 "github.com/NVIDIA/aistore/xact/xreg" 26 . "github.com/onsi/ginkgo/v2" 27 . "github.com/onsi/gomega" 28 ) 29 30 const ( 31 initialDiskUsagePct = 0.9 32 hwm = 80 33 lwm = 50 34 numberOfCreatedFiles = 45 35 fileSize = 10 * cos.MiB 36 blockSize = cos.KiB 37 basePath = "/tmp/space-tests" 38 bucketName = "space-bck" 39 bucketNameAnother = bucketName + "-another" 40 ) 41 42 type fileMetadata struct { 43 name string 44 size int64 45 } 46 47 var gT *testing.T 48 49 func TestEvictCleanup(t *testing.T) { 50 xreg.Init() 51 hk.TestInit() 52 cos.InitShortID(0) 53 54 RegisterFailHandler(Fail) 55 gT = t 56 RunSpecs(t, t.Name()) 57 } 58 59 var _ = Describe("space evict/cleanup tests", func() { 60 Describe("Run", func() { 61 var ( 62 filesPath string 63 fpAnother string 64 bckAnother cmn.Bck 65 ) 66 67 BeforeEach(func() { 68 initConfig() 69 createAndAddMountpath(basePath) 70 core.T = newTargetLRUMock() 71 availablePaths := fs.GetAvail() 72 bck := cmn.Bck{Name: bucketName, Provider: apc.AIS, Ns: cmn.NsGlobal} 73 bckAnother = cmn.Bck{Name: bucketNameAnother, Provider: apc.AIS, Ns: cmn.NsGlobal} 74 filesPath = availablePaths[basePath].MakePathCT(&bck, fs.ObjectType) 75 fpAnother = availablePaths[basePath].MakePathCT(&bckAnother, fs.ObjectType) 76 cos.CreateDir(filesPath) 77 cos.CreateDir(fpAnother) 78 }) 79 80 AfterEach(func() { 81 os.RemoveAll(basePath) 82 }) 83 84 Describe("evict files", func() { 85 var ini *space.IniLRU 86 BeforeEach(func() { 87 ini = newIniLRU() 88 }) 89 It("should not fail when there are no files", func() { 90 space.RunLRU(ini) 91 }) 92 93 It("should evict correct number of files", func() { 94 if testing.Short() { 95 Skip("skipping in short mode") 96 } 97 saveRandomFiles(filesPath, numberOfCreatedFiles) 98 99 space.RunLRU(ini) 100 101 files, err := os.ReadDir(filesPath) 102 Expect(err).NotTo(HaveOccurred()) 103 numberOfFilesLeft := len(files) 104 105 // too few files evicted 106 Expect(float64(numberOfFilesLeft) / numberOfCreatedFiles * initialDiskUsagePct).To(BeNumerically("<=", 0.01*lwm)) 107 // to many files evicted 108 Expect(float64(numberOfFilesLeft+1) / numberOfCreatedFiles * initialDiskUsagePct).To(BeNumerically(">", 0.01*lwm)) 109 }) 110 111 It("should evict the oldest files", func() { 112 const numberOfFiles = 6 113 114 ini.GetFSStats = getMockGetFSStats(numberOfFiles) 115 116 oldFiles := []fileMetadata{ 117 {getRandomFileName(3), fileSize}, 118 {getRandomFileName(4), fileSize}, 119 {getRandomFileName(5), fileSize}, 120 } 121 saveRandomFilesWithMetadata(filesPath, oldFiles) 122 time.Sleep(1 * time.Second) 123 saveRandomFiles(filesPath, 3) 124 125 space.RunLRU(ini) 126 127 files, err := os.ReadDir(filesPath) 128 Expect(err).NotTo(HaveOccurred()) 129 Expect(len(files)).To(Equal(3)) 130 131 oldFilesNames := namesFromFilesMetadatas(oldFiles) 132 for _, name := range files { 133 Expect(cos.StringInSlice(name.Name(), oldFilesNames)).To(BeFalse()) 134 } 135 }) 136 137 It("should evict files of different sizes", func() { 138 const totalSize = 32 * cos.MiB 139 if testing.Short() { 140 Skip("skipping in short mode") 141 } 142 143 ini.GetFSStats = func(string) (blocks, bavail uint64, bsize int64, err error) { 144 bsize = blockSize 145 btaken := uint64(totalSize / blockSize) 146 blocks = uint64(float64(btaken) / initialDiskUsagePct) 147 bavail = blocks - btaken 148 return 149 } 150 151 // files sum up to 32Mb 152 files := []fileMetadata{ 153 {getRandomFileName(0), int64(4 * cos.MiB)}, 154 {getRandomFileName(1), int64(16 * cos.MiB)}, 155 {getRandomFileName(2), int64(4 * cos.MiB)}, 156 {getRandomFileName(3), int64(8 * cos.MiB)}, 157 } 158 saveRandomFilesWithMetadata(filesPath, files) 159 160 // To go under lwm (50%), LRU should evict the oldest files until <=50% reached 161 // Those files are a 4MB file and a 16MB file 162 space.RunLRU(ini) 163 164 filesLeft, err := os.ReadDir(filesPath) 165 Expect(len(filesLeft)).To(Equal(2)) 166 Expect(err).NotTo(HaveOccurred()) 167 168 correctFilenamesLeft := namesFromFilesMetadatas(files[2:]) 169 for _, name := range filesLeft { 170 Expect(cos.StringInSlice(name.Name(), correctFilenamesLeft)).To(BeTrue()) 171 } 172 }) 173 174 It("should evict only files from requested bucket [ignores LRU prop]", func() { 175 if testing.Short() { 176 Skip("skipping in short mode") 177 } 178 saveRandomFiles(fpAnother, numberOfCreatedFiles) 179 saveRandomFiles(filesPath, numberOfCreatedFiles) 180 181 ini.Buckets = []cmn.Bck{bckAnother} 182 ini.Force = true // Ignore LRU enabled 183 space.RunLRU(ini) 184 185 files, err := os.ReadDir(filesPath) 186 Expect(err).NotTo(HaveOccurred()) 187 filesAnother, err := os.ReadDir(fpAnother) 188 Expect(err).NotTo(HaveOccurred()) 189 190 numFilesLeft := len(files) 191 numFilesLeftAnother := len(filesAnother) 192 193 // files not evicted from bucket 194 Expect(numFilesLeft).To(BeNumerically("==", numberOfCreatedFiles)) 195 196 // too few files evicted 197 Expect(float64(numFilesLeftAnother) / numberOfCreatedFiles * initialDiskUsagePct).To(BeNumerically("<=", 0.01*lwm)) 198 // to many files evicted 199 Expect(float64(numFilesLeftAnother+1) / numberOfCreatedFiles * initialDiskUsagePct).To(BeNumerically(">", 0.01*lwm)) 200 }) 201 }) 202 203 Describe("not evict files", func() { 204 var ini *space.IniLRU 205 BeforeEach(func() { 206 ini = newIniLRU() 207 }) 208 It("should do nothing when disk usage is below hwm", func() { 209 const numberOfFiles = 4 210 config := cmn.GCO.BeginUpdate() 211 config.Space.HighWM = 95 212 config.Space.LowWM = 40 213 cmn.GCO.CommitUpdate(config) 214 215 ini.GetFSStats = getMockGetFSStats(numberOfFiles) 216 217 saveRandomFiles(filesPath, numberOfFiles) 218 219 space.RunLRU(ini) 220 221 files, err := os.ReadDir(filesPath) 222 Expect(err).NotTo(HaveOccurred()) 223 Expect(len(files)).To(Equal(numberOfFiles)) 224 }) 225 226 It("should do nothing if dontevict time was not reached", func() { 227 const numberOfFiles = 6 228 config := cmn.GCO.BeginUpdate() 229 config.LRU.DontEvictTime = cos.Duration(5 * time.Minute) 230 cmn.GCO.CommitUpdate(config) 231 232 ini.GetFSStats = getMockGetFSStats(numberOfFiles) 233 234 saveRandomFiles(filesPath, numberOfFiles) 235 236 space.RunLRU(ini) 237 238 files, err := os.ReadDir(filesPath) 239 Expect(err).NotTo(HaveOccurred()) 240 Expect(len(files)).To(Equal(numberOfFiles)) 241 }) 242 243 It("should not evict if LRU disabled and force is false", func() { 244 saveRandomFiles(fpAnother, numberOfCreatedFiles) 245 246 ini.Buckets = []cmn.Bck{bckAnother} // bckAnother has LRU disabled 247 space.RunLRU(ini) 248 249 filesAnother, err := os.ReadDir(fpAnother) 250 Expect(err).NotTo(HaveOccurred()) 251 252 numFilesLeft := len(filesAnother) 253 Expect(numFilesLeft).To(BeNumerically("==", numberOfCreatedFiles)) 254 }) 255 }) 256 257 Describe("cleanup 'deleted'", func() { 258 var ini *space.IniCln 259 BeforeEach(func() { 260 ini = newInitStoreCln() 261 }) 262 It("should remove all deleted items", func() { 263 var ( 264 availablePaths = fs.GetAvail() 265 mi = availablePaths[basePath] 266 ) 267 268 saveRandomFiles(filesPath, 10) 269 Expect(filesPath).To(BeADirectory()) 270 271 err := mi.MoveToDeleted(filesPath) 272 Expect(err).NotTo(HaveOccurred()) 273 Expect(filesPath).NotTo(BeADirectory()) 274 275 files, err := os.ReadDir(mi.DeletedRoot()) 276 Expect(err).NotTo(HaveOccurred()) 277 Expect(len(files)).To(Equal(1)) 278 279 space.RunCleanup(ini) 280 281 files, err = os.ReadDir(mi.DeletedRoot()) 282 Expect(err).NotTo(HaveOccurred()) 283 Expect(len(files)).To(Equal(0)) 284 }) 285 }) 286 }) 287 }) 288 289 // 290 // test helpers & utilities 291 // 292 293 func namesFromFilesMetadatas(fileMetadata []fileMetadata) []string { 294 result := make([]string, len(fileMetadata)) 295 for i, file := range fileMetadata { 296 result[i] = file.name 297 } 298 return result 299 } 300 301 func mockGetFSUsedPercentage(string) (usedPrecentage int64, _ bool) { 302 return int64(initialDiskUsagePct * 100), true 303 } 304 305 func getMockGetFSStats(currentFilesNum int) func(string) (uint64, uint64, int64, error) { 306 currDiskUsage := initialDiskUsagePct 307 return func(string) (blocks, bavail uint64, bsize int64, err error) { 308 bsize = blockSize 309 btaken := uint64(currentFilesNum * fileSize / blockSize) 310 blocks = uint64(float64(btaken) / currDiskUsage) // gives around currDiskUsage of virtual disk usage 311 bavail = blocks - btaken 312 return 313 } 314 } 315 316 func newTargetLRUMock() *mock.TargetMock { 317 // Bucket owner mock, required for LOM 318 var ( 319 bmdMock = mock.NewBaseBownerMock( 320 meta.NewBck( 321 bucketName, apc.AIS, cmn.NsGlobal, 322 &cmn.Bprops{ 323 Cksum: cmn.CksumConf{Type: cos.ChecksumNone}, 324 LRU: cmn.LRUConf{Enabled: true}, 325 Access: apc.AccessAll, 326 BID: 0xa7b8c1d2, 327 }, 328 ), 329 meta.NewBck( 330 bucketNameAnother, apc.AIS, cmn.NsGlobal, 331 &cmn.Bprops{ 332 Cksum: cmn.CksumConf{Type: cos.ChecksumNone}, 333 LRU: cmn.LRUConf{Enabled: false}, 334 Access: apc.AccessAll, 335 BID: 0xf4e3d2c1, 336 }, 337 ), 338 ) 339 tMock = mock.NewTarget(bmdMock) 340 ) 341 return tMock 342 } 343 344 func newIniLRU() *space.IniLRU { 345 xlru := &space.XactLRU{} 346 xlru.InitBase(cos.GenUUID(), apc.ActLRU, nil) 347 return &space.IniLRU{ 348 Xaction: xlru, 349 Config: cmn.GCO.Get(), 350 StatsT: mock.NewStatsTracker(), 351 GetFSUsedPercentage: mockGetFSUsedPercentage, 352 GetFSStats: getMockGetFSStats(numberOfCreatedFiles), 353 } 354 } 355 356 func newInitStoreCln() *space.IniCln { 357 xcln := &space.XactCln{} 358 xcln.InitBase(cos.GenUUID(), apc.ActStoreCleanup, nil) 359 return &space.IniCln{ 360 Xaction: xcln, 361 Config: cmn.GCO.Get(), 362 StatsT: mock.NewStatsTracker(), 363 } 364 } 365 366 func initConfig() { 367 config := cmn.GCO.BeginUpdate() 368 config.LRU.DontEvictTime = 0 369 config.Space.HighWM = hwm 370 config.Space.LowWM = lwm 371 config.LRU.Enabled = true 372 config.Log.Level = "3" 373 cmn.GCO.CommitUpdate(config) 374 } 375 376 func createAndAddMountpath(path string) { 377 cos.CreateDir(path) 378 fs.TestNew(nil) 379 fs.Add(path, "daeID") 380 381 fs.CSM.Reg(fs.ObjectType, &fs.ObjectContentResolver{}, true) 382 fs.CSM.Reg(fs.WorkfileType, &fs.WorkfileContentResolver{}, true) 383 } 384 385 func getRandomFileName(fileCounter int) string { 386 return fmt.Sprintf("%v-%v.txt", trand.String(13), fileCounter) 387 } 388 389 func saveRandomFile(filename string, size int64) { 390 buff := make([]byte, size) 391 _, err := cos.SaveReader(filename, rand.Reader, buff, cos.ChecksumNone, size) 392 Expect(err).NotTo(HaveOccurred()) 393 lom := &core.LOM{} 394 err = lom.InitFQN(filename, nil) 395 Expect(err).NotTo(HaveOccurred()) 396 lom.SetSize(size) 397 lom.IncVersion() 398 lom.SetAtimeUnix(time.Now().UnixNano()) 399 Expect(lom.Persist()).NotTo(HaveOccurred()) 400 } 401 402 func saveRandomFilesWithMetadata(filesPath string, files []fileMetadata) { 403 for _, file := range files { 404 saveRandomFile(path.Join(filesPath, file.name), file.size) 405 } 406 } 407 408 // Saves random bytes to a file with random name. 409 // timestamps and names are not increasing in the same manner 410 func saveRandomFiles(filesPath string, filesNumber int) { 411 for i := range filesNumber { 412 saveRandomFile(path.Join(filesPath, getRandomFileName(i)), fileSize) 413 } 414 }