14965: Updates fuse tests with futurize stage1
[arvados.git] / services / fuse / tests / test_mount.py
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 from __future__ import absolute_import
6 import json
7 import llfuse
8 import logging
9 import mock
10 import os
11 import subprocess
12 import time
13 import unittest
14
15 import arvados
16 import arvados_fuse as fuse
17 from . import run_test_server
18
19 from .mount_test_base import MountTestBase
20
21 logger = logging.getLogger('arvados.arv-mount')
22
23
24 class AssertWithTimeout(object):
25     """Allow some time for an assertion to pass."""
26
27     def __init__(self, timeout=0):
28         self.timeout = timeout
29
30     def __iter__(self):
31         self.deadline = time.time() + self.timeout
32         self.done = False
33         return self
34
35     def next(self):
36         if self.done:
37             raise StopIteration
38         return self.attempt
39
40     def attempt(self, fn, *args, **kwargs):
41         try:
42             fn(*args, **kwargs)
43         except AssertionError:
44             if time.time() > self.deadline:
45                 raise
46             time.sleep(0.1)
47         else:
48             self.done = True
49
50
51 class FuseMountTest(MountTestBase):
52     def setUp(self):
53         super(FuseMountTest, self).setUp()
54
55         cw = arvados.CollectionWriter()
56
57         cw.start_new_file('thing1.txt')
58         cw.write("data 1")
59         cw.start_new_file('thing2.txt')
60         cw.write("data 2")
61
62         cw.start_new_stream('dir1')
63         cw.start_new_file('thing3.txt')
64         cw.write("data 3")
65         cw.start_new_file('thing4.txt')
66         cw.write("data 4")
67
68         cw.start_new_stream('dir2')
69         cw.start_new_file('thing5.txt')
70         cw.write("data 5")
71         cw.start_new_file('thing6.txt')
72         cw.write("data 6")
73
74         cw.start_new_stream('dir2/dir3')
75         cw.start_new_file('thing7.txt')
76         cw.write("data 7")
77
78         cw.start_new_file('thing8.txt')
79         cw.write("data 8")
80
81         cw.start_new_stream('edgecases')
82         for f in ":/.../-/*/ ".split("/"):
83             cw.start_new_file(f)
84             cw.write('x')
85
86         for f in ":/.../-/*/ ".split("/"):
87             cw.start_new_stream('edgecases/dirs/' + f)
88             cw.start_new_file('x/x')
89             cw.write('x')
90
91         self.testcollection = cw.finish()
92         self.api.collections().create(body={"manifest_text":cw.manifest_text()}).execute()
93
94     def runTest(self):
95         self.make_mount(fuse.CollectionDirectory, collection_record=self.testcollection)
96
97         self.assertDirContents(None, ['thing1.txt', 'thing2.txt',
98                                       'edgecases', 'dir1', 'dir2'])
99         self.assertDirContents('dir1', ['thing3.txt', 'thing4.txt'])
100         self.assertDirContents('dir2', ['thing5.txt', 'thing6.txt', 'dir3'])
101         self.assertDirContents('dir2/dir3', ['thing7.txt', 'thing8.txt'])
102         self.assertDirContents('edgecases',
103                                "dirs/:/.../-/*/ ".split("/"))
104         self.assertDirContents('edgecases/dirs',
105                                ":/.../-/*/ ".split("/"))
106
107         files = {'thing1.txt': 'data 1',
108                  'thing2.txt': 'data 2',
109                  'dir1/thing3.txt': 'data 3',
110                  'dir1/thing4.txt': 'data 4',
111                  'dir2/thing5.txt': 'data 5',
112                  'dir2/thing6.txt': 'data 6',
113                  'dir2/dir3/thing7.txt': 'data 7',
114                  'dir2/dir3/thing8.txt': 'data 8'}
115
116         for k, v in files.items():
117             with open(os.path.join(self.mounttmp, k)) as f:
118                 self.assertEqual(v, f.read())
119
120
121 class FuseMagicTest(MountTestBase):
122     def setUp(self, api=None):
123         super(FuseMagicTest, self).setUp(api=api)
124
125         self.test_project = run_test_server.fixture('groups')['aproject']['uuid']
126         self.non_project_group = run_test_server.fixture('groups')['public']['uuid']
127         self.collection_in_test_project = run_test_server.fixture('collections')['foo_collection_in_aproject']['name']
128
129         cw = arvados.CollectionWriter()
130
131         cw.start_new_file('thing1.txt')
132         cw.write("data 1")
133
134         self.testcollection = cw.finish()
135         self.test_manifest = cw.manifest_text()
136         coll = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
137         self.test_manifest_pdh = coll['portable_data_hash']
138
139     def runTest(self):
140         self.make_mount(fuse.MagicDirectory)
141
142         mount_ls = llfuse.listdir(self.mounttmp)
143         self.assertIn('README', mount_ls)
144         self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
145                              arvados.util.uuid_pattern.match(fn)
146                              for fn in mount_ls),
147                          "new FUSE MagicDirectory has no collections or projects")
148         self.assertDirContents(self.testcollection, ['thing1.txt'])
149         self.assertDirContents(os.path.join('by_id', self.testcollection),
150                                ['thing1.txt'])
151         self.assertIn(self.collection_in_test_project,
152                       llfuse.listdir(os.path.join(self.mounttmp, self.test_project)))
153         self.assertIn(self.collection_in_test_project,
154                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.test_project)))
155
156         mount_ls = llfuse.listdir(self.mounttmp)
157         self.assertIn('README', mount_ls)
158         self.assertIn(self.testcollection, mount_ls)
159         self.assertIn(self.testcollection,
160                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
161         self.assertIn(self.test_project, mount_ls)
162         self.assertIn(self.test_project,
163                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
164
165         with self.assertRaises(OSError):
166             llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.non_project_group))
167
168         files = {}
169         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
170
171         for k, v in files.items():
172             with open(os.path.join(self.mounttmp, k)) as f:
173                 self.assertEqual(v, f.read())
174
175
176 class FuseTagsTest(MountTestBase):
177     def runTest(self):
178         self.make_mount(fuse.TagsDirectory)
179
180         d1 = llfuse.listdir(self.mounttmp)
181         d1.sort()
182         self.assertEqual(['foo_tag'], d1)
183
184         d2 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag'))
185         d2.sort()
186         self.assertEqual(['zzzzz-4zz18-fy296fx3hot09f7'], d2)
187
188         d3 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag', 'zzzzz-4zz18-fy296fx3hot09f7'))
189         d3.sort()
190         self.assertEqual(['foo'], d3)
191
192
193 class FuseTagsUpdateTest(MountTestBase):
194     def tag_collection(self, coll_uuid, tag_name):
195         return self.api.links().create(
196             body={'link': {'head_uuid': coll_uuid,
197                            'link_class': 'tag',
198                            'name': tag_name,
199         }}).execute()
200
201     def runTest(self):
202         self.make_mount(fuse.TagsDirectory, poll_time=1)
203
204         self.assertIn('foo_tag', llfuse.listdir(self.mounttmp))
205
206         bar_uuid = run_test_server.fixture('collections')['bar_file']['uuid']
207         self.tag_collection(bar_uuid, 'fuse_test_tag')
208         for attempt in AssertWithTimeout(10):
209             attempt(self.assertIn, 'fuse_test_tag', llfuse.listdir(self.mounttmp))
210         self.assertDirContents('fuse_test_tag', [bar_uuid])
211
212         baz_uuid = run_test_server.fixture('collections')['baz_file']['uuid']
213         l = self.tag_collection(baz_uuid, 'fuse_test_tag')
214         for attempt in AssertWithTimeout(10):
215             attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid, baz_uuid])
216
217         self.api.links().delete(uuid=l['uuid']).execute()
218         for attempt in AssertWithTimeout(10):
219             attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid])
220
221
222 def fuseSharedTestHelper(mounttmp):
223     class Test(unittest.TestCase):
224         def runTest(self):
225             # Double check that we can open and read objects in this folder as a file,
226             # and that its contents are what we expect.
227             baz_path = os.path.join(
228                 mounttmp,
229                 'FUSE User',
230                 'FUSE Test Project',
231                 'collection in FUSE project',
232                 'baz')
233             with open(baz_path) as f:
234                 self.assertEqual("baz", f.read())
235
236             # check mtime on collection
237             st = os.stat(baz_path)
238             try:
239                 mtime = st.st_mtime_ns / 1000000000
240             except AttributeError:
241                 mtime = st.st_mtime
242             self.assertEqual(mtime, 1391448174)
243
244             # shared_dirs is a list of the directories exposed
245             # by fuse.SharedDirectory (i.e. any object visible
246             # to the current user)
247             shared_dirs = llfuse.listdir(mounttmp)
248             shared_dirs.sort()
249             self.assertIn('FUSE User', shared_dirs)
250
251             # fuse_user_objs is a list of the objects owned by the FUSE
252             # test user (which present as files in the 'FUSE User'
253             # directory)
254             fuse_user_objs = llfuse.listdir(os.path.join(mounttmp, 'FUSE User'))
255             fuse_user_objs.sort()
256             self.assertEqual(['FUSE Test Project',                    # project owned by user
257                               'collection #1 owned by FUSE',          # collection owned by user
258                               'collection #2 owned by FUSE'          # collection owned by user
259                           ], fuse_user_objs)
260
261             # test_proj_files is a list of the files in the FUSE Test Project.
262             test_proj_files = llfuse.listdir(os.path.join(mounttmp, 'FUSE User', 'FUSE Test Project'))
263             test_proj_files.sort()
264             self.assertEqual(['collection in FUSE project'
265                           ], test_proj_files)
266
267
268     Test().runTest()
269
270 class FuseSharedTest(MountTestBase):
271     def runTest(self):
272         self.make_mount(fuse.SharedDirectory,
273                         exclude=self.api.users().current().execute()['uuid'])
274         keep = arvados.keep.KeepClient()
275         keep.put("baz")
276
277         self.pool.apply(fuseSharedTestHelper, (self.mounttmp,))
278
279
280 class FuseHomeTest(MountTestBase):
281     def runTest(self):
282         self.make_mount(fuse.ProjectDirectory,
283                         project_object=self.api.users().current().execute())
284
285         d1 = llfuse.listdir(self.mounttmp)
286         self.assertIn('Unrestricted public data', d1)
287
288         d2 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data'))
289         public_project = run_test_server.fixture('groups')[
290             'anonymously_accessible_project']
291         found_in = 0
292         found_not_in = 0
293         for name, item in run_test_server.fixture('collections').iteritems():
294             if 'name' not in item:
295                 pass
296             elif item['owner_uuid'] == public_project['uuid']:
297                 self.assertIn(item['name'], d2)
298                 found_in += 1
299             else:
300                 # Artificial assumption here: there is no public
301                 # collection fixture with the same name as a
302                 # non-public collection.
303                 self.assertNotIn(item['name'], d2)
304                 found_not_in += 1
305         self.assertNotEqual(0, found_in)
306         self.assertNotEqual(0, found_not_in)
307
308         d3 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data', 'GNU General Public License, version 3'))
309         self.assertEqual(["GNU_General_Public_License,_version_3.pdf"], d3)
310
311
312 def fuseModifyFileTestHelperReadStartContents(mounttmp):
313     class Test(unittest.TestCase):
314         def runTest(self):
315             d1 = llfuse.listdir(mounttmp)
316             self.assertEqual(["file1.txt"], d1)
317             with open(os.path.join(mounttmp, "file1.txt")) as f:
318                 self.assertEqual("blub", f.read())
319     Test().runTest()
320
321 def fuseModifyFileTestHelperReadEndContents(mounttmp):
322     class Test(unittest.TestCase):
323         def runTest(self):
324             d1 = llfuse.listdir(mounttmp)
325             self.assertEqual(["file1.txt"], d1)
326             with open(os.path.join(mounttmp, "file1.txt")) as f:
327                 self.assertEqual("plnp", f.read())
328     Test().runTest()
329
330 class FuseModifyFileTest(MountTestBase):
331     def runTest(self):
332         collection = arvados.collection.Collection(api_client=self.api)
333         with collection.open("file1.txt", "w") as f:
334             f.write("blub")
335
336         collection.save_new()
337
338         m = self.make_mount(fuse.CollectionDirectory)
339         with llfuse.lock:
340             m.new_collection(collection.api_response(), collection)
341
342         self.pool.apply(fuseModifyFileTestHelperReadStartContents, (self.mounttmp,))
343
344         with collection.open("file1.txt", "w") as f:
345             f.write("plnp")
346
347         self.pool.apply(fuseModifyFileTestHelperReadEndContents, (self.mounttmp,))
348
349
350 class FuseAddFileToCollectionTest(MountTestBase):
351     def runTest(self):
352         collection = arvados.collection.Collection(api_client=self.api)
353         with collection.open("file1.txt", "w") as f:
354             f.write("blub")
355
356         collection.save_new()
357
358         m = self.make_mount(fuse.CollectionDirectory)
359         with llfuse.lock:
360             m.new_collection(collection.api_response(), collection)
361
362         d1 = llfuse.listdir(self.mounttmp)
363         self.assertEqual(["file1.txt"], d1)
364
365         with collection.open("file2.txt", "w") as f:
366             f.write("plnp")
367
368         d1 = llfuse.listdir(self.mounttmp)
369         self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
370
371
372 class FuseRemoveFileFromCollectionTest(MountTestBase):
373     def runTest(self):
374         collection = arvados.collection.Collection(api_client=self.api)
375         with collection.open("file1.txt", "w") as f:
376             f.write("blub")
377
378         with collection.open("file2.txt", "w") as f:
379             f.write("plnp")
380
381         collection.save_new()
382
383         m = self.make_mount(fuse.CollectionDirectory)
384         with llfuse.lock:
385             m.new_collection(collection.api_response(), collection)
386
387         d1 = llfuse.listdir(self.mounttmp)
388         self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
389
390         collection.remove("file2.txt")
391
392         d1 = llfuse.listdir(self.mounttmp)
393         self.assertEqual(["file1.txt"], d1)
394
395
396 def fuseCreateFileTestHelper(mounttmp):
397     class Test(unittest.TestCase):
398         def runTest(self):
399             with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
400                 pass
401     Test().runTest()
402
403 class FuseCreateFileTest(MountTestBase):
404     def runTest(self):
405         collection = arvados.collection.Collection(api_client=self.api)
406         collection.save_new()
407
408         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
409         self.assertEqual(collection2["manifest_text"], "")
410
411         collection.save_new()
412
413         m = self.make_mount(fuse.CollectionDirectory)
414         with llfuse.lock:
415             m.new_collection(collection.api_response(), collection)
416         self.assertTrue(m.writable())
417
418         self.assertNotIn("file1.txt", collection)
419
420         self.pool.apply(fuseCreateFileTestHelper, (self.mounttmp,))
421
422         self.assertIn("file1.txt", collection)
423
424         d1 = llfuse.listdir(self.mounttmp)
425         self.assertEqual(["file1.txt"], d1)
426
427         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
428         self.assertRegexpMatches(collection2["manifest_text"],
429             r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:file1\.txt$')
430
431
432 def fuseWriteFileTestHelperWriteFile(mounttmp):
433     class Test(unittest.TestCase):
434         def runTest(self):
435             with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
436                 f.write("Hello world!")
437     Test().runTest()
438
439 def fuseWriteFileTestHelperReadFile(mounttmp):
440     class Test(unittest.TestCase):
441         def runTest(self):
442             with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
443                 self.assertEqual(f.read(), "Hello world!")
444     Test().runTest()
445
446 class FuseWriteFileTest(MountTestBase):
447     def runTest(self):
448         collection = arvados.collection.Collection(api_client=self.api)
449         collection.save_new()
450
451         m = self.make_mount(fuse.CollectionDirectory)
452         with llfuse.lock:
453             m.new_collection(collection.api_response(), collection)
454         self.assertTrue(m.writable())
455
456         self.assertNotIn("file1.txt", collection)
457
458         self.assertEqual(0, self.operations.write_counter.get())
459         self.pool.apply(fuseWriteFileTestHelperWriteFile, (self.mounttmp,))
460         self.assertEqual(12, self.operations.write_counter.get())
461
462         with collection.open("file1.txt") as f:
463             self.assertEqual(f.read(), "Hello world!")
464
465         self.assertEqual(0, self.operations.read_counter.get())
466         self.pool.apply(fuseWriteFileTestHelperReadFile, (self.mounttmp,))
467         self.assertEqual(12, self.operations.read_counter.get())
468
469         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
470         self.assertRegexpMatches(collection2["manifest_text"],
471             r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
472
473
474 def fuseUpdateFileTestHelper(mounttmp):
475     class Test(unittest.TestCase):
476         def runTest(self):
477             with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
478                 f.write("Hello world!")
479
480             with open(os.path.join(mounttmp, "file1.txt"), "r+") as f:
481                 fr = f.read()
482                 self.assertEqual(fr, "Hello world!")
483                 f.seek(0)
484                 f.write("Hola mundo!")
485                 f.seek(0)
486                 fr = f.read()
487                 self.assertEqual(fr, "Hola mundo!!")
488
489             with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
490                 self.assertEqual(f.read(), "Hola mundo!!")
491
492     Test().runTest()
493
494 class FuseUpdateFileTest(MountTestBase):
495     def runTest(self):
496         collection = arvados.collection.Collection(api_client=self.api)
497         collection.save_new()
498
499         m = self.make_mount(fuse.CollectionDirectory)
500         with llfuse.lock:
501             m.new_collection(collection.api_response(), collection)
502         self.assertTrue(m.writable())
503
504         # See note in MountTestBase.setUp
505         self.pool.apply(fuseUpdateFileTestHelper, (self.mounttmp,))
506
507         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
508         self.assertRegexpMatches(collection2["manifest_text"],
509             r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A\S+ 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:11:file1\.txt 22:1:file1\.txt$')
510
511
512 def fuseMkdirTestHelper(mounttmp):
513     class Test(unittest.TestCase):
514         def runTest(self):
515             with self.assertRaises(IOError):
516                 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
517                     f.write("Hello world!")
518
519             os.mkdir(os.path.join(mounttmp, "testdir"))
520
521             with self.assertRaises(OSError):
522                 os.mkdir(os.path.join(mounttmp, "testdir"))
523
524             d1 = llfuse.listdir(mounttmp)
525             self.assertEqual(["testdir"], d1)
526
527             with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
528                 f.write("Hello world!")
529
530             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
531             self.assertEqual(["file1.txt"], d1)
532
533     Test().runTest()
534
535 class FuseMkdirTest(MountTestBase):
536     def runTest(self):
537         collection = arvados.collection.Collection(api_client=self.api)
538         collection.save_new()
539
540         m = self.make_mount(fuse.CollectionDirectory)
541         with llfuse.lock:
542             m.new_collection(collection.api_response(), collection)
543         self.assertTrue(m.writable())
544
545         self.pool.apply(fuseMkdirTestHelper, (self.mounttmp,))
546
547         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
548         self.assertRegexpMatches(collection2["manifest_text"],
549             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
550
551
552 def fuseRmTestHelperWriteFile(mounttmp):
553     class Test(unittest.TestCase):
554         def runTest(self):
555             os.mkdir(os.path.join(mounttmp, "testdir"))
556
557             with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
558                 f.write("Hello world!")
559
560     Test().runTest()
561
562 def fuseRmTestHelperDeleteFile(mounttmp):
563     class Test(unittest.TestCase):
564         def runTest(self):
565             # Can't delete because it's not empty
566             with self.assertRaises(OSError):
567                 os.rmdir(os.path.join(mounttmp, "testdir"))
568
569             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
570             self.assertEqual(["file1.txt"], d1)
571
572             # Delete file
573             os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
574
575             # Make sure it's empty
576             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
577             self.assertEqual([], d1)
578
579             # Try to delete it again
580             with self.assertRaises(OSError):
581                 os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
582
583     Test().runTest()
584
585 def fuseRmTestHelperRmdir(mounttmp):
586     class Test(unittest.TestCase):
587         def runTest(self):
588             # Should be able to delete now that it is empty
589             os.rmdir(os.path.join(mounttmp, "testdir"))
590
591             # Make sure it's empty
592             d1 = llfuse.listdir(os.path.join(mounttmp))
593             self.assertEqual([], d1)
594
595             # Try to delete it again
596             with self.assertRaises(OSError):
597                 os.rmdir(os.path.join(mounttmp, "testdir"))
598
599     Test().runTest()
600
601 class FuseRmTest(MountTestBase):
602     def runTest(self):
603         collection = arvados.collection.Collection(api_client=self.api)
604         collection.save_new()
605
606         m = self.make_mount(fuse.CollectionDirectory)
607         with llfuse.lock:
608             m.new_collection(collection.api_response(), collection)
609         self.assertTrue(m.writable())
610
611         self.pool.apply(fuseRmTestHelperWriteFile, (self.mounttmp,))
612
613         # Starting manifest
614         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
615         self.assertRegexpMatches(collection2["manifest_text"],
616             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
617         self.pool.apply(fuseRmTestHelperDeleteFile, (self.mounttmp,))
618
619         # Empty directories are represented by an empty file named "."
620         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
621         self.assertRegexpMatches(collection2["manifest_text"],
622                                  r'./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
623
624         self.pool.apply(fuseRmTestHelperRmdir, (self.mounttmp,))
625
626         # manifest should be empty now.
627         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
628         self.assertEqual(collection2["manifest_text"], "")
629
630
631 def fuseMvFileTestHelperWriteFile(mounttmp):
632     class Test(unittest.TestCase):
633         def runTest(self):
634             os.mkdir(os.path.join(mounttmp, "testdir"))
635
636             with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
637                 f.write("Hello world!")
638
639     Test().runTest()
640
641 def fuseMvFileTestHelperMoveFile(mounttmp):
642     class Test(unittest.TestCase):
643         def runTest(self):
644             d1 = llfuse.listdir(os.path.join(mounttmp))
645             self.assertEqual(["testdir"], d1)
646             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
647             self.assertEqual(["file1.txt"], d1)
648
649             os.rename(os.path.join(mounttmp, "testdir", "file1.txt"), os.path.join(mounttmp, "file1.txt"))
650
651             d1 = llfuse.listdir(os.path.join(mounttmp))
652             self.assertEqual(["file1.txt", "testdir"], sorted(d1))
653             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
654             self.assertEqual([], d1)
655
656     Test().runTest()
657
658 class FuseMvFileTest(MountTestBase):
659     def runTest(self):
660         collection = arvados.collection.Collection(api_client=self.api)
661         collection.save_new()
662
663         m = self.make_mount(fuse.CollectionDirectory)
664         with llfuse.lock:
665             m.new_collection(collection.api_response(), collection)
666         self.assertTrue(m.writable())
667
668         self.pool.apply(fuseMvFileTestHelperWriteFile, (self.mounttmp,))
669
670         # Starting manifest
671         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
672         self.assertRegexpMatches(collection2["manifest_text"],
673             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
674
675         self.pool.apply(fuseMvFileTestHelperMoveFile, (self.mounttmp,))
676
677         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
678         self.assertRegexpMatches(collection2["manifest_text"],
679             r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt\n\./testdir d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:\\056\n')
680
681
682 def fuseRenameTestHelper(mounttmp):
683     class Test(unittest.TestCase):
684         def runTest(self):
685             os.mkdir(os.path.join(mounttmp, "testdir"))
686
687             with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
688                 f.write("Hello world!")
689
690     Test().runTest()
691
692 class FuseRenameTest(MountTestBase):
693     def runTest(self):
694         collection = arvados.collection.Collection(api_client=self.api)
695         collection.save_new()
696
697         m = self.make_mount(fuse.CollectionDirectory)
698         with llfuse.lock:
699             m.new_collection(collection.api_response(), collection)
700         self.assertTrue(m.writable())
701
702         self.pool.apply(fuseRenameTestHelper, (self.mounttmp,))
703
704         # Starting manifest
705         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
706         self.assertRegexpMatches(collection2["manifest_text"],
707             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
708
709         d1 = llfuse.listdir(os.path.join(self.mounttmp))
710         self.assertEqual(["testdir"], d1)
711         d1 = llfuse.listdir(os.path.join(self.mounttmp, "testdir"))
712         self.assertEqual(["file1.txt"], d1)
713
714         os.rename(os.path.join(self.mounttmp, "testdir"), os.path.join(self.mounttmp, "testdir2"))
715
716         d1 = llfuse.listdir(os.path.join(self.mounttmp))
717         self.assertEqual(["testdir2"], sorted(d1))
718         d1 = llfuse.listdir(os.path.join(self.mounttmp, "testdir2"))
719         self.assertEqual(["file1.txt"], d1)
720
721         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
722         self.assertRegexpMatches(collection2["manifest_text"],
723             r'\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
724
725
726 class FuseUpdateFromEventTest(MountTestBase):
727     def runTest(self):
728         collection = arvados.collection.Collection(api_client=self.api)
729         collection.save_new()
730
731         m = self.make_mount(fuse.CollectionDirectory)
732         with llfuse.lock:
733             m.new_collection(collection.api_response(), collection)
734
735         self.operations.listen_for_events()
736
737         d1 = llfuse.listdir(os.path.join(self.mounttmp))
738         self.assertEqual([], sorted(d1))
739
740         with arvados.collection.Collection(collection.manifest_locator(), api_client=self.api) as collection2:
741             with collection2.open("file1.txt", "w") as f:
742                 f.write("foo")
743
744         for attempt in AssertWithTimeout(10):
745             attempt(self.assertEqual, ["file1.txt"], llfuse.listdir(os.path.join(self.mounttmp)))
746
747
748 class FuseDeleteProjectEventTest(MountTestBase):
749     def runTest(self):
750
751         aproject = self.api.groups().create(body={
752             "name": "aproject",
753             "group_class": "project"
754         }).execute()
755
756         bproject = self.api.groups().create(body={
757             "name": "bproject",
758             "group_class": "project",
759             "owner_uuid": aproject["uuid"]
760         }).execute()
761
762         self.make_mount(fuse.ProjectDirectory,
763                         project_object=self.api.users().current().execute())
764
765         self.operations.listen_for_events()
766
767         d1 = llfuse.listdir(os.path.join(self.mounttmp, "aproject"))
768         self.assertEqual(["bproject"], sorted(d1))
769
770         self.api.groups().delete(uuid=bproject["uuid"]).execute()
771
772         for attempt in AssertWithTimeout(10):
773             attempt(self.assertEqual, [], llfuse.listdir(os.path.join(self.mounttmp, "aproject")))
774
775
776 def fuseFileConflictTestHelper(mounttmp):
777     class Test(unittest.TestCase):
778         def runTest(self):
779             with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
780                 f.write("bar")
781
782             d1 = sorted(llfuse.listdir(os.path.join(mounttmp)))
783             self.assertEqual(len(d1), 2)
784
785             with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
786                 self.assertEqual(f.read(), "bar")
787
788             self.assertRegexpMatches(d1[1],
789                 r'file1\.txt~\d\d\d\d\d\d\d\d-\d\d\d\d\d\d~conflict~')
790
791             with open(os.path.join(mounttmp, d1[1]), "r") as f:
792                 self.assertEqual(f.read(), "foo")
793
794     Test().runTest()
795
796 class FuseFileConflictTest(MountTestBase):
797     def runTest(self):
798         collection = arvados.collection.Collection(api_client=self.api)
799         collection.save_new()
800
801         m = self.make_mount(fuse.CollectionDirectory)
802         with llfuse.lock:
803             m.new_collection(collection.api_response(), collection)
804
805         d1 = llfuse.listdir(os.path.join(self.mounttmp))
806         self.assertEqual([], sorted(d1))
807
808         with arvados.collection.Collection(collection.manifest_locator(), api_client=self.api) as collection2:
809             with collection2.open("file1.txt", "w") as f:
810                 f.write("foo")
811
812         # See note in MountTestBase.setUp
813         self.pool.apply(fuseFileConflictTestHelper, (self.mounttmp,))
814
815
816 def fuseUnlinkOpenFileTest(mounttmp):
817     class Test(unittest.TestCase):
818         def runTest(self):
819             with open(os.path.join(mounttmp, "file1.txt"), "w+") as f:
820                 f.write("foo")
821
822                 d1 = llfuse.listdir(os.path.join(mounttmp))
823                 self.assertEqual(["file1.txt"], sorted(d1))
824
825                 os.remove(os.path.join(mounttmp, "file1.txt"))
826
827                 d1 = llfuse.listdir(os.path.join(mounttmp))
828                 self.assertEqual([], sorted(d1))
829
830                 f.seek(0)
831                 self.assertEqual(f.read(), "foo")
832                 f.write("bar")
833
834                 f.seek(0)
835                 self.assertEqual(f.read(), "foobar")
836
837     Test().runTest()
838
839 class FuseUnlinkOpenFileTest(MountTestBase):
840     def runTest(self):
841         collection = arvados.collection.Collection(api_client=self.api)
842         collection.save_new()
843
844         m = self.make_mount(fuse.CollectionDirectory)
845         with llfuse.lock:
846             m.new_collection(collection.api_response(), collection)
847
848         # See note in MountTestBase.setUp
849         self.pool.apply(fuseUnlinkOpenFileTest, (self.mounttmp,))
850
851         self.assertEqual(collection.manifest_text(), "")
852
853
854 def fuseMvFileBetweenCollectionsTest1(mounttmp, uuid1, uuid2):
855     class Test(unittest.TestCase):
856         def runTest(self):
857             with open(os.path.join(mounttmp, uuid1, "file1.txt"), "w") as f:
858                 f.write("Hello world!")
859
860             d1 = os.listdir(os.path.join(mounttmp, uuid1))
861             self.assertEqual(["file1.txt"], sorted(d1))
862             d1 = os.listdir(os.path.join(mounttmp, uuid2))
863             self.assertEqual([], sorted(d1))
864
865     Test().runTest()
866
867 def fuseMvFileBetweenCollectionsTest2(mounttmp, uuid1, uuid2):
868     class Test(unittest.TestCase):
869         def runTest(self):
870             os.rename(os.path.join(mounttmp, uuid1, "file1.txt"), os.path.join(mounttmp, uuid2, "file2.txt"))
871
872             d1 = os.listdir(os.path.join(mounttmp, uuid1))
873             self.assertEqual([], sorted(d1))
874             d1 = os.listdir(os.path.join(mounttmp, uuid2))
875             self.assertEqual(["file2.txt"], sorted(d1))
876
877     Test().runTest()
878
879 class FuseMvFileBetweenCollectionsTest(MountTestBase):
880     def runTest(self):
881         collection1 = arvados.collection.Collection(api_client=self.api)
882         collection1.save_new()
883
884         collection2 = arvados.collection.Collection(api_client=self.api)
885         collection2.save_new()
886
887         m = self.make_mount(fuse.MagicDirectory)
888
889         # See note in MountTestBase.setUp
890         self.pool.apply(fuseMvFileBetweenCollectionsTest1, (self.mounttmp,
891                                                   collection1.manifest_locator(),
892                                                   collection2.manifest_locator()))
893
894         collection1.update()
895         collection2.update()
896
897         self.assertRegexpMatches(collection1.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
898         self.assertEqual(collection2.manifest_text(), "")
899
900         self.pool.apply(fuseMvFileBetweenCollectionsTest2, (self.mounttmp,
901                                                   collection1.manifest_locator(),
902                                                   collection2.manifest_locator()))
903
904         collection1.update()
905         collection2.update()
906
907         self.assertEqual(collection1.manifest_text(), "")
908         self.assertRegexpMatches(collection2.manifest_text(), r"\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file2\.txt$")
909
910         collection1.stop_threads()
911         collection2.stop_threads()
912
913
914 def fuseMvDirBetweenCollectionsTest1(mounttmp, uuid1, uuid2):
915     class Test(unittest.TestCase):
916         def runTest(self):
917             os.mkdir(os.path.join(mounttmp, uuid1, "testdir"))
918             with open(os.path.join(mounttmp, uuid1, "testdir", "file1.txt"), "w") as f:
919                 f.write("Hello world!")
920
921             d1 = os.listdir(os.path.join(mounttmp, uuid1))
922             self.assertEqual(["testdir"], sorted(d1))
923             d1 = os.listdir(os.path.join(mounttmp, uuid1, "testdir"))
924             self.assertEqual(["file1.txt"], sorted(d1))
925
926             d1 = os.listdir(os.path.join(mounttmp, uuid2))
927             self.assertEqual([], sorted(d1))
928
929     Test().runTest()
930
931
932 def fuseMvDirBetweenCollectionsTest2(mounttmp, uuid1, uuid2):
933     class Test(unittest.TestCase):
934         def runTest(self):
935             os.rename(os.path.join(mounttmp, uuid1, "testdir"), os.path.join(mounttmp, uuid2, "testdir2"))
936
937             d1 = os.listdir(os.path.join(mounttmp, uuid1))
938             self.assertEqual([], sorted(d1))
939
940             d1 = os.listdir(os.path.join(mounttmp, uuid2))
941             self.assertEqual(["testdir2"], sorted(d1))
942             d1 = os.listdir(os.path.join(mounttmp, uuid2, "testdir2"))
943             self.assertEqual(["file1.txt"], sorted(d1))
944
945             with open(os.path.join(mounttmp, uuid2, "testdir2", "file1.txt"), "r") as f:
946                 self.assertEqual(f.read(), "Hello world!")
947
948     Test().runTest()
949
950 class FuseMvDirBetweenCollectionsTest(MountTestBase):
951     def runTest(self):
952         collection1 = arvados.collection.Collection(api_client=self.api)
953         collection1.save_new()
954
955         collection2 = arvados.collection.Collection(api_client=self.api)
956         collection2.save_new()
957
958         m = self.make_mount(fuse.MagicDirectory)
959
960         # See note in MountTestBase.setUp
961         self.pool.apply(fuseMvDirBetweenCollectionsTest1, (self.mounttmp,
962                                                   collection1.manifest_locator(),
963                                                   collection2.manifest_locator()))
964
965         collection1.update()
966         collection2.update()
967
968         self.assertRegexpMatches(collection1.manifest_text(), r"\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
969         self.assertEqual(collection2.manifest_text(), "")
970
971         self.pool.apply(fuseMvDirBetweenCollectionsTest2, (self.mounttmp,
972                                                   collection1.manifest_locator(),
973                                                   collection2.manifest_locator()))
974
975         collection1.update()
976         collection2.update()
977
978         self.assertEqual(collection1.manifest_text(), "")
979         self.assertRegexpMatches(collection2.manifest_text(), r"\./testdir2 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$")
980
981         collection1.stop_threads()
982         collection2.stop_threads()
983
984 def fuseProjectMkdirTestHelper1(mounttmp):
985     class Test(unittest.TestCase):
986         def runTest(self):
987             os.mkdir(os.path.join(mounttmp, "testcollection"))
988             with self.assertRaises(OSError):
989                 os.mkdir(os.path.join(mounttmp, "testcollection"))
990     Test().runTest()
991
992 def fuseProjectMkdirTestHelper2(mounttmp):
993     class Test(unittest.TestCase):
994         def runTest(self):
995             with open(os.path.join(mounttmp, "testcollection", "file1.txt"), "w") as f:
996                 f.write("Hello world!")
997             with self.assertRaises(OSError):
998                 os.rmdir(os.path.join(mounttmp, "testcollection"))
999             os.remove(os.path.join(mounttmp, "testcollection", "file1.txt"))
1000             with self.assertRaises(OSError):
1001                 os.remove(os.path.join(mounttmp, "testcollection"))
1002             os.rmdir(os.path.join(mounttmp, "testcollection"))
1003     Test().runTest()
1004
1005 class FuseProjectMkdirRmdirTest(MountTestBase):
1006     def runTest(self):
1007         self.make_mount(fuse.ProjectDirectory,
1008                         project_object=self.api.users().current().execute())
1009
1010         d1 = llfuse.listdir(self.mounttmp)
1011         self.assertNotIn('testcollection', d1)
1012
1013         self.pool.apply(fuseProjectMkdirTestHelper1, (self.mounttmp,))
1014
1015         d1 = llfuse.listdir(self.mounttmp)
1016         self.assertIn('testcollection', d1)
1017
1018         self.pool.apply(fuseProjectMkdirTestHelper2, (self.mounttmp,))
1019
1020         d1 = llfuse.listdir(self.mounttmp)
1021         self.assertNotIn('testcollection', d1)
1022
1023
1024 def fuseProjectMvTestHelper1(mounttmp):
1025     class Test(unittest.TestCase):
1026         def runTest(self):
1027             d1 = llfuse.listdir(mounttmp)
1028             self.assertNotIn('testcollection', d1)
1029
1030             os.mkdir(os.path.join(mounttmp, "testcollection"))
1031
1032             d1 = llfuse.listdir(mounttmp)
1033             self.assertIn('testcollection', d1)
1034
1035             with self.assertRaises(OSError):
1036                 os.rename(os.path.join(mounttmp, "testcollection"), os.path.join(mounttmp, 'Unrestricted public data'))
1037
1038             os.rename(os.path.join(mounttmp, "testcollection"), os.path.join(mounttmp, 'Unrestricted public data', 'testcollection'))
1039
1040             d1 = llfuse.listdir(mounttmp)
1041             self.assertNotIn('testcollection', d1)
1042
1043             d1 = llfuse.listdir(os.path.join(mounttmp, 'Unrestricted public data'))
1044             self.assertIn('testcollection', d1)
1045
1046     Test().runTest()
1047
1048 class FuseProjectMvTest(MountTestBase):
1049     def runTest(self):
1050         self.make_mount(fuse.ProjectDirectory,
1051                         project_object=self.api.users().current().execute())
1052
1053         self.pool.apply(fuseProjectMvTestHelper1, (self.mounttmp,))
1054
1055
1056 def fuseFsyncTestHelper(mounttmp, k):
1057     class Test(unittest.TestCase):
1058         def runTest(self):
1059             fd = os.open(os.path.join(mounttmp, k), os.O_RDONLY)
1060             os.fsync(fd)
1061             os.close(fd)
1062
1063     Test().runTest()
1064
1065 class FuseFsyncTest(FuseMagicTest):
1066     def runTest(self):
1067         self.make_mount(fuse.MagicDirectory)
1068         self.pool.apply(fuseFsyncTestHelper, (self.mounttmp, self.testcollection))
1069
1070
1071 class MagicDirApiError(FuseMagicTest):
1072     def setUp(self):
1073         api = mock.MagicMock()
1074         super(MagicDirApiError, self).setUp(api=api)
1075         api.collections().get().execute.side_effect = iter([
1076             Exception('API fail'),
1077             {
1078                 "manifest_text": self.test_manifest,
1079                 "portable_data_hash": self.test_manifest_pdh,
1080             },
1081         ])
1082         api.keep.get.side_effect = Exception('Keep fail')
1083
1084     def runTest(self):
1085         self.make_mount(fuse.MagicDirectory)
1086
1087         self.operations.inodes.inode_cache.cap = 1
1088         self.operations.inodes.inode_cache.min_entries = 2
1089
1090         with self.assertRaises(OSError):
1091             llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1092
1093         llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1094
1095
1096 class FuseUnitTest(unittest.TestCase):
1097     def test_sanitize_filename(self):
1098         acceptable = [
1099             "foo.txt",
1100             ".foo",
1101             "..foo",
1102             "...",
1103             "foo...",
1104             "foo..",
1105             "foo.",
1106             "-",
1107             "\x01\x02\x03",
1108             ]
1109         unacceptable = [
1110             "f\00",
1111             "\00\00",
1112             "/foo",
1113             "foo/",
1114             "//",
1115             ]
1116         for f in acceptable:
1117             self.assertEqual(f, fuse.sanitize_filename(f))
1118         for f in unacceptable:
1119             self.assertNotEqual(f, fuse.sanitize_filename(f))
1120             # The sanitized filename should be the same length, though.
1121             self.assertEqual(len(f), len(fuse.sanitize_filename(f)))
1122         # Special cases
1123         self.assertEqual("_", fuse.sanitize_filename(""))
1124         self.assertEqual("_", fuse.sanitize_filename("."))
1125         self.assertEqual("__", fuse.sanitize_filename(".."))
1126
1127
1128 class FuseMagicTestPDHOnly(MountTestBase):
1129     def setUp(self, api=None):
1130         super(FuseMagicTestPDHOnly, self).setUp(api=api)
1131
1132         cw = arvados.CollectionWriter()
1133
1134         cw.start_new_file('thing1.txt')
1135         cw.write("data 1")
1136
1137         self.testcollection = cw.finish()
1138         self.test_manifest = cw.manifest_text()
1139         created = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
1140         self.testcollectionuuid = str(created['uuid'])
1141
1142     def verify_pdh_only(self, pdh_only=False, skip_pdh_only=False):
1143         if skip_pdh_only is True:
1144             self.make_mount(fuse.MagicDirectory)    # in this case, the default by_id applies
1145         else:
1146             self.make_mount(fuse.MagicDirectory, pdh_only=pdh_only)
1147
1148         mount_ls = llfuse.listdir(self.mounttmp)
1149         self.assertIn('README', mount_ls)
1150         self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
1151                              arvados.util.uuid_pattern.match(fn)
1152                              for fn in mount_ls),
1153                          "new FUSE MagicDirectory lists Collection")
1154
1155         # look up using pdh should succeed in all cases
1156         self.assertDirContents(self.testcollection, ['thing1.txt'])
1157         self.assertDirContents(os.path.join('by_id', self.testcollection),
1158                                ['thing1.txt'])
1159         mount_ls = llfuse.listdir(self.mounttmp)
1160         self.assertIn('README', mount_ls)
1161         self.assertIn(self.testcollection, mount_ls)
1162         self.assertIn(self.testcollection,
1163                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
1164
1165         files = {}
1166         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
1167
1168         for k, v in files.items():
1169             with open(os.path.join(self.mounttmp, k)) as f:
1170                 self.assertEqual(v, f.read())
1171
1172         # look up using uuid should fail when pdh_only is set
1173         if pdh_only is True:
1174             with self.assertRaises(OSError):
1175                 self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1176                                ['thing1.txt'])
1177         else:
1178             self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1179                                ['thing1.txt'])
1180
1181     def test_with_pdh_only_true(self):
1182         self.verify_pdh_only(pdh_only=True)
1183
1184     def test_with_pdh_only_false(self):
1185         self.verify_pdh_only(pdh_only=False)
1186
1187     def test_with_default_by_id(self):
1188         self.verify_pdh_only(skip_pdh_only=True)