gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/dirs_test.go (about)

     1  package renter
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"gitlab.com/NebulousLabs/errors"
    14  	"gitlab.com/SkynetLabs/skyd/build"
    15  	"gitlab.com/SkynetLabs/skyd/siatest/dependencies"
    16  	"gitlab.com/SkynetLabs/skyd/skymodules"
    17  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem"
    18  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siadir"
    19  )
    20  
    21  // timeEquals is a helper function for checking if two times are equal
    22  //
    23  // Since we can't check timestamps for equality cause they are often set to
    24  // `time.Now()` by methods, we allow a timestamp to be off by a certain delta.
    25  func timeEquals(t1, t2 time.Time, delta time.Duration) bool {
    26  	if t1.After(t2) && t1.After(t2.Add(delta)) {
    27  		return false
    28  	}
    29  	if t2.After(t1) && t2.After(t1.Add(delta)) {
    30  		return false
    31  	}
    32  	return true
    33  }
    34  
    35  // equivalentMetadata checks whether the metadata of two directories is
    36  // equivalent. In this case that means most of the fields are expected to match,
    37  // but some fields like the timestamp are allowed to be off by a bit.
    38  func equivalentMetadata(md1, md2 siadir.Metadata, delta time.Duration) (err error) {
    39  	// Check all the time fields first
    40  	// Check AggregateLastHealthCheckTime
    41  	if !timeEquals(md1.AggregateLastHealthCheckTime, md2.AggregateLastHealthCheckTime, delta) {
    42  		err = errors.Compose(err, fmt.Errorf("AggregateLastHealthCheckTimes not equal %v and %v (%v)", md1.AggregateLastHealthCheckTime, md2.AggregateLastHealthCheckTime, delta))
    43  	}
    44  	// Check AggregateModTime
    45  	if !timeEquals(md2.AggregateModTime, md1.AggregateModTime, delta) {
    46  		err = errors.Compose(err, fmt.Errorf("AggregateModTime not equal %v and %v (%v)", md1.AggregateModTime, md2.AggregateModTime, delta))
    47  	}
    48  	// Check LastHealthCheckTime
    49  	if !timeEquals(md1.LastHealthCheckTime, md2.LastHealthCheckTime, delta) {
    50  		err = errors.Compose(err, fmt.Errorf("LastHealthCheckTimes not equal %v and %v (%v)", md1.LastHealthCheckTime, md2.LastHealthCheckTime, delta))
    51  	}
    52  	// Check ModTime
    53  	if !timeEquals(md2.ModTime, md1.ModTime, delta) {
    54  		err = errors.Compose(err, fmt.Errorf("ModTime not equal %v and %v (%v)", md1.ModTime, md2.ModTime, delta))
    55  	}
    56  
    57  	// Check a copy of md2 with the time fields of md1 and check for Equality
    58  	md2Copy := md2
    59  	md2Copy.AggregateLastHealthCheckTime = md1.AggregateLastHealthCheckTime
    60  	md2Copy.AggregateModTime = md1.AggregateModTime
    61  	md2Copy.LastHealthCheckTime = md1.LastHealthCheckTime
    62  	md2Copy.ModTime = md1.ModTime
    63  	return errors.Compose(err, siadir.EqualMetadatas(md1, md2Copy))
    64  }
    65  
    66  // FileListCollect returns information on all of the files stored by the
    67  // renter at the specified folder. The 'cached' argument specifies whether
    68  // cached values should be returned or not.
    69  func (r *Renter) FileListCollect(siaPath skymodules.SiaPath, recursive, cached bool) ([]skymodules.FileInfo, error) {
    70  	var files []skymodules.FileInfo
    71  	var mu sync.Mutex
    72  	err := r.FileList(siaPath, recursive, cached, func(fi skymodules.FileInfo) {
    73  		mu.Lock()
    74  		files = append(files, fi)
    75  		mu.Unlock()
    76  	})
    77  	// Sort slices by SiaPath.
    78  	sort.Slice(files, func(i, j int) bool {
    79  		return files[i].SiaPath.String() < files[j].SiaPath.String()
    80  	})
    81  	return files, err
    82  }
    83  
    84  // TestRenterCreateDirectories checks that the renter properly created metadata files
    85  // for direcotries
    86  func TestRenterCreateDirectories(t *testing.T) {
    87  	if testing.Short() {
    88  		t.SkipNow()
    89  	}
    90  	t.Parallel()
    91  
    92  	// Create renterTester
    93  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  	defer func() {
    98  		if err := rt.Close(); err != nil {
    99  			t.Fatal(err)
   100  		}
   101  	}()
   102  
   103  	// Test creating directory
   104  	siaPath, err := skymodules.NewSiaPath("foo/bar/baz")
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  	err = rt.renter.CreateDir(siaPath, skymodules.DefaultDirPerm)
   109  	if err != nil {
   110  		t.Fatal(err)
   111  	}
   112  
   113  	// Confirm that directory metadata files were created in all directories
   114  	for {
   115  		if err := rt.checkDirInitialized(siaPath); err != nil {
   116  			t.Logf("check for '%v'", siaPath)
   117  			t.Fatal(err)
   118  		}
   119  		if siaPath.IsRoot() {
   120  			break
   121  		}
   122  		siaPath, err = siaPath.Dir()
   123  		if err != nil {
   124  			t.Fatal(err)
   125  		}
   126  	}
   127  }
   128  
   129  // checkDirInitialized is a helper function that checks that the directory was
   130  // initialized correctly and the metadata file exist and contain the correct
   131  // information
   132  func (rt *renterTester) checkDirInitialized(siaPath skymodules.SiaPath) (err error) {
   133  	siaDir, err := rt.renter.staticFileSystem.OpenSiaDir(siaPath)
   134  	if err != nil {
   135  		return fmt.Errorf("unable to load directory %v metadata: %v", siaPath, err)
   136  	}
   137  	defer func() {
   138  		err = errors.Compose(err, siaDir.Close())
   139  	}()
   140  	fullpath := siaPath.SiaDirMetadataSysPath(rt.renter.staticFileSystem.Root())
   141  	if _, err := os.Stat(fullpath); err != nil {
   142  		return err
   143  	}
   144  
   145  	// Check that metadata is default value
   146  	metadata, err := siaDir.Metadata()
   147  	if err != nil {
   148  		return err
   149  	}
   150  	// Check Aggregate Fields
   151  	if metadata.AggregateHealth != siadir.DefaultDirHealth {
   152  		return fmt.Errorf("AggregateHealth not initialized properly: have %v expected %v", metadata.AggregateHealth, siadir.DefaultDirHealth)
   153  	}
   154  	if !metadata.AggregateLastHealthCheckTime.IsZero() {
   155  		return fmt.Errorf("AggregateLastHealthCheckTime should be a zero timestamp: %v", metadata.AggregateLastHealthCheckTime)
   156  	}
   157  	if metadata.AggregateModTime.IsZero() {
   158  		return fmt.Errorf("AggregateModTime not initialized: %v", metadata.AggregateModTime)
   159  	}
   160  	if metadata.AggregateMinRedundancy != siadir.DefaultDirRedundancy {
   161  		return fmt.Errorf("AggregateMinRedundancy not initialized properly: have %v expected %v", metadata.AggregateMinRedundancy, siadir.DefaultDirRedundancy)
   162  	}
   163  	if metadata.AggregateStuckHealth != siadir.DefaultDirHealth {
   164  		return fmt.Errorf("AggregateStuckHealth not initialized properly: have %v expected %v", metadata.AggregateStuckHealth, siadir.DefaultDirHealth)
   165  	}
   166  	// Check SiaDir Fields
   167  	if metadata.Health != siadir.DefaultDirHealth {
   168  		return fmt.Errorf("Health not initialized properly: have %v expected %v", metadata.Health, siadir.DefaultDirHealth)
   169  	}
   170  	if !metadata.LastHealthCheckTime.IsZero() {
   171  		return fmt.Errorf("LastHealthCheckTime should be a zero timestamp: %v", metadata.LastHealthCheckTime)
   172  	}
   173  	if metadata.ModTime.IsZero() {
   174  		return fmt.Errorf("ModTime not initialized: %v", metadata.ModTime)
   175  	}
   176  	if metadata.MinRedundancy != siadir.DefaultDirRedundancy {
   177  		return fmt.Errorf("MinRedundancy not initialized properly: have %v expected %v", metadata.MinRedundancy, siadir.DefaultDirRedundancy)
   178  	}
   179  	if metadata.StuckHealth != siadir.DefaultDirHealth {
   180  		return fmt.Errorf("StuckHealth not initialized properly: have %v expected %v", metadata.StuckHealth, siadir.DefaultDirHealth)
   181  	}
   182  	path, err := siaDir.Path()
   183  	if err != nil {
   184  		return err
   185  	}
   186  	if path != rt.renter.staticFileSystem.DirPath(siaPath) {
   187  		return fmt.Errorf("Expected path to be %v, got %v", path, rt.renter.staticFileSystem.DirPath(siaPath))
   188  	}
   189  	return nil
   190  }
   191  
   192  // TestDirInfo probes the DirInfo method
   193  func TestDirInfo(t *testing.T) {
   194  	if testing.Short() {
   195  		t.SkipNow()
   196  	}
   197  	t.Parallel()
   198  
   199  	// Create renterTester
   200  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   201  	if err != nil {
   202  		t.Fatal(err)
   203  	}
   204  	defer func() {
   205  		if err := rt.Close(); err != nil {
   206  			t.Fatal(err)
   207  		}
   208  	}()
   209  
   210  	// Create directory
   211  	siaPath, err := skymodules.NewSiaPath("foo/")
   212  	if err != nil {
   213  		t.Fatal(err)
   214  	}
   215  	err = rt.renter.CreateDir(siaPath, skymodules.DefaultDirPerm)
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  
   220  	// Check that DirInfo returns the same information as stored in the metadata
   221  	fooDirInfo, err := rt.renter.staticFileSystem.DirInfo(siaPath)
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	rootDirInfo, err := rt.renter.staticFileSystem.DirInfo(skymodules.RootSiaPath())
   226  	if err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	fooEntry, err := rt.renter.staticFileSystem.OpenSiaDir(siaPath)
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	}
   233  	rootEntry, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath())
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	err = compareDirectoryInfoAndMetadata(fooDirInfo, fooEntry)
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	err = compareDirectoryInfoAndMetadata(rootDirInfo, rootEntry)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  }
   246  
   247  // TestRenterListDirectory verifies that the renter properly lists the contents
   248  // of a directory
   249  func TestRenterListDirectory(t *testing.T) {
   250  	if testing.Short() {
   251  		t.SkipNow()
   252  	}
   253  	t.Parallel()
   254  
   255  	// Create renterTester
   256  	rt, err := newRenterTesterWithDependency(t.Name(), &dependencies.DependencyDisableRepairAndHealthLoops{})
   257  	if err != nil {
   258  		t.Fatal(err)
   259  	}
   260  	defer func() {
   261  		if err := rt.Close(); err != nil {
   262  			t.Fatal(err)
   263  		}
   264  	}()
   265  
   266  	// Create directory
   267  	siaPath, err := skymodules.NewSiaPath("foo/")
   268  	if err != nil {
   269  		t.Fatal(err)
   270  	}
   271  	err = rt.renter.CreateDir(siaPath, skymodules.DefaultDirPerm)
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  
   276  	// Upload a file
   277  	_, err = rt.renter.newRenterTestFile()
   278  	if err != nil {
   279  		t.Fatal(err)
   280  	}
   281  
   282  	// Confirm that we get expected number of FileInfo and DirectoryInfo.
   283  	directories, err := rt.renter.DirList(skymodules.RootSiaPath())
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	// 5 Directories because of root, foo, home, var, and snapshots
   288  	if len(directories) != 5 {
   289  		t.Fatal("Expected 5 DirectoryInfos but got", len(directories))
   290  	}
   291  	files, err := rt.renter.FileListCollect(skymodules.RootSiaPath(), false, false)
   292  	if err != nil {
   293  		t.Fatal(err)
   294  	}
   295  	if len(files) != 1 {
   296  		t.Fatal("Expected 1 FileInfo but got", len(files))
   297  	}
   298  
   299  	// Ensure that the metadata of all the directories gets collected into the
   300  	// root aggregate stats.
   301  	for _, dir := range directories {
   302  		err = rt.renter.UpdateMetadata(dir.SiaPath, false)
   303  		if err != nil {
   304  			t.Fatal(err)
   305  		}
   306  	}
   307  
   308  	// Wait for root directory to show proper number of files and subdirs.
   309  	err = build.Retry(100, 100*time.Millisecond, func() error {
   310  		root, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath())
   311  		if err != nil {
   312  			return err
   313  		}
   314  		rootMD, err := root.Metadata()
   315  		if err != nil {
   316  			return err
   317  		}
   318  		// Check the aggregate and siadir fields.
   319  		//
   320  		// Expecting /home, /home/user, /var, /var/skynet, /snapshots, /foo
   321  		if rootMD.AggregateNumSubDirs != 6 {
   322  			return fmt.Errorf("Expected 6 subdirs in aggregate but got %v", rootMD.AggregateNumSubDirs)
   323  		}
   324  		if rootMD.NumSubDirs != 4 {
   325  			return fmt.Errorf("Expected 4 subdirs but got %v", rootMD.NumSubDirs)
   326  		}
   327  		if rootMD.AggregateNumFiles != 1 {
   328  			return fmt.Errorf("Expected 1 file in aggregate but got %v", rootMD.AggregateNumFiles)
   329  		}
   330  		if rootMD.NumFiles != 1 {
   331  			return fmt.Errorf("Expected 1 file but got %v", rootMD.NumFiles)
   332  		}
   333  		return nil
   334  	})
   335  	if err != nil {
   336  		t.Fatal(err)
   337  	}
   338  
   339  	// Verify that the directory information matches the on disk information
   340  	err = build.Retry(100, 100*time.Millisecond, func() error {
   341  		rootDir, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.RootSiaPath())
   342  		if err != nil {
   343  			return err
   344  		}
   345  		fooDir, err := rt.renter.staticFileSystem.OpenSiaDir(siaPath)
   346  		if err != nil {
   347  			return err
   348  		}
   349  		homeDir, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.HomeFolder)
   350  		if err != nil {
   351  			return err
   352  		}
   353  		snapshotsDir, err := rt.renter.staticFileSystem.OpenSiaDir(skymodules.BackupFolder)
   354  		if err != nil {
   355  			return err
   356  		}
   357  		defer func() {
   358  			err = errors.Compose(err, rootDir.Close(), fooDir.Close(), homeDir.Close(), snapshotsDir.Close())
   359  		}()
   360  
   361  		// Refresh Directories
   362  		directories, err = rt.renter.DirList(skymodules.RootSiaPath())
   363  		if err != nil {
   364  			return err
   365  		}
   366  		// Sort directories.
   367  		sort.Slice(directories, func(i, j int) bool {
   368  			return strings.Compare(directories[i].SiaPath.String(), directories[j].SiaPath.String()) < 0
   369  		})
   370  		if err = compareDirectoryInfoAndMetadataCustom(directories[0], rootDir, false); err != nil {
   371  			return err
   372  		}
   373  		if err = compareDirectoryInfoAndMetadataCustom(directories[1], fooDir, false); err != nil {
   374  			return err
   375  		}
   376  		if err = compareDirectoryInfoAndMetadataCustom(directories[2], homeDir, false); err != nil {
   377  			return err
   378  		}
   379  		if err = compareDirectoryInfoAndMetadataCustom(directories[3], snapshotsDir, false); err != nil {
   380  			return err
   381  		}
   382  		return nil
   383  	})
   384  	if err != nil {
   385  		t.Fatal(err)
   386  	}
   387  }
   388  
   389  // compareDirectoryInfoAndMetadata is a helper that compares the information in
   390  // a DirectoryInfo struct and a SiaDirSetEntry struct
   391  func compareDirectoryInfoAndMetadata(di skymodules.DirectoryInfo, siaDir *filesystem.DirNode) error {
   392  	return compareDirectoryInfoAndMetadataCustom(di, siaDir, true)
   393  }
   394  
   395  // compareDirectoryInfoAndMetadataCustom is a helper that compares the
   396  // information in a DirectoryInfo struct and a SiaDirSetEntry struct with the
   397  // option to ignore fields based on differences in persistence
   398  func compareDirectoryInfoAndMetadataCustom(di skymodules.DirectoryInfo, siaDir *filesystem.DirNode, checkTimes bool) error {
   399  	md, err := siaDir.Metadata()
   400  	if err != nil {
   401  		return err
   402  	}
   403  
   404  	// Compare Aggregate Fields
   405  	if md.AggregateHealth != di.AggregateHealth {
   406  		return fmt.Errorf("AggregateHealths not equal, %v and %v", md.AggregateHealth, di.AggregateHealth)
   407  	}
   408  	aggregateMaxHealth := math.Max(md.AggregateHealth, md.AggregateStuckHealth)
   409  	if di.AggregateMaxHealth != aggregateMaxHealth {
   410  		return fmt.Errorf("AggregateMaxHealths not equal %v and %v", di.AggregateMaxHealth, aggregateMaxHealth)
   411  	}
   412  	aggregateMaxHealthPercentage := skymodules.HealthPercentage(aggregateMaxHealth)
   413  	if di.AggregateMaxHealthPercentage != aggregateMaxHealthPercentage {
   414  		return fmt.Errorf("AggregateMaxHealthPercentage not equal %v and %v", di.AggregateMaxHealthPercentage, aggregateMaxHealthPercentage)
   415  	}
   416  	if md.AggregateMinRedundancy != di.AggregateMinRedundancy {
   417  		return fmt.Errorf("AggregateMinRedundancy not equal, %v and %v", md.AggregateMinRedundancy, di.AggregateMinRedundancy)
   418  	}
   419  	if md.AggregateNumFiles != di.AggregateNumFiles {
   420  		return fmt.Errorf("AggregateNumFiles not equal, %v and %v", md.AggregateNumFiles, di.AggregateNumFiles)
   421  	}
   422  	if md.AggregateNumLostFiles != di.AggregateNumLostFiles {
   423  		return fmt.Errorf("AggregateNumLostFiles not equal, %v and %v", md.AggregateNumLostFiles, di.AggregateNumLostFiles)
   424  	}
   425  	if md.AggregateNumStuckChunks != di.AggregateNumStuckChunks {
   426  		return fmt.Errorf("AggregateNumStuckChunks not equal, %v and %v", md.AggregateNumStuckChunks, di.AggregateNumStuckChunks)
   427  	}
   428  	if md.AggregateNumSubDirs != di.AggregateNumSubDirs {
   429  		return fmt.Errorf("AggregateNumSubDirs not equal, %v and %v", md.AggregateNumSubDirs, di.AggregateNumSubDirs)
   430  	}
   431  	if md.AggregateNumUnfinishedFiles != di.AggregateNumUnfinishedFiles {
   432  		return fmt.Errorf("AggregateNumUnfinishedFiles not equal, %v and %v", md.AggregateNumUnfinishedFiles, di.AggregateNumUnfinishedFiles)
   433  	}
   434  	if md.AggregateSize != di.AggregateSize {
   435  		return fmt.Errorf("AggregateSizes not equal, %v and %v", md.AggregateSize, di.AggregateSize)
   436  	}
   437  	if md.NumStuckChunks != di.AggregateNumStuckChunks {
   438  		return fmt.Errorf("NumStuckChunks not equal, %v and %v", md.NumStuckChunks, di.AggregateNumStuckChunks)
   439  	}
   440  
   441  	// Compare Aggregate Time Fields
   442  	if checkTimes {
   443  		if di.AggregateLastHealthCheckTime != md.AggregateLastHealthCheckTime {
   444  			return fmt.Errorf("AggregateLastHealthCheckTimes not equal %v and %v", di.AggregateLastHealthCheckTime, md.AggregateLastHealthCheckTime)
   445  		}
   446  		if di.AggregateMostRecentModTime != md.AggregateModTime {
   447  			return fmt.Errorf("AggregateModTimes not equal %v and %v", di.AggregateMostRecentModTime, md.AggregateModTime)
   448  		}
   449  	}
   450  
   451  	// Compare Directory Fields
   452  	if md.Health != di.Health {
   453  		return fmt.Errorf("healths not equal, %v and %v", md.Health, di.Health)
   454  	}
   455  	maxHealth := math.Max(md.Health, md.StuckHealth)
   456  	if di.MaxHealth != maxHealth {
   457  		return fmt.Errorf("MaxHealths not equal %v and %v", di.MaxHealth, maxHealth)
   458  	}
   459  	maxHealthPercentage := skymodules.HealthPercentage(maxHealth)
   460  	if di.MaxHealthPercentage != maxHealthPercentage {
   461  		return fmt.Errorf("MaxHealthPercentage not equal %v and %v", di.MaxHealthPercentage, maxHealthPercentage)
   462  	}
   463  	if md.MinRedundancy != di.MinRedundancy {
   464  		return fmt.Errorf("MinRedundancy not equal, %v and %v", md.MinRedundancy, di.MinRedundancy)
   465  	}
   466  	if md.NumFiles != di.NumFiles {
   467  		return fmt.Errorf("NumFiles not equal, %v and %v", md.NumFiles, di.NumFiles)
   468  	}
   469  	if md.NumLostFiles != di.NumLostFiles {
   470  		return fmt.Errorf("NumLostFiles not equal, %v and %v", md.NumLostFiles, di.NumLostFiles)
   471  	}
   472  	if md.NumStuckChunks != di.NumStuckChunks {
   473  		return fmt.Errorf("NumStuckChunks not equal, %v and %v", md.NumStuckChunks, di.NumStuckChunks)
   474  	}
   475  	if md.NumSubDirs != di.NumSubDirs {
   476  		return fmt.Errorf("NumSubDirs not equal, %v and %v", md.NumSubDirs, di.NumSubDirs)
   477  	}
   478  	if md.NumUnfinishedFiles != di.NumUnfinishedFiles {
   479  		return fmt.Errorf("NumUnfinishedFiles not equal, %v and %v", md.NumUnfinishedFiles, di.NumUnfinishedFiles)
   480  	}
   481  	if md.Size != di.DirSize {
   482  		return fmt.Errorf("Sizes not equal, %v and %v", md.Size, di.DirSize)
   483  	}
   484  	if md.StuckHealth != di.StuckHealth {
   485  		return fmt.Errorf("stuck healths not equal, %v and %v", md.StuckHealth, di.StuckHealth)
   486  	}
   487  
   488  	// Compare Directory Time Fields
   489  	if checkTimes {
   490  		if di.LastHealthCheckTime != md.LastHealthCheckTime {
   491  			return fmt.Errorf("LastHealthCheckTimes not equal %v and %v", di.LastHealthCheckTime, md.LastHealthCheckTime)
   492  		}
   493  		if di.MostRecentModTime != md.ModTime {
   494  			return fmt.Errorf("ModTimes not equal %v and %v", di.MostRecentModTime, md.ModTime)
   495  		}
   496  	}
   497  	return nil
   498  }