gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/renter_test.go (about) 1 package api 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/url" 9 "os" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "testing" 14 "time" 15 16 "gitlab.com/NebulousLabs/errors" 17 "gitlab.com/NebulousLabs/fastrand" 18 19 "gitlab.com/SiaPrime/SiaPrime/build" 20 "gitlab.com/SiaPrime/SiaPrime/modules" 21 "gitlab.com/SiaPrime/SiaPrime/modules/renter/contractor" 22 "gitlab.com/SiaPrime/SiaPrime/modules/renter/siafile" 23 "gitlab.com/SiaPrime/SiaPrime/types" 24 ) 25 26 const ( 27 testFunds = "10000000000000000000000000000" // 10k SC 28 testPeriod = "5" 29 testRenewWindow = "2" 30 ) 31 32 // createRandFile creates a file on disk and fills it with random bytes. 33 func createRandFile(path string, size int) error { 34 return ioutil.WriteFile(path, fastrand.Bytes(size), 0600) 35 } 36 37 // setupTestDownload creates a server tester with an uploaded file of size 38 // `size` and name `name`. 39 func setupTestDownload(t *testing.T, size int, name string, waitOnRedundancy bool) (*serverTester, string) { 40 st, err := createServerTester(t.Name()) 41 if err != nil { 42 t.Fatal(err) 43 } 44 45 // Announce the host and start accepting contracts. 46 err = st.setHostStorage() 47 if err != nil { 48 t.Fatal(err) 49 } 50 err = st.announceHost() 51 if err != nil { 52 t.Fatal(err) 53 } 54 err = st.acceptContracts() 55 if err != nil { 56 t.Fatal(err) 57 } 58 59 // Set an allowance for the renter, allowing a contract to be formed. 60 allowanceValues := url.Values{} 61 testFunds := testFunds 62 testPeriod := "10" 63 renewWindow := "5" 64 allowanceValues.Set("funds", testFunds) 65 allowanceValues.Set("period", testPeriod) 66 allowanceValues.Set("renewwindow", renewWindow) 67 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 68 err = st.stdPostAPI("/renter", allowanceValues) 69 if err != nil { 70 t.Fatal(err) 71 } 72 73 // Create a file. 74 path := filepath.Join(build.SiaTestingDir, "api", t.Name(), name) 75 err = createRandFile(path, size) 76 if err != nil { 77 t.Fatal(err) 78 } 79 80 // Upload to host. 81 uploadValues := url.Values{} 82 uploadValues.Set("source", path) 83 uploadValues.Set("renew", "true") 84 uploadValues.Set("datapieces", "1") 85 uploadValues.Set("paritypieces", "1") 86 err = st.stdPostAPI("/renter/upload/"+name, uploadValues) 87 if err != nil { 88 t.Fatal(err) 89 } 90 91 if waitOnRedundancy { 92 // wait for the file to have a redundancy > 1 93 err = build.Retry(200, time.Second, func() error { 94 var rf RenterFile 95 st.getAPI("/renter/file/"+name, &rf) 96 if rf.File.Redundancy < 1 { 97 return fmt.Errorf("the uploading is not succeeding for some reason: %v", rf.File) 98 } 99 return nil 100 }) 101 if err != nil { 102 t.Fatal(err) 103 } 104 } 105 106 return st, path 107 } 108 109 // runDownloadTest uploads a file and downloads it using the specified 110 // parameters, verifying that the parameters are applied correctly and the file 111 // is downloaded successfully. 112 func runDownloadTest(t *testing.T, filesize, offset, length int64, useHttpResp bool, testName string) error { 113 ulSiaPath, err := modules.NewSiaPath(testName + ".dat") 114 if err != nil { 115 t.Fatal(err) 116 } 117 st, path := setupTestDownload(t, int(filesize), ulSiaPath.String(), true) 118 defer func() { 119 st.server.panicClose() 120 os.Remove(path) 121 }() 122 123 // Read the section to be downloaded from the original file. 124 uf, err := os.Open(path) // Uploaded file. 125 if err != nil { 126 return errors.AddContext(err, "unable to open the uploaded file locally") 127 } 128 var originalBytes bytes.Buffer 129 _, err = uf.Seek(offset, 0) 130 if err != nil { 131 return errors.AddContext(err, "error when seeking through the local uploaded file") 132 } 133 _, err = io.CopyN(&originalBytes, uf, length) 134 if err != nil { 135 return errors.AddContext(err, "error when copying from the local uploaded file") 136 } 137 138 // Download the original file from the passed offsets. 139 fname := testName + "-download.dat" 140 downpath := filepath.Join(st.dir, fname) 141 defer os.Remove(downpath) 142 143 dlURL := fmt.Sprintf("/renter/download/%s?offset=%d&length=%d", ulSiaPath, offset, length) 144 145 var downbytes bytes.Buffer 146 147 if useHttpResp { 148 dlURL += "&httpresp=true" 149 // Make request. 150 resp, err := HttpGET("http://" + st.server.listener.Addr().String() + dlURL) 151 if err != nil { 152 return errors.AddContext(err, "unable to make an http request") 153 } 154 defer resp.Body.Close() 155 if non2xx(resp.StatusCode) { 156 return decodeError(resp) 157 } 158 _, err = io.Copy(&downbytes, resp.Body) 159 if err != nil { 160 return errors.AddContext(err, "unable to make a copy after the http request") 161 } 162 } else { 163 dlURL += "&destination=" + downpath 164 err := st.getAPI(dlURL, nil) 165 if err != nil { 166 return errors.AddContext(err, "download request failed") 167 } 168 // wait for the download to complete 169 err = build.Retry(30, time.Second, func() error { 170 var rdq RenterDownloadQueue 171 err = st.getAPI("/renter/downloads", &rdq) 172 if err != nil { 173 return errors.AddContext(err, "unable to view the download queue") 174 } 175 for _, download := range rdq.Downloads { 176 if download.Received == download.Filesize && download.SiaPath.Equals(ulSiaPath) { 177 return nil 178 } 179 } 180 return errors.New("file not downloaded") 181 }) 182 if err != nil { 183 t.Fatal(errors.AddContext(err, "download does not appear to have completed")) 184 } 185 186 // open the downloaded file 187 df, err := os.Open(downpath) 188 if err != nil { 189 return err 190 } 191 defer df.Close() 192 193 _, err = io.Copy(&downbytes, df) 194 if err != nil { 195 return err 196 } 197 } 198 199 // should have correct length 200 if int64(downbytes.Len()) != length { 201 return fmt.Errorf("downloaded file has incorrect size: %d, %d expected", downbytes.Len(), length) 202 } 203 204 // should be byte-for-byte equal to the original uploaded file 205 if !bytes.Equal(originalBytes.Bytes(), downbytes.Bytes()) { 206 return fmt.Errorf("downloaded content differs from original content") 207 } 208 209 return nil 210 } 211 212 // TestRenterDownloadError tests that the /renter/download route sets the 213 // download's error field if it fails. 214 func TestRenterDownloadError(t *testing.T) { 215 if testing.Short() { 216 t.SkipNow() 217 } 218 t.Parallel() 219 220 st, _ := setupTestDownload(t, 1e4, "test.dat", false) 221 defer st.server.Close() 222 223 // don't wait for the upload to complete, try to download immediately to 224 // intentionally cause a download error 225 downpath := filepath.Join(st.dir, "down.dat") 226 expectedErr := st.getAPI("/renter/download/test.dat?destination="+downpath, nil) 227 if expectedErr == nil { 228 t.Fatal("download unexpectedly succeeded") 229 } 230 231 // verify the file has the expected error 232 var rdq RenterDownloadQueue 233 err := st.getAPI("/renter/downloads", &rdq) 234 if err != nil { 235 t.Fatal(err) 236 } 237 for _, download := range rdq.Downloads { 238 if download.SiaPath.String() == "test.dat" && download.Received == download.Filesize && download.Error == expectedErr.Error() { 239 t.Fatal("download had unexpected error: ", download.Error) 240 } 241 } 242 } 243 244 // TestValidDownloads tests valid and boundary parameter combinations. 245 func TestValidDownloads(t *testing.T) { 246 if testing.Short() { 247 t.SkipNow() 248 } 249 t.Parallel() 250 251 sectorSize := int64(modules.SectorSize) 252 253 testParams := []struct { 254 filesize, 255 offset, 256 length int64 257 useHttpResp bool 258 testName string 259 }{ 260 // file-backed tests. 261 {sectorSize, 40, sectorSize - 40, false, "OffsetSingleChunk"}, 262 {sectorSize * 2, 20, sectorSize*2 - 20, false, "OffsetTwoChunk"}, 263 {int64(float64(sectorSize) * 2.4), 20, int64(float64(sectorSize)*2.4) - 20, false, "OffsetThreeChunk"}, 264 {sectorSize, 0, sectorSize / 2, false, "ShortLengthSingleChunk"}, 265 {sectorSize, sectorSize / 4, sectorSize / 2, false, "ShortLengthAndOffsetSingleChunk"}, 266 {sectorSize * 2, 0, int64(float64(sectorSize) * 2 * 0.75), false, "ShortLengthTwoChunk"}, 267 {int64(float64(sectorSize) * 2.7), 0, int64(2.2 * float64(sectorSize)), false, "ShortLengthThreeChunkInThirdChunk"}, 268 {int64(float64(sectorSize) * 2.7), 0, int64(1.6 * float64(sectorSize)), false, "ShortLengthThreeChunkInSecondChunk"}, 269 {sectorSize * 5, 0, int64(float64(sectorSize*5) * 0.75), false, "ShortLengthMultiChunk"}, 270 {sectorSize * 2, 50, int64(float64(sectorSize*2) * 0.75), false, "ShortLengthAndOffsetTwoChunk"}, 271 {sectorSize * 3, 50, int64(float64(sectorSize*3) * 0.5), false, "ShortLengthAndOffsetThreeChunkInSecondChunk"}, 272 {sectorSize * 3, 50, int64(float64(sectorSize*3) * 0.75), false, "ShortLengthAndOffsetThreeChunkInThirdChunk"}, 273 274 // http response tests. 275 {sectorSize, 40, sectorSize - 40, true, "HttpRespOffsetSingleChunk"}, 276 {sectorSize * 2, 40, sectorSize*2 - 40, true, "HttpRespOffsetTwoChunk"}, 277 {sectorSize * 5, 40, sectorSize*5 - 40, true, "HttpRespOffsetManyChunks"}, 278 {sectorSize, 40, 4 * sectorSize / 5, true, "RespOffsetAndLengthSingleChunk"}, 279 {sectorSize * 2, 80, 3 * (sectorSize * 2) / 4, true, "RespOffsetAndLengthTwoChunk"}, 280 {sectorSize * 5, 150, 3 * (sectorSize * 5) / 4, true, "HttpRespOffsetAndLengthManyChunks"}, 281 {sectorSize * 5, 150, sectorSize * 5 / 4, true, "HttpRespOffsetAndLengthManyChunksSubsetOfChunks"}, 282 } 283 for _, params := range testParams { 284 t.Run(params.testName, func(st *testing.T) { 285 st.Parallel() 286 err := runDownloadTest(st, params.filesize, params.offset, params.length, params.useHttpResp, params.testName) 287 if err != nil { 288 st.Fatal(err) 289 } 290 }) 291 } 292 } 293 294 func runDownloadParamTest(t *testing.T, length, offset, filesize int) error { 295 ulSiaPath := "test.dat" 296 297 st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true) 298 defer st.server.Close() 299 300 // Download the original file from offset 40 and length 10. 301 fname := "offsetsinglechunk.dat" 302 downpath := filepath.Join(st.dir, fname) 303 dlURL := fmt.Sprintf("/renter/download/%s?destination=%s", ulSiaPath, downpath) 304 dlURL += fmt.Sprintf("&length=%d", length) 305 dlURL += fmt.Sprintf("&offset=%d", offset) 306 return st.getAPI(dlURL, nil) 307 } 308 309 func TestInvalidDownloadParameters(t *testing.T) { 310 if testing.Short() || !build.VLONG { 311 t.SkipNow() 312 } 313 t.Parallel() 314 315 testParams := []struct { 316 length int 317 offset int 318 filesize int 319 errorMsg string 320 }{ 321 {0, -10, 1e4, "/download not prompting error when passing negative offset."}, 322 {0, 1e4, 1e4, "/download not prompting error when passing offset equal to filesize."}, 323 {1e4 + 1, 0, 1e4, "/download not prompting error when passing length exceeding filesize."}, 324 {1e4 + 11, 10, 1e4, "/download not prompting error when passing length exceeding filesize with non-zero offset."}, 325 {-1, 0, 1e4, "/download not prompting error when passing negative length."}, 326 } 327 328 for _, params := range testParams { 329 err := runDownloadParamTest(t, params.length, params.offset, params.filesize) 330 if err == nil { 331 t.Fatal(params.errorMsg) 332 } 333 } 334 } 335 336 func TestRenterDownloadAsyncAndHttpRespError(t *testing.T) { 337 if testing.Short() { 338 t.SkipNow() 339 } 340 t.Parallel() 341 342 filesize := 1e4 343 ulSiaPath := "test.dat" 344 345 st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true) 346 defer st.server.Close() 347 348 // Download the original file from offset 40 and length 10. 349 fname := "offsetsinglechunk.dat" 350 dlURL := fmt.Sprintf("/renter/download/%s?destination=%s&async=true&httpresp=true", ulSiaPath, fname) 351 err := st.getAPI(dlURL, nil) 352 if err == nil { 353 t.Fatalf("/download not prompting error when only passing both async and httpresp fields.") 354 } 355 } 356 357 func TestRenterDownloadAsyncNonexistentFile(t *testing.T) { 358 if testing.Short() { 359 t.SkipNow() 360 } 361 t.Parallel() 362 363 st, err := createServerTester(t.Name()) 364 if err != nil { 365 t.Fatal(err) 366 } 367 defer st.server.Close() 368 369 downpath := filepath.Join(st.dir, "testfile") 370 err = st.getAPI(fmt.Sprintf("/renter/downloadasync/doesntexist?destination=%v", downpath), nil) 371 if err == nil { 372 t.Error("should not be able to download a file that does not exist") 373 } 374 } 375 376 func TestRenterDownloadAsyncAndNotDestinationError(t *testing.T) { 377 if testing.Short() { 378 t.SkipNow() 379 } 380 t.Parallel() 381 382 filesize := 1e4 383 ulSiaPath := "test.dat" 384 385 st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true) 386 defer st.server.Close() 387 388 // Download the original file from offset 40 and length 10. 389 dlURL := fmt.Sprintf("/renter/download/%s?async=true", ulSiaPath) 390 err := st.getAPI(dlURL, nil) 391 if err == nil { 392 t.Fatal("/download not prompting error when async is specified but destination is empty.") 393 } 394 } 395 396 func TestRenterDownloadHttpRespAndDestinationError(t *testing.T) { 397 if testing.Short() { 398 t.SkipNow() 399 } 400 t.Parallel() 401 402 filesize := 1e4 403 ulSiaPath := "test.dat" 404 405 st, _ := setupTestDownload(t, int(filesize), ulSiaPath, true) 406 defer st.server.Close() 407 408 // Download the original file from offset 40 and length 10. 409 fname := "test.dat" 410 dlURL := fmt.Sprintf("/renter/download/%s?destination=%shttpresp=true", ulSiaPath, fname) 411 err := st.getAPI(dlURL, nil) 412 if err == nil { 413 t.Fatal("/download not prompting error when httpresp is specified and destination is non-empty.") 414 } 415 } 416 417 // TestRenterAsyncDownloadError tests that the /renter/asyncdownload route sets 418 // the download's error field if it fails. 419 func TestRenterAsyncDownloadError(t *testing.T) { 420 if testing.Short() { 421 t.SkipNow() 422 } 423 t.Parallel() 424 425 st, _ := setupTestDownload(t, 1e4, "test.dat", false) 426 defer st.server.panicClose() 427 428 // don't wait for the upload to complete, try to download immediately to 429 // intentionally cause a download error 430 downpath := filepath.Join(st.dir, "asyncdown.dat") 431 st.getAPI("/renter/downloadasync/test.dat?destination="+downpath, nil) 432 433 // verify the file has an error 434 var rdq RenterDownloadQueue 435 err := st.getAPI("/renter/downloads", &rdq) 436 if err != nil { 437 t.Fatal(err) 438 } 439 for _, download := range rdq.Downloads { 440 if download.SiaPath.String() == "test.dat" && download.Received == download.Filesize && download.Error == "" { 441 t.Fatal("download had nil error") 442 } 443 } 444 } 445 446 // TestRenterAsyncDownload tests that the /renter/downloadasync route works 447 // correctly. 448 func TestRenterAsyncDownload(t *testing.T) { 449 if testing.Short() { 450 t.SkipNow() 451 } 452 t.Parallel() 453 454 st, _ := setupTestDownload(t, 1e4, "test.dat", true) 455 defer st.server.panicClose() 456 457 // Download the file asynchronously. 458 downpath := filepath.Join(st.dir, "asyncdown.dat") 459 err := st.getAPI("/renter/downloadasync/test.dat?destination="+downpath, nil) 460 if err != nil { 461 t.Fatal(err) 462 } 463 464 // download should eventually complete 465 var rdq RenterDownloadQueue 466 success := false 467 for start := time.Now(); time.Since(start) < 30*time.Second; time.Sleep(time.Millisecond * 10) { 468 err = st.getAPI("/renter/downloads", &rdq) 469 if err != nil { 470 t.Fatal(err) 471 } 472 for _, download := range rdq.Downloads { 473 if download.Received == download.Filesize && download.SiaPath.String() == "test.dat" { 474 success = true 475 } 476 } 477 if success { 478 break 479 } 480 } 481 if !success { 482 t.Fatal("/renter/downloadasync did not download our test file") 483 } 484 } 485 486 // TestRenterPaths tests that the /renter routes handle path parameters 487 // properly. 488 func TestRenterPaths(t *testing.T) { 489 if testing.Short() { 490 t.SkipNow() 491 } 492 t.Parallel() 493 st, err := createServerTester(t.Name()) 494 if err != nil { 495 t.Fatal(err) 496 } 497 defer st.server.panicClose() 498 499 // Announce the host. 500 err = st.announceHost() 501 if err != nil { 502 t.Fatal(err) 503 } 504 505 // Create a file. 506 path := filepath.Join(build.SiaTestingDir, "api", t.Name(), "test.dat") 507 err = createRandFile(path, 1024) 508 if err != nil { 509 t.Fatal(err) 510 } 511 512 // Upload to host. 513 uploadValues := url.Values{} 514 uploadValues.Set("source", path) 515 uploadValues.Set("renew", "true") 516 err = st.stdPostAPI("/renter/upload/foo/bar/test", uploadValues) 517 if err != nil { 518 t.Fatal(err) 519 } 520 521 // File should be listed by the renter. 522 var rf RenterFiles 523 err = st.getAPI("/renter/files", &rf) 524 if err != nil { 525 t.Fatal(err) 526 } 527 if len(rf.Files) != 1 || rf.Files[0].SiaPath.String() != "foo/bar/test" { 528 t.Fatal("/renter/files did not return correct file:", rf) 529 } 530 } 531 532 // TestRenterConflicts tests that the renter handles naming conflicts properly. 533 func TestRenterConflicts(t *testing.T) { 534 if testing.Short() { 535 t.SkipNow() 536 } 537 t.Parallel() 538 st, err := createServerTester(t.Name()) 539 if err != nil { 540 t.Fatal(err) 541 } 542 defer st.server.panicClose() 543 544 // Announce the host. 545 err = st.announceHost() 546 if err != nil { 547 t.Fatal(err) 548 } 549 550 // Create a file. 551 path := filepath.Join(build.SiaTestingDir, "api", t.Name(), "test.dat") 552 err = createRandFile(path, 1024) 553 if err != nil { 554 t.Fatal(err) 555 } 556 557 // Upload to host, using a path designed to cause conflicts. The renter 558 // should automatically create a folder called foo/bar.sia. Later, we'll 559 // exploit this by uploading a file called foo/bar. 560 uploadValues := url.Values{} 561 uploadValues.Set("source", path) 562 uploadValues.Set("renew", "true") 563 err = st.stdPostAPI("/renter/upload/foo/bar.sia/test", uploadValues) 564 if err != nil { 565 t.Fatal(err) 566 } 567 568 // File should be listed by the renter. 569 var rf RenterFiles 570 err = st.getAPI("/renter/files", &rf) 571 if err != nil { 572 t.Fatal(err) 573 } 574 if len(rf.Files) != 1 || rf.Files[0].SiaPath.String() != "foo/bar.sia/test" { 575 t.Fatal("/renter/files did not return correct file:", rf) 576 } 577 578 // Upload using the same nickname. 579 err = st.stdPostAPI("/renter/upload/foo/bar.sia/test", uploadValues) 580 if err == nil { 581 t.Fatalf("expected %v, got %v", Error{"upload failed: " + siafile.ErrPathOverload.Error()}, err) 582 } 583 584 // Upload using nickname that conflicts with folder. 585 err = st.stdPostAPI("/renter/upload/foo/bar", uploadValues) 586 if err == nil { 587 t.Fatal("expecting conflict error, got nil") 588 } 589 } 590 591 // TestRenterHandlerContracts checks that contract formation between a host and 592 // renter behaves as expected, and that contract spending is the right amount. 593 func TestRenterHandlerContracts(t *testing.T) { 594 if testing.Short() { 595 t.SkipNow() 596 } 597 t.Parallel() 598 st, err := createServerTester(t.Name()) 599 if err != nil { 600 t.Fatal(err) 601 } 602 defer st.server.panicClose() 603 604 // Announce the host and start accepting contracts. 605 if err = st.setHostStorage(); err != nil { 606 t.Fatal(err) 607 } 608 if err := st.announceHost(); err != nil { 609 t.Fatal(err) 610 } 611 if err = st.acceptContracts(); err != nil { 612 t.Fatal(err) 613 } 614 615 // The renter should not have any contracts yet. 616 var contracts RenterContracts 617 if err = st.getAPI("/renter/contracts", &contracts); err != nil { 618 t.Fatal(err) 619 } 620 if len(contracts.Contracts) != 0 { 621 t.Fatalf("expected renter to have 0 contracts; got %v", len(contracts.Contracts)) 622 } 623 624 // Set an allowance for the renter, allowing a contract to be formed. 625 allowanceValues := url.Values{} 626 allowanceValues.Set("funds", testFunds) 627 allowanceValues.Set("period", testPeriod) 628 allowanceValues.Set("renewwindow", testRenewWindow) 629 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 630 allowanceValues.Set("expectedstorage", fmt.Sprint(modules.DefaultAllowance.ExpectedStorage)) 631 allowanceValues.Set("expectedupload", fmt.Sprint(modules.DefaultAllowance.ExpectedStorage)) 632 allowanceValues.Set("expecteddownload", fmt.Sprint(modules.DefaultAllowance.ExpectedStorage)) 633 allowanceValues.Set("expectedredundancy", fmt.Sprint(modules.DefaultAllowance.ExpectedRedundancy)) 634 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 635 t.Fatal(err) 636 } 637 638 // Block until the allowance has finished forming contracts. 639 err = build.Retry(50, time.Millisecond*250, func() error { 640 var rc RenterContracts 641 err = st.getAPI("/renter/contracts", &rc) 642 if err != nil { 643 return errors.New("couldn't get renter stats") 644 } 645 if len(rc.Contracts) == 0 { 646 return errors.New("no contracts") 647 } 648 if len(rc.Contracts) > 1 { 649 return errors.New("more than one contract") 650 } 651 return nil 652 }) 653 if err != nil { 654 t.Fatal("allowance setting failed:", err) 655 } 656 657 // The renter should now have 1 contract. 658 if err = st.getAPI("/renter/contracts", &contracts); err != nil { 659 t.Fatal(err) 660 } 661 if len(contracts.Contracts) != 1 { 662 t.Fatalf("expected renter to have 1 contract; got %v", len(contracts.Contracts)) 663 } 664 if !contracts.Contracts[0].GoodForUpload || !contracts.Contracts[0].GoodForRenew { 665 t.Errorf("expected contract to be good for upload and renew") 666 } 667 668 // Check the renter's contract spending. 669 var get RenterGET 670 if err = st.getAPI("/renter", &get); err != nil { 671 t.Fatal(err) 672 } 673 expectedContractSpending := types.ZeroCurrency 674 for _, contract := range contracts.Contracts { 675 expectedContractSpending = expectedContractSpending.Add(contract.TotalCost) 676 } 677 if got := get.FinancialMetrics.TotalAllocated; got.Cmp(expectedContractSpending) != 0 { 678 t.Fatalf("expected contract spending to be %v; got %v", expectedContractSpending, got) 679 } 680 } 681 682 // TestRenterHandlerGetAndPost checks that valid /renter calls successfully set 683 // allowance values, while /renter calls with invalid allowance values are 684 // correctly handled. 685 func TestRenterHandlerGetAndPost(t *testing.T) { 686 if testing.Short() { 687 t.SkipNow() 688 } 689 t.Parallel() 690 st, err := createServerTester(t.Name()) 691 if err != nil { 692 t.Fatal(err) 693 } 694 defer st.server.panicClose() 695 696 // Announce the host and start accepting contracts. 697 if err = st.setHostStorage(); err != nil { 698 t.Fatal(err) 699 } 700 if err := st.announceHost(); err != nil { 701 t.Fatal(err) 702 } 703 if err = st.acceptContracts(); err != nil { 704 t.Fatal(err) 705 } 706 707 // Set an allowance for the renter, allowing a contract to be formed. 708 allowanceValues := url.Values{} 709 allowanceValues.Set("funds", testFunds) 710 allowanceValues.Set("period", testPeriod) 711 allowanceValues.Set("renewwindow", testRenewWindow) 712 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 713 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 714 t.Fatal(err) 715 } 716 717 // Check that a call to /renter returns the expected values. 718 var get RenterGET 719 if err = st.getAPI("/renter", &get); err != nil { 720 t.Fatal(err) 721 } 722 // Check the renter's funds. 723 expectedFunds, ok := scanAmount(testFunds) 724 if !ok { 725 t.Fatal("scanAmount failed") 726 } 727 if got := get.Settings.Allowance.Funds; got.Cmp(expectedFunds) != 0 { 728 t.Fatalf("expected funds to be %v; got %v", expectedFunds, got) 729 } 730 // Check the renter's period. 731 intPeriod, err := strconv.Atoi(testPeriod) 732 if err != nil { 733 t.Fatal(err) 734 } 735 expectedPeriod := types.BlockHeight(intPeriod) 736 if got := get.Settings.Allowance.Period; got != expectedPeriod { 737 t.Fatalf("expected period to be %v; got %v", expectedPeriod, got) 738 } 739 // Check the renter's renew window. 740 expectedRenewWindow := expectedPeriod / 2 741 if got := get.Settings.Allowance.RenewWindow; got != expectedRenewWindow { 742 t.Fatalf("expected renew window to be %v; got %v", expectedRenewWindow, got) 743 } 744 // Try an invalid period string. 745 allowanceValues.Set("period", "-1") 746 err = st.stdPostAPI("/renter", allowanceValues) 747 if err == nil || !strings.Contains(err.Error(), "unable to parse period") { 748 t.Errorf("expected error to begin with 'unable to parse period'; got %v", err) 749 } 750 // Try to set a zero renew window 751 allowanceValues.Set("period", "2") 752 allowanceValues.Set("renewwindow", "0") 753 err = st.stdPostAPI("/renter", allowanceValues) 754 if err == nil || err.Error() != contractor.ErrAllowanceZeroWindow.Error() { 755 t.Errorf("expected error to be %v, got %v", contractor.ErrAllowanceZeroWindow, err) 756 } 757 // Try to set a negative bandwidth limit 758 allowanceValues.Set("maxdownloadspeed", "-1") 759 allowanceValues.Set("renewwindow", "1") 760 err = st.stdPostAPI("/renter", allowanceValues) 761 if err == nil { 762 t.Errorf("expected error to be 'download/upload rate limit...'; got %v", err) 763 } 764 allowanceValues.Set("maxuploadspeed", "-1") 765 err = st.stdPostAPI("/renter", allowanceValues) 766 if err == nil { 767 t.Errorf("expected error to be 'download/upload rate limit...'; got %v", err) 768 } 769 } 770 771 // TestRenterLoadNonexistent checks that attempting to upload or download a 772 // nonexistent file triggers the appropriate error. 773 func TestRenterLoadNonexistent(t *testing.T) { 774 if testing.Short() { 775 t.SkipNow() 776 } 777 t.Parallel() 778 st, err := createServerTester(t.Name()) 779 if err != nil { 780 t.Fatal(err) 781 } 782 defer st.server.panicClose() 783 784 // Announce the host and start accepting contracts. 785 if err = st.setHostStorage(); err != nil { 786 t.Fatal(err) 787 } 788 if err := st.announceHost(); err != nil { 789 t.Fatal(err) 790 } 791 if err = st.acceptContracts(); err != nil { 792 t.Fatal(err) 793 } 794 795 // Set an allowance for the renter, allowing a contract to be formed. 796 allowanceValues := url.Values{} 797 allowanceValues.Set("funds", testFunds) 798 allowanceValues.Set("period", testPeriod) 799 allowanceValues.Set("renewwindow", testRenewWindow) 800 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 801 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 802 t.Fatal(err) 803 } 804 805 // Try uploading a nonexistent file. 806 fakepath := filepath.Join(st.dir, "dne.dat") 807 uploadValues := url.Values{} 808 uploadValues.Set("source", fakepath) 809 err = st.stdPostAPI("/renter/upload/dne", uploadValues) 810 if err == nil { 811 t.Errorf("expected error when uploading nonexistent file") 812 } 813 814 // Try downloading a nonexistent file. 815 downpath := filepath.Join(st.dir, "dnedown.dat") 816 err = st.stdGetAPI("/renter/download/dne?destination=" + downpath) 817 if err == nil { 818 t.Error("should not be able to download non-existent file") 819 } 820 821 // The renter's downloads queue should be empty. 822 var queue RenterDownloadQueue 823 if err = st.getAPI("/renter/downloads", &queue); err != nil { 824 t.Fatal(err) 825 } 826 if len(queue.Downloads) != 0 { 827 t.Fatalf("expected renter to have 0 downloads in the queue; got %v", len(queue.Downloads)) 828 } 829 } 830 831 // TestRenterHandlerRename checks that valid /renter/rename calls are 832 // successful, and that invalid calls fail with the appropriate error. 833 func TestRenterHandlerRename(t *testing.T) { 834 if testing.Short() { 835 t.SkipNow() 836 } 837 t.Parallel() 838 st, err := createServerTester(t.Name()) 839 if err != nil { 840 t.Fatal(err) 841 } 842 defer st.server.panicClose() 843 844 // Announce the host and start accepting contracts. 845 if err = st.setHostStorage(); err != nil { 846 t.Fatal(err) 847 } 848 if err := st.announceHost(); err != nil { 849 t.Fatal(err) 850 } 851 if err = st.acceptContracts(); err != nil { 852 t.Fatal(err) 853 } 854 855 // Try renaming a nonexistent file. 856 renameValues := url.Values{} 857 renameValues.Set("newsiapath", "newdne") 858 err = st.stdPostAPI("/renter/rename/dne", renameValues) 859 if err == nil || err.Error() != siafile.ErrUnknownPath.Error() { 860 t.Errorf("Expected '%v' got '%v'", siafile.ErrUnknownPath, err) 861 } 862 863 // Set an allowance for the renter, allowing a contract to be formed. 864 allowanceValues := url.Values{} 865 allowanceValues.Set("funds", testFunds) 866 allowanceValues.Set("period", testPeriod) 867 allowanceValues.Set("renewwindow", testRenewWindow) 868 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 869 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 870 t.Fatal(err) 871 } 872 873 // Block until the allowance has finished forming contracts. 874 err = build.Retry(50, time.Millisecond*250, func() error { 875 var rc RenterContracts 876 err = st.getAPI("/renter/contracts", &rc) 877 if err != nil { 878 return errors.New("couldn't get renter stats") 879 } 880 if len(rc.Contracts) != 1 { 881 return errors.New("no contracts") 882 } 883 return nil 884 }) 885 if err != nil { 886 t.Fatal("allowance setting failed") 887 } 888 889 // Create a file. 890 path1 := filepath.Join(st.dir, "test1.dat") 891 if err = createRandFile(path1, 512); err != nil { 892 t.Fatal(err) 893 } 894 895 // Upload to host. 896 uploadValues := url.Values{} 897 uploadValues.Set("source", path1) 898 if err = st.stdPostAPI("/renter/upload/test1", uploadValues); err != nil { 899 t.Fatal(err) 900 } 901 902 // Try renaming to an empty string. 903 renameValues.Set("newsiapath", "") 904 err = st.stdPostAPI("/renter/rename/test1", renameValues) 905 if err == nil || err.Error() != modules.ErrEmptySiaPath.Error() { 906 t.Fatalf("expected error to be %v; got %v", modules.ErrEmptySiaPath, err) 907 } 908 909 // Rename the file. 910 renameValues.Set("newsiapath", "newtest1") 911 if err = st.stdPostAPI("/renter/rename/test1", renameValues); err != nil { 912 t.Fatal(err) 913 } 914 915 // Should be able to continue uploading and downloading using the new name. 916 var rf RenterFiles 917 for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ { 918 st.getAPI("/renter/files", &rf) 919 time.Sleep(100 * time.Millisecond) 920 } 921 if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 { 922 t.Fatal("upload is not succeeding:", rf.Files[0]) 923 } 924 err = st.stdGetAPI("/renter/download/newtest1?destination=" + filepath.Join(st.dir, "testdown2.dat")) 925 if err != nil { 926 t.Fatal(err) 927 } 928 929 // Create and upload another file. 930 path2 := filepath.Join(st.dir, "test2.dat") 931 if err = createRandFile(path2, 512); err != nil { 932 t.Fatal(err) 933 } 934 uploadValues.Set("source", path2) 935 if err = st.stdPostAPI("/renter/upload/test2", uploadValues); err != nil { 936 t.Fatal(err) 937 } 938 // Try renaming to a name that's already taken. 939 renameValues.Set("newsiapath", "newtest1") 940 err = st.stdPostAPI("/renter/rename/test2", renameValues) 941 if err == nil || err.Error() != siafile.ErrPathOverload.Error() { 942 t.Errorf("expected error to be %v; got %v", siafile.ErrPathOverload, err) 943 } 944 } 945 946 // TestRenterHandlerDelete checks that deleting a valid file from the renter 947 // goes as planned and that attempting to delete a nonexistent file fails with 948 // the appropriate error. 949 func TestRenterHandlerDelete(t *testing.T) { 950 if testing.Short() { 951 t.SkipNow() 952 } 953 t.Parallel() 954 st, err := createServerTester(t.Name()) 955 if err != nil { 956 t.Fatal(err) 957 } 958 defer st.server.panicClose() 959 960 // Announce the host and start accepting contracts. 961 if err = st.setHostStorage(); err != nil { 962 t.Fatal(err) 963 } 964 if err := st.announceHost(); err != nil { 965 t.Fatal(err) 966 } 967 if err = st.acceptContracts(); err != nil { 968 t.Fatal(err) 969 } 970 971 // Set an allowance for the renter, allowing a contract to be formed. 972 allowanceValues := url.Values{} 973 allowanceValues.Set("funds", testFunds) 974 allowanceValues.Set("period", testPeriod) 975 allowanceValues.Set("renewwindow", testRenewWindow) 976 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 977 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 978 t.Fatal(err) 979 } 980 981 // Create a file. 982 path := filepath.Join(st.dir, "test.dat") 983 if err = createRandFile(path, 1024); err != nil { 984 t.Fatal(err) 985 } 986 987 // Upload to host. 988 uploadValues := url.Values{} 989 uploadValues.Set("source", path) 990 if err = st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 991 t.Fatal(err) 992 } 993 994 // Delete the file. 995 if err = st.stdPostAPI("/renter/delete/test", url.Values{}); err != nil { 996 t.Fatal(err) 997 } 998 999 // The renter's list of files should now be empty. 1000 var files RenterFiles 1001 if err = st.getAPI("/renter/files", &files); err != nil { 1002 t.Fatal(err) 1003 } 1004 if len(files.Files) != 0 { 1005 t.Fatalf("renter's list of files should be empty; got %v instead", files) 1006 } 1007 1008 // Try deleting a nonexistent file. 1009 err = st.stdPostAPI("/renter/delete/dne", url.Values{}) 1010 if err == nil || err.Error() != siafile.ErrUnknownPath.Error() { 1011 t.Errorf("Expected '%v' got '%v'", siafile.ErrUnknownPath, err) 1012 } 1013 } 1014 1015 // Tests that the /renter/upload call checks for relative paths. 1016 func TestRenterRelativePathErrorUpload(t *testing.T) { 1017 if testing.Short() { 1018 t.SkipNow() 1019 } 1020 t.Parallel() 1021 st, err := createServerTester(t.Name()) 1022 if err != nil { 1023 t.Fatal(err) 1024 } 1025 defer st.server.panicClose() 1026 1027 // Announce the host and start accepting contracts. 1028 if err = st.setHostStorage(); err != nil { 1029 t.Fatal(err) 1030 } 1031 if err := st.announceHost(); err != nil { 1032 t.Fatal(err) 1033 } 1034 if err = st.acceptContracts(); err != nil { 1035 t.Fatal(err) 1036 } 1037 1038 // Set an allowance for the renter, allowing a contract to be formed. 1039 allowanceValues := url.Values{} 1040 allowanceValues.Set("funds", testFunds) 1041 allowanceValues.Set("period", testPeriod) 1042 allowanceValues.Set("renewwindow", testRenewWindow) 1043 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 1044 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 1045 t.Fatal(err) 1046 } 1047 1048 renterUploadAbsoluteError := "source must be an absolute path" 1049 1050 // Create a file. 1051 path := filepath.Join(st.dir, "test.dat") 1052 if err = createRandFile(path, 1024); err != nil { 1053 t.Fatal(err) 1054 } 1055 1056 // This should fail. 1057 uploadValues := url.Values{} 1058 uploadValues.Set("source", "test.dat") 1059 if err = st.stdPostAPI("/renter/upload/test", uploadValues); err.Error() != renterUploadAbsoluteError { 1060 t.Fatal(err) 1061 } 1062 1063 // As should this. 1064 uploadValues = url.Values{} 1065 uploadValues.Set("source", "../test.dat") 1066 if err = st.stdPostAPI("/renter/upload/test", uploadValues); err.Error() != renterUploadAbsoluteError { 1067 t.Fatal(err) 1068 } 1069 1070 // This should succeed. 1071 uploadValues = url.Values{} 1072 uploadValues.Set("source", path) 1073 if err = st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 1074 t.Fatal(err) 1075 } 1076 } 1077 1078 // Tests that the /renter/download call checks for relative paths. 1079 func TestRenterRelativePathErrorDownload(t *testing.T) { 1080 if testing.Short() { 1081 t.SkipNow() 1082 } 1083 t.Parallel() 1084 st, err := createServerTester(t.Name()) 1085 if err != nil { 1086 t.Fatal(err) 1087 } 1088 defer st.server.panicClose() 1089 1090 // Announce the host and start accepting contracts. 1091 if err = st.setHostStorage(); err != nil { 1092 t.Fatal(err) 1093 } 1094 if err := st.announceHost(); err != nil { 1095 t.Fatal(err) 1096 } 1097 if err = st.acceptContracts(); err != nil { 1098 t.Fatal(err) 1099 } 1100 1101 // Set an allowance for the renter, allowing a contract to be formed. 1102 allowanceValues := url.Values{} 1103 allowanceValues.Set("funds", testFunds) 1104 allowanceValues.Set("period", testPeriod) 1105 allowanceValues.Set("renewwindow", testRenewWindow) 1106 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 1107 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 1108 t.Fatal(err) 1109 } 1110 1111 renterDownloadAbsoluteError := "download failed: destination must be an absolute path" 1112 1113 // Create a file, and upload it. 1114 path := filepath.Join(st.dir, "test.dat") 1115 if err = createRandFile(path, 1024); err != nil { 1116 t.Fatal(err) 1117 } 1118 uploadValues := url.Values{} 1119 uploadValues.Set("source", path) 1120 if err = st.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 1121 t.Fatal(err) 1122 } 1123 var rf RenterFiles 1124 for i := 0; i < 200 && (len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10); i++ { 1125 st.getAPI("/renter/files", &rf) 1126 time.Sleep(200 * time.Millisecond) 1127 } 1128 if len(rf.Files) != 1 || rf.Files[0].UploadProgress < 10 { 1129 t.Fatal("the uploading is not succeeding for some reason:", rf.Files[0]) 1130 } 1131 1132 // Use a relative destination, which should fail. 1133 downloadPath := "test1.dat" 1134 if err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath); err.Error() != renterDownloadAbsoluteError { 1135 t.Fatal(err) 1136 } 1137 1138 // Relative destination stepping backwards should also fail. 1139 downloadPath = "../test1.dat" 1140 if err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath); err.Error() != renterDownloadAbsoluteError { 1141 t.Fatal(err) 1142 } 1143 1144 // Long relative destination should also fail (just missing leading slash). 1145 downloadPath = filepath.Join(st.dir[1:], "test1.dat") 1146 err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath) 1147 if err == nil { 1148 t.Fatal("expecting an error") 1149 } 1150 1151 // Full destination should succeed. 1152 downloadPath = filepath.Join(st.dir, "test1.dat") 1153 err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath) 1154 if err != nil { 1155 t.Fatal("expecting an error") 1156 } 1157 } 1158 1159 // TestRenterPricesHandler checks that the prices command returns reasonable 1160 // values given the settings of the hosts. 1161 func TestRenterPricesHandler(t *testing.T) { 1162 if testing.Short() { 1163 t.SkipNow() 1164 } 1165 t.Parallel() 1166 st, err := createServerTester(t.Name()) 1167 if err != nil { 1168 t.Fatal(err) 1169 } 1170 defer st.server.panicClose() 1171 1172 // Announce the host and then get the calculated prices for when there is a 1173 // single host. 1174 var rpeSingle modules.RenterPriceEstimation 1175 if err := st.setHostStorage(); err != nil { 1176 t.Fatal(err) 1177 } 1178 if err = st.announceHost(); err != nil { 1179 t.Fatal(err) 1180 } 1181 1182 // Set an allowance for the renter, allowing a contract to be formed. 1183 allowanceValues := url.Values{} 1184 allowanceValues.Set("funds", testFunds) 1185 allowanceValues.Set("period", testPeriod) 1186 allowanceValues.Set("renewwindow", testRenewWindow) 1187 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 1188 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 1189 t.Fatal(err) 1190 } 1191 1192 if err = st.getAPI("/renter/prices", &rpeSingle); err != nil { 1193 t.Fatal(err) 1194 } 1195 1196 // Create several more hosts all using the default settings. 1197 stHost1, err := blankServerTester(t.Name() + " - Host 1") 1198 if err != nil { 1199 t.Fatal(err) 1200 } 1201 defer stHost1.panicClose() 1202 stHost2, err := blankServerTester(t.Name() + " - Host 2") 1203 if err != nil { 1204 t.Fatal(err) 1205 } 1206 defer stHost2.panicClose() 1207 1208 // Connect all the nodes and announce all of the hosts. 1209 sts := []*serverTester{st, stHost1, stHost2} 1210 err = fullyConnectNodes(sts) 1211 if err != nil { 1212 t.Fatal(err) 1213 } 1214 err = fundAllNodes(sts) 1215 if err != nil { 1216 t.Fatal(err) 1217 } 1218 err = announceAllHosts(sts) 1219 if err != nil { 1220 t.Fatal(err) 1221 } 1222 1223 // Grab the price estimates for when there are a bunch of hosts with the 1224 // same stats. 1225 var rpeMulti modules.RenterPriceEstimation 1226 if err = st.getAPI("/renter/prices", &rpeMulti); err != nil { 1227 t.Fatal(err) 1228 } 1229 1230 // Verify that the aggregate is the same. 1231 if !rpeMulti.DownloadTerabyte.Equals(rpeSingle.DownloadTerabyte) { 1232 t.Log(rpeMulti.DownloadTerabyte) 1233 t.Log(rpeSingle.DownloadTerabyte) 1234 t.Error("price changed from single to multi") 1235 } 1236 if rpeMulti.FormContracts.Equals(rpeSingle.FormContracts) { 1237 t.Error("price of forming contracts should have increased from single to multi") 1238 } 1239 if !rpeMulti.StorageTerabyteMonth.Equals(rpeSingle.StorageTerabyteMonth) { 1240 t.Error("price changed from single to multi") 1241 } 1242 if !rpeMulti.UploadTerabyte.Equals(rpeSingle.UploadTerabyte) { 1243 t.Error("price changed from single to multi") 1244 } 1245 } 1246 1247 // TestRenterPricesHandlerPricey checks that the prices command returns 1248 // reasonable values given the settings of the hosts. 1249 func TestRenterPricesHandlerPricey(t *testing.T) { 1250 if testing.Short() { 1251 t.SkipNow() 1252 } 1253 t.Parallel() 1254 st, err := createServerTester(t.Name()) 1255 if err != nil { 1256 t.Fatal(err) 1257 } 1258 defer st.server.panicClose() 1259 1260 // Announce the host and then get the calculated prices for when there is a 1261 // single host. 1262 var rpeSingle modules.RenterPriceEstimation 1263 if err := st.setHostStorage(); err != nil { 1264 t.Fatal(err) 1265 } 1266 if err = st.announceHost(); err != nil { 1267 t.Fatal(err) 1268 } 1269 1270 // Set an allowance for the renter, allowing a contract to be formed. 1271 allowanceValues := url.Values{} 1272 allowanceValues.Set("funds", testFunds) 1273 allowanceValues.Set("period", testPeriod) 1274 allowanceValues.Set("renewwindow", testRenewWindow) 1275 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 1276 if err = st.stdPostAPI("/renter", allowanceValues); err != nil { 1277 t.Fatal(err) 1278 } 1279 1280 if err = st.getAPI("/renter/prices", &rpeSingle); err != nil { 1281 t.Fatal(err) 1282 } 1283 1284 // Create several more hosts all using the default settings. 1285 stHost1, err := blankServerTester(t.Name() + " - Host 1") 1286 if err != nil { 1287 t.Fatal(err) 1288 } 1289 if err := stHost1.setHostStorage(); err != nil { 1290 t.Fatal(err) 1291 } 1292 stHost2, err := blankServerTester(t.Name() + " - Host 2") 1293 if err != nil { 1294 t.Fatal(err) 1295 } 1296 if err := stHost2.setHostStorage(); err != nil { 1297 t.Fatal(err) 1298 } 1299 1300 var hg HostGET 1301 err = st.getAPI("/host", &hg) 1302 if err != nil { 1303 t.Fatal(err) 1304 } 1305 err = stHost1.getAPI("/host", &hg) 1306 if err != nil { 1307 t.Fatal(err) 1308 } 1309 err = stHost2.getAPI("/host", &hg) 1310 if err != nil { 1311 t.Fatal(err) 1312 } 1313 1314 // Set host 2 to be more expensive than the rest by a substantial amount. This 1315 // should result in an increase for the price estimation. 1316 vals := url.Values{} 1317 vals.Set("mindownloadbandwidthprice", "100000000000000000000") 1318 vals.Set("mincontractprice", "1000000000000000000000000") 1319 vals.Set("minstorageprice", "1000000000000000000000") 1320 vals.Set("minuploadbandwidthprice", "100000000000000000000") 1321 err = stHost2.stdPostAPI("/host", vals) 1322 if err != nil { 1323 t.Fatal(err) 1324 } 1325 1326 // Connect all the nodes and announce all of the hosts. 1327 sts := []*serverTester{st, stHost1, stHost2} 1328 err = fullyConnectNodes(sts) 1329 if err != nil { 1330 t.Fatal(err) 1331 } 1332 err = fundAllNodes(sts) 1333 if err != nil { 1334 t.Fatal(err) 1335 } 1336 err = announceAllHosts(sts) 1337 if err != nil { 1338 t.Fatal(err) 1339 } 1340 1341 // Grab the price estimates for when there are a bunch of hosts with the 1342 // same stats. 1343 var rpeMulti modules.RenterPriceEstimation 1344 if err = st.getAPI("/renter/prices", &rpeMulti); err != nil { 1345 t.Fatal(err) 1346 } 1347 1348 // Verify that the estimate for downloading, uploading, and storing 1349 // increased but the estimate for formaing contracts decreased. Forming 1350 // contracts decreases because with a more expensive host you are not able 1351 // to store as much data therefore reducing the amount of host collateral 1352 // you have to pay the siafund fee on 1353 if !(rpeMulti.DownloadTerabyte.Cmp(rpeSingle.DownloadTerabyte) > 0) { 1354 t.Log("Multi DownloadTerabyte cost:", rpeMulti.DownloadTerabyte.HumanString()) 1355 t.Log("Single DownloadTerabyte cost:", rpeSingle.DownloadTerabyte.HumanString()) 1356 t.Error("price did not increase from single to multi") 1357 } 1358 if rpeMulti.FormContracts.Cmp(rpeSingle.FormContracts) > 0 { 1359 t.Log("Multi FormContracts cost:", rpeMulti.FormContracts.HumanString()) 1360 t.Log("Single FormContracts cost:", rpeSingle.FormContracts.HumanString()) 1361 t.Error("price did not drop from single to multi") 1362 } 1363 if !(rpeMulti.StorageTerabyteMonth.Cmp(rpeSingle.StorageTerabyteMonth) > 0) { 1364 t.Log("Multi StorageTerabyteMonth cost:", rpeMulti.StorageTerabyteMonth.HumanString()) 1365 t.Log("Single StorageTerabyteMonth cost:", rpeSingle.StorageTerabyteMonth.HumanString()) 1366 t.Error("price did not increase from single to multi") 1367 } 1368 if !(rpeMulti.UploadTerabyte.Cmp(rpeSingle.UploadTerabyte) > 0) { 1369 t.Log("Multi UploadTerabyte cost:", rpeMulti.UploadTerabyte.HumanString()) 1370 t.Log("Single UploadTerabyte cost:", rpeSingle.UploadTerabyte.HumanString()) 1371 t.Error("price did not increase from single to multi") 1372 } 1373 } 1374 1375 // TestContractorHostRemoval checks that the contractor properly migrates away 1376 // from low quality hosts when there are higher quality hosts available. 1377 func TestContractorHostRemoval(t *testing.T) { 1378 // Create a renter and 2 hosts. Connect to the hosts and start uploading. 1379 if testing.Short() || !build.VLONG { 1380 t.SkipNow() 1381 } 1382 st, err := createServerTester(t.Name() + "renter") 1383 if err != nil { 1384 t.Fatal(err) 1385 } 1386 defer st.server.panicClose() 1387 stH1, err := blankServerTester(t.Name() + " - Host 1") 1388 if err != nil { 1389 t.Fatal(err) 1390 } 1391 defer stH1.server.Close() 1392 testGroup := []*serverTester{st, stH1} 1393 1394 // Connect the testers to eachother so that they are all on the same 1395 // blockchain. 1396 err = fullyConnectNodes(testGroup) 1397 if err != nil { 1398 t.Fatal(err) 1399 } 1400 // Make sure that every wallet has money in it. 1401 err = fundAllNodes(testGroup) 1402 if err != nil { 1403 t.Fatal(err) 1404 } 1405 1406 // Add storage to every host. 1407 err = addStorageToAllHosts(testGroup) 1408 if err != nil { 1409 t.Fatal(err) 1410 } 1411 // Raise the prices significantly for the two hosts. 1412 raisedPrice := url.Values{} 1413 raisedPrice.Set("mincontractprice", "5000000000000000000000000000") // 5 KS 1414 raisedPrice.Set("period", testPeriod) 1415 err = st.stdPostAPI("/host", raisedPrice) 1416 if err != nil { 1417 t.Fatal(err) 1418 } 1419 err = stH1.stdPostAPI("/host", raisedPrice) 1420 if err != nil { 1421 t.Fatal(err) 1422 } 1423 // Announce the hosts. 1424 err = announceAllHosts(testGroup) 1425 if err != nil { 1426 t.Fatal(err) 1427 } 1428 1429 // Set an allowance with two hosts. 1430 allowanceValues := url.Values{} 1431 allowanceValues.Set("funds", "500000000000000000000000000000") // 500k SC 1432 allowanceValues.Set("hosts", "2") 1433 allowanceValues.Set("period", "15") 1434 err = st.stdPostAPI("/renter", allowanceValues) 1435 if err != nil { 1436 t.Fatal(err) 1437 } 1438 1439 // Create a file to upload. 1440 filesize := int(100) 1441 path := filepath.Join(st.dir, "test.dat") 1442 err = createRandFile(path, filesize) 1443 if err != nil { 1444 t.Fatal(err) 1445 } 1446 origBytes, err := ioutil.ReadFile(path) 1447 if err != nil { 1448 t.Fatal(err) 1449 } 1450 1451 // upload the file 1452 uploadValues := url.Values{} 1453 uploadValues.Set("source", path) 1454 uploadValues.Set("datapieces", "1") 1455 uploadValues.Set("paritypieces", "1") 1456 err = st.stdPostAPI("/renter/upload/test", uploadValues) 1457 if err != nil { 1458 t.Fatal(err) 1459 } 1460 1461 // redundancy should reach 2 1462 var rf RenterFiles 1463 err = build.Retry(120, 250*time.Millisecond, func() error { 1464 st.getAPI("/renter/files", &rf) 1465 if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 2 { 1466 return nil 1467 } 1468 return errors.New("file not uploaded") 1469 }) 1470 if err != nil { 1471 t.Fatal(err) 1472 } 1473 1474 // verify we can download 1475 downloadPath := filepath.Join(st.dir, "test-downloaded-verify.dat") 1476 err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath) 1477 if err != nil { 1478 t.Fatal(err) 1479 } 1480 downloadBytes, err := ioutil.ReadFile(downloadPath) 1481 if err != nil { 1482 t.Fatal(err) 1483 } 1484 if !bytes.Equal(downloadBytes, origBytes) { 1485 t.Fatal("downloaded file and uploaded file do not match") 1486 } 1487 1488 // Get the values of the first and second contract. 1489 var rc RenterContracts 1490 err = st.getAPI("/renter/contracts", &rc) 1491 if err != nil { 1492 t.Fatal(err) 1493 } 1494 if len(rc.ActiveContracts) != 2 { 1495 t.Fatal("wrong contract count") 1496 } 1497 rc1Host := rc.ActiveContracts[0].HostPublicKey.String() 1498 rc2Host := rc.ActiveContracts[1].HostPublicKey.String() 1499 1500 // Add 3 new hosts that will be competing with the expensive hosts. 1501 stH2, err := blankServerTester(t.Name() + " - Host 2") 1502 if err != nil { 1503 t.Fatal(err) 1504 } 1505 defer stH2.server.Close() 1506 stH3, err := blankServerTester(t.Name() + " - Host 3") 1507 if err != nil { 1508 t.Fatal(err) 1509 } 1510 defer stH3.server.Close() 1511 stH4, err := blankServerTester(t.Name() + " - Host 4") 1512 if err != nil { 1513 t.Fatal(err) 1514 } 1515 defer stH4.server.Close() 1516 testGroup = []*serverTester{st, stH1, stH2, stH3, stH4} 1517 // Connect the testers to eachother so that they are all on the same 1518 // blockchain. 1519 err = fullyConnectNodes(testGroup) 1520 if err != nil { 1521 t.Fatal(err) 1522 } 1523 // Make sure that every wallet has money in it. 1524 err = fundAllNodes(testGroup) 1525 if err != nil { 1526 t.Fatal(err) 1527 } 1528 // Add storage to every host. 1529 err = addStorageToAllHosts([]*serverTester{stH2, stH3, stH4}) 1530 if err != nil { 1531 t.Fatal(err) 1532 } 1533 // Announce the hosts. 1534 err = announceAllHosts(testGroup) 1535 if err != nil { 1536 t.Fatal(err) 1537 } 1538 1539 // Block until the hostdb reaches five hosts. 1540 err = build.Retry(150, time.Millisecond*250, func() error { 1541 var ah HostdbActiveGET 1542 err = st.getAPI("/hostdb/active", &ah) 1543 if err != nil { 1544 return err 1545 } 1546 if len(ah.Hosts) < 5 { 1547 return errors.New("new hosts never appeared in hostdb") 1548 } 1549 return nil 1550 }) 1551 if err != nil { 1552 t.Fatal(err) 1553 } 1554 1555 // Mine a block to trigger a second run of threadedContractMaintenance. 1556 _, err = st.miner.AddBlock() 1557 if err != nil { 1558 t.Fatal(err) 1559 } 1560 1561 // Verify that st and stH1 are dropped in favor of the newer, better hosts. 1562 err = build.Retry(600, time.Millisecond*100, func() error { 1563 var newContracts int 1564 var rc RenterContracts 1565 err = st.getAPI("/renter/contracts", &rc) 1566 if err != nil { 1567 return errors.New("couldn't get renter stats") 1568 } 1569 hostMap := make(map[string]struct{}) 1570 hostMap[rc1Host] = struct{}{} 1571 hostMap[rc2Host] = struct{}{} 1572 for _, contract := range rc.ActiveContracts { 1573 _, exists := hostMap[contract.HostPublicKey.String()] 1574 if !exists { 1575 newContracts++ 1576 hostMap[contract.HostPublicKey.String()] = struct{}{} 1577 } 1578 } 1579 if newContracts != 2 { 1580 return fmt.Errorf("not the right number of new contracts: %v", newContracts) 1581 } 1582 return nil 1583 }) 1584 if err != nil { 1585 t.Fatal(err) 1586 } 1587 1588 // Block until redundancy is restored to 2. 1589 err = build.Retry(120, 250*time.Millisecond, func() error { 1590 st.getAPI("/renter/files", &rf) 1591 if len(rf.Files) == 1 && rf.Files[0].Redundancy == 2 { 1592 return nil 1593 } 1594 return errors.New("file not uploaded to full redundancy") 1595 }) 1596 if err != nil { 1597 t.Fatal(err) 1598 } 1599 1600 // Block until data has been uploaded ot new contracts. 1601 err = build.Retry(120, 250*time.Millisecond, func() error { 1602 err = st.getAPI("/renter/contracts", &rc) 1603 if err != nil { 1604 return err 1605 } 1606 for _, contract := range rc.ActiveContracts { 1607 if contract.Size != modules.SectorSize { 1608 return fmt.Errorf("Each contrat should have 1 sector: %v - %v", contract.Size, contract.ID) 1609 } 1610 } 1611 return nil 1612 }) 1613 if err != nil { 1614 t.Fatal(err) 1615 } 1616 1617 // Grab the current contracts, then mine blocks to trigger a renew, and then 1618 // wait until the renew is complete. 1619 err = st.getAPI("/renter/contracts", &rc) 1620 if err != nil { 1621 t.Fatal(err) 1622 } 1623 // Mine blocks to force a contract renewal. 1624 for i := 0; i < 11; i++ { 1625 _, err := st.miner.AddBlock() 1626 if err != nil { 1627 t.Fatal(err) 1628 } 1629 _, err = synchronizationCheck(testGroup) 1630 if err != nil { 1631 t.Fatal(err) 1632 } 1633 // Sleep to give the contractor some time to perform the renew. 1634 time.Sleep(time.Millisecond * 100) 1635 } 1636 // Give the renter time to renew. Two of the contracts should renew. 1637 var rc2 RenterContracts 1638 loop := 0 1639 err = build.Retry(100, time.Millisecond*100, func() error { 1640 loop++ 1641 // Mine a block every 10 iterations to make sure 1642 // threadedContractMaintenance continues to create and replace contracts 1643 if loop%10 == 0 { 1644 _, err = st.miner.AddBlock() 1645 if err != nil { 1646 return err 1647 } 1648 } 1649 err = st.getAPI("/renter/contracts", &rc2) 1650 if err != nil { 1651 return errors.New("couldn't get renter stats") 1652 } 1653 1654 // Check that at least 2 contracts are different between rc and rc2. 1655 tracker := make(map[types.FileContractID]struct{}) 1656 // Add all the contracts. 1657 for _, contract := range rc.ActiveContracts { 1658 tracker[contract.ID] = struct{}{} 1659 } 1660 // Count the number of contracts that were not seen in the previous 1661 // batch of contracts, and check that the new contracts are not with the 1662 // expensive hosts. 1663 var unseen int 1664 for _, contract := range rc2.ActiveContracts { 1665 _, exists := tracker[contract.ID] 1666 if !exists { 1667 unseen++ 1668 tracker[contract.ID] = struct{}{} 1669 if contract.HostPublicKey.String() == rc1Host || contract.HostPublicKey.String() == rc2Host { 1670 return errors.New("the wrong contracts are being renewed") 1671 } 1672 } 1673 } 1674 if unseen != 2 { 1675 return fmt.Errorf("the wrong number of contracts seem to be getting renewed, have %v expect 2", unseen) 1676 } 1677 return nil 1678 }) 1679 if err != nil { 1680 t.Fatal(err) 1681 } 1682 // The renewing process should not have resulted in additional data being 1683 // uploaded - it should be the same data in the contracts. 1684 for _, contract := range rc2.ActiveContracts { 1685 if contract.Size != modules.SectorSize { 1686 t.Error("Contract has the wrong size:", contract.Size) 1687 } 1688 } 1689 1690 // Try again to download the file we uploaded. It should still be 1691 // retrievable. 1692 downloadPath2 := filepath.Join(st.dir, "test-downloaded-verify-2.dat") 1693 err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath2) 1694 if err != nil { 1695 t.Fatal(err) 1696 } 1697 downloadBytes2, err := ioutil.ReadFile(downloadPath2) 1698 if err != nil { 1699 t.Fatal(err) 1700 } 1701 if !bytes.Equal(downloadBytes2, origBytes) { 1702 t.Fatal("downloaded file and uploaded file do not match") 1703 } 1704 1705 // Mine out another set of the blocks so that the bad contracts expire. 1706 for i := 0; i < 11; i++ { 1707 _, err := st.miner.AddBlock() 1708 if err != nil { 1709 t.Fatal(err) 1710 } 1711 _, err = synchronizationCheck(testGroup) 1712 if err != nil { 1713 t.Fatal(err) 1714 } 1715 time.Sleep(time.Millisecond * 100) 1716 } 1717 1718 // Should be back down to 2 contracts now, with the new hosts. Verify that 1719 // st and stH1 are dropped in favor of the newer, better hosts. The 1720 // contracts should also have data in them. 1721 err = build.Retry(50, time.Millisecond*250, func() error { 1722 var rc RenterContracts 1723 err = st.getAPI("/renter/contracts", &rc) 1724 if err != nil { 1725 return errors.New("couldn't get renter stats") 1726 } 1727 if len(rc.ActiveContracts) != 2 { 1728 return fmt.Errorf("renewing seems to have failed: %v", len(rc.ActiveContracts)) 1729 } 1730 return nil 1731 }) 1732 if err != nil { 1733 t.Fatal(err) 1734 } 1735 rc = RenterContracts{} 1736 err = st.getAPI("/renter/contracts", &rc) 1737 if err != nil { 1738 t.Fatal(err) 1739 } 1740 if rc.ActiveContracts[0].HostPublicKey.String() == rc1Host || rc.ActiveContracts[0].HostPublicKey.String() == rc2Host { 1741 t.Error("renter is renewing the wrong contracts", rc.ActiveContracts[0].HostPublicKey.String()) 1742 } 1743 if rc.ActiveContracts[1].HostPublicKey.String() == rc1Host || rc.ActiveContracts[1].HostPublicKey.String() == rc2Host { 1744 t.Error("renter is renewing the wrong contracts", rc.ActiveContracts[1].HostPublicKey.String()) 1745 } 1746 // The renewing process should not have resulted in additional data being 1747 // uploaded - it should be the same data in the contracts. 1748 for _, contract := range rc.ActiveContracts { 1749 if contract.Size != modules.SectorSize { 1750 t.Error("Contract has the wrong size:", contract.Size, contract.ID) 1751 } 1752 } 1753 // Redundancy should still be 2. 1754 err = build.Retry(120, 250*time.Millisecond, func() error { 1755 st.getAPI("/renter/files", &rf) 1756 if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 2 { 1757 return nil 1758 } 1759 return errors.New("file not uploaded to full redundancy") 1760 }) 1761 if err != nil { 1762 t.Fatal(err, "::", rf.Files[0].Redundancy) 1763 } 1764 1765 // Try again to download the file we uploaded. It should still be 1766 // retrievable. 1767 downloadPath3 := filepath.Join(st.dir, "test-downloaded-verify-3.dat") 1768 err = st.stdGetAPI("/renter/download/test?destination=" + downloadPath3) 1769 if err != nil { 1770 t.Error("Final download has failed:", err) 1771 } 1772 downloadBytes3, err := ioutil.ReadFile(downloadPath3) 1773 if err != nil { 1774 t.Error(err) 1775 } 1776 if !bytes.Equal(downloadBytes3, origBytes) { 1777 t.Error("downloaded file and uploaded file do not match") 1778 } 1779 } 1780 1781 // TestAdversarialPriceRenewal verifies that host cannot maliciously raise 1782 // their storage price in order to trigger a premature file contract renewal. 1783 func TestAdversarialPriceRenewal(t *testing.T) { 1784 if testing.Short() || !build.VLONG { 1785 t.SkipNow() 1786 } 1787 t.Parallel() 1788 st, err := createServerTester(t.Name()) 1789 if err != nil { 1790 t.Fatal(err) 1791 } 1792 defer st.server.panicClose() 1793 1794 // announce our host 1795 err = st.acceptContracts() 1796 if err != nil { 1797 t.Fatal(err) 1798 } 1799 err = st.setHostStorage() 1800 if err != nil { 1801 t.Fatal(err) 1802 } 1803 err = st.announceHost() 1804 if err != nil { 1805 t.Fatal(err) 1806 } 1807 1808 // Set an allowance for the renter, allowing a contract to be formed. 1809 allowanceValues := url.Values{} 1810 testPeriod := "10000" 1811 allowanceValues.Set("funds", types.SiacoinPrecision.Mul64(10000).String()) 1812 allowanceValues.Set("period", testPeriod) 1813 err = st.stdPostAPI("/renter", allowanceValues) 1814 if err != nil { 1815 t.Fatal(err) 1816 } 1817 1818 // wait until we have a contract 1819 err = build.Retry(500, time.Millisecond*50, func() error { 1820 if len(st.renter.Contracts()) >= 1 { 1821 return nil 1822 } 1823 return errors.New("no renter contracts") 1824 }) 1825 if err != nil { 1826 t.Fatal(err) 1827 } 1828 1829 // upload a file 1830 path := filepath.Join(st.dir, "randUploadFile") 1831 size := int(modules.SectorSize * 50) 1832 err = createRandFile(path, size) 1833 if err != nil { 1834 t.Fatal(err) 1835 } 1836 uploadValues := url.Values{} 1837 uploadValues.Set("source", path) 1838 uploadValues.Set("renew", "true") 1839 uploadValues.Set("datapieces", "1") 1840 uploadValues.Set("paritypieces", "1") 1841 err = st.stdPostAPI("/renter/upload/"+filepath.Base(path), uploadValues) 1842 if err != nil { 1843 t.Fatal(err) 1844 } 1845 err = build.Retry(100, time.Millisecond*500, func() error { 1846 // mine blocks each iteration to trigger contract maintenance 1847 _, err = st.miner.AddBlock() 1848 if err != nil { 1849 t.Fatal(err) 1850 } 1851 files, err := st.renter.FileList(modules.RootSiaPath(), true, false) 1852 if err != nil { 1853 return err 1854 } 1855 if !files[0].Available { 1856 return errors.New("file did not complete uploading") 1857 } 1858 return nil 1859 }) 1860 if err != nil { 1861 t.Fatal(err) 1862 } 1863 initialID := st.renter.Contracts()[0].ID 1864 1865 // jack up the host's storage price to try to trigger a renew 1866 settings := st.host.InternalSettings() 1867 settings.MinStoragePrice = settings.MinStoragePrice.Mul64(10800000000) 1868 err = st.host.SetInternalSettings(settings) 1869 if err != nil { 1870 t.Fatal(err) 1871 } 1872 1873 for i := 0; i < 100; i++ { 1874 _, err = st.miner.AddBlock() 1875 if err != nil { 1876 t.Fatal(err) 1877 } 1878 if st.renter.Contracts()[0].ID != initialID { 1879 t.Fatal("changing host price caused renew") 1880 } 1881 time.Sleep(time.Millisecond * 100) 1882 } 1883 } 1884 1885 // TestHealthLoop probes the code involved with threadedUpdateRenterHealth to 1886 // verify the health information stored in the siadir metadata is getting 1887 // updated as the health of the files changes 1888 func TestHealthLoop(t *testing.T) { 1889 if testing.Short() { 1890 t.SkipNow() 1891 } 1892 t.Parallel() 1893 1894 // Create renter and hosts 1895 st1, err := createServerTester(t.Name() + "1") 1896 if err != nil { 1897 t.Fatal(err) 1898 } 1899 defer st1.server.panicClose() 1900 if err = st1.announceHost(); err != nil { 1901 t.Fatal(err) 1902 } 1903 st2, err := createServerTester(t.Name() + "2") 1904 if err != nil { 1905 t.Fatal(err) 1906 } 1907 1908 // Connect the testers to eachother so that they are all on the same 1909 // blockchain. 1910 testGroup := []*serverTester{st1, st2} 1911 err = fullyConnectNodes(testGroup) 1912 if err != nil { 1913 t.Fatal(err) 1914 } 1915 // Make sure that every wallet has money in it. 1916 err = fundAllNodes(testGroup) 1917 if err != nil { 1918 t.Fatal(err) 1919 } 1920 // Add storage to every host. 1921 err = addStorageToAllHosts(testGroup) 1922 if err != nil { 1923 t.Fatal(err) 1924 } 1925 // Announce the hosts. 1926 err = announceAllHosts(testGroup) 1927 if err != nil { 1928 t.Fatal(err) 1929 } 1930 1931 // Set an allowance for the renter, allowing a contract to be formed. 1932 allowanceValues := url.Values{} 1933 allowanceValues.Set("funds", testFunds) 1934 allowanceValues.Set("period", testPeriod) 1935 allowanceValues.Set("renewwindow", testRenewWindow) 1936 allowanceValues.Set("hosts", fmt.Sprint(modules.DefaultAllowance.Hosts)) 1937 if err = st1.stdPostAPI("/renter", allowanceValues); err != nil { 1938 t.Fatal(err) 1939 } 1940 1941 // Block until the allowance has finished forming contracts. 1942 err = build.Retry(50, time.Millisecond*250, func() error { 1943 var rc RenterContracts 1944 err = st1.getAPI("/renter/contracts", &rc) 1945 if err != nil { 1946 return errors.New("couldn't get renter stats") 1947 } 1948 if len(rc.Contracts) != 2 { 1949 return errors.New("no contracts") 1950 } 1951 return nil 1952 }) 1953 if err != nil { 1954 t.Fatal("allowance setting failed") 1955 } 1956 1957 // Create and upload files 1958 path := filepath.Join(st1.dir, "test.dat") 1959 if err = createRandFile(path, 1024); err != nil { 1960 t.Fatal(err) 1961 } 1962 1963 // Upload to host. 1964 uploadValues := url.Values{} 1965 uploadValues.Set("source", path) 1966 uploadValues.Set("datapieces", "1") 1967 uploadValues.Set("paritypieces", "1") 1968 if err = st1.stdPostAPI("/renter/upload/test", uploadValues); err != nil { 1969 t.Fatal(err) 1970 } 1971 1972 // redundancy should reach 2 1973 err = build.Retry(120, 250*time.Millisecond, func() error { 1974 var rf RenterFiles 1975 st1.getAPI("/renter/files", &rf) 1976 if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 2 { 1977 return nil 1978 } 1979 return fmt.Errorf("file not uploaded, %v files found and redundancy of first file is %v", len(rf.Files), rf.Files[0].Redundancy) 1980 }) 1981 if err != nil { 1982 t.Fatal(err) 1983 } 1984 1985 // Verify folder metadata is update, directory health should be 0 1986 err = build.Retry(100, 100*time.Millisecond, func() error { 1987 var rd RenterDirectory 1988 err := st1.getAPI("/renter/dir/", &rd) 1989 if err != nil { 1990 return err 1991 } 1992 if rd.Directories[0].Health != 0 { 1993 return fmt.Errorf("Directory health should be 0 but was %v", rd.Directories[0].Health) 1994 } 1995 return nil 1996 }) 1997 if err != nil { 1998 t.Fatal(err) 1999 } 2000 2001 // Take Down a host 2002 st2.server.panicClose() 2003 2004 // redundancy should drop 2005 err = build.Retry(120, 250*time.Millisecond, func() error { 2006 var rf RenterFiles 2007 st1.getAPI("/renter/files", &rf) 2008 if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 2 { 2009 return fmt.Errorf("Expect 1 file with a redundancy of less than 2, got %v files and redundancy of %v", len(rf.Files), rf.Files[0].Redundancy) 2010 } 2011 return nil 2012 }) 2013 if err != nil { 2014 t.Fatal(err) 2015 } 2016 2017 // Check that the metadata has been updated 2018 err = build.Retry(100, 100*time.Millisecond, func() error { 2019 var rd RenterDirectory 2020 st1.getAPI("/renter/dir/", &rd) 2021 if rd.Directories[0].MaxHealth == 0 { 2022 return fmt.Errorf("Directory max health should have dropped below 0 but was %v", rd.Directories[0].MaxHealth) 2023 } 2024 return nil 2025 }) 2026 if err != nil { 2027 t.Fatal(err) 2028 } 2029 }