Commit 777433ac authored by David Vorick's avatar David Vorick Committed by GitHub

Merge pull request #2157 from NebulousLabs/repair-regression

add file repair regression test
parents 5a799ac5 71e2a89d
...@@ -22,6 +22,184 @@ import ( ...@@ -22,6 +22,184 @@ import (
"github.com/NebulousLabs/Sia/types" "github.com/NebulousLabs/Sia/types"
) )
// TestRenterLocalRepair verifies that the renter will use the local file to
// repair if the file exists locally
func TestRenterLocalRepair(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
t.Parallel()
st, err := createServerTester(t.Name())
if err != nil {
t.Fatal(err)
}
defer st.server.Close()
stH1, err := blankServerTester(t.Name() + " - Host 1")
if err != nil {
t.Fatal(err)
}
defer stH1.server.Close()
testGroup := []*serverTester{st, stH1}
// Connect the testers to eachother so that they are all on the same
// blockchain.
err = fullyConnectNodes(testGroup)
if err != nil {
t.Fatal(err)
}
// Make sure that every wallet has money in it.
err = fundAllNodes(testGroup)
if err != nil {
t.Fatal(err)
}
// Add storage to every host.
err = addStorageToAllHosts(testGroup)
if err != nil {
t.Fatal(err)
}
err = announceAllHosts(testGroup)
if err != nil {
t.Fatal(err)
}
// Set an allowance with two hosts.
allowanceValues := url.Values{}
allowanceValues.Set("funds", "50000000000000000000000000000") // 50k SC
allowanceValues.Set("hosts", "2")
allowanceValues.Set("period", "10")
err = st.stdPostAPI("/renter", allowanceValues)
if err != nil {
t.Fatal(err)
}
// Create a file to upload.
filesize := int(1024)
path := filepath.Join(st.dir, "test.dat")
err = createRandFile(path, filesize)
if err != nil {
t.Fatal(err)
}
// upload the file
uploadValues := url.Values{}
uploadValues.Set("source", path)
err = st.stdPostAPI("/renter/upload/test", uploadValues)
if err != nil {
t.Fatal(err)
}
// redundancy should reach 2
var rf RenterFiles
err = retry(60, time.Second, func() error {
st.getAPI("/renter/files", &rf)
if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 2 {
return nil
}
return errors.New("file not uploaded")
})
if err != nil {
t.Fatal(err)
}
// download spending should not have increased
var rg RenterGET
err = st.getAPI("/renter", &rg)
if err != nil {
t.Fatal(err)
}
if rg.FinancialMetrics.DownloadSpending.Cmp(types.NewCurrency64(0)) > 0 {
t.Fatalf("expected no download spending, got %v instead\n", rg.FinancialMetrics.DownloadSpending)
}
// take down one of the hosts
err = stH1.server.Close()
if err != nil {
t.Fatal(err)
}
// wait for the redundancy to decrement
err = retry(60, time.Second, func() error {
st.getAPI("/renter/files", &rf)
if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 1 {
return nil
}
return errors.New("file redundancy not decremented")
})
if err != nil {
t.Fatal(err)
}
// bring up a new host
stNewHost, err := blankServerTester(t.Name() + "-newhost")
if err != nil {
t.Fatal(err)
}
defer stNewHost.server.Close()
testGroup = []*serverTester{st, stNewHost}
// Connect the testers to eachother so that they are all on the same
// blockchain.
err = fullyConnectNodes(testGroup)
if err != nil {
t.Fatal(err)
}
_, err = synchronizationCheck(testGroup)
if err != nil {
t.Fatal(err)
}
// Make sure that every wallet has money in it.
err = fundAllNodes(testGroup)
if err != nil {
t.Fatal(err)
}
err = stNewHost.setHostStorage()
if err != nil {
t.Fatal(err)
}
err = stNewHost.announceHost()
if err != nil {
t.Fatal(err)
}
err = waitForBlock(stNewHost.cs.CurrentBlock().ID(), st)
if err != nil {
t.Fatal(err)
}
// add a few new blocks in order to cause the renter to form contracts with the new host
for i := 0; i < 10; i++ {
b, err := stNewHost.miner.AddBlock()
if err != nil {
t.Fatal(err)
}
for _, tester := range testGroup {
err = waitForBlock(b.ID(), tester)
if err != nil {
t.Fatal(err)
}
}
}
// redundancy should increment back to 2 as the renter uploads to the new
// host using the download-to-upload strategy
err = retry(240, time.Second, func() error {
st.getAPI("/renter/files", &rf)
if len(rf.Files) >= 1 && rf.Files[0].Redundancy == 2 && rf.Files[0].Available {
return nil
}
return errors.New("file redundancy not incremented")
})
if err != nil {
t.Fatal(err)
}
if rg.FinancialMetrics.DownloadSpending.Cmp(types.NewCurrency64(0)) > 0 {
t.Fatalf("expected no download spending, got %v instead\n", rg.FinancialMetrics.DownloadSpending)
}
}
// TestRemoteFileRepair verifies that if a trackedFile is made unavailable // TestRemoteFileRepair verifies that if a trackedFile is made unavailable
// locally by being deleted, the repair loop will download the necessary chunks // locally by being deleted, the repair loop will download the necessary chunks
// from the living hosts and upload them to new hosts. // from the living hosts and upload them to new hosts.
......
...@@ -304,19 +304,9 @@ func (r *Renter) managedRepairIteration(rs *repairState) { ...@@ -304,19 +304,9 @@ func (r *Renter) managedRepairIteration(rs *repairState) {
r.managedWaitOnRepairWork(rs) r.managedWaitOnRepairWork(rs)
} }
// managedGetChunkData grabs the requested `chunkID` from the file, in order to // managedDownloadChunkData downloads the requested chunk from Sia, for use in
// repair the file. If the `trackedFile` can be found on disk, grab the chunk // the repair loop.
// from the file, otherwise attempt to queue a new download for only that chunk func (r *Renter) managedDownloadChunkData(rs *repairState, file *file, offset uint64, chunkIndex uint64, chunkID chunkID) ([]byte, error) {
// and return the downloaded chunk.
func (r *Renter) managedGetChunkData(rs *repairState, file *file, trackedFile trackedFile, chunkID chunkID) ([]byte, error) {
chunkIndex := chunkID.index
offset := chunkIndex * file.chunkSize()
// try to read the chunk from disk
f, err := os.Open(trackedFile.RepairPath)
if err != nil {
// if that fails, try to download the chunk
// mark the chunk as being downloaded
rs.downloadingChunks[chunkID] = struct{}{} rs.downloadingChunks[chunkID] = struct{}{}
defer delete(rs.downloadingChunks, chunkID) defer delete(rs.downloadingChunks, chunkID)
...@@ -354,6 +344,21 @@ func (r *Renter) managedGetChunkData(rs *repairState, file *file, trackedFile tr ...@@ -354,6 +344,21 @@ func (r *Renter) managedGetChunkData(rs *repairState, file *file, trackedFile tr
case <-time.After(chunkDownloadTimeout): case <-time.After(chunkDownloadTimeout):
return nil, errors.New("chunk download timed out") return nil, errors.New("chunk download timed out")
} }
}
// managedGetChunkData grabs the requested `chunkID` from the file, in order to
// repair the file. If the `trackedFile` can be found on disk, grab the chunk
// from the file, otherwise attempt to queue a new download for only that chunk
// and return the downloaded chunk.
func (r *Renter) managedGetChunkData(rs *repairState, file *file, trackedFile trackedFile, chunkID chunkID) ([]byte, error) {
chunkIndex := chunkID.index
offset := chunkIndex * file.chunkSize()
// try to read the chunk from disk
f, err := os.Open(trackedFile.RepairPath)
if err != nil {
// if that fails, try to download the chunk
return r.managedDownloadChunkData(rs, file, offset, chunkIndex, chunkID)
} }
defer f.Close() defer f.Close()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment