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  */