gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/siafile/dependencies_test.go (about)

     1  package siafile
     2  
     3  import (
     4  	"errors"
     5  	"io/ioutil"
     6  	"os"
     7  	"sync"
     8  
     9  	"gitlab.com/NebulousLabs/fastrand"
    10  
    11  	"gitlab.com/SiaPrime/SiaPrime/modules"
    12  )
    13  
    14  var (
    15  	errDiskFault = errors.New("disk fault")
    16  )
    17  
    18  // scrambleData takes some data as input and replaces parts of it randomly with
    19  // random data
    20  func scrambleData(d []byte) []byte {
    21  	randomData := fastrand.Bytes(len(d))
    22  	scrambled := make([]byte, len(d), len(d))
    23  	for i := 0; i < len(d); i++ {
    24  		if fastrand.Intn(4) == 0 { // 25% chance to replace byte
    25  			scrambled[i] = randomData[i]
    26  		} else {
    27  			scrambled[i] = d[i]
    28  		}
    29  	}
    30  	return scrambled
    31  }
    32  
    33  // dependencyFaultyDisk implements dependencies that simulate a faulty disk.
    34  type dependencyFaultyDisk struct {
    35  	modules.ProductionDependencies
    36  	// failDenominator determines how likely it is that a write will fail,
    37  	// defined as 1/failDenominator. Each write call increments
    38  	// failDenominator, and it starts at 2. This means that the more calls to
    39  	// WriteAt, the less likely the write is to fail. All calls will start
    40  	// automatically failing after writeLimit writes.
    41  	disabled        bool
    42  	failed          bool
    43  	failDenominator int
    44  	totalWrites     int
    45  	writeLimit      int
    46  
    47  	mu sync.Mutex
    48  }
    49  
    50  // newFaultyDiskDependency creates a dependency that can be used to simulate a
    51  // failing disk. writeLimit is the maximum number of writes the disk will
    52  // endure before failing
    53  func newFaultyDiskDependency(writeLimit int) *dependencyFaultyDisk {
    54  	return &dependencyFaultyDisk{
    55  		writeLimit: writeLimit,
    56  	}
    57  }
    58  
    59  func (d *dependencyFaultyDisk) create(path string) (modules.File, error) {
    60  	d.mu.Lock()
    61  	defer d.mu.Unlock()
    62  	if d.tryFail() {
    63  		return nil, errDiskFault
    64  	}
    65  
    66  	f, err := os.Create(path)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return d.newFaultyFile(f), nil
    71  }
    72  
    73  // disabled allows the caller to temporarily disable the dependency
    74  func (d *dependencyFaultyDisk) disable() {
    75  	d.mu.Lock()
    76  	d.disabled = true
    77  	d.mu.Unlock()
    78  }
    79  func (d *dependencyFaultyDisk) enable() {
    80  	d.mu.Lock()
    81  	d.disabled = false
    82  	d.mu.Unlock()
    83  }
    84  
    85  // tryFail will check if the disk has failed yet, and if not, it'll rng to see
    86  // if the disk should fail now. Returns 'true' if the disk has failed.
    87  func (d *dependencyFaultyDisk) tryFail() bool {
    88  	d.totalWrites++
    89  	if d.disabled {
    90  		return false
    91  	}
    92  	if d.failed {
    93  		return true
    94  	}
    95  
    96  	d.failDenominator += fastrand.Intn(8)
    97  	fail := fastrand.Intn(int(d.failDenominator+1)) == 0 // +1 to prevent 0 from being passed in.
    98  	if fail || d.failDenominator >= d.writeLimit {
    99  		d.failed = true
   100  		return true
   101  	}
   102  	return false
   103  }
   104  
   105  // newFaultyFile creates a new faulty file around the provided file handle.
   106  func (d *dependencyFaultyDisk) newFaultyFile(f *os.File) modules.File {
   107  	return &faultyFile{d: d, file: f}
   108  }
   109  func (*dependencyFaultyDisk) readFile(path string) ([]byte, error) {
   110  	return ioutil.ReadFile(path)
   111  }
   112  func (d *dependencyFaultyDisk) remove(path string) error {
   113  	d.mu.Lock()
   114  	defer d.mu.Unlock()
   115  
   116  	if d.tryFail() {
   117  		return nil
   118  	}
   119  	return os.Remove(path)
   120  }
   121  
   122  // reset resets the failDenominator and the failed flag of the dependency
   123  func (d *dependencyFaultyDisk) reset() {
   124  	d.mu.Lock()
   125  	d.failDenominator = 0
   126  	d.failed = false
   127  	d.mu.Unlock()
   128  }
   129  func (d *dependencyFaultyDisk) Open(path string) (modules.File, error) {
   130  	return d.OpenFile(path, os.O_RDONLY, 0)
   131  }
   132  func (d *dependencyFaultyDisk) OpenFile(path string, flag int, perm os.FileMode) (modules.File, error) {
   133  	f, err := os.OpenFile(path, flag, perm)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return d.newFaultyFile(f), nil
   138  }
   139  
   140  // faultyFile implements a file that simulates a faulty disk.
   141  type faultyFile struct {
   142  	d    *dependencyFaultyDisk
   143  	file *os.File
   144  }
   145  
   146  func (f *faultyFile) Read(p []byte) (int, error) {
   147  	return f.file.Read(p)
   148  }
   149  func (f *faultyFile) Write(p []byte) (int, error) {
   150  	f.d.mu.Lock()
   151  	defer f.d.mu.Unlock()
   152  	if f.d.tryFail() {
   153  		return f.file.Write(scrambleData(p))
   154  	}
   155  	return f.file.Write(p)
   156  }
   157  func (f *faultyFile) Close() error { return f.file.Close() }
   158  func (f *faultyFile) Name() string {
   159  	return f.file.Name()
   160  }
   161  func (f *faultyFile) ReadAt(p []byte, off int64) (int, error) {
   162  	return f.file.ReadAt(p, off)
   163  }
   164  func (f *faultyFile) Seek(offset int64, whence int) (int64, error) {
   165  	return f.file.Seek(offset, whence)
   166  }
   167  func (f *faultyFile) Truncate(size int64) error {
   168  	return f.file.Truncate(size)
   169  }
   170  func (f *faultyFile) WriteAt(p []byte, off int64) (int, error) {
   171  	f.d.mu.Lock()
   172  	defer f.d.mu.Unlock()
   173  	if f.d.tryFail() {
   174  		return f.file.WriteAt(scrambleData(p), off)
   175  	}
   176  	return f.file.WriteAt(p, off)
   177  }
   178  func (f *faultyFile) Stat() (os.FileInfo, error) {
   179  	return f.file.Stat()
   180  }
   181  func (f *faultyFile) Sync() error {
   182  	f.d.mu.Lock()
   183  	defer f.d.mu.Unlock()
   184  	if f.d.tryFail() {
   185  		return errDiskFault
   186  	}
   187  	return f.file.Sync()
   188  }