gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/healthloop_test.go (about) 1 package renter 2 3 // healthloop_test.go contains unit tests for the health loop code. 4 5 import ( 6 "os" 7 "path/filepath" 8 "testing" 9 "time" 10 11 "gitlab.com/SkynetLabs/skyd/build" 12 "gitlab.com/SkynetLabs/skyd/persist" 13 ) 14 15 // TestSystemScanDurationEstimator checks that the logic for computing the 16 // estimated system scan duration is working correctly. 17 func TestSystemScanDurationEstimator(t *testing.T) { 18 // Base case, check what happens when computing an estimate on an empty dir 19 // finder. 20 r := new(Renter) 21 dirFinder := r.newHealthLoopDirFinder() 22 dirFinder.totalFiles = 100 23 dirFinder.updateEstimatedSystemScanDuration() 24 if dirFinder.estimatedSystemScanDuration != 0 { 25 t.Error("bad") 26 } 27 28 // Set some window variables, demonstrate processing at about 1 file per 29 // second. 30 dirFinder.windowFilesProcessed = 10 31 dirFinder.windowStartTime = time.Now().Add(-10 * time.Second) 32 dirFinder.windowSleepTime = 0 33 dirFinder.updateEstimatedSystemScanDuration() 34 if dirFinder.estimatedSystemScanDuration > time.Second*101 || dirFinder.estimatedSystemScanDuration < time.Second*99 { 35 t.Error("bad", dirFinder.estimatedSystemScanDuration) 36 } 37 // Try again with the same values, the EMA should not be off target if we 38 // are moving at the same speed. 39 dirFinder.windowFilesProcessed = 10 40 dirFinder.windowStartTime = time.Now().Add(-10 * time.Second) 41 dirFinder.windowSleepTime = 0 42 dirFinder.updateEstimatedSystemScanDuration() 43 if dirFinder.estimatedSystemScanDuration > time.Second*101 || dirFinder.estimatedSystemScanDuration < time.Second*99 { 44 t.Error("bad", dirFinder.estimatedSystemScanDuration) 45 } 46 // Try again, but this time with an average sleep of 1 second per file. So 47 // the processing is going at 1 second per file, and the sleep is going at 1 48 // second per file, meaning that the EMA should still result in the exact 49 // same value. 50 dirFinder.windowFilesProcessed = 10 51 dirFinder.windowStartTime = time.Now().Add(-20 * time.Second) 52 dirFinder.windowSleepTime = 10 * time.Second 53 dirFinder.updateEstimatedSystemScanDuration() 54 if dirFinder.estimatedSystemScanDuration > time.Second*101 || dirFinder.estimatedSystemScanDuration < time.Second*99 { 55 t.Error("bad", dirFinder.estimatedSystemScanDuration) 56 } 57 // Try again, but this time double the total number of files, this should 58 // cause the total estimate to increase. 59 dirFinder.totalFiles = 200 60 dirFinder.windowFilesProcessed = 10 61 dirFinder.windowStartTime = time.Now().Add(-20 * time.Second) 62 dirFinder.windowSleepTime = 10 * time.Second 63 dirFinder.updateEstimatedSystemScanDuration() 64 if dirFinder.estimatedSystemScanDuration > time.Second*135 || dirFinder.estimatedSystemScanDuration < time.Second*125 { 65 t.Error("bad", dirFinder.estimatedSystemScanDuration) 66 } 67 // Try again, but this time we are going faster per file, this should 68 // improve the estimated total time by a bit. 69 dirFinder.windowFilesProcessed = 20 70 dirFinder.windowStartTime = time.Now().Add(-20 * time.Second) 71 dirFinder.windowSleepTime = 10 * time.Second 72 dirFinder.updateEstimatedSystemScanDuration() 73 if dirFinder.estimatedSystemScanDuration > time.Second*125 || dirFinder.estimatedSystemScanDuration < time.Second*115 { 74 t.Error("bad", dirFinder.estimatedSystemScanDuration) 75 } 76 // Update a few times in a loop, the result should converge closely to 77 // double the original speed. 78 for i := 0; i < 100; i++ { 79 dirFinder.windowFilesProcessed = 20 80 dirFinder.windowStartTime = time.Now().Add(-20 * time.Second) 81 dirFinder.windowSleepTime = 10 * time.Second 82 dirFinder.updateEstimatedSystemScanDuration() 83 } 84 if dirFinder.estimatedSystemScanDuration > time.Second*105 || dirFinder.estimatedSystemScanDuration < time.Second*95 { 85 t.Error("bad", dirFinder.estimatedSystemScanDuration) 86 } 87 // Introduce going a lot slower, the result should slow the estimated time. 88 dirFinder.windowFilesProcessed = 5 89 dirFinder.windowStartTime = time.Now().Add(-20 * time.Second) 90 dirFinder.windowSleepTime = 10 * time.Second 91 dirFinder.updateEstimatedSystemScanDuration() 92 if dirFinder.estimatedSystemScanDuration > time.Second*135 || dirFinder.estimatedSystemScanDuration < time.Second*125 { 93 t.Error("bad", dirFinder.estimatedSystemScanDuration) 94 } 95 // Update a few times in a loop, the result should converge closely to half 96 // the original speed. 97 for i := 0; i < 100; i++ { 98 dirFinder.windowFilesProcessed = 5 99 dirFinder.windowStartTime = time.Now().Add(-20 * time.Second) 100 dirFinder.windowSleepTime = 10 * time.Second 101 dirFinder.updateEstimatedSystemScanDuration() 102 } 103 if dirFinder.estimatedSystemScanDuration > time.Second*410 || dirFinder.estimatedSystemScanDuration < time.Second*395 { 104 t.Error("bad", dirFinder.estimatedSystemScanDuration) 105 } 106 } 107 108 // TestDirFinderSleepDuration tests the logic that determines how long the dir 109 // finder should be asleep. 110 func TestDirFinderSleepDuration(t *testing.T) { 111 // Need to skip on the short testing because in order for this to work we 112 // need to add a logger to the renter owned by the dirFinder. 113 if testing.Short() { 114 t.SkipNow() 115 } 116 117 testdir := build.TempDir("renter", "TestDirFinderSleepDuration") 118 err := os.MkdirAll(testdir, 0700) 119 if err != nil { 120 t.Fatal(err) 121 } 122 dirFinder := new(healthLoopDirFinder) 123 dirFinder.renter = new(Renter) 124 dirFinder.renter.staticLog, err = persist.NewFileLogger(filepath.Join(testdir, logFile)) 125 if err != nil { 126 t.Fatal(err) 127 } 128 129 // First check, the sleep duration should be the empty filesystem sleep 130 // duration if there are no files in the filesystem. 131 sleepDuration := dirFinder.sleepDurationBeforeNextDir() 132 if sleepDuration != emptyFilesystemSleepDuration { 133 t.Error("bad") 134 } 135 136 // Standard check - set the number of files to 3, which means there should 137 // be a full second of sleep between each file. 138 dirFinder.totalFiles = 3 139 dirFinder.filesInNextDir = 1 140 dirFinder.leastRecentCheck = time.Now().Add(-1 * TargetHealthCheckFrequency / 2) 141 sleepDuration = dirFinder.sleepDurationBeforeNextDir() 142 baseExpectedTime := TargetHealthCheckFrequency / 3 143 if sleepDuration < baseExpectedTime-(time.Millisecond*5) || sleepDuration > baseExpectedTime+(time.Millisecond*5) { 144 t.Error("bad", sleepDuration) 145 } 146 dirFinder.filesInNextDir = 2 147 sleepDuration = dirFinder.sleepDurationBeforeNextDir() 148 if sleepDuration < 2*baseExpectedTime-(time.Millisecond*5) || sleepDuration > 2*baseExpectedTime+(time.Millisecond*5) { 149 t.Error("bad", sleepDuration) 150 } 151 dirFinder.filesInNextDir = 3 152 sleepDuration = dirFinder.sleepDurationBeforeNextDir() 153 if sleepDuration < 3*baseExpectedTime-(time.Millisecond*5) || sleepDuration > 3*baseExpectedTime+(time.Millisecond*5) { 154 t.Error("bad", sleepDuration) 155 } 156 157 // Check that the compression is working as desired. 158 dirFinder.filesInNextDir = 2 159 halfwayToUrgent := TargetHealthCheckFrequency + (urgentHealthCheckFrequency-TargetHealthCheckFrequency)/2 160 dirFinder.leastRecentCheck = time.Now().Add(-1 * halfwayToUrgent) 161 sleepDuration = dirFinder.sleepDurationBeforeNextDir() 162 if sleepDuration < baseExpectedTime-(time.Millisecond*5) || sleepDuration > baseExpectedTime+(time.Millisecond*5) { 163 t.Error("bad", sleepDuration) 164 } 165 166 // Check that a manual check being active results in no sleep. 167 dirFinder.manualCheckTime = time.Now().Add(baseExpectedTime) 168 sleepDuration = dirFinder.sleepDurationBeforeNextDir() 169 if sleepDuration != 0 { 170 t.Error("bad", sleepDuration) 171 } 172 dirFinder.manualCheckTime = time.Now().Add(-1 * time.Minute) 173 174 // Check that a slow scan time results in no sleep. 175 dirFinder.estimatedSystemScanDuration = urgentHealthCheckFrequency 176 sleepDuration = dirFinder.sleepDurationBeforeNextDir() 177 if sleepDuration != 0 { 178 t.Error("bad", sleepDuration) 179 } 180 dirFinder.estimatedSystemScanDuration = 0 181 182 // Check that being far behind results in no sleep. 183 dirFinder.leastRecentCheck = time.Now().Add(-1 * 2 * urgentHealthCheckFrequency) 184 sleepDuration = dirFinder.sleepDurationBeforeNextDir() 185 if sleepDuration != 0 { 186 t.Error("bad", sleepDuration) 187 } 188 dirFinder.leastRecentCheck = time.Now() 189 } 190 191 /* TODO: bring back in some form 192 // TestOldestHealthCheckTime probes managedOldestHealthCheckTime to verify that 193 // the directory with the oldest LastHealthCheckTime is returned 194 func TestOldestHealthCheckTime(t *testing.T) { 195 if testing.Short() { 196 t.SkipNow() 197 } 198 t.Parallel() 199 200 // Create test renter 201 rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{}) 202 if err != nil { 203 t.Fatal(err) 204 } 205 defer func() { 206 if err := rt.Close(); err != nil { 207 t.Fatal(err) 208 } 209 }() 210 211 // Create a test directory with sub folders 212 // 213 // root/ 1 214 // root/SubDir1/ 215 // root/SubDir1/SubDir1/ 216 // root/SubDir1/SubDir2/ 217 // root/SubDir2/ 218 // root/SubDir3/ 219 directories := []string{ 220 "root", 221 "root/SubDir1", 222 "root/SubDir1/SubDir1", 223 "root/SubDir1/SubDir2", 224 "root/SubDir2", 225 "root/SubDir3", 226 } 227 228 // Create directory tree with consistent metadata 229 now := time.Now() 230 nowMD := siadir.Metadata{ 231 Health: 1, 232 StuckHealth: 0, 233 AggregateLastHealthCheckTime: now, 234 LastHealthCheckTime: now, 235 } 236 for _, dir := range directories { 237 // Create Directory 238 dirSiaPath := newSiaPath(dir) 239 if err := rt.renter.CreateDir(dirSiaPath, skymodules.DefaultDirPerm); err != nil { 240 t.Fatal(err) 241 } 242 err = rt.openAndUpdateDir(dirSiaPath, nowMD) 243 if err != nil { 244 t.Fatal(err) 245 } 246 // Put two files in the directory 247 for i := 0; i < 2; i++ { 248 fileSiaPath, err := dirSiaPath.Join(persist.RandomSuffix()) 249 if err != nil { 250 t.Fatal(err) 251 } 252 sf, err := rt.renter.createRenterTestFile(fileSiaPath) 253 if err != nil { 254 t.Fatal(err) 255 } 256 sf.SetLastHealthCheckTime() 257 err = errors.Compose(sf.SaveMetadata(), sf.Close()) 258 if err != nil { 259 t.Fatal(err) 260 } 261 } 262 } 263 264 // Update all common directories to same health check time. 265 err1 := rt.openAndUpdateDir(skymodules.RootSiaPath(), nowMD) 266 err2 := rt.openAndUpdateDir(skymodules.BackupFolder, nowMD) 267 err3 := rt.openAndUpdateDir(skymodules.HomeFolder, nowMD) 268 err4 := rt.openAndUpdateDir(skymodules.SkynetFolder, nowMD) 269 err5 := rt.openAndUpdateDir(skymodules.UserFolder, nowMD) 270 err6 := rt.openAndUpdateDir(skymodules.VarFolder, nowMD) 271 err = errors.Compose(err1, err2, err3, err4, err5, err6) 272 if err != nil { 273 t.Fatal(err) 274 } 275 276 // Set the LastHealthCheckTime of SubDir1/SubDir2 to be the oldest 277 oldestCheckTime := now.AddDate(0, 0, -1) 278 oldestHealthCheckUpdate := siadir.Metadata{ 279 Health: 1, 280 StuckHealth: 0, 281 AggregateLastHealthCheckTime: oldestCheckTime, 282 LastHealthCheckTime: oldestCheckTime, 283 } 284 subDir1_2 := newSiaPath("root/SubDir1/SubDir2") 285 if err := rt.openAndUpdateDir(subDir1_2, oldestHealthCheckUpdate); err != nil { 286 t.Fatal(err) 287 } 288 289 // Bubble the health of SubDir1 so that the oldest LastHealthCheckTime of 290 // SubDir1/SubDir2 gets bubbled up 291 subDir1 := newSiaPath("root/SubDir1") 292 if err := rt.bubbleBlocking(subDir1); err != nil { 293 t.Fatal(err) 294 } 295 err = build.Retry(60, time.Second, func() error { 296 // Find the oldest directory, even though SubDir1/SubDir2 is the oldest, 297 // SubDir1 should be returned since it is the lowest level directory tree 298 // containing the Oldest LastHealthCheckTime 299 dir, lastCheck, err := rt.renter.managedOldestHealthCheckTime() 300 if err != nil { 301 return err 302 } 303 if !dir.Equals(subDir1) { 304 return fmt.Errorf("Expected to find %v but found %v", subDir1.String(), dir.String()) 305 } 306 if !lastCheck.Equal(oldestCheckTime) { 307 return fmt.Errorf("Expected to find time of %v but found %v", oldestCheckTime, lastCheck) 308 } 309 return nil 310 }) 311 if err != nil { 312 t.Fatal(err) 313 } 314 315 // Now add more files to SubDir1, this will force the return of SubDir1_2 316 // since the subtree including SubDir1 now has too many files 317 for i := uint64(0); i < healthLoopNumBatchFiles; i++ { 318 fileSiaPath, err := subDir1.Join(persist.RandomSuffix()) 319 if err != nil { 320 t.Fatal(err) 321 } 322 sf, err := rt.renter.createRenterTestFile(fileSiaPath) 323 if err != nil { 324 t.Fatal(err) 325 } 326 sf.SetLastHealthCheckTime() 327 err = errors.Compose(sf.SaveMetadata(), sf.Close()) 328 if err != nil { 329 t.Fatal(err) 330 } 331 } 332 if err := rt.bubbleBlocking(subDir1); err != nil { 333 t.Fatal(err) 334 } 335 err = build.Retry(60, time.Second, func() error { 336 // Find the oldest directory, should be SubDir1_2 now 337 dir, lastCheck, err := rt.renter.managedOldestHealthCheckTime() 338 if err != nil { 339 return err 340 } 341 if !dir.Equals(subDir1_2) { 342 return fmt.Errorf("Expected to find %v but found %v", subDir1_2.String(), dir.String()) 343 } 344 if !lastCheck.Equal(oldestCheckTime) { 345 return fmt.Errorf("Expected to find time of %v but found %v", oldestCheckTime, lastCheck) 346 } 347 return nil 348 }) 349 if err != nil { 350 t.Fatal(err) 351 } 352 353 // Now update the root directory to have an older AggregateLastHealthCheckTime 354 // than the sub directory but a more recent LastHealthCheckTime. This will 355 // simulate a shutdown before all the pending bubbles could finish. 356 entry, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath()) 357 if err != nil { 358 t.Fatal(err) 359 } 360 rootTime := now.AddDate(0, 0, -3) 361 err = entry.UpdateLastHealthCheckTime(rootTime, now) 362 if err != nil { 363 t.Fatal(err) 364 } 365 err = entry.Close() 366 if err != nil { 367 t.Fatal(err) 368 } 369 370 // A call to managedOldestHealthCheckTime should still return the same 371 // subDir1_2 372 dir, lastCheck, err := rt.renter.managedOldestHealthCheckTime() 373 if err != nil { 374 t.Fatal(err) 375 } 376 if !dir.Equals(subDir1_2) { 377 t.Error(fmt.Errorf("Expected to find %v but found %v", subDir1_2.String(), dir.String())) 378 } 379 if !lastCheck.Equal(oldestCheckTime) { 380 t.Error(fmt.Errorf("Expected to find time of %v but found %v", oldestCheckTime, lastCheck)) 381 } 382 } 383 */