gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/siadir/siadirset_test.go (about)

     1  package siadir
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"gitlab.com/NebulousLabs/errors"
    11  	"gitlab.com/NebulousLabs/fastrand"
    12  
    13  	"gitlab.com/SiaPrime/SiaPrime/modules"
    14  )
    15  
    16  // newTestSiaDirSet creates a new SiaDirSet
    17  func newTestSiaDirSet() *SiaDirSet {
    18  	// Create params
    19  	dir := filepath.Join(os.TempDir(), "siadirs")
    20  	wal, _ := newTestWAL()
    21  	return NewSiaDirSet(dir, wal)
    22  }
    23  
    24  // newTestSiaDirSetWithDir creates a new SiaDirSet and SiaDir and makes sure
    25  // that they are linked
    26  func newTestSiaDirSetWithDir() (*SiaDirSetEntry, *SiaDirSet, error) {
    27  	// Create directory
    28  	dir := filepath.Join(os.TempDir(), "siadirs")
    29  	// Create SiaDirSet and SiaDirSetEntry
    30  	wal, _ := newTestWAL()
    31  	sds := NewSiaDirSet(dir, wal)
    32  	entry, err := sds.NewSiaDir(modules.RandomSiaPath())
    33  	if err != nil {
    34  		return nil, nil, err
    35  	}
    36  	return entry, sds, nil
    37  }
    38  
    39  // TestInitRootDir checks that InitRootDir creates a siadir on disk and that it
    40  // can be called again without returning an error
    41  func TestInitRootDir(t *testing.T) {
    42  	if testing.Short() {
    43  		t.SkipNow()
    44  	}
    45  	t.Parallel()
    46  
    47  	// Create new SiaDirSet
    48  	sds := newTestSiaDirSet()
    49  
    50  	// Create a root SiaDirt
    51  	if err := sds.InitRootDir(); err != nil {
    52  		t.Fatal(err)
    53  	}
    54  
    55  	// Verify the siadir exists on disk
    56  	siaPath := modules.RootSiaPath().SiaDirMetadataSysPath(sds.staticRootDir)
    57  	_, err := os.Stat(siaPath)
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  
    62  	// Verify that the siadir is not stored in memory
    63  	if len(sds.siaDirMap) != 0 {
    64  		t.Fatal("SiaDirSet has siadirs in memory")
    65  	}
    66  
    67  	// Try initializing the root directory again, there should be no error
    68  	if err := sds.InitRootDir(); err != nil {
    69  		t.Fatal(err)
    70  	}
    71  }
    72  
    73  // TestSiaDirSetOpenClose tests that the threadCount of the siadir is
    74  // incremented and decremented properly when Open() and Close() are called
    75  func TestSiaDirSetOpenClose(t *testing.T) {
    76  	if testing.Short() {
    77  		t.SkipNow()
    78  	}
    79  	t.Parallel()
    80  
    81  	// Create SiaDirSet with SiaDir
    82  	entry, sds, err := newTestSiaDirSetWithDir()
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  	siaPath := entry.SiaPath()
    87  	exists, err := sds.Exists(siaPath)
    88  	if !exists {
    89  		t.Fatal("No SiaDirSetEntry found")
    90  	}
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  
    95  	// Confirm dir is in memory
    96  	if len(sds.siaDirMap) != 1 {
    97  		t.Fatalf("Expected SiaDirSet map to be of length 1, instead is length %v", len(sds.siaDirMap))
    98  	}
    99  
   100  	// Confirm threadCount is incremented properly
   101  	if len(entry.threadMap) != 1 {
   102  		t.Fatalf("Expected threadMap to be of length 1, got %v", len(entry.threadMap))
   103  	}
   104  
   105  	// Close SiaDirSetEntry
   106  	entry.Close()
   107  
   108  	// Confirm that threadCount was decremented
   109  	if len(entry.threadMap) != 0 {
   110  		t.Fatalf("Expected threadCount to be 0, got %v", len(entry.threadMap))
   111  	}
   112  
   113  	// Confirm dir was removed from memory
   114  	if len(sds.siaDirMap) != 0 {
   115  		t.Fatalf("Expected SiaDirSet map to be empty, instead is length %v", len(sds.siaDirMap))
   116  	}
   117  
   118  	// Open siafile again and confirm threadCount was incremented
   119  	entry, err = sds.Open(siaPath)
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  	if len(entry.threadMap) != 1 {
   124  		t.Fatalf("Expected threadCount to be 1, got %v", len(entry.threadMap))
   125  	}
   126  }
   127  
   128  // TestDirsInMemory confirms that files are added and removed from memory
   129  // as expected when files are in use and not in use
   130  func TestDirsInMemory(t *testing.T) {
   131  	if testing.Short() {
   132  		t.SkipNow()
   133  	}
   134  	t.Parallel()
   135  
   136  	// Create SiaDirSet with SiaDir
   137  	entry, sds, err := newTestSiaDirSetWithDir()
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  	siaPath := entry.SiaPath()
   142  	exists, err := sds.Exists(siaPath)
   143  	if !exists {
   144  		t.Fatal("No SiaDirSetEntry found")
   145  	}
   146  	if err != nil {
   147  		t.Fatal(err)
   148  	}
   149  	// Confirm there is 1 dir in memory
   150  	if len(sds.siaDirMap) != 1 {
   151  		t.Fatal("Expected 1 dir in memory, got:", len(sds.siaDirMap))
   152  	}
   153  	// Close File
   154  	err = entry.Close()
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	// Confirm therte are no files in memory
   159  	if len(sds.siaDirMap) != 0 {
   160  		t.Fatal("Expected 0 files in memory, got:", len(sds.siaDirMap))
   161  	}
   162  
   163  	// Test accessing the same dir from two separate threads
   164  	//
   165  	// Open dir
   166  	entry1, err := sds.Open(siaPath)
   167  	if err != nil {
   168  		t.Fatal(err)
   169  	}
   170  	// Confirm there is 1 dir in memory
   171  	if len(sds.siaDirMap) != 1 {
   172  		t.Fatal("Expected 1 dir in memory, got:", len(sds.siaDirMap))
   173  	}
   174  	// Access the dir again
   175  	entry2, err := sds.Open(siaPath)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	// Confirm there is still only has 1 dir in memory
   180  	if len(sds.siaDirMap) != 1 {
   181  		t.Fatal("Expected 1 dir in memory, got:", len(sds.siaDirMap))
   182  	}
   183  	// Close one of the dir instances
   184  	err = entry1.Close()
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  	// Confirm there is still only has 1 dir in memory
   189  	if len(sds.siaDirMap) != 1 {
   190  		t.Fatal("Expected 1 dir in memory, got:", len(sds.siaDirMap))
   191  	}
   192  
   193  	// Confirm closing out remaining files removes all files from memory
   194  	//
   195  	// Close last instance of the first dir
   196  	err = entry2.Close()
   197  	if err != nil {
   198  		t.Fatal(err)
   199  	}
   200  	// Confirm there are no files in memory
   201  	if len(sds.siaDirMap) != 0 {
   202  		t.Fatal("Expected 0 files in memory, got:", len(sds.siaDirMap))
   203  	}
   204  }
   205  
   206  // TestUpdateSiaDirSetMetadata probes the UpdateMetadata method of the SiaDirSet
   207  func TestUpdateSiaDirSetMetadata(t *testing.T) {
   208  	if testing.Short() {
   209  		t.SkipNow()
   210  	}
   211  	t.Parallel()
   212  
   213  	// Create SiaDirSet with SiaDir
   214  	entry, sds, err := newTestSiaDirSetWithDir()
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	siaPath := entry.SiaPath()
   219  	exists, err := sds.Exists(siaPath)
   220  	if !exists {
   221  		t.Fatal("No SiaDirSetEntry found")
   222  	}
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  
   227  	// Confirm metadata is set properly
   228  	md := entry.metadata
   229  	if err = checkMetadataInit(md); err != nil {
   230  		t.Fatal(err)
   231  	}
   232  
   233  	// Update the metadata of the entry
   234  	checkTime := time.Now()
   235  	metadataUpdate := md
   236  	// Aggregate fields
   237  	metadataUpdate.AggregateHealth = 7
   238  	metadataUpdate.AggregateLastHealthCheckTime = checkTime
   239  	metadataUpdate.AggregateMinRedundancy = 2.2
   240  	metadataUpdate.AggregateModTime = checkTime
   241  	metadataUpdate.AggregateNumFiles = 11
   242  	metadataUpdate.AggregateNumStuckChunks = 15
   243  	metadataUpdate.AggregateNumSubDirs = 5
   244  	metadataUpdate.AggregateSize = 2432
   245  	metadataUpdate.AggregateStuckHealth = 5
   246  	// SiaDir fields
   247  	metadataUpdate.Health = 4
   248  	metadataUpdate.LastHealthCheckTime = checkTime
   249  	metadataUpdate.MinRedundancy = 2
   250  	metadataUpdate.ModTime = checkTime
   251  	metadataUpdate.NumFiles = 5
   252  	metadataUpdate.NumStuckChunks = 6
   253  	metadataUpdate.NumSubDirs = 4
   254  	metadataUpdate.Size = 223
   255  	metadataUpdate.StuckHealth = 2
   256  
   257  	err = sds.UpdateMetadata(siaPath, metadataUpdate)
   258  	if err != nil {
   259  		t.Fatal(err)
   260  	}
   261  
   262  	// Check if the metadata was updated properly in memory and on disk
   263  	md = entry.metadata
   264  	err = equalMetadatas(md, metadataUpdate)
   265  	if err != nil {
   266  		t.Fatal(err)
   267  	}
   268  }
   269  
   270  // TestSiaDirRename tests the Rename method of the siadirset.
   271  func TestSiaDirRename(t *testing.T) {
   272  	if testing.Short() {
   273  		t.SkipNow()
   274  	}
   275  	// Prepare a siadirset
   276  	dir := filepath.Join(os.TempDir(), "siadirs", t.Name())
   277  	os.RemoveAll(dir)
   278  	wal, _ := newTestWAL()
   279  	sds := NewSiaDirSet(dir, wal)
   280  
   281  	// Specify a directory structure for this test.
   282  	var dirStructure = []string{
   283  		"dir1",
   284  		"dir1/subdir1",
   285  		"dir1/subdir1/subsubdir1",
   286  		"dir1/subdir1/subsubdir2",
   287  		"dir1/subdir1/subsubdir3",
   288  		"dir1/subdir2",
   289  		"dir1/subdir2/subsubdir1",
   290  		"dir1/subdir2/subsubdir2",
   291  		"dir1/subdir2/subsubdir3",
   292  		"dir1/subdir3",
   293  		"dir1/subdir3/subsubdir1",
   294  		"dir1/subdir3/subsubdir2",
   295  		"dir1/subdir3/subsubdir3",
   296  	}
   297  	// Specify a function that's executed in parallel which continuously saves dirs
   298  	// to disk.
   299  	stop := make(chan struct{})
   300  	wg := new(sync.WaitGroup)
   301  	f := func(entry *SiaDirSetEntry) {
   302  		defer wg.Done()
   303  		defer entry.Close()
   304  		for {
   305  			select {
   306  			case <-stop:
   307  				return
   308  			default:
   309  			}
   310  			err := entry.UpdateMetadata(Metadata{})
   311  			if err != nil {
   312  				t.Fatal(err)
   313  			}
   314  			time.Sleep(50 * time.Millisecond)
   315  		}
   316  	}
   317  	// Create the structure and spawn a goroutine that keeps saving the structure
   318  	// to disk for each directory.
   319  	for _, dir := range dirStructure {
   320  		sp, err := modules.NewSiaPath(dir)
   321  		if err != nil {
   322  			t.Fatal(err)
   323  		}
   324  		entry, err := sds.NewSiaDir(sp)
   325  		if err != nil {
   326  			t.Fatal(err)
   327  		}
   328  		// 50% chance to spawn goroutine. It's not realistic to assume that all dirs
   329  		// are loaded.
   330  		if fastrand.Intn(2) == 0 {
   331  			wg.Add(1)
   332  			go f(entry)
   333  		} else {
   334  			entry.Close()
   335  		}
   336  	}
   337  	// Wait a second for the goroutines to write to disk a few times.
   338  	time.Sleep(time.Second)
   339  	// Rename dir1 to dir2.
   340  	oldPath, err1 := modules.NewSiaPath(dirStructure[0])
   341  	newPath, err2 := modules.NewSiaPath("dir2")
   342  	if err := errors.Compose(err1, err2); err != nil {
   343  		t.Fatal(err)
   344  	}
   345  	if err := sds.Rename(oldPath, newPath); err != nil {
   346  		t.Fatal(err)
   347  	}
   348  	// Wait another second for more writes to disk after renaming the dir before
   349  	// killing the goroutines.
   350  	time.Sleep(time.Second)
   351  	close(stop)
   352  	wg.Wait()
   353  	time.Sleep(time.Second)
   354  	// Make sure we can't open any of the old folders on disk but we can open the
   355  	// new ones.
   356  	for _, dir := range dirStructure {
   357  		oldDir, err1 := modules.NewSiaPath(dir)
   358  		newDir, err2 := oldDir.Rebase(oldPath, newPath)
   359  		if err := errors.Compose(err1, err2); err != nil {
   360  			t.Fatal(err)
   361  		}
   362  		// Open entry with old dir. Shouldn't work.
   363  		_, err := sds.Open(oldDir)
   364  		if err != ErrUnknownPath {
   365  			t.Fatal("shouldn't be able to open old path", oldDir.String(), err)
   366  		}
   367  		// Open entry with new dir. Should succeed.
   368  		entry, err := sds.Open(newDir)
   369  		if err != nil {
   370  			t.Fatal(err)
   371  		}
   372  		defer entry.Close()
   373  		// Check siapath of entry.
   374  		if entry.siaPath != newDir {
   375  			t.Fatalf("entry should have siapath '%v' but was '%v'", newDir, entry.siaPath)
   376  		}
   377  	}
   378  }
   379  
   380  // TestHealthPercentage checks the values returned from HealthPercentage
   381  func TestHealthPercentage(t *testing.T) {
   382  	var tests = []struct {
   383  		health           float64
   384  		healthPercentage float64
   385  	}{
   386  		{1.5, 0},
   387  		{1.25, 0},
   388  		{1.0, 25},
   389  		{0.75, 50},
   390  		{0.5, 75},
   391  		{0.25, 100},
   392  		{0, 100},
   393  	}
   394  	for _, test := range tests {
   395  		hp := HealthPercentage(test.health)
   396  		if hp != test.healthPercentage {
   397  			t.Fatalf("Expect %v got %v", test.healthPercentage, hp)
   398  		}
   399  	}
   400  }