gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/host_test.go (about) 1 package api 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "net/url" 8 "os" 9 "path/filepath" 10 "reflect" 11 "strconv" 12 "testing" 13 "time" 14 15 "gitlab.com/SiaPrime/SiaPrime/build" 16 "gitlab.com/SiaPrime/SiaPrime/crypto" 17 "gitlab.com/SiaPrime/SiaPrime/modules" 18 "gitlab.com/SiaPrime/SiaPrime/modules/host/contractmanager" 19 "gitlab.com/SiaPrime/SiaPrime/types" 20 ) 21 22 var ( 23 // Various folder sizes for testing host storage folder resizing. 24 // Must be provided as strings to the API call. 25 minFolderSizeString = strconv.FormatUint(modules.SectorSize*contractmanager.MinimumSectorsPerStorageFolder, 10) 26 maxFolderSizeString = strconv.FormatUint(modules.SectorSize*contractmanager.MaximumSectorsPerStorageFolder, 10) 27 tooSmallFolderString = strconv.FormatUint(modules.SectorSize*(contractmanager.MinimumSectorsPerStorageFolder-1), 10) 28 tooLargeFolderString = strconv.FormatUint(modules.SectorSize*(contractmanager.MaximumSectorsPerStorageFolder+1), 10) 29 mediumSizeFolderString = strconv.FormatUint(modules.SectorSize*contractmanager.MinimumSectorsPerStorageFolder*3, 10) 30 31 // Test cases for resizing a host's storage folder. 32 // Running all the invalid cases before the valid ones simplifies some 33 // logic in the tests that use resizeTests. 34 resizeTests = []struct { 35 sizeString string 36 size uint64 37 err error 38 }{ 39 // invalid sizes 40 {"", 0, io.EOF}, 41 {"0", 0, contractmanager.ErrSmallStorageFolder}, 42 {tooSmallFolderString, modules.SectorSize * (contractmanager.MinimumSectorsPerStorageFolder - 1), contractmanager.ErrSmallStorageFolder}, 43 {tooLargeFolderString, modules.SectorSize * (contractmanager.MaximumSectorsPerStorageFolder + 1), contractmanager.ErrLargeStorageFolder}, 44 45 // valid sizes 46 // 47 // TODO: Re-enable these when the host can support resizing into the 48 // same folder. 49 // 50 // {minFolderSizeString, contractmanager.MinimumSectorsPerStorageFolder * modules.SectorSize, nil}, 51 // {maxFolderSizeString, contractmanager.MaximumSectorsPerStorageFolder * modules.SectorSize, nil}, 52 // {mediumSizeFolderString, 3 * contractmanager.MinimumSectorsPerStorageFolder * modules.SectorSize, nil}, 53 } 54 ) 55 56 // TestEstimateWeight tests that /host/estimatescore works correctly. 57 func TestEstimateWeight(t *testing.T) { 58 if testing.Short() { 59 t.SkipNow() 60 } 61 t.Parallel() 62 63 st, err := createServerTester(t.Name()) 64 if err != nil { 65 t.Fatal(err) 66 } 67 defer st.server.panicClose() 68 69 // announce a host, create an allowance, upload some data. 70 if err := st.setHostStorage(); err != nil { 71 t.Fatal(err) 72 } 73 if err := st.announceHost(); err != nil { 74 t.Fatal(err) 75 } 76 if err := st.acceptContracts(); err != nil { 77 t.Fatal(err) 78 } 79 80 var eg HostEstimateScoreGET 81 if err := st.getAPI("/host/estimatescore", &eg); err != nil { 82 t.Fatal(err) 83 } 84 originalEstimate := eg.EstimatedScore 85 86 // verify that the estimate is being correctly updated by setting a massively 87 // increased min contract price and verifying that the score decreases. 88 is := st.host.InternalSettings() 89 is.MinContractPrice = is.MinContractPrice.Add(types.SiacoinPrecision.Mul64(9999999999)) 90 if err := st.host.SetInternalSettings(is); err != nil { 91 t.Fatal(err) 92 } 93 if err := st.getAPI("/host/estimatescore", &eg); err != nil { 94 t.Fatal(err) 95 } 96 if eg.EstimatedScore.Cmp(originalEstimate) != -1 { 97 t.Fatal("score estimate did not decrease after incrementing mincontractprice") 98 } 99 100 // add a few hosts to the hostdb and verify that the conversion rate is 101 // reflected correctly 102 st2, err := blankServerTester(t.Name() + "-st2") 103 if err != nil { 104 t.Fatal(err) 105 } 106 defer st2.panicClose() 107 st3, err := blankServerTester(t.Name() + "-st3") 108 if err != nil { 109 t.Fatal(err) 110 } 111 defer st3.panicClose() 112 st4, err := blankServerTester(t.Name() + "-st4") 113 if err != nil { 114 t.Fatal(err) 115 } 116 defer st4.panicClose() 117 sts := []*serverTester{st, st2, st3, st4} 118 err = fullyConnectNodes(sts) 119 if err != nil { 120 t.Fatal(err) 121 } 122 err = fundAllNodes(sts) 123 if err != nil { 124 t.Fatal(err) 125 } 126 for i, tester := range sts { 127 is = tester.host.InternalSettings() 128 is.MinContractPrice = types.SiacoinPrecision.Mul64(1000 + (1000 * uint64(i))) 129 err = tester.host.SetInternalSettings(is) 130 if err != nil { 131 t.Fatal(err) 132 } 133 } 134 err = announceAllHosts(sts) 135 if err != nil { 136 t.Fatal(err) 137 } 138 139 tests := []struct { 140 price types.Currency 141 minConversionRate float64 142 }{ 143 {types.SiacoinPrecision, 100}, 144 {types.SiacoinPrecision.Mul64(50), 98}, 145 {types.SiacoinPrecision.Mul64(2500), 10}, 146 {types.SiacoinPrecision.Mul64(3000), 1}, 147 {types.SiacoinPrecision.Mul64(20000), 0.00001}, 148 } 149 t.Log("Estimating conversion rate from changed contract price") 150 t.Logf("Allowance: %+v", st.renter.Settings().Allowance) 151 for i, test := range tests { 152 err = st.getAPI(fmt.Sprintf("/host/estimatescore?mincontractprice=%v", test.price.String()), &eg) 153 if err != nil { 154 t.Fatal("test", i, "failed:", err) 155 } 156 t.Logf("Estimated score = %+v", eg.EstimatedScore) 157 if eg.ConversionRate < test.minConversionRate { 158 t.Errorf("test %v: incorrect conversion rate: got %v wanted %v\n", i, eg.ConversionRate, test.minConversionRate) 159 } else { 160 t.Logf("test %v: conversion rate: got %v expected %v\n", i, eg.ConversionRate, test.minConversionRate) 161 } 162 } 163 } 164 165 // TestHostSettingsHandlerParsing verifies that providing invalid host settings 166 // doesn't reset the host's settings. 167 func TestHostSettingsHandlerParsing(t *testing.T) { 168 if testing.Short() { 169 t.SkipNow() 170 } 171 t.Parallel() 172 173 st, err := createServerTester(t.Name()) 174 if err != nil { 175 t.Fatal(err) 176 } 177 defer st.server.panicClose() 178 179 settings := st.host.InternalSettings() 180 settingsValues := url.Values{} 181 settingsValues.Set("maxdownloadbatchsize", "foo") 182 st.stdPostAPI("/host", settingsValues) 183 newSettings := st.host.InternalSettings() 184 if !reflect.DeepEqual(newSettings, settings) { 185 t.Fatal("invalid acceptingcontracts value changed host settings! got", newSettings, "wanted", settings) 186 } 187 } 188 189 // TestWorkingStatus tests that the host's WorkingStatus field is set 190 // correctly. 191 func TestWorkingStatus(t *testing.T) { 192 if testing.Short() { 193 t.SkipNow() 194 } 195 t.Parallel() 196 197 st, err := createServerTester(t.Name()) 198 if err != nil { 199 t.Fatal(err) 200 } 201 defer st.server.panicClose() 202 203 // announce a host, create an allowance, upload some data. 204 if err := st.setHostStorage(); err != nil { 205 t.Fatal(err) 206 } 207 if err := st.announceHost(); err != nil { 208 t.Fatal(err) 209 } 210 if err := st.acceptContracts(); err != nil { 211 t.Fatal(err) 212 } 213 214 // Set an allowance for the renter, allowing a contract to be formed. 215 allowanceValues := url.Values{} 216 allowanceValues.Set("funds", testFunds) 217 allowanceValues.Set("period", testPeriod) 218 allowanceValues.Set("renewwindow", testRenewWindow) 219 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 220 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 221 t.Fatal(err) 222 } 223 224 // Block until the allowance has finished forming contracts. 225 err = build.Retry(50, time.Millisecond*250, func() error { 226 var rc RenterContracts 227 err = st.getAPI("/renter/contracts", &rc) 228 if err != nil { 229 return errors.New("couldn't get renter stats") 230 } 231 if len(rc.Contracts) != 1 { 232 return errors.New("no contracts") 233 } 234 return nil 235 }) 236 if err != nil { 237 t.Fatal("allowance setting failed") 238 } 239 240 // Create a file. 241 path := filepath.Join(st.dir, "test.dat") 242 fileBytes := 1024 243 if err := createRandFile(path, fileBytes); err != nil { 244 t.Fatal(err) 245 } 246 247 // Upload to host. 248 uploadValues := url.Values{} 249 uploadValues.Set("source", path) 250 if err := st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 251 t.Fatal(err) 252 } 253 254 // Only one piece will be uploaded (10% at current redundancy) 255 var rf RenterFiles 256 for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ { 257 st.getAPI("/renter/files", &rf) 258 time.Sleep(50 * time.Millisecond) 259 } 260 if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 { 261 t.Error(rf.Files[0].UploadProgress) 262 t.Fatal("uploading has failed") 263 } 264 265 err = build.Retry(30, time.Second, func() error { 266 var hg HostGET 267 st.getAPI("/host", &hg) 268 269 if hg.WorkingStatus != modules.HostWorkingStatusWorking { 270 return errors.New("expected host to be working") 271 } 272 return nil 273 }) 274 if err != nil { 275 t.Fatal(err) 276 } 277 } 278 279 // TestConnectabilityStatus tests that the host's ConnectabilityStatus field is 280 // set correctly. 281 func TestConnectabilityStatus(t *testing.T) { 282 if testing.Short() { 283 t.SkipNow() 284 } 285 t.Parallel() 286 287 // create and announce a host 288 st, err := createServerTester(t.Name()) 289 if err != nil { 290 t.Fatal(err) 291 } 292 defer st.server.panicClose() 293 294 if err := st.announceHost(); err != nil { 295 t.Fatal(err) 296 } 297 298 err = build.Retry(30, time.Second, func() error { 299 var hg HostGET 300 st.getAPI("/host", &hg) 301 302 if hg.ConnectabilityStatus != modules.HostConnectabilityStatusConnectable { 303 return errors.New("expected host to be connectable") 304 } 305 return nil 306 }) 307 if err != nil { 308 t.Fatal(err) 309 } 310 } 311 312 // TestStorageHandler tests that host storage is being reported correctly. 313 func TestStorageHandler(t *testing.T) { 314 if testing.Short() { 315 t.SkipNow() 316 } 317 t.Parallel() 318 st, err := createServerTester(t.Name()) 319 if err != nil { 320 t.Fatal(err) 321 } 322 defer st.server.panicClose() 323 324 // Announce the host and start accepting contracts. 325 if err := st.setHostStorage(); err != nil { 326 t.Fatal(err) 327 } 328 if err := st.announceHost(); err != nil { 329 t.Fatal(err) 330 } 331 if err := st.acceptContracts(); err != nil { 332 t.Fatal(err) 333 } 334 335 // Set an allowance for the renter, allowing a contract to be formed. 336 allowanceValues := url.Values{} 337 allowanceValues.Set("funds", testFunds) 338 allowanceValues.Set("period", testPeriod) 339 allowanceValues.Set("renewwindow", testRenewWindow) 340 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 341 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 342 t.Fatal(err) 343 } 344 345 // Block until the allowance has finished forming contracts. 346 err = build.Retry(50, time.Millisecond*250, func() error { 347 var rc RenterContracts 348 err = st.getAPI("/renter/contracts", &rc) 349 if err != nil { 350 return errors.New("couldn't get renter stats") 351 } 352 if len(rc.Contracts) != 1 { 353 return errors.New("no contracts") 354 } 355 return nil 356 }) 357 if err != nil { 358 t.Fatal("allowance setting failed") 359 } 360 361 // Create a file. 362 path := filepath.Join(st.dir, "test.dat") 363 fileBytes := 1024 364 if err := createRandFile(path, fileBytes); err != nil { 365 t.Fatal(err) 366 } 367 368 // Upload to host. 369 uploadValues := url.Values{} 370 uploadValues.Set("source", path) 371 if err := st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 372 t.Fatal(err) 373 } 374 375 // Only one piece will be uploaded (10% at current redundancy) 376 var rf RenterFiles 377 for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ { 378 st.getAPI("/renter/files", &rf) 379 time.Sleep(50 * time.Millisecond) 380 } 381 if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 { 382 t.Error(rf.Files[0].UploadProgress) 383 t.Fatal("uploading has failed") 384 } 385 386 var sg StorageGET 387 if err := st.getAPI("/host/storage", &sg); err != nil { 388 t.Fatal(err) 389 } 390 391 // Uploading succeeded, so /host/storage should be reporting a successful 392 // write. 393 if sg.Folders[0].SuccessfulWrites != 1 { 394 t.Fatalf("expected 1 successful write, got %v", sg.Folders[0].SuccessfulWrites) 395 } 396 if used := sg.Folders[0].Capacity - sg.Folders[0].CapacityRemaining; used != modules.SectorSize { 397 t.Fatalf("expected used capacity to be the size of one sector (%v bytes), got %v bytes", modules.SectorSize, used) 398 } 399 } 400 401 // TestAddFolderNoPath tests that an API call to add a storage folder fails if 402 // no path was provided. 403 func TestAddFolderNoPath(t *testing.T) { 404 if testing.Short() { 405 t.SkipNow() 406 } 407 t.Parallel() 408 st, err := createServerTester(t.Name()) 409 if err != nil { 410 t.Fatal(err) 411 } 412 defer st.server.panicClose() 413 414 // Try adding a storage folder without setting "path" in the API call. 415 addValues := url.Values{} 416 addValues.Set("size", mediumSizeFolderString) 417 err = st.stdPostAPI("/host/storage/folders/add", addValues) 418 if err == nil { 419 t.Fatal(err) 420 } 421 422 // Setting the path to an empty string should trigger the same error. 423 addValues.Set("path", "") 424 err = st.stdPostAPI("/host/storage/folders/add", addValues) 425 if err == nil { 426 t.Fatal(err) 427 } 428 } 429 430 // TestAddFolderNoSize tests that an API call to add a storage folder fails if 431 // no path was provided. 432 func TestAddFolderNoSize(t *testing.T) { 433 if testing.Short() { 434 t.SkipNow() 435 } 436 t.Parallel() 437 st, err := createServerTester(t.Name()) 438 if err != nil { 439 t.Fatal(err) 440 } 441 defer st.server.panicClose() 442 443 // Try adding a storage folder without setting "size" in the API call. 444 addValues := url.Values{} 445 addValues.Set("path", st.dir) 446 err = st.stdPostAPI("/host/storage/folders/add", addValues) 447 if err == nil || err.Error() != io.EOF.Error() { 448 t.Fatalf("expected error to be %v, got %v", io.EOF, err) 449 } 450 } 451 452 // TestAddSameFolderTwice tests that an API call that attempts to add a 453 // host storage folder that's already been added is handled gracefully. 454 func TestAddSameFolderTwice(t *testing.T) { 455 if testing.Short() { 456 t.SkipNow() 457 } 458 t.Parallel() 459 st, err := createServerTester(t.Name()) 460 if err != nil { 461 t.Fatal(err) 462 } 463 defer st.server.panicClose() 464 465 // Make the call to add a storage folder twice. 466 addValues := url.Values{} 467 addValues.Set("path", st.dir) 468 addValues.Set("size", mediumSizeFolderString) 469 err = st.stdPostAPI("/host/storage/folders/add", addValues) 470 if err != nil { 471 t.Fatal(err) 472 } 473 err = st.stdPostAPI("/host/storage/folders/add", addValues) 474 if err == nil || err.Error() != contractmanager.ErrRepeatFolder.Error() { 475 t.Fatalf("expected err to be %v, got %v", err, contractmanager.ErrRepeatFolder) 476 } 477 } 478 479 // TestResizeEmptyStorageFolder tests that invalid and valid calls to resize 480 // an empty storage folder are properly handled. 481 func TestResizeEmptyStorageFolder(t *testing.T) { 482 if testing.Short() { 483 t.SkipNow() 484 } 485 t.Parallel() 486 st, err := createServerTester(t.Name()) 487 if err != nil { 488 t.Fatal(err) 489 } 490 defer st.server.panicClose() 491 492 // Announce the host and start accepting contracts. 493 if err := st.setHostStorage(); err != nil { 494 t.Fatal(err) 495 } 496 if err := st.announceHost(); err != nil { 497 t.Fatal(err) 498 } 499 if err := st.acceptContracts(); err != nil { 500 t.Fatal(err) 501 } 502 503 // Find out how large the host's initial storage folder is. 504 var sg StorageGET 505 if err := st.getAPI("/host/storage", &sg); err != nil { 506 t.Fatal(err) 507 } 508 defaultSize := sg.Folders[0].Capacity 509 // Convert defaultSize (uint64) to a string for the API call. 510 defaultSizeString := strconv.FormatUint(defaultSize, 10) 511 512 resizeValues := url.Values{} 513 resizeValues.Set("path", st.dir) 514 resizeValues.Set("newsize", defaultSizeString) 515 516 // Attempting to resize to the same size should return an error. 517 err = st.stdPostAPI("/host/storage/folders/resize", resizeValues) 518 if err == nil || err.Error() != contractmanager.ErrNoResize.Error() { 519 t.Fatalf("expected error %v, got %v", contractmanager.ErrNoResize, err) 520 } 521 522 // Try resizing to a bunch of sizes (invalid ones first, valid ones second). 523 // This ordering simplifies logic within the for loop. 524 for i, test := range resizeTests { 525 // Attempt to resize the host's storage folder. 526 resizeValues.Set("newsize", test.sizeString) 527 err = st.stdPostAPI("/host/storage/folders/resize", resizeValues) 528 if (err == nil && test.err != nil) || (err != nil && err.Error() != test.err.Error()) { 529 t.Fatalf("test %v: expected error to be %v, got %v", i, test.err, err) 530 } 531 532 // Find out if the resize call worked as expected. 533 if err := st.getAPI("/host/storage", &sg); err != nil { 534 t.Fatal(err) 535 } 536 // If the test size is valid, check that the folder has been resized 537 // properly. 538 if test.err == nil { 539 // Check that the folder's total capacity has been updated. 540 if got := sg.Folders[0].Capacity; got != test.size { 541 t.Fatalf("test %v: expected folder to be resized to %v; got %v instead", i, test.size, got) 542 } 543 // Check that the folder's remaining capacity has been updated. 544 if got := sg.Folders[0].CapacityRemaining; got != test.size { 545 t.Fatalf("folder should be empty, but capacity remaining (%v) != total capacity (%v)", got, test.size) 546 } 547 } else { 548 // If the test size is invalid, the folder should not have been 549 // resized. The invalid test cases are all run before the valid ones, 550 // so the folder size should still be defaultSize. 551 if got := sg.Folders[0].Capacity; got != defaultSize { 552 t.Fatalf("folder was resized to an invalid size (%v) in a test case that should have failed: %v", got, test) 553 } 554 } 555 } 556 } 557 558 // TestResizeNonemptyStorageFolder tests that invalid and valid calls to resize 559 // a storage folder with one sector filled are properly handled. 560 // Ideally, we would also test a very full storage folder (including the case 561 // where the host tries to resize to a size smaller than the amount of data 562 // in the folder), but that would be a very expensive test. 563 func TestResizeNonemptyStorageFolder(t *testing.T) { 564 if testing.Short() { 565 t.SkipNow() 566 } 567 t.Parallel() 568 st, err := createServerTester(t.Name()) 569 if err != nil { 570 t.Fatal(err) 571 } 572 defer st.server.panicClose() 573 574 // Announce the host and start accepting contracts. 575 if err := st.setHostStorage(); err != nil { 576 t.Fatal(err) 577 } 578 if err := st.announceHost(); err != nil { 579 t.Fatal(err) 580 } 581 if err := st.acceptContracts(); err != nil { 582 t.Fatal(err) 583 } 584 585 // Set an allowance for the renter, allowing a contract to be formed. 586 allowanceValues := url.Values{} 587 allowanceValues.Set("funds", testFunds) 588 allowanceValues.Set("period", testPeriod) 589 allowanceValues.Set("renewwindow", testRenewWindow) 590 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 591 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 592 t.Fatal(err) 593 } 594 595 // Block until the allowance has finished forming contracts. 596 err = build.Retry(50, time.Millisecond*250, func() error { 597 var rc RenterContracts 598 err = st.getAPI("/renter/contracts", &rc) 599 if err != nil { 600 return errors.New("couldn't get renter stats") 601 } 602 if len(rc.Contracts) != 1 { 603 return errors.New("no contracts") 604 } 605 return nil 606 }) 607 if err != nil { 608 t.Fatal("allowance setting failed") 609 } 610 611 // Create a file. 612 path := filepath.Join(st.dir, "test.dat") 613 fileBytes := 1024 614 if err := createRandFile(path, fileBytes); err != nil { 615 t.Fatal(err) 616 } 617 618 // Upload to host. 619 uploadValues := url.Values{} 620 uploadValues.Set("source", path) 621 if err := st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 622 t.Fatal(err) 623 } 624 625 // Only one piece will be uploaded (10% at current redundancy) 626 var rf RenterFiles 627 for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ { 628 st.getAPI("/renter/files", &rf) 629 time.Sleep(50 * time.Millisecond) 630 } 631 if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 { 632 t.Error(rf.Files[0].UploadProgress) 633 t.Fatal("uploading has failed") 634 } 635 636 // Find out how large the host's initial storage folder is. 637 var sg StorageGET 638 if err := st.getAPI("/host/storage", &sg); err != nil { 639 t.Fatal(err) 640 } 641 defaultSize := sg.Folders[0].Capacity 642 // Convert defaultSize (uint64) to a string for the API call. 643 defaultSizeString := strconv.FormatUint(defaultSize, 10) 644 645 resizeValues := url.Values{} 646 resizeValues.Set("path", st.dir) 647 resizeValues.Set("newsize", defaultSizeString) 648 649 // Attempting to resize to the same size should return an error. 650 err = st.stdPostAPI("/host/storage/folders/resize", resizeValues) 651 if err == nil || err.Error() != contractmanager.ErrNoResize.Error() { 652 t.Fatalf("expected error %v, got %v", contractmanager.ErrNoResize, err) 653 } 654 655 // Try resizing to a bunch of sizes (invalid ones first, valid ones second). 656 // This ordering simplifies logic within the for loop. 657 for _, test := range resizeTests { 658 // Attempt to resize the host's storage folder. 659 resizeValues.Set("newsize", test.sizeString) 660 err = st.stdPostAPI("/host/storage/folders/resize", resizeValues) 661 if (err == nil && test.err != nil) || (err != nil && test.err == nil) || (err != nil && err.Error() != test.err.Error()) { 662 t.Fatalf("expected error to be %v, got %v", test.err, err) 663 } 664 665 // Find out if the resize call worked as expected. 666 if err := st.getAPI("/host/storage", &sg); err != nil { 667 t.Fatal(err) 668 } 669 // If the test size is valid, check that the folder has been resized 670 // properly. 671 if test.err == nil { 672 // Check that the folder's total capacity has been updated. 673 if sg.Folders[0].Capacity != test.size { 674 t.Fatalf("expected folder to be resized to %v; got %v instead", test.size, sg.Folders[0].Capacity) 675 } 676 // Since one sector has been uploaded, the available capacity 677 // should be one sector size smaller than the total capacity. 678 if used := test.size - sg.Folders[0].CapacityRemaining; used != modules.SectorSize { 679 t.Fatalf("used capacity (%v) != the size of 1 sector (%v)", used, modules.SectorSize) 680 } 681 } else { 682 // If the test size is invalid, the folder should not have been 683 // resized. The invalid test cases are all run before the valid 684 // ones, so the folder size should still be defaultSize. 685 if got := sg.Folders[0].Capacity; got != defaultSize { 686 t.Fatalf("folder was resized to an invalid size (%v) in a test case that should have failed: %v", got, test) 687 } 688 } 689 } 690 } 691 692 // TestResizeNonexistentFolder checks that an API call to resize a nonexistent 693 // folder triggers the appropriate error. 694 func TestResizeNonexistentFolder(t *testing.T) { 695 if testing.Short() { 696 t.SkipNow() 697 } 698 t.Parallel() 699 st, err := createServerTester(t.Name()) 700 if err != nil { 701 t.Fatal(err) 702 } 703 defer st.server.panicClose() 704 705 // No folder has been created yet at st.dir, so using it as the path for 706 // the resize call should trigger an error. 707 resizeValues := url.Values{} 708 resizeValues.Set("path", st.dir) 709 resizeValues.Set("newsize", mediumSizeFolderString) 710 err = st.stdPostAPI("/host/storage/folders/resize", resizeValues) 711 if err == nil || err.Error() != errStorageFolderNotFound.Error() { 712 t.Fatalf("expected error to be %v, got %v", errStorageFolderNotFound, err) 713 } 714 } 715 716 // TestStorageFolderUnavailable simulates the situation where a storage folder 717 // is not available to the host when the host starts, verifying that it sets 718 // FailedWrites and FailedReads correctly and eventually finds the storage 719 // folder when it is made available to the host again. 720 func TestStorageFolderUnavailable(t *testing.T) { 721 if testing.Short() || !build.VLONG { 722 t.SkipNow() 723 } 724 t.Parallel() 725 726 st, err := createServerTester(t.Name()) 727 if err != nil { 728 t.Fatal(err) 729 } 730 defer st.server.Close() 731 732 // add a storage folder 733 sfPath := build.TempDir(t.Name(), "storagefolder") 734 err = os.MkdirAll(sfPath, 0755) 735 if err != nil { 736 t.Fatal(err) 737 } 738 sfValues := url.Values{} 739 sfValues.Set("path", sfPath) 740 sfValues.Set("size", "1048576") 741 err = st.stdPostAPI("/host/storage/folders/add", sfValues) 742 if err != nil { 743 t.Fatal(err) 744 } 745 746 var sfs StorageGET 747 err = st.getAPI("/host/storage", &sfs) 748 if err != nil { 749 t.Fatal(err) 750 } 751 752 if sfs.Folders[0].FailedReads != 0 || sfs.Folders[0].FailedWrites != 0 { 753 t.Fatal("newly added folder has failed reads or writes") 754 } 755 756 // remove the folder on disk 757 st.server.Close() 758 sfPath2 := build.TempDir(t.Name(), "storagefolder-old") 759 err = os.Rename(sfPath, sfPath2) 760 if err != nil { 761 t.Fatal(err) 762 } 763 764 // reload the host 765 st, err = st.reloadedServerTester() 766 if err != nil { 767 t.Fatal(err) 768 } 769 defer st.server.Close() 770 771 err = st.getAPI("/host/storage", &sfs) 772 if err != nil { 773 t.Fatal(err) 774 } 775 if sfs.Folders[0].FailedWrites < 999 { 776 t.Fatal("storage folder should have lots of failed writes after being moved on disk") 777 } 778 if sfs.Folders[0].FailedReads < 999 { 779 t.Fatal("storage folder should have lots of failed reads after being moved on disk") 780 } 781 782 // try some actions on the dead storage folder 783 // resize 784 sfValues.Set("size", "2097152") 785 err = st.stdPostAPI("/host/storage/folders/resize", sfValues) 786 if err == nil { 787 t.Fatal("expected resize on unavailable storage folder to fail") 788 } 789 // remove 790 err = st.stdPostAPI("/host/storage/folders/remove", sfValues) 791 if err == nil { 792 t.Fatal("expected remove on unavailable storage folder to fail") 793 } 794 795 // move the folder back 796 err = os.Rename(sfPath2, sfPath) 797 if err != nil { 798 t.Fatal(err) 799 } 800 801 // wait for the contract manager to recheck the storage folder 802 // NOTE: this is a hard-coded constant based on the contractmanager's maxFolderRecheckInterval constant. 803 time.Sleep(time.Second * 10) 804 805 // verify the storage folder is reset to normal 806 err = st.getAPI("/host/storage", &sfs) 807 if err != nil { 808 t.Fatal(err) 809 } 810 if sfs.Folders[0].FailedWrites > 0 { 811 t.Fatal("storage folder should have no failed writes after being moved back") 812 } 813 if sfs.Folders[0].FailedReads > 0 { 814 t.Fatal("storage folder should have no failed reads after being moved back") 815 } 816 817 // reload the host and verify the storage folder is still good 818 st.server.Close() 819 st, err = st.reloadedServerTester() 820 if err != nil { 821 t.Fatal(err) 822 } 823 defer st.server.Close() 824 825 // storage folder should still be good 826 err = st.getAPI("/host/storage", &sfs) 827 if err != nil { 828 t.Fatal(err) 829 } 830 if sfs.Folders[0].FailedWrites > 0 { 831 t.Fatal("storage folder should have no failed writes after being moved back") 832 } 833 if sfs.Folders[0].FailedReads > 0 { 834 t.Fatal("storage folder should have no failed reads after being moved back") 835 } 836 } 837 838 // TestResizeFolderNoPath checks that an API call to resize a storage folder fails 839 // if no path was provided. 840 func TestResizeFolderNoPath(t *testing.T) { 841 if testing.Short() { 842 t.SkipNow() 843 } 844 t.Parallel() 845 st, err := createServerTester(t.Name()) 846 if err != nil { 847 t.Fatal(err) 848 } 849 defer st.server.panicClose() 850 851 // The call to resize should fail if no path has been provided. 852 resizeValues := url.Values{} 853 resizeValues.Set("newsize", mediumSizeFolderString) 854 err = st.stdPostAPI("/host/storage/folders/resize", resizeValues) 855 if err == nil || err.Error() != errNoPath.Error() { 856 t.Fatalf("expected error to be %v; got %v", errNoPath, err) 857 } 858 } 859 860 // TestRemoveEmptyStorageFolder checks that removing an empty storage folder 861 // succeeds -- even if the host is left with zero storage folders. 862 func TestRemoveEmptyStorageFolder(t *testing.T) { 863 if testing.Short() { 864 t.SkipNow() 865 } 866 t.Parallel() 867 st, err := createServerTester(t.Name()) 868 if err != nil { 869 t.Fatal(err) 870 } 871 defer st.server.panicClose() 872 873 // Set up a storage folder for the host. 874 if err := st.setHostStorage(); err != nil { 875 t.Fatal(err) 876 } 877 878 // Try to delete the host's empty storage folder. 879 removeValues := url.Values{} 880 removeValues.Set("path", st.dir) 881 if err = st.stdPostAPI("/host/storage/folders/remove", removeValues); err != nil { 882 t.Fatal(err) 883 } 884 } 885 886 // TestRemoveStorageFolderError checks that invalid calls to 887 // /host/storage/folders/remove fail with the appropriate error. 888 func TestRemoveStorageFolderError(t *testing.T) { 889 if testing.Short() { 890 t.SkipNow() 891 } 892 t.Parallel() 893 st, err := createServerTester(t.Name()) 894 if err != nil { 895 t.Fatal(err) 896 } 897 defer st.server.panicClose() 898 899 // Set up a storage folder for the host. 900 if err := st.setHostStorage(); err != nil { 901 t.Fatal(err) 902 } 903 904 // Try removing a nonexistent folder. 905 removeValues := url.Values{} 906 removeValues.Set("path", "/foo/bar") 907 err = st.stdPostAPI("/host/storage/folders/remove", removeValues) 908 if err == nil || err.Error() != errStorageFolderNotFound.Error() { 909 t.Fatalf("expected error %v, got %v", errStorageFolderNotFound, err) 910 } 911 912 // The folder path can't be an empty string. 913 removeValues.Set("path", "") 914 err = st.stdPostAPI("/host/storage/folders/remove", removeValues) 915 if err == nil || err.Error() != errNoPath.Error() { 916 t.Fatalf("expected error to be %v; got %v", errNoPath, err) 917 } 918 } 919 920 // TestRemoveStorageFolderForced checks that if a call to remove a storage 921 // folder will result in data loss, that call succeeds if and only if "force" 922 // has been set to "true". 923 func TestRemoveStorageFolderForced(t *testing.T) { 924 if testing.Short() { 925 t.SkipNow() 926 } 927 t.Parallel() 928 st, err := createServerTester(t.Name()) 929 if err != nil { 930 t.Fatal(err) 931 } 932 defer st.server.panicClose() 933 934 // Announce the host. 935 if err := st.setHostStorage(); err != nil { 936 t.Fatal(err) 937 } 938 if err := st.announceHost(); err != nil { 939 t.Fatal(err) 940 } 941 if err := st.acceptContracts(); err != nil { 942 t.Fatal(err) 943 } 944 945 // Set an allowance for the renter, allowing a contract to be formed. 946 allowanceValues := url.Values{} 947 allowanceValues.Set("funds", testFunds) 948 allowanceValues.Set("period", testPeriod) 949 allowanceValues.Set("renewwindow", testRenewWindow) 950 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 951 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 952 t.Fatal(err) 953 } 954 955 // Block until the allowance has finished forming contracts. 956 err = build.Retry(50, time.Millisecond*250, func() error { 957 var rc RenterContracts 958 err = st.getAPI("/renter/contracts", &rc) 959 if err != nil { 960 return errors.New("couldn't get renter stats") 961 } 962 if len(rc.Contracts) != 1 { 963 return errors.New("no contracts") 964 } 965 return nil 966 }) 967 if err != nil { 968 t.Fatal("allowance setting failed") 969 } 970 971 // Create a file for upload. 972 path := filepath.Join(st.dir, "test.dat") 973 if err := createRandFile(path, 512); err != nil { 974 t.Fatal(err) 975 } 976 // Upload to host. 977 uploadValues := url.Values{} 978 uploadValues.Set("source", path) 979 if err := st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 980 t.Fatal(err) 981 } 982 983 // Only one piece will be uploaded (10% at current redundancy) 984 var rf RenterFiles 985 for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ { 986 st.getAPI("/renter/files", &rf) 987 time.Sleep(50 * time.Millisecond) 988 } 989 if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 { 990 t.Error(rf.Files[0].UploadProgress) 991 t.Fatal("uploading has failed") 992 } 993 994 // The host should not be able to remove its only folder without setting 995 // "force" to "true", since this will result in data loss (there are no 996 // other folders for the data to be redistributed to). 997 removeValues := url.Values{} 998 removeValues.Set("path", st.dir) 999 err = st.stdPostAPI("/host/storage/folders/remove", removeValues) 1000 if err == nil || err.Error() != contractmanager.ErrPartialRelocation.Error() { 1001 t.Fatalf("expected err to be %v; got %v", contractmanager.ErrPartialRelocation, err) 1002 } 1003 // Forced removal of the folder should succeed, though. 1004 removeValues.Set("force", "true") 1005 err = st.stdPostAPI("/host/storage/folders/remove", removeValues) 1006 if err != nil { 1007 t.Fatal(err) 1008 } 1009 } 1010 1011 // TestDeleteSector tests the call to delete a storage sector from the host. 1012 func TestDeleteSector(t *testing.T) { 1013 t.Skip("broken because Merkle roots are no longer exposed") 1014 if testing.Short() { 1015 t.SkipNow() 1016 } 1017 t.Parallel() 1018 st, err := createServerTester(t.Name()) 1019 if err != nil { 1020 t.Fatal(err) 1021 } 1022 defer st.server.panicClose() 1023 1024 // Set up the host for forming contracts. 1025 if err := st.setHostStorage(); err != nil { 1026 t.Fatal(err) 1027 } 1028 if err := st.announceHost(); err != nil { 1029 t.Fatal(err) 1030 } 1031 if err := st.acceptContracts(); err != nil { 1032 t.Fatal(err) 1033 } 1034 1035 // Set an allowance for the renter, allowing contracts to formed. 1036 allowanceValues := url.Values{} 1037 allowanceValues.Set("funds", testFunds) 1038 allowanceValues.Set("period", testPeriod) 1039 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 1040 t.Fatal(err) 1041 } 1042 1043 // Block until the allowance has finished forming contracts. 1044 err = build.Retry(50, time.Millisecond*250, func() error { 1045 var rc RenterContracts 1046 err = st.getAPI("/renter/contracts", &rc) 1047 if err != nil { 1048 return errors.New("couldn't get renter stats") 1049 } 1050 if len(rc.Contracts) != 1 { 1051 return errors.New("no contracts") 1052 } 1053 return nil 1054 }) 1055 if err != nil { 1056 t.Fatal("allowance setting failed") 1057 } 1058 1059 // Create a file. 1060 path := filepath.Join(st.dir, "test.dat") 1061 if err := createRandFile(path, 1024); err != nil { 1062 t.Fatal(err) 1063 } 1064 1065 // Upload to host. 1066 uploadValues := url.Values{} 1067 uploadValues.Set("source", path) 1068 if err = st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 1069 t.Fatal(err) 1070 } 1071 1072 // Only one piece will be uploaded (10% at current redundancy) 1073 var rf RenterFiles 1074 for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ { 1075 st.getAPI("/renter/files", &rf) 1076 time.Sleep(50 * time.Millisecond) 1077 } 1078 if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 { 1079 t.Error(rf.Files[0].UploadProgress) 1080 t.Fatal("uploading has failed") 1081 } 1082 1083 // Get the Merkle root of the piece that was uploaded. 1084 contracts := st.renter.Contracts() 1085 if len(contracts) != 1 { 1086 t.Fatalf("expected exactly 1 contract to have been formed; got %v instead", len(contracts)) 1087 } 1088 // if len(contracts[0].MerkleRoots) < 1 { 1089 // t.Fatal("expected at least one merkle root") 1090 // } 1091 // sectorRoot := contracts[0].MerkleRoots[0].String() 1092 1093 // if err = st.stdPostAPI("/host/storage/sectors/delete/"+sectorRoot, url.Values{}); err != nil { 1094 // t.Fatal(err) 1095 // } 1096 } 1097 1098 // TestDeleteNonexistentSector checks that attempting to delete a storage 1099 // sector that doesn't exist will fail with the appropriate error. 1100 func TestDeleteNonexistentSector(t *testing.T) { 1101 if testing.Short() { 1102 t.SkipNow() 1103 } 1104 t.Parallel() 1105 st, err := createServerTester(t.Name()) 1106 if err != nil { 1107 t.Fatal(err) 1108 } 1109 defer st.server.panicClose() 1110 1111 // These calls to delete imaginary sectors should fail for a few reasons: 1112 // - the given sector root strings are invalid 1113 // - the renter hasn't uploaded anything 1114 // - the host has no storage folders yet 1115 // Right now, the calls fail for the first reason. This test will report if that behavior changes. 1116 badHash := crypto.HashObject("fake object").String() 1117 err = st.stdPostAPI("/host/storage/sectors/delete/"+badHash, url.Values{}) 1118 if err == nil || err.Error() != contractmanager.ErrSectorNotFound.Error() { 1119 t.Fatalf("expected error to be %v; got %v", contractmanager.ErrSectorNotFound, err) 1120 } 1121 wrongSize := "wrong size string" 1122 err = st.stdPostAPI("/host/storage/sectors/delete/"+wrongSize, url.Values{}) 1123 if err == nil || err.Error() != crypto.ErrHashWrongLen.Error() { 1124 t.Fatalf("expected error to be %v; got %v", crypto.ErrHashWrongLen, err) 1125 } 1126 }