github.com/ZuluSpl0it/Sia@v1.3.7/siatest/renter.go (about)

     1  package siatest
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"path/filepath"
     7  	"reflect"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/NebulousLabs/Sia/crypto"
    12  	"github.com/NebulousLabs/Sia/modules"
    13  	"github.com/NebulousLabs/Sia/node/api"
    14  
    15  	"github.com/NebulousLabs/errors"
    16  	"github.com/NebulousLabs/fastrand"
    17  )
    18  
    19  // DownloadToDisk downloads a previously uploaded file. The file will be downloaded
    20  // to a random location and returned as a TestFile object.
    21  func (tn *TestNode) DownloadToDisk(rf *RemoteFile, async bool) (*LocalFile, error) {
    22  	fi, err := tn.FileInfo(rf)
    23  	if err != nil {
    24  		return nil, errors.AddContext(err, "failed to retrieve FileInfo")
    25  	}
    26  	// Create a random destination for the download
    27  	fileName := strconv.Itoa(fastrand.Intn(math.MaxInt32))
    28  	dest := filepath.Join(SiaTestingDir, fileName)
    29  	if err := tn.RenterDownloadGet(rf.siaPath, dest, 0, fi.Filesize, async); err != nil {
    30  		return nil, errors.AddContext(err, "failed to download file")
    31  	}
    32  	// Create the TestFile
    33  	lf := &LocalFile{
    34  		path:     dest,
    35  		checksum: rf.checksum,
    36  	}
    37  	// If we download the file asynchronously we are done
    38  	if async {
    39  		return lf, nil
    40  	}
    41  	// Verify checksum if we downloaded the file blocking
    42  	if err := lf.checkIntegrity(); err != nil {
    43  		return lf, errors.AddContext(err, "downloaded file's checksum doesn't match")
    44  	}
    45  	return lf, nil
    46  }
    47  
    48  // DownloadByStream downloads a file and returns its contents as a slice of bytes.
    49  func (tn *TestNode) DownloadByStream(rf *RemoteFile) (data []byte, err error) {
    50  	fi, err := tn.FileInfo(rf)
    51  	if err != nil {
    52  		return nil, errors.AddContext(err, "failed to retrieve FileInfo")
    53  	}
    54  	data, err = tn.RenterDownloadHTTPResponseGet(rf.siaPath, 0, fi.Filesize)
    55  	if err == nil && rf.checksum != crypto.HashBytes(data) {
    56  		err = errors.New("downloaded bytes don't match requested data")
    57  	}
    58  	return
    59  }
    60  
    61  // Stream uses the streaming endpoint to download a file.
    62  func (tn *TestNode) Stream(rf *RemoteFile) (data []byte, err error) {
    63  	data, err = tn.RenterStreamGet(rf.siaPath)
    64  	if err == nil && rf.checksum != crypto.HashBytes(data) {
    65  		err = errors.New("downloaded bytes don't match requested data")
    66  	}
    67  	return
    68  }
    69  
    70  // StreamPartial uses the streaming endpoint to download a partial file in
    71  // range [from;to]. A local file can be provided optionally to implicitly check
    72  // the checksum of the downloaded data.
    73  func (tn *TestNode) StreamPartial(rf *RemoteFile, lf *LocalFile, from, to uint64) (data []byte, err error) {
    74  	data, err = tn.RenterStreamPartialGet(rf.siaPath, from, to)
    75  	if err != nil {
    76  		return
    77  	}
    78  	if uint64(len(data)) != to-from+1 {
    79  		err = fmt.Errorf("length of downloaded data should be %v but was %v",
    80  			to-from+1, len(data))
    81  		return
    82  	}
    83  	if lf != nil {
    84  		var checksum crypto.Hash
    85  		checksum, err = lf.partialChecksum(from, to+1)
    86  		if err != nil {
    87  			err = errors.AddContext(err, "failed to get partial checksum")
    88  			return
    89  		}
    90  		if checksum != crypto.HashBytes(data) {
    91  			err = fmt.Errorf("downloaded bytes don't match requested data %v-%v", from, to)
    92  			return
    93  		}
    94  	}
    95  	return
    96  }
    97  
    98  // DownloadInfo returns the DownloadInfo struct of a file. If it returns nil,
    99  // the download has either finished, or was never started in the first place.
   100  // If the corresponding download info was found, DownloadInfo also performs a
   101  // few sanity checks on its fields.
   102  func (tn *TestNode) DownloadInfo(lf *LocalFile, rf *RemoteFile) (*api.DownloadInfo, error) {
   103  	rdq, err := tn.RenterDownloadsGet()
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	var di *api.DownloadInfo
   108  	for _, d := range rdq.Downloads {
   109  		if rf.siaPath == d.SiaPath && lf.path == d.Destination {
   110  			di = &d
   111  			break
   112  		}
   113  	}
   114  	if di == nil {
   115  		// No download info found.
   116  		return nil, errors.New("download info not found")
   117  	}
   118  	// Check if length and filesize were set correctly
   119  	if di.Length != di.Filesize {
   120  		err = errors.AddContext(err, "filesize != length")
   121  	}
   122  	// Received data can't be larger than transferred data
   123  	if di.Received > di.TotalDataTransferred {
   124  		err = errors.AddContext(err, "received > TotalDataTransferred")
   125  	}
   126  	// If the download is completed, the amount of received data has to equal
   127  	// the amount of requested data.
   128  	if di.Completed && di.Received != di.Length {
   129  		err = errors.AddContext(err, "completed == true but received != length")
   130  	}
   131  	return di, err
   132  }
   133  
   134  // File returns the file queried by the user
   135  func (tn *TestNode) File(siaPath string) (modules.FileInfo, error) {
   136  	rf, err := tn.RenterFileGet(siaPath)
   137  	if err != nil {
   138  		return rf.File, err
   139  	}
   140  	return rf.File, err
   141  }
   142  
   143  // Files lists the files tracked by the renter
   144  func (tn *TestNode) Files() ([]modules.FileInfo, error) {
   145  	rf, err := tn.RenterFilesGet()
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	return rf.Files, err
   150  }
   151  
   152  // FileInfo retrieves the info of a certain file that is tracked by the renter
   153  func (tn *TestNode) FileInfo(rf *RemoteFile) (modules.FileInfo, error) {
   154  	files, err := tn.Files()
   155  	if err != nil {
   156  		return modules.FileInfo{}, err
   157  	}
   158  	for _, file := range files {
   159  		if file.SiaPath == rf.siaPath {
   160  			return file, nil
   161  		}
   162  	}
   163  	return modules.FileInfo{}, errors.New("file is not tracked by the renter")
   164  }
   165  
   166  // Upload uses the node to upload the file.
   167  func (tn *TestNode) Upload(lf *LocalFile, dataPieces, parityPieces uint64) (*RemoteFile, error) {
   168  	// Upload file
   169  	err := tn.RenterUploadPost(lf.path, "/"+lf.fileName(), dataPieces, parityPieces)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	// Create remote file object
   174  	rf := &RemoteFile{
   175  		siaPath:  lf.fileName(),
   176  		checksum: lf.checksum,
   177  	}
   178  	// Make sure renter tracks file
   179  	_, err = tn.FileInfo(rf)
   180  	if err != nil {
   181  		return rf, errors.AddContext(err, "uploaded file is not tracked by the renter")
   182  	}
   183  	return rf, nil
   184  }
   185  
   186  // UploadNewFile initiates the upload of a filesize bytes large file.
   187  func (tn *TestNode) UploadNewFile(filesize int, dataPieces uint64, parityPieces uint64) (*LocalFile, *RemoteFile, error) {
   188  	// Create file for upload
   189  	localFile, err := NewFile(filesize)
   190  	if err != nil {
   191  		return nil, nil, errors.AddContext(err, "failed to create file")
   192  	}
   193  	// Upload file, creating a parity piece for each host in the group
   194  	remoteFile, err := tn.Upload(localFile, dataPieces, parityPieces)
   195  	if err != nil {
   196  		return nil, nil, errors.AddContext(err, "failed to start upload")
   197  	}
   198  	return localFile, remoteFile, nil
   199  }
   200  
   201  // UploadNewFileBlocking uploads a filesize bytes large file and waits for the
   202  // upload to reach 100% progress and redundancy.
   203  func (tn *TestNode) UploadNewFileBlocking(filesize int, dataPieces uint64, parityPieces uint64) (*LocalFile, *RemoteFile, error) {
   204  	localFile, remoteFile, err := tn.UploadNewFile(filesize, dataPieces, parityPieces)
   205  	if err != nil {
   206  		return nil, nil, err
   207  	}
   208  	// Wait until upload reached the specified progress
   209  	if err = tn.WaitForUploadProgress(remoteFile, 1); err != nil {
   210  		return nil, nil, err
   211  	}
   212  	// Wait until upload reaches a certain redundancy
   213  	err = tn.WaitForUploadRedundancy(remoteFile, float64((dataPieces+parityPieces))/float64(dataPieces))
   214  	return localFile, remoteFile, err
   215  }
   216  
   217  // WaitForDownload waits for the download of a file to finish. If a file wasn't
   218  // scheduled for download it will return instantly without an error. If parent
   219  // is provided, it will compare the contents of the downloaded file to the
   220  // contents of tf2 after the download is finished. WaitForDownload also
   221  // verifies the checksum of the downloaded file.
   222  func (tn *TestNode) WaitForDownload(lf *LocalFile, rf *RemoteFile) error {
   223  	var downloadErr error
   224  	err := Retry(1000, 100*time.Millisecond, func() error {
   225  		file, err := tn.DownloadInfo(lf, rf)
   226  		if err != nil {
   227  			return errors.AddContext(err, "couldn't retrieve DownloadInfo")
   228  		}
   229  		if file == nil {
   230  			return nil
   231  		}
   232  		if !file.Completed {
   233  			return errors.New("download hasn't finished yet")
   234  		}
   235  		if file.Error != "" {
   236  			downloadErr = errors.New(file.Error)
   237  		}
   238  		return nil
   239  	})
   240  	if err != nil || downloadErr != nil {
   241  		return errors.Compose(err, downloadErr)
   242  	}
   243  	// Verify checksum
   244  	return lf.checkIntegrity()
   245  }
   246  
   247  // WaitForUploadProgress waits for a file to reach a certain upload progress.
   248  func (tn *TestNode) WaitForUploadProgress(rf *RemoteFile, progress float64) error {
   249  	if _, err := tn.FileInfo(rf); err != nil {
   250  		return errors.New("file is not tracked by renter")
   251  	}
   252  	// Wait until it reaches the progress
   253  	return Retry(1000, 100*time.Millisecond, func() error {
   254  		file, err := tn.FileInfo(rf)
   255  		if err != nil {
   256  			return errors.AddContext(err, "couldn't retrieve FileInfo")
   257  		}
   258  		if file.UploadProgress < progress {
   259  			return fmt.Errorf("progress should be %v but was %v", progress, file.UploadProgress)
   260  		}
   261  		return nil
   262  	})
   263  
   264  }
   265  
   266  // WaitForUploadRedundancy waits for a file to reach a certain upload redundancy.
   267  func (tn *TestNode) WaitForUploadRedundancy(rf *RemoteFile, redundancy float64) error {
   268  	// Check if file is tracked by renter at all
   269  	if _, err := tn.FileInfo(rf); err != nil {
   270  		return errors.New("file is not tracked by renter")
   271  	}
   272  	// Wait until it reaches the redundancy
   273  	return Retry(600, 100*time.Millisecond, func() error {
   274  		file, err := tn.FileInfo(rf)
   275  		if err != nil {
   276  			return errors.AddContext(err, "couldn't retrieve FileInfo")
   277  		}
   278  		if file.Redundancy < redundancy {
   279  			return fmt.Errorf("redundancy should be %v but was %v", redundancy, file.Redundancy)
   280  		}
   281  		return nil
   282  	})
   283  }
   284  
   285  // WaitForDecreasingRedundancy waits until the redundancy decreases to a
   286  // certain point.
   287  func (tn *TestNode) WaitForDecreasingRedundancy(rf *RemoteFile, redundancy float64) error {
   288  	// Check if file is tracked by renter at all
   289  	if _, err := tn.FileInfo(rf); err != nil {
   290  		return errors.New("file is not tracked by renter")
   291  	}
   292  	// Wait until it reaches the redundancy
   293  	return Retry(1000, 100*time.Millisecond, func() error {
   294  		file, err := tn.FileInfo(rf)
   295  		if err != nil {
   296  			return errors.AddContext(err, "couldn't retrieve FileInfo")
   297  		}
   298  		if file.Redundancy > redundancy {
   299  			return fmt.Errorf("redundancy should be %v but was %v", redundancy, file.Redundancy)
   300  		}
   301  		return nil
   302  	})
   303  }
   304  
   305  // KnowsHost checks if tn has a certain host in its hostdb. This check is
   306  // performed using the host's public key.
   307  func (tn *TestNode) KnowsHost(host *TestNode) error {
   308  	hdag, err := tn.HostDbActiveGet()
   309  	if err != nil {
   310  		return err
   311  	}
   312  	for _, h := range hdag.Hosts {
   313  		pk, err := host.HostPublicKey()
   314  		if err != nil {
   315  			return err
   316  		}
   317  		if reflect.DeepEqual(h.PublicKey, pk) {
   318  			return nil
   319  		}
   320  	}
   321  	return errors.New("host ist unknown")
   322  }