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  }