gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/dirs_test.go (about) 1 package renter 2 3 import ( 4 "fmt" 5 "math" 6 "os" 7 "sort" 8 "strings" 9 "sync" 10 "testing" 11 "time" 12 13 "gitlab.com/NebulousLabs/errors" 14 "gitlab.com/SkynetLabs/skyd/build" 15 "gitlab.com/SkynetLabs/skyd/siatest/dependencies" 16 "gitlab.com/SkynetLabs/skyd/skymodules" 17 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem" 18 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siadir" 19 ) 20 21 // timeEquals is a helper function for checking if two times are equal 22 // 23 // Since we can't check timestamps for equality cause they are often set to 24 // `time.Now()` by methods, we allow a timestamp to be off by a certain delta. 25 func timeEquals(t1, t2 time.Time, delta time.Duration) bool { 26 if t1.After(t2) && t1.After(t2.Add(delta)) { 27 return false 28 } 29 if t2.After(t1) && t2.After(t1.Add(delta)) { 30 return false 31 } 32 return true 33 } 34 35 // equivalentMetadata checks whether the metadata of two directories is 36 // equivalent. In this case that means most of the fields are expected to match, 37 // but some fields like the timestamp are allowed to be off by a bit. 38 func equivalentMetadata(md1, md2 siadir.Metadata, delta time.Duration) (err error) { 39 // Check all the time fields first 40 // Check AggregateLastHealthCheckTime 41 if !timeEquals(md1.AggregateLastHealthCheckTime, md2.AggregateLastHealthCheckTime, delta) { 42 err = errors.Compose(err, fmt.Errorf("AggregateLastHealthCheckTimes not equal %v and %v (%v)", md1.AggregateLastHealthCheckTime, md2.AggregateLastHealthCheckTime, delta)) 43 } 44 // Check AggregateModTime 45 if !timeEquals(md2.AggregateModTime, md1.AggregateModTime, delta) { 46 err = errors.Compose(err, fmt.Errorf("AggregateModTime not equal %v and %v (%v)", md1.AggregateModTime, md2.AggregateModTime, delta)) 47 } 48 // Check LastHealthCheckTime 49 if !timeEquals(md1.LastHealthCheckTime, md2.LastHealthCheckTime, delta) { 50 err = errors.Compose(err, fmt.Errorf("LastHealthCheckTimes not equal %v and %v (%v)", md1.LastHealthCheckTime, md2.LastHealthCheckTime, delta)) 51 } 52 // Check ModTime 53 if !timeEquals(md2.ModTime, md1.ModTime, delta) { 54 err = errors.Compose(err, fmt.Errorf("ModTime not equal %v and %v (%v)", md1.ModTime, md2.ModTime, delta)) 55 } 56 57 // Check a copy of md2 with the time fields of md1 and check for Equality 58 md2Copy := md2 59 md2Copy.AggregateLastHealthCheckTime = md1.AggregateLastHealthCheckTime 60 md2Copy.AggregateModTime = md1.AggregateModTime 61 md2Copy.LastHealthCheckTime = md1.LastHealthCheckTime 62 md2Copy.ModTime = md1.ModTime 63 return errors.Compose(err, siadir.EqualMetadatas(md1, md2Copy)) 64 } 65 66 // FileListCollect returns information on all of the files stored by the 67 // renter at the specified folder. The 'cached' argument specifies whether 68 // cached values should be returned or not. 69 func (r *Renter) FileListCollect(siaPath skymodules.SiaPath, recursive, cached bool) ([]skymodules.FileInfo, error) { 70 var files []skymodules.FileInfo 71 var mu sync.Mutex 72 err := r.FileList(siaPath, recursive, cached, func(fi skymodules.FileInfo) { 73 mu.Lock() 74 files = append(files, fi) 75 mu.Unlock() 76 }) 77 // Sort slices by SiaPath. 78 sort.Slice(files, func(i, j int) bool { 79 return files[i].SiaPath.String() < files[j].SiaPath.String() 80 }) 81 return files, err 82 } 83 84 // TestRenterCreateDirectories checks that the renter properly created metadata files 85 // for direcotries 86 func TestRenterCreateDirectories(t *testing.T) { 87 if testing.Short() { 88 t.SkipNow() 89 } 90 t.Parallel() 91 92 // Create renterTester 93 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 94 if err != nil { 95 t.Fatal(err) 96 } 97 defer func() { 98 if err := rt.Close(); err != nil { 99 t.Fatal(err) 100 } 101 }() 102 103 // Test creating directory 104 siaPath, err := skymodules.NewSiaPath("foo/bar/baz") 105 if err != nil { 106 t.Fatal(err) 107 } 108 err = rt.renter.CreateDir(siaPath, skymodules.DefaultDirPerm) 109 if err != nil { 110 t.Fatal(err) 111 } 112 113 // Confirm that directory metadata files were created in all directories 114 for { 115 if err := rt.checkDirInitialized(siaPath); err != nil { 116 t.Logf("check for '%v'", siaPath) 117 t.Fatal(err) 118 } 119 if siaPath.IsRoot() { 120 break 121 } 122 siaPath, err = siaPath.Dir() 123 if err != nil { 124 t.Fatal(err) 125 } 126 } 127 } 128 129 // checkDirInitialized is a helper function that checks that the directory was 130 // initialized correctly and the metadata file exist and contain the correct 131 // information 132 func (rt *renterTester) checkDirInitialized(siaPath skymodules.SiaPath) (err error) { 133 siaDir, err := rt.renter.staticFileSystem.OpenSiaDir(siaPath) 134 if err != nil { 135 return fmt.Errorf("unable to load directory %v metadata: %v", siaPath, err) 136 } 137 defer func() { 138 err = errors.Compose(err, siaDir.Close()) 139 }() 140 fullpath := siaPath.SiaDirMetadataSysPath(rt.renter.staticFileSystem.Root()) 141 if _, err := os.Stat(fullpath); err != nil { 142 return err 143 } 144 145 // Check that metadata is default value 146 metadata, err := siaDir.Metadata() 147 if err != nil { 148 return err 149 } 150 // Check Aggregate Fields 151 if metadata.AggregateHealth != siadir.DefaultDirHealth { 152 return fmt.Errorf("AggregateHealth not initialized properly: have %v expected %v", metadata.AggregateHealth, siadir.DefaultDirHealth) 153 } 154 if !metadata.AggregateLastHealthCheckTime.IsZero() { 155 return fmt.Errorf("AggregateLastHealthCheckTime should be a zero timestamp: %v", metadata.AggregateLastHealthCheckTime) 156 } 157 if metadata.AggregateModTime.IsZero() { 158 return fmt.Errorf("AggregateModTime not initialized: %v", metadata.AggregateModTime) 159 } 160 if metadata.AggregateMinRedundancy != siadir.DefaultDirRedundancy { 161 return fmt.Errorf("AggregateMinRedundancy not initialized properly: have %v expected %v", metadata.AggregateMinRedundancy, siadir.DefaultDirRedundancy) 162 } 163 if metadata.AggregateStuckHealth != siadir.DefaultDirHealth { 164 return fmt.Errorf("AggregateStuckHealth not initialized properly: have %v expected %v", metadata.AggregateStuckHealth, siadir.DefaultDirHealth) 165 } 166 // Check SiaDir Fields 167 if metadata.Health != siadir.DefaultDirHealth { 168 return fmt.Errorf("Health not initialized properly: have %v expected %v", metadata.Health, siadir.DefaultDirHealth) 169 } 170 if !metadata.LastHealthCheckTime.IsZero() { 171 return fmt.Errorf("LastHealthCheckTime should be a zero timestamp: %v", metadata.LastHealthCheckTime) 172 } 173 if metadata.ModTime.IsZero() { 174 return fmt.Errorf("ModTime not initialized: %v", metadata.ModTime) 175 } 176 if metadata.MinRedundancy != siadir.DefaultDirRedundancy { 177 return fmt.Errorf("MinRedundancy not initialized properly: have %v expected %v", metadata.MinRedundancy, siadir.DefaultDirRedundancy) 178 } 179 if metadata.StuckHealth != siadir.DefaultDirHealth { 180 return fmt.Errorf("StuckHealth not initialized properly: have %v expected %v", metadata.StuckHealth, siadir.DefaultDirHealth) 181 } 182 path, err := siaDir.Path() 183 if err != nil { 184 return err 185 } 186 if path != rt.renter.staticFileSystem.DirPath(siaPath) { 187 return fmt.Errorf("Expected path to be %v, got %v", path, rt.renter.staticFileSystem.DirPath(siaPath)) 188 } 189 return nil 190 } 191 192 // TestDirInfo probes the DirInfo method 193 func TestDirInfo(t *testing.T) { 194 if testing.Short() { 195 t.SkipNow() 196 } 197 t.Parallel() 198 199 // Create renterTester 200 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 201 if err != nil { 202 t.Fatal(err) 203 } 204 defer func() { 205 if err := rt.Close(); err != nil { 206 t.Fatal(err) 207 } 208 }() 209 210 // Create directory 211 siaPath, err := skymodules.NewSiaPath("foo/") 212 if err != nil { 213 t.Fatal(err) 214 } 215 err = rt.renter.CreateDir(siaPath, skymodules.DefaultDirPerm) 216 if err != nil { 217 t.Fatal(err) 218 } 219 220 // Check that DirInfo returns the same information as stored in the metadata 221 fooDirInfo, err := rt.renter.staticFileSystem.DirInfo(siaPath) 222 if err != nil { 223 t.Fatal(err) 224 } 225 rootDirInfo, err := rt.renter.staticFileSystem.DirInfo(skymodules.RootSiaPath()) 226 if err != nil { 227 t.Fatal(err) 228 } 229 fooEntry, err := rt.renter.staticFileSystem.OpenSiaDir(siaPath) 230 if err != nil { 231 t.Fatal(err) 232 } 233 rootEntry, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath()) 234 if err != nil { 235 t.Fatal(err) 236 } 237 err = compareDirectoryInfoAndMetadata(fooDirInfo, fooEntry) 238 if err != nil { 239 t.Fatal(err) 240 } 241 err = compareDirectoryInfoAndMetadata(rootDirInfo, rootEntry) 242 if err != nil { 243 t.Fatal(err) 244 } 245 } 246 247 // TestRenterListDirectory verifies that the renter properly lists the contents 248 // of a directory 249 func TestRenterListDirectory(t *testing.T) { 250 if testing.Short() { 251 t.SkipNow() 252 } 253 t.Parallel() 254 255 // Create renterTester 256 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 257 if err != nil { 258 t.Fatal(err) 259 } 260 defer func() { 261 if err := rt.Close(); err != nil { 262 t.Fatal(err) 263 } 264 }() 265 266 // Create directory 267 siaPath, err := skymodules.NewSiaPath("foo/") 268 if err != nil { 269 t.Fatal(err) 270 } 271 err = rt.renter.CreateDir(siaPath, skymodules.DefaultDirPerm) 272 if err != nil { 273 t.Fatal(err) 274 } 275 276 // Upload a file 277 _, err = rt.renter.newRenterTestFile() 278 if err != nil { 279 t.Fatal(err) 280 } 281 282 // Confirm that we get expected number of FileInfo and DirectoryInfo. 283 directories, err := rt.renter.DirList(skymodules.RootSiaPath()) 284 if err != nil { 285 t.Fatal(err) 286 } 287 // 5 Directories because of root, foo, home, var, and snapshots 288 if len(directories) != 5 { 289 t.Fatal("Expected 5 DirectoryInfos but got", len(directories)) 290 } 291 files, err := rt.renter.FileListCollect(skymodules.RootSiaPath(), false, false) 292 if err != nil { 293 t.Fatal(err) 294 } 295 if len(files) != 1 { 296 t.Fatal("Expected 1 FileInfo but got", len(files)) 297 } 298 299 // Ensure that the metadata of all the directories gets collected into the 300 // root aggregate stats. 301 for _, dir := range directories { 302 err = rt.renter.UpdateMetadata(dir.SiaPath, false) 303 if err != nil { 304 t.Fatal(err) 305 } 306 } 307 308 // Wait for root directory to show proper number of files and subdirs. 309 err = build.Retry(100, 100*time.Millisecond, func() error { 310 root, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath()) 311 if err != nil { 312 return err 313 } 314 rootMD, err := root.Metadata() 315 if err != nil { 316 return err 317 } 318 // Check the aggregate and siadir fields. 319 // 320 // Expecting /home, /home/user, /var, /var/skynet, /snapshots, /foo 321 if rootMD.AggregateNumSubDirs != 6 { 322 return fmt.Errorf("Expected 6 subdirs in aggregate but got %v", rootMD.AggregateNumSubDirs) 323 } 324 if rootMD.NumSubDirs != 4 { 325 return fmt.Errorf("Expected 4 subdirs but got %v", rootMD.NumSubDirs) 326 } 327 if rootMD.AggregateNumFiles != 1 { 328 return fmt.Errorf("Expected 1 file in aggregate but got %v", rootMD.AggregateNumFiles) 329 } 330 if rootMD.NumFiles != 1 { 331 return fmt.Errorf("Expected 1 file but got %v", rootMD.NumFiles) 332 } 333 return nil 334 }) 335 if err != nil { 336 t.Fatal(err) 337 } 338 339 // Verify that the directory information matches the on disk information 340 err = build.Retry(100, 100*time.Millisecond, func() error { 341 rootDir, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath()) 342 if err != nil { 343 return err 344 } 345 fooDir, err := rt.renter.staticFileSystem.OpenSiaDir(siaPath) 346 if err != nil { 347 return err 348 } 349 homeDir, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.HomeFolder) 350 if err != nil { 351 return err 352 } 353 snapshotsDir, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.BackupFolder) 354 if err != nil { 355 return err 356 } 357 defer func() { 358 err = errors.Compose(err, rootDir.Close(), fooDir.Close(), homeDir.Close(), snapshotsDir.Close()) 359 }() 360 361 // Refresh Directories 362 directories, err = rt.renter.DirList(skymodules.RootSiaPath()) 363 if err != nil { 364 return err 365 } 366 // Sort directories. 367 sort.Slice(directories, func(i, j int) bool { 368 return strings.Compare(directories[i].SiaPath.String(), directories[j].SiaPath.String()) < 0 369 }) 370 if err = compareDirectoryInfoAndMetadataCustom(directories[0], rootDir, false); err != nil { 371 return err 372 } 373 if err = compareDirectoryInfoAndMetadataCustom(directories[1], fooDir, false); err != nil { 374 return err 375 } 376 if err = compareDirectoryInfoAndMetadataCustom(directories[2], homeDir, false); err != nil { 377 return err 378 } 379 if err = compareDirectoryInfoAndMetadataCustom(directories[3], snapshotsDir, false); err != nil { 380 return err 381 } 382 return nil 383 }) 384 if err != nil { 385 t.Fatal(err) 386 } 387 } 388 389 // compareDirectoryInfoAndMetadata is a helper that compares the information in 390 // a DirectoryInfo struct and a SiaDirSetEntry struct 391 func compareDirectoryInfoAndMetadata(di skymodules.DirectoryInfo, siaDir *filesystem.DirNode) error { 392 return compareDirectoryInfoAndMetadataCustom(di, siaDir, true) 393 } 394 395 // compareDirectoryInfoAndMetadataCustom is a helper that compares the 396 // information in a DirectoryInfo struct and a SiaDirSetEntry struct with the 397 // option to ignore fields based on differences in persistence 398 func compareDirectoryInfoAndMetadataCustom(di skymodules.DirectoryInfo, siaDir *filesystem.DirNode, checkTimes bool) error { 399 md, err := siaDir.Metadata() 400 if err != nil { 401 return err 402 } 403 404 // Compare Aggregate Fields 405 if md.AggregateHealth != di.AggregateHealth { 406 return fmt.Errorf("AggregateHealths not equal, %v and %v", md.AggregateHealth, di.AggregateHealth) 407 } 408 aggregateMaxHealth := math.Max(md.AggregateHealth, md.AggregateStuckHealth) 409 if di.AggregateMaxHealth != aggregateMaxHealth { 410 return fmt.Errorf("AggregateMaxHealths not equal %v and %v", di.AggregateMaxHealth, aggregateMaxHealth) 411 } 412 aggregateMaxHealthPercentage := skymodules.HealthPercentage(aggregateMaxHealth) 413 if di.AggregateMaxHealthPercentage != aggregateMaxHealthPercentage { 414 return fmt.Errorf("AggregateMaxHealthPercentage not equal %v and %v", di.AggregateMaxHealthPercentage, aggregateMaxHealthPercentage) 415 } 416 if md.AggregateMinRedundancy != di.AggregateMinRedundancy { 417 return fmt.Errorf("AggregateMinRedundancy not equal, %v and %v", md.AggregateMinRedundancy, di.AggregateMinRedundancy) 418 } 419 if md.AggregateNumFiles != di.AggregateNumFiles { 420 return fmt.Errorf("AggregateNumFiles not equal, %v and %v", md.AggregateNumFiles, di.AggregateNumFiles) 421 } 422 if md.AggregateNumLostFiles != di.AggregateNumLostFiles { 423 return fmt.Errorf("AggregateNumLostFiles not equal, %v and %v", md.AggregateNumLostFiles, di.AggregateNumLostFiles) 424 } 425 if md.AggregateNumStuckChunks != di.AggregateNumStuckChunks { 426 return fmt.Errorf("AggregateNumStuckChunks not equal, %v and %v", md.AggregateNumStuckChunks, di.AggregateNumStuckChunks) 427 } 428 if md.AggregateNumSubDirs != di.AggregateNumSubDirs { 429 return fmt.Errorf("AggregateNumSubDirs not equal, %v and %v", md.AggregateNumSubDirs, di.AggregateNumSubDirs) 430 } 431 if md.AggregateNumUnfinishedFiles != di.AggregateNumUnfinishedFiles { 432 return fmt.Errorf("AggregateNumUnfinishedFiles not equal, %v and %v", md.AggregateNumUnfinishedFiles, di.AggregateNumUnfinishedFiles) 433 } 434 if md.AggregateSize != di.AggregateSize { 435 return fmt.Errorf("AggregateSizes not equal, %v and %v", md.AggregateSize, di.AggregateSize) 436 } 437 if md.NumStuckChunks != di.AggregateNumStuckChunks { 438 return fmt.Errorf("NumStuckChunks not equal, %v and %v", md.NumStuckChunks, di.AggregateNumStuckChunks) 439 } 440 441 // Compare Aggregate Time Fields 442 if checkTimes { 443 if di.AggregateLastHealthCheckTime != md.AggregateLastHealthCheckTime { 444 return fmt.Errorf("AggregateLastHealthCheckTimes not equal %v and %v", di.AggregateLastHealthCheckTime, md.AggregateLastHealthCheckTime) 445 } 446 if di.AggregateMostRecentModTime != md.AggregateModTime { 447 return fmt.Errorf("AggregateModTimes not equal %v and %v", di.AggregateMostRecentModTime, md.AggregateModTime) 448 } 449 } 450 451 // Compare Directory Fields 452 if md.Health != di.Health { 453 return fmt.Errorf("healths not equal, %v and %v", md.Health, di.Health) 454 } 455 maxHealth := math.Max(md.Health, md.StuckHealth) 456 if di.MaxHealth != maxHealth { 457 return fmt.Errorf("MaxHealths not equal %v and %v", di.MaxHealth, maxHealth) 458 } 459 maxHealthPercentage := skymodules.HealthPercentage(maxHealth) 460 if di.MaxHealthPercentage != maxHealthPercentage { 461 return fmt.Errorf("MaxHealthPercentage not equal %v and %v", di.MaxHealthPercentage, maxHealthPercentage) 462 } 463 if md.MinRedundancy != di.MinRedundancy { 464 return fmt.Errorf("MinRedundancy not equal, %v and %v", md.MinRedundancy, di.MinRedundancy) 465 } 466 if md.NumFiles != di.NumFiles { 467 return fmt.Errorf("NumFiles not equal, %v and %v", md.NumFiles, di.NumFiles) 468 } 469 if md.NumLostFiles != di.NumLostFiles { 470 return fmt.Errorf("NumLostFiles not equal, %v and %v", md.NumLostFiles, di.NumLostFiles) 471 } 472 if md.NumStuckChunks != di.NumStuckChunks { 473 return fmt.Errorf("NumStuckChunks not equal, %v and %v", md.NumStuckChunks, di.NumStuckChunks) 474 } 475 if md.NumSubDirs != di.NumSubDirs { 476 return fmt.Errorf("NumSubDirs not equal, %v and %v", md.NumSubDirs, di.NumSubDirs) 477 } 478 if md.NumUnfinishedFiles != di.NumUnfinishedFiles { 479 return fmt.Errorf("NumUnfinishedFiles not equal, %v and %v", md.NumUnfinishedFiles, di.NumUnfinishedFiles) 480 } 481 if md.Size != di.DirSize { 482 return fmt.Errorf("Sizes not equal, %v and %v", md.Size, di.DirSize) 483 } 484 if md.StuckHealth != di.StuckHealth { 485 return fmt.Errorf("stuck healths not equal, %v and %v", md.StuckHealth, di.StuckHealth) 486 } 487 488 // Compare Directory Time Fields 489 if checkTimes { 490 if di.LastHealthCheckTime != md.LastHealthCheckTime { 491 return fmt.Errorf("LastHealthCheckTimes not equal %v and %v", di.LastHealthCheckTime, md.LastHealthCheckTime) 492 } 493 if di.MostRecentModTime != md.ModTime { 494 return fmt.Errorf("ModTimes not equal %v and %v", di.MostRecentModTime, md.ModTime) 495 } 496 } 497 return nil 498 }