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 }