1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
15 check "gopkg.in/check.v1"
18 type spiedRequest struct {
21 params map[string]interface{}
24 type spyingClient struct {
29 func (sc *spyingClient) RequestAndDecode(dst interface{}, method, path string, body io.Reader, params interface{}) error {
30 var paramsCopy map[string]interface{}
32 json.NewEncoder(&buf).Encode(params)
33 json.NewDecoder(&buf).Decode(¶msCopy)
34 sc.calls = append(sc.calls, spiedRequest{
39 return sc.Client.RequestAndDecode(dst, method, path, body, params)
42 func (s *SiteFSSuite) TestFilterGroup(c *check.C) {
43 // Make sure that a collection and group that match the filter are present,
44 // and that a group that does not match the filter is not present.
46 checkOpen := func(path string, exists bool) {
47 f, err := s.fs.Open(path)
49 if c.Check(err, check.IsNil) {
50 c.Check(f.Close(), check.IsNil)
53 c.Check(err, check.Equals, os.ErrNotExist)
57 checkDirContains := func(parent, child string, exists bool) {
58 f, err := s.fs.Open(parent)
59 if !c.Check(err, check.IsNil) {
62 ents, err := f.Readdir(-1)
63 if !c.Check(err, check.IsNil) {
66 for _, ent := range ents {
68 c.Check(ent.Name(), check.Not(check.Equals), child)
70 // no children are expected
71 c.Errorf("child %q found in parent %q", child, parent)
73 } else if ent.Name() == child {
78 c.Errorf("child %q not found in parent %q", child, parent)
82 checkOpen("/users/active/This filter group/baz_file", true)
83 checkOpen("/users/active/This filter group/A Subproject", true)
84 checkOpen("/users/active/This filter group/A Project", false)
85 s.fs.MountProject("fg", fixtureThisFilterGroupUUID)
86 checkOpen("/fg/baz_file", true)
87 checkOpen("/fg/A Subproject", true)
88 checkOpen("/fg/A Project", false)
89 s.fs.MountProject("home", "")
90 checkOpen("/home/A filter group with an is_a collection filter/baz_file", true)
91 checkOpen("/home/A filter group with an is_a collection filter/baz_file/baz", true)
92 checkOpen("/home/A filter group with an is_a collection filter/A Subproject", false)
93 checkOpen("/home/A filter group with an is_a collection filter/A Project", false)
95 // An empty filter means everything that is visible should be returned.
96 checkOpen("/users/active/A filter group without filters/baz_file", true)
97 checkOpen("/users/active/A filter group without filters/A Subproject", true)
98 checkOpen("/users/active/A filter group without filters/A Project", true)
99 s.fs.MountProject("fg2", fixtureAFilterGroupTwoUUID)
100 checkOpen("/fg2/baz_file", true)
101 checkOpen("/fg2/A Subproject", true)
102 checkOpen("/fg2/A Project", true)
104 // If a filter group matches itself or one of its ancestors,
105 // the matched item appears as an empty directory.
106 checkDirContains("/users/active/A filter group without filters", "A filter group without filters", true)
107 checkOpen("/users/active/A filter group without filters/A filter group without filters", true)
108 checkOpen("/users/active/A filter group without filters/A filter group without filters/baz_file", false)
109 checkDirContains("/users/active/A filter group without filters/A filter group without filters", "", false)
111 // An 'is_a' 'arvados#collection' filter means only collections should be returned.
112 checkOpen("/users/active/A filter group with an is_a collection filter/baz_file", true)
113 checkOpen("/users/active/A filter group with an is_a collection filter/baz_file/baz", true)
114 checkOpen("/users/active/A filter group with an is_a collection filter/A Subproject", false)
115 checkOpen("/users/active/A filter group with an is_a collection filter/A Project", false)
116 s.fs.MountProject("fg3", fixtureAFilterGroupThreeUUID)
117 checkOpen("/fg3/baz_file", true)
118 checkOpen("/fg3/baz_file/baz", true)
119 checkOpen("/fg3/A Subproject", false)
121 // An 'exists' 'arvados#collection' filter means only collections with certain properties should be returned.
122 s.fs.MountProject("fg4", fixtureAFilterGroupFourUUID)
123 checkOpen("/fg4/collection with list property with odd values", true)
124 checkOpen("/fg4/collection with list property with even values", true)
125 checkOpen("/fg4/baz_file", false)
127 // A 'contains' 'arvados#collection' filter means only collections with certain properties should be returned.
128 s.fs.MountProject("fg5", fixtureAFilterGroupFiveUUID)
129 checkOpen("/fg5/collection with list property with odd values", true)
130 checkOpen("/fg5/collection with list property with string value", true)
131 checkOpen("/fg5/collection with prop2 5", false)
132 checkOpen("/fg5/collection with list property with even values", false)
135 func (s *SiteFSSuite) TestCurrentUserHome(c *check.C) {
136 s.fs.MountProject("home", "")
137 s.testHomeProject(c, "/home", "home")
140 func (s *SiteFSSuite) TestUsersDir(c *check.C) {
141 // /users/active is a hardlink to a dir whose name is the UUID
142 // of the active user
143 s.testHomeProject(c, "/users/active", fixtureActiveUserUUID)
146 func (s *SiteFSSuite) testHomeProject(c *check.C, path, expectRealName string) {
147 f, err := s.fs.Open(path)
148 c.Assert(err, check.IsNil)
149 fis, err := f.Readdir(-1)
150 c.Assert(err, check.IsNil)
151 c.Check(len(fis), check.Not(check.Equals), 0)
154 for _, fi := range fis {
155 c.Check(fi.Name(), check.Not(check.Equals), "")
156 if fi.Name() == "A Project" {
160 c.Check(ok, check.Equals, true)
162 f, err = s.fs.Open(path + "/A Project/..")
163 c.Assert(err, check.IsNil)
165 c.Assert(err, check.IsNil)
166 c.Check(fi.IsDir(), check.Equals, true)
167 c.Check(fi.Name(), check.Equals, expectRealName)
169 f, err = s.fs.Open(path + "/A Project/A Subproject")
170 c.Assert(err, check.IsNil)
172 c.Assert(err, check.IsNil)
173 c.Check(fi.IsDir(), check.Equals, true)
175 for _, nx := range []string{
176 path + "/Unrestricted public data",
177 path + "/Unrestricted public data/does not exist",
178 path + "/A Project/does not exist",
181 f, err = s.fs.Open(nx)
182 c.Check(err, check.NotNil)
183 c.Check(os.IsNotExist(err), check.Equals, true)
187 func (s *SiteFSSuite) TestProjectReaddirAfterLoadOne(c *check.C) {
188 f, err := s.fs.Open("/users/active/A Project/A Subproject")
189 c.Assert(err, check.IsNil)
191 f, err = s.fs.Open("/users/active/A Project/Project does not exist")
192 c.Assert(err, check.NotNil)
193 f, err = s.fs.Open("/users/active/A Project/A Subproject")
194 c.Assert(err, check.IsNil)
196 f, err = s.fs.Open("/users/active/A Project")
197 c.Assert(err, check.IsNil)
199 fis, err := f.Readdir(-1)
200 c.Assert(err, check.IsNil)
202 var foundSubproject, foundCollection bool
203 for _, fi := range fis {
206 foundSubproject = true
207 case "collection_to_move_around":
208 foundCollection = true
211 c.Check(foundSubproject, check.Equals, true)
212 c.Check(foundCollection, check.Equals, true)
215 func (s *SiteFSSuite) TestSlashInName(c *check.C) {
216 var badCollection Collection
217 err := s.client.RequestAndDecode(&badCollection, "POST", "arvados/v1/collections", nil, map[string]interface{}{
218 "collection": map[string]string{
219 "name": "bad/collection",
220 "owner_uuid": fixtureAProjectUUID,
223 c.Assert(err, check.IsNil)
224 defer s.client.RequestAndDecode(nil, "DELETE", "arvados/v1/collections/"+badCollection.UUID, nil, nil)
227 err = s.client.RequestAndDecode(&badProject, "POST", "arvados/v1/groups", nil, map[string]interface{}{
228 "group": map[string]string{
229 "name": "bad/project",
230 "group_class": "project",
231 "owner_uuid": fixtureAProjectUUID,
234 c.Assert(err, check.IsNil)
235 defer s.client.RequestAndDecode(nil, "DELETE", "arvados/v1/groups/"+badProject.UUID, nil, nil)
237 dir, err := s.fs.Open("/users/active/A Project")
238 c.Assert(err, check.IsNil)
239 fis, err := dir.Readdir(-1)
240 c.Check(err, check.IsNil)
241 for _, fi := range fis {
242 c.Logf("fi.Name() == %q", fi.Name())
243 c.Check(strings.Contains(fi.Name(), "/"), check.Equals, false)
246 // Make a new fs (otherwise content will still be cached from
247 // above) and enable "/" replacement string.
248 s.fs = s.client.SiteFileSystem(s.kc)
249 s.fs.ForwardSlashNameSubstitution("___")
250 dir, err = s.fs.Open("/users/active/A Project/bad___collection")
251 if c.Check(err, check.IsNil) {
252 _, err = dir.Readdir(-1)
253 c.Check(err, check.IsNil)
255 dir, err = s.fs.Open("/users/active/A Project/bad___project")
256 if c.Check(err, check.IsNil) {
257 _, err = dir.Readdir(-1)
258 c.Check(err, check.IsNil)
262 func (s *SiteFSSuite) TestProjectUpdatedByOther(c *check.C) {
263 s.fs.MountProject("home", "")
265 project, err := s.fs.OpenFile("/home/A Project", 0, 0)
266 c.Assert(err, check.IsNil)
268 _, err = s.fs.Open("/home/A Project/oob")
269 c.Check(err, check.NotNil)
272 err = s.client.RequestAndDecode(&oob, "POST", "arvados/v1/collections", nil, map[string]interface{}{
273 "collection": map[string]string{
275 "owner_uuid": fixtureAProjectUUID,
278 c.Assert(err, check.IsNil)
279 defer s.client.RequestAndDecode(nil, "DELETE", "arvados/v1/collections/"+oob.UUID, nil, nil)
282 c.Check(err, check.IsNil)
283 f, err := s.fs.Open("/home/A Project/oob")
284 c.Assert(err, check.IsNil)
286 c.Assert(err, check.IsNil)
287 c.Check(fi.IsDir(), check.Equals, true)
290 wf, err := s.fs.OpenFile("/home/A Project/oob/test.txt", os.O_CREATE|os.O_RDWR, 0700)
291 c.Assert(err, check.IsNil)
292 _, err = wf.Write([]byte("hello oob\n"))
293 c.Check(err, check.IsNil)
295 c.Check(err, check.IsNil)
298 c.Check(err, check.IsNil)
299 f, err = s.fs.Open("/home/A Project/oob/test.txt")
300 if c.Check(err, check.IsNil) {
304 // Ensure collection was flushed by Sync
305 var latest Collection
306 err = s.client.RequestAndDecode(&latest, "GET", "arvados/v1/collections/"+oob.UUID, nil, nil)
307 c.Check(err, check.IsNil)
308 c.Check(latest.ManifestText, check.Matches, `.*:test.txt.*\n`)
310 // Delete test.txt behind s.fs's back by updating the
311 // collection record with an empty ManifestText.
312 err = s.client.RequestAndDecode(nil, "PATCH", "arvados/v1/collections/"+oob.UUID, nil, map[string]interface{}{
313 "collection": map[string]string{
315 "portable_data_hash": "d41d8cd98f00b204e9800998ecf8427e+0",
318 c.Assert(err, check.IsNil)
320 // Sync again to reload collection.
322 c.Check(err, check.IsNil)
324 // Check test.txt deletion is reflected in fs.
325 _, err = s.fs.Open("/home/A Project/oob/test.txt")
326 c.Check(err, check.NotNil)
327 f, err = s.fs.Open("/home/A Project/oob")
328 if c.Check(err, check.IsNil) {
332 err = s.client.RequestAndDecode(nil, "DELETE", "arvados/v1/collections/"+oob.UUID, nil, nil)
333 c.Assert(err, check.IsNil)
335 wf, err = s.fs.OpenFile("/home/A Project/oob/test.txt", os.O_CREATE|os.O_RDWR, 0700)
336 c.Assert(err, check.IsNil)
338 c.Check(err, check.IsNil)
341 c.Check(err, check.NotNil) // can't update the deleted collection
342 _, err = s.fs.Open("/home/A Project/oob")
343 c.Check(err, check.IsNil) // parent dir still has old collection -- didn't reload, because Sync failed
346 func (s *SiteFSSuite) TestProjectUnsupportedOperations(c *check.C) {
347 s.fs.MountByID("by_id")
348 s.fs.MountProject("home", "")
350 _, err := s.fs.OpenFile("/home/A Project/newfilename", os.O_CREATE|os.O_RDWR, 0)
351 c.Check(err, ErrorIs, ErrInvalidOperation)
353 err = s.fs.Mkdir("/home/A Project/newdirname", 0)
354 c.Check(err, ErrorIs, ErrInvalidOperation)
356 err = s.fs.Mkdir("/by_id/newdirname", 0)
357 c.Check(err, ErrorIs, ErrInvalidOperation)
359 err = s.fs.Mkdir("/by_id/"+fixtureAProjectUUID+"/newdirname", 0)
360 c.Check(err, ErrorIs, ErrInvalidOperation)
362 _, err = s.fs.OpenFile("/home/A Project", 0, 0)
363 c.Check(err, check.IsNil)
366 type errorIsChecker struct {
370 var ErrorIs check.Checker = errorIsChecker{
371 &check.CheckerInfo{Name: "ErrorIs", Params: []string{"value", "target"}},
374 func (checker errorIsChecker) Check(params []interface{}, names []string) (result bool, errStr string) {
375 err, ok := params[0].(error)
379 target, ok := params[1].(error)
383 return errors.Is(err, target), ""