gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/host/contractmanager/storagefolderadd_test.go (about)

     1  package contractmanager
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"gitlab.com/SiaPrime/SiaPrime/modules"
    14  )
    15  
    16  // TestAddStorageFolder tries to add a storage folder to the contract manager,
    17  // blocking until the add has completed.
    18  func TestAddStorageFolder(t *testing.T) {
    19  	if testing.Short() {
    20  		t.SkipNow()
    21  	}
    22  	t.Parallel()
    23  	cmt, err := newContractManagerTester("TestAddStorageFolder")
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  	defer cmt.panicClose()
    28  
    29  	// Add a storage folder to the contract manager tester.
    30  	storageFolderDir := filepath.Join(cmt.persistDir, "storageFolderOne")
    31  	// Create the storage folder dir.
    32  	err = os.MkdirAll(storageFolderDir, 0700)
    33  	if err != nil {
    34  		t.Fatal(err)
    35  	}
    36  	err = cmt.cm.AddStorageFolder(storageFolderDir, modules.SectorSize*storageFolderGranularity*2)
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  
    41  	// Check that the storage folder has been added.
    42  	sfs := cmt.cm.StorageFolders()
    43  	if len(sfs) != 1 {
    44  		t.Fatal("There should be one storage folder reported")
    45  	}
    46  	// Check that the storage folder has the right path and size.
    47  	if sfs[0].Path != storageFolderDir {
    48  		t.Error("storage folder reported with wrong path")
    49  	}
    50  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*2 {
    51  		t.Error("storage folder reported with wrong sector size")
    52  	}
    53  }
    54  
    55  // dependencyLargeFolder is a mocked dependency that will return files which
    56  // can only handle 1 MiB of data being written to them.
    57  type dependencyLargeFolder struct {
    58  	modules.ProductionDependencies
    59  }
    60  
    61  // limitFile will return an error if a call to Write is made that will put the
    62  // total throughput of the file over 1 MiB.
    63  type limitFile struct {
    64  	throughput int64
    65  	mu         sync.Mutex
    66  	*os.File
    67  	sync.Mutex
    68  }
    69  
    70  // createFile will return a file that will return an error if a write will put
    71  // the total throughput of the file over 1 MiB.
    72  func (*dependencyLargeFolder) CreateFile(s string) (modules.File, error) {
    73  	osFile, err := os.Create(s)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	lf := &limitFile{
    79  		File: osFile,
    80  	}
    81  	return lf, nil
    82  }
    83  
    84  // Truncate returns an error if the operation will put the total throughput of
    85  // the file over 8 MiB.
    86  func (l *limitFile) Truncate(offset int64) error {
    87  	l.mu.Lock()
    88  	defer l.mu.Unlock()
    89  	// If the limit has already been reached, return an error.
    90  	if l.throughput >= 1<<20 {
    91  		return errors.New("limitFile throughput limit reached earlier")
    92  	}
    93  
    94  	fi, err := l.Stat()
    95  	if err != nil {
    96  		return errors.New("limitFile could not fetch fileinfo: " + err.Error())
    97  	}
    98  	// No throughput if file is shrinking.
    99  	if fi.Size() > offset {
   100  		return l.File.Truncate(offset)
   101  	}
   102  	writeSize := offset - fi.Size()
   103  
   104  	// If the limit has not been reached, pass the call through to the
   105  	// underlying file. Limit counting is a little wonky because we assume the
   106  	// file being passed in has currently a size of zero.
   107  	if l.throughput+writeSize <= 1<<20 {
   108  		l.throughput += writeSize
   109  		return l.File.Truncate(offset)
   110  	}
   111  
   112  	// If the limit has been reached, return an error.
   113  	return errors.New("limitFile throughput limit reached before all input was written to disk")
   114  }
   115  
   116  // TestAddLargeStorageFolder tries to add a storage folder that is too large to
   117  // fit on disk. This is represented by mocking a file that returns an error
   118  // after more than 8 MiB have been written.
   119  func TestAddLargeStorageFolder(t *testing.T) {
   120  	if testing.Short() {
   121  		t.SkipNow()
   122  	}
   123  	t.Parallel()
   124  	d := new(dependencyLargeFolder)
   125  	cmt, err := newMockedContractManagerTester(d, "TestAddLargeStorageFolder")
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	defer cmt.panicClose()
   130  
   131  	// Add a storage folder to the contract manager tester.
   132  	storageFolderDir := filepath.Join(cmt.persistDir, "storageFolderOne")
   133  	// Create the storage folder dir.
   134  	err = os.MkdirAll(storageFolderDir, 0700)
   135  	if err != nil {
   136  		t.Fatal(err)
   137  	}
   138  	addErr := cmt.cm.AddStorageFolder(storageFolderDir, modules.SectorSize*storageFolderGranularity*16) // Total size must exceed the limit of the limitFile.
   139  	// Should be a storage folder error, but with all the context adding, I'm
   140  	// not sure how to check the error type.
   141  	if addErr == nil {
   142  		t.Fatal(err)
   143  	}
   144  
   145  	// Check that the storage folder has been added.
   146  	sfs := cmt.cm.StorageFolders()
   147  	if len(sfs) != 0 {
   148  		t.Fatal("Storage folder add should have failed.")
   149  	}
   150  	// Check that the storage folder is empty - because the operation failed,
   151  	// any files that got created should have been removed.
   152  	files, err := ioutil.ReadDir(storageFolderDir)
   153  	if err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	if len(files) != 0 {
   157  		t.Log(addErr)
   158  		t.Error("there should not be any files in the storage folder because the AddStorageFolder operation failed.")
   159  		t.Error(len(files))
   160  		for _, file := range files {
   161  			t.Error(file.Name())
   162  		}
   163  	}
   164  }
   165  
   166  // TestAddStorageFolderConcurrent adds multiple storage folders concurrently to
   167  // the contract manager.
   168  func TestAddStorageFolderConcurrent(t *testing.T) {
   169  	if testing.Short() {
   170  		t.SkipNow()
   171  	}
   172  	t.Parallel()
   173  	cmt, err := newContractManagerTester("TestAddStorageFolderConcurrent")
   174  	if err != nil {
   175  		t.Fatal(err)
   176  	}
   177  	defer cmt.panicClose()
   178  
   179  	// Add a storage folder to the contract manager tester.
   180  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   181  	storageFolderTwo := filepath.Join(cmt.persistDir, "storageFolderTwo")
   182  	storageFolderThree := filepath.Join(cmt.persistDir, "storageFolderThree")
   183  	// Create the storage folder dir.
   184  	err = os.MkdirAll(storageFolderOne, 0700)
   185  	if err != nil {
   186  		t.Fatal(err)
   187  	}
   188  	err = os.MkdirAll(storageFolderTwo, 0700)
   189  	if err != nil {
   190  		t.Fatal(err)
   191  	}
   192  	err = os.MkdirAll(storageFolderThree, 0700)
   193  	if err != nil {
   194  		t.Fatal(err)
   195  	}
   196  
   197  	// Launch three calls to add simultaneously and wait for all three to
   198  	// finish.
   199  	var wg sync.WaitGroup
   200  	wg.Add(3)
   201  	go func() {
   202  		defer wg.Done()
   203  		err := cmt.cm.AddStorageFolder(storageFolderOne, modules.SectorSize*storageFolderGranularity*8)
   204  		if err != nil {
   205  			t.Fatal(err)
   206  		}
   207  	}()
   208  	go func() {
   209  		defer wg.Done()
   210  		err := cmt.cm.AddStorageFolder(storageFolderTwo, modules.SectorSize*storageFolderGranularity*8)
   211  		if err != nil {
   212  			t.Fatal(err)
   213  		}
   214  	}()
   215  	go func() {
   216  		defer wg.Done()
   217  		err = cmt.cm.AddStorageFolder(storageFolderThree, modules.SectorSize*storageFolderGranularity*8)
   218  		if err != nil {
   219  			t.Fatal(err)
   220  		}
   221  	}()
   222  	wg.Wait()
   223  
   224  	// Check that the storage folder has been added.
   225  	sfs := cmt.cm.StorageFolders()
   226  	if len(sfs) != 3 {
   227  		t.Fatal("There should be one storage folder reported")
   228  	}
   229  }
   230  
   231  // dependencyBlockSFOne is a mocked dependency for os.Create that will return a
   232  // file for storage folder one only which will block on a call to file.Truncate
   233  // until a signal has been given that the block can be released.
   234  type dependencyBlockSFOne struct {
   235  	blockLifted chan struct{}
   236  	writeCalled chan struct{}
   237  	modules.ProductionDependencies
   238  }
   239  
   240  // blockedFile is the file that gets returned by dependencyBlockSFOne to
   241  // storageFolderOne.
   242  type blockedFile struct {
   243  	blockLifted chan struct{}
   244  	writeCalled chan struct{}
   245  	*os.File
   246  	sync.Mutex
   247  }
   248  
   249  // Truncate will block until a signal is given that the block may be lifted.
   250  // Truncate will signal when it has been called for the first time, so that the
   251  // tester knows the function has reached a blocking point.
   252  func (bf *blockedFile) Truncate(offset int64) error {
   253  	if !strings.Contains(bf.File.Name(), "storageFolderOne") || strings.Contains(bf.File.Name(), "siahostmetadata.dat") {
   254  		return bf.File.Truncate(offset)
   255  	}
   256  	close(bf.writeCalled)
   257  	<-bf.blockLifted
   258  	return bf.File.Truncate(offset)
   259  }
   260  
   261  // createFile will return a normal file to all callers except for
   262  // storageFolderOne, which will have calls to file.Write blocked until a signal
   263  // is given that the blocks may be released.
   264  func (d *dependencyBlockSFOne) CreateFile(s string) (modules.File, error) {
   265  	// If storageFolderOne, return a file that will not write until the signal
   266  	// is sent that writing is okay.
   267  	if strings.Contains(s, "storageFolderOne") {
   268  		file, err := os.Create(s)
   269  		if err != nil {
   270  			return nil, err
   271  		}
   272  		bf := &blockedFile{
   273  			blockLifted: d.blockLifted,
   274  			writeCalled: d.writeCalled,
   275  			File:        file,
   276  		}
   277  		return bf, nil
   278  	}
   279  
   280  	// If not storageFolderOne, return a normal file.
   281  	return os.Create(s)
   282  }
   283  
   284  // TestAddStorageFolderBlocking adds multiple storage folders concurrently to
   285  // the contract manager, blocking on the first one to make sure that the others
   286  // are still allowed to complete.
   287  func TestAddStorageFolderBlocking(t *testing.T) {
   288  	if testing.Short() {
   289  		t.SkipNow()
   290  	}
   291  	t.Parallel()
   292  	// Create the mocked dependencies that will block for the first storage
   293  	// folder.
   294  	d := &dependencyBlockSFOne{
   295  		blockLifted: make(chan struct{}),
   296  		writeCalled: make(chan struct{}),
   297  	}
   298  
   299  	// Create a contract manager tester with the mocked dependencies.
   300  	cmt, err := newMockedContractManagerTester(d, "TestAddStorageFolderBlocking")
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  	defer cmt.panicClose()
   305  
   306  	// Add a storage folder to the contract manager tester.
   307  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   308  	storageFolderTwo := filepath.Join(cmt.persistDir, "storageFolderTwo")
   309  	storageFolderThree := filepath.Join(cmt.persistDir, "storageFolderThree")
   310  	// Create the storage folder dir.
   311  	err = os.MkdirAll(storageFolderOne, 0700)
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  	err = os.MkdirAll(storageFolderTwo, 0700)
   316  	if err != nil {
   317  		t.Fatal(err)
   318  	}
   319  	err = os.MkdirAll(storageFolderThree, 0700)
   320  	if err != nil {
   321  		t.Fatal(err)
   322  	}
   323  
   324  	// Spin off the first goroutine, and then wait until write has been called
   325  	// on the underlying file.
   326  	sfOneSize := modules.SectorSize * storageFolderGranularity * 8
   327  	go func() {
   328  		err := cmt.cm.AddStorageFolder(storageFolderOne, sfOneSize)
   329  		if err != nil {
   330  			t.Fatal(err)
   331  		}
   332  	}()
   333  	select {
   334  	case <-time.After(time.Second * 5):
   335  		t.Fatal("storage folder not written out")
   336  	case <-d.writeCalled:
   337  	}
   338  
   339  	// Check the status of the storage folder. At this point, the folder should
   340  	// be returned as an unfinished storage folder addition, with progress
   341  	// indicating that the storage folder is at 0 bytes progressed out of
   342  	// sfOneSize.
   343  	sfs := cmt.cm.StorageFolders()
   344  	if len(sfs) != 1 {
   345  		t.Fatal("there should be one storage folder reported")
   346  	}
   347  	if sfs[0].ProgressNumerator != 0 {
   348  		t.Error("storage folder is showing progress despite being blocked")
   349  	}
   350  	if sfs[0].ProgressDenominator != sfOneSize+sectorMetadataDiskSize*storageFolderGranularity*8 {
   351  		t.Error("storage folder is not showing that an action is in progress, though one is", sfs[0].ProgressDenominator, sfOneSize)
   352  	}
   353  
   354  	var wg sync.WaitGroup
   355  	wg.Add(2)
   356  	go func() {
   357  		defer wg.Done()
   358  		err := cmt.cm.AddStorageFolder(storageFolderTwo, modules.SectorSize*storageFolderGranularity*8)
   359  		if err != nil {
   360  			t.Fatal(err)
   361  		}
   362  	}()
   363  	go func() {
   364  		defer wg.Done()
   365  		err = cmt.cm.AddStorageFolder(storageFolderThree, modules.SectorSize*storageFolderGranularity*8)
   366  		if err != nil {
   367  			t.Fatal(err)
   368  		}
   369  	}()
   370  	wg.Wait()
   371  	close(d.blockLifted)
   372  	cmt.cm.tg.Flush()
   373  
   374  	// Check that the storage folder has been added.
   375  	sfs = cmt.cm.StorageFolders()
   376  	if len(sfs) != 3 {
   377  		t.Fatal("There should be one storage folder reported")
   378  	}
   379  	// All actions should have completed, so all storage folders should be
   380  	// reporting '0' in the progress denominator.
   381  	for _, sf := range sfs {
   382  		if sf.ProgressDenominator != 0 {
   383  			t.Error("ProgressDenominator is indicating that actions still remain")
   384  		}
   385  	}
   386  }
   387  
   388  // TestAddStorageFolderConsecutive adds multiple storage folders consecutively
   389  // to the contract manager, blocking on the first one to make sure that the
   390  // others are still allowed to complete.
   391  func TestAddStorageFolderConsecutive(t *testing.T) {
   392  	if testing.Short() {
   393  		t.SkipNow()
   394  	}
   395  	t.Parallel()
   396  	// Create a contract manager tester with the mocked dependencies.
   397  	cmt, err := newContractManagerTester("TestAddStorageFolderConsecutive")
   398  	if err != nil {
   399  		t.Fatal(err)
   400  	}
   401  	defer cmt.panicClose()
   402  
   403  	// Add a storage folder to the contract manager tester.
   404  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   405  	storageFolderTwo := filepath.Join(cmt.persistDir, "storageFolderTwo")
   406  	storageFolderThree := filepath.Join(cmt.persistDir, "storageFolderThree")
   407  	// Create the storage folder dir.
   408  	err = os.MkdirAll(storageFolderOne, 0700)
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	err = os.MkdirAll(storageFolderTwo, 0700)
   413  	if err != nil {
   414  		t.Fatal(err)
   415  	}
   416  	err = os.MkdirAll(storageFolderThree, 0700)
   417  	if err != nil {
   418  		t.Fatal(err)
   419  	}
   420  
   421  	// Spin off the first goroutine, and then wait until write has been called
   422  	// on the underlying file.
   423  	sfSize := modules.SectorSize * storageFolderGranularity * 8
   424  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize)
   425  	if err != nil {
   426  		t.Fatal(err)
   427  	}
   428  	err = cmt.cm.AddStorageFolder(storageFolderTwo, sfSize)
   429  	if err != nil {
   430  		t.Fatal(err)
   431  	}
   432  	err = cmt.cm.AddStorageFolder(storageFolderThree, sfSize)
   433  	if err != nil {
   434  		t.Fatal(err)
   435  	}
   436  
   437  	// Check that the storage folder has been added.
   438  	sfs := cmt.cm.StorageFolders()
   439  	if len(sfs) != 3 {
   440  		t.Fatal("There should be one storage folder reported")
   441  	}
   442  	// All actions should have completed, so all storage folders should be
   443  	// reporting '0' in the progress denominator.
   444  	for _, sf := range sfs {
   445  		if sf.ProgressDenominator != 0 {
   446  			t.Error("ProgressDenominator is indicating that actions still remain")
   447  		}
   448  	}
   449  }
   450  
   451  // TestAddStorageFolderDoubleAdd concurrently adds two storage
   452  // folders with the same path to the contract manager.
   453  func TestAddStorageFolderDoubleAdd(t *testing.T) {
   454  	if testing.Short() {
   455  		t.SkipNow()
   456  	}
   457  	t.Parallel()
   458  	// Create a contract manager tester with the mocked dependencies.
   459  	cmt, err := newContractManagerTester("TestAddStorageFolderDoubleAdd")
   460  	if err != nil {
   461  		t.Fatal(err)
   462  	}
   463  	defer cmt.panicClose()
   464  
   465  	// Add a storage folder to the contract manager tester.
   466  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   467  	// Create the storage folder dir.
   468  	err = os.MkdirAll(storageFolderOne, 0700)
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  
   473  	// Call AddStorageFolder in three separate goroutines, where the same path
   474  	// is used in each. The errors are not checked because one of the storage
   475  	// folders will succeed, but it's uncertain which one.
   476  	sfSize := modules.SectorSize * storageFolderGranularity * 8
   477  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize)
   478  	if err != nil {
   479  		t.Fatal(err)
   480  	}
   481  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize*2)
   482  	if err != ErrRepeatFolder {
   483  		t.Fatal(err)
   484  	}
   485  
   486  	// Check that the storage folder has been added.
   487  	sfs := cmt.cm.StorageFolders()
   488  	if len(sfs) != 1 {
   489  		t.Fatal("There should be one storage folder reported")
   490  	}
   491  	// All actions should have completed, so all storage folders should be
   492  	// reporting '0' in the progress denominator
   493  	for _, sf := range sfs {
   494  		if sf.ProgressDenominator != 0 {
   495  			t.Error("ProgressDenominator is indicating that actions still remain")
   496  		}
   497  	}
   498  }
   499  
   500  // dependencyNoSyncLoop is a mocked dependency that will disable the sync loop.
   501  type dependencyNoSyncLoop struct {
   502  	modules.ProductionDependencies
   503  }
   504  
   505  // disrupt will disrupt the threadedSyncLoop, causing the loop to terminate as
   506  // soon as it is created.
   507  func (*dependencyNoSyncLoop) Disrupt(s string) bool {
   508  	if s == "threadedSyncLoopStart" || s == "cleanWALFile" {
   509  		// Disrupt threadedSyncLoop. The sync loop will exit immediately
   510  		// instead of executing commits. Also disrupt the process that removes
   511  		// the WAL file following clean shutdown.
   512  		return true
   513  	}
   514  	return false
   515  }
   516  
   517  // TestAddStorageFolderDoubleAddNoCommit hijacks the sync loop in the contract
   518  // manager such that the sync loop will not run automatically. Then, without
   519  // doing an actual commit, the test will indicate to open functions that a
   520  // commit has completed, allowing the next storage folder operation to happen.
   521  // Because the changes were finalized but not committed, extra code coverage
   522  // should be achieved, though the result of the storage folder being rejected
   523  // should be the same.
   524  func TestAddStorageFolderDoubleAddNoCommit(t *testing.T) {
   525  	if testing.Short() {
   526  		t.SkipNow()
   527  	}
   528  	t.Parallel()
   529  	d := new(dependencyNoSyncLoop)
   530  	cmt, err := newMockedContractManagerTester(d, "TestAddStorageFolderDoubleAddNoCommit")
   531  	if err != nil {
   532  		t.Fatal(err)
   533  	}
   534  	// The closing of this channel must happen after the call to panicClose.
   535  	closeFakeSyncChan := make(chan struct{})
   536  	defer close(closeFakeSyncChan)
   537  	defer cmt.panicClose()
   538  
   539  	// The sync loop will never run, which means naively AddStorageFolder will
   540  	// never return. To get AddStorageFolder to return before the commit
   541  	// completes, spin up an alternate sync loop which only performs the
   542  	// signaling responsibilities of the commit function.
   543  	go func() {
   544  		for {
   545  			select {
   546  			case <-closeFakeSyncChan:
   547  				return
   548  			case <-time.After(time.Millisecond * 250):
   549  				// Signal that the commit operation has completed, even though
   550  				// it has not.
   551  				cmt.cm.wal.mu.Lock()
   552  				close(cmt.cm.wal.syncChan)
   553  				cmt.cm.wal.syncChan = make(chan struct{})
   554  				cmt.cm.wal.mu.Unlock()
   555  			}
   556  		}
   557  	}()
   558  
   559  	// Add a storage folder to the contract manager tester.
   560  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   561  	// Create the storage folder dir.
   562  	err = os.MkdirAll(storageFolderOne, 0700)
   563  	if err != nil {
   564  		t.Fatal(err)
   565  	}
   566  
   567  	// Call AddStorageFolder in three separate goroutines, where the same path
   568  	// is used in each. The errors are not checked because one of the storage
   569  	// folders will succeed, but it's uncertain which one.
   570  	sfSize := modules.SectorSize * storageFolderGranularity * 8
   571  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize)
   572  	if err != nil {
   573  		t.Fatal(err)
   574  	}
   575  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize*2)
   576  	if err != ErrRepeatFolder {
   577  		t.Fatal(err)
   578  	}
   579  
   580  	// Check that the storage folder has been added.
   581  	sfs := cmt.cm.StorageFolders()
   582  	if len(sfs) != 1 {
   583  		t.Fatal("There should be one storage folder reported", len(sfs))
   584  	}
   585  	// All actions should have completed, so all storage folders should be
   586  	// reporting '0' in the progress denominator
   587  	for _, sf := range sfs {
   588  		if sf.ProgressDenominator != 0 {
   589  			t.Error("ProgressDenominator is indicating that actions still remain")
   590  		}
   591  	}
   592  }
   593  
   594  // TestAddStorageFolderFailedCommit adds a storage folder without ever saving
   595  // the settings.
   596  func TestAddStorageFolderFailedCommit(t *testing.T) {
   597  	if testing.Short() {
   598  		t.SkipNow()
   599  	}
   600  	t.Parallel()
   601  	d := new(dependencyNoSettingsSave)
   602  	cmt, err := newMockedContractManagerTester(d, "TestAddStorageFolderFailedCommit")
   603  	if err != nil {
   604  		t.Fatal(err)
   605  	}
   606  	defer cmt.panicClose()
   607  
   608  	// Add a storage folder to the contract manager tester.
   609  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   610  	// Create the storage folder dir.
   611  	err = os.MkdirAll(storageFolderOne, 0700)
   612  	if err != nil {
   613  		t.Fatal(err)
   614  	}
   615  	sfSize := modules.SectorSize * storageFolderGranularity * 8
   616  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize)
   617  	if err != nil {
   618  		t.Fatal(err)
   619  	}
   620  	d.mu.Lock()
   621  	d.triggered = true
   622  	d.mu.Unlock()
   623  
   624  	// Check that the storage folder has been added.
   625  	sfs := cmt.cm.StorageFolders()
   626  	if len(sfs) != 1 {
   627  		t.Fatal("There should be one storage folder reported")
   628  	}
   629  	// All actions should have completed, so all storage folders should be
   630  	// reporting '0' in the progress denominator
   631  	if sfs[0].ProgressDenominator != 0 {
   632  		t.Error("ProgressDenominator is indicating that actions still remain")
   633  	}
   634  
   635  	// Close the contract manager and replace it with a new contract manager.
   636  	// The new contract manager should have normal dependencies.
   637  	err = cmt.cm.Close()
   638  	if err != nil {
   639  		t.Fatal(err)
   640  	}
   641  	// Create the new contract manager using the same persist dir, so that it
   642  	// will see the uncommitted WAL.
   643  	cmt.cm, err = New(filepath.Join(cmt.persistDir, modules.ContractManagerDir))
   644  	if err != nil {
   645  		t.Fatal(err)
   646  	}
   647  	// Check that the storage folder was properly recovered.
   648  	sfs = cmt.cm.StorageFolders()
   649  	if len(sfs) != 1 {
   650  		t.Fatal("There should be one storage folder reported", len(sfs))
   651  	}
   652  }
   653  
   654  // dependencySFAddNoFinish is a mocked dependency that will prevent the
   655  type dependencySFAddNoFinish struct {
   656  	modules.ProductionDependencies
   657  }
   658  
   659  // disrupt will disrupt the threadedSyncLoop, causing the loop to terminate as
   660  // soon as it is created.
   661  func (d *dependencySFAddNoFinish) Disrupt(s string) bool {
   662  	if s == "storageFolderAddFinish" {
   663  		return true
   664  	}
   665  	if s == "cleanWALFile" {
   666  		// Prevent the WAL file from being removed.
   667  		return true
   668  	}
   669  	return false
   670  }
   671  
   672  // TestAddStorageFolderUnfinishedCreate hijacks both the sync loop and the
   673  // AddStorageFolder code to create a situation where the added storage folder
   674  // is started but not seen through to conclusion, and no commit is run.
   675  func TestAddStorageFolderUnfinishedCreate(t *testing.T) {
   676  	if testing.Short() {
   677  		t.SkipNow()
   678  	}
   679  	t.Parallel()
   680  	d := new(dependencySFAddNoFinish)
   681  	cmt, err := newMockedContractManagerTester(d, "TestAddStorageFolderUnfinishedCreate")
   682  	if err != nil {
   683  		t.Fatal(err)
   684  	}
   685  	defer cmt.panicClose()
   686  
   687  	// Add a storage folder to the contract manager tester.
   688  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   689  	// Create the storage folder dir.
   690  	err = os.MkdirAll(storageFolderOne, 0700)
   691  	if err != nil {
   692  		t.Fatal(err)
   693  	}
   694  	// Call AddStorageFolder, knowing that the changes will not be properly
   695  	// committed, and that the call itself will not actually complete.
   696  	sfSize := modules.SectorSize * storageFolderGranularity * 8
   697  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize)
   698  	if err != nil {
   699  		t.Fatal(err)
   700  	}
   701  
   702  	// Check that the storage folder has been added.
   703  	sfs := cmt.cm.StorageFolders()
   704  	if len(sfs) != 1 {
   705  		t.Fatal("There should be one storage folder reported")
   706  	}
   707  
   708  	// Close the contract manager and replace it with a new contract manager.
   709  	// The new contract manager should have normal dependencies.
   710  	err = cmt.cm.Close()
   711  	if err != nil {
   712  		t.Fatal(err)
   713  	}
   714  	// Create the new contract manager using the same persist dir, so that it
   715  	// will see the uncommitted WAL.
   716  	cmt.cm, err = New(filepath.Join(cmt.persistDir, modules.ContractManagerDir))
   717  	if err != nil {
   718  		t.Fatal(err)
   719  	}
   720  	// Check that the storage folder was properly removed - incomplete storage
   721  	// folder adds should be removed upon startup.
   722  	sfs = cmt.cm.StorageFolders()
   723  	if len(sfs) != 0 {
   724  		t.Error("Storage folder add should have failed.")
   725  	}
   726  	// Check that the storage folder is empty - because the operation failed,
   727  	// any files that got created should have been removed.
   728  	files, err := ioutil.ReadDir(storageFolderOne)
   729  	if err != nil {
   730  		t.Error(err)
   731  	}
   732  	if len(files) != 0 {
   733  		t.Error("there should not be any files in the storage folder because the AddStorageFolder operation failed:", len(files))
   734  		t.Error(len(files))
   735  		for _, file := range files {
   736  			t.Error(file.Name())
   737  		}
   738  	}
   739  }
   740  
   741  // TestAddStorageFolderDoubleAddConcurrent concurrently adds two storage
   742  // folders with the same path to the contract manager.
   743  func TestAddStorageFolderDoubleAddConcurrent(t *testing.T) {
   744  	if testing.Short() {
   745  		t.SkipNow()
   746  	}
   747  	t.Parallel()
   748  	// Create a contract manager tester with the mocked dependencies.
   749  	cmt, err := newContractManagerTester("TestAddStorageFolderDoubleAddConcurrent")
   750  	if err != nil {
   751  		t.Fatal(err)
   752  	}
   753  	defer cmt.panicClose()
   754  
   755  	// Add a storage folder to the contract manager tester.
   756  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   757  	// Create the storage folder dir.
   758  	err = os.MkdirAll(storageFolderOne, 0700)
   759  	if err != nil {
   760  		t.Fatal(err)
   761  	}
   762  
   763  	// Call AddStorageFolder in three separate goroutines, where the same path
   764  	// is used in each. The errors are not checked because one of the storage
   765  	// folders will succeed, but it's uncertain which one.
   766  	var wg sync.WaitGroup
   767  	sfSize := modules.SectorSize * storageFolderGranularity * 8
   768  	wg.Add(3)
   769  	go func() {
   770  		_ = cmt.cm.AddStorageFolder(storageFolderOne, sfSize)
   771  		wg.Done()
   772  	}()
   773  	go func() {
   774  		_ = cmt.cm.AddStorageFolder(storageFolderOne, sfSize*2)
   775  		wg.Done()
   776  	}()
   777  	go func() {
   778  		_ = cmt.cm.AddStorageFolder(storageFolderOne, sfSize*3)
   779  		wg.Done()
   780  	}()
   781  	wg.Wait()
   782  
   783  	// Check that the storage folder has been added.
   784  	sfs := cmt.cm.StorageFolders()
   785  	if len(sfs) != 1 {
   786  		t.Fatal("There should be one storage folder reported")
   787  	}
   788  	// All actions should have completed, so all storage folders should be
   789  	// reporting '0' in the progress denominator.
   790  	for _, sf := range sfs {
   791  		if sf.ProgressDenominator != 0 {
   792  			t.Error("ProgressDenominator is indicating that actions still remain")
   793  		}
   794  	}
   795  }
   796  
   797  // TestAddStorageFolderReload adds a storage folder to the contract manager,
   798  // and then reloads the contract manager to see if the storage folder is still
   799  // there.
   800  func TestAddStorageFolderReload(t *testing.T) {
   801  	if testing.Short() {
   802  		t.SkipNow()
   803  	}
   804  	t.Parallel()
   805  	// Create a contract manager tester with the mocked dependencies.
   806  	cmt, err := newContractManagerTester("TestAddStorageFolderReload")
   807  	if err != nil {
   808  		t.Fatal(err)
   809  	}
   810  	defer cmt.panicClose()
   811  
   812  	// Add a storage folder to the contract manager tester.
   813  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   814  	// Create the storage folder dir.
   815  	err = os.MkdirAll(storageFolderOne, 0700)
   816  	if err != nil {
   817  		t.Fatal(err)
   818  	}
   819  	sfSize := modules.SectorSize * storageFolderGranularity * 24
   820  	err = cmt.cm.AddStorageFolder(storageFolderOne, sfSize)
   821  	if err != nil {
   822  		t.Fatal(err)
   823  	}
   824  
   825  	// Check that the storage folder has been added.
   826  	sfs := cmt.cm.StorageFolders()
   827  	if len(sfs) != 1 {
   828  		t.Fatal("There should be one storage folder reported")
   829  	}
   830  	// Check that the size of the storage folder is correct.
   831  	if sfs[0].Capacity != sfSize {
   832  		t.Error("capacity reported by storage folder is not the capacity alloacted")
   833  	}
   834  	if sfs[0].CapacityRemaining != sfSize {
   835  		t.Error("capacity remaining reported by storage folder is not the capacity alloacted")
   836  	}
   837  	// All actions should have completed, so all storage folders should be
   838  	// reporting '0' in the progress denominator.
   839  	for _, sf := range sfs {
   840  		if sf.ProgressDenominator != 0 {
   841  			t.Error("ProgressDenominator is indicating that actions still remain")
   842  		}
   843  	}
   844  
   845  	// Close the contract manager and open a new one using the same
   846  	// persistence.
   847  	err = cmt.cm.Close()
   848  	if err != nil {
   849  		t.Fatal(err)
   850  	}
   851  	cmt.cm, err = New(filepath.Join(cmt.persistDir, modules.ContractManagerDir))
   852  	if err != nil {
   853  		t.Fatal(err)
   854  	}
   855  
   856  	// Check that the storage folder has been added.
   857  	sfs = cmt.cm.StorageFolders()
   858  	if len(sfs) != 1 {
   859  		t.Fatal("There should be one storage folder reported", len(sfs))
   860  	}
   861  	// Check that the size of the storage folder is correct.
   862  	if sfs[0].Capacity != sfSize {
   863  		t.Error("capacity reported by storage folder is not the capacity alloacted")
   864  	}
   865  	if sfs[0].CapacityRemaining != sfSize {
   866  		t.Error("capacity remaining reported by storage folder is not the capacity alloacted", sfs[0].Capacity, sfs[0].CapacityRemaining)
   867  	}
   868  	// Check that the storage folder as represented on disk has the correct
   869  	// size.
   870  	sectorLookupTableSize := int64(storageFolderGranularity * 24 * sectorMetadataDiskSize)
   871  	expectedSize := int64(sfSize)
   872  	fi, err := os.Stat(filepath.Join(storageFolderOne, sectorFile))
   873  	if err != nil {
   874  		t.Fatal(err)
   875  	}
   876  	if fi.Size() != expectedSize {
   877  		t.Error("sector file had unexpected size", fi.Size(), expectedSize)
   878  	}
   879  	fi, err = os.Stat(filepath.Join(storageFolderOne, metadataFile))
   880  	if err != nil {
   881  		t.Fatal(err)
   882  	}
   883  	if fi.Size() != sectorLookupTableSize {
   884  		t.Error("sector file had unexpected size", fi.Size(), sectorLookupTableSize)
   885  	}
   886  }