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 }