gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/repair_test.go (about) 1 package renter 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "os" 7 "testing" 8 "time" 9 10 "gitlab.com/NebulousLabs/fastrand" 11 12 "gitlab.com/SiaPrime/SiaPrime/build" 13 "gitlab.com/SiaPrime/SiaPrime/crypto" 14 "gitlab.com/SiaPrime/SiaPrime/modules" 15 "gitlab.com/SiaPrime/SiaPrime/modules/renter/siadir" 16 "gitlab.com/SiaPrime/SiaPrime/modules/renter/siafile" 17 "gitlab.com/SiaPrime/SiaPrime/siatest/dependencies" 18 ) 19 20 // equalBubbledMetadata is a helper that checks for equality in the siadir 21 // metadata that gets bubbled 22 func equalBubbledMetadata(md1, md2 siadir.Metadata) error { 23 // Check AggregateHealth 24 if md1.AggregateHealth != md2.AggregateHealth { 25 return fmt.Errorf("AggregateHealth not equal, %v and %v", md1.AggregateHealth, md2.AggregateHealth) 26 } 27 // Check AggregateNumFiles 28 if md1.AggregateNumFiles != md2.AggregateNumFiles { 29 return fmt.Errorf("AggregateNumFiles not equal, %v and %v", md1.AggregateNumFiles, md2.AggregateNumFiles) 30 } 31 // Check Size 32 if md1.AggregateSize != md2.AggregateSize { 33 return fmt.Errorf("aggregate sizes not equal, %v and %v", md1.AggregateSize, md2.AggregateSize) 34 } 35 // Check Health 36 if md1.Health != md2.Health { 37 return fmt.Errorf("healths not equal, %v and %v", md1.Health, md2.Health) 38 } 39 // Check LastHealthCheckTimes 40 if md2.LastHealthCheckTime != md1.LastHealthCheckTime { 41 return fmt.Errorf("LastHealthCheckTimes not equal %v and %v", md2.LastHealthCheckTime, md1.LastHealthCheckTime) 42 } 43 // Check MinRedundancy 44 if md1.MinRedundancy != md2.MinRedundancy { 45 return fmt.Errorf("MinRedundancy not equal, %v and %v", md1.MinRedundancy, md2.MinRedundancy) 46 } 47 // Check Mod Times 48 if md2.ModTime != md1.ModTime { 49 return fmt.Errorf("ModTimes not equal %v and %v", md2.ModTime, md1.ModTime) 50 } 51 // Check NumFiles 52 if md1.NumFiles != md2.NumFiles { 53 return fmt.Errorf("NumFiles not equal, %v and %v", md1.NumFiles, md2.NumFiles) 54 } 55 // Check NumStuckChunks 56 if md1.NumStuckChunks != md2.NumStuckChunks { 57 return fmt.Errorf("NumStuckChunks not equal, %v and %v", md1.NumStuckChunks, md2.NumStuckChunks) 58 } 59 // Check NumSubDirs 60 if md1.NumSubDirs != md2.NumSubDirs { 61 return fmt.Errorf("NumSubDirs not equal, %v and %v", md1.NumSubDirs, md2.NumSubDirs) 62 } 63 // Check StuckHealth 64 if md1.StuckHealth != md2.StuckHealth { 65 return fmt.Errorf("stuck healths not equal, %v and %v", md1.StuckHealth, md2.StuckHealth) 66 } 67 return nil 68 } 69 70 // TestBubbleHealth tests to make sure that the health of the most in need file 71 // in a directory is bubbled up to the right levels and probes the supporting 72 // functions as well 73 func TestBubbleHealth(t *testing.T) { 74 if testing.Short() { 75 t.SkipNow() 76 } 77 t.Parallel() 78 79 // Create test renter 80 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 81 if err != nil { 82 t.Fatal(err) 83 } 84 defer rt.Close() 85 86 // Check to make sure bubble doesn't error on an empty directory 87 err = rt.renter.managedBubbleMetadata(modules.RootSiaPath()) 88 if err != nil { 89 t.Fatal(err) 90 } 91 defaultMetadata := siadir.Metadata{ 92 AggregateHealth: siadir.DefaultDirHealth, 93 Health: siadir.DefaultDirHealth, 94 StuckHealth: siadir.DefaultDirHealth, 95 LastHealthCheckTime: time.Now(), 96 NumStuckChunks: 0, 97 } 98 build.Retry(100, 100*time.Millisecond, func() error { 99 // Get Root Directory Health 100 metadata, err := rt.renter.managedDirectoryMetadata(modules.RootSiaPath()) 101 if err != nil { 102 return err 103 } 104 // Check Health 105 if err = equalBubbledMetadata(metadata, defaultMetadata); err != nil { 106 return err 107 } 108 return nil 109 }) 110 if err != nil { 111 t.Fatal(err) 112 } 113 114 // Create a test directory with the following healths 115 // 116 // root/ 1 117 // root/SubDir1/ 1 118 // root/SubDir1/SubDir1/ 1 119 // root/SubDir1/SubDir2/ 4 120 121 // Create directory tree 122 subDir1, err := modules.NewSiaPath("SubDir1") 123 if err != nil { 124 t.Fatal(err) 125 } 126 subDir2, err := modules.NewSiaPath("SubDir2") 127 if err != nil { 128 t.Fatal(err) 129 } 130 subDir1_1, err := subDir1.Join(subDir1.String()) 131 if err != nil { 132 t.Fatal(err) 133 } 134 subDir1_2, err := subDir1.Join(subDir2.String()) 135 if err != nil { 136 t.Fatal(err) 137 } 138 if err := rt.renter.CreateDir(subDir1_1); err != nil { 139 t.Fatal(err) 140 } 141 if err := rt.renter.CreateDir(subDir1_2); err != nil { 142 t.Fatal(err) 143 } 144 145 // Set Healths of all the directories so they are not the defaults 146 // 147 // NOTE: You cannot set the NumStuckChunks to a non zero number without a 148 // file in the directory as this will create a developer error 149 var siaPath modules.SiaPath 150 checkTime := time.Now() 151 metadataUpdate := siadir.Metadata{ 152 AggregateHealth: 1, 153 Health: 1, 154 StuckHealth: 0, 155 LastHealthCheckTime: checkTime, 156 } 157 if err := rt.renter.staticDirSet.UpdateMetadata(modules.RootSiaPath(), metadataUpdate); err != nil { 158 t.Fatal(err) 159 } 160 siaPath = subDir1 161 if err := rt.renter.staticDirSet.UpdateMetadata(siaPath, metadataUpdate); err != nil { 162 t.Fatal(err) 163 } 164 siaPath = subDir1_1 165 if err := rt.renter.staticDirSet.UpdateMetadata(siaPath, metadataUpdate); err != nil { 166 t.Fatal(err) 167 } 168 // Set health of subDir1/subDir2 to be the worst and set the 169 siaPath = subDir1_2 170 metadataUpdate.Health = 4 171 if err := rt.renter.staticDirSet.UpdateMetadata(siaPath, metadataUpdate); err != nil { 172 t.Fatal(err) 173 } 174 175 // Bubble the health of the directory that has the worst pre set health 176 // subDir1/subDir2, the health that gets bubbled should be the health of 177 // subDir1/subDir1 since subDir1/subDir2 is empty meaning it's calculated 178 // health will return to the default health, even through we set the health 179 // to be the worst health 180 // 181 // Note: this tests the edge case of bubbling an empty directory and 182 // directories with no files but do have sub directories since bubble will 183 // execute on all the parent directories 184 rt.renter.managedBubbleMetadata(siaPath) 185 build.Retry(100, 100*time.Millisecond, func() error { 186 // Get Root Directory Health 187 metadata, err := rt.renter.managedDirectoryMetadata(modules.RootSiaPath()) 188 if err != nil { 189 return err 190 } 191 // Compare to metadata of subDir1/subDir1 192 expectedHealth, err := rt.renter.managedDirectoryMetadata(subDir1_1) 193 if err != nil { 194 return err 195 } 196 if err = equalBubbledMetadata(metadata, expectedHealth); err != nil { 197 return err 198 } 199 return nil 200 }) 201 if err != nil { 202 t.Fatal(err) 203 } 204 205 // Add a file to the lowest level 206 // 207 // Worst health with current erasure coding is 2 = (1 - (0-1)/1) 208 rsc, _ := siafile.NewRSCode(1, 1) 209 siaPath, err = subDir1_2.Join("test") 210 if err != nil { 211 t.Fatal(err) 212 } 213 up := modules.FileUploadParams{ 214 Source: "", 215 SiaPath: siaPath, 216 ErasureCode: rsc, 217 } 218 f, err := rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), 100, 0777) 219 if err != nil { 220 t.Fatal(err) 221 } 222 // Since we are just adding the file, no chunks will have been uploaded 223 // meaning the health of the file should be the worst case health. Now the 224 // health that is bubbled up should be the health of the file added to 225 // subDir1/subDir2 226 // 227 // Note: this tests the edge case of bubbling a directory with a file 228 // but no sub directories 229 offline, goodForRenew, _ := rt.renter.managedRenterContractsAndUtilities([]*siafile.SiaFileSetEntry{f}) 230 fileHealth, _, _, _, _ := f.Health(offline, goodForRenew) 231 if fileHealth != 2 { 232 t.Fatalf("Expected heath to be 2, got %v", fileHealth) 233 } 234 235 // Mark the file as stuck by marking one of its chunks as stuck 236 f.SetStuck(0, true) 237 238 // Now when we bubble the health and check for the worst health we should still see 239 // that the health is the health of subDir1/subDir1 which was set to 1 again 240 // and the stuck health will be the health of the stuck file 241 rt.renter.managedBubbleMetadata(siaPath) 242 build.Retry(100, 100*time.Millisecond, func() error { 243 // Get Root Directory Health 244 metadata, err := rt.renter.managedDirectoryMetadata(modules.RootSiaPath()) 245 if err != nil { 246 return err 247 } 248 // Compare to metadata of subDir1/subDir1 249 expectedHealth, err := rt.renter.managedDirectoryMetadata(subDir1_1) 250 if err != nil { 251 return err 252 } 253 expectedHealth.StuckHealth = 2 254 expectedHealth.NumStuckChunks++ 255 if err = equalBubbledMetadata(metadata, expectedHealth); err != nil { 256 return err 257 } 258 return nil 259 }) 260 if err != nil { 261 t.Fatal(err) 262 } 263 264 // Mark the file as un-stuck 265 f.SetStuck(0, false) 266 267 // Now if we bubble the health and check for the worst health we should see 268 // that the health is the health of the file 269 rt.renter.managedBubbleMetadata(siaPath) 270 expectedHealth := siadir.Metadata{ 271 Health: 2, 272 StuckHealth: 0, 273 } 274 build.Retry(100, 100*time.Millisecond, func() error { 275 // Get Root Directory Health 276 health, err := rt.renter.managedDirectoryMetadata(modules.RootSiaPath()) 277 if err != nil { 278 return err 279 } 280 // Check Health 281 if err = equalBubbledMetadata(health, expectedHealth); err != nil { 282 return err 283 } 284 return nil 285 }) 286 if err != nil { 287 t.Fatal(err) 288 } 289 290 // Add a sub directory to the directory that contains the file that has a 291 // worst health than the file and confirm that health gets bubbled up. 292 // 293 // Note: this tests the edge case of bubbling a directory that has both a 294 // file and a sub directory 295 subDir1_2_1, err := subDir1_2.Join(subDir1.String()) 296 if err != nil { 297 t.Fatal(err) 298 } 299 if err := rt.renter.CreateDir(subDir1_2_1); err != nil { 300 t.Fatal(err) 301 } 302 // Reset metadataUpdate with expected values 303 expectedHealth = siadir.Metadata{ 304 AggregateHealth: 4, 305 Health: 4, 306 StuckHealth: 0, 307 LastHealthCheckTime: time.Now(), 308 } 309 if err := rt.renter.staticDirSet.UpdateMetadata(subDir1_2_1, expectedHealth); err != nil { 310 t.Fatal(err) 311 } 312 rt.renter.managedBubbleMetadata(siaPath) 313 build.Retry(100, 100*time.Millisecond, func() error { 314 // Get Root Directory Health 315 health, err := rt.renter.managedDirectoryMetadata(modules.RootSiaPath()) 316 if err != nil { 317 return err 318 } 319 // Check Health 320 if err = equalBubbledMetadata(health, expectedHealth); err != nil { 321 return err 322 } 323 return nil 324 }) 325 if err != nil { 326 t.Fatal(err) 327 } 328 } 329 330 // TestOldestHealthCheckTime probes managedOldestHealthCheckTime to verify that 331 // the directory with the oldest LastHealthCheckTime is returned 332 func TestOldestHealthCheckTime(t *testing.T) { 333 if testing.Short() { 334 t.SkipNow() 335 } 336 t.Parallel() 337 338 // Create a test directory with sub folders 339 // 340 // root/ 1 341 // root/SubDir1/ 342 // root/SubDir1/SubDir2/ 343 // root/SubDir2/ 344 345 // Create test renter 346 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 347 if err != nil { 348 t.Fatal(err) 349 } 350 defer rt.Close() 351 352 // Create directory tree 353 subDir1, err := modules.NewSiaPath("SubDir1") 354 if err != nil { 355 t.Fatal(err) 356 } 357 subDir2, err := modules.NewSiaPath("SubDir2") 358 if err != nil { 359 t.Fatal(err) 360 } 361 if err := rt.renter.CreateDir(subDir1); err != nil { 362 t.Fatal(err) 363 } 364 if err := rt.renter.CreateDir(subDir2); err != nil { 365 t.Fatal(err) 366 } 367 subDir1_2, err := subDir1.Join(subDir2.String()) 368 if err != nil { 369 t.Fatal(err) 370 } 371 if err := rt.renter.CreateDir(subDir1_2); err != nil { 372 t.Fatal(err) 373 } 374 375 // Set the LastHealthCheckTime of SubDir1/SubDir2 to be the oldest 376 oldestCheckTime := time.Now().AddDate(0, 0, -1) 377 oldestHealthCheckUpdate := siadir.Metadata{ 378 Health: 1, 379 StuckHealth: 0, 380 LastHealthCheckTime: oldestCheckTime, 381 } 382 if err := rt.renter.staticDirSet.UpdateMetadata(subDir1_2, oldestHealthCheckUpdate); err != nil { 383 t.Fatal(err) 384 } 385 386 // Bubble the health of SubDir1 so that the oldest LastHealthCheckTime of 387 // SubDir1/SubDir2 gets bubbled up 388 rt.renter.managedBubbleMetadata(subDir1) 389 390 // Find the oldest directory, should be SubDir1/SubDir2 391 build.Retry(100, 100*time.Millisecond, func() error { 392 dir, lastCheck, err := rt.renter.managedOldestHealthCheckTime() 393 if err != nil { 394 return err 395 } 396 if dir.Equals(subDir1_2) { 397 return fmt.Errorf("Expected to find %v but found %v", subDir1_2.String(), dir.String()) 398 } 399 if !lastCheck.Equal(oldestCheckTime) { 400 return fmt.Errorf("Expected to find time of %v but found %v", oldestCheckTime, lastCheck) 401 } 402 return nil 403 }) 404 if err != nil { 405 t.Fatal(err) 406 } 407 } 408 409 // TestNumFiles verifies that the number of files and aggregate number of files 410 // is accurately reported 411 func TestNumFiles(t *testing.T) { 412 if testing.Short() { 413 t.SkipNow() 414 } 415 t.Parallel() 416 417 // Create a test directory with sub folders 418 // 419 // root/ file 420 // root/SubDir1/ 421 // root/SubDir1/SubDir2/ file 422 423 // Create test renter 424 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 425 if err != nil { 426 t.Fatal(err) 427 } 428 defer rt.Close() 429 430 // Create directory tree 431 subDir1, err := modules.NewSiaPath("SubDir1") 432 if err != nil { 433 t.Fatal(err) 434 } 435 subDir2, err := modules.NewSiaPath("SubDir2") 436 if err != nil { 437 t.Fatal(err) 438 } 439 if err := rt.renter.CreateDir(subDir1); err != nil { 440 t.Fatal(err) 441 } 442 subDir1_2, err := subDir1.Join(subDir2.String()) 443 if err != nil { 444 t.Fatal(err) 445 } 446 if err := rt.renter.CreateDir(subDir1_2); err != nil { 447 t.Fatal(err) 448 } 449 // Add files 450 rsc, _ := siafile.NewRSCode(1, 1) 451 up := modules.FileUploadParams{ 452 Source: "", 453 SiaPath: modules.RandomSiaPath(), 454 ErasureCode: rsc, 455 } 456 _, err = rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), 100, 0777) 457 if err != nil { 458 t.Fatal(err) 459 } 460 up.SiaPath, err = subDir1_2.Join(hex.EncodeToString(fastrand.Bytes(8))) 461 if err != nil { 462 t.Fatal(err) 463 } 464 _, err = rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), 100, 0777) 465 if err != nil { 466 t.Fatal(err) 467 } 468 469 // Call bubble on lowest lever and confirm top level reports accurate number 470 // of files and aggregate number of files 471 rt.renter.managedBubbleMetadata(subDir1_2) 472 build.Retry(100, 100*time.Millisecond, func() error { 473 dirInfo, err := rt.renter.staticDirSet.DirInfo(modules.RootSiaPath()) 474 if err != nil { 475 return err 476 } 477 if dirInfo.NumFiles != 1 { 478 return fmt.Errorf("NumFiles incorrect, got %v expected %v", dirInfo.NumFiles, 1) 479 } 480 if dirInfo.AggregateNumFiles != 2 { 481 return fmt.Errorf("AggregateNumFiles incorrect, got %v expected %v", dirInfo.AggregateNumFiles, 2) 482 } 483 return nil 484 }) 485 if err != nil { 486 t.Fatal(err) 487 } 488 } 489 490 // TestDirectorySize verifies that the Size of a directory is accurately 491 // reported 492 func TestDirectorySize(t *testing.T) { 493 if testing.Short() { 494 t.SkipNow() 495 } 496 t.Parallel() 497 498 // Create a test directory with sub folders 499 // 500 // root/ file 501 // root/SubDir1/ 502 // root/SubDir1/SubDir2/ file 503 504 // Create test renter 505 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 506 if err != nil { 507 t.Fatal(err) 508 } 509 defer rt.Close() 510 511 // Create directory tree 512 subDir1, err := modules.NewSiaPath("SubDir1") 513 if err != nil { 514 t.Fatal(err) 515 } 516 subDir2, err := modules.NewSiaPath("SubDir2") 517 if err != nil { 518 t.Fatal(err) 519 } 520 if err := rt.renter.CreateDir(subDir1); err != nil { 521 t.Fatal(err) 522 } 523 subDir1_2, err := subDir1.Join(subDir2.String()) 524 if err != nil { 525 t.Fatal(err) 526 } 527 if err := rt.renter.CreateDir(subDir1_2); err != nil { 528 t.Fatal(err) 529 } 530 // Add files 531 rsc, _ := siafile.NewRSCode(1, 1) 532 up := modules.FileUploadParams{ 533 Source: "", 534 SiaPath: modules.RandomSiaPath(), 535 ErasureCode: rsc, 536 } 537 fileSize := uint64(100) 538 _, err = rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), fileSize, 0777) 539 if err != nil { 540 t.Fatal(err) 541 } 542 up.SiaPath, err = subDir1_2.Join(hex.EncodeToString(fastrand.Bytes(8))) 543 if err != nil { 544 t.Fatal(err) 545 } 546 _, err = rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), 2*fileSize, 0777) 547 if err != nil { 548 t.Fatal(err) 549 } 550 551 // Call bubble on lowest lever and confirm top level reports accurate size 552 rt.renter.managedBubbleMetadata(subDir1_2) 553 build.Retry(100, 100*time.Millisecond, func() error { 554 dirInfo, err := rt.renter.staticDirSet.DirInfo(modules.RootSiaPath()) 555 if err != nil { 556 return err 557 } 558 if dirInfo.AggregateSize != 3*fileSize { 559 return fmt.Errorf("AggregateSize incorrect, got %v expected %v", dirInfo.AggregateSize, 3*fileSize) 560 } 561 return nil 562 }) 563 if err != nil { 564 t.Fatal(err) 565 } 566 } 567 568 // TestDirectoryModTime verifies that the last update time of a directory is 569 // accurately reported 570 func TestDirectoryModTime(t *testing.T) { 571 if testing.Short() { 572 t.SkipNow() 573 } 574 t.Parallel() 575 576 // Create a test directory with sub folders 577 // 578 // root/ file 579 // root/SubDir1/ 580 // root/SubDir1/SubDir2/ file 581 582 // Create test renter 583 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 584 if err != nil { 585 t.Fatal(err) 586 } 587 defer rt.Close() 588 589 // Create directory tree 590 subDir1, err := modules.NewSiaPath("SubDir1") 591 if err != nil { 592 t.Fatal(err) 593 } 594 subDir2, err := modules.NewSiaPath("SubDir2") 595 if err != nil { 596 t.Fatal(err) 597 } 598 if err := rt.renter.CreateDir(subDir1); err != nil { 599 t.Fatal(err) 600 } 601 subDir1_2, err := subDir1.Join(subDir2.String()) 602 if err != nil { 603 t.Fatal(err) 604 } 605 if err := rt.renter.CreateDir(subDir1_2); err != nil { 606 t.Fatal(err) 607 } 608 // Add files 609 rsc, _ := siafile.NewRSCode(1, 1) 610 up := modules.FileUploadParams{ 611 Source: "", 612 SiaPath: modules.RandomSiaPath(), 613 ErasureCode: rsc, 614 } 615 fileSize := uint64(100) 616 _, err = rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), fileSize, 0777) 617 if err != nil { 618 t.Fatal(err) 619 } 620 up.SiaPath, err = subDir1_2.Join(hex.EncodeToString(fastrand.Bytes(8))) 621 if err != nil { 622 t.Fatal(err) 623 } 624 f, err := rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), fileSize, 0777) 625 if err != nil { 626 t.Fatal(err) 627 } 628 629 // Call bubble on lowest lever and confirm top level reports accurate last 630 // update time 631 rt.renter.managedBubbleMetadata(subDir1_2) 632 build.Retry(100, 100*time.Millisecond, func() error { 633 dirInfo, err := rt.renter.staticDirSet.DirInfo(modules.RootSiaPath()) 634 if err != nil { 635 return err 636 } 637 if dirInfo.MostRecentModTime != f.ModTime() { 638 return fmt.Errorf("ModTime is incorrect, got %v expected %v", dirInfo.MostRecentModTime, f.ModTime()) 639 } 640 return nil 641 }) 642 if err != nil { 643 t.Fatal(err) 644 } 645 } 646 647 // TestRandomStuckDirectory probes managedStuckDirectory to make sure it 648 // randomly picks a correct directory 649 func TestRandomStuckDirectory(t *testing.T) { 650 if testing.Short() { 651 t.SkipNow() 652 } 653 t.Parallel() 654 655 // Create test renter 656 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 657 if err != nil { 658 t.Fatal(err) 659 } 660 defer rt.Close() 661 662 // Create a test directory with sub folders 663 // 664 // root/ 665 // root/SubDir1/ 666 // root/SubDir1/SubDir2/ 667 // root/SubDir2/ 668 subDir1, err := modules.NewSiaPath("SubDir1") 669 if err != nil { 670 t.Fatal(err) 671 } 672 subDir2, err := modules.NewSiaPath("SubDir2") 673 if err != nil { 674 t.Fatal(err) 675 } 676 if err := rt.renter.CreateDir(subDir1); err != nil { 677 t.Fatal(err) 678 } 679 if err := rt.renter.CreateDir(subDir2); err != nil { 680 t.Fatal(err) 681 } 682 subDir1_2, err := subDir1.Join(subDir2.String()) 683 if err != nil { 684 t.Fatal(err) 685 } 686 if err := rt.renter.CreateDir(subDir1_2); err != nil { 687 t.Fatal(err) 688 } 689 690 // Add a file to root and SubDir1/SubDir2 and mark the first chunk as stuck 691 // in each file 692 // 693 // This will test the edge case of continuing to find stuck files when a 694 // directory has no files only directories 695 rsc, _ := siafile.NewRSCode(1, 1) 696 up := modules.FileUploadParams{ 697 Source: "", 698 SiaPath: modules.RandomSiaPath(), 699 ErasureCode: rsc, 700 } 701 f, err := rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), 100, 0777) 702 if err != nil { 703 t.Fatal(err) 704 } 705 if err = f.SetStuck(uint64(0), true); err != nil { 706 t.Fatal(err) 707 } 708 if err = f.Close(); err != nil { 709 t.Fatal(err) 710 } 711 up.SiaPath, err = subDir1_2.Join(hex.EncodeToString(fastrand.Bytes(8))) 712 if err != nil { 713 t.Fatal(err) 714 } 715 f, err = rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), 100, 0777) 716 if err != nil { 717 t.Fatal(err) 718 } 719 if err = f.SetStuck(uint64(0), true); err != nil { 720 t.Fatal(err) 721 } 722 if err = f.Close(); err != nil { 723 t.Fatal(err) 724 } 725 726 // Bubble directory information so NumStuckChunks is updated, there should 727 // be at least 2 stuck chunks because of the two we manually marked as 728 // stuck, but the repair loop could have marked the rest as stuck so we just 729 // want to ensure that the root directory reflects at least the two we 730 // marked as stuck 731 rt.renter.managedBubbleMetadata(subDir1_2) 732 build.Retry(100, 100*time.Millisecond, func() error { 733 // Get Root Directory Metadata 734 metadata, err := rt.renter.managedDirectoryMetadata(modules.RootSiaPath()) 735 if err != nil { 736 return err 737 } 738 // Check Aggregate number of stuck chunks 739 if metadata.AggregateNumStuckChunks != uint64(2) { 740 return fmt.Errorf("Incorrect number of stuck chunks, should be 2") 741 } 742 return nil 743 }) 744 if err != nil { 745 t.Fatal(err) 746 } 747 748 // Create map of stuck directories that contain files. These are the only 749 // directories that should be returned. 750 stuckDirectories := make(map[modules.SiaPath]struct{}) 751 stuckDirectories[modules.RootSiaPath()] = struct{}{} 752 stuckDirectories[subDir1_2] = struct{}{} 753 754 // Find random directory several times, confirm that it finds a stuck 755 // directory and there it finds unique directories 756 var unique bool 757 var previousDir modules.SiaPath 758 for i := 0; i < 10; i++ { 759 dir, err := rt.renter.managedStuckDirectory() 760 if err != nil { 761 t.Log("Error with Directory `", dir, "` on iteration", i) 762 t.Fatal(err) 763 } 764 _, ok := stuckDirectories[dir] 765 if !ok { 766 t.Fatal("Found non stuck directory:", dir) 767 } 768 if !dir.Equals(previousDir) { 769 unique = true 770 } 771 previousDir = dir 772 } 773 if !unique { 774 t.Fatal("No unique directories found") 775 } 776 } 777 778 // TestCalculateFileMetadata checks that the values returned from 779 // managedCalculateFileMetadata make sense 780 func TestCalculateFileMetadata(t *testing.T) { 781 if testing.Short() { 782 t.SkipNow() 783 } 784 t.Parallel() 785 786 // Create renter 787 rt, err := newRenterTester(t.Name()) 788 if err != nil { 789 t.Fatal(err) 790 } 791 defer rt.Close() 792 793 // Create a file 794 rsc, _ := siafile.NewRSCode(1, 1) 795 siaPath, err := modules.NewSiaPath("rootFile") 796 if err != nil { 797 t.Fatal(err) 798 } 799 up := modules.FileUploadParams{ 800 Source: "", 801 SiaPath: siaPath, 802 ErasureCode: rsc, 803 } 804 fileSize := uint64(100) 805 sf, err := rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), fileSize, 0777) 806 if err != nil { 807 t.Fatal(err) 808 } 809 810 // Grab initial metadata values 811 offline, goodForRenew, _ := rt.renter.managedRenterContractsAndUtilities([]*siafile.SiaFileSetEntry{sf}) 812 health, stuckHealth, _, _, numStuckChunks := sf.Health(offline, goodForRenew) 813 redundancy, _, err := sf.Redundancy(offline, goodForRenew) 814 if err != nil { 815 t.Fatal(err) 816 } 817 lastHealthCheckTime := sf.LastHealthCheckTime() 818 modTime := sf.ModTime() 819 820 // Check calculated metadata 821 fileMetadata, err := rt.renter.managedCalculateAndUpdateFileMetadata(up.SiaPath) 822 if err != nil { 823 t.Fatal(err) 824 } 825 826 // Check siafile calculated metadata 827 if fileMetadata.Health != health { 828 t.Fatalf("health incorrect, expected %v got %v", health, fileMetadata.Health) 829 } 830 if fileMetadata.StuckHealth != stuckHealth { 831 t.Fatalf("stuckHealth incorrect, expected %v got %v", stuckHealth, fileMetadata.StuckHealth) 832 } 833 if fileMetadata.Redundancy != redundancy { 834 t.Fatalf("redundancy incorrect, expected %v got %v", redundancy, fileMetadata.Redundancy) 835 } 836 if fileMetadata.Size != fileSize { 837 t.Fatalf("size incorrect, expected %v got %v", fileSize, fileMetadata.Size) 838 } 839 if fileMetadata.NumStuckChunks != numStuckChunks { 840 t.Fatalf("numstuckchunks incorrect, expected %v got %v", numStuckChunks, fileMetadata.NumStuckChunks) 841 } 842 if fileMetadata.LastHealthCheckTime.Equal(lastHealthCheckTime) || fileMetadata.LastHealthCheckTime.IsZero() { 843 t.Log("Initial lasthealthchecktime", lastHealthCheckTime) 844 t.Log("Calculated lasthealthchecktime", fileMetadata.LastHealthCheckTime) 845 t.Fatal("Expected lasthealthchecktime to have updated and be non zero") 846 } 847 if !fileMetadata.ModTime.Equal(modTime) { 848 t.Fatalf("Unexpected modtime, expected %v got %v", modTime, fileMetadata.ModTime) 849 } 850 } 851 852 // TestCreateMissingSiaDir confirms that the repair code creates a siadir file 853 // if one is not found 854 func TestCreateMissingSiaDir(t *testing.T) { 855 if testing.Short() { 856 t.SkipNow() 857 } 858 t.Parallel() 859 860 // Create test renter 861 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 862 if err != nil { 863 t.Fatal(err) 864 } 865 defer rt.Close() 866 867 // Confirm the siadir file is on disk 868 siaDirPath := modules.RootSiaPath().SiaDirMetadataSysPath(rt.renter.staticFilesDir) 869 _, err = os.Stat(siaDirPath) 870 if err != nil { 871 t.Fatal(err) 872 } 873 874 // Remove .siadir file on disk 875 err = os.Remove(siaDirPath) 876 if err != nil { 877 t.Fatal(err) 878 } 879 880 // Confirm siadir is gone 881 _, err = os.Stat(siaDirPath) 882 if !os.IsNotExist(err) { 883 t.Fatal("Err should have been IsNotExist", err) 884 } 885 886 // Create siadir file with managedDirectoryMetadata 887 _, err = rt.renter.managedDirectoryMetadata(modules.RootSiaPath()) 888 if err != nil { 889 t.Fatal(err) 890 } 891 892 // Confirm it is on disk 893 _, err = os.Stat(siaDirPath) 894 if err != nil { 895 t.Fatal(err) 896 } 897 } 898 899 // TestAddStuckChunksToHeap probes the managedAddStuckChunksToHeap method 900 func TestAddStuckChunksToHeap(t *testing.T) { 901 if testing.Short() { 902 t.SkipNow() 903 } 904 t.Parallel() 905 906 // create renter with dependencies, first to disable the background health, 907 // repair, and stuck loops from running, then update it to bypass the worker 908 // pool length check in managedBuildUnfinishedChunks 909 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 910 if err != nil { 911 t.Fatal(err) 912 } 913 914 // create file with no stuck chunks 915 rsc, _ := siafile.NewRSCode(1, 1) 916 up := modules.FileUploadParams{ 917 Source: "", 918 SiaPath: modules.RandomSiaPath(), 919 ErasureCode: rsc, 920 } 921 f, err := rt.renter.staticFileSet.NewSiaFile(up, crypto.GenerateSiaKey(crypto.RandomCipherType()), 100, 0777) 922 if err != nil { 923 t.Fatal(err) 924 } 925 926 // Create maps for method inputs 927 hosts := make(map[string]struct{}) 928 offline := make(map[string]bool) 929 goodForRenew := make(map[string]bool) 930 931 // Manually add workers to worker pool 932 for i := 0; i < int(f.NumChunks()); i++ { 933 rt.renter.staticWorkerPool.workers[string(i)] = &worker{ 934 killChan: make(chan struct{}), 935 wakeChan: make(chan struct{}, 1), 936 } 937 } 938 939 // call managedAddStuckChunksToHeap, no chunks should be added 940 err = rt.renter.managedAddStuckChunksToHeap(up.SiaPath, hosts, offline, goodForRenew) 941 if err != errNoStuckChunks { 942 t.Fatal(err) 943 } 944 if rt.renter.uploadHeap.managedLen() != 0 { 945 t.Fatal("Expected uploadHeap to be of length 0 got", rt.renter.uploadHeap.managedLen()) 946 } 947 948 // make chunk stuck 949 if err = f.SetStuck(uint64(0), true); err != nil { 950 t.Fatal(err) 951 } 952 953 // call managedAddStuckChunksToHeap, chunk should be added to heap 954 err = rt.renter.managedAddStuckChunksToHeap(up.SiaPath, hosts, offline, goodForRenew) 955 if err != nil { 956 t.Fatal(err) 957 } 958 if rt.renter.uploadHeap.managedLen() != 1 { 959 t.Fatal("Expected uploadHeap to be of length 1 got", rt.renter.uploadHeap.managedLen()) 960 } 961 }