Merge branch '18681-install-passenger-only-when-required'
[arvados.git] / sdk / go / arvados / fs_site_test.go
index 51ca88764e6625dd25428a526f325e6acf138af8..bf24efa7ed055f78fe2db76c56f43794cf0d0e0b 100644 (file)
@@ -5,8 +5,13 @@
 package arvados
 
 import (
+       "fmt"
+       "io"
+       "io/ioutil"
        "net/http"
        "os"
+       "strings"
+       "syscall"
        "time"
 
        check "gopkg.in/check.v1"
@@ -131,16 +136,239 @@ func (s *SiteFSSuite) TestByUUIDAndPDH(c *check.C) {
        c.Check(names, check.DeepEquals, []string{"baz"})
 
        _, err = s.fs.OpenFile("/by_id/"+fixtureNonexistentCollection, os.O_RDWR|os.O_CREATE, 0755)
-       c.Check(err, check.Equals, ErrInvalidArgument)
+       c.Check(err, ErrorIs, ErrInvalidOperation)
        err = s.fs.Rename("/by_id/"+fixtureFooCollection, "/by_id/beep")
-       c.Check(err, check.Equals, ErrInvalidArgument)
+       c.Check(err, ErrorIs, ErrInvalidOperation)
        err = s.fs.Rename("/by_id/"+fixtureFooCollection+"/foo", "/by_id/beep")
-       c.Check(err, check.Equals, ErrInvalidArgument)
+       c.Check(err, ErrorIs, ErrInvalidOperation)
        _, err = s.fs.Stat("/by_id/beep")
        c.Check(err, check.Equals, os.ErrNotExist)
        err = s.fs.Rename("/by_id/"+fixtureFooCollection+"/foo", "/by_id/"+fixtureFooCollection+"/bar")
        c.Check(err, check.IsNil)
 
        err = s.fs.Rename("/by_id", "/beep")
-       c.Check(err, check.Equals, ErrInvalidArgument)
+       c.Check(err, ErrorIs, ErrInvalidOperation)
+}
+
+// Copy subtree from OS src to dst path inside fs. If src is a
+// directory, dst must exist and be a directory.
+func copyFromOS(fs FileSystem, dst, src string) error {
+       inf, err := os.Open(src)
+       if err != nil {
+               return err
+       }
+       defer inf.Close()
+       dirents, err := inf.Readdir(-1)
+       if e, ok := err.(*os.PathError); ok {
+               if e, ok := e.Err.(syscall.Errno); ok {
+                       if e == syscall.ENOTDIR {
+                               err = syscall.ENOTDIR
+                       }
+               }
+       }
+       if err == syscall.ENOTDIR {
+               outf, err := fs.OpenFile(dst, os.O_CREATE|os.O_EXCL|os.O_TRUNC|os.O_WRONLY, 0700)
+               if err != nil {
+                       return fmt.Errorf("open %s: %s", dst, err)
+               }
+               defer outf.Close()
+               _, err = io.Copy(outf, inf)
+               if err != nil {
+                       return fmt.Errorf("%s: copying data from %s: %s", dst, src, err)
+               }
+               err = outf.Close()
+               if err != nil {
+                       return err
+               }
+       } else if err != nil {
+               return fmt.Errorf("%s: readdir: %T %s", src, err, err)
+       } else {
+               {
+                       d, err := fs.Open(dst)
+                       if err != nil {
+                               return fmt.Errorf("opendir(%s): %s", dst, err)
+                       }
+                       d.Close()
+               }
+               for _, ent := range dirents {
+                       if ent.Name() == "." || ent.Name() == ".." {
+                               continue
+                       }
+                       dstname := dst + "/" + ent.Name()
+                       if ent.IsDir() {
+                               err = fs.Mkdir(dstname, 0700)
+                               if err != nil {
+                                       return fmt.Errorf("mkdir %s: %s", dstname, err)
+                               }
+                       }
+                       err = copyFromOS(fs, dstname, src+"/"+ent.Name())
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+       return nil
+}
+
+func (s *SiteFSSuite) TestSnapshotSplice(c *check.C) {
+       s.fs.MountProject("home", "")
+       thisfile, err := ioutil.ReadFile("fs_site_test.go")
+       c.Assert(err, check.IsNil)
+
+       var src1 Collection
+       err = s.client.RequestAndDecode(&src1, "POST", "arvados/v1/collections", nil, map[string]interface{}{
+               "collection": map[string]string{
+                       "name":       "TestSnapshotSplice src1",
+                       "owner_uuid": fixtureAProjectUUID,
+               },
+       })
+       c.Assert(err, check.IsNil)
+       defer s.client.RequestAndDecode(nil, "DELETE", "arvados/v1/collections/"+src1.UUID, nil, nil)
+       err = s.fs.Sync()
+       c.Assert(err, check.IsNil)
+       err = copyFromOS(s.fs, "/home/A Project/TestSnapshotSplice src1", "..") // arvados.git/sdk/go
+       c.Assert(err, check.IsNil)
+
+       var src2 Collection
+       err = s.client.RequestAndDecode(&src2, "POST", "arvados/v1/collections", nil, map[string]interface{}{
+               "collection": map[string]string{
+                       "name":       "TestSnapshotSplice src2",
+                       "owner_uuid": fixtureAProjectUUID,
+               },
+       })
+       c.Assert(err, check.IsNil)
+       defer s.client.RequestAndDecode(nil, "DELETE", "arvados/v1/collections/"+src2.UUID, nil, nil)
+       err = s.fs.Sync()
+       c.Assert(err, check.IsNil)
+       err = copyFromOS(s.fs, "/home/A Project/TestSnapshotSplice src2", "..") // arvados.git/sdk/go
+       c.Assert(err, check.IsNil)
+
+       var dst Collection
+       err = s.client.RequestAndDecode(&dst, "POST", "arvados/v1/collections", nil, map[string]interface{}{
+               "collection": map[string]string{
+                       "name":       "TestSnapshotSplice dst",
+                       "owner_uuid": fixtureAProjectUUID,
+               },
+       })
+       c.Assert(err, check.IsNil)
+       defer s.client.RequestAndDecode(nil, "DELETE", "arvados/v1/collections/"+dst.UUID, nil, nil)
+       err = s.fs.Sync()
+       c.Assert(err, check.IsNil)
+
+       dstPath := "/home/A Project/TestSnapshotSplice dst"
+       err = copyFromOS(s.fs, dstPath, "..") // arvados.git/sdk/go
+       c.Assert(err, check.IsNil)
+
+       // Snapshot directory
+       snap1, err := Snapshot(s.fs, "/home/A Project/TestSnapshotSplice src1/ctxlog")
+       c.Check(err, check.IsNil)
+       // Attach same snapshot twice, at paths that didn't exist before
+       err = Splice(s.fs, dstPath+"/ctxlog-copy", snap1)
+       c.Check(err, check.IsNil)
+       err = Splice(s.fs, dstPath+"/ctxlog-copy2", snap1)
+       c.Check(err, check.IsNil)
+       // Splicing a snapshot twice results in two independent copies
+       err = s.fs.Rename(dstPath+"/ctxlog-copy2/log.go", dstPath+"/ctxlog-copy/log2.go")
+       c.Check(err, check.IsNil)
+       _, err = s.fs.Open(dstPath + "/ctxlog-copy2/log.go")
+       c.Check(err, check.Equals, os.ErrNotExist)
+       f, err := s.fs.Open(dstPath + "/ctxlog-copy/log.go")
+       if c.Check(err, check.IsNil) {
+               buf, err := ioutil.ReadAll(f)
+               c.Check(err, check.IsNil)
+               c.Check(string(buf), check.Not(check.Equals), "")
+               f.Close()
+       }
+
+       // Snapshot regular file
+       snapFile, err := Snapshot(s.fs, "/home/A Project/TestSnapshotSplice src1/arvados/fs_site_test.go")
+       c.Check(err, check.IsNil)
+       // Replace dir with file
+       err = Splice(s.fs, dstPath+"/ctxlog-copy2", snapFile)
+       c.Check(err, check.IsNil)
+       if f, err := s.fs.Open(dstPath + "/ctxlog-copy2"); c.Check(err, check.IsNil) {
+               buf, err := ioutil.ReadAll(f)
+               c.Check(err, check.IsNil)
+               c.Check(string(buf), check.Equals, string(thisfile))
+       }
+
+       // Cannot splice a file onto a collection root; cannot splice
+       // anything to a target outside a collection.
+       for _, badpath := range []string{
+               dstPath + "/",
+               dstPath,
+               "/home/A Project/newnodename/",
+               "/home/A Project/newnodename",
+               "/home/A Project/",
+               "/home/A Project",
+               "/home/newnodename/",
+               "/home/newnodename",
+               "/home/",
+               "/home",
+               "/newnodename/",
+               "/newnodename",
+               "/",
+       } {
+               err = Splice(s.fs, badpath, snapFile)
+               c.Check(err, check.NotNil)
+               if strings.Contains(badpath, "newnodename") && strings.HasSuffix(badpath, "/") {
+                       c.Check(err, ErrorIs, os.ErrNotExist, check.Commentf("badpath %q", badpath))
+               } else {
+                       c.Check(err, ErrorIs, ErrInvalidOperation, check.Commentf("badpath %q", badpath))
+               }
+               if strings.TrimSuffix(badpath, "/") == dstPath {
+                       c.Check(err, check.ErrorMatches, `cannot use Splice to attach a file at top level of \*arvados.collectionFileSystem: invalid operation`, check.Commentf("badpath: %q", badpath))
+                       continue
+               }
+
+               err = Splice(s.fs, badpath, snap1)
+               if strings.Contains(badpath, "newnodename") && strings.HasSuffix(badpath, "/") {
+                       c.Check(err, ErrorIs, os.ErrNotExist, check.Commentf("badpath %q", badpath))
+               } else {
+                       c.Check(err, ErrorIs, ErrInvalidOperation, check.Commentf("badpath %q", badpath))
+               }
+       }
+
+       // Destination's parent must already exist
+       for _, badpath := range []string{
+               dstPath + "/newdirname/",
+               dstPath + "/newdirname/foobar",
+               "/foo/bar",
+       } {
+               err = Splice(s.fs, badpath, snap1)
+               c.Check(err, ErrorIs, os.ErrNotExist, check.Commentf("badpath %s", badpath))
+               err = Splice(s.fs, badpath, snapFile)
+               c.Check(err, ErrorIs, os.ErrNotExist, check.Commentf("badpath %s", badpath))
+       }
+
+       snap2, err := Snapshot(s.fs, dstPath+"/ctxlog-copy")
+       if c.Check(err, check.IsNil) {
+               err = Splice(s.fs, dstPath+"/ctxlog-copy-copy", snap2)
+               c.Check(err, check.IsNil)
+       }
+
+       // Snapshot entire collection, splice into same collection at
+       // a new path, remove file from original location, verify
+       // spliced content survives
+       snapDst, err := Snapshot(s.fs, dstPath+"")
+       c.Check(err, check.IsNil)
+       err = Splice(s.fs, dstPath+"", snapDst)
+       c.Check(err, check.IsNil)
+       err = Splice(s.fs, dstPath+"/copy1", snapDst)
+       c.Check(err, check.IsNil)
+       err = Splice(s.fs, dstPath+"/copy2", snapDst)
+       c.Check(err, check.IsNil)
+       err = s.fs.RemoveAll(dstPath + "/arvados/fs_site_test.go")
+       c.Check(err, check.IsNil)
+       err = s.fs.RemoveAll(dstPath + "/arvados")
+       c.Check(err, check.IsNil)
+       _, err = s.fs.Open(dstPath + "/arvados/fs_site_test.go")
+       c.Check(err, check.Equals, os.ErrNotExist)
+       f, err = s.fs.Open(dstPath + "/copy2/arvados/fs_site_test.go")
+       if c.Check(err, check.IsNil) {
+               defer f.Close()
+               buf, err := ioutil.ReadAll(f)
+               c.Check(err, check.IsNil)
+               c.Check(string(buf), check.Equals, string(thisfile))
+       }
 }