github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/siatest/renter.go (about)

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