1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
5 from __future__ import absolute_import
6 from future.utils import viewitems
7 from builtins import str
8 from builtins import object
9 from six import assertRegex
21 import arvados_fuse as fuse
22 from . import run_test_server
24 from .integration_test import IntegrationTest
25 from .mount_test_base import MountTestBase
26 from .test_tmp_collection import storage_classes_desired
28 logger = logging.getLogger('arvados.arv-mount')
31 class AssertWithTimeout(object):
32 """Allow some time for an assertion to pass."""
34 def __init__(self, timeout=0):
35 self.timeout = timeout
38 self.deadline = time.time() + self.timeout
47 def attempt(self, fn, *args, **kwargs):
50 except AssertionError:
51 if time.time() > self.deadline:
58 class FuseMountTest(MountTestBase):
60 super(FuseMountTest, self).setUp()
62 cw = arvados.CollectionWriter()
64 cw.start_new_file('thing1.txt')
66 cw.start_new_file('thing2.txt')
69 cw.start_new_stream('dir1')
70 cw.start_new_file('thing3.txt')
72 cw.start_new_file('thing4.txt')
75 cw.start_new_stream('dir2')
76 cw.start_new_file('thing5.txt')
78 cw.start_new_file('thing6.txt')
81 cw.start_new_stream('dir2/dir3')
82 cw.start_new_file('thing7.txt')
85 cw.start_new_file('thing8.txt')
88 cw.start_new_stream('edgecases')
89 for f in ":/.../-/*/ ".split("/"):
93 for f in ":/.../-/*/ ".split("/"):
94 cw.start_new_stream('edgecases/dirs/' + f)
95 cw.start_new_file('x/x')
98 self.testcollection = cw.finish()
99 self.api.collections().create(body={"manifest_text":cw.manifest_text()}).execute()
102 self.make_mount(fuse.CollectionDirectory, collection_record=self.testcollection)
104 self.assertDirContents(None, ['thing1.txt', 'thing2.txt',
105 'edgecases', 'dir1', 'dir2'])
106 self.assertDirContents('dir1', ['thing3.txt', 'thing4.txt'])
107 self.assertDirContents('dir2', ['thing5.txt', 'thing6.txt', 'dir3'])
108 self.assertDirContents('dir2/dir3', ['thing7.txt', 'thing8.txt'])
109 self.assertDirContents('edgecases',
110 "dirs/:/.../-/*/ ".split("/"))
111 self.assertDirContents('edgecases/dirs',
112 ":/.../-/*/ ".split("/"))
114 files = {'thing1.txt': 'data 1',
115 'thing2.txt': 'data 2',
116 'dir1/thing3.txt': 'data 3',
117 'dir1/thing4.txt': 'data 4',
118 'dir2/thing5.txt': 'data 5',
119 'dir2/thing6.txt': 'data 6',
120 'dir2/dir3/thing7.txt': 'data 7',
121 'dir2/dir3/thing8.txt': 'data 8'}
123 for k, v in viewitems(files):
124 with open(os.path.join(self.mounttmp, k), 'rb') as f:
125 self.assertEqual(v, f.read().decode())
128 class FuseMagicTest(MountTestBase):
129 def setUp(self, api=None):
130 super(FuseMagicTest, self).setUp(api=api)
132 self.test_project = run_test_server.fixture('groups')['aproject']['uuid']
133 self.non_project_group = run_test_server.fixture('groups')['public_role']['uuid']
134 self.filter_group = run_test_server.fixture('groups')['afiltergroup']['uuid']
135 self.collection_in_test_project = run_test_server.fixture('collections')['foo_collection_in_aproject']['name']
136 self.collection_in_filter_group = run_test_server.fixture('collections')['baz_file']['name']
138 cw = arvados.CollectionWriter()
140 cw.start_new_file('thing1.txt')
143 self.testcollection = cw.finish()
144 self.test_manifest = cw.manifest_text()
145 coll = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
146 self.test_manifest_pdh = coll['portable_data_hash']
149 self.make_mount(fuse.MagicDirectory)
151 mount_ls = llfuse.listdir(self.mounttmp)
152 self.assertIn('README', mount_ls)
153 self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
154 arvados.util.uuid_pattern.match(fn)
156 "new FUSE MagicDirectory has no collections or projects")
157 self.assertDirContents(self.testcollection, ['thing1.txt'])
158 self.assertDirContents(os.path.join('by_id', self.testcollection),
160 self.assertIn(self.collection_in_test_project,
161 llfuse.listdir(os.path.join(self.mounttmp, self.test_project)))
162 self.assertIn(self.collection_in_test_project,
163 llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.test_project)))
164 self.assertIn(self.collection_in_filter_group,
165 llfuse.listdir(os.path.join(self.mounttmp, self.filter_group)))
166 self.assertIn(self.collection_in_filter_group,
167 llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.filter_group)))
170 mount_ls = llfuse.listdir(self.mounttmp)
171 self.assertIn('README', mount_ls)
172 self.assertIn(self.testcollection, mount_ls)
173 self.assertIn(self.testcollection,
174 llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
175 self.assertIn(self.test_project, mount_ls)
176 self.assertIn(self.test_project,
177 llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
178 self.assertIn(self.filter_group,
179 llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
181 with self.assertRaises(OSError):
182 llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.non_project_group))
185 files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
187 for k, v in viewitems(files):
188 with open(os.path.join(self.mounttmp, k), 'rb') as f:
189 self.assertEqual(v, f.read().decode())
192 class FuseTagsTest(MountTestBase):
194 self.make_mount(fuse.TagsDirectory)
196 d1 = llfuse.listdir(self.mounttmp)
198 self.assertEqual(['foo_tag'], d1)
200 d2 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag'))
202 self.assertEqual(['zzzzz-4zz18-fy296fx3hot09f7'], d2)
204 d3 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag', 'zzzzz-4zz18-fy296fx3hot09f7'))
206 self.assertEqual(['foo'], d3)
209 class FuseTagsUpdateTest(MountTestBase):
210 def tag_collection(self, coll_uuid, tag_name):
211 return self.api.links().create(
212 body={'link': {'head_uuid': coll_uuid,
218 self.make_mount(fuse.TagsDirectory, poll_time=1)
220 self.assertIn('foo_tag', llfuse.listdir(self.mounttmp))
222 bar_uuid = run_test_server.fixture('collections')['bar_file']['uuid']
223 self.tag_collection(bar_uuid, 'fuse_test_tag')
224 for attempt in AssertWithTimeout(10):
225 attempt(self.assertIn, 'fuse_test_tag', llfuse.listdir(self.mounttmp))
226 self.assertDirContents('fuse_test_tag', [bar_uuid])
228 baz_uuid = run_test_server.fixture('collections')['baz_file']['uuid']
229 l = self.tag_collection(baz_uuid, 'fuse_test_tag')
230 for attempt in AssertWithTimeout(10):
231 attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid, baz_uuid])
233 self.api.links().delete(uuid=l['uuid']).execute()
234 for attempt in AssertWithTimeout(10):
235 attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid])
238 def fuseSharedTestHelper(mounttmp):
239 class Test(unittest.TestCase):
241 # Double check that we can open and read objects in this folder as a file,
242 # and that its contents are what we expect.
243 baz_path = os.path.join(
247 'collection in FUSE project',
249 with open(baz_path) as f:
250 self.assertEqual("baz", f.read())
252 # check mtime on collection
253 st = os.stat(baz_path)
255 mtime = st.st_mtime_ns // 1000000000
256 except AttributeError:
258 self.assertEqual(mtime, 1391448174)
260 # shared_dirs is a list of the directories exposed
261 # by fuse.SharedDirectory (i.e. any object visible
262 # to the current user)
263 shared_dirs = llfuse.listdir(mounttmp)
265 self.assertIn('FUSE User', shared_dirs)
267 # fuse_user_objs is a list of the objects owned by the FUSE
268 # test user (which present as files in the 'FUSE User'
270 fuse_user_objs = llfuse.listdir(os.path.join(mounttmp, 'FUSE User'))
271 fuse_user_objs.sort()
272 self.assertEqual(['FUSE Test Project', # project owned by user
273 'collection #1 owned by FUSE', # collection owned by user
274 'collection #2 owned by FUSE' # collection owned by user
277 # test_proj_files is a list of the files in the FUSE Test Project.
278 test_proj_files = llfuse.listdir(os.path.join(mounttmp, 'FUSE User', 'FUSE Test Project'))
279 test_proj_files.sort()
280 self.assertEqual(['collection in FUSE project'
286 class FuseSharedTest(MountTestBase):
288 self.make_mount(fuse.SharedDirectory,
289 exclude=self.api.users().current().execute()['uuid'])
290 keep = arvados.keep.KeepClient()
291 keep.put("baz".encode())
293 self.pool.apply(fuseSharedTestHelper, (self.mounttmp,))
296 class FuseHomeTest(MountTestBase):
298 self.make_mount(fuse.ProjectDirectory,
299 project_object=self.api.users().current().execute())
301 d1 = llfuse.listdir(self.mounttmp)
302 self.assertIn('Unrestricted public data', d1)
304 d2 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data'))
305 public_project = run_test_server.fixture('groups')[
306 'anonymously_accessible_project']
309 for name, item in viewitems(run_test_server.fixture('collections')):
310 if 'name' not in item:
312 elif item['owner_uuid'] == public_project['uuid']:
313 self.assertIn(item['name'], d2)
316 # Artificial assumption here: there is no public
317 # collection fixture with the same name as a
318 # non-public collection.
319 self.assertNotIn(item['name'], d2)
321 self.assertNotEqual(0, found_in)
322 self.assertNotEqual(0, found_not_in)
324 d3 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data', 'GNU General Public License, version 3'))
325 self.assertEqual(["GNU_General_Public_License,_version_3.pdf"], d3)
328 def fuseModifyFileTestHelperReadStartContents(mounttmp):
329 class Test(unittest.TestCase):
331 d1 = llfuse.listdir(mounttmp)
332 self.assertEqual(["file1.txt"], d1)
333 with open(os.path.join(mounttmp, "file1.txt")) as f:
334 self.assertEqual("blub", f.read())
337 def fuseModifyFileTestHelperReadEndContents(mounttmp):
338 class Test(unittest.TestCase):
340 d1 = llfuse.listdir(mounttmp)
341 self.assertEqual(["file1.txt"], d1)
342 with open(os.path.join(mounttmp, "file1.txt")) as f:
343 self.assertEqual("plnp", f.read())
346 class FuseModifyFileTest(MountTestBase):
348 collection = arvados.collection.Collection(api_client=self.api)
349 with collection.open("file1.txt", "w") as f:
352 collection.save_new()
354 m = self.make_mount(fuse.CollectionDirectory)
356 m.new_collection(collection.api_response(), collection)
358 self.pool.apply(fuseModifyFileTestHelperReadStartContents, (self.mounttmp,))
360 with collection.open("file1.txt", "w") as f:
363 self.pool.apply(fuseModifyFileTestHelperReadEndContents, (self.mounttmp,))
366 class FuseAddFileToCollectionTest(MountTestBase):
368 collection = arvados.collection.Collection(api_client=self.api)
369 with collection.open("file1.txt", "w") as f:
372 collection.save_new()
374 m = self.make_mount(fuse.CollectionDirectory)
376 m.new_collection(collection.api_response(), collection)
378 d1 = llfuse.listdir(self.mounttmp)
379 self.assertEqual(["file1.txt"], d1)
381 with collection.open("file2.txt", "w") as f:
384 d1 = llfuse.listdir(self.mounttmp)
385 self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
388 class FuseRemoveFileFromCollectionTest(MountTestBase):
390 collection = arvados.collection.Collection(api_client=self.api)
391 with collection.open("file1.txt", "w") as f:
394 with collection.open("file2.txt", "w") as f:
397 collection.save_new()
399 m = self.make_mount(fuse.CollectionDirectory)
401 m.new_collection(collection.api_response(), collection)
403 d1 = llfuse.listdir(self.mounttmp)
404 self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
406 collection.remove("file2.txt")
408 d1 = llfuse.listdir(self.mounttmp)
409 self.assertEqual(["file1.txt"], d1)
412 def fuseCreateFileTestHelper(mounttmp):
413 class Test(unittest.TestCase):
415 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
419 class FuseCreateFileTest(MountTestBase):
421 collection = arvados.collection.Collection(api_client=self.api)
422 collection.save_new()
424 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
425 self.assertEqual(collection2["manifest_text"], "")
427 collection.save_new()
429 m = self.make_mount(fuse.CollectionDirectory)
431 m.new_collection(collection.api_response(), collection)
432 self.assertTrue(m.writable())
434 self.assertNotIn("file1.txt", collection)
436 self.pool.apply(fuseCreateFileTestHelper, (self.mounttmp,))
438 self.assertIn("file1.txt", collection)
440 d1 = llfuse.listdir(self.mounttmp)
441 self.assertEqual(["file1.txt"], d1)
443 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
444 assertRegex(self, collection2["manifest_text"],
445 r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:file1\.txt$')
448 def fuseWriteFileTestHelperWriteFile(mounttmp):
449 class Test(unittest.TestCase):
451 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
452 f.write("Hello world!")
455 def fuseWriteFileTestHelperReadFile(mounttmp):
456 class Test(unittest.TestCase):
458 with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
459 self.assertEqual(f.read(), "Hello world!")
462 class FuseWriteFileTest(MountTestBase):
464 collection = arvados.collection.Collection(api_client=self.api)
465 collection.save_new()
467 m = self.make_mount(fuse.CollectionDirectory)
469 m.new_collection(collection.api_response(), collection)
470 self.assertTrue(m.writable())
472 self.assertNotIn("file1.txt", collection)
474 self.assertEqual(0, self.operations.write_counter.get())
475 self.pool.apply(fuseWriteFileTestHelperWriteFile, (self.mounttmp,))
476 self.assertEqual(12, self.operations.write_counter.get())
478 with collection.open("file1.txt") as f:
479 self.assertEqual(f.read(), "Hello world!")
481 self.assertEqual(0, self.operations.read_counter.get())
482 self.pool.apply(fuseWriteFileTestHelperReadFile, (self.mounttmp,))
483 self.assertEqual(12, self.operations.read_counter.get())
485 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
486 assertRegex(self, collection2["manifest_text"],
487 r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
490 def fuseUpdateFileTestHelper(mounttmp):
491 class Test(unittest.TestCase):
493 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
494 f.write("Hello world!")
496 with open(os.path.join(mounttmp, "file1.txt"), "r+") as f:
498 self.assertEqual(fr, "Hello world!")
500 f.write("Hola mundo!")
503 self.assertEqual(fr, "Hola mundo!!")
505 with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
506 self.assertEqual(f.read(), "Hola mundo!!")
510 class FuseUpdateFileTest(MountTestBase):
512 collection = arvados.collection.Collection(api_client=self.api)
513 collection.save_new()
515 m = self.make_mount(fuse.CollectionDirectory)
517 m.new_collection(collection.api_response(), collection)
518 self.assertTrue(m.writable())
520 # See note in MountTestBase.setUp
521 self.pool.apply(fuseUpdateFileTestHelper, (self.mounttmp,))
523 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
524 assertRegex(self, collection2["manifest_text"],
525 r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A\S+ 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:11:file1\.txt 22:1:file1\.txt$')
528 def fuseMkdirTestHelper(mounttmp):
529 class Test(unittest.TestCase):
531 with self.assertRaises(IOError):
532 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
533 f.write("Hello world!")
535 os.mkdir(os.path.join(mounttmp, "testdir"))
537 with self.assertRaises(OSError):
538 os.mkdir(os.path.join(mounttmp, "testdir"))
540 d1 = llfuse.listdir(mounttmp)
541 self.assertEqual(["testdir"], d1)
543 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
544 f.write("Hello world!")
546 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
547 self.assertEqual(["file1.txt"], d1)
551 class FuseMkdirTest(MountTestBase):
553 collection = arvados.collection.Collection(api_client=self.api)
554 collection.save_new()
556 m = self.make_mount(fuse.CollectionDirectory)
558 m.new_collection(collection.api_response(), collection)
559 self.assertTrue(m.writable())
561 self.pool.apply(fuseMkdirTestHelper, (self.mounttmp,))
563 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
564 assertRegex(self, collection2["manifest_text"],
565 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
568 def fuseRmTestHelperWriteFile(mounttmp):
569 class Test(unittest.TestCase):
571 os.mkdir(os.path.join(mounttmp, "testdir"))
573 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
574 f.write("Hello world!")
578 def fuseRmTestHelperDeleteFile(mounttmp):
579 class Test(unittest.TestCase):
581 # Can't delete because it's not empty
582 with self.assertRaises(OSError):
583 os.rmdir(os.path.join(mounttmp, "testdir"))
585 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
586 self.assertEqual(["file1.txt"], d1)
589 os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
591 # Make sure it's empty
592 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
593 self.assertEqual([], d1)
595 # Try to delete it again
596 with self.assertRaises(OSError):
597 os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
601 def fuseRmTestHelperRmdir(mounttmp):
602 class Test(unittest.TestCase):
604 # Should be able to delete now that it is empty
605 os.rmdir(os.path.join(mounttmp, "testdir"))
607 # Make sure it's empty
608 d1 = llfuse.listdir(os.path.join(mounttmp))
609 self.assertEqual([], d1)
611 # Try to delete it again
612 with self.assertRaises(OSError):
613 os.rmdir(os.path.join(mounttmp, "testdir"))
617 class FuseRmTest(MountTestBase):
619 collection = arvados.collection.Collection(api_client=self.api)
620 collection.save_new()
622 m = self.make_mount(fuse.CollectionDirectory)
624 m.new_collection(collection.api_response(), collection)
625 self.assertTrue(m.writable())
627 self.pool.apply(fuseRmTestHelperWriteFile, (self.mounttmp,))
630 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
631 assertRegex(self, collection2["manifest_text"],
632 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
633 self.pool.apply(fuseRmTestHelperDeleteFile, (self.mounttmp,))
635 # Empty directories are represented by an empty file named "."
636 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
637 assertRegex(self, collection2["manifest_text"],
638 r'./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
640 self.pool.apply(fuseRmTestHelperRmdir, (self.mounttmp,))
642 # manifest should be empty now.
643 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
644 self.assertEqual(collection2["manifest_text"], "")
647 def fuseMvFileTestHelperWriteFile(mounttmp):
648 class Test(unittest.TestCase):
650 os.mkdir(os.path.join(mounttmp, "testdir"))
652 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
653 f.write("Hello world!")
657 def fuseMvFileTestHelperMoveFile(mounttmp):
658 class Test(unittest.TestCase):
660 d1 = llfuse.listdir(os.path.join(mounttmp))
661 self.assertEqual(["testdir"], d1)
662 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
663 self.assertEqual(["file1.txt"], d1)
665 os.rename(os.path.join(mounttmp, "testdir", "file1.txt"), os.path.join(mounttmp, "file1.txt"))
667 d1 = llfuse.listdir(os.path.join(mounttmp))
668 self.assertEqual(["file1.txt", "testdir"], sorted(d1))
669 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
670 self.assertEqual([], d1)
674 class FuseMvFileTest(MountTestBase):
676 collection = arvados.collection.Collection(api_client=self.api)
677 collection.save_new()
679 m = self.make_mount(fuse.CollectionDirectory)
681 m.new_collection(collection.api_response(), collection)
682 self.assertTrue(m.writable())
684 self.pool.apply(fuseMvFileTestHelperWriteFile, (self.mounttmp,))
687 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
688 assertRegex(self, collection2["manifest_text"],
689 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
691 self.pool.apply(fuseMvFileTestHelperMoveFile, (self.mounttmp,))
693 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
694 assertRegex(self, collection2["manifest_text"],
695 r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt\n\./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
698 def fuseRenameTestHelper(mounttmp):
699 class Test(unittest.TestCase):
701 os.mkdir(os.path.join(mounttmp, "testdir"))
703 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
704 f.write("Hello world!")
708 class FuseRenameTest(MountTestBase):
710 collection = arvados.collection.Collection(api_client=self.api)
711 collection.save_new()
713 m = self.make_mount(fuse.CollectionDirectory)
715 m.new_collection(collection.api_response(), collection)
716 self.assertTrue(m.writable())
718 self.pool.apply(fuseRenameTestHelper, (self.mounttmp,))
721 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
722 assertRegex(self, collection2["manifest_text"],
723 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
725 d1 = llfuse.listdir(os.path.join(self.mounttmp))
726 self.assertEqual(["testdir"], d1)
727 d1 = llfuse.listdir(os.path.join(self.mounttmp, "testdir"))
728 self.assertEqual(["file1.txt"], d1)
730 os.rename(os.path.join(self.mounttmp, "testdir"), os.path.join(self.mounttmp, "testdir2"))
732 d1 = llfuse.listdir(os.path.join(self.mounttmp))
733 self.assertEqual(["testdir2"], sorted(d1))
734 d1 = llfuse.listdir(os.path.join(self.mounttmp, "testdir2"))
735 self.assertEqual(["file1.txt"], d1)
737 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
738 assertRegex(self, collection2["manifest_text"],
739 r'\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
742 class FuseUpdateFromEventTest(MountTestBase):
744 collection = arvados.collection.Collection(api_client=self.api)
745 collection.save_new()
747 m = self.make_mount(fuse.CollectionDirectory)
749 m.new_collection(collection.api_response(), collection)
751 self.operations.listen_for_events()
753 d1 = llfuse.listdir(os.path.join(self.mounttmp))
754 self.assertEqual([], sorted(d1))
756 with arvados.collection.Collection(collection.manifest_locator(), api_client=self.api) as collection2:
757 with collection2.open("file1.txt", "w") as f:
760 for attempt in AssertWithTimeout(10):
761 attempt(self.assertEqual, ["file1.txt"], llfuse.listdir(os.path.join(self.mounttmp)))
764 class FuseDeleteProjectEventTest(MountTestBase):
767 aproject = self.api.groups().create(body={
769 "group_class": "project"
772 bproject = self.api.groups().create(body={
774 "group_class": "project",
775 "owner_uuid": aproject["uuid"]
778 self.make_mount(fuse.ProjectDirectory,
779 project_object=self.api.users().current().execute())
781 self.operations.listen_for_events()
783 d1 = llfuse.listdir(os.path.join(self.mounttmp, "aproject"))
784 self.assertEqual(["bproject"], sorted(d1))
786 self.api.groups().delete(uuid=bproject["uuid"]).execute()
788 for attempt in AssertWithTimeout(10):
789 attempt(self.assertEqual, [], llfuse.listdir(os.path.join(self.mounttmp, "aproject")))
792 def fuseFileConflictTestHelper(mounttmp, uuid, keeptmp, settings):
793 class Test(unittest.TestCase):
795 os.environ['KEEP_LOCAL_STORE'] = keeptmp
797 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
798 with arvados.collection.Collection(uuid, api_client=arvados.api_from_config('v1', apiconfig=settings)) as collection2:
799 with collection2.open("file1.txt", "w") as f2:
803 d1 = sorted(llfuse.listdir(os.path.join(mounttmp)))
804 self.assertEqual(len(d1), 2)
806 with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
807 self.assertEqual(f.read(), "bar")
809 assertRegex(self, d1[1],
810 r'file1\.txt~\d\d\d\d\d\d\d\d-\d\d\d\d\d\d~conflict~')
812 with open(os.path.join(mounttmp, d1[1]), "r") as f:
813 self.assertEqual(f.read(), "foo")
817 class FuseFileConflictTest(MountTestBase):
819 collection = arvados.collection.Collection(api_client=self.api)
820 collection.save_new()
822 m = self.make_mount(fuse.CollectionDirectory)
824 m.new_collection(collection.api_response(), collection)
826 d1 = llfuse.listdir(os.path.join(self.mounttmp))
827 self.assertEqual([], sorted(d1))
829 # See note in MountTestBase.setUp
830 self.pool.apply(fuseFileConflictTestHelper, (self.mounttmp, collection.manifest_locator(), self.keeptmp, arvados.config.settings()))
833 def fuseUnlinkOpenFileTest(mounttmp):
834 class Test(unittest.TestCase):
836 with open(os.path.join(mounttmp, "file1.txt"), "w+") as f:
839 d1 = llfuse.listdir(os.path.join(mounttmp))
840 self.assertEqual(["file1.txt"], sorted(d1))
842 os.remove(os.path.join(mounttmp, "file1.txt"))
844 d1 = llfuse.listdir(os.path.join(mounttmp))
845 self.assertEqual([], sorted(d1))
848 self.assertEqual(f.read(), "foo")
852 self.assertEqual(f.read(), "foobar")
856 class FuseUnlinkOpenFileTest(MountTestBase):
858 collection = arvados.collection.Collection(api_client=self.api)
859 collection.save_new()
861 m = self.make_mount(fuse.CollectionDirectory)
863 m.new_collection(collection.api_response(), collection)
865 # See note in MountTestBase.setUp
866 self.pool.apply(fuseUnlinkOpenFileTest, (self.mounttmp,))
868 self.assertEqual(collection.manifest_text(), "")
871 def fuseMvFileBetweenCollectionsTest1(mounttmp, uuid1, uuid2):
872 class Test(unittest.TestCase):
874 with open(os.path.join(mounttmp, uuid1, "file1.txt"), "w") as f:
875 f.write("Hello world!")
877 d1 = os.listdir(os.path.join(mounttmp, uuid1))
878 self.assertEqual(["file1.txt"], sorted(d1))
879 d1 = os.listdir(os.path.join(mounttmp, uuid2))
880 self.assertEqual([], sorted(d1))
884 def fuseMvFileBetweenCollectionsTest2(mounttmp, uuid1, uuid2):
885 class Test(unittest.TestCase):
887 os.rename(os.path.join(mounttmp, uuid1, "file1.txt"), os.path.join(mounttmp, uuid2, "file2.txt"))
889 d1 = os.listdir(os.path.join(mounttmp, uuid1))
890 self.assertEqual([], sorted(d1))
891 d1 = os.listdir(os.path.join(mounttmp, uuid2))
892 self.assertEqual(["file2.txt"], sorted(d1))
896 class FuseMvFileBetweenCollectionsTest(MountTestBase):
898 collection1 = arvados.collection.Collection(api_client=self.api)
899 collection1.save_new()
901 collection2 = arvados.collection.Collection(api_client=self.api)
902 collection2.save_new()
904 m = self.make_mount(fuse.MagicDirectory)
906 # See note in MountTestBase.setUp
907 self.pool.apply(fuseMvFileBetweenCollectionsTest1, (self.mounttmp,
908 collection1.manifest_locator(),
909 collection2.manifest_locator()))
914 assertRegex(self, collection1.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
915 self.assertEqual(collection2.manifest_text(), "")
917 self.pool.apply(fuseMvFileBetweenCollectionsTest2, (self.mounttmp,
918 collection1.manifest_locator(),
919 collection2.manifest_locator()))
924 self.assertEqual(collection1.manifest_text(), "")
925 assertRegex(self, collection2.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file2\.txt$")
927 collection1.stop_threads()
928 collection2.stop_threads()
931 def fuseMvDirBetweenCollectionsTest1(mounttmp, uuid1, uuid2):
932 class Test(unittest.TestCase):
934 os.mkdir(os.path.join(mounttmp, uuid1, "testdir"))
935 with open(os.path.join(mounttmp, uuid1, "testdir", "file1.txt"), "w") as f:
936 f.write("Hello world!")
938 d1 = os.listdir(os.path.join(mounttmp, uuid1))
939 self.assertEqual(["testdir"], sorted(d1))
940 d1 = os.listdir(os.path.join(mounttmp, uuid1, "testdir"))
941 self.assertEqual(["file1.txt"], sorted(d1))
943 d1 = os.listdir(os.path.join(mounttmp, uuid2))
944 self.assertEqual([], sorted(d1))
949 def fuseMvDirBetweenCollectionsTest2(mounttmp, uuid1, uuid2):
950 class Test(unittest.TestCase):
952 os.rename(os.path.join(mounttmp, uuid1, "testdir"), os.path.join(mounttmp, uuid2, "testdir2"))
954 d1 = os.listdir(os.path.join(mounttmp, uuid1))
955 self.assertEqual([], sorted(d1))
957 d1 = os.listdir(os.path.join(mounttmp, uuid2))
958 self.assertEqual(["testdir2"], sorted(d1))
959 d1 = os.listdir(os.path.join(mounttmp, uuid2, "testdir2"))
960 self.assertEqual(["file1.txt"], sorted(d1))
962 with open(os.path.join(mounttmp, uuid2, "testdir2", "file1.txt"), "r") as f:
963 self.assertEqual(f.read(), "Hello world!")
967 class FuseMvDirBetweenCollectionsTest(MountTestBase):
969 collection1 = arvados.collection.Collection(api_client=self.api)
970 collection1.save_new()
972 collection2 = arvados.collection.Collection(api_client=self.api)
973 collection2.save_new()
975 m = self.make_mount(fuse.MagicDirectory)
977 # See note in MountTestBase.setUp
978 self.pool.apply(fuseMvDirBetweenCollectionsTest1, (self.mounttmp,
979 collection1.manifest_locator(),
980 collection2.manifest_locator()))
985 assertRegex(self, collection1.manifest_text(), r"\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
986 self.assertEqual(collection2.manifest_text(), "")
988 self.pool.apply(fuseMvDirBetweenCollectionsTest2, (self.mounttmp,
989 collection1.manifest_locator(),
990 collection2.manifest_locator()))
995 self.assertEqual(collection1.manifest_text(), "")
996 assertRegex(self, collection2.manifest_text(), r"\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
998 collection1.stop_threads()
999 collection2.stop_threads()
1001 def fuseProjectMkdirTestHelper1(mounttmp):
1002 class Test(unittest.TestCase):
1004 os.mkdir(os.path.join(mounttmp, "testcollection"))
1005 with self.assertRaises(OSError):
1006 os.mkdir(os.path.join(mounttmp, "testcollection"))
1009 def fuseProjectMkdirTestHelper2(mounttmp):
1010 class Test(unittest.TestCase):
1012 with open(os.path.join(mounttmp, "testcollection", "file1.txt"), "w") as f:
1013 f.write("Hello world!")
1014 with self.assertRaises(OSError):
1015 os.rmdir(os.path.join(mounttmp, "testcollection"))
1016 os.remove(os.path.join(mounttmp, "testcollection", "file1.txt"))
1017 with self.assertRaises(OSError):
1018 os.remove(os.path.join(mounttmp, "testcollection"))
1019 os.rmdir(os.path.join(mounttmp, "testcollection"))
1022 class FuseProjectMkdirRmdirTest(MountTestBase):
1024 self.make_mount(fuse.ProjectDirectory,
1025 project_object=self.api.users().current().execute())
1027 d1 = llfuse.listdir(self.mounttmp)
1028 self.assertNotIn('testcollection', d1)
1030 self.pool.apply(fuseProjectMkdirTestHelper1, (self.mounttmp,))
1032 d1 = llfuse.listdir(self.mounttmp)
1033 self.assertIn('testcollection', d1)
1035 self.pool.apply(fuseProjectMkdirTestHelper2, (self.mounttmp,))
1037 d1 = llfuse.listdir(self.mounttmp)
1038 self.assertNotIn('testcollection', d1)
1041 def fuseProjectMvTestHelper1(mounttmp):
1042 class Test(unittest.TestCase):
1044 d1 = llfuse.listdir(mounttmp)
1045 self.assertNotIn('testcollection', d1)
1047 os.mkdir(os.path.join(mounttmp, "testcollection"))
1049 d1 = llfuse.listdir(mounttmp)
1050 self.assertIn('testcollection', d1)
1052 with self.assertRaises(OSError):
1053 os.rename(os.path.join(mounttmp, "testcollection"), os.path.join(mounttmp, 'Unrestricted public data'))
1055 os.rename(os.path.join(mounttmp, "testcollection"), os.path.join(mounttmp, 'Unrestricted public data', 'testcollection'))
1057 d1 = llfuse.listdir(mounttmp)
1058 self.assertNotIn('testcollection', d1)
1060 d1 = llfuse.listdir(os.path.join(mounttmp, 'Unrestricted public data'))
1061 self.assertIn('testcollection', d1)
1065 class FuseProjectMvTest(MountTestBase):
1067 self.make_mount(fuse.ProjectDirectory,
1068 project_object=self.api.users().current().execute())
1070 self.pool.apply(fuseProjectMvTestHelper1, (self.mounttmp,))
1073 def fuseFsyncTestHelper(mounttmp, k):
1074 class Test(unittest.TestCase):
1076 fd = os.open(os.path.join(mounttmp, k), os.O_RDONLY)
1082 class FuseFsyncTest(FuseMagicTest):
1084 self.make_mount(fuse.MagicDirectory)
1085 self.pool.apply(fuseFsyncTestHelper, (self.mounttmp, self.testcollection))
1088 class MagicDirApiError(FuseMagicTest):
1090 api = mock.MagicMock()
1091 api.keep.block_cache = mock.MagicMock(cache_max=1)
1092 super(MagicDirApiError, self).setUp(api=api)
1093 api.collections().get().execute.side_effect = iter([
1094 Exception('API fail'),
1096 "manifest_text": self.test_manifest,
1097 "portable_data_hash": self.test_manifest_pdh,
1100 api.keep.get.side_effect = Exception('Keep fail')
1103 with mock.patch('arvados_fuse.fresh.FreshBase._poll_time', new_callable=mock.PropertyMock, return_value=60) as mock_poll_time:
1104 self.make_mount(fuse.MagicDirectory)
1106 self.operations.inodes.inode_cache.cap = 1
1107 self.operations.inodes.inode_cache.min_entries = 2
1109 with self.assertRaises(OSError):
1110 llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1112 llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1115 class SanitizeFilenameTest(MountTestBase):
1116 def test_sanitize_filename(self):
1117 pdir = fuse.ProjectDirectory(1, {}, self.api, 0, False, project_object=self.api.users().current().execute())
1136 for f in acceptable:
1137 self.assertEqual(f, pdir.sanitize_filename(f))
1138 for f in unacceptable:
1139 self.assertNotEqual(f, pdir.sanitize_filename(f))
1140 # The sanitized filename should be the same length, though.
1141 self.assertEqual(len(f), len(pdir.sanitize_filename(f)))
1143 self.assertEqual("_", pdir.sanitize_filename(""))
1144 self.assertEqual("_", pdir.sanitize_filename("."))
1145 self.assertEqual("__", pdir.sanitize_filename(".."))
1148 class FuseMagicTestPDHOnly(MountTestBase):
1149 def setUp(self, api=None):
1150 super(FuseMagicTestPDHOnly, self).setUp(api=api)
1152 cw = arvados.CollectionWriter()
1154 cw.start_new_file('thing1.txt')
1157 self.testcollection = cw.finish()
1158 self.test_manifest = cw.manifest_text()
1159 created = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
1160 self.testcollectionuuid = str(created['uuid'])
1162 def verify_pdh_only(self, pdh_only=False, skip_pdh_only=False):
1163 if skip_pdh_only is True:
1164 self.make_mount(fuse.MagicDirectory) # in this case, the default by_id applies
1166 self.make_mount(fuse.MagicDirectory, pdh_only=pdh_only)
1168 mount_ls = llfuse.listdir(self.mounttmp)
1169 self.assertIn('README', mount_ls)
1170 self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
1171 arvados.util.uuid_pattern.match(fn)
1172 for fn in mount_ls),
1173 "new FUSE MagicDirectory lists Collection")
1175 # look up using pdh should succeed in all cases
1176 self.assertDirContents(self.testcollection, ['thing1.txt'])
1177 self.assertDirContents(os.path.join('by_id', self.testcollection),
1179 mount_ls = llfuse.listdir(self.mounttmp)
1180 self.assertIn('README', mount_ls)
1181 self.assertIn(self.testcollection, mount_ls)
1182 self.assertIn(self.testcollection,
1183 llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
1186 files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
1188 for k, v in viewitems(files):
1189 with open(os.path.join(self.mounttmp, k), 'rb') as f:
1190 self.assertEqual(v, f.read().decode())
1192 # look up using uuid should fail when pdh_only is set
1193 if pdh_only is True:
1194 with self.assertRaises(OSError):
1195 self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1198 self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1201 def test_with_pdh_only_true(self):
1202 self.verify_pdh_only(pdh_only=True)
1204 def test_with_pdh_only_false(self):
1205 self.verify_pdh_only(pdh_only=False)
1207 def test_with_default_by_id(self):
1208 self.verify_pdh_only(skip_pdh_only=True)
1211 class SlashSubstitutionTest(IntegrationTest):
1214 '--mount-home', 'zzz',
1218 super(SlashSubstitutionTest, self).setUp()
1219 self.api = arvados.safeapi.ThreadSafeApiCache(arvados.config.settings())
1220 self.api.config = lambda: {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
1221 self.testcoll = self.api.collections().create(body={"name": "foo/bar/baz"}).execute()
1222 self.testcolleasy = self.api.collections().create(body={"name": "foo-bar-baz"}).execute()
1223 self.fusename = 'foo[SLASH]bar[SLASH]baz'
1225 @IntegrationTest.mount(argv=mnt_args)
1226 @mock.patch('arvados.util.get_config_once')
1227 def test_slash_substitution_before_listing(self, get_config_once):
1228 get_config_once.return_value = {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
1229 self.pool_test(os.path.join(self.mnt, 'zzz'), self.fusename)
1230 self.checkContents()
1232 def _test_slash_substitution_before_listing(self, tmpdir, fusename):
1233 with open(os.path.join(tmpdir, 'foo-bar-baz', 'waz'), 'w') as f:
1235 with open(os.path.join(tmpdir, fusename, 'waz'), 'w') as f:
1238 @IntegrationTest.mount(argv=mnt_args)
1239 @mock.patch('arvados.util.get_config_once')
1240 def test_slash_substitution_after_listing(self, get_config_once):
1241 get_config_once.return_value = {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
1242 self.pool_test(os.path.join(self.mnt, 'zzz'), self.fusename)
1243 self.checkContents()
1245 def _test_slash_substitution_after_listing(self, tmpdir, fusename):
1246 with open(os.path.join(tmpdir, 'foo-bar-baz', 'waz'), 'w') as f:
1249 with open(os.path.join(tmpdir, fusename, 'waz'), 'w') as f:
1252 def checkContents(self):
1253 self.assertRegexpMatches(self.api.collections().get(uuid=self.testcoll['uuid']).execute()['manifest_text'], ' acbd18db') # md5(foo)
1254 self.assertRegexpMatches(self.api.collections().get(uuid=self.testcolleasy['uuid']).execute()['manifest_text'], ' f561aaf6') # md5(xxx)
1256 @IntegrationTest.mount(argv=mnt_args)
1257 @mock.patch('arvados.util.get_config_once')
1258 def test_slash_substitution_conflict(self, get_config_once):
1259 self.testcollconflict = self.api.collections().create(body={"name": self.fusename}).execute()
1260 get_config_once.return_value = {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
1261 self.pool_test(os.path.join(self.mnt, 'zzz'), self.fusename)
1262 self.assertRegexpMatches(self.api.collections().get(uuid=self.testcollconflict['uuid']).execute()['manifest_text'], ' acbd18db') # md5(foo)
1263 # foo/bar/baz collection unchanged, because it is masked by foo[SLASH]bar[SLASH]baz
1264 self.assertEqual(self.api.collections().get(uuid=self.testcoll['uuid']).execute()['manifest_text'], '')
1266 def _test_slash_substitution_conflict(self, tmpdir, fusename):
1267 with open(os.path.join(tmpdir, fusename, 'waz'), 'w') as f:
1270 class StorageClassesTest(IntegrationTest):
1273 '--mount-home', 'homedir',
1277 super(StorageClassesTest, self).setUp()
1278 self.api = arvados.safeapi.ThreadSafeApiCache(arvados.config.settings())
1280 @IntegrationTest.mount(argv=mnt_args)
1281 def test_collection_default_storage_classes(self):
1282 coll_path = os.path.join(self.mnt, 'homedir', 'a_collection')
1283 self.api.collections().create(body={'name':'a_collection'}).execute()
1284 self.pool_test(coll_path)
1286 def _test_collection_default_storage_classes(self, coll):
1287 self.assertEqual(storage_classes_desired(coll), ['default'])
1289 @IntegrationTest.mount(argv=mnt_args+['--storage-classes', 'foo'])
1290 def test_collection_custom_storage_classes(self):
1291 coll_path = os.path.join(self.mnt, 'homedir', 'new_coll')
1293 self.pool_test(coll_path)
1295 def _test_collection_custom_storage_classes(self, coll):
1296 self.assertEqual(storage_classes_desired(coll), ['foo'])
1298 def _readonlyCollectionTestHelper(mounttmp):
1299 f = open(os.path.join(mounttmp, 'thing1.txt'), 'rt')
1300 # Testing that close() doesn't raise an error.
1303 class ReadonlyCollectionTest(MountTestBase):
1305 super(ReadonlyCollectionTest, self).setUp()
1306 cw = arvados.collection.Collection()
1307 with cw.open('thing1.txt', 'wt') as f:
1309 cw.save_new(owner_uuid=run_test_server.fixture("groups")["aproject"]["uuid"])
1310 self.testcollection = cw.api_response()
1313 settings = arvados.config.settings().copy()
1314 settings["ARVADOS_API_TOKEN"] = run_test_server.fixture("api_client_authorizations")["project_viewer"]["api_token"]
1315 self.api = arvados.safeapi.ThreadSafeApiCache(settings)
1316 self.make_mount(fuse.CollectionDirectory, collection_record=self.testcollection, enable_write=False)
1318 self.pool.apply(_readonlyCollectionTestHelper, (self.mounttmp,))