Merge branch '12462-search-periods'
[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 import json
6 import llfuse
7 import logging
8 import mock
9 import os
10 import subprocess
11 import time
12 import unittest
13
14 import arvados
15 import arvados_fuse as fuse
16 import run_test_server
17
18 from mount_test_base import MountTestBase
19
20 logger = logging.getLogger('arvados.arv-mount')
21
22
23 class AssertWithTimeout(object):
24     """Allow some time for an assertion to pass."""
25
26     def __init__(self, timeout=0):
27         self.timeout = timeout
28
29     def __iter__(self):
30         self.deadline = time.time() + self.timeout
31         self.done = False
32         return self
33
34     def next(self):
35         if self.done:
36             raise StopIteration
37         return self.attempt
38
39     def attempt(self, fn, *args, **kwargs):
40         try:
41             fn(*args, **kwargs)
42         except AssertionError:
43             if time.time() > self.deadline:
44                 raise
45             time.sleep(0.1)
46         else:
47             self.done = True
48
49
50 class FuseMountTest(MountTestBase):
51     def setUp(self):
52         super(FuseMountTest, self).setUp()
53
54         cw = arvados.CollectionWriter()
55
56         cw.start_new_file('thing1.txt')
57         cw.write("data 1")
58         cw.start_new_file('thing2.txt')
59         cw.write("data 2")
60
61         cw.start_new_stream('dir1')
62         cw.start_new_file('thing3.txt')
63         cw.write("data 3")
64         cw.start_new_file('thing4.txt')
65         cw.write("data 4")
66
67         cw.start_new_stream('dir2')
68         cw.start_new_file('thing5.txt')
69         cw.write("data 5")
70         cw.start_new_file('thing6.txt')
71         cw.write("data 6")
72
73         cw.start_new_stream('dir2/dir3')
74         cw.start_new_file('thing7.txt')
75         cw.write("data 7")
76
77         cw.start_new_file('thing8.txt')
78         cw.write("data 8")
79
80         cw.start_new_stream('edgecases')
81         for f in ":/.../-/*/\x01\\/ ".split("/"):
82             cw.start_new_file(f)
83             cw.write('x')
84
85         for f in ":/.../-/*/\x01\\/ ".split("/"):
86             cw.start_new_stream('edgecases/dirs/' + f)
87             cw.start_new_file('x/x')
88             cw.write('x')
89
90         self.testcollection = cw.finish()
91         self.api.collections().create(body={"manifest_text":cw.manifest_text()}).execute()
92
93     def runTest(self):
94         self.make_mount(fuse.CollectionDirectory, collection_record=self.testcollection)
95
96         self.assertDirContents(None, ['thing1.txt', 'thing2.txt',
97                                       'edgecases', 'dir1', 'dir2'])
98         self.assertDirContents('dir1', ['thing3.txt', 'thing4.txt'])
99         self.assertDirContents('dir2', ['thing5.txt', 'thing6.txt', 'dir3'])
100         self.assertDirContents('dir2/dir3', ['thing7.txt', 'thing8.txt'])
101         self.assertDirContents('edgecases',
102                                "dirs/:/.../-/*/\x01\\/ ".split("/"))
103         self.assertDirContents('edgecases/dirs',
104                                ":/.../-/*/\x01\\/ ".split("/"))
105
106         files = {'thing1.txt': 'data 1',
107                  'thing2.txt': 'data 2',
108                  'dir1/thing3.txt': 'data 3',
109                  'dir1/thing4.txt': 'data 4',
110                  'dir2/thing5.txt': 'data 5',
111                  'dir2/thing6.txt': 'data 6',
112                  'dir2/dir3/thing7.txt': 'data 7',
113                  'dir2/dir3/thing8.txt': 'data 8'}
114
115         for k, v in files.items():
116             with open(os.path.join(self.mounttmp, k)) as f:
117                 self.assertEqual(v, f.read())
118
119
120 class FuseNoAPITest(MountTestBase):
121     def setUp(self):
122         super(FuseNoAPITest, self).setUp()
123         keep = arvados.keep.KeepClient(local_store=self.keeptmp)
124         self.file_data = "API-free text\n"
125         self.file_loc = keep.put(self.file_data)
126         self.coll_loc = keep.put(". {} 0:{}:api-free.txt\n".format(
127                 self.file_loc, len(self.file_data)))
128
129     def runTest(self):
130         self.make_mount(fuse.MagicDirectory)
131         self.assertDirContents(self.coll_loc, ['api-free.txt'])
132         with open(os.path.join(
133                 self.mounttmp, self.coll_loc, 'api-free.txt')) as keep_file:
134             actual = keep_file.read(-1)
135         self.assertEqual(self.file_data, actual)
136
137
138 class FuseMagicTest(MountTestBase):
139     def setUp(self, api=None):
140         super(FuseMagicTest, self).setUp(api=api)
141
142         cw = arvados.CollectionWriter()
143
144         cw.start_new_file('thing1.txt')
145         cw.write("data 1")
146
147         self.testcollection = cw.finish()
148         self.test_manifest = cw.manifest_text()
149         self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
150
151     def runTest(self):
152         self.make_mount(fuse.MagicDirectory)
153
154         mount_ls = llfuse.listdir(self.mounttmp)
155         self.assertIn('README', mount_ls)
156         self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
157                              arvados.util.uuid_pattern.match(fn)
158                              for fn in mount_ls),
159                          "new FUSE MagicDirectory lists Collection")
160         self.assertDirContents(self.testcollection, ['thing1.txt'])
161         self.assertDirContents(os.path.join('by_id', self.testcollection),
162                                ['thing1.txt'])
163         mount_ls = llfuse.listdir(self.mounttmp)
164         self.assertIn('README', mount_ls)
165         self.assertIn(self.testcollection, mount_ls)
166         self.assertIn(self.testcollection,
167                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
168
169         files = {}
170         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
171
172         for k, v in files.items():
173             with open(os.path.join(self.mounttmp, k)) as f:
174                 self.assertEqual(v, f.read())
175
176
177 class FuseTagsTest(MountTestBase):
178     def runTest(self):
179         self.make_mount(fuse.TagsDirectory)
180
181         d1 = llfuse.listdir(self.mounttmp)
182         d1.sort()
183         self.assertEqual(['foo_tag'], d1)
184
185         d2 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag'))
186         d2.sort()
187         self.assertEqual(['zzzzz-4zz18-fy296fx3hot09f7'], d2)
188
189         d3 = llfuse.listdir(os.path.join(self.mounttmp, 'foo_tag', 'zzzzz-4zz18-fy296fx3hot09f7'))
190         d3.sort()
191         self.assertEqual(['foo'], d3)
192
193
194 class FuseTagsUpdateTest(MountTestBase):
195     def tag_collection(self, coll_uuid, tag_name):
196         return self.api.links().create(
197             body={'link': {'head_uuid': coll_uuid,
198                            'link_class': 'tag',
199                            'name': tag_name,
200         }}).execute()
201
202     def runTest(self):
203         self.make_mount(fuse.TagsDirectory, poll_time=1)
204
205         self.assertIn('foo_tag', llfuse.listdir(self.mounttmp))
206
207         bar_uuid = run_test_server.fixture('collections')['bar_file']['uuid']
208         self.tag_collection(bar_uuid, 'fuse_test_tag')
209         for attempt in AssertWithTimeout(10):
210             attempt(self.assertIn, 'fuse_test_tag', llfuse.listdir(self.mounttmp))
211         self.assertDirContents('fuse_test_tag', [bar_uuid])
212
213         baz_uuid = run_test_server.fixture('collections')['baz_file']['uuid']
214         l = self.tag_collection(baz_uuid, 'fuse_test_tag')
215         for attempt in AssertWithTimeout(10):
216             attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid, baz_uuid])
217
218         self.api.links().delete(uuid=l['uuid']).execute()
219         for attempt in AssertWithTimeout(10):
220             attempt(self.assertDirContents, 'fuse_test_tag', [bar_uuid])
221
222
223 def fuseSharedTestHelper(mounttmp):
224     class Test(unittest.TestCase):
225         def runTest(self):
226             # Double check that we can open and read objects in this folder as a file,
227             # and that its contents are what we expect.
228             baz_path = os.path.join(
229                 mounttmp,
230                 'FUSE User',
231                 'FUSE Test Project',
232                 'collection in FUSE project',
233                 'baz')
234             with open(baz_path) as f:
235                 self.assertEqual("baz", f.read())
236
237             # check mtime on collection
238             st = os.stat(baz_path)
239             try:
240                 mtime = st.st_mtime_ns / 1000000000
241             except AttributeError:
242                 mtime = st.st_mtime
243             self.assertEqual(mtime, 1391448174)
244
245             # shared_dirs is a list of the directories exposed
246             # by fuse.SharedDirectory (i.e. any object visible
247             # to the current user)
248             shared_dirs = llfuse.listdir(mounttmp)
249             shared_dirs.sort()
250             self.assertIn('FUSE User', shared_dirs)
251
252             # fuse_user_objs is a list of the objects owned by the FUSE
253             # test user (which present as files in the 'FUSE User'
254             # directory)
255             fuse_user_objs = llfuse.listdir(os.path.join(mounttmp, 'FUSE User'))
256             fuse_user_objs.sort()
257             self.assertEqual(['FUSE Test Project',                    # project owned by user
258                               'collection #1 owned by FUSE',          # collection owned by user
259                               'collection #2 owned by FUSE'          # collection owned by user
260                           ], fuse_user_objs)
261
262             # test_proj_files is a list of the files in the FUSE Test Project.
263             test_proj_files = llfuse.listdir(os.path.join(mounttmp, 'FUSE User', 'FUSE Test Project'))
264             test_proj_files.sort()
265             self.assertEqual(['collection in FUSE project'
266                           ], test_proj_files)
267
268
269     Test().runTest()
270
271 class FuseSharedTest(MountTestBase):
272     def runTest(self):
273         self.make_mount(fuse.SharedDirectory,
274                         exclude=self.api.users().current().execute()['uuid'])
275         keep = arvados.keep.KeepClient()
276         keep.put("baz")
277
278         self.pool.apply(fuseSharedTestHelper, (self.mounttmp,))
279
280
281 class FuseHomeTest(MountTestBase):
282     def runTest(self):
283         self.make_mount(fuse.ProjectDirectory,
284                         project_object=self.api.users().current().execute())
285
286         d1 = llfuse.listdir(self.mounttmp)
287         self.assertIn('Unrestricted public data', d1)
288
289         d2 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data'))
290         public_project = run_test_server.fixture('groups')[
291             'anonymously_accessible_project']
292         found_in = 0
293         found_not_in = 0
294         for name, item in run_test_server.fixture('collections').iteritems():
295             if 'name' not in item:
296                 pass
297             elif item['owner_uuid'] == public_project['uuid']:
298                 self.assertIn(item['name'], d2)
299                 found_in += 1
300             else:
301                 # Artificial assumption here: there is no public
302                 # collection fixture with the same name as a
303                 # non-public collection.
304                 self.assertNotIn(item['name'], d2)
305                 found_not_in += 1
306         self.assertNotEqual(0, found_in)
307         self.assertNotEqual(0, found_not_in)
308
309         d3 = llfuse.listdir(os.path.join(self.mounttmp, 'Unrestricted public data', 'GNU General Public License, version 3'))
310         self.assertEqual(["GNU_General_Public_License,_version_3.pdf"], d3)
311
312
313 def fuseModifyFileTestHelperReadStartContents(mounttmp):
314     class Test(unittest.TestCase):
315         def runTest(self):
316             d1 = llfuse.listdir(mounttmp)
317             self.assertEqual(["file1.txt"], d1)
318             with open(os.path.join(mounttmp, "file1.txt")) as f:
319                 self.assertEqual("blub", f.read())
320     Test().runTest()
321
322 def fuseModifyFileTestHelperReadEndContents(mounttmp):
323     class Test(unittest.TestCase):
324         def runTest(self):
325             d1 = llfuse.listdir(mounttmp)
326             self.assertEqual(["file1.txt"], d1)
327             with open(os.path.join(mounttmp, "file1.txt")) as f:
328                 self.assertEqual("plnp", f.read())
329     Test().runTest()
330
331 class FuseModifyFileTest(MountTestBase):
332     def runTest(self):
333         collection = arvados.collection.Collection(api_client=self.api)
334         with collection.open("file1.txt", "w") as f:
335             f.write("blub")
336
337         collection.save_new()
338
339         m = self.make_mount(fuse.CollectionDirectory)
340         with llfuse.lock:
341             m.new_collection(collection.api_response(), collection)
342
343         self.pool.apply(fuseModifyFileTestHelperReadStartContents, (self.mounttmp,))
344
345         with collection.open("file1.txt", "w") as f:
346             f.write("plnp")
347
348         self.pool.apply(fuseModifyFileTestHelperReadEndContents, (self.mounttmp,))
349
350
351 class FuseAddFileToCollectionTest(MountTestBase):
352     def runTest(self):
353         collection = arvados.collection.Collection(api_client=self.api)
354         with collection.open("file1.txt", "w") as f:
355             f.write("blub")
356
357         collection.save_new()
358
359         m = self.make_mount(fuse.CollectionDirectory)
360         with llfuse.lock:
361             m.new_collection(collection.api_response(), collection)
362
363         d1 = llfuse.listdir(self.mounttmp)
364         self.assertEqual(["file1.txt"], d1)
365
366         with collection.open("file2.txt", "w") as f:
367             f.write("plnp")
368
369         d1 = llfuse.listdir(self.mounttmp)
370         self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
371
372
373 class FuseRemoveFileFromCollectionTest(MountTestBase):
374     def runTest(self):
375         collection = arvados.collection.Collection(api_client=self.api)
376         with collection.open("file1.txt", "w") as f:
377             f.write("blub")
378
379         with collection.open("file2.txt", "w") as f:
380             f.write("plnp")
381
382         collection.save_new()
383
384         m = self.make_mount(fuse.CollectionDirectory)
385         with llfuse.lock:
386             m.new_collection(collection.api_response(), collection)
387
388         d1 = llfuse.listdir(self.mounttmp)
389         self.assertEqual(["file1.txt", "file2.txt"], sorted(d1))
390
391         collection.remove("file2.txt")
392
393         d1 = llfuse.listdir(self.mounttmp)
394         self.assertEqual(["file1.txt"], d1)
395
396
397 def fuseCreateFileTestHelper(mounttmp):
398     class Test(unittest.TestCase):
399         def runTest(self):
400             with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
401                 pass
402     Test().runTest()
403
404 class FuseCreateFileTest(MountTestBase):
405     def runTest(self):
406         collection = arvados.collection.Collection(api_client=self.api)
407         collection.save_new()
408
409         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
410         self.assertEqual(collection2["manifest_text"], "")
411
412         collection.save_new()
413
414         m = self.make_mount(fuse.CollectionDirectory)
415         with llfuse.lock:
416             m.new_collection(collection.api_response(), collection)
417         self.assertTrue(m.writable())
418
419         self.assertNotIn("file1.txt", collection)
420
421         self.pool.apply(fuseCreateFileTestHelper, (self.mounttmp,))
422
423         self.assertIn("file1.txt", collection)
424
425         d1 = llfuse.listdir(self.mounttmp)
426         self.assertEqual(["file1.txt"], d1)
427
428         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
429         self.assertRegexpMatches(collection2["manifest_text"],
430             r'\. d41d8cd98f00b204e9800998ecf8427e\+0\+A\S+ 0:0:file1\.txt$')
431
432
433 def fuseWriteFileTestHelperWriteFile(mounttmp):
434     class Test(unittest.TestCase):
435         def runTest(self):
436             with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
437                 f.write("Hello world!")
438     Test().runTest()
439
440 def fuseWriteFileTestHelperReadFile(mounttmp):
441     class Test(unittest.TestCase):
442         def runTest(self):
443             with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
444                 self.assertEqual(f.read(), "Hello world!")
445     Test().runTest()
446
447 class FuseWriteFileTest(MountTestBase):
448     def runTest(self):
449         collection = arvados.collection.Collection(api_client=self.api)
450         collection.save_new()
451
452         m = self.make_mount(fuse.CollectionDirectory)
453         with llfuse.lock:
454             m.new_collection(collection.api_response(), collection)
455         self.assertTrue(m.writable())
456
457         self.assertNotIn("file1.txt", collection)
458
459         self.assertEqual(0, self.operations.write_counter.get())
460         self.pool.apply(fuseWriteFileTestHelperWriteFile, (self.mounttmp,))
461         self.assertEqual(12, self.operations.write_counter.get())
462
463         with collection.open("file1.txt") as f:
464             self.assertEqual(f.read(), "Hello world!")
465
466         self.assertEqual(0, self.operations.read_counter.get())
467         self.pool.apply(fuseWriteFileTestHelperReadFile, (self.mounttmp,))
468         self.assertEqual(12, self.operations.read_counter.get())
469
470         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
471         self.assertRegexpMatches(collection2["manifest_text"],
472             r'\. 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
473
474
475 def fuseUpdateFileTestHelper(mounttmp):
476     class Test(unittest.TestCase):
477         def runTest(self):
478             with open(os.path.join(mounttmp, "file1.txt"), "w") as f:
479                 f.write("Hello world!")
480
481             with open(os.path.join(mounttmp, "file1.txt"), "r+") as f:
482                 fr = f.read()
483                 self.assertEqual(fr, "Hello world!")
484                 f.seek(0)
485                 f.write("Hola mundo!")
486                 f.seek(0)
487                 fr = f.read()
488                 self.assertEqual(fr, "Hola mundo!!")
489
490             with open(os.path.join(mounttmp, "file1.txt"), "r") as f:
491                 self.assertEqual(f.read(), "Hola mundo!!")
492
493     Test().runTest()
494
495 class FuseUpdateFileTest(MountTestBase):
496     def runTest(self):
497         collection = arvados.collection.Collection(api_client=self.api)
498         collection.save_new()
499
500         m = self.make_mount(fuse.CollectionDirectory)
501         with llfuse.lock:
502             m.new_collection(collection.api_response(), collection)
503         self.assertTrue(m.writable())
504
505         # See note in MountTestBase.setUp
506         self.pool.apply(fuseUpdateFileTestHelper, (self.mounttmp,))
507
508         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
509         self.assertRegexpMatches(collection2["manifest_text"],
510             r'\. daaef200ebb921e011e3ae922dd3266b\+11\+A\S+ 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:11:file1\.txt 22:1:file1\.txt$')
511
512
513 def fuseMkdirTestHelper(mounttmp):
514     class Test(unittest.TestCase):
515         def runTest(self):
516             with self.assertRaises(IOError):
517                 with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
518                     f.write("Hello world!")
519
520             os.mkdir(os.path.join(mounttmp, "testdir"))
521
522             with self.assertRaises(OSError):
523                 os.mkdir(os.path.join(mounttmp, "testdir"))
524
525             d1 = llfuse.listdir(mounttmp)
526             self.assertEqual(["testdir"], d1)
527
528             with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
529                 f.write("Hello world!")
530
531             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
532             self.assertEqual(["file1.txt"], d1)
533
534     Test().runTest()
535
536 class FuseMkdirTest(MountTestBase):
537     def runTest(self):
538         collection = arvados.collection.Collection(api_client=self.api)
539         collection.save_new()
540
541         m = self.make_mount(fuse.CollectionDirectory)
542         with llfuse.lock:
543             m.new_collection(collection.api_response(), collection)
544         self.assertTrue(m.writable())
545
546         self.pool.apply(fuseMkdirTestHelper, (self.mounttmp,))
547
548         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
549         self.assertRegexpMatches(collection2["manifest_text"],
550             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
551
552
553 def fuseRmTestHelperWriteFile(mounttmp):
554     class Test(unittest.TestCase):
555         def runTest(self):
556             os.mkdir(os.path.join(mounttmp, "testdir"))
557
558             with open(os.path.join(mounttmp, "testdir", "file1.txt"), "w") as f:
559                 f.write("Hello world!")
560
561     Test().runTest()
562
563 def fuseRmTestHelperDeleteFile(mounttmp):
564     class Test(unittest.TestCase):
565         def runTest(self):
566             # Can't delete because it's not empty
567             with self.assertRaises(OSError):
568                 os.rmdir(os.path.join(mounttmp, "testdir"))
569
570             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
571             self.assertEqual(["file1.txt"], d1)
572
573             # Delete file
574             os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
575
576             # Make sure it's empty
577             d1 = llfuse.listdir(os.path.join(mounttmp, "testdir"))
578             self.assertEqual([], d1)
579
580             # Try to delete it again
581             with self.assertRaises(OSError):
582                 os.remove(os.path.join(mounttmp, "testdir", "file1.txt"))
583
584     Test().runTest()
585
586 def fuseRmTestHelperRmdir(mounttmp):
587     class Test(unittest.TestCase):
588         def runTest(self):
589             # Should be able to delete now that it is empty
590             os.rmdir(os.path.join(mounttmp, "testdir"))
591
592             # Make sure it's empty
593             d1 = llfuse.listdir(os.path.join(mounttmp))
594             self.assertEqual([], d1)
595
596             # Try to delete it again
597             with self.assertRaises(OSError):
598                 os.rmdir(os.path.join(mounttmp, "testdir"))
599
600     Test().runTest()
601
602 class FuseRmTest(MountTestBase):
603     def runTest(self):
604         collection = arvados.collection.Collection(api_client=self.api)
605         collection.save_new()
606
607         m = self.make_mount(fuse.CollectionDirectory)
608         with llfuse.lock:
609             m.new_collection(collection.api_response(), collection)
610         self.assertTrue(m.writable())
611
612         self.pool.apply(fuseRmTestHelperWriteFile, (self.mounttmp,))
613
614         # Starting manifest
615         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
616         self.assertRegexpMatches(collection2["manifest_text"],
617             r'\./testdir 86fb269d190d2c85f6e0468ceca42a20\+12\+A\S+ 0:12:file1\.txt$')
618         self.pool.apply(fuseRmTestHelperDeleteFile, (self.mounttmp,))
619
620         # Can't have empty directories :-( so manifest will be empty.
621         collection2 = self.api.collections().get(uuid=collection.manifest_locator()).execute()
622         self.assertEqual(collection2["manifest_text"], "")
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$')
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([Exception('API fail'), {"manifest_text": self.test_manifest}])
1076         api.keep.get.side_effect = Exception('Keep fail')
1077
1078     def runTest(self):
1079         self.make_mount(fuse.MagicDirectory)
1080
1081         self.operations.inodes.inode_cache.cap = 1
1082         self.operations.inodes.inode_cache.min_entries = 2
1083
1084         with self.assertRaises(OSError):
1085             llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1086
1087         llfuse.listdir(os.path.join(self.mounttmp, self.testcollection))
1088
1089
1090 class FuseUnitTest(unittest.TestCase):
1091     def test_sanitize_filename(self):
1092         acceptable = [
1093             "foo.txt",
1094             ".foo",
1095             "..foo",
1096             "...",
1097             "foo...",
1098             "foo..",
1099             "foo.",
1100             "-",
1101             "\x01\x02\x03",
1102             ]
1103         unacceptable = [
1104             "f\00",
1105             "\00\00",
1106             "/foo",
1107             "foo/",
1108             "//",
1109             ]
1110         for f in acceptable:
1111             self.assertEqual(f, fuse.sanitize_filename(f))
1112         for f in unacceptable:
1113             self.assertNotEqual(f, fuse.sanitize_filename(f))
1114             # The sanitized filename should be the same length, though.
1115             self.assertEqual(len(f), len(fuse.sanitize_filename(f)))
1116         # Special cases
1117         self.assertEqual("_", fuse.sanitize_filename(""))
1118         self.assertEqual("_", fuse.sanitize_filename("."))
1119         self.assertEqual("__", fuse.sanitize_filename(".."))
1120
1121
1122 class FuseMagicTestPDHOnly(MountTestBase):
1123     def setUp(self, api=None):
1124         super(FuseMagicTestPDHOnly, self).setUp(api=api)
1125
1126         cw = arvados.CollectionWriter()
1127
1128         cw.start_new_file('thing1.txt')
1129         cw.write("data 1")
1130
1131         self.testcollection = cw.finish()
1132         self.test_manifest = cw.manifest_text()
1133         created = self.api.collections().create(body={"manifest_text":self.test_manifest}).execute()
1134         self.testcollectionuuid = str(created['uuid'])
1135
1136     def verify_pdh_only(self, pdh_only=False, skip_pdh_only=False):
1137         if skip_pdh_only is True:
1138             self.make_mount(fuse.MagicDirectory)    # in this case, the default by_id applies
1139         else:
1140             self.make_mount(fuse.MagicDirectory, pdh_only=pdh_only)
1141
1142         mount_ls = llfuse.listdir(self.mounttmp)
1143         self.assertIn('README', mount_ls)
1144         self.assertFalse(any(arvados.util.keep_locator_pattern.match(fn) or
1145                              arvados.util.uuid_pattern.match(fn)
1146                              for fn in mount_ls),
1147                          "new FUSE MagicDirectory lists Collection")
1148
1149         # look up using pdh should succeed in all cases
1150         self.assertDirContents(self.testcollection, ['thing1.txt'])
1151         self.assertDirContents(os.path.join('by_id', self.testcollection),
1152                                ['thing1.txt'])
1153         mount_ls = llfuse.listdir(self.mounttmp)
1154         self.assertIn('README', mount_ls)
1155         self.assertIn(self.testcollection, mount_ls)
1156         self.assertIn(self.testcollection,
1157                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
1158
1159         files = {}
1160         files[os.path.join(self.mounttmp, self.testcollection, 'thing1.txt')] = 'data 1'
1161
1162         for k, v in files.items():
1163             with open(os.path.join(self.mounttmp, k)) as f:
1164                 self.assertEqual(v, f.read())
1165
1166         # look up using uuid should fail when pdh_only is set
1167         if pdh_only is True:
1168             with self.assertRaises(OSError):
1169                 self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1170                                ['thing1.txt'])
1171         else:
1172             self.assertDirContents(os.path.join('by_id', self.testcollectionuuid),
1173                                ['thing1.txt'])
1174
1175     def test_with_pdh_only_true(self):
1176         self.verify_pdh_only(pdh_only=True)
1177
1178     def test_with_pdh_only_false(self):
1179         self.verify_pdh_only(pdh_only=False)
1180
1181     def test_with_default_by_id(self):
1182         self.verify_pdh_only(skip_pdh_only=True)