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 }