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

     1  package contractmanager
     2  
     3  import (
     4  	"errors"
     5  	"os"
     6  	"path/filepath"
     7  	"sync"
     8  	"testing"
     9  
    10  	"gitlab.com/SiaPrime/SiaPrime/modules"
    11  )
    12  
    13  // TestGrowStorageFolder checks that a storage folder can be successfully
    14  // increased in size.
    15  func TestGrowStorageFolder(t *testing.T) {
    16  	if testing.Short() {
    17  		t.SkipNow()
    18  	}
    19  	t.Parallel()
    20  	cmt, err := newContractManagerTester("TestGrowStorageFolder")
    21  	if err != nil {
    22  		t.Fatal(err)
    23  	}
    24  	defer cmt.panicClose()
    25  
    26  	// Add a storage folder.
    27  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
    28  	// Create the storage folder dir.
    29  	err = os.MkdirAll(storageFolderOne, 0700)
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  	err = cmt.cm.AddStorageFolder(storageFolderOne, modules.SectorSize*storageFolderGranularity)
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  
    38  	// Get the index of the storage folder.
    39  	sfs := cmt.cm.StorageFolders()
    40  	if len(sfs) != 1 {
    41  		t.Fatal("there should only be one storage folder")
    42  	}
    43  	sfIndex := sfs[0].Index
    44  	// Verify that the storage folder has the correct capacity.
    45  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity {
    46  		t.Error("new storage folder is reporting the wrong capacity")
    47  	}
    48  	// Verify that the on-disk files are the right size.
    49  	mfn := filepath.Join(storageFolderOne, metadataFile)
    50  	sfn := filepath.Join(storageFolderOne, sectorFile)
    51  	mfi, err := os.Stat(mfn)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  	sfi, err := os.Stat(sfn)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  	if uint64(mfi.Size()) != sectorMetadataDiskSize*storageFolderGranularity {
    60  		t.Error("metadata file is the wrong size")
    61  	}
    62  	if uint64(sfi.Size()) != modules.SectorSize*storageFolderGranularity {
    63  		t.Error("sector file is the wrong size")
    64  	}
    65  
    66  	// Increase the size of the storage folder.
    67  	err = cmt.cm.ResizeStorageFolder(sfIndex, modules.SectorSize*storageFolderGranularity*2, false)
    68  	if err != nil {
    69  		t.Fatal(err)
    70  	}
    71  	// Verify that the capacity and file sizes are correct.
    72  	sfs = cmt.cm.StorageFolders()
    73  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*2 {
    74  		t.Error("new storage folder is reporting the wrong capacity")
    75  	}
    76  	mfi, err = os.Stat(mfn)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	sfi, err = os.Stat(sfn)
    81  	if err != nil {
    82  		t.Fatal(err)
    83  	}
    84  	if uint64(mfi.Size()) != sectorMetadataDiskSize*storageFolderGranularity*2 {
    85  		t.Error("metadata file is the wrong size")
    86  	}
    87  	if uint64(sfi.Size()) != modules.SectorSize*storageFolderGranularity*2 {
    88  		t.Error("sector file is the wrong size")
    89  	}
    90  
    91  	// Restart the contract manager to see that the change is persistent.
    92  	err = cmt.cm.Close()
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  	cmt.cm, err = New(filepath.Join(cmt.persistDir, modules.ContractManagerDir))
    97  	if err != nil {
    98  		t.Fatal(err)
    99  	}
   100  
   101  	// Verify that the capacity and file sizes are correct.
   102  	sfs = cmt.cm.StorageFolders()
   103  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*2 {
   104  		t.Error("new storage folder is reporting the wrong capacity")
   105  	}
   106  	mfi, err = os.Stat(mfn)
   107  	if err != nil {
   108  		t.Fatal(err)
   109  	}
   110  	sfi, err = os.Stat(sfn)
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	if uint64(mfi.Size()) != sectorMetadataDiskSize*storageFolderGranularity*2 {
   115  		t.Error("metadata file is the wrong size")
   116  	}
   117  	if uint64(sfi.Size()) != modules.SectorSize*storageFolderGranularity*2 {
   118  		t.Error("sector file is the wrong size")
   119  	}
   120  }
   121  
   122  // dependencyIncompleteGrow will start to have disk failures after too much
   123  // data is written and also after 'triggered' ahs been set to true.
   124  type dependencyIncompleteGrow struct {
   125  	modules.ProductionDependencies
   126  	triggered bool
   127  	threshold int
   128  	mu        sync.Mutex
   129  }
   130  
   131  // triggerLimitFile will return an error if a call to Write is made that will
   132  // put the total throughput of the file over 1 MiB. Counting only begins once
   133  // triggered.
   134  type triggerLimitFile struct {
   135  	dig *dependencyIncompleteGrow
   136  
   137  	throughput int
   138  	mu         sync.Mutex
   139  	*os.File
   140  	sync.Mutex
   141  }
   142  
   143  // CreateFile will return a file that will return an error if a write will put
   144  // the total throughput of the file over 1 MiB.
   145  func (dig *dependencyIncompleteGrow) CreateFile(s string) (modules.File, error) {
   146  	osFile, err := os.Create(s)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  
   151  	tlf := &triggerLimitFile{
   152  		dig:  dig,
   153  		File: osFile,
   154  	}
   155  	return tlf, nil
   156  }
   157  
   158  // Write returns an error if the operation will put the total throughput of the
   159  // file over 8 MiB. The write will write all the way to 8 MiB before returning
   160  // the error.
   161  func (l *triggerLimitFile) WriteAt(b []byte, offset int64) (int, error) {
   162  	l.mu.Lock()
   163  	defer l.mu.Unlock()
   164  	l.dig.mu.Lock()
   165  	triggered := l.dig.triggered
   166  	l.dig.mu.Unlock()
   167  	if !triggered {
   168  		return l.File.WriteAt(b, offset)
   169  	}
   170  
   171  	// If the limit has already been reached, return an error.
   172  	if l.throughput >= l.dig.threshold {
   173  		return 0, errors.New("triggerLimitFile throughput limit reached earlier")
   174  	}
   175  
   176  	// If the limit has not been reached, pass the call through to the
   177  	// underlying file.
   178  	if l.throughput+len(b) <= l.dig.threshold {
   179  		l.throughput += len(b)
   180  		return l.File.WriteAt(b, offset)
   181  	}
   182  
   183  	// If the limit has been reached, write enough bytes to get to 8 MiB, then
   184  	// return an error.
   185  	remaining := l.dig.threshold - l.throughput
   186  	l.throughput = l.dig.threshold
   187  	written, err := l.File.WriteAt(b[:remaining], offset)
   188  	if err != nil {
   189  		return written, err
   190  	}
   191  	return written, errors.New("triggerLimitFile throughput limit reached before all input was written to disk")
   192  }
   193  
   194  // Truncate returns an error if the operation will put the total throughput of
   195  // the file over 8 MiB.
   196  func (l *triggerLimitFile) Truncate(offset int64) error {
   197  	l.mu.Lock()
   198  	defer l.mu.Unlock()
   199  	l.dig.mu.Lock()
   200  	triggered := l.dig.triggered
   201  	l.dig.mu.Unlock()
   202  	if !triggered {
   203  		return l.File.Truncate(offset)
   204  	}
   205  
   206  	// If the limit has already been reached, return an error.
   207  	if l.throughput >= l.dig.threshold {
   208  		return errors.New("triggerLimitFile throughput limit reached earlier")
   209  	}
   210  
   211  	// Get the file size, so we know what the throughput is.
   212  	fi, err := l.Stat()
   213  	if err != nil {
   214  		return errors.New("triggerLimitFile unable to get FileInfo: " + err.Error())
   215  	}
   216  
   217  	// Run truncate with 0 throughput if size is larger than offset.
   218  	if fi.Size() > offset {
   219  		return l.File.Truncate(offset)
   220  	}
   221  
   222  	writeSize := int(offset - fi.Size())
   223  
   224  	// If the limit has not been reached, pass the call through to the
   225  	// underlying file.
   226  	if l.throughput+writeSize <= l.dig.threshold {
   227  		l.throughput += writeSize
   228  		return l.File.Truncate(offset)
   229  	}
   230  
   231  	// If the limit has been reached, return an error.
   232  	// return an error.
   233  	return errors.New("triggerLimitFile throughput limit reached, no ability to allocate more")
   234  }
   235  
   236  // TestGrowStorageFolderIncopmleteWrite checks that growStorageFolder operates
   237  // as intended when the writing to increase the filesize does not complete all
   238  // the way.
   239  func TestGrowStorageFolderIncompleteWrite(t *testing.T) {
   240  	if testing.Short() {
   241  		t.SkipNow()
   242  	}
   243  	t.Parallel()
   244  	d := new(dependencyIncompleteGrow)
   245  	cmt, err := newMockedContractManagerTester(d, "TestGrowStorageFolderIncompleteWrite")
   246  	if err != nil {
   247  		t.Fatal(err)
   248  	}
   249  	defer cmt.panicClose()
   250  
   251  	// Add a storage folder.
   252  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   253  	// Create the storage folder dir.
   254  	err = os.MkdirAll(storageFolderOne, 0700)
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  	err = cmt.cm.AddStorageFolder(storageFolderOne, modules.SectorSize*storageFolderGranularity*3)
   259  	if err != nil {
   260  		t.Fatal(err)
   261  	}
   262  
   263  	// Get the index of the storage folder.
   264  	sfs := cmt.cm.StorageFolders()
   265  	if len(sfs) != 1 {
   266  		t.Fatal("there should only be one storage folder")
   267  	}
   268  	sfIndex := sfs[0].Index
   269  	// Verify that the storage folder has the correct capacity.
   270  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*3 {
   271  		t.Error("new storage folder is reporting the wrong capacity")
   272  	}
   273  
   274  	// Trigger the dependencies, so that writes begin failing.
   275  	d.mu.Lock()
   276  	d.threshold = 1 << 20
   277  	d.triggered = true
   278  	d.mu.Unlock()
   279  
   280  	// Increase the size of the storage folder, to large enough that it will
   281  	// fail.
   282  	err = cmt.cm.ResizeStorageFolder(sfIndex, modules.SectorSize*storageFolderGranularity*25, false)
   283  	if err == nil {
   284  		t.Fatal("expecting error upon resize")
   285  	}
   286  
   287  	// Verify that the storage folder has the correct capacity.
   288  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*3 {
   289  		t.Error("new storage folder is reporting the wrong capacity")
   290  	}
   291  	// Verify that the on-disk files are the right size.
   292  	mfn := filepath.Join(storageFolderOne, metadataFile)
   293  	sfn := filepath.Join(storageFolderOne, sectorFile)
   294  	mfi, err := os.Stat(mfn)
   295  	if err != nil {
   296  		t.Fatal(err)
   297  	}
   298  	sfi, err := os.Stat(sfn)
   299  	if err != nil {
   300  		t.Fatal(err)
   301  	}
   302  	if uint64(mfi.Size()) != sectorMetadataDiskSize*storageFolderGranularity*3 {
   303  		t.Error("metadata file is the wrong size:", mfi.Size(), sectorMetadataDiskSize*storageFolderGranularity*3)
   304  	}
   305  	if uint64(sfi.Size()) != modules.SectorSize*storageFolderGranularity*3 {
   306  		t.Error("sector file is the wrong size:", sfi.Size(), modules.SectorSize*storageFolderGranularity*3)
   307  	}
   308  
   309  	// Restart the contract manager.
   310  	err = cmt.cm.Close()
   311  	if err != nil {
   312  		t.Fatal(err)
   313  	}
   314  	cmt.cm, err = New(filepath.Join(cmt.persistDir, modules.ContractManagerDir))
   315  	if err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	// Verify that the storage folder has the correct capacity.
   320  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*3 {
   321  		t.Error("new storage folder is reporting the wrong capacity")
   322  	}
   323  	// Verify that the on-disk files are the right size.
   324  	mfi, err = os.Stat(mfn)
   325  	if err != nil {
   326  		t.Fatal(err)
   327  	}
   328  	sfi, err = os.Stat(sfn)
   329  	if err != nil {
   330  		t.Fatal(err)
   331  	}
   332  	if uint64(mfi.Size()) != sectorMetadataDiskSize*storageFolderGranularity*3 {
   333  		t.Error("metadata file is the wrong size:", mfi.Size(), sectorMetadataDiskSize*storageFolderGranularity*3)
   334  	}
   335  	if uint64(sfi.Size()) != modules.SectorSize*storageFolderGranularity*3 {
   336  		t.Error("sector file is the wrong size:", sfi.Size(), modules.SectorSize*storageFolderGranularity*3)
   337  	}
   338  }
   339  
   340  // dependencyGrowNoFinalize will not add a confirmation to the WAL that a
   341  // growStorageFolder operation has completed.
   342  type dependencyGrowNoFinalize struct {
   343  	modules.ProductionDependencies
   344  }
   345  
   346  // disrupt will prevent the growStorageFolder operation from committing a
   347  // finalized growStorageFolder operation to the WAL.
   348  func (*dependencyGrowNoFinalize) Disrupt(s string) bool {
   349  	if s == "incompleteGrowStorageFolder" {
   350  		return true
   351  	}
   352  	if s == "cleanWALFile" {
   353  		return true
   354  	}
   355  	return false
   356  }
   357  
   358  // TestGrowStorageFolderShutdownAfterWrite simulates an unclean shutdown that
   359  // occurs after the storage folder write has completed, but before it has
   360  // established through the WAL that the write has completed. The result should
   361  // be that the storage folder grow is not accepted after restart.
   362  func TestGrowStorageFolderShutdownAfterWrite(t *testing.T) {
   363  	if testing.Short() {
   364  		t.SkipNow()
   365  	}
   366  	t.Parallel()
   367  	d := new(dependencyGrowNoFinalize)
   368  	cmt, err := newMockedContractManagerTester(d, "TestGrowStorageFolderShutdownAfterWrite")
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  	defer cmt.panicClose()
   373  
   374  	// Add a storage folder.
   375  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   376  	// Create the storage folder dir.
   377  	err = os.MkdirAll(storageFolderOne, 0700)
   378  	if err != nil {
   379  		t.Fatal(err)
   380  	}
   381  	err = cmt.cm.AddStorageFolder(storageFolderOne, modules.SectorSize*storageFolderGranularity*3)
   382  	if err != nil {
   383  		t.Fatal(err)
   384  	}
   385  
   386  	// Get the index of the storage folder.
   387  	sfs := cmt.cm.StorageFolders()
   388  	if len(sfs) != 1 {
   389  		t.Fatal("there should only be one storage folder")
   390  	}
   391  	sfIndex := sfs[0].Index
   392  	// Verify that the storage folder has the correct capacity.
   393  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*3 {
   394  		t.Error("new storage folder is reporting the wrong capacity")
   395  	}
   396  
   397  	// Increase the size of the storage folder, to large enough that it will
   398  	// fail.
   399  	err = cmt.cm.ResizeStorageFolder(sfIndex, modules.SectorSize*storageFolderGranularity*25, false)
   400  	if err != nil {
   401  		t.Fatal(err)
   402  	}
   403  
   404  	// Restart the contract manager.
   405  	err = cmt.cm.Close()
   406  	if err != nil {
   407  		t.Fatal(err)
   408  	}
   409  	cmt.cm, err = New(filepath.Join(cmt.persistDir, modules.ContractManagerDir))
   410  	if err != nil {
   411  		t.Fatal(err)
   412  	}
   413  
   414  	// Verify that the storage folder has the correct capacity.
   415  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*3 {
   416  		t.Error("new storage folder is reporting the wrong capacity")
   417  	}
   418  	// Verify that the on-disk files are the right size.
   419  	mfn := filepath.Join(storageFolderOne, metadataFile)
   420  	sfn := filepath.Join(storageFolderOne, sectorFile)
   421  	mfi, err := os.Stat(mfn)
   422  	if err != nil {
   423  		t.Fatal(err)
   424  	}
   425  	sfi, err := os.Stat(sfn)
   426  	if err != nil {
   427  		t.Fatal(err)
   428  	}
   429  	if uint64(mfi.Size()) != sectorMetadataDiskSize*storageFolderGranularity*3 {
   430  		t.Error("metadata file is the wrong size:", mfi.Size(), sectorMetadataDiskSize*storageFolderGranularity*3)
   431  	}
   432  	if uint64(sfi.Size()) != modules.SectorSize*storageFolderGranularity*3 {
   433  		t.Error("sector file is the wrong size:", sfi.Size(), modules.SectorSize*storageFolderGranularity*3)
   434  	}
   435  }
   436  
   437  // dependencyLeaveWAL will leave the WAL on disk during shutdown.
   438  type dependencyLeaveWAL struct {
   439  	mu sync.Mutex
   440  	modules.ProductionDependencies
   441  	triggered bool
   442  }
   443  
   444  // disrupt will prevent the WAL file from being removed at shutdown.
   445  func (dlw *dependencyLeaveWAL) Disrupt(s string) bool {
   446  	if s == "cleanWALFile" {
   447  		return true
   448  	}
   449  
   450  	dlw.mu.Lock()
   451  	triggered := dlw.triggered
   452  	dlw.mu.Unlock()
   453  	if s == "walRename" && triggered {
   454  		return true
   455  	}
   456  
   457  	return false
   458  }
   459  
   460  // TestGrowStorageFolderWAL completes a storage folder growing, but leaves the
   461  // WAL behind so that a commit is necessary to finalize things.
   462  func TestGrowStorageFolderWAL(t *testing.T) {
   463  	if testing.Short() {
   464  		t.SkipNow()
   465  	}
   466  	t.Parallel()
   467  	d := new(dependencyLeaveWAL)
   468  	cmt, err := newMockedContractManagerTester(d, "TestGrowStorageFolderWAL")
   469  	if err != nil {
   470  		t.Fatal(err)
   471  	}
   472  	defer cmt.panicClose()
   473  
   474  	// Add a storage folder.
   475  	storageFolderOne := filepath.Join(cmt.persistDir, "storageFolderOne")
   476  	// Create the storage folder dir.
   477  	err = os.MkdirAll(storageFolderOne, 0700)
   478  	if err != nil {
   479  		t.Fatal(err)
   480  	}
   481  	err = cmt.cm.AddStorageFolder(storageFolderOne, modules.SectorSize*storageFolderGranularity*3)
   482  	if err != nil {
   483  		t.Fatal(err)
   484  	}
   485  
   486  	// Get the index of the storage folder.
   487  	sfs := cmt.cm.StorageFolders()
   488  	if len(sfs) != 1 {
   489  		t.Fatal("there should only be one storage folder")
   490  	}
   491  	sfIndex := sfs[0].Index
   492  	// Verify that the storage folder has the correct capacity.
   493  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*3 {
   494  		t.Error("new storage folder is reporting the wrong capacity")
   495  	}
   496  
   497  	// Increase the size of the storage folder, to large enough that it will
   498  	// fail.
   499  	err = cmt.cm.ResizeStorageFolder(sfIndex, modules.SectorSize*storageFolderGranularity*25, false)
   500  	if err != nil {
   501  		t.Fatal(err)
   502  	}
   503  	d.mu.Lock()
   504  	d.triggered = true
   505  	d.mu.Unlock()
   506  
   507  	// Restart the contract manager.
   508  	err = cmt.cm.Close()
   509  	if err != nil {
   510  		t.Fatal(err)
   511  	}
   512  	cmt.cm, err = New(filepath.Join(cmt.persistDir, modules.ContractManagerDir))
   513  	if err != nil {
   514  		t.Fatal(err)
   515  	}
   516  
   517  	// Verify that the storage folder has the correct capacity.
   518  	sfs = cmt.cm.StorageFolders()
   519  	if sfs[0].Capacity != modules.SectorSize*storageFolderGranularity*25 {
   520  		t.Error("new storage folder is reporting the wrong capacity", sfs[0].Capacity/modules.SectorSize, storageFolderGranularity*25)
   521  	}
   522  	// Verify that the on-disk files are the right size.
   523  	mfn := filepath.Join(storageFolderOne, metadataFile)
   524  	sfn := filepath.Join(storageFolderOne, sectorFile)
   525  	mfi, err := os.Stat(mfn)
   526  	if err != nil {
   527  		t.Fatal(err)
   528  	}
   529  	sfi, err := os.Stat(sfn)
   530  	if err != nil {
   531  		t.Fatal(err)
   532  	}
   533  	if uint64(mfi.Size()) != sectorMetadataDiskSize*storageFolderGranularity*25 {
   534  		t.Error("metadata file is the wrong size:", mfi.Size(), sectorMetadataDiskSize*storageFolderGranularity*25)
   535  	}
   536  	if uint64(sfi.Size()) != modules.SectorSize*storageFolderGranularity*25 {
   537  		t.Error("sector file is the wrong size:", sfi.Size(), modules.SectorSize*storageFolderGranularity*25)
   538  	}
   539  }