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  }