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
20 import arvados_fuse as fuse
21 from . import run_test_server
23 from .integration_test import IntegrationTest
24 from .mount_test_base import MountTestBase
26 logger = logging.getLogger('arvados.arv-mount')
29 class AssertWithTimeout(object):
30 """Allow some time for an assertion to pass."""
32 def __init__(self, timeout=0):
33 self.timeout = timeout
36 self.deadline = time.time() + self.timeout
45 def attempt(self, fn, *args, **kwargs):
48 except AssertionError:
49 if time.time() > self.deadline:
56 class FuseMountTest(MountTestBase):
58 super(FuseMountTest, self).setUp()
60 cw = arvados.CollectionWriter()
62 cw.start_new_file('thing1.txt')
64 cw.start_new_file('thing2.txt')
67 cw.start_new_stream('dir1')
68 cw.start_new_file('thing3.txt')
70 cw.start_new_file('thing4.txt')
73 cw.start_new_stream('dir2')
74 cw.start_new_file('thing5.txt')
76 cw.start_new_file('thing6.txt')
79 cw.start_new_stream('dir2/dir3')
80 cw.start_new_file('thing7.txt')
83 cw.start_new_file('thing8.txt')
86 cw.start_new_stream('edgecases')
87 for f in ":/.../-/*/ ".split("/"):
91 for f in ":/.../-/*/ ".split("/"):
92 cw.start_new_stream('edgecases/dirs/' + f)
93 cw.start_new_file('x/x')
96 self.testcollection = cw.finish()
97 self.api.collections().create(body={"manifest_text":cw.manifest_text()}).execute()
100 self.make_mount(fuse.CollectionDirectory, collection_record=self.testcollection)
102 self.assertDirContents(None, ['thing1.txt', 'thing2.txt',
103 'edgecases', 'dir1', 'dir2'])
104 self.assertDirContents('dir1', ['thing3.txt', 'thing4.txt'])
105 self.assertDirContents('dir2', ['thing5.txt', 'thing6.txt', 'dir3'])
106 self.assertDirContents('dir2/dir3', ['thing7.txt', 'thing8.txt'])
107 self.assertDirContents('edgecases',
108 "dirs/:/.../-/*/ ".split("/"))
109 self.assertDirContents('edgecases/dirs',
110 ":/.../-/*/ ".split("/"))
112 files = {'thing1.txt': 'data 1',
113 'thing2.txt': 'data 2',
114 'dir1/thing3.txt': 'data 3',
115 'dir1/thing4.txt': 'data 4',
116 'dir2/thing5.txt': 'data 5',
117 'dir2/thing6.txt': 'data 6',
118 'dir2/dir3/thing7.txt': 'data 7',
119 'dir2/dir3/thing8.txt': 'data 8'}
121 for k, v in viewitems(files):
122 with open(os.path.join(self.mounttmp, k), 'rb') as f:
123 self.assertEqual(v, f.read().decode())
126 class FuseMagicTest(MountTestBase):
127 def setUp(self, api=None):
128 super(FuseMagicTest, self).setUp(api=api)
130 self.test_project = run_test_server.fixture('groups')['aproject']['uuid']
131 self.non_project_group = run_test_server.fixture('groups')['public_role']['uuid']
132 self.collection_in_test_project = run_test_server.fixture('collections')['foo_collection_in_aproject']['name']
134 cw = arvados.CollectionWriter()
136 cw.start_new_file('thing1.txt')
139 self.testcollection = cw.finish()
140 self.test_manifest = cw.manifest_text()
141 coll = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
142 self.test_manifest_pdh = coll['portable_data_hash']
145 self.make_mount(fuse.MagicDirectory)
147 mount_ls = llfuse.listdir(self.mounttmp)
148 self.assertIn('README', mount_ls)
149 self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
150 arvados.util.uuid_pattern.match(fn)
152 "new FUSE MagicDirectory has no collections or projects")
153 self.assertDirContents(self.testcollection, ['thing1.txt'])
154 self.assertDirContents(os.path.join('by_id', self.testcollection),
156 self.assertIn(self.collection_in_test_project,
157 llfuse.listdir(os.path.join(self.mounttmp, self.test_project)))
158 self.assertIn(self.collection_in_test_project,
159 llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.test_project)))
161 mount_ls = llfuse.listdir(self.mounttmp)
162 self.assertIn('README', mount_ls)
163 self.assertIn(self.testcollection, mount_ls)
164 self.assertIn(self.testcollection,
165 llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
166 self.assertIn(self.test_project, mount_ls)
167 self.assertIn(self.test_project,
168 llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
170 with self.assertRaises(OSError):
171 llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.non_project_group))
174 files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
176 for k, v in viewitems(files):
177 with open(os.path.join(self.mounttmp, k), 'rb') as f:
178 self.assertEqual(v, f.read().decode())
181 class FuseTagsTest(MountTestBase):
183 self.make_mount(fuse.TagsDirectory)
185 d1 = llfuse.listdir(self.mounttmp)
187 self.assertEqual(['foo_tag'], d1)
189 d2 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag'))
191 self.assertEqual(['zzzzz-4zz18-fy296fx3hot09f7'], d2)
193 d3 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag', 'zzzzz-4zz18-fy296fx3hot09f7'))
195 self.assertEqual(['foo'], d3)
198 class FuseTagsUpdateTest(MountTestBase):
199 def tag_collection(self, coll_uuid, tag_name):
200 return self.api.links().create(
201 body={'link': {'head_uuid': coll_uuid,
207 self.make_mount(fuse.TagsDirectory, poll_time=1)
209 self.assertIn('foo_tag', llfuse.listdir(self.mounttmp))
211 bar_uuid = run_test_server.fixture('collections')['bar_file']['uuid']
212 self.tag_collection(bar_uuid, 'fuse_test_tag')
213 for attempt in AssertWithTimeout(10):
214 attempt(self.assertIn, 'fuse_test_tag', llfuse.listdir(self.mounttmp))
215 self.assertDirContents('fuse_test_tag', [bar_uuid])
217 baz_uuid = run_test_server.fixture('collections')['baz_file']['uuid']
218 l = self.tag_collection(baz_uuid, 'fuse_test_tag')
219 for attempt in AssertWithTimeout(10):
220 attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid, baz_uuid])
222 self.api.links().delete(uuid=l['uuid']).execute()
223 for attempt in AssertWithTimeout(10):
224 attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid])
227 def fuseSharedTestHelper(mounttmp):
228 class Test(unittest.TestCase):
230 # Double check that we can open and read objects in this folder as a file,
231 # and that its contents are what we expect.
232 baz_path = os.path.join(
236 'collection in FUSE project',
238 with open(baz_path) as f:
239 self.assertEqual("baz", f.read())
241 # check mtime on collection
242 st = os.stat(baz_path)
244 mtime = st.st_mtime_ns // 1000000000
245 except AttributeError:
247 self.assertEqual(mtime, 1391448174)
249 # shared_dirs is a list of the directories exposed
250 # by fuse.SharedDirectory (i.e. any object visible
251 # to the current user)
252 shared_dirs = llfuse.listdir(mounttmp)
254 self.assertIn('FUSE User', shared_dirs)
256 # fuse_user_objs is a list of the objects owned by the FUSE
257 # test user (which present as files in the 'FUSE User'
259 fuse_user_objs = llfuse.listdir(os.path.join(mounttmp, 'FUSE User'))
260 fuse_user_objs.sort()
261 self.assertEqual(['FUSE Test Project', # project owned by user
262 'collection #1 owned by FUSE', # collection owned by user
263 'collection #2 owned by FUSE' # collection owned by user
266 # test_proj_files is a list of the files in the FUSE Test Project.
267 test_proj_files = llfuse.listdir(os.path.join(mounttmp, 'FUSE User', 'FUSE Test Project'))
268 test_proj_files.sort()
269 self.assertEqual(['collection in FUSE project'
275 class FuseSharedTest(MountTestBase):
277 self.make_mount(fuse.SharedDirectory,
278 exclude=self.api.users().current().execute()['uuid'])
279 keep = arvados.keep.KeepClient()
280 keep.put("baz".encode())
282 self.pool.apply(fuseSharedTestHelper, (self.mounttmp,))
285 class FuseHomeTest(MountTestBase):
287 self.make_mount(fuse.ProjectDirectory,
288 project_object=self.api.users().current().execute())
290 d1 = llfuse.listdir(self.mounttmp)
291 self.assertIn('Unrestricted public data', d1)
293 d2 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data'))
294 public_project = run_test_server.fixture('groups')[
295 'anonymously_accessible_project']
298 for name, item in viewitems(run_test_server.fixture('collections')):
299 if 'name' not in item:
301 elif item['owner_uuid'] == public_project['uuid']:
302 self.assertIn(item['name'], d2)
305 # Artificial assumption here: there is no public
306 # collection fixture with the same name as a
307 # non-public collection.
308 self.assertNotIn(item['name'], d2)
310 self.assertNotEqual(0, found_in)
311 self.assertNotEqual(0, found_not_in)
313 d3 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data', 'GNU General Public License, version 3'))
314 self.assertEqual(["GNU_General_Public_License,_version_3.pdf"], d3)
317 def fuseModifyFileTestHelperReadStartContents(mounttmp):
318 class Test(unittest.TestCase):
320 d1 = llfuse.listdir(mounttmp)
321 self.assertEqual(["file1.txt"], d1)
322 with open(os.path.join(mounttmp, "file1.txt")) as f:
323 self.assertEqual("blub", f.read())
326 def fuseModifyFileTestHelperReadEndContents(mounttmp):
327 class Test(unittest.TestCase):
329 d1 = llfuse.listdir(mounttmp)
330 self.assertEqual(["file1.txt"], d1)
331 with open(os.path.join(mounttmp, "file1.txt")) as f:
332 self.assertEqual("plnp", f.read())
335 class FuseModifyFileTest(MountTestBase):
337 collection = arvados.collection.Collection(api_client=self.api)
338 with collection.open("file1.txt", "w") as f:
341 collection.save_new()
343 m = self.make_mount(fuse.CollectionDirectory)
345 m.new_collection(collection.api_response(), collection)
347 self.pool.apply(fuseModifyFileTestHelperReadStartContents, (self.mounttmp,))
349 with collection.open("file1.txt", "w") as f:
352 self.pool.apply(fuseModifyFileTestHelperReadEndContents, (self.mounttmp,))
355 class FuseAddFileToCollectionTest(MountTestBase):
357 collection = arvados.collection.Collection(api_client=self.api)
358 with collection.open("file1.txt", "w") as f:
361 collection.save_new()
363 m = self.make_mount(fuse.CollectionDirectory)
365 m.new_collection(collection.api_response(), collection)
367 d1 = llfuse.listdir(self.mounttmp)
368 self.assertEqual(["file1.txt"], d1)
370 with collection.open("file2.txt", "w") as f:
373 d1 = llfuse.listdir(self.mounttmp)
374 self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
377 class FuseRemoveFileFromCollectionTest(MountTestBase):
379 collection = arvados.collection.Collection(api_client=self.api)
380 with collection.open("file1.txt", "w") as f:
383 with collection.open("file2.txt", "w") as f:
386 collection.save_new()
388 m = self.make_mount(fuse.CollectionDirectory)
390 m.new_collection(collection.api_response(), collection)
392 d1 = llfuse.listdir(self.mounttmp)
393 self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
395 collection.remove("file2.txt")
397 d1 = llfuse.listdir(self.mounttmp)
398 self.assertEqual(["file1.txt"], d1)
401 def fuseCreateFileTestHelper(mounttmp):
402 class Test(unittest.TestCase):
404 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
408 class FuseCreateFileTest(MountTestBase):
410 collection = arvados.collection.Collection(api_client=self.api)
411 collection.save_new()
413 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
414 self.assertEqual(collection2["manifest_text"], "")
416 collection.save_new()
418 m = self.make_mount(fuse.CollectionDirectory)
420 m.new_collection(collection.api_response(), collection)
421 self.assertTrue(m.writable())
423 self.assertNotIn("file1.txt", collection)
425 self.pool.apply(fuseCreateFileTestHelper, (self.mounttmp,))
427 self.assertIn("file1.txt", collection)
429 d1 = llfuse.listdir(self.mounttmp)
430 self.assertEqual(["file1.txt"], d1)
432 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
433 assertRegex(self, collection2["manifest_text"],
434 r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:file1\.txt$')
437 def fuseWriteFileTestHelperWriteFile(mounttmp):
438 class Test(unittest.TestCase):
440 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
441 f.write("Hello world!")
444 def fuseWriteFileTestHelperReadFile(mounttmp):
445 class Test(unittest.TestCase):
447 with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
448 self.assertEqual(f.read(), "Hello world!")
451 class FuseWriteFileTest(MountTestBase):
453 collection = arvados.collection.Collection(api_client=self.api)
454 collection.save_new()
456 m = self.make_mount(fuse.CollectionDirectory)
458 m.new_collection(collection.api_response(), collection)
459 self.assertTrue(m.writable())
461 self.assertNotIn("file1.txt", collection)
463 self.assertEqual(0, self.operations.write_counter.get())
464 self.pool.apply(fuseWriteFileTestHelperWriteFile, (self.mounttmp,))
465 self.assertEqual(12, self.operations.write_counter.get())
467 with collection.open("file1.txt") as f:
468 self.assertEqual(f.read(), "Hello world!")
470 self.assertEqual(0, self.operations.read_counter.get())
471 self.pool.apply(fuseWriteFileTestHelperReadFile, (self.mounttmp,))
472 self.assertEqual(12, self.operations.read_counter.get())
474 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
475 assertRegex(self, collection2["manifest_text"],
476 r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
479 def fuseUpdateFileTestHelper(mounttmp):
480 class Test(unittest.TestCase):
482 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
483 f.write("Hello world!")
485 with open(os.path.join(mounttmp, "file1.txt"), "r+") as f:
487 self.assertEqual(fr, "Hello world!")
489 f.write("Hola mundo!")
492 self.assertEqual(fr, "Hola mundo!!")
494 with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
495 self.assertEqual(f.read(), "Hola mundo!!")
499 class FuseUpdateFileTest(MountTestBase):
501 collection = arvados.collection.Collection(api_client=self.api)
502 collection.save_new()
504 m = self.make_mount(fuse.CollectionDirectory)
506 m.new_collection(collection.api_response(), collection)
507 self.assertTrue(m.writable())
509 # See note in MountTestBase.setUp
510 self.pool.apply(fuseUpdateFileTestHelper, (self.mounttmp,))
512 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
513 assertRegex(self, collection2["manifest_text"],
514 r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A\S+ 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:11:file1\.txt 22:1:file1\.txt$')
517 def fuseMkdirTestHelper(mounttmp):
518 class Test(unittest.TestCase):
520 with self.assertRaises(IOError):
521 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
522 f.write("Hello world!")
524 os.mkdir(os.path.join(mounttmp, "testdir"))
526 with self.assertRaises(OSError):
527 os.mkdir(os.path.join(mounttmp, "testdir"))
529 d1 = llfuse.listdir(mounttmp)
530 self.assertEqual(["testdir"], d1)
532 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
533 f.write("Hello world!")
535 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
536 self.assertEqual(["file1.txt"], d1)
540 class FuseMkdirTest(MountTestBase):
542 collection = arvados.collection.Collection(api_client=self.api)
543 collection.save_new()
545 m = self.make_mount(fuse.CollectionDirectory)
547 m.new_collection(collection.api_response(), collection)
548 self.assertTrue(m.writable())
550 self.pool.apply(fuseMkdirTestHelper, (self.mounttmp,))
552 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
553 assertRegex(self, collection2["manifest_text"],
554 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
557 def fuseRmTestHelperWriteFile(mounttmp):
558 class Test(unittest.TestCase):
560 os.mkdir(os.path.join(mounttmp, "testdir"))
562 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
563 f.write("Hello world!")
567 def fuseRmTestHelperDeleteFile(mounttmp):
568 class Test(unittest.TestCase):
570 # Can't delete because it's not empty
571 with self.assertRaises(OSError):
572 os.rmdir(os.path.join(mounttmp, "testdir"))
574 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
575 self.assertEqual(["file1.txt"], d1)
578 os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
580 # Make sure it's empty
581 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
582 self.assertEqual([], d1)
584 # Try to delete it again
585 with self.assertRaises(OSError):
586 os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
590 def fuseRmTestHelperRmdir(mounttmp):
591 class Test(unittest.TestCase):
593 # Should be able to delete now that it is empty
594 os.rmdir(os.path.join(mounttmp, "testdir"))
596 # Make sure it's empty
597 d1 = llfuse.listdir(os.path.join(mounttmp))
598 self.assertEqual([], d1)
600 # Try to delete it again
601 with self.assertRaises(OSError):
602 os.rmdir(os.path.join(mounttmp, "testdir"))
606 class FuseRmTest(MountTestBase):
608 collection = arvados.collection.Collection(api_client=self.api)
609 collection.save_new()
611 m = self.make_mount(fuse.CollectionDirectory)
613 m.new_collection(collection.api_response(), collection)
614 self.assertTrue(m.writable())
616 self.pool.apply(fuseRmTestHelperWriteFile, (self.mounttmp,))
619 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
620 assertRegex(self, collection2["manifest_text"],
621 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
622 self.pool.apply(fuseRmTestHelperDeleteFile, (self.mounttmp,))
624 # Empty directories are represented by an empty file named "."
625 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
626 assertRegex(self, collection2["manifest_text"],
627 r'./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
629 self.pool.apply(fuseRmTestHelperRmdir, (self.mounttmp,))
631 # manifest should be empty now.
632 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
633 self.assertEqual(collection2["manifest_text"], "")
636 def fuseMvFileTestHelperWriteFile(mounttmp):
637 class Test(unittest.TestCase):
639 os.mkdir(os.path.join(mounttmp, "testdir"))
641 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
642 f.write("Hello world!")
646 def fuseMvFileTestHelperMoveFile(mounttmp):
647 class Test(unittest.TestCase):
649 d1 = llfuse.listdir(os.path.join(mounttmp))
650 self.assertEqual(["testdir"], d1)
651 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
652 self.assertEqual(["file1.txt"], d1)
654 os.rename(os.path.join(mounttmp, "testdir", "file1.txt"), os.path.join(mounttmp, "file1.txt"))
656 d1 = llfuse.listdir(os.path.join(mounttmp))
657 self.assertEqual(["file1.txt", "testdir"], sorted(d1))
658 d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
659 self.assertEqual([], d1)
663 class FuseMvFileTest(MountTestBase):
665 collection = arvados.collection.Collection(api_client=self.api)
666 collection.save_new()
668 m = self.make_mount(fuse.CollectionDirectory)
670 m.new_collection(collection.api_response(), collection)
671 self.assertTrue(m.writable())
673 self.pool.apply(fuseMvFileTestHelperWriteFile, (self.mounttmp,))
676 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
677 assertRegex(self, collection2["manifest_text"],
678 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
680 self.pool.apply(fuseMvFileTestHelperMoveFile, (self.mounttmp,))
682 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
683 assertRegex(self, collection2["manifest_text"],
684 r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt\n\./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
687 def fuseRenameTestHelper(mounttmp):
688 class Test(unittest.TestCase):
690 os.mkdir(os.path.join(mounttmp, "testdir"))
692 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
693 f.write("Hello world!")
697 class FuseRenameTest(MountTestBase):
699 collection = arvados.collection.Collection(api_client=self.api)
700 collection.save_new()
702 m = self.make_mount(fuse.CollectionDirectory)
704 m.new_collection(collection.api_response(), collection)
705 self.assertTrue(m.writable())
707 self.pool.apply(fuseRenameTestHelper, (self.mounttmp,))
710 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
711 assertRegex(self, collection2["manifest_text"],
712 r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
714 d1 = llfuse.listdir(os.path.join(self.mounttmp))
715 self.assertEqual(["testdir"], d1)
716 d1 = llfuse.listdir(os.path.join(self.mounttmp, "testdir"))
717 self.assertEqual(["file1.txt"], d1)
719 os.rename(os.path.join(self.mounttmp, "testdir"), os.path.join(self.mounttmp, "testdir2"))
721 d1 = llfuse.listdir(os.path.join(self.mounttmp))
722 self.assertEqual(["testdir2"], sorted(d1))
723 d1 = llfuse.listdir(os.path.join(self.mounttmp, "testdir2"))
724 self.assertEqual(["file1.txt"], d1)
726 collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
727 assertRegex(self, collection2["manifest_text"],
728 r'\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
731 class FuseUpdateFromEventTest(MountTestBase):
733 collection = arvados.collection.Collection(api_client=self.api)
734 collection.save_new()
736 m = self.make_mount(fuse.CollectionDirectory)
738 m.new_collection(collection.api_response(), collection)
740 self.operations.listen_for_events()
742 d1 = llfuse.listdir(os.path.join(self.mounttmp))
743 self.assertEqual([], sorted(d1))
745 with arvados.collection.Collection(collection.manifest_locator(), api_client=self.api) as collection2:
746 with collection2.open("file1.txt", "w") as f:
749 for attempt in AssertWithTimeout(10):
750 attempt(self.assertEqual, ["file1.txt"], llfuse.listdir(os.path.join(self.mounttmp)))
753 class FuseDeleteProjectEventTest(MountTestBase):
756 aproject = self.api.groups().create(body={
758 "group_class": "project"
761 bproject = self.api.groups().create(body={
763 "group_class": "project",
764 "owner_uuid": aproject["uuid"]
767 self.make_mount(fuse.ProjectDirectory,
768 project_object=self.api.users().current().execute())
770 self.operations.listen_for_events()
772 d1 = llfuse.listdir(os.path.join(self.mounttmp, "aproject"))
773 self.assertEqual(["bproject"], sorted(d1))
775 self.api.groups().delete(uuid=bproject["uuid"]).execute()
777 for attempt in AssertWithTimeout(10):
778 attempt(self.assertEqual, [], llfuse.listdir(os.path.join(self.mounttmp, "aproject")))
781 def fuseFileConflictTestHelper(mounttmp):
782 class Test(unittest.TestCase):
784 with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
787 d1 = sorted(llfuse.listdir(os.path.join(mounttmp)))
788 self.assertEqual(len(d1), 2)
790 with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
791 self.assertEqual(f.read(), "bar")
793 assertRegex(self, d1[1],
794 r'file1\.txt~\d\d\d\d\d\d\d\d-\d\d\d\d\d\d~conflict~')
796 with open(os.path.join(mounttmp, d1[1]), "r") as f:
797 self.assertEqual(f.read(), "foo")
801 class FuseFileConflictTest(MountTestBase):
803 collection = arvados.collection.Collection(api_client=self.api)
804 collection.save_new()
806 m = self.make_mount(fuse.CollectionDirectory)
808 m.new_collection(collection.api_response(), collection)
810 d1 = llfuse.listdir(os.path.join(self.mounttmp))
811 self.assertEqual([], sorted(d1))
813 with arvados.collection.Collection(collection.manifest_locator(), api_client=self.api) as collection2:
814 with collection2.open("file1.txt", "w") as f:
817 # See note in MountTestBase.setUp
818 self.pool.apply(fuseFileConflictTestHelper, (self.mounttmp,))
821 def fuseUnlinkOpenFileTest(mounttmp):
822 class Test(unittest.TestCase):
824 with open(os.path.join(mounttmp, "file1.txt"), "w+") as f:
827 d1 = llfuse.listdir(os.path.join(mounttmp))
828 self.assertEqual(["file1.txt"], sorted(d1))
830 os.remove(os.path.join(mounttmp, "file1.txt"))
832 d1 = llfuse.listdir(os.path.join(mounttmp))
833 self.assertEqual([], sorted(d1))
836 self.assertEqual(f.read(), "foo")
840 self.assertEqual(f.read(), "foobar")
844 class FuseUnlinkOpenFileTest(MountTestBase):
846 collection = arvados.collection.Collection(api_client=self.api)
847 collection.save_new()
849 m = self.make_mount(fuse.CollectionDirectory)
851 m.new_collection(collection.api_response(), collection)
853 # See note in MountTestBase.setUp
854 self.pool.apply(fuseUnlinkOpenFileTest, (self.mounttmp,))
856 self.assertEqual(collection.manifest_text(), "")
859 def fuseMvFileBetweenCollectionsTest1(mounttmp, uuid1, uuid2):
860 class Test(unittest.TestCase):
862 with open(os.path.join(mounttmp, uuid1, "file1.txt"), "w") as f:
863 f.write("Hello world!")
865 d1 = os.listdir(os.path.join(mounttmp, uuid1))
866 self.assertEqual(["file1.txt"], sorted(d1))
867 d1 = os.listdir(os.path.join(mounttmp, uuid2))
868 self.assertEqual([], sorted(d1))
872 def fuseMvFileBetweenCollectionsTest2(mounttmp, uuid1, uuid2):
873 class Test(unittest.TestCase):
875 os.rename(os.path.join(mounttmp, uuid1, "file1.txt"), os.path.join(mounttmp, uuid2, "file2.txt"))
877 d1 = os.listdir(os.path.join(mounttmp, uuid1))
878 self.assertEqual([], sorted(d1))
879 d1 = os.listdir(os.path.join(mounttmp, uuid2))
880 self.assertEqual(["file2.txt"], sorted(d1))
884 class FuseMvFileBetweenCollectionsTest(MountTestBase):
886 collection1 = arvados.collection.Collection(api_client=self.api)
887 collection1.save_new()
889 collection2 = arvados.collection.Collection(api_client=self.api)
890 collection2.save_new()
892 m = self.make_mount(fuse.MagicDirectory)
894 # See note in MountTestBase.setUp
895 self.pool.apply(fuseMvFileBetweenCollectionsTest1, (self.mounttmp,
896 collection1.manifest_locator(),
897 collection2.manifest_locator()))
902 assertRegex(self, collection1.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
903 self.assertEqual(collection2.manifest_text(), "")
905 self.pool.apply(fuseMvFileBetweenCollectionsTest2, (self.mounttmp,
906 collection1.manifest_locator(),
907 collection2.manifest_locator()))
912 self.assertEqual(collection1.manifest_text(), "")
913 assertRegex(self, collection2.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file2\.txt$")
915 collection1.stop_threads()
916 collection2.stop_threads()
919 def fuseMvDirBetweenCollectionsTest1(mounttmp, uuid1, uuid2):
920 class Test(unittest.TestCase):
922 os.mkdir(os.path.join(mounttmp, uuid1, "testdir"))
923 with open(os.path.join(mounttmp, uuid1, "testdir", "file1.txt"), "w") as f:
924 f.write("Hello world!")
926 d1 = os.listdir(os.path.join(mounttmp, uuid1))
927 self.assertEqual(["testdir"], sorted(d1))
928 d1 = os.listdir(os.path.join(mounttmp, uuid1, "testdir"))
929 self.assertEqual(["file1.txt"], sorted(d1))
931 d1 = os.listdir(os.path.join(mounttmp, uuid2))
932 self.assertEqual([], sorted(d1))
937 def fuseMvDirBetweenCollectionsTest2(mounttmp, uuid1, uuid2):
938 class Test(unittest.TestCase):
940 os.rename(os.path.join(mounttmp, uuid1, "testdir"), os.path.join(mounttmp, uuid2, "testdir2"))
942 d1 = os.listdir(os.path.join(mounttmp, uuid1))
943 self.assertEqual([], sorted(d1))
945 d1 = os.listdir(os.path.join(mounttmp, uuid2))
946 self.assertEqual(["testdir2"], sorted(d1))
947 d1 = os.listdir(os.path.join(mounttmp, uuid2, "testdir2"))
948 self.assertEqual(["file1.txt"], sorted(d1))
950 with open(os.path.join(mounttmp, uuid2, "testdir2", "file1.txt"), "r") as f:
951 self.assertEqual(f.read(), "Hello world!")
955 class FuseMvDirBetweenCollectionsTest(MountTestBase):
957 collection1 = arvados.collection.Collection(api_client=self.api)
958 collection1.save_new()
960 collection2 = arvados.collection.Collection(api_client=self.api)
961 collection2.save_new()
963 m = self.make_mount(fuse.MagicDirectory)
965 # See note in MountTestBase.setUp
966 self.pool.apply(fuseMvDirBetweenCollectionsTest1, (self.mounttmp,
967 collection1.manifest_locator(),
968 collection2.manifest_locator()))
973 assertRegex(self, collection1.manifest_text(), r"\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
974 self.assertEqual(collection2.manifest_text(), "")
976 self.pool.apply(fuseMvDirBetweenCollectionsTest2, (self.mounttmp,
977 collection1.manifest_locator(),
978 collection2.manifest_locator()))
983 self.assertEqual(collection1.manifest_text(), "")
984 assertRegex(self, collection2.manifest_text(), r"\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
986 collection1.stop_threads()
987 collection2.stop_threads()
989 def fuseProjectMkdirTestHelper1(mounttmp):
990 class Test(unittest.TestCase):
992 os.mkdir(os.path.join(mounttmp, "testcollection"))
993 with self.assertRaises(OSError):
994 os.mkdir(os.path.join(mounttmp, "testcollection"))
997 def fuseProjectMkdirTestHelper2(mounttmp):
998 class Test(unittest.TestCase):
1000 with open(os.path.join(mounttmp, "testcollection", "file1.txt"), "w") as f:
1001 f.write("Hello world!")
1002 with self.assertRaises(OSError):
1003 os.rmdir(os.path.join(mounttmp, "testcollection"))
1004 os.remove(os.path.join(mounttmp, "testcollection", "file1.txt"))
1005 with self.assertRaises(OSError):
1006 os.remove(os.path.join(mounttmp, "testcollection"))
1007 os.rmdir(os.path.join(mounttmp, "testcollection"))
1010 class FuseProjectMkdirRmdirTest(MountTestBase):
1012 self.make_mount(fuse.ProjectDirectory,
1013 project_object=self.api.users().current().execute())
1015 d1 = llfuse.listdir(self.mounttmp)
1016 self.assertNotIn('testcollection', d1)
1018 self.pool.apply(fuseProjectMkdirTestHelper1, (self.mounttmp,))
1020 d1 = llfuse.listdir(self.mounttmp)
1021 self.assertIn('testcollection', d1)
1023 self.pool.apply(fuseProjectMkdirTestHelper2, (self.mounttmp,))
1025 d1 = llfuse.listdir(self.mounttmp)
1026 self.assertNotIn('testcollection', d1)
1029 def fuseProjectMvTestHelper1(mounttmp):
1030 class Test(unittest.TestCase):
1032 d1 = llfuse.listdir(mounttmp)
1033 self.assertNotIn('testcollection', d1)
1035 os.mkdir(os.path.join(mounttmp, "testcollection"))
1037 d1 = llfuse.listdir(mounttmp)
1038 self.assertIn('testcollection', d1)
1040 with self.assertRaises(OSError):
1041 os.rename(os.path.join(mounttmp, "testcollection"), os.path.join(mounttmp, 'Unrestricted public data'))
1043 os.rename(os.path.join(mounttmp, "testcollection"), os.path.join(mounttmp, 'Unrestricted public data', 'testcollection'))
1045 d1 = llfuse.listdir(mounttmp)
1046 self.assertNotIn('testcollection', d1)
1048 d1 = llfuse.listdir(os.path.join(mounttmp, 'Unrestricted public data'))
1049 self.assertIn('testcollection', d1)
1053 class FuseProjectMvTest(MountTestBase):
1055 self.make_mount(fuse.ProjectDirectory,
1056 project_object=self.api.users().current().execute())
1058 self.pool.apply(fuseProjectMvTestHelper1, (self.mounttmp,))
1061 def fuseFsyncTestHelper(mounttmp, k):
1062 class Test(unittest.TestCase):
1064 fd = os.open(os.path.join(mounttmp, k), os.O_RDONLY)
1070 class FuseFsyncTest(FuseMagicTest):
1072 self.make_mount(fuse.MagicDirectory)
1073 self.pool.apply(fuseFsyncTestHelper, (self.mounttmp, self.testcollection))
1076 class MagicDirApiError(FuseMagicTest):
1078 api = mock.MagicMock()
1079 super(MagicDirApiError, self).setUp(api=api)
1080 api.collections().get().execute.side_effect = iter([
1081 Exception('API fail'),
1083 "manifest_text": self.test_manifest,
1084 "portable_data_hash": self.test_manifest_pdh,
1087 api.keep.get.side_effect = Exception('Keep fail')
1090 with mock.patch('arvados_fuse.fresh.FreshBase._poll_time', new_callable=mock.PropertyMock, return_value=60) as mock_poll_time:
1091 self.make_mount(fuse.MagicDirectory)
1093 self.operations.inodes.inode_cache.cap = 1
1094 self.operations.inodes.inode_cache.min_entries = 2
1096 with self.assertRaises(OSError):
1097 llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1099 llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1102 class SanitizeFilenameTest(MountTestBase):
1103 def test_sanitize_filename(self):
1104 pdir = fuse.ProjectDirectory(1, {}, self.api, 0, project_object=self.api.users().current().execute())
1123 for f in acceptable:
1124 self.assertEqual(f, pdir.sanitize_filename(f))
1125 for f in unacceptable:
1126 self.assertNotEqual(f, pdir.sanitize_filename(f))
1127 # The sanitized filename should be the same length, though.
1128 self.assertEqual(len(f), len(pdir.sanitize_filename(f)))
1130 self.assertEqual("_", pdir.sanitize_filename(""))
1131 self.assertEqual("_", pdir.sanitize_filename("."))
1132 self.assertEqual("__", pdir.sanitize_filename(".."))
1135 class FuseMagicTestPDHOnly(MountTestBase):
1136 def setUp(self, api=None):
1137 super(FuseMagicTestPDHOnly, self).setUp(api=api)
1139 cw = arvados.CollectionWriter()
1141 cw.start_new_file('thing1.txt')
1144 self.testcollection = cw.finish()
1145 self.test_manifest = cw.manifest_text()
1146 created = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
1147 self.testcollectionuuid = str(created['uuid'])
1149 def verify_pdh_only(self, pdh_only=False, skip_pdh_only=False):
1150 if skip_pdh_only is True:
1151 self.make_mount(fuse.MagicDirectory) # in this case, the default by_id applies
1153 self.make_mount(fuse.MagicDirectory, pdh_only=pdh_only)
1155 mount_ls = llfuse.listdir(self.mounttmp)
1156 self.assertIn('README', mount_ls)
1157 self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
1158 arvados.util.uuid_pattern.match(fn)
1159 for fn in mount_ls),
1160 "new FUSE MagicDirectory lists Collection")
1162 # look up using pdh should succeed in all cases
1163 self.assertDirContents(self.testcollection, ['thing1.txt'])
1164 self.assertDirContents(os.path.join('by_id', self.testcollection),
1166 mount_ls = llfuse.listdir(self.mounttmp)
1167 self.assertIn('README', mount_ls)
1168 self.assertIn(self.testcollection, mount_ls)
1169 self.assertIn(self.testcollection,
1170 llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
1173 files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
1175 for k, v in viewitems(files):
1176 with open(os.path.join(self.mounttmp, k), 'rb') as f:
1177 self.assertEqual(v, f.read().decode())
1179 # look up using uuid should fail when pdh_only is set
1180 if pdh_only is True:
1181 with self.assertRaises(OSError):
1182 self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1185 self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1188 def test_with_pdh_only_true(self):
1189 self.verify_pdh_only(pdh_only=True)
1191 def test_with_pdh_only_false(self):
1192 self.verify_pdh_only(pdh_only=False)
1194 def test_with_default_by_id(self):
1195 self.verify_pdh_only(skip_pdh_only=True)
1198 class SlashSubstitutionTest(IntegrationTest):
1201 '--mount-home', 'zzz',
1205 super(SlashSubstitutionTest, self).setUp()
1206 self.api = arvados.safeapi.ThreadSafeApiCache(arvados.config.settings())
1207 self.api.config = lambda: {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
1208 self.testcoll = self.api.collections().create(body={"name": "foo/bar/baz"}).execute()
1209 self.testcolleasy = self.api.collections().create(body={"name": "foo-bar-baz"}).execute()
1210 self.fusename = 'foo[SLASH]bar[SLASH]baz'
1212 @IntegrationTest.mount(argv=mnt_args)
1213 @mock.patch('arvados.util.get_config_once')
1214 def test_slash_substitution_before_listing(self, get_config_once):
1215 get_config_once.return_value = {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
1216 self.pool_test(os.path.join(self.mnt, 'zzz'), self.fusename)
1217 self.checkContents()
1219 def _test_slash_substitution_before_listing(self, tmpdir, fusename):
1220 with open(os.path.join(tmpdir, 'foo-bar-baz', 'waz'), 'w') as f:
1222 with open(os.path.join(tmpdir, fusename, 'waz'), 'w') as f:
1225 @IntegrationTest.mount(argv=mnt_args)
1226 @mock.patch('arvados.util.get_config_once')
1227 def test_slash_substitution_after_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_after_listing(self, tmpdir, fusename):
1233 with open(os.path.join(tmpdir, 'foo-bar-baz', 'waz'), 'w') as f:
1236 with open(os.path.join(tmpdir, fusename, 'waz'), 'w') as f:
1239 def checkContents(self):
1240 self.assertRegexpMatches(self.api.collections().get(uuid=self.testcoll['uuid']).execute()['manifest_text'], ' acbd18db') # md5(foo)
1241 self.assertRegexpMatches(self.api.collections().get(uuid=self.testcolleasy['uuid']).execute()['manifest_text'], ' f561aaf6') # md5(xxx)
1243 @IntegrationTest.mount(argv=mnt_args)
1244 @mock.patch('arvados.util.get_config_once')
1245 def test_slash_substitution_conflict(self, get_config_once):
1246 self.testcollconflict = self.api.collections().create(body={"name": self.fusename}).execute()
1247 get_config_once.return_value = {"Collections": {"ForwardSlashNameSubstitution": "[SLASH]"}}
1248 self.pool_test(os.path.join(self.mnt, 'zzz'), self.fusename)
1249 self.assertRegexpMatches(self.api.collections().get(uuid=self.testcollconflict['uuid']).execute()['manifest_text'], ' acbd18db') # md5(foo)
1250 # foo/bar/baz collection unchanged, because it is masked by foo[SLASH]bar[SLASH]baz
1251 self.assertEqual(self.api.collections().get(uuid=self.testcoll['uuid']).execute()['manifest_text'], '')
1253 def _test_slash_substitution_conflict(self, tmpdir, fusename):
1254 with open(os.path.join(tmpdir, fusename, 'waz'), 'w') as f: