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  }