github.com/VertebrateResequencing/muxfys/v4@v4.0.3/s3_test.go (about) 1 // Copyright © 2017, 2018 Genome Research Limited 2 // Author: Sendu Bala <sb10@sanger.ac.uk>. 3 // 4 // This file is part of muxfys. 5 // 6 // muxfys is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU Lesser General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // (at your option) any later version. 10 // 11 // muxfys is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU Lesser General Public License for more details. 15 // 16 // You should have received a copy of the GNU Lesser General Public License 17 // along with muxfys. If not, see <http://www.gnu.org/licenses/>. 18 19 package muxfys 20 21 import ( 22 "bufio" 23 "bytes" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "log" 28 "math" 29 "net/url" 30 "os" 31 "os/exec" 32 "path" 33 "path/filepath" 34 "runtime" 35 "sort" 36 "strconv" 37 "strings" 38 "sync" 39 "testing" 40 "time" 41 42 "github.com/hanwen/go-fuse/v2/fuse" 43 . "github.com/smartystreets/goconvey/convey" 44 ) 45 46 func TestS3Localntegration(t *testing.T) { 47 // We will create test files on local disk and then start up minio server 48 // to give us an S3 system to test against. 49 // 50 // minio server can be installed by: 51 // go get -u github.com/minio/minio 52 // 53 // These tests will only run if minio has already been installed and this 54 // env var has been set: MUXFYS_S3_PORT (eg. set it to 9000) 55 // 56 // We must be able to start minio server on that port (ie. it can't already 57 // be running for some other purpose). 58 59 port := os.Getenv("MUXFYS_S3_PORT") 60 _, lperr := exec.LookPath("minio") 61 if port == "" || lperr != nil { 62 SkipConvey("Without MUXFYS_S3_PORT environment variable and minio being installed, we'll skip local S3 tests", t, func() {}) 63 return 64 } 65 66 target := fmt.Sprintf("http://localhost:%s/user/wr_tests", port) 67 accessKey := "AKIAIOSFODNN7EXAMPLE" 68 secretKey := "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 69 origAKey := os.Getenv("AWS_ACCESS_KEY_ID") 70 origSKey := os.Getenv("AWS_SECRET_ACCESS_KEY") 71 os.Setenv("AWS_ACCESS_KEY_ID", accessKey) 72 os.Setenv("AWS_SECRET_ACCESS_KEY", secretKey) 73 defer func() { 74 os.Setenv("AWS_ACCESS_KEY_ID", origAKey) 75 os.Setenv("AWS_SECRET_ACCESS_KEY", origSKey) 76 }() 77 78 // create our test files 79 tmpdir, err := ioutil.TempDir("", "muxfys_testing") 80 if err != nil { 81 log.Fatal(err) 82 } 83 defer os.RemoveAll(tmpdir) 84 85 err = os.Chdir(tmpdir) // because muxfys will create dirs relative to cwd 86 if err != nil { 87 log.Panic(err) // Panic instead of fatal so our deferred removal of tmpdir works 88 } 89 90 minioDir := filepath.Join(tmpdir, "minio") 91 wrTestsDir := filepath.Join(minioDir, "user", "wr_tests") 92 wrTestsSubDir := filepath.Join(wrTestsDir, "sub") 93 wrTestsDeepDir := filepath.Join(wrTestsSubDir, "deep") 94 err = os.MkdirAll(wrTestsDeepDir, os.FileMode(0700)) 95 if err != nil { 96 log.Panic(err) 97 } 98 err = os.MkdirAll(filepath.Join(wrTestsDir, "emptyDir"), os.FileMode(0700)) 99 if err != nil { 100 log.Panic(err) 101 } 102 103 bigFileSize := 10000000 // 10MB, also tested with 1GB and it's fine 104 err = exec.Command("dd", "if=/dev/zero", "of="+filepath.Join(wrTestsDir, "big.file"), fmt.Sprintf("bs=%d", bigFileSize), "count=1").Run() 105 if err != nil { 106 log.Panic(err) 107 } 108 109 f, err := os.Create(filepath.Join(wrTestsDir, "numalphanum.txt")) 110 if err != nil { 111 log.Panic(err) 112 } 113 _, err = f.WriteString("1234567890abcdefghijklmnopqrstuvwxyz1234567890\n") 114 if err != nil { 115 log.Panic(err) 116 } 117 f.Close() 118 119 f, err = os.Create(filepath.Join(wrTestsDir, "100k.lines")) 120 if err != nil { 121 log.Panic(err) 122 } 123 for i := 1; i <= 100000; i++ { 124 _, err = f.WriteString(fmt.Sprintf("%06d\n", i)) 125 if err != nil { 126 log.Panic(err) 127 } 128 } 129 f.Close() 130 131 f, err = os.Create(filepath.Join(wrTestsSubDir, "empty.file")) 132 if err != nil { 133 log.Panic(err) 134 } 135 f.Close() 136 137 f, err = os.Create(filepath.Join(wrTestsDeepDir, "bar")) 138 if err != nil { 139 log.Panic(err) 140 } 141 _, err = f.WriteString("foo\n") 142 if err != nil { 143 log.Panic(err) 144 } 145 f.Close() 146 147 // start minio 148 os.Setenv("MINIO_ACCESS_KEY", accessKey) 149 os.Setenv("MINIO_SECRET_KEY", secretKey) 150 os.Setenv("MINIO_BROWSER", "off") 151 minioCmd := exec.Command("minio", "server", "--address", fmt.Sprintf("localhost:%s", port), minioDir) 152 153 // if all tests accessing what minio server is supposed to serve fail, debug 154 // minio's startup: 155 // go func() { 156 // <-time.After(30 * time.Second) 157 // minioCmd.Process.Kill() 158 // minioCmd.Wait() 159 // }() 160 // out, err := minioCmd.CombinedOutput() 161 // fmt.Println(string(out)) 162 // fmt.Println(err.Error()) 163 // fmt.Println(target) 164 // return 165 166 err = minioCmd.Start() 167 if err != nil { 168 log.Panic(err) 169 } 170 defer func() { 171 minioCmd.Process.Kill() 172 minioCmd.Wait() 173 }() 174 175 // give it time to become ready to respond to accesses 176 if os.Getenv("CI") == "true" { 177 <-time.After(30 * time.Second) 178 } else { 179 <-time.After(5 * time.Second) 180 } 181 182 s3IntegrationTests(t, tmpdir, target, accessKey, secretKey, bigFileSize, false) 183 } 184 185 func TestS3RemoteIntegration(t *testing.T) { 186 // For these tests to work, MUXFYS_REMOTES3_TARGET must be the full URL to 187 // an immediate child directory of a bucket that you have read and write 188 // permissions for, eg: https://cog.domain.com/bucket/wr_tests You must also 189 // have a ~/.s3cfg file with a [default] section specifying the same domain 190 // and scheme via host_base and use_https. 191 // 192 // The child directory must contain the following: 193 // perl -e 'for (1..100000) { printf "%06d\n", $_; }' > 100k.lines 194 // echo 1234567890abcdefghijklmnopqrstuvwxyz1234567890 > numalphanum.txt 195 // dd if=/dev/zero of=big.file bs=1073741824 count=1 196 // mkdir -p sub/deep 197 // touch sub/empty.file 198 // echo foo > sub/deep/bar 199 // export WR_BUCKET_SUB=s3://bucket/wr_tests 200 // s3cmd put 100k.lines $WR_BUCKET_SUB/100k.lines 201 // s3cmd put numalphanum.txt $WR_BUCKET_SUB/numalphanum.txt 202 // s3cmd put big.file $WR_BUCKET_SUB/big.file 203 // s3cmd put sub/empty.file $WR_BUCKET_SUB/sub/empty.file 204 // s3cmd put sub/deep/bar $WR_BUCKET_SUB/sub/deep/bar 205 // rm -fr 100k.lines numalphanum.txt big.file sub 206 // [use s3fs to mkdir s3://bucket/wr_tests/emptyDir] 207 208 target := os.Getenv("MUXFYS_REMOTES3_TARGET") 209 accessKey := os.Getenv("AWS_ACCESS_KEY_ID") 210 secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY") 211 212 if target == "" || accessKey == "" || secretKey == "" { 213 SkipConvey("Without MUXFYS_REMOTES3_TARGET, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables, we'll skip remote S3 tests", t, func() {}) 214 return 215 } 216 217 tmpdir, err := ioutil.TempDir("", "muxfys_testing") 218 if err != nil { 219 log.Fatal(err) 220 } 221 defer os.RemoveAll(tmpdir) 222 223 err = os.Chdir(tmpdir) 224 if err != nil { 225 log.Panic(err) 226 } 227 228 s3IntegrationTests(t, tmpdir, target, accessKey, secretKey, 1073741824, true) 229 } 230 231 func s3IntegrationTests(t *testing.T, tmpdir, target, accessKey, secretKey string, bigFileSize int, doRemoteTests bool) { 232 // common configuration of muxfys 233 mountPoint := filepath.Join(tmpdir, "mount") 234 cacheDir := filepath.Join(tmpdir, "cacheDir") 235 236 manualConfig := &S3Config{ 237 Target: target, 238 AccessKey: accessKey, 239 SecretKey: secretKey, 240 } 241 accessor, errn := NewS3Accessor(manualConfig) 242 if errn != nil { 243 log.Panic(errn) 244 } 245 246 remoteConfig := &RemoteConfig{ 247 Accessor: accessor, 248 CacheData: true, 249 Write: false, 250 } 251 252 cfg := &Config{ 253 Mount: mountPoint, 254 Retries: 3, 255 Verbose: false, 256 } 257 258 bigFileEntry := fmt.Sprintf("big.file:file:%d", bigFileSize) 259 Convey("You can configure S3 from the environment", t, func() { 260 envConfig, err := S3ConfigFromEnvironment("", "mybucket/subdir") 261 So(err, ShouldBeNil) 262 So(envConfig.AccessKey, ShouldEqual, manualConfig.AccessKey) 263 So(envConfig.SecretKey, ShouldEqual, manualConfig.SecretKey) 264 So(envConfig.Target, ShouldNotBeNil) 265 So(envConfig.Target, ShouldEndWith, "mybucket/subdir") 266 267 if doRemoteTests { 268 u, _ := url.Parse(target) 269 uNew := url.URL{ 270 Scheme: u.Scheme, 271 Host: u.Host, 272 Path: "mybucket/subdir", 273 } 274 So(envConfig.Target, ShouldEqual, uNew.String()) 275 276 envConfig2, err := S3ConfigFromEnvironment("default", "mybucket/subdir") 277 So(err, ShouldBeNil) 278 So(envConfig2.AccessKey, ShouldEqual, envConfig.AccessKey) 279 So(envConfig2.SecretKey, ShouldEqual, envConfig.SecretKey) 280 So(envConfig2.Target, ShouldEqual, envConfig.Target) 281 282 _, err = S3ConfigFromEnvironment("-fake-", "mybucket/subdir") 283 So(err, ShouldNotBeNil) 284 285 // *** how can we test chaining of ~/.s3cfg and ~/.aws/credentials 286 // without messing with those files? 287 } 288 }) 289 290 var bigFileGetTime time.Duration 291 Convey("You can mount with local file caching", t, func() { 292 fs, errc := New(cfg) 293 So(errc, ShouldBeNil) 294 295 errm := fs.Mount(remoteConfig) 296 So(errm, ShouldBeNil) 297 298 defer func() { 299 erru := fs.Unmount() 300 So(erru, ShouldBeNil) 301 }() 302 303 Convey("You can read a whole file as well as parts of it by seeking", func() { 304 path := mountPoint + "/100k.lines" 305 read, err := streamFile(path, 0) 306 So(err, ShouldBeNil) 307 So(read, ShouldEqual, 700000) 308 309 read, err = streamFile(path, 350000) 310 So(err, ShouldBeNil) 311 So(read, ShouldEqual, 350000) 312 313 // make sure the contents are actually correct 314 var expected bytes.Buffer 315 for i := 1; i <= 100000; i++ { 316 expected.WriteString(fmt.Sprintf("%06d\n", i)) 317 } 318 bytes, err := ioutil.ReadFile(path) 319 So(err, ShouldBeNil) 320 So(string(bytes), ShouldEqual, expected.String()) 321 }) 322 323 Convey("You can do random reads", func() { 324 // it works on a small file 325 path := mountPoint + "/numalphanum.txt" 326 r, err := os.Open(path) 327 So(err, ShouldBeNil) 328 defer r.Close() 329 330 r.Seek(36, io.SeekStart) 331 332 b := make([]byte, 10) 333 done, err := io.ReadFull(r, b) 334 So(err, ShouldBeNil) 335 So(done, ShouldEqual, 10) 336 So(b, ShouldResemble, []byte("1234567890")) 337 338 r.Seek(10, io.SeekStart) 339 b = make([]byte, 10) 340 done, err = io.ReadFull(r, b) 341 So(err, ShouldBeNil) 342 So(done, ShouldEqual, 10) 343 So(b, ShouldResemble, []byte("abcdefghij")) 344 345 // and it works on a big file 346 path = mountPoint + "/100k.lines" 347 rbig, err := os.Open(path) 348 So(err, ShouldBeNil) 349 defer rbig.Close() 350 351 rbig.Seek(350000, io.SeekStart) 352 b = make([]byte, 6) 353 done, err = io.ReadFull(rbig, b) 354 So(err, ShouldBeNil) 355 So(done, ShouldEqual, 6) 356 So(b, ShouldResemble, []byte("050001")) 357 358 rbig.Seek(175000, io.SeekStart) 359 b = make([]byte, 6) 360 done, err = io.ReadFull(rbig, b) 361 So(err, ShouldBeNil) 362 So(done, ShouldEqual, 6) 363 So(b, ShouldResemble, []byte("025001")) 364 }) 365 366 Convey("You can read a very big file", func() { 367 path := mountPoint + "/big.file" 368 start := time.Now() 369 read, err := streamFile(path, 0) 370 bigFileGetTime = time.Since(start) 371 So(err, ShouldBeNil) 372 So(read, ShouldEqual, bigFileSize) 373 }) 374 375 Convey("Reading a small part of a very big file doesn't download the entire file", func() { 376 path := mountPoint + "/big.file" 377 t := time.Now() 378 rbig, err := os.Open(path) 379 So(err, ShouldBeNil) 380 381 rbig.Seek(int64(bigFileSize/2), io.SeekStart) 382 b := make([]byte, 6) 383 done, err := io.ReadFull(rbig, b) 384 So(err, ShouldBeNil) 385 So(done, ShouldEqual, 6) 386 rbig.Close() 387 So(time.Since(t).Seconds(), ShouldBeLessThan, 1) 388 389 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("big.file")) 390 stat, err := os.Stat(cachePath) 391 So(err, ShouldBeNil) 392 So(stat.Size(), ShouldEqual, bigFileSize) 393 394 cmd := exec.Command("du", "-B1", "--apparent-size", cachePath) 395 out, err := cmd.CombinedOutput() 396 So(err, ShouldBeNil) 397 So(string(out), ShouldStartWith, fmt.Sprintf("%d\t", bigFileSize)) 398 399 // even though we seeked to half way and only tried to read 6 400 // bytes, the underlying system ends up sending a larger Read 401 // request around the desired point, where the size depends on 402 // the filesystem and other OS related things 403 404 cmd = exec.Command("du", "-B1", cachePath) 405 out, err = cmd.CombinedOutput() 406 So(err, ShouldBeNil) 407 parts := strings.Split(string(out), "\t") 408 i, err := strconv.Atoi(parts[0]) 409 So(err, ShouldBeNil) 410 So(i, ShouldBeGreaterThan, 6) 411 }) 412 413 Convey("You can read different parts of a file simultaneously from 1 mount", func() { 414 init := mountPoint + "/numalphanum.txt" 415 path := mountPoint + "/100k.lines" 416 417 // the first read takes longer than others, so read something 418 // to "initialise" minio 419 streamFile(init, 0) 420 421 // first get a reference for how long it takes to read the whole 422 // thing 423 t2 := time.Now() 424 read, errs := streamFile(path, 0) 425 wt := time.Since(t2) 426 So(errs, ShouldBeNil) 427 So(read, ShouldEqual, 700000) 428 429 // sanity check that re-reading uses our cache 430 t2 = time.Now() 431 streamFile(path, 0) 432 st := time.Since(t2) 433 434 // should have completed in under 90% of the time (since minio is 435 // local, the difference in reading from cache vs from minio is just 436 // the overhead of serving files from minio; if s3 was remote and 437 // slow this would be more like 20%) 438 et := time.Duration((wt.Nanoseconds()/100)*90) * time.Nanosecond 439 So(st, ShouldBeLessThan, et) 440 441 // remount to clear the cache 442 erru := fs.Unmount() 443 So(erru, ShouldBeNil) 444 errm := fs.Mount(remoteConfig) 445 So(errm, ShouldBeNil) 446 streamFile(init, 0) 447 448 // now read the whole file and half the file at the ~same time 449 times := make(chan time.Duration, 2) 450 errors := make(chan error, 2) 451 streamer := func(offset, size int) { 452 t := time.Now() 453 thisRead, thisErr := streamFile(path, int64(offset)) 454 times <- time.Since(t) 455 if thisErr != nil { 456 errors <- thisErr 457 return 458 } 459 if thisRead != int64(size) { 460 errors <- fmt.Errorf("did not read %d bytes for offset %d (%d)", size, offset, thisRead) 461 return 462 } 463 errors <- nil 464 } 465 466 t2 = time.Now() 467 var wg sync.WaitGroup 468 wg.Add(2) 469 go func() { 470 defer wg.Done() 471 streamer(350000, 350000) 472 }() 473 go func() { 474 defer wg.Done() 475 streamer(0, 700000) 476 }() 477 wg.Wait() 478 ot := time.Since(t2) 479 480 // both should complete in not much more time than the slowest, 481 // and that shouldn't be much slower than when reading alone 482 // *** debugging shows that caching definitely is occurring as 483 // expected, but I can't really prove it with these timings... 484 So(<-errors, ShouldBeNil) 485 So(<-errors, ShouldBeNil) 486 pt1 := <-times 487 pt2 := <-times 488 eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*110) * time.Nanosecond 489 // fmt.Printf("\nwt: %s, pt1: %s, pt2: %s, ot: %s, eto: %s, ets: %s\n", wt, pt1, pt2, ot, eto, ets) 490 So(ot, ShouldBeLessThan, eto) // *** this can rarely fail, just have to repeat :( 491 492 // *** unforunately the variability is too high, with both 493 // pt1 and pt2 sometimes taking more than 2x longer to read 494 // compared to wt, even though the below passes most of the time 495 // ets := time.Duration((wt.Nanoseconds()/100)*150) * time.Nanosecond 496 // So(ot, ShouldBeLessThan, ets) 497 }) 498 499 Convey("You can read different files simultaneously from 1 mount", func() { 500 init := mountPoint + "/numalphanum.txt" 501 path1 := mountPoint + "/100k.lines" 502 path2 := mountPoint + "/big.file" 503 504 streamFile(init, 0) 505 506 // first get a reference for how long it takes to read a certain 507 // sized chunk of each file 508 // t := time.Now() 509 read, err := streamFile(path1, 0) 510 // f1t := time.Since(t) 511 So(err, ShouldBeNil) 512 So(read, ShouldEqual, 700000) 513 514 // t = time.Now() 515 read, err = streamFile(path2, int64(bigFileSize-700000)) 516 // f2t := time.Since(t) 517 So(err, ShouldBeNil) 518 So(read, ShouldEqual, 700000) 519 520 // remount to clear the cache 521 err = fs.Unmount() 522 So(err, ShouldBeNil) 523 err = fs.Mount(remoteConfig) 524 So(err, ShouldBeNil) 525 streamFile(init, 0) 526 527 // now repeat reading them at the ~same time 528 times := make(chan time.Duration, 2) 529 errors := make(chan error, 2) 530 streamer := func(path string, offset, size int) { 531 t := time.Now() 532 thisRead, thisErr := streamFile(path, int64(offset)) 533 times <- time.Since(t) 534 if thisErr != nil { 535 errors <- thisErr 536 return 537 } 538 if thisRead != int64(size) { 539 errors <- fmt.Errorf("did not read %d bytes of %s at offset %d (%d)", size, path, offset, thisRead) 540 return 541 } 542 errors <- nil 543 } 544 545 t := time.Now() 546 var wg sync.WaitGroup 547 wg.Add(2) 548 go func() { 549 defer wg.Done() 550 streamer(path1, 0, 700000) 551 }() 552 go func() { 553 defer wg.Done() 554 streamer(path2, bigFileSize-700000, 700000) 555 }() 556 wg.Wait() 557 ot := time.Since(t) 558 559 // each should have completed in less than 190% of the time 560 // needed to read them sequentially, and both should have 561 // completed in less than 110% of the slowest one 562 So(<-errors, ShouldBeNil) 563 So(<-errors, ShouldBeNil) 564 pt1 := <-times 565 pt2 := <-times 566 // et1 := time.Duration((f1t.Nanoseconds()/100)*190) * time.Nanosecond 567 // et2 := time.Duration((f2t.Nanoseconds()/100)*190) * time.Nanosecond 568 var multiplier int64 569 if bigFileSize > 10000000 { 570 multiplier = 110 571 } else { 572 multiplier = 250 573 } 574 eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*multiplier) * time.Nanosecond 575 // *** these timing tests are too unreliable when using minio server 576 // So(pt1, ShouldBeLessThan, et1) 577 // So(pt2, ShouldBeLessThan, et2) 578 So(ot, ShouldBeLessThan, eto) 579 }) 580 581 Convey("Trying to write in non Write mode fails", func() { 582 path := mountPoint + "/write.test" 583 b := []byte("write test\n") 584 err := ioutil.WriteFile(path, b, 0644) 585 So(err, ShouldNotBeNil) 586 perr, ok := err.(*os.PathError) 587 So(ok, ShouldBeTrue) 588 So(perr.Error(), ShouldContainSubstring, "operation not permitted") 589 }) 590 591 Convey("You can't delete files either", func() { 592 path := mountPoint + "/big.file" 593 err := os.Remove(path) 594 So(err, ShouldNotBeNil) 595 perr, ok := err.(*os.PathError) 596 So(ok, ShouldBeTrue) 597 So(perr.Error(), ShouldContainSubstring, "operation not permitted") 598 }) 599 600 Convey("And you can't rename files", func() { 601 path := mountPoint + "/big.file" 602 dest := mountPoint + "/1G.moved" 603 cmd := exec.Command("mv", path, dest) 604 err := cmd.Run() 605 So(err, ShouldNotBeNil) 606 }) 607 608 Convey("You can't touch files in non Write mode", func() { 609 path := mountPoint + "/big.file" 610 cmd := exec.Command("touch", path) 611 err := cmd.Run() 612 So(err, ShouldNotBeNil) 613 }) 614 615 Convey("You can't make, delete or rename directories in non Write mode", func() { 616 newDir := mountPoint + "/newdir_test" 617 cmd := exec.Command("mkdir", newDir) 618 err := cmd.Run() 619 So(err, ShouldNotBeNil) 620 621 path := mountPoint + "/sub" 622 cmd = exec.Command("rmdir", path) 623 err = cmd.Run() 624 So(err, ShouldNotBeNil) 625 626 cmd = exec.Command("mv", path, newDir) 627 err = cmd.Run() 628 So(err, ShouldNotBeNil) 629 }) 630 631 Convey("Unmounting after reading a file deletes the cache dir", func() { 632 streamFile(mountPoint+"/numalphanum.txt", 0) 633 thisCacheDir := fs.remotes[0].cacheDir 634 _, err := os.Stat(thisCacheDir) 635 So(err, ShouldBeNil) 636 err = fs.Unmount() 637 So(err, ShouldBeNil) 638 _, err = os.Stat(thisCacheDir) 639 So(err, ShouldNotBeNil) 640 So(os.IsNotExist(err), ShouldBeTrue) 641 }) 642 }) 643 644 Convey("You can mount with local file caching in write mode", t, func() { 645 remoteConfig.Write = true 646 fs, errc := New(cfg) 647 So(errc, ShouldBeNil) 648 649 errm := fs.Mount(remoteConfig) 650 So(errm, ShouldBeNil) 651 652 defer func() { 653 erru := fs.Unmount() 654 remoteConfig.Write = false 655 So(erru, ShouldBeNil) 656 }() 657 658 Convey("Trying to write in write mode works", func() { 659 path := mountPoint + "/write.test" 660 b := []byte("write test\n") 661 errf := ioutil.WriteFile(path, b, 0644) 662 So(errf, ShouldBeNil) 663 664 // you can immediately read it back 665 bytes, errf := ioutil.ReadFile(path) 666 So(errf, ShouldBeNil) 667 So(bytes, ShouldResemble, b) 668 669 // (because it's in the the local cache) 670 thisCacheDir := fs.remotes[0].cacheDir 671 _, errf = os.Stat(thisCacheDir) 672 So(errf, ShouldBeNil) 673 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 674 _, errf = os.Stat(cachePath) 675 So(errf, ShouldBeNil) 676 677 // and it's statable and listable 678 _, errf = os.Stat(path) 679 So(errf, ShouldBeNil) 680 681 entries, errf := ioutil.ReadDir(mountPoint) 682 So(errf, ShouldBeNil) 683 details := dirDetails(entries) 684 rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"} 685 So(details, ShouldResemble, rootEntries) 686 687 // unmounting causes the local cached file to be deleted 688 errf = fs.Unmount() 689 So(errf, ShouldBeNil) 690 691 _, errf = os.Stat(cachePath) 692 So(errf, ShouldNotBeNil) 693 So(os.IsNotExist(errf), ShouldBeTrue) 694 _, errf = os.Stat(thisCacheDir) 695 So(errf, ShouldNotBeNil) 696 So(os.IsNotExist(errf), ShouldBeTrue) 697 _, errf = os.Stat(path) 698 So(errf, ShouldNotBeNil) 699 So(os.IsNotExist(errf), ShouldBeTrue) 700 701 // remounting lets us read the file again - it actually got 702 // uploaded 703 errf = fs.Mount(remoteConfig) 704 So(errf, ShouldBeNil) 705 706 cachePath = fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 707 _, errf = os.Stat(cachePath) 708 So(errf, ShouldNotBeNil) 709 So(os.IsNotExist(errf), ShouldBeTrue) 710 711 bytes, errf = ioutil.ReadFile(path) 712 So(errf, ShouldBeNil) 713 So(bytes, ShouldResemble, b) 714 715 _, errf = os.Stat(cachePath) 716 So(errf, ShouldBeNil) 717 718 Convey("You can append to a cached file", func() { 719 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 720 So(err, ShouldBeNil) 721 722 line2 := "line2\n" 723 _, err = f.WriteString(line2) 724 f.Close() 725 So(err, ShouldBeNil) 726 727 bytes, err = ioutil.ReadFile(path) 728 So(err, ShouldBeNil) 729 So(string(bytes), ShouldEqual, string(b)+line2) 730 731 err = fs.Unmount() 732 So(err, ShouldBeNil) 733 734 _, err = os.Stat(cachePath) 735 So(err, ShouldNotBeNil) 736 So(os.IsNotExist(err), ShouldBeTrue) 737 _, err = os.Stat(path) 738 So(err, ShouldNotBeNil) 739 So(os.IsNotExist(err), ShouldBeTrue) 740 741 err = fs.Mount(remoteConfig) 742 So(err, ShouldBeNil) 743 744 bytes, err = ioutil.ReadFile(path) 745 So(err, ShouldBeNil) 746 So(string(bytes), ShouldEqual, string(b)+line2) 747 748 Convey("You can truncate a cached file", func() { 749 err := os.Truncate(path, 0) 750 So(err, ShouldBeNil) 751 752 cachePath2 := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 753 stat, err := os.Stat(cachePath2) 754 So(err, ShouldBeNil) 755 So(stat.Size(), ShouldEqual, 0) 756 stat, err = os.Stat(path) 757 So(err, ShouldBeNil) 758 So(stat.Size(), ShouldEqual, 0) 759 760 err = fs.Unmount() 761 So(err, ShouldBeNil) 762 763 _, err = os.Stat(cachePath2) 764 So(err, ShouldNotBeNil) 765 So(os.IsNotExist(err), ShouldBeTrue) 766 767 err = fs.Mount(remoteConfig) 768 So(err, ShouldBeNil) 769 770 cachePath2 = fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 771 _, err = os.Stat(cachePath2) 772 So(err, ShouldNotBeNil) 773 So(os.IsNotExist(err), ShouldBeTrue) 774 stat, err = os.Stat(path) 775 So(err, ShouldBeNil) 776 So(stat.Size(), ShouldEqual, 0) 777 bytes, err = ioutil.ReadFile(path) 778 So(err, ShouldBeNil) 779 So(string(bytes), ShouldEqual, "") 780 781 Convey("You can delete files", func() { 782 err = os.Remove(path) 783 So(err, ShouldBeNil) 784 785 _, err = os.Stat(cachePath2) 786 So(err, ShouldNotBeNil) 787 So(os.IsNotExist(err), ShouldBeTrue) 788 _, err = os.Stat(path) 789 So(err, ShouldNotBeNil) 790 So(os.IsNotExist(err), ShouldBeTrue) 791 }) 792 }) 793 794 Convey("You can truncate a cached file using an offset", func() { 795 err := os.Truncate(path, 3) 796 So(err, ShouldBeNil) 797 798 cachePath2 := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 799 stat, err := os.Stat(cachePath2) 800 So(err, ShouldBeNil) 801 So(stat.Size(), ShouldEqual, 3) 802 stat, err = os.Stat(path) 803 So(err, ShouldBeNil) 804 So(stat.Size(), ShouldEqual, 3) 805 806 err = fs.Unmount() 807 So(err, ShouldBeNil) 808 809 _, err = os.Stat(cachePath2) 810 So(err, ShouldNotBeNil) 811 So(os.IsNotExist(err), ShouldBeTrue) 812 813 err = fs.Mount(remoteConfig) 814 So(err, ShouldBeNil) 815 816 cachePath2 = fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 817 _, err = os.Stat(cachePath2) 818 So(err, ShouldNotBeNil) 819 So(os.IsNotExist(err), ShouldBeTrue) 820 stat, err = os.Stat(path) 821 So(err, ShouldBeNil) 822 So(stat.Size(), ShouldEqual, 3) 823 bytes, err = ioutil.ReadFile(path) 824 So(err, ShouldBeNil) 825 So(string(bytes), ShouldEqual, "wri") 826 827 err = os.Remove(path) 828 So(err, ShouldBeNil) 829 }) 830 831 Convey("You can truncate a cached file and then write to it", func() { 832 err := os.Truncate(path, 0) 833 So(err, ShouldBeNil) 834 835 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 836 So(err, ShouldBeNil) 837 838 line := "trunc\n" 839 _, err = f.WriteString(line) 840 f.Close() 841 So(err, ShouldBeNil) 842 843 bytes, err = ioutil.ReadFile(path) 844 So(err, ShouldBeNil) 845 So(string(bytes), ShouldEqual, line) 846 847 cachePath2 := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 848 err = fs.Unmount() 849 So(err, ShouldBeNil) 850 851 _, err = os.Stat(cachePath2) 852 So(err, ShouldNotBeNil) 853 So(os.IsNotExist(err), ShouldBeTrue) 854 _, err = os.Stat(path) 855 So(err, ShouldNotBeNil) 856 So(os.IsNotExist(err), ShouldBeTrue) 857 858 err = fs.Mount(remoteConfig) 859 So(err, ShouldBeNil) 860 861 bytes, err = ioutil.ReadFile(path) 862 So(err, ShouldBeNil) 863 So(string(bytes), ShouldEqual, line) 864 865 err = os.Remove(path) 866 So(err, ShouldBeNil) 867 }) 868 }) 869 870 Convey("You can rename files using mv", func() { 871 dest := mountPoint + "/write.moved" 872 cmd := exec.Command("mv", path, dest) 873 err := cmd.Run() 874 So(err, ShouldBeNil) 875 876 bytes, err = ioutil.ReadFile(dest) 877 So(err, ShouldBeNil) 878 So(bytes, ShouldResemble, b) 879 880 _, err = os.Stat(path) 881 So(err, ShouldNotBeNil) 882 883 err = fs.Unmount() 884 So(err, ShouldBeNil) 885 err = fs.Mount(remoteConfig) 886 So(err, ShouldBeNil) 887 888 defer func() { 889 err = os.Remove(dest) 890 So(err, ShouldBeNil) 891 }() 892 893 bytes, err = ioutil.ReadFile(dest) 894 So(err, ShouldBeNil) 895 So(bytes, ShouldResemble, b) 896 897 _, err = os.Stat(dest) 898 So(err, ShouldBeNil) 899 900 _, err = os.Stat(path) 901 So(err, ShouldNotBeNil) 902 }) 903 904 Convey("You can rename uncached files using os.Rename", func() { 905 // unmount first to clear the cache 906 err := fs.Unmount() 907 So(err, ShouldBeNil) 908 err = fs.Mount(remoteConfig) 909 So(err, ShouldBeNil) 910 911 dest := mountPoint + "/write.moved" 912 err = os.Rename(path, dest) 913 So(err, ShouldBeNil) 914 915 _, err = os.Stat(cachePath) 916 So(err, ShouldNotBeNil) 917 cachePathDest := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.moved")) 918 _, err = os.Stat(cachePathDest) 919 So(err, ShouldNotBeNil) 920 921 bytes, err = ioutil.ReadFile(dest) 922 So(err, ShouldBeNil) 923 So(bytes, ShouldResemble, b) 924 925 _, err = os.Stat(path) 926 So(err, ShouldNotBeNil) 927 928 err = fs.Unmount() 929 So(err, ShouldBeNil) 930 err = fs.Mount(remoteConfig) 931 So(err, ShouldBeNil) 932 933 defer func() { 934 err = os.Remove(dest) 935 So(err, ShouldBeNil) 936 }() 937 938 bytes, err = ioutil.ReadFile(dest) 939 So(err, ShouldBeNil) 940 So(bytes, ShouldResemble, b) 941 942 _, err = os.Stat(dest) 943 So(err, ShouldBeNil) 944 945 _, err = os.Stat(path) 946 So(err, ShouldNotBeNil) 947 }) 948 949 Convey("You can rename cached and altered files", func() { 950 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 951 So(err, ShouldBeNil) 952 953 line2 := "line2\n" 954 _, err = f.WriteString(line2) 955 f.Close() 956 So(err, ShouldBeNil) 957 958 bytes, err = ioutil.ReadFile(path) 959 So(err, ShouldBeNil) 960 So(string(bytes), ShouldEqual, string(b)+line2) 961 962 dest := mountPoint + "/write.moved" 963 err = os.Rename(path, dest) 964 So(err, ShouldBeNil) 965 966 _, err = os.Stat(cachePath) 967 So(err, ShouldNotBeNil) 968 cachePathDest := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.moved")) 969 _, err = os.Stat(cachePathDest) 970 So(err, ShouldBeNil) 971 972 bytes, err = ioutil.ReadFile(dest) 973 So(err, ShouldBeNil) 974 So(string(bytes), ShouldEqual, string(b)+line2) 975 976 _, err = os.Stat(path) 977 So(err, ShouldNotBeNil) 978 979 err = fs.Unmount() 980 So(err, ShouldBeNil) 981 err = fs.Mount(remoteConfig) 982 So(err, ShouldBeNil) 983 984 defer func() { 985 err = os.Remove(dest) 986 So(err, ShouldBeNil) 987 }() 988 989 bytes, err = ioutil.ReadFile(dest) 990 So(err, ShouldBeNil) 991 So(string(bytes), ShouldEqual, string(b)+line2) 992 993 _, err = os.Stat(dest) 994 So(err, ShouldBeNil) 995 996 _, err = os.Stat(path) 997 So(err, ShouldNotBeNil) 998 }) 999 }) 1000 1001 Convey("You can't rename remote directories", func() { 1002 newDir := mountPoint + "/newdir_test" 1003 subDir := mountPoint + "/sub" 1004 cmd := exec.Command("mv", subDir, newDir) 1005 err := cmd.Run() 1006 So(err, ShouldNotBeNil) 1007 }) 1008 1009 Convey("You can't remove remote directories", func() { 1010 subDir := mountPoint + "/sub" 1011 cmd := exec.Command("rmdir", subDir) 1012 err := cmd.Run() 1013 So(err, ShouldNotBeNil) 1014 }) 1015 1016 Convey("You can create directories and rename and remove those", func() { 1017 newDir := mountPoint + "/newdir_test" 1018 cmd := exec.Command("mkdir", newDir) 1019 err := cmd.Run() 1020 So(err, ShouldBeNil) 1021 1022 entries, err := ioutil.ReadDir(mountPoint) 1023 So(err, ShouldBeNil) 1024 details := dirDetails(entries) 1025 rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "newdir_test:dir", "numalphanum.txt:file:47", "sub:dir"} 1026 So(details, ShouldResemble, rootEntries) 1027 1028 movedDir := mountPoint + "/newdir_moved" 1029 cmd = exec.Command("mv", newDir, movedDir) 1030 err = cmd.Run() 1031 So(err, ShouldBeNil) 1032 1033 entries, err = ioutil.ReadDir(mountPoint) 1034 So(err, ShouldBeNil) 1035 details = dirDetails(entries) 1036 rootEntries = []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "newdir_moved:dir", "numalphanum.txt:file:47", "sub:dir"} 1037 So(details, ShouldResemble, rootEntries) 1038 1039 cmd = exec.Command("rmdir", movedDir) 1040 err = cmd.Run() 1041 So(err, ShouldBeNil) 1042 1043 Convey("You can create nested directories and add files to them", func() { 1044 nestedDir := mountPoint + "/newdir_test/a/b/c" 1045 err = os.MkdirAll(nestedDir, os.FileMode(700)) 1046 So(err, ShouldBeNil) 1047 1048 path := nestedDir + "/write.nested" 1049 b := []byte("nested test\n") 1050 err := ioutil.WriteFile(path, b, 0644) 1051 So(err, ShouldBeNil) 1052 1053 bytes, err := ioutil.ReadFile(path) 1054 So(err, ShouldBeNil) 1055 So(bytes, ShouldResemble, b) 1056 1057 entries, err := ioutil.ReadDir(nestedDir) 1058 So(err, ShouldBeNil) 1059 details := dirDetails(entries) 1060 nestEntries := []string{"write.nested:file:12"} 1061 So(details, ShouldResemble, nestEntries) 1062 1063 err = fs.Unmount() 1064 So(err, ShouldBeNil) 1065 err = fs.Mount(remoteConfig) 1066 So(err, ShouldBeNil) 1067 1068 bytes, err = ioutil.ReadFile(path) 1069 So(err, ShouldBeNil) 1070 So(bytes, ShouldResemble, b) 1071 1072 os.Remove(path) 1073 }) 1074 }) 1075 1076 Convey("Given a local directory", func() { 1077 mvDir := filepath.Join(tmpdir, "mvtest") 1078 mvSubDir := filepath.Join(mvDir, "mvsubdir") 1079 errf := os.MkdirAll(mvSubDir, os.FileMode(0700)) 1080 So(errf, ShouldBeNil) 1081 mvFile := filepath.Join(mvSubDir, "file") 1082 mvBytes := []byte("mvfile\n") 1083 errf = ioutil.WriteFile(mvFile, mvBytes, 0644) 1084 So(errf, ShouldBeNil) 1085 errf = ioutil.WriteFile(filepath.Join(mvDir, "a.file"), mvBytes, 0644) 1086 So(errf, ShouldBeNil) 1087 1088 Convey("You can mv it to the mount point", func() { 1089 mountDir := filepath.Join(mountPoint, "mvtest") 1090 dest := filepath.Join(mountDir, "mvsubdir", "file") 1091 dest2 := filepath.Join(mountDir, "a.file") 1092 1093 cmd := exec.Command("mv", mvDir, mountDir) 1094 err := cmd.Run() 1095 So(err, ShouldBeNil) 1096 1097 bytes, err := ioutil.ReadFile(dest) 1098 So(err, ShouldBeNil) 1099 So(bytes, ShouldResemble, mvBytes) 1100 1101 err = fs.Unmount() 1102 So(err, ShouldBeNil) 1103 err = fs.Mount(remoteConfig) 1104 So(err, ShouldBeNil) 1105 1106 defer func() { 1107 err = os.Remove(dest) 1108 So(err, ShouldBeNil) 1109 err = os.Remove(dest2) 1110 So(err, ShouldBeNil) 1111 }() 1112 1113 bytes, err = ioutil.ReadFile(dest) 1114 So(err, ShouldBeNil) 1115 So(bytes, ShouldResemble, mvBytes) 1116 }) 1117 1118 Convey("You can mv its contents to the mount point", func() { 1119 dest := filepath.Join(mountPoint, "mvsubdir", "file") 1120 dest2 := filepath.Join(mountPoint, "a.file") 1121 1122 cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s/* %s/", mvDir, mountPoint)) 1123 err := cmd.Run() 1124 So(err, ShouldBeNil) 1125 1126 bytes, err := ioutil.ReadFile(dest) 1127 So(err, ShouldBeNil) 1128 So(bytes, ShouldResemble, mvBytes) 1129 1130 err = fs.Unmount() 1131 So(err, ShouldBeNil) 1132 err = fs.Mount(remoteConfig) 1133 So(err, ShouldBeNil) 1134 1135 defer func() { 1136 err = os.Remove(dest) 1137 So(err, ShouldBeNil) 1138 err = os.Remove(dest2) 1139 So(err, ShouldBeNil) 1140 }() 1141 1142 bytes, err = ioutil.ReadFile(dest) 1143 So(err, ShouldBeNil) 1144 So(bytes, ShouldResemble, mvBytes) 1145 }) 1146 }) 1147 1148 Convey("Trying to read a non-existent file fails as expected", func() { 1149 name := "non-existent.file" 1150 path := mountPoint + "/" + name 1151 _, err := streamFile(path, 0) 1152 So(err, ShouldNotBeNil) 1153 So(os.IsNotExist(err), ShouldBeTrue) 1154 }) 1155 1156 Convey("Trying to read an externally deleted file fails as expected", func() { 1157 name := "non-existent.file" 1158 path := mountPoint + "/" + name 1159 // we'll hack fs to make it think non-existent.file does exist 1160 // so we can test the behaviour of a file getting deleted 1161 // externally 1162 //ioutil.ReadDir(mountPoint) // *** can't figure out why this causes a race condition 1163 fs.GetAttr("/", &fuse.Context{}) 1164 So(fs.files["big.file"], ShouldNotBeNil) 1165 So(fs.files[name], ShouldBeNil) 1166 fs.mapMutex.Lock() 1167 fs.addNewEntryToItsDir(name, fuse.S_IFREG) 1168 fs.files[name] = fs.files["big.file"] 1169 fs.fileToRemote[name] = fs.fileToRemote["big.file"] 1170 fs.mapMutex.Unlock() 1171 So(fs.files[name], ShouldNotBeNil) 1172 _, err := streamFile(path, 0) 1173 So(err, ShouldNotBeNil) 1174 So(os.IsNotExist(err), ShouldBeTrue) 1175 So(fs.files[name], ShouldNotBeNil) // *** unfortunately we only know it doesn't exist when we try to read, which means we can't update fs 1176 }) 1177 1178 Convey("In write mode, you can create a file to test with...", func() { 1179 // create a file we can play with first 1180 path := mountPoint + "/write.test" 1181 b := []byte("write test\n") 1182 err := ioutil.WriteFile(path, b, 0644) 1183 So(err, ShouldBeNil) 1184 1185 err = fs.Unmount() 1186 So(err, ShouldBeNil) 1187 1188 defer func() { 1189 err = os.Remove(path) 1190 So(err, ShouldBeNil) 1191 }() 1192 1193 err = fs.Mount(remoteConfig) 1194 So(err, ShouldBeNil) 1195 1196 Convey("You can't write to a file you open RDONLY", func() { 1197 f, err := os.OpenFile(path, os.O_RDONLY, 0644) 1198 So(err, ShouldBeNil) 1199 _, err = f.WriteString("fails\n") 1200 f.Close() 1201 So(err, ShouldNotBeNil) 1202 }) 1203 1204 Convey("You can append to an uncached file", func() { 1205 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 1206 So(err, ShouldBeNil) 1207 1208 line2 := "line2\n" 1209 _, err = f.WriteString(line2) 1210 f.Close() 1211 So(err, ShouldBeNil) 1212 1213 bytes, err := ioutil.ReadFile(path) 1214 So(err, ShouldBeNil) 1215 So(string(bytes), ShouldEqual, string(b)+line2) 1216 1217 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 1218 err = fs.Unmount() 1219 So(err, ShouldBeNil) 1220 1221 _, err = os.Stat(cachePath) 1222 So(err, ShouldNotBeNil) 1223 So(os.IsNotExist(err), ShouldBeTrue) 1224 _, err = os.Stat(path) 1225 So(err, ShouldNotBeNil) 1226 So(os.IsNotExist(err), ShouldBeTrue) 1227 1228 err = fs.Mount(remoteConfig) 1229 So(err, ShouldBeNil) 1230 1231 bytes, err = ioutil.ReadFile(path) 1232 So(err, ShouldBeNil) 1233 So(string(bytes), ShouldEqual, string(b)+line2) 1234 }) 1235 1236 Convey("You can append to an uncached file and upload without reading the original part of the file", func() { 1237 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 1238 So(err, ShouldBeNil) 1239 1240 line2 := "line2\n" 1241 _, err = f.WriteString(line2) 1242 f.Close() 1243 So(err, ShouldBeNil) 1244 1245 err = fs.Unmount() 1246 So(err, ShouldBeNil) 1247 err = fs.Mount(remoteConfig) 1248 So(err, ShouldBeNil) 1249 1250 bytes, err := ioutil.ReadFile(path) 1251 So(err, ShouldBeNil) 1252 So(string(bytes), ShouldEqual, string(b)+line2) 1253 }) 1254 1255 Convey("You can append to a partially read file", func() { 1256 // first make the file bigger so we can avoid minimum file 1257 // read size issues 1258 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 1259 So(err, ShouldBeNil) 1260 1261 for i := 2; i <= 10000; i++ { 1262 _, err = f.WriteString(fmt.Sprintf("line%d\n", i)) 1263 if err != nil { 1264 break 1265 } 1266 } 1267 f.Close() 1268 So(err, ShouldBeNil) 1269 1270 err = fs.Unmount() 1271 So(err, ShouldBeNil) 1272 err = fs.Mount(remoteConfig) 1273 So(err, ShouldBeNil) 1274 1275 // now do a partial read 1276 r, err := os.Open(path) 1277 So(err, ShouldBeNil) 1278 1279 r.Seek(11, io.SeekStart) 1280 1281 b := make([]byte, 5) 1282 done, err := io.ReadFull(r, b) 1283 r.Close() 1284 So(err, ShouldBeNil) 1285 So(done, ShouldEqual, 5) 1286 So(string(b), ShouldEqual, "line2") 1287 1288 info, err := os.Stat(path) 1289 So(err, ShouldBeNil) 1290 So(info.Size(), ShouldEqual, 88899) 1291 1292 // now append 1293 f, err = os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 1294 So(err, ShouldBeNil) 1295 1296 newline := "line10001\n" 1297 _, err = f.WriteString(newline) 1298 f.Close() 1299 So(err, ShouldBeNil) 1300 1301 err = fs.Unmount() 1302 So(err, ShouldBeNil) 1303 err = fs.Mount(remoteConfig) 1304 So(err, ShouldBeNil) 1305 1306 // check it worked correctly 1307 info, err = os.Stat(path) 1308 So(err, ShouldBeNil) 1309 So(info.Size(), ShouldEqual, 88909) 1310 1311 r, err = os.Open(path) 1312 So(err, ShouldBeNil) 1313 1314 r.Seek(11, io.SeekStart) 1315 1316 b = make([]byte, 5) 1317 done, err = io.ReadFull(r, b) 1318 So(err, ShouldBeNil) 1319 So(done, ShouldEqual, 5) 1320 So(string(b), ShouldEqual, "line2") 1321 1322 r.Seek(88889, io.SeekStart) 1323 1324 b = make([]byte, 19) 1325 done, err = io.ReadFull(r, b) 1326 r.Close() 1327 So(err, ShouldBeNil) 1328 So(done, ShouldEqual, 19) 1329 So(string(b), ShouldEqual, "line10000\nline10001") 1330 }) 1331 1332 Convey("You can truncate an uncached file", func() { 1333 err := os.Truncate(path, 0) 1334 So(err, ShouldBeNil) 1335 1336 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 1337 stat, err := os.Stat(cachePath) 1338 So(err, ShouldBeNil) 1339 So(stat.Size(), ShouldEqual, 0) 1340 stat, err = os.Stat(path) 1341 So(err, ShouldBeNil) 1342 So(stat.Size(), ShouldEqual, 0) 1343 1344 err = fs.Unmount() 1345 So(err, ShouldBeNil) 1346 1347 _, err = os.Stat(cachePath) 1348 So(err, ShouldNotBeNil) 1349 So(os.IsNotExist(err), ShouldBeTrue) 1350 1351 err = fs.Mount(remoteConfig) 1352 So(err, ShouldBeNil) 1353 1354 _, err = os.Stat(cachePath) 1355 So(err, ShouldNotBeNil) 1356 So(os.IsNotExist(err), ShouldBeTrue) 1357 stat, err = os.Stat(path) 1358 So(err, ShouldBeNil) 1359 So(stat.Size(), ShouldEqual, 0) 1360 bytes, err := ioutil.ReadFile(path) 1361 So(err, ShouldBeNil) 1362 So(string(bytes), ShouldEqual, "") 1363 }) 1364 1365 Convey("You can truncate an uncached file using an offset", func() { 1366 err := os.Truncate(path, 3) 1367 So(err, ShouldBeNil) 1368 1369 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 1370 stat, err := os.Stat(cachePath) 1371 So(err, ShouldBeNil) 1372 So(stat.Size(), ShouldEqual, 3) 1373 stat, err = os.Stat(path) 1374 So(err, ShouldBeNil) 1375 So(stat.Size(), ShouldEqual, 3) 1376 1377 err = fs.Unmount() 1378 So(err, ShouldBeNil) 1379 1380 _, err = os.Stat(cachePath) 1381 So(err, ShouldNotBeNil) 1382 So(os.IsNotExist(err), ShouldBeTrue) 1383 1384 err = fs.Mount(remoteConfig) 1385 So(err, ShouldBeNil) 1386 1387 _, err = os.Stat(cachePath) 1388 So(err, ShouldNotBeNil) 1389 So(os.IsNotExist(err), ShouldBeTrue) 1390 stat, err = os.Stat(path) 1391 So(err, ShouldBeNil) 1392 So(stat.Size(), ShouldEqual, 3) 1393 bytes, err := ioutil.ReadFile(path) 1394 So(err, ShouldBeNil) 1395 So(string(bytes), ShouldEqual, "wri") 1396 }) 1397 1398 Convey("You can truncate an uncached file using an Open call", func() { 1399 f, err := os.OpenFile(path, os.O_TRUNC, 0644) 1400 So(err, ShouldBeNil) 1401 f.Close() 1402 1403 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 1404 stat, err := os.Stat(cachePath) 1405 So(err, ShouldBeNil) 1406 So(stat.Size(), ShouldEqual, 0) 1407 stat, err = os.Stat(path) 1408 So(err, ShouldBeNil) 1409 So(stat.Size(), ShouldEqual, 0) 1410 1411 err = fs.Unmount() 1412 So(err, ShouldBeNil) 1413 1414 _, err = os.Stat(cachePath) 1415 So(err, ShouldNotBeNil) 1416 So(os.IsNotExist(err), ShouldBeTrue) 1417 1418 err = fs.Mount(remoteConfig) 1419 So(err, ShouldBeNil) 1420 1421 _, err = os.Stat(cachePath) 1422 So(err, ShouldNotBeNil) 1423 So(os.IsNotExist(err), ShouldBeTrue) 1424 stat, err = os.Stat(path) 1425 So(err, ShouldBeNil) 1426 So(stat.Size(), ShouldEqual, 0) 1427 bytes, err := ioutil.ReadFile(path) 1428 So(err, ShouldBeNil) 1429 So(string(bytes), ShouldEqual, "") 1430 }) 1431 1432 Convey("You can truncate an uncached file and immediately write to it", func() { 1433 err := os.Truncate(path, 0) 1434 So(err, ShouldBeNil) 1435 1436 f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644) 1437 So(err, ShouldBeNil) 1438 1439 line := "trunc\n" 1440 _, err = f.WriteString(line) 1441 f.Close() 1442 So(err, ShouldBeNil) 1443 1444 bytes, err := ioutil.ReadFile(path) 1445 So(err, ShouldBeNil) 1446 So(string(bytes), ShouldEqual, line) 1447 1448 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 1449 err = fs.Unmount() 1450 So(err, ShouldBeNil) 1451 1452 _, err = os.Stat(cachePath) 1453 So(err, ShouldNotBeNil) 1454 So(os.IsNotExist(err), ShouldBeTrue) 1455 _, err = os.Stat(path) 1456 So(err, ShouldNotBeNil) 1457 So(os.IsNotExist(err), ShouldBeTrue) 1458 1459 err = fs.Mount(remoteConfig) 1460 So(err, ShouldBeNil) 1461 1462 bytes, err = ioutil.ReadFile(path) 1463 So(err, ShouldBeNil) 1464 So(string(bytes), ShouldEqual, line) 1465 }) 1466 1467 SkipConvey("You can truncate an uncached file using an Open call and write to it", func() { 1468 f, err := os.OpenFile(path, os.O_TRUNC|os.O_WRONLY, 0644) 1469 So(err, ShouldBeNil) 1470 //*** this fails because it results in an fs.Open() call 1471 // where I see the os.O_WRONLY flag but not the os.O_TRUNC 1472 // flag 1473 1474 line := "trunc\n" 1475 _, err = f.WriteString(line) 1476 f.Close() 1477 So(err, ShouldBeNil) 1478 1479 bytes, err := ioutil.ReadFile(path) 1480 So(err, ShouldBeNil) 1481 So(string(bytes), ShouldEqual, line) 1482 1483 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 1484 err = fs.Unmount() 1485 So(err, ShouldBeNil) 1486 1487 _, err = os.Stat(cachePath) 1488 So(err, ShouldNotBeNil) 1489 So(os.IsNotExist(err), ShouldBeTrue) 1490 _, err = os.Stat(path) 1491 So(err, ShouldNotBeNil) 1492 So(os.IsNotExist(err), ShouldBeTrue) 1493 1494 err = fs.Mount(remoteConfig) 1495 So(err, ShouldBeNil) 1496 1497 bytes, err = ioutil.ReadFile(path) 1498 So(err, ShouldBeNil) 1499 So(string(bytes), ShouldEqual, line) 1500 }) 1501 1502 Convey("You can write to the mount point and immediately delete the file and get the correct listing", func() { 1503 entries, err := ioutil.ReadDir(mountPoint) 1504 So(err, ShouldBeNil) 1505 details := dirDetails(entries) 1506 subEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"} 1507 So(details, ShouldResemble, subEntries) 1508 1509 path2 := mountPoint + "/write.test2" 1510 b := []byte("write test2\n") 1511 err = ioutil.WriteFile(path2, b, 0644) 1512 So(err, ShouldBeNil) 1513 1514 // it's statable and listable 1515 _, err = os.Stat(path2) 1516 So(err, ShouldBeNil) 1517 1518 entries, err = ioutil.ReadDir(mountPoint) 1519 So(err, ShouldBeNil) 1520 details = dirDetails(entries) 1521 subEntries = []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test2:file:12", "write.test:file:11"} 1522 So(details, ShouldResemble, subEntries) 1523 1524 // once deleted, it's no longer listed 1525 err = os.Remove(path2) 1526 So(err, ShouldBeNil) 1527 1528 _, err = os.Stat(path2) 1529 So(err, ShouldNotBeNil) 1530 1531 entries, err = ioutil.ReadDir(mountPoint) 1532 So(err, ShouldBeNil) 1533 details = dirDetails(entries) 1534 subEntries = []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"} 1535 So(details, ShouldResemble, subEntries) 1536 1537 // running unix ls reveals problems that ReadDir doesn't 1538 cmd := exec.Command("ls", "-alth", mountPoint) 1539 err = cmd.Run() 1540 So(err, ShouldBeNil) 1541 }) 1542 1543 Convey("You can touch an uncached file", func() { 1544 info, err := os.Stat(path) 1545 So(err, ShouldBeNil) 1546 cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path) 1547 err = cmd.Run() 1548 So(err, ShouldBeNil) 1549 info2, err := os.Stat(path) 1550 So(err, ShouldBeNil) 1551 So(info.ModTime().Unix(), ShouldNotAlmostEqual, info2.ModTime().Unix(), 62) 1552 So(info2.ModTime().String(), ShouldStartWith, "2006-01-02 15:04:05 +0000") 1553 }) 1554 1555 Convey("You can immediately touch an uncached file", func() { 1556 cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path) 1557 err := cmd.Run() 1558 So(err, ShouldBeNil) 1559 1560 // (looking at the contents of a subdir revealed a bug) 1561 entries, err := ioutil.ReadDir(mountPoint + "/sub") 1562 So(err, ShouldBeNil) 1563 details := dirDetails(entries) 1564 subEntries := []string{"deep:dir", "empty.file:file:0"} 1565 So(details, ShouldResemble, subEntries) 1566 1567 info, err := os.Stat(path) 1568 So(err, ShouldBeNil) 1569 So(info.ModTime().String(), ShouldStartWith, "2006-01-02 15:04:05 +0000") 1570 }) 1571 1572 Convey("You can touch a cached file", func() { 1573 info, err := os.Stat(path) 1574 So(err, ShouldBeNil) 1575 _, err = ioutil.ReadFile(path) 1576 So(err, ShouldBeNil) 1577 1578 cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path) 1579 err = cmd.Run() 1580 So(err, ShouldBeNil) 1581 info2, err := os.Stat(path) 1582 So(err, ShouldBeNil) 1583 So(info.ModTime().Unix(), ShouldNotAlmostEqual, info2.ModTime().Unix(), 62) 1584 So(info2.ModTime().String(), ShouldStartWith, "2006-01-02 15:04:05 +0000") 1585 1586 cmd = exec.Command("touch", "-d", "2007-01-02 15:04:05", path) 1587 err = cmd.Run() 1588 So(err, ShouldBeNil) 1589 info3, err := os.Stat(path) 1590 So(err, ShouldBeNil) 1591 So(info2.ModTime().Unix(), ShouldNotAlmostEqual, info3.ModTime().Unix(), 62) 1592 So(info3.ModTime().String(), ShouldStartWith, "2007-01-02 15:04:05 +0000") 1593 }) 1594 1595 Convey("You can directly change the mtime on a cached file", func() { 1596 info, err := os.Stat(path) 1597 So(err, ShouldBeNil) 1598 _, err = ioutil.ReadFile(path) 1599 So(err, ShouldBeNil) 1600 1601 t := time.Now().Add(5 * time.Minute) 1602 err = os.Chtimes(path, t, t) 1603 So(err, ShouldBeNil) 1604 info2, err := os.Stat(path) 1605 So(err, ShouldBeNil) 1606 So(info.ModTime().Unix(), ShouldNotAlmostEqual, info2.ModTime().Unix(), 62) 1607 So(info2.ModTime().Unix(), ShouldAlmostEqual, t.Unix(), 2) 1608 }) 1609 1610 Convey("But not an uncached one", func() { 1611 info, err := os.Stat(path) 1612 So(err, ShouldBeNil) 1613 t := time.Now().Add(5 * time.Minute) 1614 err = os.Chtimes(path, t, t) 1615 So(err, ShouldBeNil) 1616 info2, err := os.Stat(path) 1617 So(err, ShouldBeNil) 1618 So(info.ModTime().Unix(), ShouldAlmostEqual, info2.ModTime().Unix(), 62) 1619 So(info2.ModTime().Unix(), ShouldNotAlmostEqual, t.Unix(), 2) 1620 }) 1621 }) 1622 1623 Convey("You can immediately write in to a subdirectory", func() { 1624 path := mountPoint + "/sub/write.test" 1625 b := []byte("write test\n") 1626 err := ioutil.WriteFile(path, b, 0644) 1627 So(err, ShouldBeNil) 1628 1629 defer func() { 1630 err = os.Remove(path) 1631 So(err, ShouldBeNil) 1632 }() 1633 1634 // you can immediately read it back 1635 bytes, err := ioutil.ReadFile(path) 1636 So(err, ShouldBeNil) 1637 So(bytes, ShouldResemble, b) 1638 1639 // and it's statable and listable 1640 _, err = os.Stat(path) 1641 So(err, ShouldBeNil) 1642 1643 entries, err := ioutil.ReadDir(mountPoint + "/sub") 1644 So(err, ShouldBeNil) 1645 details := dirDetails(entries) 1646 subEntries := []string{"deep:dir", "empty.file:file:0", "write.test:file:11"} 1647 So(details, ShouldResemble, subEntries) 1648 1649 err = fs.Unmount() 1650 So(err, ShouldBeNil) 1651 1652 // remounting lets us read the file again - it actually got 1653 // uploaded 1654 err = fs.Mount(remoteConfig) 1655 So(err, ShouldBeNil) 1656 1657 bytes, err = ioutil.ReadFile(path) 1658 So(err, ShouldBeNil) 1659 So(bytes, ShouldResemble, b) 1660 }) 1661 1662 Convey("You can write in to a subdirectory that has been previously listed", func() { 1663 entries, err := ioutil.ReadDir(mountPoint + "/sub") 1664 So(err, ShouldBeNil) 1665 details := dirDetails(entries) 1666 subEntries := []string{"deep:dir", "empty.file:file:0"} 1667 So(details, ShouldResemble, subEntries) 1668 1669 path := mountPoint + "/sub/write.test" 1670 b := []byte("write test\n") 1671 err = ioutil.WriteFile(path, b, 0644) 1672 So(err, ShouldBeNil) 1673 1674 defer func() { 1675 err = os.Remove(path) 1676 So(err, ShouldBeNil) 1677 }() 1678 1679 // you can immediately read it back 1680 bytes, err := ioutil.ReadFile(path) 1681 So(err, ShouldBeNil) 1682 So(bytes, ShouldResemble, b) 1683 1684 // and it's statable and listable 1685 _, err = os.Stat(path) 1686 So(err, ShouldBeNil) 1687 1688 entries, err = ioutil.ReadDir(mountPoint + "/sub") 1689 So(err, ShouldBeNil) 1690 details = dirDetails(entries) 1691 subEntries = []string{"deep:dir", "empty.file:file:0", "write.test:file:11"} 1692 So(details, ShouldResemble, subEntries) 1693 1694 err = fs.Unmount() 1695 So(err, ShouldBeNil) 1696 1697 // remounting lets us read the file again - it actually got 1698 // uploaded 1699 err = fs.Mount(remoteConfig) 1700 So(err, ShouldBeNil) 1701 1702 bytes, err = ioutil.ReadFile(path) 1703 So(err, ShouldBeNil) 1704 So(bytes, ShouldResemble, b) 1705 }) 1706 1707 Convey("You can write in to a subdirectory and immediately delete the file and get the correct listing", func() { 1708 entries, err := ioutil.ReadDir(mountPoint + "/sub") 1709 So(err, ShouldBeNil) 1710 details := dirDetails(entries) 1711 subEntries := []string{"deep:dir", "empty.file:file:0"} 1712 So(details, ShouldResemble, subEntries) 1713 1714 path := mountPoint + "/sub/write.test" 1715 b := []byte("write test\n") 1716 err = ioutil.WriteFile(path, b, 0644) 1717 So(err, ShouldBeNil) 1718 1719 // it's statable and listable 1720 _, err = os.Stat(path) 1721 So(err, ShouldBeNil) 1722 1723 entries, err = ioutil.ReadDir(mountPoint + "/sub") 1724 So(err, ShouldBeNil) 1725 details = dirDetails(entries) 1726 subEntries = []string{"deep:dir", "empty.file:file:0", "write.test:file:11"} 1727 So(details, ShouldResemble, subEntries) 1728 1729 // once deleted, it's no longer listed 1730 err = os.Remove(path) 1731 So(err, ShouldBeNil) 1732 1733 _, err = os.Stat(path) 1734 So(err, ShouldNotBeNil) 1735 1736 entries, err = ioutil.ReadDir(mountPoint + "/sub") 1737 So(err, ShouldBeNil) 1738 details = dirDetails(entries) 1739 subEntries = []string{"deep:dir", "empty.file:file:0"} 1740 So(details, ShouldResemble, subEntries) 1741 1742 // running unix ls reveals problems that ReadDir doesn't 1743 cmd := exec.Command("ls", "-alth", mountPoint+"/sub") 1744 err = cmd.Run() 1745 So(err, ShouldBeNil) 1746 }) 1747 1748 Convey("You can touch a non-existent file", func() { 1749 path := mountPoint + "/write.test" 1750 cmd := exec.Command("touch", path) 1751 err := cmd.Run() 1752 defer func() { 1753 err = os.Remove(path) 1754 So(err, ShouldBeNil) 1755 }() 1756 So(err, ShouldBeNil) 1757 }) 1758 1759 Convey("You can write multiple files and they get uploaded in final mtime order", func() { 1760 path1 := mountPoint + "/write.test1" 1761 b := []byte("write test1\n") 1762 err := ioutil.WriteFile(path1, b, 0644) 1763 So(err, ShouldBeNil) 1764 1765 path2 := mountPoint + "/write.test2" 1766 b = []byte("write test2\n") 1767 err = ioutil.WriteFile(path2, b, 0644) 1768 So(err, ShouldBeNil) 1769 1770 path3 := mountPoint + "/write.test3" 1771 b = []byte("write test3\n") 1772 err = ioutil.WriteFile(path3, b, 0644) 1773 So(err, ShouldBeNil) 1774 1775 cmd := exec.Command("touch", "-d", "2006-01-02 15:04:05", path2) 1776 err = cmd.Run() 1777 So(err, ShouldBeNil) 1778 1779 t := time.Now().Add(5 * time.Minute) 1780 err = os.Chtimes(path1, t, t) 1781 So(err, ShouldBeNil) 1782 1783 err = fs.Unmount() 1784 So(err, ShouldBeNil) 1785 1786 defer func() { 1787 os.Remove(path1) 1788 os.Remove(path2) 1789 os.Remove(path3) 1790 }() 1791 1792 err = fs.Mount(remoteConfig) 1793 So(err, ShouldBeNil) 1794 1795 info1, err := os.Stat(path1) 1796 So(err, ShouldBeNil) 1797 info2, err := os.Stat(path2) 1798 So(err, ShouldBeNil) 1799 info3, err := os.Stat(path3) 1800 So(err, ShouldBeNil) 1801 So(info2.ModTime().Unix(), ShouldBeLessThanOrEqualTo, info3.ModTime().Unix()) 1802 So(info3.ModTime().Unix(), ShouldBeLessThanOrEqualTo, info1.ModTime().Unix()) 1803 1804 // *** unfortunately they only get second-resolution mtimes, and 1805 // they all get uploaded in the same second, so this isn't a very 1806 // good test... need uploads that take more than 1 second each... 1807 }) 1808 1809 Convey("You can't create hard links", func() { 1810 source := mountPoint + "/numalphanum.txt" 1811 dest := mountPoint + "/link.hard" 1812 err := os.Link(source, dest) 1813 So(err, ShouldNotBeNil) 1814 }) 1815 1816 Convey("You can create and use symbolic links", func() { 1817 source := mountPoint + "/numalphanum.txt" 1818 dest := mountPoint + "/link.soft" 1819 err := os.Symlink(source, dest) 1820 So(err, ShouldBeNil) 1821 bytes, err := ioutil.ReadFile(dest) 1822 So(err, ShouldBeNil) 1823 So(string(bytes), ShouldEqual, "1234567890abcdefghijklmnopqrstuvwxyz1234567890\n") 1824 1825 info, err := os.Lstat(dest) 1826 So(err, ShouldBeNil) 1827 So(info.Size(), ShouldEqual, 7) 1828 1829 d, err := os.Readlink(dest) 1830 So(err, ShouldBeNil) 1831 So(d, ShouldEqual, source) 1832 1833 Convey("But they're not uploaded", func() { 1834 err = fs.Unmount() 1835 So(err, ShouldBeNil) 1836 err = fs.Mount(remoteConfig) 1837 So(err, ShouldBeNil) 1838 1839 _, err = os.Stat(dest) 1840 So(err, ShouldNotBeNil) 1841 }) 1842 1843 Convey("You can delete them", func() { 1844 err = os.Remove(dest) 1845 So(err, ShouldBeNil) 1846 _, err = os.Stat(dest) 1847 So(err, ShouldNotBeNil) 1848 }) 1849 }) 1850 }) 1851 1852 Convey("You can mount with local file caching in an explicit location", t, func() { 1853 remoteConfig.CacheDir = cacheDir 1854 fs, err := New(cfg) 1855 So(err, ShouldBeNil) 1856 1857 err = fs.Mount(remoteConfig) 1858 So(err, ShouldBeNil) 1859 1860 defer func() { 1861 err = fs.Unmount() 1862 remoteConfig.CacheDir = "" 1863 So(err, ShouldBeNil) 1864 }() 1865 1866 path := mountPoint + "/numalphanum.txt" 1867 _, err = ioutil.ReadFile(path) 1868 So(err, ShouldBeNil) 1869 1870 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("numalphanum.txt")) 1871 _, err = os.Stat(cachePath) 1872 So(err, ShouldBeNil) 1873 So(cachePath, ShouldStartWith, cacheDir) 1874 1875 Convey("Unmounting doesn't delete the cache", func() { 1876 err = fs.Unmount() 1877 So(err, ShouldBeNil) 1878 1879 _, err = os.Stat(cachePath) 1880 So(err, ShouldBeNil) 1881 }) 1882 1883 Convey("You can read different parts of a file simultaneously from 1 mount, and it's only downloaded once", func() { 1884 init := mountPoint + "/numalphanum.txt" 1885 path := mountPoint + "/100k.lines" 1886 1887 // the first read takes longer than others, so read something 1888 // to "initialise" minio 1889 streamFile(init, 0) 1890 1891 // first get a reference for how long it takes to read the whole 1892 // thing 1893 t := time.Now() 1894 read, err := streamFile(path, 0) 1895 wt := time.Since(t) 1896 So(err, ShouldBeNil) 1897 So(read, ShouldEqual, 700000) 1898 1899 // sanity check that re-reading uses our cache 1900 t = time.Now() 1901 streamFile(path, 0) 1902 st := time.Since(t) 1903 1904 // should have completed in under 90% of the time 1905 et := time.Duration((wt.Nanoseconds()/100)*99) * time.Nanosecond 1906 So(st, ShouldBeLessThan, et) 1907 1908 // remount to clear the cache 1909 err = fs.Unmount() 1910 So(err, ShouldBeNil) 1911 err = fs.Mount(remoteConfig) 1912 So(err, ShouldBeNil) 1913 streamFile(init, 0) 1914 1915 // now read the whole file and half the file at the ~same time 1916 times := make(chan time.Duration, 2) 1917 errors := make(chan error, 2) 1918 streamer := func(offset, size int) { 1919 t2 := time.Now() 1920 thisRead, thisErr := streamFile(path, int64(offset)) 1921 times <- time.Since(t2) 1922 if thisErr != nil { 1923 errors <- thisErr 1924 return 1925 } 1926 if thisRead != int64(size) { 1927 errors <- fmt.Errorf("did not read %d bytes for offset %d (%d)", size, offset, thisRead) 1928 return 1929 } 1930 errors <- nil 1931 } 1932 1933 t = time.Now() 1934 var wg sync.WaitGroup 1935 wg.Add(2) 1936 go func() { 1937 defer wg.Done() 1938 streamer(350000, 350000) 1939 }() 1940 go func() { 1941 defer wg.Done() 1942 streamer(0, 700000) 1943 }() 1944 wg.Wait() 1945 ot := time.Since(t) 1946 1947 // both should complete in not much more time than the slowest, 1948 // and that shouldn't be much slower than when reading alone 1949 // *** debugging shows that the file is only downloaded once, 1950 // but don't have a good way of proving that here 1951 So(<-errors, ShouldBeNil) 1952 So(<-errors, ShouldBeNil) 1953 pt1 := <-times 1954 pt2 := <-times 1955 var multiplier int64 1956 if runtime.NumCPU() == 1 { 1957 multiplier = 240 1958 } else { 1959 multiplier = 190 1960 } 1961 eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*multiplier) * time.Nanosecond 1962 // ets := time.Duration((wt.Nanoseconds()/100)*160) * time.Nanosecond 1963 // fmt.Printf("\nwt: %s, pt1: %s, pt2: %s, ot: %s, eto: %s, ets: %s\n", wt, pt1, pt2, ot, eto, ets) 1964 So(ot, ShouldBeLessThan, eto) 1965 // So(ot, ShouldBeLessThan, ets) 1966 }) 1967 1968 Convey("You can read different files simultaneously from 1 mount", func() { 1969 init := mountPoint + "/numalphanum.txt" 1970 path1 := mountPoint + "/100k.lines" 1971 path2 := mountPoint + "/big.file" 1972 1973 streamFile(init, 0) 1974 1975 // first get a reference for how long it takes to read a certain 1976 // sized chunk of each file 1977 // t := time.Now() 1978 read, err := streamFile(path1, 0) 1979 // f1t := time.Since(t) 1980 So(err, ShouldBeNil) 1981 So(read, ShouldEqual, 700000) 1982 1983 // t = time.Now() 1984 read, err = streamFile(path2, int64(bigFileSize-700000)) 1985 // f2t := time.Since(t) 1986 So(err, ShouldBeNil) 1987 So(read, ShouldEqual, 700000) 1988 1989 // remount to clear the cache 1990 err = fs.Unmount() 1991 So(err, ShouldBeNil) 1992 err = fs.Mount(remoteConfig) 1993 So(err, ShouldBeNil) 1994 streamFile(init, 0) 1995 1996 // now repeat reading them at the ~same time 1997 times := make(chan time.Duration, 2) 1998 errors := make(chan error, 2) 1999 streamer := func(path string, offset, size int) { 2000 t := time.Now() 2001 thisRead, thisErr := streamFile(path, int64(offset)) 2002 times <- time.Since(t) 2003 if thisErr != nil { 2004 errors <- thisErr 2005 return 2006 } 2007 if thisRead != int64(size) { 2008 errors <- fmt.Errorf("did not read %d bytes of %s at offset %d (%d)", size, path, offset, thisRead) 2009 return 2010 } 2011 errors <- nil 2012 } 2013 2014 t := time.Now() 2015 var wg sync.WaitGroup 2016 wg.Add(2) 2017 go func() { 2018 defer wg.Done() 2019 streamer(path1, 0, 700000) 2020 }() 2021 go func() { 2022 defer wg.Done() 2023 streamer(path2, bigFileSize-700000, 700000) 2024 }() 2025 wg.Wait() 2026 ot := time.Since(t) 2027 2028 // each should have completed in less than 190% of the time 2029 // needed to read them sequentially, and both should have 2030 // completed in less than 110% of the slowest one 2031 So(<-errors, ShouldBeNil) 2032 So(<-errors, ShouldBeNil) 2033 pt1 := <-times 2034 pt2 := <-times 2035 // et1 := time.Duration((f1t.Nanoseconds()/100)*190) * time.Nanosecond 2036 // et2 := time.Duration((f2t.Nanoseconds()/100)*190) * time.Nanosecond 2037 var multiplier int64 2038 if bigFileSize > 10000000 { 2039 if runtime.NumCPU() == 1 { 2040 multiplier = 200 2041 } else { 2042 multiplier = 110 2043 } 2044 } else { 2045 if runtime.NumCPU() == 1 { 2046 multiplier = 350 2047 } else { 2048 multiplier = 250 2049 } 2050 } 2051 eto := time.Duration((int64(math.Max(float64(pt1.Nanoseconds()), float64(pt2.Nanoseconds())))/100)*multiplier) * time.Nanosecond 2052 // *** these timing tests are too unreliable when using minio server 2053 // So(pt1, ShouldBeLessThan, et1) 2054 // So(pt2, ShouldBeLessThan, et2) 2055 So(ot, ShouldBeLessThan, eto) 2056 }) 2057 }) 2058 2059 Convey("You can mount with local file caching in an explicit relative location", t, func() { 2060 remoteConfig.CacheDir = ".muxfys_test_cache_dir" 2061 fs, err := New(cfg) 2062 So(err, ShouldBeNil) 2063 2064 err = fs.Mount(remoteConfig) 2065 So(err, ShouldBeNil) 2066 2067 defer func() { 2068 err = fs.Unmount() 2069 So(err, ShouldBeNil) 2070 os.RemoveAll(remoteConfig.CacheDir) 2071 remoteConfig.CacheDir = "" 2072 }() 2073 2074 path := mountPoint + "/numalphanum.txt" 2075 _, err = ioutil.ReadFile(path) 2076 So(err, ShouldBeNil) 2077 2078 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("numalphanum.txt")) 2079 _, err = os.Stat(cachePath) 2080 So(err, ShouldBeNil) 2081 cwd, _ := os.Getwd() 2082 So(cachePath, ShouldStartWith, filepath.Join(cwd, ".muxfys_test_cache_dir")) 2083 2084 Convey("Unmounting doesn't delete the cache", func() { 2085 err = fs.Unmount() 2086 So(err, ShouldBeNil) 2087 2088 _, err = os.Stat(cachePath) 2089 So(err, ShouldBeNil) 2090 }) 2091 }) 2092 2093 Convey("You can mount with local file caching relative to the home directory", t, func() { 2094 remoteConfig.CacheDir = "~/.wr_muxfys_test_cache_dir" 2095 fs, err := New(cfg) 2096 So(err, ShouldBeNil) 2097 2098 err = fs.Mount(remoteConfig) 2099 So(err, ShouldBeNil) 2100 2101 defer func() { 2102 err = fs.Unmount() 2103 remoteConfig.CacheDir = "" 2104 So(err, ShouldBeNil) 2105 os.RemoveAll(filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_cache_dir")) 2106 }() 2107 2108 path := mountPoint + "/numalphanum.txt" 2109 _, err = ioutil.ReadFile(path) 2110 So(err, ShouldBeNil) 2111 2112 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("numalphanum.txt")) 2113 _, err = os.Stat(cachePath) 2114 So(err, ShouldBeNil) 2115 2116 So(cachePath, ShouldStartWith, filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_cache_dir")) 2117 2118 Convey("Unmounting doesn't delete the cache", func() { 2119 err = fs.Unmount() 2120 So(err, ShouldBeNil) 2121 2122 _, err = os.Stat(cachePath) 2123 So(err, ShouldBeNil) 2124 }) 2125 }) 2126 2127 Convey("You can mount with a relative mount point", t, func() { 2128 cfg.Mount = ".wr_muxfys_test_mount_dir" 2129 fs, err := New(cfg) 2130 So(err, ShouldBeNil) 2131 2132 err = fs.Mount(remoteConfig) 2133 So(err, ShouldBeNil) 2134 2135 defer func() { 2136 err = fs.Unmount() 2137 So(err, ShouldBeNil) 2138 os.RemoveAll(cfg.Mount) 2139 cfg.Mount = mountPoint 2140 }() 2141 2142 path := filepath.Join(cfg.Mount, "numalphanum.txt") 2143 _, err = ioutil.ReadFile(path) 2144 So(err, ShouldBeNil) 2145 }) 2146 2147 Convey("You can mount with a ~/ mount point", t, func() { 2148 cfg.Mount = "~/.wr_muxfys_test_mount_dir" 2149 fs, err := New(cfg) 2150 So(err, ShouldBeNil) 2151 2152 err = fs.Mount(remoteConfig) 2153 So(err, ShouldBeNil) 2154 2155 defer func() { 2156 err = fs.Unmount() 2157 cfg.Mount = mountPoint 2158 So(err, ShouldBeNil) 2159 os.RemoveAll(filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_mount_dir")) 2160 }() 2161 2162 path := filepath.Join(os.Getenv("HOME"), ".wr_muxfys_test_mount_dir", "numalphanum.txt") 2163 _, err = ioutil.ReadFile(path) 2164 So(err, ShouldBeNil) 2165 }) 2166 2167 Convey("You can mount with no defined mount point", t, func() { 2168 cfg.Mount = "" 2169 fs, err := New(cfg) 2170 So(err, ShouldBeNil) 2171 2172 err = fs.Mount(remoteConfig) 2173 So(err, ShouldBeNil) 2174 2175 defer func() { 2176 err = fs.Unmount() 2177 cfg.Mount = mountPoint 2178 So(err, ShouldBeNil) 2179 os.RemoveAll("mnt") 2180 }() 2181 2182 path := filepath.Join("mnt", "numalphanum.txt") 2183 _, err = ioutil.ReadFile(path) 2184 So(err, ShouldBeNil) 2185 }) 2186 2187 Convey("You can't mount on a non-empty directory", t, func() { 2188 cfg.Mount = os.Getenv("HOME") 2189 _, err := New(cfg) 2190 defer func() { 2191 cfg.Mount = mountPoint 2192 }() 2193 So(err, ShouldNotBeNil) 2194 So(err.Error(), ShouldContainSubstring, "not empty") 2195 }) 2196 2197 Convey("You can mount in write mode and not upload on unmount", t, func() { 2198 remoteConfig.Write = true 2199 fs, err := New(cfg) 2200 So(err, ShouldBeNil) 2201 2202 err = fs.Mount(remoteConfig) 2203 So(err, ShouldBeNil) 2204 2205 defer func() { 2206 err = fs.Unmount() 2207 remoteConfig.Write = false 2208 So(err, ShouldBeNil) 2209 }() 2210 2211 path := mountPoint + "/write.test" 2212 b := []byte("write test\n") 2213 err = ioutil.WriteFile(path, b, 0644) 2214 So(err, ShouldBeNil) 2215 2216 bytes, err := ioutil.ReadFile(path) 2217 So(err, ShouldBeNil) 2218 So(bytes, ShouldResemble, b) 2219 2220 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("write.test")) 2221 _, err = os.Stat(cachePath) 2222 So(err, ShouldBeNil) 2223 2224 // unmount without uploads 2225 err = fs.Unmount(true) 2226 So(err, ShouldBeNil) 2227 2228 _, err = os.Stat(cachePath) 2229 So(err, ShouldNotBeNil) 2230 So(os.IsNotExist(err), ShouldBeTrue) 2231 _, err = os.Stat(path) 2232 So(err, ShouldNotBeNil) 2233 So(os.IsNotExist(err), ShouldBeTrue) 2234 2235 // remounting reveals it did not get uploaded 2236 err = fs.Mount(remoteConfig) 2237 So(err, ShouldBeNil) 2238 2239 _, err = os.Stat(cachePath) 2240 So(err, ShouldNotBeNil) 2241 So(os.IsNotExist(err), ShouldBeTrue) 2242 2243 _, err = os.Stat(path) 2244 So(err, ShouldNotBeNil) 2245 So(os.IsNotExist(err), ShouldBeTrue) 2246 }) 2247 2248 Convey("You can mount with verbose to get more logs", t, func() { 2249 origVerbose := cfg.Verbose 2250 cfg.Verbose = true 2251 defer func() { 2252 cfg.Verbose = origVerbose 2253 }() 2254 fs, err := New(cfg) 2255 So(err, ShouldBeNil) 2256 2257 err = fs.Mount(remoteConfig) 2258 So(err, ShouldBeNil) 2259 2260 _, err = ioutil.ReadDir(mountPoint) 2261 expectedErrorLog := "" 2262 if err != nil { 2263 expectedErrorLog = "ListEntries" 2264 } 2265 2266 err = fs.Unmount() 2267 So(err, ShouldBeNil) 2268 2269 logs := fs.Logs() 2270 So(logs, ShouldNotBeNil) 2271 var foundExpectedLog bool 2272 var foundErrorLog bool 2273 for _, log := range logs { 2274 if strings.Contains(log, "ListEntries") { 2275 foundExpectedLog = true 2276 } 2277 if expectedErrorLog != "" && strings.Contains(log, expectedErrorLog) { 2278 foundErrorLog = true 2279 } 2280 } 2281 So(foundExpectedLog, ShouldBeTrue) 2282 if expectedErrorLog != "" { 2283 So(foundErrorLog, ShouldBeTrue) 2284 } 2285 }) 2286 2287 var bigFileGetTimeUncached time.Duration 2288 Convey("You can mount without local file caching", t, func() { 2289 remoteConfig.CacheData = false 2290 fs, errc := New(cfg) 2291 So(errc, ShouldBeNil) 2292 2293 errm := fs.Mount(remoteConfig) 2294 So(errm, ShouldBeNil) 2295 2296 defer func() { 2297 erru := fs.Unmount() 2298 So(erru, ShouldBeNil) 2299 }() 2300 2301 cachePath := fs.remotes[0].getLocalPath(fs.remotes[0].getRemotePath("big.file")) 2302 So(cachePath, ShouldBeBlank) 2303 2304 Convey("Listing mount directory and subdirs works", func() { 2305 s := time.Now() 2306 entries, err := ioutil.ReadDir(mountPoint) 2307 d := time.Since(s) 2308 So(err, ShouldBeNil) 2309 2310 details := dirDetails(entries) 2311 rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"} 2312 So(details, ShouldResemble, rootEntries) 2313 2314 // test it twice in a row to make sure caching is ok 2315 s = time.Now() 2316 entries, err = ioutil.ReadDir(mountPoint) 2317 dc := time.Since(s) 2318 So(err, ShouldBeNil) 2319 So(dc.Nanoseconds(), ShouldBeLessThan, d.Nanoseconds()/4) 2320 2321 details = dirDetails(entries) 2322 So(details, ShouldResemble, rootEntries) 2323 2324 // test the sub directories 2325 entries, err = ioutil.ReadDir(mountPoint + "/sub") 2326 So(err, ShouldBeNil) 2327 2328 details = dirDetails(entries) 2329 So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"}) 2330 2331 entries, err = ioutil.ReadDir(mountPoint + "/sub/deep") 2332 So(err, ShouldBeNil) 2333 2334 details = dirDetails(entries) 2335 So(details, ShouldResemble, []string{"bar:file:4"}) 2336 2337 if doRemoteTests { 2338 entries, err = ioutil.ReadDir(mountPoint + "/emptyDir") 2339 So(err, ShouldBeNil) 2340 2341 details = dirDetails(entries) 2342 So(len(details), ShouldEqual, 0) 2343 } 2344 }) 2345 2346 Convey("You can immediately list a subdir", func() { 2347 entries, err := ioutil.ReadDir(mountPoint + "/sub") 2348 So(err, ShouldBeNil) 2349 2350 details := dirDetails(entries) 2351 So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"}) 2352 }) 2353 2354 if doRemoteTests { 2355 Convey("You can immediately list an empty subdir", func() { 2356 entries, err := ioutil.ReadDir(mountPoint + "/emptyDir") 2357 So(err, ShouldBeNil) 2358 2359 details := dirDetails(entries) 2360 So(len(details), ShouldEqual, 0) 2361 }) 2362 } 2363 2364 Convey("Trying to list a non-existent subdir fails as expected", func() { 2365 entries, err := ioutil.ReadDir(mountPoint + "/emptyDi") 2366 So(err, ShouldNotBeNil) 2367 So(os.IsNotExist(err), ShouldBeTrue) 2368 details := dirDetails(entries) 2369 So(len(details), ShouldEqual, 0) 2370 }) 2371 2372 Convey("You can immediately list a deep subdir", func() { 2373 entries, err := ioutil.ReadDir(mountPoint + "/sub/deep") 2374 So(err, ShouldBeNil) 2375 2376 details := dirDetails(entries) 2377 So(details, ShouldResemble, []string{"bar:file:4"}) 2378 2379 info, err := os.Stat(mountPoint + "/sub/deep/bar") 2380 So(err, ShouldBeNil) 2381 So(info.Name(), ShouldEqual, "bar") 2382 So(info.Size(), ShouldEqual, 4) 2383 }) 2384 2385 Convey("You can immediately stat a deep file", func() { 2386 info, err := os.Stat(mountPoint + "/sub/deep/bar") 2387 So(err, ShouldBeNil) 2388 So(info.Name(), ShouldEqual, "bar") 2389 So(info.Size(), ShouldEqual, 4) 2390 }) 2391 2392 Convey("You can read a whole file as well as parts of it by seeking", func() { 2393 path := mountPoint + "/100k.lines" 2394 read, err := streamFile(path, 0) 2395 So(err, ShouldBeNil) 2396 So(read, ShouldEqual, 700000) 2397 2398 read, err = streamFile(path, 350000) 2399 So(err, ShouldBeNil) 2400 So(read, ShouldEqual, 350000) 2401 2402 // make sure the contents are actually correct 2403 var expected bytes.Buffer 2404 for i := 1; i <= 100000; i++ { 2405 expected.WriteString(fmt.Sprintf("%06d\n", i)) 2406 } 2407 bytes, err := ioutil.ReadFile(path) 2408 So(err, ShouldBeNil) 2409 So(string(bytes), ShouldEqual, expected.String()) 2410 }) 2411 2412 Convey("You can do random reads on large files", func() { 2413 // sanity check that it works on a small file 2414 path := mountPoint + "/numalphanum.txt" 2415 r, err := os.Open(path) 2416 So(err, ShouldBeNil) 2417 defer r.Close() 2418 2419 r.Seek(36, io.SeekStart) 2420 2421 b := make([]byte, 10) 2422 done, err := io.ReadFull(r, b) 2423 So(err, ShouldBeNil) 2424 So(done, ShouldEqual, 10) 2425 So(b, ShouldResemble, []byte("1234567890")) 2426 2427 r.Seek(10, io.SeekStart) 2428 b = make([]byte, 10) 2429 done, err = io.ReadFull(r, b) 2430 So(err, ShouldBeNil) 2431 So(done, ShouldEqual, 10) 2432 So(b, ShouldResemble, []byte("abcdefghij")) 2433 2434 // it also works on a big one 2435 path = mountPoint + "/100k.lines" 2436 rbig, err := os.Open(path) 2437 So(err, ShouldBeNil) 2438 defer rbig.Close() 2439 2440 rbig.Seek(350000, io.SeekStart) 2441 b = make([]byte, 6) 2442 done, err = io.ReadFull(rbig, b) 2443 So(err, ShouldBeNil) 2444 So(done, ShouldEqual, 6) 2445 So(b, ShouldResemble, []byte("050001")) 2446 2447 rbig.Seek(175000, io.SeekStart) 2448 b = make([]byte, 6) 2449 done, err = io.ReadFull(rbig, b) 2450 So(err, ShouldBeNil) 2451 So(done, ShouldEqual, 6) 2452 So(b, ShouldResemble, []byte("025001")) 2453 }) 2454 2455 Convey("You can read a very big file", func() { 2456 ioutil.ReadDir(mountPoint) // we need to time reading the file, not stating it 2457 path := mountPoint + "/big.file" 2458 start := time.Now() 2459 read, err := streamFile(path, 0) 2460 bigFileGetTimeUncached = time.Since(start) 2461 // fmt.Printf("\n1G file read took %s cached vs %s uncached\n", bigFileGetTime, thisGetTime) 2462 So(err, ShouldBeNil) 2463 So(read, ShouldEqual, bigFileSize) 2464 So(math.Ceil(bigFileGetTimeUncached.Seconds()), ShouldBeLessThanOrEqualTo, math.Ceil(bigFileGetTime.Seconds())+2) // if it isn't, it's almost certainly a bug! 2465 }) 2466 2467 Convey("You can read a very big file using cat", func() { 2468 ioutil.ReadDir(mountPoint) 2469 path := mountPoint + "/big.file" 2470 start := time.Now() 2471 err := exec.Command("cat", path).Run() 2472 So(err, ShouldBeNil) 2473 thisGetTime := time.Since(start) 2474 // fmt.Printf("\n1G file cat took %s \n", thisGetTime) 2475 So(err, ShouldBeNil) 2476 2477 // it should be about as quick as the above streamFile call: much 2478 // slower means a bug 2479 So(math.Ceil(thisGetTime.Seconds()), ShouldBeLessThanOrEqualTo, math.Ceil(bigFileGetTimeUncached.Seconds())+2) 2480 }) 2481 2482 Convey("You can read a very big file using cp", func() { 2483 ioutil.ReadDir(mountPoint) 2484 path := mountPoint + "/big.file" 2485 start := time.Now() 2486 err := exec.Command("cp", path, "/dev/null").Run() 2487 So(err, ShouldBeNil) 2488 thisGetTime := time.Since(start) 2489 // fmt.Printf("\n1G file cp took %s \n", thisGetTime) 2490 So(err, ShouldBeNil) 2491 2492 // it should be about as quick as the above streamFile call: much 2493 // slower means a bug 2494 So(math.Ceil(thisGetTime.Seconds()), ShouldBeLessThanOrEqualTo, math.Ceil(bigFileGetTimeUncached.Seconds())+7) 2495 }) 2496 2497 Convey("Trying to read a non-existent file fails as expected", func() { 2498 name := "non-existent.file" 2499 path := mountPoint + "/" + name 2500 _, err := streamFile(path, 0) 2501 So(err, ShouldNotBeNil) 2502 So(os.IsNotExist(err), ShouldBeTrue) 2503 }) 2504 2505 Convey("Trying to read an externally deleted file fails as expected", func() { 2506 name := "non-existent.file" 2507 path := mountPoint + "/" + name 2508 // we'll hack fs to make it think non-existent.file does exist 2509 // so we can test the behaviour of a file getting deleted 2510 // externally 2511 fs.GetAttr("/", &fuse.Context{}) 2512 fs.mapMutex.Lock() 2513 fs.addNewEntryToItsDir(name, fuse.S_IFREG) 2514 fs.files[name] = fs.files["big.file"] 2515 fs.fileToRemote[name] = fs.fileToRemote["big.file"] 2516 fs.mapMutex.Unlock() 2517 _, err := streamFile(path, 0) 2518 So(err, ShouldNotBeNil) 2519 So(os.IsNotExist(err), ShouldBeTrue) 2520 }) 2521 }) 2522 2523 Convey("You can mount in write mode without any caching", t, func() { 2524 remoteConfig.Write = true 2525 remoteConfig.CacheData = false 2526 fs, errc := New(cfg) 2527 So(errc, ShouldBeNil) 2528 2529 errm := fs.Mount(remoteConfig) 2530 So(errm, ShouldBeNil) 2531 2532 defer func() { 2533 erru := fs.Unmount() 2534 remoteConfig.Write = false 2535 So(erru, ShouldBeNil) 2536 }() 2537 2538 Convey("Trying to write works", func() { 2539 path := mountPoint + "/write.test" 2540 b := []byte("write test\n") 2541 err := ioutil.WriteFile(path, b, 0644) 2542 So(err, ShouldBeNil) 2543 2544 defer func() { 2545 err = os.Remove(path) 2546 So(err, ShouldBeNil) 2547 }() 2548 2549 // you can immediately read it back 2550 bytes, err := ioutil.ReadFile(path) 2551 So(err, ShouldBeNil) 2552 So(bytes, ShouldResemble, b) 2553 2554 // and it's statable and listable 2555 _, err = os.Stat(path) 2556 So(err, ShouldBeNil) 2557 2558 entries, err := ioutil.ReadDir(mountPoint) 2559 So(err, ShouldBeNil) 2560 details := dirDetails(entries) 2561 rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:11"} 2562 So(details, ShouldResemble, rootEntries) 2563 2564 err = fs.Unmount() 2565 So(err, ShouldBeNil) 2566 2567 _, err = os.Stat(path) 2568 So(err, ShouldNotBeNil) 2569 So(os.IsNotExist(err), ShouldBeTrue) 2570 2571 // remounting lets us read the file again - it actually got 2572 // uploaded 2573 err = fs.Mount(remoteConfig) 2574 So(err, ShouldBeNil) 2575 2576 bytes, err = ioutil.ReadFile(path) 2577 So(err, ShouldBeNil) 2578 So(bytes, ShouldResemble, b) 2579 }) 2580 2581 Convey("You can upload empty files with cp", func() { 2582 path := mountPoint + "/touch.test" 2583 localPath := "touch.test" 2584 cmd := exec.Command("touch", localPath) // ("bash", "-c", "echo foo > touch.test") 2585 err := cmd.Run() 2586 defer func() { 2587 err = os.Remove(localPath) 2588 So(err, ShouldBeNil) 2589 err = os.Remove(path) 2590 So(err, ShouldBeNil) 2591 }() 2592 So(err, ShouldBeNil) 2593 cmd = exec.Command("cp", localPath, path) 2594 err = cmd.Run() 2595 So(err, ShouldBeNil) 2596 2597 <-time.After(1500 * time.Millisecond) // implementation for empty files does a final flush after 1s 2598 2599 _, err = os.Stat(path) 2600 So(err, ShouldBeNil) 2601 2602 err = fs.Unmount() 2603 So(err, ShouldBeNil) 2604 2605 _, err = os.Stat(path) 2606 So(err, ShouldNotBeNil) 2607 So(os.IsNotExist(err), ShouldBeTrue) 2608 2609 err = fs.Mount(remoteConfig) 2610 So(err, ShouldBeNil) 2611 2612 _, err = os.Stat(path) 2613 So(err, ShouldBeNil) 2614 }) 2615 2616 Convey("You can create empty files with touch", func() { 2617 path := mountPoint + "/touch.test" 2618 cmd := exec.Command("touch", path) 2619 err := cmd.Run() 2620 defer func() { 2621 err = os.Remove(path) 2622 So(err, ShouldBeNil) 2623 }() 2624 So(err, ShouldBeNil) 2625 2626 <-time.After(1500 * time.Millisecond) 2627 2628 _, err = os.Stat(path) 2629 So(err, ShouldBeNil) 2630 2631 err = fs.Unmount() 2632 So(err, ShouldBeNil) 2633 2634 _, err = os.Stat(path) 2635 So(err, ShouldNotBeNil) 2636 So(os.IsNotExist(err), ShouldBeTrue) 2637 2638 err = fs.Mount(remoteConfig) 2639 So(err, ShouldBeNil) 2640 2641 _, err = os.Stat(path) 2642 So(err, ShouldBeNil) 2643 }) 2644 2645 if bigFileSize > 10000000 { 2646 // *** -race flag isn't passed through to us, so can't limit this skip to just -race 2647 SkipConvey("Writing a very large file breaks machines with race detector on", func() {}) 2648 } else { 2649 Convey("Writing a large file works", func() { 2650 // because minio-go currently uses a ~600MB bytes.Buffer (2x 2651 // allocation growth) during streaming upload, and dd itself creates 2652 // an input buffer of the size bs, we have to give dd a small bs and 2653 // increase the count instead. This way we don't run out of memory 2654 // even when bigFileSize is greater than physical memory on the 2655 // machine 2656 2657 path := mountPoint + "/write.test" 2658 err := exec.Command("dd", "if=/dev/zero", "of="+path, fmt.Sprintf("bs=%d", bigFileSize/1000), "count=1000").Run() 2659 So(err, ShouldBeNil) 2660 2661 defer func() { 2662 err = os.Remove(path) 2663 So(err, ShouldBeNil) 2664 }() 2665 2666 info, err := os.Stat(path) 2667 So(err, ShouldBeNil) 2668 expectedSize := (bigFileSize / 1000) * 1000 2669 So(info.Size(), ShouldEqual, expectedSize) 2670 2671 err = fs.Unmount() 2672 So(err, ShouldBeNil) 2673 2674 _, err = os.Stat(path) 2675 So(err, ShouldNotBeNil) 2676 So(os.IsNotExist(err), ShouldBeTrue) 2677 2678 err = fs.Mount(remoteConfig) 2679 So(err, ShouldBeNil) 2680 2681 info, err = os.Stat(path) 2682 So(err, ShouldBeNil) 2683 So(info.Size(), ShouldEqual, expectedSize) 2684 }) 2685 } 2686 2687 Convey("Given a local directory", func() { 2688 mvDir := filepath.Join(tmpdir, "mvtest2") 2689 mvSubDir := filepath.Join(mvDir, "mvsubdir") 2690 errf := os.MkdirAll(mvSubDir, os.FileMode(0700)) 2691 So(errf, ShouldBeNil) 2692 mvFile := filepath.Join(mvSubDir, "file") 2693 mvBytes := []byte("mvfile\n") 2694 errf = ioutil.WriteFile(mvFile, mvBytes, 0644) 2695 So(errf, ShouldBeNil) 2696 errf = ioutil.WriteFile(filepath.Join(mvDir, "a.file"), mvBytes, 0644) 2697 So(errf, ShouldBeNil) 2698 2699 Convey("You can mv it to the mount point", func() { 2700 mountDir := filepath.Join(mountPoint, "mvtest2") 2701 dest := filepath.Join(mountDir, "mvsubdir", "file") 2702 dest2 := filepath.Join(mountDir, "a.file") 2703 2704 cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s %s/", mvDir, mountPoint)) 2705 out, err := cmd.CombinedOutput() 2706 So(err, ShouldBeNil) 2707 So(len(out), ShouldEqual, 0) 2708 2709 bytes, err := ioutil.ReadFile(dest) 2710 So(err, ShouldBeNil) 2711 So(bytes, ShouldResemble, mvBytes) 2712 2713 bytes, err = ioutil.ReadFile(dest2) 2714 So(err, ShouldBeNil) 2715 So(bytes, ShouldResemble, mvBytes) 2716 2717 _, err = os.Stat(filepath.Join(mountPoint, "mvtest2", "mvsubdir")) 2718 So(err, ShouldBeNil) 2719 _, err = os.Stat(mountDir) 2720 So(err, ShouldBeNil) 2721 2722 err = fs.Unmount() 2723 So(err, ShouldBeNil) 2724 err = fs.Mount(remoteConfig) 2725 So(err, ShouldBeNil) 2726 2727 defer func() { 2728 err = os.Remove(dest) 2729 So(err, ShouldBeNil) 2730 err = os.Remove(dest2) 2731 So(err, ShouldBeNil) 2732 }() 2733 2734 bytes, err = ioutil.ReadFile(dest) 2735 So(err, ShouldBeNil) 2736 So(bytes, ShouldResemble, mvBytes) 2737 }) 2738 2739 Convey("You can mv its contents to the mount point", func() { 2740 dest := filepath.Join(mountPoint, "mvsubdir", "file") 2741 dest2 := filepath.Join(mountPoint, "a.file") 2742 2743 cmd := exec.Command("sh", "-c", fmt.Sprintf("mv %s/* %s/", mvDir, mountPoint)) 2744 err := cmd.Run() 2745 So(err, ShouldBeNil) 2746 2747 bytes, err := ioutil.ReadFile(dest) 2748 So(err, ShouldBeNil) 2749 So(bytes, ShouldResemble, mvBytes) 2750 2751 err = fs.Unmount() 2752 So(err, ShouldBeNil) 2753 err = fs.Mount(remoteConfig) 2754 So(err, ShouldBeNil) 2755 2756 defer func() { 2757 err = os.Remove(dest) 2758 So(err, ShouldBeNil) 2759 err = os.Remove(dest2) 2760 So(err, ShouldBeNil) 2761 }() 2762 2763 bytes, err = ioutil.ReadFile(dest) 2764 So(err, ShouldBeNil) 2765 So(bytes, ShouldResemble, mvBytes) 2766 }) 2767 }) 2768 }) 2769 2770 Convey("You can mount multiple remotes on the same mount point", t, func() { 2771 remoteConfig.CacheData = true 2772 manualConfig2 := &S3Config{ 2773 Target: target + "/sub", 2774 AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"), 2775 SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"), 2776 } 2777 accessor, errn = NewS3Accessor(manualConfig2) 2778 So(errn, ShouldBeNil) 2779 remoteConfig2 := &RemoteConfig{ 2780 Accessor: accessor, 2781 CacheData: true, 2782 } 2783 2784 fs, err := New(cfg) 2785 So(err, ShouldBeNil) 2786 2787 err = fs.Mount(remoteConfig, remoteConfig2) 2788 So(err, ShouldBeNil) 2789 2790 defer func() { 2791 err = fs.Unmount() 2792 So(err, ShouldBeNil) 2793 }() 2794 2795 Convey("Listing mount directory and subdirs works", func() { 2796 s := time.Now() 2797 entries, err := ioutil.ReadDir(mountPoint) 2798 d := time.Since(s) 2799 So(err, ShouldBeNil) 2800 2801 details := dirDetails(entries) 2802 rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "deep:dir", "empty.file:file:0", "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"} 2803 So(details, ShouldResemble, rootEntries) 2804 2805 // test it twice in a row to make sure caching is ok 2806 s = time.Now() 2807 entries, err = ioutil.ReadDir(mountPoint) 2808 dc := time.Since(s) 2809 So(err, ShouldBeNil) 2810 So(dc.Nanoseconds(), ShouldBeLessThan, d.Nanoseconds()/4) 2811 2812 details = dirDetails(entries) 2813 So(details, ShouldResemble, rootEntries) 2814 2815 // test the sub directories 2816 entries, err = ioutil.ReadDir(mountPoint + "/sub") 2817 So(err, ShouldBeNil) 2818 2819 details = dirDetails(entries) 2820 So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"}) 2821 2822 entries, err = ioutil.ReadDir(mountPoint + "/sub/deep") 2823 So(err, ShouldBeNil) 2824 2825 details = dirDetails(entries) 2826 So(details, ShouldResemble, []string{"bar:file:4"}) 2827 2828 // and the sub dirs of the second mount 2829 entries, err = ioutil.ReadDir(mountPoint + "/deep") 2830 So(err, ShouldBeNil) 2831 2832 details = dirDetails(entries) 2833 So(details, ShouldResemble, []string{"bar:file:4"}) 2834 }) 2835 2836 Convey("You can immediately list a subdir", func() { 2837 entries, err := ioutil.ReadDir(mountPoint + "/sub") 2838 So(err, ShouldBeNil) 2839 2840 details := dirDetails(entries) 2841 So(details, ShouldResemble, []string{"deep:dir", "empty.file:file:0"}) 2842 }) 2843 2844 Convey("You can immediately list a subdir of the second remote", func() { 2845 entries, err := ioutil.ReadDir(mountPoint + "/deep") 2846 So(err, ShouldBeNil) 2847 2848 details := dirDetails(entries) 2849 So(details, ShouldResemble, []string{"bar:file:4"}) 2850 2851 info, err := os.Stat(mountPoint + "/deep/bar") 2852 So(err, ShouldBeNil) 2853 So(info.Name(), ShouldEqual, "bar") 2854 So(info.Size(), ShouldEqual, 4) 2855 }) 2856 2857 Convey("You can immediately list a deep subdir", func() { 2858 entries, err := ioutil.ReadDir(mountPoint + "/sub/deep") 2859 So(err, ShouldBeNil) 2860 2861 details := dirDetails(entries) 2862 So(details, ShouldResemble, []string{"bar:file:4"}) 2863 2864 info, err := os.Stat(mountPoint + "/sub/deep/bar") 2865 So(err, ShouldBeNil) 2866 So(info.Name(), ShouldEqual, "bar") 2867 So(info.Size(), ShouldEqual, 4) 2868 }) 2869 2870 Convey("You can read files from both remotes", func() { 2871 path := mountPoint + "/deep/bar" 2872 bytes, err := ioutil.ReadFile(path) 2873 So(err, ShouldBeNil) 2874 So(string(bytes), ShouldEqual, "foo\n") 2875 2876 path = mountPoint + "/sub/deep/bar" 2877 bytes, err = ioutil.ReadFile(path) 2878 So(err, ShouldBeNil) 2879 So(string(bytes), ShouldEqual, "foo\n") 2880 }) 2881 }) 2882 2883 Convey("You can mount the bucket directly", t, func() { 2884 u, errp := url.Parse(target) 2885 So(errp, ShouldBeNil) 2886 parts := strings.Split(u.Path[1:], "/") 2887 manualConfig.Target = u.Scheme + "://" + u.Host + "/" + parts[0] 2888 accessor, errn = NewS3Accessor(manualConfig) 2889 So(errn, ShouldBeNil) 2890 remoteConfig := &RemoteConfig{ 2891 Accessor: accessor, 2892 CacheData: true, 2893 Write: false, 2894 } 2895 fs, errc := New(cfg) 2896 So(errc, ShouldBeNil) 2897 2898 errm := fs.Mount(remoteConfig) 2899 So(errm, ShouldBeNil) 2900 2901 defer func() { 2902 erru := fs.Unmount() 2903 So(erru, ShouldBeNil) 2904 }() 2905 2906 Convey("Listing bucket directory works", func() { 2907 entries, err := ioutil.ReadDir(mountPoint) 2908 So(err, ShouldBeNil) 2909 2910 details := dirDetails(entries) 2911 So(details, ShouldContain, path.Join(parts[1:]...)+":dir") 2912 }) 2913 2914 Convey("You can't mount more than once at a time", func() { 2915 err := fs.Mount(remoteConfig) 2916 So(err, ShouldNotBeNil) 2917 }) 2918 }) 2919 2920 Convey("For non-existent paths...", t, func() { 2921 manualConfig.Target = target + "/nonexistent/subdir" 2922 accessor, errn = NewS3Accessor(manualConfig) 2923 So(errn, ShouldBeNil) 2924 2925 Convey("You can mount them read-only", func() { 2926 remoteConfig := &RemoteConfig{ 2927 Accessor: accessor, 2928 CacheData: true, 2929 Write: false, 2930 } 2931 fs, err := New(cfg) 2932 So(err, ShouldBeNil) 2933 2934 err = fs.Mount(remoteConfig) 2935 So(err, ShouldBeNil) 2936 defer fs.Unmount() 2937 2938 Convey("Getting the contents of the dir works", func() { 2939 entries, err := ioutil.ReadDir(mountPoint) 2940 So(err, ShouldBeNil) 2941 So(len(entries), ShouldEqual, 0) 2942 }) 2943 }) 2944 2945 Convey("You can mount them writeable and writes work", func() { 2946 remoteConfig := &RemoteConfig{ 2947 Accessor: accessor, 2948 CacheData: false, 2949 Write: true, 2950 } 2951 fs, err := New(cfg) 2952 So(err, ShouldBeNil) 2953 2954 err = fs.Mount(remoteConfig) 2955 defer func() { 2956 err = fs.Unmount() 2957 So(err, ShouldBeNil) 2958 }() 2959 So(err, ShouldBeNil) 2960 2961 entries, err := ioutil.ReadDir(mountPoint) 2962 So(err, ShouldBeNil) 2963 So(len(entries), ShouldEqual, 0) 2964 2965 path := mountPoint + "/write.test" 2966 b := []byte("write test\n") 2967 err = ioutil.WriteFile(path, b, 0644) 2968 So(err, ShouldBeNil) 2969 2970 defer func() { 2971 err = os.Remove(path) 2972 So(err, ShouldBeNil) 2973 }() 2974 2975 // you can immediately read it back 2976 bytes, err := ioutil.ReadFile(path) 2977 So(err, ShouldBeNil) 2978 So(bytes, ShouldResemble, b) 2979 2980 // and it's statable and listable 2981 _, err = os.Stat(path) 2982 So(err, ShouldBeNil) 2983 2984 entries, err = ioutil.ReadDir(mountPoint) 2985 So(err, ShouldBeNil) 2986 details := dirDetails(entries) 2987 rootEntries := []string{"write.test:file:11"} 2988 So(details, ShouldResemble, rootEntries) 2989 2990 err = fs.Unmount() 2991 So(err, ShouldBeNil) 2992 2993 _, err = os.Stat(path) 2994 So(err, ShouldNotBeNil) 2995 So(os.IsNotExist(err), ShouldBeTrue) 2996 2997 // remounting lets us read the file again - it actually got 2998 // uploaded 2999 err = fs.Mount(remoteConfig) 3000 So(err, ShouldBeNil) 3001 3002 bytes, err = ioutil.ReadFile(path) 3003 So(err, ShouldBeNil) 3004 So(bytes, ShouldResemble, b) 3005 }) 3006 3007 Convey("You can mount a non-empty dir for reading and a non-existent dir for writing", func() { 3008 remoteConfig := &RemoteConfig{ 3009 Accessor: accessor, 3010 CacheData: false, 3011 Write: true, 3012 } 3013 3014 manualConfig2 := &S3Config{ 3015 Target: target, 3016 AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"), 3017 SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"), 3018 } 3019 accessor, errn = NewS3Accessor(manualConfig2) 3020 So(errn, ShouldBeNil) 3021 remoteConfig2 := &RemoteConfig{ 3022 Accessor: accessor, 3023 CacheData: false, 3024 } 3025 3026 fs, err := New(cfg) 3027 So(err, ShouldBeNil) 3028 3029 err = fs.Mount(remoteConfig2, remoteConfig) 3030 defer func() { 3031 err = fs.Unmount() 3032 So(err, ShouldBeNil) 3033 }() 3034 So(err, ShouldBeNil) 3035 3036 entries, err := ioutil.ReadDir(mountPoint) 3037 So(err, ShouldBeNil) 3038 So(len(entries), ShouldEqual, 5) 3039 3040 details := dirDetails(entries) 3041 rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir"} 3042 So(details, ShouldResemble, rootEntries) 3043 3044 Convey("Reads and writes work", func() { 3045 source := mountPoint + "/numalphanum.txt" 3046 dest := mountPoint + "/write.test" 3047 err := exec.Command("cp", source, dest).Run() 3048 So(err, ShouldBeNil) 3049 3050 defer func() { 3051 err = os.Remove(dest) 3052 So(err, ShouldBeNil) 3053 }() 3054 3055 // you can immediately read it back 3056 bytes, err := ioutil.ReadFile(dest) 3057 So(err, ShouldBeNil) 3058 b := []byte("1234567890abcdefghijklmnopqrstuvwxyz1234567890\n") 3059 So(bytes, ShouldResemble, b) 3060 3061 // and it's statable and listable 3062 _, err = os.Stat(dest) 3063 So(err, ShouldBeNil) 3064 3065 entries, err = ioutil.ReadDir(mountPoint) 3066 So(err, ShouldBeNil) 3067 details := dirDetails(entries) 3068 rootEntries := []string{"100k.lines:file:700000", bigFileEntry, "emptyDir:dir", "numalphanum.txt:file:47", "sub:dir", "write.test:file:47"} 3069 So(details, ShouldResemble, rootEntries) 3070 3071 err = fs.Unmount() 3072 So(err, ShouldBeNil) 3073 3074 _, err = os.Stat(dest) 3075 So(err, ShouldNotBeNil) 3076 So(os.IsNotExist(err), ShouldBeTrue) 3077 3078 // remounting lets us read the file again - it actually got 3079 // uploaded 3080 err = fs.Mount(remoteConfig2, remoteConfig) 3081 So(err, ShouldBeNil) 3082 3083 bytes, err = ioutil.ReadFile(dest) 3084 So(err, ShouldBeNil) 3085 So(bytes, ShouldResemble, b) 3086 }) 3087 }) 3088 }) 3089 3090 if strings.HasPrefix(target, "https://cog.sanger.ac.uk") { 3091 Convey("You can mount a public bucket", t, func() { 3092 manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository" 3093 accessor, errn = NewS3Accessor(manualConfig) 3094 So(errn, ShouldBeNil) 3095 remoteConfig := &RemoteConfig{ 3096 Accessor: accessor, 3097 CacheData: true, 3098 Write: false, 3099 } 3100 fs, err := New(cfg) 3101 So(err, ShouldBeNil) 3102 3103 err = fs.Mount(remoteConfig) 3104 So(err, ShouldBeNil) 3105 3106 defer func() { 3107 err = fs.Unmount() 3108 So(err, ShouldBeNil) 3109 }() 3110 3111 Convey("Listing mount directory works", func() { 3112 entries, errr := ioutil.ReadDir(mountPoint) 3113 So(errr, ShouldBeNil) 3114 3115 details := dirDetails(entries) 3116 So(details, ShouldContain, "cram_cache:dir") 3117 So(details, ShouldContain, "references:dir") 3118 }) 3119 3120 Convey("You can immediately stat deep files", func() { 3121 fasta := mountPoint + "/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla" 3122 // _, err := os.Stat(fasta + ".fa") //*** temp removed 3123 // So(err, ShouldBeNil) 3124 _, err = os.Stat(fasta + ".fa.alt") 3125 So(err, ShouldBeNil) 3126 _, err = os.Stat(fasta + ".fa.fai") 3127 So(err, ShouldBeNil) 3128 _, err = os.Stat(fasta + ".dict") 3129 So(err, ShouldBeNil) 3130 }) 3131 }) 3132 3133 Convey("You can mount a public bucket at a deep path", t, func() { 3134 manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta" 3135 accessor, errn = NewS3Accessor(manualConfig) 3136 So(errn, ShouldBeNil) 3137 remoteConfig := &RemoteConfig{ 3138 Accessor: accessor, 3139 CacheData: true, 3140 Write: false, 3141 } 3142 fs, err := New(cfg) 3143 So(err, ShouldBeNil) 3144 3145 err = fs.Mount(remoteConfig) 3146 So(err, ShouldBeNil) 3147 3148 defer func() { 3149 err = fs.Unmount() 3150 So(err, ShouldBeNil) 3151 }() 3152 3153 Convey("Listing mount directory works", func() { 3154 entries, errr := ioutil.ReadDir(mountPoint) 3155 So(errr, ShouldBeNil) 3156 3157 details := dirDetails(entries) 3158 So(details, ShouldContain, "Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict:file:756744") 3159 }) 3160 3161 Convey("You can immediately stat files within", func() { 3162 fasta := mountPoint + "/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla" 3163 // _, err := os.Stat(fasta + ".fa") 3164 // So(err, ShouldBeNil) 3165 _, err = os.Stat(fasta + ".fa.alt") 3166 So(err, ShouldBeNil) 3167 _, err = os.Stat(fasta + ".fa.fai") 3168 So(err, ShouldBeNil) 3169 _, err = os.Stat(fasta + ".dict") 3170 So(err, ShouldBeNil) 3171 }) 3172 }) 3173 3174 Convey("You can multiplex different buckets", t, func() { 3175 manualConfig2 := &S3Config{ 3176 Target: target + "/sub", 3177 AccessKey: os.Getenv("AWS_ACCESS_KEY_ID"), 3178 SecretKey: os.Getenv("AWS_SECRET_ACCESS_KEY"), 3179 } 3180 accessor2, err := NewS3Accessor(manualConfig2) 3181 So(err, ShouldBeNil) 3182 remoteConfig2 := &RemoteConfig{ 3183 Accessor: accessor2, 3184 CacheData: false, 3185 } 3186 3187 manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository/references/Homo_sapiens/GRCh38_full_analysis_set_plus_decoy_hla/all/fasta" 3188 accessor, err = NewS3Accessor(manualConfig) 3189 So(err, ShouldBeNil) 3190 remoteConfig := &RemoteConfig{ 3191 Accessor: accessor, 3192 CacheData: true, 3193 Write: false, 3194 } 3195 3196 fs, err := New(cfg) 3197 So(err, ShouldBeNil) 3198 3199 err = fs.Mount(remoteConfig, remoteConfig2) 3200 So(err, ShouldBeNil) 3201 3202 defer func() { 3203 err = fs.Unmount() 3204 So(err, ShouldBeNil) 3205 }() 3206 3207 Convey("Listing mount directory works", func() { 3208 entries, err := ioutil.ReadDir(mountPoint) 3209 So(err, ShouldBeNil) 3210 3211 details := dirDetails(entries) 3212 So(details, ShouldContain, "Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict:file:756744") 3213 So(details, ShouldContain, "empty.file:file:0") 3214 }) 3215 3216 Convey("You can immediately stat files within", func() { 3217 _, err := os.Stat(mountPoint + "/Homo_sapiens.GRCh38_full_analysis_set_plus_decoy_hla.dict") 3218 So(err, ShouldBeNil) 3219 _, err = os.Stat(mountPoint + "/empty.file") 3220 So(err, ShouldBeNil) 3221 }) 3222 }) 3223 3224 Convey("You can mount a public bucket with blank credentials", t, func() { 3225 manualConfig.Target = "https://cog.sanger.ac.uk/npg-repository" 3226 manualConfig.AccessKey = "" 3227 manualConfig.SecretKey = "" 3228 accessor, errn = NewS3Accessor(manualConfig) 3229 So(errn, ShouldBeNil) 3230 remoteConfig := &RemoteConfig{ 3231 Accessor: accessor, 3232 CacheData: true, 3233 Write: false, 3234 } 3235 3236 fs, err := New(cfg) 3237 So(err, ShouldBeNil) 3238 3239 err = fs.Mount(remoteConfig) 3240 So(err, ShouldBeNil) 3241 3242 defer func() { 3243 err = fs.Unmount() 3244 So(err, ShouldBeNil) 3245 }() 3246 3247 Convey("Listing mount directory works", func() { 3248 entries, err := ioutil.ReadDir(mountPoint) 3249 So(err, ShouldBeNil) 3250 3251 details := dirDetails(entries) 3252 So(details, ShouldContain, "cram_cache:dir") 3253 So(details, ShouldContain, "references:dir") 3254 }) 3255 }) 3256 } 3257 } 3258 3259 func dirDetails(entries []os.FileInfo) []string { 3260 details := make([]string, 0, len(entries)) 3261 for _, entry := range entries { 3262 info := entry.Name() 3263 if entry.IsDir() { 3264 info += ":dir" 3265 } else { 3266 info += fmt.Sprintf(":file:%d", entry.Size()) 3267 } 3268 details = append(details, info) 3269 } 3270 sort.Slice(details, func(i, j int) bool { return details[i] < details[j] }) 3271 return details 3272 } 3273 3274 func streamFile(src string, seek int64) (read int64, err error) { 3275 r, err := os.Open(src) 3276 if err != nil { 3277 return read, err 3278 } 3279 if seek > 0 { 3280 r.Seek(seek, io.SeekStart) 3281 } 3282 read, err = stream(r) 3283 r.Close() 3284 return read, err 3285 } 3286 3287 func stream(r io.Reader) (read int64, err error) { 3288 br := bufio.NewReader(r) 3289 b := make([]byte, 1000) 3290 for { 3291 done, rerr := br.Read(b) 3292 if rerr != nil { 3293 if rerr != io.EOF { 3294 err = rerr 3295 } 3296 break 3297 } 3298 read += int64(done) 3299 } 3300 return read, err 3301 }