X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/a7631a1ccb6e2a6925d00a06562e171c4ce4ea2f..f7bf9d69603db2d500563648460e2a96524de266:/sdk/go/arvados/fs_site_test.go diff --git a/sdk/go/arvados/fs_site_test.go b/sdk/go/arvados/fs_site_test.go index 778b12015a..bf24efa7ed 100644 --- a/sdk/go/arvados/fs_site_test.go +++ b/sdk/go/arvados/fs_site_test.go @@ -5,8 +5,13 @@ package arvados import ( + "fmt" + "io" + "io/ioutil" "net/http" "os" + "strings" + "syscall" "time" check "gopkg.in/check.v1" @@ -16,19 +21,34 @@ const ( // Importing arvadostest would be an import cycle, so these // fixtures are duplicated here [until fs moves to a separate // package]. - fixtureActiveToken = "3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi" - fixtureAProjectUUID = "zzzzz-j7d0g-v955i6s2oi1cbso" - fixtureFooAndBarFilesInDirUUID = "zzzzz-4zz18-foonbarfilesdir" - fixtureFooCollectionName = "zzzzz-4zz18-fy296fx3hot09f7 added sometime" - fixtureFooCollectionPDH = "1f4b0bc7583c2a7f9102c395f4ffc5e3+45" - fixtureFooCollection = "zzzzz-4zz18-fy296fx3hot09f7" - fixtureNonexistentCollection = "zzzzz-4zz18-totallynotexist" - fixtureBlobSigningKey = "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc" - fixtureBlobSigningTTL = 336 * time.Hour + fixtureActiveToken = "3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi" + fixtureAProjectUUID = "zzzzz-j7d0g-v955i6s2oi1cbso" + fixtureThisFilterGroupUUID = "zzzzz-j7d0g-thisfiltergroup" + fixtureAFilterGroupTwoUUID = "zzzzz-j7d0g-afiltergrouptwo" + fixtureAFilterGroupThreeUUID = "zzzzz-j7d0g-filtergroupthre" + fixtureAFilterGroupFourUUID = "zzzzz-j7d0g-filtergroupfour" + fixtureAFilterGroupFiveUUID = "zzzzz-j7d0g-filtergroupfive" + fixtureFooAndBarFilesInDirUUID = "zzzzz-4zz18-foonbarfilesdir" + fixtureFooCollectionName = "zzzzz-4zz18-fy296fx3hot09f7 added sometime" + fixtureFooCollectionPDH = "1f4b0bc7583c2a7f9102c395f4ffc5e3+45" + fixtureFooCollection = "zzzzz-4zz18-fy296fx3hot09f7" + fixtureNonexistentCollection = "zzzzz-4zz18-totallynotexist" + fixtureStorageClassesDesiredArchive = "zzzzz-4zz18-3t236wr12769qqa" + fixtureBlobSigningKey = "zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc" + fixtureBlobSigningTTL = 336 * time.Hour ) var _ = check.Suite(&SiteFSSuite{}) +func init() { + // Enable DebugLocksPanicMode sometimes. Don't enable it all + // the time, though -- it adds many calls to time.Sleep(), + // which could hide different bugs. + if time.Now().Second()&1 == 0 { + DebugLocksPanicMode = true + } +} + type SiteFSSuite struct { client *Client fs CustomFileSystem @@ -65,6 +85,17 @@ func (s *SiteFSSuite) TestByIDEmpty(c *check.C) { c.Check(len(fis), check.Equals, 0) } +func (s *SiteFSSuite) TestUpdateStorageClasses(c *check.C) { + f, err := s.fs.OpenFile("/by_id/"+fixtureStorageClassesDesiredArchive+"/newfile", os.O_CREATE|os.O_RDWR, 0777) + c.Assert(err, check.IsNil) + _, err = f.Write([]byte("nope")) + c.Assert(err, check.IsNil) + err = f.Close() + c.Assert(err, check.IsNil) + err = s.fs.Sync() + c.Assert(err, check.ErrorMatches, `.*stub does not write storage class "archive"`) +} + func (s *SiteFSSuite) TestByUUIDAndPDH(c *check.C) { f, err := s.fs.Open("/by_id") c.Assert(err, check.IsNil) @@ -105,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)) + } }