gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/directoryheap_test.go (about) 1 package renter 2 3 import ( 4 "os" 5 "reflect" 6 "testing" 7 8 "gitlab.com/NebulousLabs/errors" 9 "gitlab.com/NebulousLabs/fastrand" 10 "gitlab.com/SkynetLabs/skyd/siatest/dependencies" 11 "gitlab.com/SkynetLabs/skyd/skymodules" 12 ) 13 14 // updateSiaDirHealth is a helper method to update the health and the aggregate 15 // health of a siadir 16 func (r *Renter) updateSiaDirHealth(siaPath skymodules.SiaPath, health, aggregateHealth float64) (err error) { 17 siaDir, err := r.staticFileSystem.OpenSiaDir(siaPath) 18 if err != nil { 19 return err 20 } 21 defer func() { 22 err = errors.Compose(err, siaDir.Close()) 23 }() 24 metadata, err := siaDir.Metadata() 25 if err != nil { 26 return err 27 } 28 metadata.Health = health 29 metadata.AggregateHealth = aggregateHealth 30 err = siaDir.UpdateMetadata(metadata) 31 if err != nil { 32 return err 33 } 34 return nil 35 } 36 37 // addDirectoriesToHeap is a helper function for adding directories to the 38 // Renter's directory heap 39 func addDirectoriesToHeap(r *Renter, numDirs int, explored, remote bool) { 40 // If the directory is remote, the remote health should be worse than the 41 // non remote healths to ensure the remote prioritization overrides the 42 // health comparison 43 // 44 // If the directory is explored, the aggregateHealth should be worse to 45 // ensure that the directories health is used and prioritized 46 47 for i := 0; i < numDirs; i++ { 48 // Initialize values 49 var remoteHealth, aggregateRemoteHealth float64 50 health := float64(fastrand.Intn(100)) + 0.25 51 aggregateHealth := float64(fastrand.Intn(100)) + 0.25 52 53 // If remote then set the remote healths to be non zero and make sure 54 // that the coresponding healths are worse 55 if remote { 56 remoteHealth = float64(fastrand.Intn(100)) + 0.25 57 aggregateRemoteHealth = float64(fastrand.Intn(100)) + 0.25 58 health = remoteHealth + 1 59 aggregateHealth = aggregateRemoteHealth + 1 60 } 61 62 // If explored, set the aggregate values to be half the RepairThreshold 63 // higher than the non aggregate values. Using half the RepairThreshold 64 // so that non remote directories are still considered non remote. 65 if explored { 66 aggregateHealth = health + skymodules.RepairThreshold/2 67 aggregateRemoteHealth = remoteHealth + skymodules.RepairThreshold/2 68 } 69 70 // Create the directory and push it on to the heap 71 d := &directory{ 72 aggregateHealth: aggregateHealth, 73 aggregateRemoteHealth: aggregateRemoteHealth, 74 explored: explored, 75 health: health, 76 remoteHealth: remoteHealth, 77 staticSiaPath: skymodules.RandomSiaPath(), 78 } 79 r.staticDirectoryHeap.managedPush(d) 80 } 81 } 82 83 // TestDirectoryHeap probes the directory heap implementation 84 func TestDirectoryHeap(t *testing.T) { 85 if testing.Short() { 86 t.SkipNow() 87 } 88 t.Parallel() 89 90 // Create renter 91 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 92 if err != nil { 93 t.Fatal(err) 94 } 95 defer func() { 96 if err := rt.Close(); err != nil { 97 t.Fatal(err) 98 } 99 }() 100 101 // Check that the heap was initialized properly 102 if rt.renter.staticDirectoryHeap.managedLen() != 0 { 103 t.Fatal("directory heap should have length of 0 but has length of", rt.renter.staticDirectoryHeap.managedLen()) 104 } 105 106 // Add directories of each type to the heap 107 addDirectoriesToHeap(rt.renter, 1, true, true) 108 addDirectoriesToHeap(rt.renter, 1, true, false) 109 addDirectoriesToHeap(rt.renter, 1, false, true) 110 addDirectoriesToHeap(rt.renter, 1, false, false) 111 112 // Confirm all elements added. 113 if rt.renter.staticDirectoryHeap.managedLen() != 4 { 114 t.Fatalf("heap should have length of %v but was %v", 4, rt.renter.staticDirectoryHeap.managedLen()) 115 } 116 117 // Check that the heapHealth is remote 118 _, remote := rt.renter.staticDirectoryHeap.managedPeekHealth() 119 if !remote { 120 t.Error("Heap should have a remote health at the top") 121 } 122 123 // Pop the directories and validate their position 124 d1 := rt.renter.staticDirectoryHeap.managedPop() 125 d2 := rt.renter.staticDirectoryHeap.managedPop() 126 d1Health, d1Remote := d1.managedHeapHealth() 127 d2Health, d2Remote := d2.managedHeapHealth() 128 129 // Both Directories should be remote 130 if !d1Remote || !d2Remote { 131 t.Errorf("Expected both directories to be remote but got %v and %v", d1Remote, d2Remote) 132 } 133 // The top directory should have the worst health 134 if d1Health < d2Health { 135 t.Errorf("Expected top directory to have worse health but got %v >= %v", d1Health, d2Health) 136 } 137 138 d3 := rt.renter.staticDirectoryHeap.managedPop() 139 d4 := rt.renter.staticDirectoryHeap.managedPop() 140 d3Health, d3Remote := d3.managedHeapHealth() 141 d4Health, d4Remote := d4.managedHeapHealth() 142 // Both Directories should not be remote 143 if d3Remote || d4Remote { 144 t.Errorf("Expected both directories to not be remote but got %v and %v", d3Remote, d4Remote) 145 } 146 // The top directory should have the worst health 147 if d3Health < d4Health { 148 t.Errorf("Expected top directory to have worse health but got %v >= %v", d3Health, d4Health) 149 } 150 151 // Push directories part on to the heap 152 rt.renter.staticDirectoryHeap.managedPush(d1) 153 rt.renter.staticDirectoryHeap.managedPush(d2) 154 rt.renter.staticDirectoryHeap.managedPush(d3) 155 rt.renter.staticDirectoryHeap.managedPush(d4) 156 157 // Modifying d4 and re-push it to update it's position in the heap 158 d4.explored = true 159 d4.aggregateHealth = 0 160 d4.aggregateRemoteHealth = d1Health + 1 161 d4.health = 0 162 d4.remoteHealth = 0 163 rt.renter.staticDirectoryHeap.managedPush(d4) 164 165 // Now, even though d4 has a worse aggregate remote health than d1's 166 // heapHealth, it should not be on the top of the heap because it is 167 // explored and therefore its heapHealth will be using the non aggregate 168 // fields 169 d := rt.renter.staticDirectoryHeap.managedPop() 170 if reflect.DeepEqual(d, d4) { 171 t.Log(d) 172 t.Log(d4) 173 t.Error("Expected top directory to not be directory 4") 174 } 175 if !reflect.DeepEqual(d, d1) { 176 t.Log(d) 177 t.Log(d1) 178 t.Error("Expected top directory to still be directory 1") 179 } 180 181 // Push top directory back onto heap 182 rt.renter.staticDirectoryHeap.managedPush(d) 183 184 // No set d4 to not be explored, this should be enough to force it to the 185 // top of the heap 186 d4.explored = false 187 rt.renter.staticDirectoryHeap.managedPush(d4) 188 189 // Check that top directory is directory 4 190 d = rt.renter.staticDirectoryHeap.managedPop() 191 if !reflect.DeepEqual(d, d4) { 192 t.Log(d) 193 t.Log(d4) 194 t.Error("Expected top directory to be directory 4") 195 } 196 197 // Reset Directory heap 198 rt.renter.staticDirectoryHeap.managedReset() 199 200 // Confirm that the heap is empty 201 if rt.renter.staticDirectoryHeap.managedLen() != 0 { 202 t.Fatal("heap should empty but has length of", rt.renter.staticDirectoryHeap.managedLen()) 203 } 204 205 // Test pushing an unexplored directory 206 err = rt.renter.managedPushUnexploredDirectory(skymodules.RootSiaPath()) 207 if err != nil { 208 t.Fatal(err) 209 } 210 if rt.renter.staticDirectoryHeap.managedLen() != 1 { 211 t.Fatal("directory heap should have length of 1 but has length of", rt.renter.staticDirectoryHeap.managedLen()) 212 } 213 d = rt.renter.staticDirectoryHeap.managedPop() 214 if d.explored { 215 t.Fatal("directory should be unexplored root") 216 } 217 if !d.staticSiaPath.Equals(skymodules.RootSiaPath()) { 218 t.Fatal("Directory should be root directory but is", d.staticSiaPath) 219 } 220 221 // Make sure pushing an unexplored dir that doesn't exist works. 222 randomSP, err := skymodules.RootSiaPath().Join(skymodules.RandomSiaPath().String()) 223 if err != nil { 224 t.Fatal(err) 225 } 226 err = rt.renter.managedPushUnexploredDirectory(randomSP) 227 if err != nil { 228 t.Fatal(err) 229 } 230 // Try again but this time just remove the .siadir file. 231 err = os.Remove(randomSP.SiaDirMetadataSysPath(rt.renter.staticFileSystem.Root())) 232 if err != nil { 233 t.Fatal(err) 234 } 235 err = rt.renter.managedPushUnexploredDirectory(randomSP) 236 if err != nil { 237 t.Fatal(err) 238 } 239 } 240 241 // TestPushSubDirectories probes the methods that add sub directories to the 242 // heap. This in turn tests the methods for adding unexplored directories 243 func TestPushSubDirectories(t *testing.T) { 244 if testing.Short() { 245 t.SkipNow() 246 } 247 t.Parallel() 248 249 // Create renter 250 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 251 if err != nil { 252 t.Fatal(err) 253 } 254 defer func() { 255 if err := rt.Close(); err != nil { 256 t.Fatal(err) 257 } 258 }() 259 260 // Create a test directory with the following healths 261 // 262 // / 1 263 // /SubDir1/ 2 264 // /SubDir2/ 3 265 266 // Create directory tree 267 siaPath1, err := skymodules.NewSiaPath("SubDir1") 268 if err != nil { 269 t.Fatal(err) 270 } 271 siaPath2, err := skymodules.NewSiaPath("SubDir2") 272 if err != nil { 273 t.Fatal(err) 274 } 275 if err := rt.renter.CreateDir(siaPath1, skymodules.DefaultDirPerm); err != nil { 276 t.Fatal(err) 277 } 278 if err := rt.renter.CreateDir(siaPath2, skymodules.DefaultDirPerm); err != nil { 279 t.Fatal(err) 280 } 281 282 // Update metadata 283 err = rt.renter.updateSiaDirHealth(siaPath1, float64(2), float64(2)) 284 if err != nil { 285 t.Fatal(err) 286 } 287 288 err = rt.renter.updateSiaDirHealth(siaPath2, float64(3), float64(3)) 289 if err != nil { 290 t.Fatal(err) 291 } 292 293 // Make sure we are starting with an empty heap 294 rt.renter.staticDirectoryHeap.managedReset() 295 296 // Add siafiles sub directories 297 d := &directory{ 298 staticSiaPath: skymodules.RootSiaPath(), 299 } 300 err = rt.renter.managedPushSubDirectories(d) 301 if err != nil { 302 t.Fatal(err) 303 } 304 305 // Heap should have a length of 5 306 if rt.renter.staticDirectoryHeap.managedLen() != 5 { 307 t.Fatal("Heap should have length of 5 but was", rt.renter.staticDirectoryHeap.managedLen()) 308 } 309 310 // Pop off elements and confirm the are correct 311 d = rt.renter.staticDirectoryHeap.managedPop() 312 if !d.staticSiaPath.Equals(siaPath2) { 313 t.Fatalf("Expected directory %v but found %v", siaPath2.String(), d.staticSiaPath.String()) 314 } 315 if d.aggregateHealth != float64(3) { 316 t.Fatal("Expected AggregateHealth to be 3 but was", d.aggregateHealth) 317 } 318 if d.health != float64(3) { 319 t.Fatal("Expected Health to be 3 but was", d.health) 320 } 321 if d.explored { 322 t.Fatal("Expected directory to be unexplored") 323 } 324 d = rt.renter.staticDirectoryHeap.managedPop() 325 if !d.staticSiaPath.Equals(siaPath1) { 326 t.Fatalf("Expected directory %v but found %v", siaPath1.String(), d.staticSiaPath.String()) 327 } 328 if d.aggregateHealth != float64(2) { 329 t.Fatal("Expected AggregateHealth to be 2 but was", d.aggregateHealth) 330 } 331 if d.health != float64(2) { 332 t.Fatal("Expected Health to be 2 but was", d.health) 333 } 334 if d.explored { 335 t.Fatal("Expected directory to be unexplored") 336 } 337 } 338 339 // TestNextExploredDirectory probes managedNextExploredDirectory to ensure that 340 // the directory traverses the filesystem as expected 341 func TestNextExploredDirectory(t *testing.T) { 342 if testing.Short() { 343 t.SkipNow() 344 } 345 t.Parallel() 346 347 // Create renter 348 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 349 if err != nil { 350 t.Fatal(err) 351 } 352 defer func() { 353 if err := rt.Close(); err != nil { 354 t.Fatal(err) 355 } 356 }() 357 358 // Create a test directory with the following healths/aggregateHealths 359 // 360 // root/ 0/3 361 // root/SubDir1/ 1/2 362 // root/SubDir1/SubDir1/ 1/1 363 // root/SubDir1/SubDir2/ 2/2 364 // root/SubDir2/ 1/3 365 // root/SubDir2/SubDir1/ 1/1 366 // root/SubDir2/SubDir2/ 3/3 367 // 368 // Overall we would expect to see root/SubDir2/SubDir2 popped first followed 369 // by root/SubDir1/SubDir2 370 371 // Create SiaPaths 372 siaPath1, err := skymodules.NewSiaPath("SubDir1") 373 if err != nil { 374 t.Fatal(err) 375 } 376 siaPath2, err := skymodules.NewSiaPath("SubDir2") 377 if err != nil { 378 t.Fatal(err) 379 } 380 siaPath1_1, err := siaPath1.Join("SubDir1") 381 if err != nil { 382 t.Fatal(err) 383 } 384 siaPath1_2, err := siaPath1.Join("SubDir2") 385 if err != nil { 386 t.Fatal(err) 387 } 388 siaPath2_1, err := siaPath2.Join("SubDir1") 389 if err != nil { 390 t.Fatal(err) 391 } 392 siaPath2_2, err := siaPath2.Join("SubDir2") 393 if err != nil { 394 t.Fatal(err) 395 } 396 // Create Directories 397 if err := rt.renter.CreateDir(siaPath1_1, skymodules.DefaultDirPerm); err != nil { 398 t.Fatal(err) 399 } 400 if err := rt.renter.CreateDir(siaPath1_2, skymodules.DefaultDirPerm); err != nil { 401 t.Fatal(err) 402 } 403 if err := rt.renter.CreateDir(siaPath2_1, skymodules.DefaultDirPerm); err != nil { 404 t.Fatal(err) 405 } 406 if err := rt.renter.CreateDir(siaPath2_2, skymodules.DefaultDirPerm); err != nil { 407 t.Fatal(err) 408 } 409 410 // Update metadata 411 err = rt.renter.updateSiaDirHealth(skymodules.RootSiaPath(), float64(0), float64(3)) 412 if err != nil { 413 t.Fatal(err) 414 } 415 err = rt.renter.updateSiaDirHealth(siaPath1, float64(1), float64(2)) 416 if err != nil { 417 t.Fatal(err) 418 } 419 err = rt.renter.updateSiaDirHealth(siaPath1_1, float64(1), float64(1)) 420 if err != nil { 421 t.Fatal(err) 422 } 423 err = rt.renter.updateSiaDirHealth(siaPath1_2, float64(2), float64(2)) 424 if err != nil { 425 t.Fatal(err) 426 } 427 err = rt.renter.updateSiaDirHealth(siaPath2, float64(1), float64(3)) 428 if err != nil { 429 t.Fatal(err) 430 } 431 err = rt.renter.updateSiaDirHealth(siaPath2_1, float64(1), float64(1)) 432 if err != nil { 433 t.Fatal(err) 434 } 435 err = rt.renter.updateSiaDirHealth(siaPath2_2, float64(3), float64(3)) 436 if err != nil { 437 t.Fatal(err) 438 } 439 440 // Make sure we are starting with an empty heap, this helps with ndfs and 441 // tests proper handling of empty heaps 442 rt.renter.staticDirectoryHeap.managedReset() 443 err = rt.renter.managedPushUnexploredDirectory(skymodules.RootSiaPath()) 444 if err != nil { 445 t.Fatal(err) 446 } 447 448 // Pop off next explored directory 449 d, err := rt.renter.managedNextExploredDirectory() 450 if err != nil { 451 t.Fatal(err) 452 } 453 if d == nil { 454 t.Fatal("No directory popped off heap") 455 } 456 457 // Directory should be root/home/siafiles/SubDir2/SubDir2 458 if !d.staticSiaPath.Equals(siaPath2_2) { 459 t.Fatalf("Expected directory %v but found %v", siaPath2_2.String(), d.staticSiaPath.String()) 460 } 461 if d.aggregateHealth != float64(3) { 462 t.Fatal("Expected AggregateHealth to be 3 but was", d.aggregateHealth) 463 } 464 if d.health != float64(3) { 465 t.Fatal("Expected Health to be 3 but was", d.health) 466 } 467 if !d.explored { 468 t.Fatal("Expected directory to be explored") 469 } 470 471 // Pop off next explored directory 472 d, err = rt.renter.managedNextExploredDirectory() 473 if err != nil { 474 t.Fatal(err) 475 } 476 if d == nil { 477 t.Fatal("No directory popped off heap") 478 } 479 480 // Directory should be root/homes/siafiles/SubDir1/SubDir2 481 if !d.staticSiaPath.Equals(siaPath1_2) { 482 t.Fatalf("Expected directory %v but found %v", siaPath1_2.String(), d.staticSiaPath.String()) 483 } 484 if d.aggregateHealth != float64(2) { 485 t.Fatal("Expected AggregateHealth to be 2 but was", d.aggregateHealth) 486 } 487 if d.health != float64(2) { 488 t.Fatal("Expected Health to be 2 but was", d.health) 489 } 490 if !d.explored { 491 t.Fatal("Expected directory to be explored") 492 } 493 } 494 495 // TestDirectoryHeapHealth probes the directory managedHeapHealth method 496 func TestDirectoryHeapHealth(t *testing.T) { 497 // Initiate directory struct 498 aggregateRemoteHealth := float64(fastrand.Intn(100)) + 0.25 499 aggregateHealth := aggregateRemoteHealth + 1 500 remoteHealth := aggregateHealth + 1 501 health := remoteHealth + 1 502 d := directory{ 503 aggregateHealth: aggregateHealth, 504 aggregateRemoteHealth: aggregateRemoteHealth, 505 health: health, 506 remoteHealth: remoteHealth, 507 } 508 509 // Explored is false so aggregate values should return even though the 510 // directory health's are worse. Even though the aggregateHealth is worse 511 // the aggregateRemoteHealth should be returned as it is above the 512 // RepairThreshold 513 heapHealth, remote := d.managedHeapHealth() 514 if !remote { 515 t.Fatal("directory should be considered remote") 516 } 517 if heapHealth != d.aggregateRemoteHealth { 518 t.Errorf("Expected heapHealth to be %v but was %v", d.aggregateRemoteHealth, heapHealth) 519 } 520 521 // Setting the aggregateRemoteHealth to 0 should make the aggregateHealth 522 // value be returned 523 d.aggregateRemoteHealth = 0 524 heapHealth, remote = d.managedHeapHealth() 525 if remote { 526 t.Fatal("directory should not be considered remote") 527 } 528 if heapHealth != d.aggregateHealth { 529 t.Errorf("Expected heapHealth to be %v but was %v", d.aggregateHealth, heapHealth) 530 } 531 532 // Setting the explored value to true should recreate the above to checks 533 // but for the non aggregate values 534 d.explored = true 535 heapHealth, remote = d.managedHeapHealth() 536 if !remote { 537 t.Fatal("directory should be considered remote") 538 } 539 if heapHealth != d.remoteHealth { 540 t.Errorf("Expected heapHealth to be %v but was %v", d.remoteHealth, heapHealth) 541 } 542 d.remoteHealth = 0 543 heapHealth, remote = d.managedHeapHealth() 544 if remote { 545 t.Fatal("directory should not be considered remote") 546 } 547 if heapHealth != d.health { 548 t.Errorf("Expected heapHealth to be %v but was %v", d.health, heapHealth) 549 } 550 }