github.com/yandex-cloud/geesefs@v0.40.9/internal/goofys_unix_test.go (about) 1 // Copyright 2015 - 2017 Ka-Hing Cheung 2 // Copyright 2021 Yandex LLC 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 // Tests for a mounted UNIX (but not Windows) FUSE FS 17 18 // +build !windows 19 20 package internal 21 22 import ( 23 "github.com/yandex-cloud/geesefs/internal/cfg" 24 25 "bytes" 26 "context" 27 "io/ioutil" 28 "os" 29 "os/exec" 30 "runtime" 31 "sync" 32 "syscall" 33 "time" 34 35 "golang.org/x/sys/unix" 36 "github.com/pkg/xattr" 37 "github.com/sirupsen/logrus" 38 . "gopkg.in/check.v1" 39 40 "github.com/jacobsa/fuse/fuseops" 41 42 bench_embed "github.com/yandex-cloud/geesefs/bench" 43 test_embed "github.com/yandex-cloud/geesefs/test" 44 ) 45 46 func (s *GoofysTest) mountCommon(t *C, mountPoint string, sameProc bool) { 47 err := os.MkdirAll(mountPoint, 0700) 48 if err == syscall.EEXIST { 49 err = nil 50 } 51 t.Assert(err, IsNil) 52 53 if !hasEnv("SAME_PROCESS_MOUNT") && !sameProc { 54 55 region := "" 56 if os.Getenv("REGION") != "" { 57 region = " --region \""+os.Getenv("REGION")+"\"" 58 } 59 exe := os.Getenv("GEESEFS_BINARY") 60 if exe == "" { 61 exe = "../geesefs" 62 } 63 c := exec.Command("/bin/bash", "-c", 64 exe+" --debug_fuse --debug_s3"+ 65 " --stat-cache-ttl "+s.fs.flags.StatCacheTTL.String()+ 66 " --log-file \"mount_"+t.TestName()+".log\""+ 67 " --endpoint \""+s.fs.flags.Endpoint+"\""+ 68 region+ 69 " "+s.fs.bucket+" "+mountPoint) 70 err = c.Run() 71 t.Assert(err, IsNil) 72 73 } else { 74 s.fs.flags.MountPoint = mountPoint 75 s.mfs, err = mountFuseFS(s.fs) 76 t.Assert(err, IsNil) 77 } 78 } 79 80 func (s *GoofysTest) umount(t *C, mountPoint string) { 81 var err error 82 for i := 0; i < 10; i++ { 83 err = TryUnmount(mountPoint) 84 if err != nil { 85 time.Sleep(100 * time.Millisecond) 86 } else { 87 break 88 } 89 } 90 t.Assert(err, IsNil) 91 92 os.Remove(mountPoint) 93 } 94 95 func FsyncDir(dir string) error { 96 fh, err := os.Open(dir) 97 if err != nil { 98 return err 99 } 100 err = fh.Sync() 101 if err != nil { 102 fh.Close() 103 return err 104 } 105 return fh.Close() 106 } 107 108 func IsAccessDenied(err error) bool { 109 return err == syscall.EACCES 110 } 111 112 func (s *GoofysTest) SetUpSuite(t *C) { 113 s.tmp = os.Getenv("TMPDIR") 114 if s.tmp == "" { 115 s.tmp = "/tmp" 116 } 117 os.WriteFile(s.tmp+"/fuse-test.sh", []byte(test_embed.FuseTestSh), 0755) 118 os.WriteFile(s.tmp+"/bench.sh", []byte(bench_embed.BenchSh), 0755) 119 } 120 121 func (s *GoofysTest) runFuseTest(t *C, mountPoint string, umount bool, cmdArgs ...string) { 122 s.mount(t, mountPoint) 123 124 if umount { 125 defer s.umount(t, mountPoint) 126 } 127 128 // if command starts with ./ or ../ then we are executing a 129 // relative path and cannot do chdir 130 chdir := cmdArgs[0][0] != '.' 131 132 cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) 133 cmd.Env = append(cmd.Env, os.Environ()...) 134 cmd.Env = append(cmd.Env, "FAST=true") 135 cmd.Env = append(cmd.Env, "LANG=C") 136 cmd.Env = append(cmd.Env, "LC_ALL=C") 137 cmd.Env = append(cmd.Env, "CLEANUP=false") 138 139 if true { 140 logger := cfg.NewLogger("test") 141 lvl := logrus.InfoLevel 142 logger.Formatter.(*cfg.LogHandle).Lvl = &lvl 143 w := logger.Writer() 144 145 cmd.Stdout = w 146 cmd.Stderr = w 147 } 148 149 if chdir { 150 oldCwd, err := os.Getwd() 151 t.Assert(err, IsNil) 152 153 err = os.Chdir(mountPoint) 154 t.Assert(err, IsNil) 155 156 defer os.Chdir(oldCwd) 157 } 158 159 err := cmd.Run() 160 t.Assert(err, IsNil) 161 } 162 163 func (s *GoofysTest) TestFuse(t *C) { 164 mountPoint := s.tmp + "/mnt" + s.fs.bucket 165 166 s.runFuseTest(t, mountPoint, true, s.tmp+"/fuse-test.sh", mountPoint) 167 } 168 169 func (s *GoofysTest) TestFuseWithTTL(t *C) { 170 s.fs.flags.StatCacheTTL = 60 * 1000 * 1000 * 1000 171 mountPoint := s.tmp + "/mnt" + s.fs.bucket 172 173 s.runFuseTest(t, mountPoint, true, s.tmp+"/fuse-test.sh", mountPoint) 174 } 175 176 func (s *GoofysTest) TestBenchLs(t *C) { 177 s.fs.flags.StatCacheTTL = 1 * time.Minute 178 mountPoint := s.tmp + "/mnt" + s.fs.bucket 179 s.setUpTestTimeout(t, 20*time.Minute) 180 s.runFuseTest(t, mountPoint, true, s.tmp+"/bench.sh", mountPoint, "ls") 181 } 182 183 func (s *GoofysTest) TestBenchCreate(t *C) { 184 s.fs.flags.StatCacheTTL = 1 * time.Minute 185 mountPoint := s.tmp + "/mnt" + s.fs.bucket 186 s.runFuseTest(t, mountPoint, true, s.tmp+"/bench.sh", mountPoint, "create") 187 } 188 189 func (s *GoofysTest) TestBenchCreateParallel(t *C) { 190 s.fs.flags.StatCacheTTL = 1 * time.Minute 191 mountPoint := s.tmp + "/mnt" + s.fs.bucket 192 s.runFuseTest(t, mountPoint, true, s.tmp+"/bench.sh", mountPoint, "create_parallel") 193 } 194 195 func (s *GoofysTest) TestBenchIO(t *C) { 196 s.fs.flags.StatCacheTTL = 1 * time.Minute 197 mountPoint := s.tmp + "/mnt" + s.fs.bucket 198 s.runFuseTest(t, mountPoint, true, s.tmp+"/bench.sh", mountPoint, "io") 199 } 200 201 func (s *GoofysTest) TestBenchFindTree(t *C) { 202 s.fs.flags.StatCacheTTL = 1 * time.Minute 203 mountPoint := s.tmp + "/mnt" + s.fs.bucket 204 205 s.runFuseTest(t, mountPoint, true, s.tmp+"/bench.sh", mountPoint, "find") 206 } 207 208 func (s *GoofysTest) TestIssue231(t *C) { 209 if isTravis() { 210 t.Skip("disable in travis, not sure if it has enough memory") 211 } 212 mountPoint := s.tmp + "/mnt" + s.fs.bucket 213 s.runFuseTest(t, mountPoint, true, s.tmp+"/bench.sh", mountPoint, "issue231") 214 } 215 216 func (s *GoofysTest) TestFuseWithPrefix(t *C) { 217 mountPoint := s.tmp + "/mnt" + s.fs.bucket 218 219 s.fs.Shutdown() 220 s.fs, _ = NewGoofys(context.Background(), s.fs.bucket+":testprefix", s.fs.flags) 221 222 s.runFuseTest(t, mountPoint, true, s.tmp+"/fuse-test.sh", mountPoint) 223 } 224 225 func (s *GoofysTest) TestClientForkExec(t *C) { 226 mountPoint := s.tmp + "/mnt" + s.fs.bucket 227 s.mount(t, mountPoint) 228 defer s.umount(t, mountPoint) 229 file := mountPoint + "/TestClientForkExec" 230 231 // Create new file. 232 fh, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0600) 233 t.Assert(err, IsNil) 234 defer func() { // Defer close file if it's not already closed. 235 if fh != nil { 236 fh.Close() 237 } 238 }() 239 // Write to file. 240 _, err = fh.WriteString("1.1;") 241 t.Assert(err, IsNil) 242 // The `Command` is run via fork+exec. 243 // So all the file descriptors are copied over to the child process. 244 // The child process 'closes' the files before exiting. This should 245 // not result in goofys failing file operations invoked from the test. 246 someCmd := exec.Command("echo", "hello") 247 err = someCmd.Run() 248 t.Assert(err, IsNil) 249 // One more write. 250 _, err = fh.WriteString("1.2;") 251 t.Assert(err, IsNil) 252 // Close file. 253 err = fh.Close() 254 t.Assert(err, IsNil) 255 fh = nil 256 // Check file content. 257 content, err := ioutil.ReadFile(file) 258 t.Assert(err, IsNil) 259 t.Assert(string(content), Equals, "1.1;1.2;") 260 261 // Repeat the same excercise, but now with an existing file. 262 fh, err = os.OpenFile(file, os.O_RDWR, 0600) 263 // Write to file. 264 _, err = fh.WriteString("2.1;") 265 // fork+exec. 266 someCmd = exec.Command("echo", "hello") 267 err = someCmd.Run() 268 t.Assert(err, IsNil) 269 // One more write. 270 _, err = fh.WriteString("2.2;") 271 t.Assert(err, IsNil) 272 // Close file. 273 err = fh.Close() 274 t.Assert(err, IsNil) 275 fh = nil 276 // Verify that the file is updated as per the new write. 277 content, err = ioutil.ReadFile(file) 278 t.Assert(err, IsNil) 279 t.Assert(string(content), Equals, "2.1;2.2;") 280 } 281 282 func (s *GoofysTest) TestXAttrFuse(t *C) { 283 if _, ok := s.cloud.(*ADLv1); ok { 284 t.Skip("ADLv1 doesn't support metadata") 285 } 286 287 _, checkETag := s.cloud.Delegate().(*S3Backend) 288 xattrPrefix := s.cloud.Capabilities().Name + "." 289 290 //fuseLog.Level = logrus.DebugLevel 291 mountPoint := s.tmp + "/mnt" + s.fs.bucket 292 s.mount(t, mountPoint) 293 defer s.umount(t, mountPoint) 294 295 // STANDARD storage-class may be present or not 296 expectedXattrs1 := xattrPrefix + "etag\x00" + 297 xattrPrefix + "storage-class\x00" + 298 "user.name\x00" 299 expectedXattrs2 := xattrPrefix + "etag\x00" + 300 "user.name\x00" 301 302 var buf [1024]byte 303 304 // error if size is too small (but not zero) 305 _, err := unix.Listxattr(mountPoint+"/file1", buf[:1]) 306 t.Assert(err, Equals, unix.ERANGE) 307 308 // 0 len buffer means interogate the size of buffer 309 nbytes, err := unix.Listxattr(mountPoint+"/file1", nil) 310 t.Assert(err, Equals, nil) 311 if nbytes != len(expectedXattrs2) { 312 t.Assert(nbytes, Equals, len(expectedXattrs1)) 313 } 314 315 nbytes, err = unix.Listxattr(mountPoint+"/file1", buf[:nbytes]) 316 t.Assert(err, IsNil) 317 if nbytes == len(expectedXattrs2) { 318 t.Assert(string(buf[:nbytes]), Equals, expectedXattrs2) 319 } else { 320 t.Assert(string(buf[:nbytes]), Equals, expectedXattrs1) 321 } 322 323 _, err = unix.Getxattr(mountPoint+"/file1", "user.name", buf[:1]) 324 t.Assert(err, Equals, unix.ERANGE) 325 326 nbytes, err = unix.Getxattr(mountPoint+"/file1", "user.name", nil) 327 t.Assert(err, IsNil) 328 t.Assert(nbytes, Equals, 9) 329 330 nbytes, err = unix.Getxattr(mountPoint+"/file1", "user.name", buf[:nbytes]) 331 t.Assert(err, IsNil) 332 t.Assert(nbytes, Equals, 9) 333 t.Assert(string(buf[:nbytes]), Equals, "file1+/#\x00") 334 335 if !s.cloud.Capabilities().DirBlob { 336 // dir1 has no xattrs 337 nbytes, err = unix.Listxattr(mountPoint+"/dir1", nil) 338 t.Assert(err, IsNil) 339 t.Assert(nbytes, Equals, 0) 340 341 nbytes, err = unix.Listxattr(mountPoint+"/dir1", buf[:1]) 342 t.Assert(err, IsNil) 343 t.Assert(nbytes, Equals, 0) 344 } 345 346 if checkETag { 347 _, err = unix.Getxattr(mountPoint+"/file1", "s3.etag", buf[:1]) 348 t.Assert(err, Equals, unix.ERANGE) 349 350 nbytes, err = unix.Getxattr(mountPoint+"/file1", "s3.etag", nil) 351 t.Assert(err, IsNil) 352 // 32 bytes md5 plus quotes 353 t.Assert(nbytes, Equals, 34) 354 355 nbytes, err = unix.Getxattr(mountPoint+"/file1", "s3.etag", buf[:nbytes]) 356 t.Assert(err, IsNil) 357 t.Assert(nbytes, Equals, 34) 358 t.Assert(string(buf[:nbytes]), Equals, 359 "\"826e8142e6baabe8af779f5f490cf5f5\"") 360 } 361 } 362 363 func (s *GoofysTest) TestPythonCopyTree(t *C) { 364 s.clearPrefix(t, s.cloud, "dir5") 365 366 mountPoint := s.tmp + "/mnt" + s.fs.bucket 367 368 s.runFuseTest(t, mountPoint, true, "python", "-c", 369 "import shutil; shutil.copytree('dir2', 'dir5')", 370 mountPoint) 371 } 372 373 func (s *GoofysTest) TestCreateRenameBeforeCloseFuse(t *C) { 374 if s.azurite { 375 // Azurite returns 400 when copy source doesn't exist 376 // https://github.com/Azure/Azurite/issues/219 377 // so our code to ignore ENOENT fails 378 t.Skip("https://github.com/Azure/Azurite/issues/219") 379 } 380 381 mountPoint := s.tmp + "/mnt" + s.fs.bucket 382 383 s.mount(t, mountPoint) 384 defer s.umount(t, mountPoint) 385 386 from := mountPoint + "/newfile" 387 to := mountPoint + "/newfile2" 388 389 fh, err := os.Create(from) 390 t.Assert(err, IsNil) 391 defer func() { 392 // close the file if the test failed so we can unmount 393 if fh != nil { 394 fh.Close() 395 } 396 }() 397 398 _, err = fh.WriteString("hello world") 399 t.Assert(err, IsNil) 400 401 err = os.Rename(from, to) 402 t.Assert(err, IsNil) 403 404 err = fh.Close() 405 t.Assert(err, IsNil) 406 fh = nil 407 408 _, err = os.Stat(from) 409 t.Assert(err, NotNil) 410 pathErr, ok := err.(*os.PathError) 411 t.Assert(ok, Equals, true) 412 t.Assert(pathErr.Err, Equals, syscall.ENOENT) 413 414 content, err := ioutil.ReadFile(to) 415 t.Assert(err, IsNil) 416 t.Assert(string(content), Equals, "hello world") 417 } 418 419 func (s *GoofysTest) TestRenameBeforeCloseFuse(t *C) { 420 mountPoint := s.tmp + "/mnt" + s.fs.bucket 421 422 s.mount(t, mountPoint) 423 defer s.umount(t, mountPoint) 424 425 from := mountPoint + "/newfile" 426 to := mountPoint + "/newfile2" 427 428 err := ioutil.WriteFile(from, []byte(""), 0600) 429 t.Assert(err, IsNil) 430 431 fh, err := os.OpenFile(from, os.O_WRONLY, 0600) 432 t.Assert(err, IsNil) 433 defer func() { 434 // close the file if the test failed so we can unmount 435 if fh != nil { 436 fh.Close() 437 } 438 }() 439 440 _, err = fh.WriteString("hello world") 441 t.Assert(err, IsNil) 442 443 err = os.Rename(from, to) 444 t.Assert(err, IsNil) 445 446 err = fh.Close() 447 t.Assert(err, IsNil) 448 fh = nil 449 450 _, err = os.Stat(from) 451 t.Assert(err, NotNil) 452 pathErr, ok := err.(*os.PathError) 453 t.Assert(ok, Equals, true) 454 t.Assert(pathErr.Err, Equals, syscall.ENOENT) 455 456 content, err := ioutil.ReadFile(to) 457 t.Assert(err, IsNil) 458 t.Assert(string(content), Equals, "hello world") 459 } 460 461 func containsFile(dir, wantedFile string) bool { 462 files, err := os.ReadDir(dir) 463 if err != nil { 464 return false 465 } 466 for _, f := range files { 467 if f.Name() == wantedFile { 468 return true 469 } 470 } 471 return false 472 } 473 474 // Notification tests: 475 // 1. Lookup and read a file, modify it out of band, refresh and check that 476 // it returns the new size and data 477 // 2. Lookup and read a file, remove it out of band, refresh and check that 478 // it does not exist and does not return an entry in unknown state 479 // 3. List a non-root directory, add a file in it, refresh, list it again 480 // and check that it has the new file 481 // 4. List a non-root directory, modify a file in it, refresh dir, list it again 482 // and check that the file is updated 483 // 5. List a non-root directory, remove a file in it, refresh dir, list it again 484 // and check that the file does not exists 485 // 6-10. Same as 1-5, but with the root directory 486 487 // 3, 1, 2 488 func (s *GoofysTest) TestNotifyRefreshFile(t *C) { 489 s.testNotifyRefresh(t, false, false) 490 } 491 492 // 3, 4, 5 493 func (s *GoofysTest) TestNotifyRefreshDir(t *C) { 494 s.testNotifyRefresh(t, false, true) 495 } 496 497 // 8, 6, 7 498 func (s *GoofysTest) TestNotifyRefreshSubdir(t *C) { 499 s.testNotifyRefresh(t, true, false) 500 } 501 502 // 8, 9, 10 503 func (s *GoofysTest) TestNotifyRefreshSubfile(t *C) { 504 s.testNotifyRefresh(t, true, true) 505 } 506 507 func (s *GoofysTest) testNotifyRefresh(t *C, testInSubdir bool, testRefreshDir bool) { 508 mountPoint := s.tmp + "/mnt" + s.fs.bucket 509 s.mount(t, mountPoint) 510 defer s.umount(t, mountPoint) 511 512 testdir := mountPoint 513 subdir := "" 514 if testInSubdir { 515 testdir += "/dir1" 516 subdir = "dir1/" 517 } 518 refreshFile := testdir 519 if !testRefreshDir { 520 refreshFile += "/testnotify" 521 } 522 523 t.Assert(containsFile(testdir, "testnotify"), Equals, false) 524 525 // Create file 526 _, err := s.cloud.PutBlob(&PutBlobInput{ 527 Key: subdir+"testnotify", 528 Body: bytes.NewReader([]byte("foo")), 529 Size: PUInt64(3), 530 }) 531 t.Assert(err, IsNil) 532 533 t.Assert(containsFile(testdir, "testnotify"), Equals, false) 534 535 // Force-refresh 536 err = xattr.Set(testdir, ".invalidate", []byte("")) 537 t.Assert(err, IsNil) 538 539 t.Assert(containsFile(testdir, "testnotify"), Equals, true) 540 541 buf, err := ioutil.ReadFile(testdir+"/testnotify") 542 t.Assert(err, IsNil) 543 t.Assert(string(buf), Equals, "foo") 544 545 // Update file 546 _, err = s.cloud.PutBlob(&PutBlobInput{ 547 Key: subdir+"testnotify", 548 Body: bytes.NewReader([]byte("baur")), 549 Size: PUInt64(4), 550 }) 551 t.Assert(err, IsNil) 552 553 buf, err = ioutil.ReadFile(testdir+"/testnotify") 554 t.Assert(err, IsNil) 555 t.Assert(string(buf), Equals, "foo") 556 557 // Force-refresh 558 err = xattr.Set(refreshFile, ".invalidate", []byte("")) 559 t.Assert(err, IsNil) 560 time.Sleep(500 * time.Millisecond) 561 562 buf, err = ioutil.ReadFile(testdir+"/testnotify") 563 t.Assert(err, IsNil) 564 t.Assert(string(buf), Equals, "baur") 565 566 // Delete file 567 _, err = s.cloud.DeleteBlob(&DeleteBlobInput{ 568 Key: subdir+"testnotify", 569 }) 570 t.Assert(err, IsNil) 571 572 buf, err = ioutil.ReadFile(testdir+"/testnotify") 573 t.Assert(err, IsNil) 574 t.Assert(string(buf), Equals, "baur") 575 576 // Force-refresh 577 err = xattr.Set(refreshFile, ".invalidate", []byte("")) 578 t.Assert(err, IsNil) 579 580 // Refresh is done asynchronously (it needs kernel locks), so wait a bit 581 time.Sleep(500 * time.Millisecond) 582 583 _, err = os.Open(testdir+"/testnotify") 584 t.Assert(os.IsNotExist(err), Equals, true) 585 586 t.Assert(containsFile(testdir, "testnotify"), Equals, false) 587 } 588 589 func (s *GoofysTest) TestNestedMountUnmountSimple(t *C) { 590 t.Skip("Test for the strange 'child mount' feature, unusable from cmdline") 591 childBucket := "goofys-test-" + RandStringBytesMaskImprSrc(16) 592 childCloud := s.newBackend(t, childBucket, true) 593 594 parFileContent := "parent" 595 childFileContent := "child" 596 parEnv := map[string]*string{ 597 "childmnt/x/in_child_and_par": &parFileContent, 598 "childmnt/x/in_par_only": &parFileContent, 599 "nonchildmnt/something": &parFileContent, 600 } 601 childEnv := map[string]*string{ 602 "x/in_child_only": &childFileContent, 603 "x/in_child_and_par": &childFileContent, 604 } 605 s.setupBlobs(s.cloud, t, parEnv) 606 s.setupBlobs(childCloud, t, childEnv) 607 608 rootMountPath := s.tmp + "/fusetesting/" + RandStringBytesMaskImprSrc(16) 609 s.mountInside(t, rootMountPath) 610 defer s.umount(t, rootMountPath) 611 // Files under /tmp/fusetesting/ should all be from goofys root. 612 verifyFileData(t, rootMountPath, "childmnt/x/in_par_only", &parFileContent) 613 verifyFileData(t, rootMountPath, "childmnt/x/in_child_and_par", &parFileContent) 614 verifyFileData(t, rootMountPath, "nonchildmnt/something", &parFileContent) 615 verifyFileData(t, rootMountPath, "childmnt/x/in_child_only", nil) 616 617 childMount := &Mount{"childmnt", childCloud, "", false} 618 s.fs.Mount(childMount) 619 // Now files under /tmp/fusetesting/childmnt should be from childBucket 620 verifyFileData(t, rootMountPath, "childmnt/x/in_par_only", nil) 621 verifyFileData(t, rootMountPath, "childmnt/x/in_child_and_par", &childFileContent) 622 verifyFileData(t, rootMountPath, "childmnt/x/in_child_only", &childFileContent) 623 // /tmp/fusetesting/nonchildmnt should be from parent bucket. 624 verifyFileData(t, rootMountPath, "nonchildmnt/something", &parFileContent) 625 626 s.fs.Unmount(childMount.name) 627 // Child is unmounted. So files under /tmp/fusetesting/ should all be from goofys root. 628 verifyFileData(t, rootMountPath, "childmnt/x/in_par_only", &parFileContent) 629 verifyFileData(t, rootMountPath, "childmnt/x/in_child_and_par", &parFileContent) 630 verifyFileData(t, rootMountPath, "nonchildmnt/something", &parFileContent) 631 verifyFileData(t, rootMountPath, "childmnt/x/in_child_only", nil) 632 } 633 634 func (s *GoofysTest) TestUnmountBucketWithChild(t *C) { 635 t.Skip("Test for the strange 'child mount' feature, unusable from cmdline") 636 637 // This bucket will be mounted at ${goofysroot}/c 638 cBucket := "goofys-test-" + RandStringBytesMaskImprSrc(16) 639 cCloud := s.newBackend(t, cBucket, true) 640 641 // This bucket will be mounted at ${goofysroot}/c/c 642 ccBucket := "goofys-test-" + RandStringBytesMaskImprSrc(16) 643 ccCloud := s.newBackend(t, ccBucket, true) 644 645 pFileContent := "parent" 646 cFileContent := "child" 647 ccFileContent := "childchild" 648 pEnv := map[string]*string{ 649 "c/c/x/foo": &pFileContent, 650 } 651 cEnv := map[string]*string{ 652 "c/x/foo": &cFileContent, 653 } 654 ccEnv := map[string]*string{ 655 "x/foo": &ccFileContent, 656 } 657 658 s.setupBlobs(s.cloud, t, pEnv) 659 s.setupBlobs(cCloud, t, cEnv) 660 s.setupBlobs(ccCloud, t, ccEnv) 661 662 rootMountPath := s.tmp + "/fusetesting/" + RandStringBytesMaskImprSrc(16) 663 s.mountInside(t, rootMountPath) 664 defer s.umount(t, rootMountPath) 665 // c/c/foo should come from root mount. 666 verifyFileData(t, rootMountPath, "c/c/x/foo", &pFileContent) 667 668 cMount := &Mount{"c", cCloud, "", false} 669 s.fs.Mount(cMount) 670 // c/c/foo should come from "c" mount. 671 verifyFileData(t, rootMountPath, "c/c/x/foo", &cFileContent) 672 673 ccMount := &Mount{"c/c", ccCloud, "", false} 674 s.fs.Mount(ccMount) 675 // c/c/foo should come from "c/c" mount. 676 verifyFileData(t, rootMountPath, "c/c/x/foo", &ccFileContent) 677 678 s.fs.Unmount(cMount.name) 679 // c/c/foo should still come from "c/c" mount. 680 verifyFileData(t, rootMountPath, "c/c/x/foo", &ccFileContent) 681 } 682 683 // Specific to "lowlevel" fuse, so also checked here 684 func (s *GoofysTest) TestConcurrentRefDeref(t *C) { 685 fsint := NewGoofysFuse(s.fs) 686 root := s.getRoot(t) 687 688 lookupOp := fuseops.LookUpInodeOp{ 689 Parent: root.Id, 690 Name: "file1", 691 } 692 693 for i := 0; i < 20; i++ { 694 err := fsint.LookUpInode(nil, &lookupOp) 695 t.Assert(err, IsNil) 696 t.Assert(lookupOp.Entry.Child, Not(Equals), 0) 697 698 var wg sync.WaitGroup 699 700 // The idea of this test is just that lookup->forget->lookup shouldn't crash with "Unknown inode: xxx" 701 wg.Add(2) 702 go func() { 703 // we want to yield to the forget goroutine so that it's run first 704 // to trigger this bug 705 if i%2 == 0 { 706 runtime.Gosched() 707 } 708 fsint.LookUpInode(nil, &lookupOp) 709 wg.Done() 710 }() 711 go func() { 712 fsint.ForgetInode(nil, &fuseops.ForgetInodeOp{ 713 Inode: lookupOp.Entry.Child, 714 N: 1, 715 }) 716 wg.Done() 717 }() 718 719 wg.Wait() 720 721 fsint.ForgetInode(nil, &fuseops.ForgetInodeOp{ 722 Inode: lookupOp.Entry.Child, 723 N: 1, 724 }) 725 } 726 } 727 728 func (s *GoofysTest) TestDirMTime(t *C) { 729 s.fs.flags.StatCacheTTL = 1 * time.Minute 730 // enable cheap to ensure GET dir/ will come back before LIST dir/ 731 s.fs.flags.Cheap = true 732 733 root := s.getRoot(t) 734 t.Assert(time.Time{}.Before(root.Attributes.Mtime), Equals, true) 735 736 dir1, err := s.fs.LookupPath("dir1") 737 t.Assert(err, IsNil) 738 739 attr1 := dir1.GetAttributes() 740 m1 := attr1.Mtime 741 742 time.Sleep(2 * time.Second) 743 744 dir2, err := dir1.MkDir("dir2") 745 t.Assert(err, IsNil) 746 747 attr2 := dir2.GetAttributes() 748 m2 := attr2.Mtime 749 t.Assert(m1.Add(2*time.Second).Before(m2), Equals, true) 750 751 // dir1 didn't have an explicit mtime, so it should update now 752 // that we did a mkdir inside it 753 attr1 = dir1.GetAttributes() 754 m1 = attr1.Mtime 755 t.Assert(m1, Equals, m2) 756 757 time.Sleep(2 * time.Second) 758 759 // different dir2 760 dir2, err = s.fs.LookupPath("dir2") 761 t.Assert(err, IsNil) 762 763 attr2 = dir2.GetAttributes() 764 m2 = attr2.Mtime 765 766 // this fails because we are listing dir/, which means we 767 // don't actually see the dir blob dir2/dir3/ (it's returned 768 // as common prefix), so we can't get dir3's mtime 769 if false { 770 // dir2/dir3/ exists and has mtime 771 s.readDirIntoCache(t, dir2.Id) 772 dir3, err := s.fs.LookupPath("dir2/dir3") 773 t.Assert(err, IsNil) 774 775 attr3 := dir3.GetAttributes() 776 // setupDefaultEnv is before mounting 777 t.Assert(attr3.Mtime.Before(m2), Equals, true) 778 } 779 780 time.Sleep(time.Second) 781 782 params := &PutBlobInput{ 783 Key: "dir2/newfile", 784 Body: bytes.NewReader([]byte("foo")), 785 Size: PUInt64(3), 786 } 787 _, err = s.cloud.PutBlob(params) 788 t.Assert(err, IsNil) 789 790 // dir2 could be already preloaded due to optimisations, it may have older mtime 791 // FIXME: (maybe) update parent directory modification times when flushing files inside them 792 s.fs.flags.StatCacheTTL = 1 * time.Second 793 s.readDirIntoCache(t, dir2.Id) 794 s.fs.flags.StatCacheTTL = 1 * time.Minute 795 796 newfile, err := dir2.LookUp("newfile", false) 797 t.Assert(err, IsNil) 798 799 attr2New := dir2.GetAttributes() 800 // mtime should reflect that of the latest object 801 // GCS can return nano second resolution so truncate to second for compare 802 t.Assert(attr2New.Mtime.Unix(), Equals, newfile.Attributes.Mtime.Unix()) 803 t.Assert(m2.Before(attr2New.Mtime), Equals, true) 804 }