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 }