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  }