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,6 +304,48 @@ func (r *Renter) managedRepairIteration(rs *repairState) { ...@@ -304,6 +304,48 @@ func (r *Renter) managedRepairIteration(rs *repairState) {
r.managedWaitOnRepairWork(rs) r.managedWaitOnRepairWork(rs)
} }
// managedDownloadChunkData downloads the requested chunk from Sia, for use in
// the repair loop.
func (r *Renter) managedDownloadChunkData(rs *repairState, file *file, offset uint64, chunkIndex uint64, chunkID chunkID) ([]byte, error) {
rs.downloadingChunks[chunkID] = struct{}{}
defer delete(rs.downloadingChunks, chunkID)
// build current contracts map
currentContracts := make(map[modules.NetAddress]types.FileContractID)
for _, contract := range r.hostContractor.Contracts() {
currentContracts[contract.NetAddress] = contract.ID
}
downloadSize := file.chunkSize()
if offset+downloadSize > file.size {
downloadSize = file.size - offset
}
// create a DownloadBufferWriter for the chunk
buf := NewDownloadBufferWriter(file.chunkSize())
// create the download object and push it on to the download queue
d := r.newSectionDownload(file, buf, currentContracts, offset, downloadSize)
done := make(chan struct{})
defer close(done)
go func() {
select {
case r.newDownloads <- d:
case <-done:
}
}()
// wait for the download to complete and return the data
select {
case <-d.downloadFinished:
return buf.Bytes(), d.Err()
case <-r.tg.StopChan():
return nil, errors.New("chunk download interrupted by shutdown")
case <-time.After(chunkDownloadTimeout):
return nil, errors.New("chunk download timed out")
}
}
// managedGetChunkData grabs the requested `chunkID` from the file, in order to // 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 // 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 // from the file, otherwise attempt to queue a new download for only that chunk
...@@ -316,44 +358,7 @@ func (r *Renter) managedGetChunkData(rs *repairState, file *file, trackedFile tr ...@@ -316,44 +358,7 @@ func (r *Renter) managedGetChunkData(rs *repairState, file *file, trackedFile tr
f, err := os.Open(trackedFile.RepairPath) f, err := os.Open(trackedFile.RepairPath)
if err != nil { if err != nil {
// if that fails, try to download the chunk // if that fails, try to download the chunk
// mark the chunk as being downloaded return r.managedDownloadChunkData(rs, file, offset, chunkIndex, chunkID)
rs.downloadingChunks[chunkID] = struct{}{}
defer delete(rs.downloadingChunks, chunkID)
// build current contracts map
currentContracts := make(map[modules.NetAddress]types.FileContractID)
for _, contract := range r.hostContractor.Contracts() {
currentContracts[contract.NetAddress] = contract.ID
}
downloadSize := file.chunkSize()
if offset+downloadSize > file.size {
downloadSize = file.size - offset
}
// create a DownloadBufferWriter for the chunk
buf := NewDownloadBufferWriter(file.chunkSize())
// create the download object and push it on to the download queue
d := r.newSectionDownload(file, buf, currentContracts, offset, downloadSize)
done := make(chan struct{})
defer close(done)
go func() {
select {
case r.newDownloads <- d:
case <-done:
}
}()
// wait for the download to complete and return the data
select {
case <-d.downloadFinished:
return buf.Bytes(), d.Err()
case <-r.tg.StopChan():
return nil, errors.New("chunk download interrupted by shutdown")
case <-time.After(chunkDownloadTimeout):
return nil, errors.New("chunk download timed out")
}
} }
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