11209: Test using ./bin/arv-mount from source dir.
[arvados.git] / services / fuse / tests / test_command_args.py
1 import arvados
2 import arvados_fuse
3 import arvados_fuse.command
4 import contextlib
5 import functools
6 import io
7 import json
8 import llfuse
9 import logging
10 import mock
11 import os
12 import run_test_server
13 import sys
14 import tempfile
15 import unittest
16
17 def noexit(func):
18     """If argparse or arvados_fuse tries to exit, fail the test instead"""
19     class SystemExitCaught(StandardError):
20         pass
21     @functools.wraps(func)
22     def wrapper(*args, **kwargs):
23         try:
24             return func(*args, **kwargs)
25         except SystemExit:
26             raise SystemExitCaught
27     return wrapper
28
29 @contextlib.contextmanager
30 def nostderr():
31     orig, sys.stderr = sys.stderr, open(os.devnull, 'w')
32     try:
33         yield
34     finally:
35         sys.stderr = orig
36
37
38 class MountArgsTest(unittest.TestCase):
39     def setUp(self):
40         self.mntdir = tempfile.mkdtemp()
41         run_test_server.authorize_with('active')
42
43     def tearDown(self):
44         os.rmdir(self.mntdir)
45
46     def lookup(self, mnt, *path):
47         ent = mnt.operations.inodes[llfuse.ROOT_INODE]
48         for p in path:
49             ent = ent[p]
50         return ent
51
52     @contextlib.contextmanager
53     def stderrMatches(self, stderr):
54         orig, sys.stderr = sys.stderr, stderr
55         try:
56             yield
57         finally:
58             sys.stderr = orig
59
60     def check_ent_type(self, cls, *path):
61         ent = self.lookup(self.mnt, *path)
62         self.assertEqual(ent.__class__, cls)
63         return ent
64
65     @noexit
66     def test_default_all(self):
67         args = arvados_fuse.command.ArgumentParser().parse_args([
68             '--foreground', self.mntdir])
69         self.assertEqual(args.mode, None)
70         self.mnt = arvados_fuse.command.Mount(args)
71         e = self.check_ent_type(arvados_fuse.ProjectDirectory, 'home')
72         self.assertEqual(e.project_object['uuid'],
73                          run_test_server.fixture('users')['active']['uuid'])
74         e = self.check_ent_type(arvados_fuse.MagicDirectory, 'by_id')
75
76         e = self.check_ent_type(arvados_fuse.StringFile, 'README')
77         readme = e.readfrom(0, -1)
78         self.assertRegexpMatches(readme, r'active-user@arvados\.local')
79         self.assertRegexpMatches(readme, r'\n$')
80
81         e = self.check_ent_type(arvados_fuse.StringFile, 'by_id', 'README')
82         txt = e.readfrom(0, -1)
83         self.assertRegexpMatches(txt, r'portable data hash')
84         self.assertRegexpMatches(txt, r'\n$')
85
86     @noexit
87     def test_by_id(self):
88         args = arvados_fuse.command.ArgumentParser().parse_args([
89             '--by-id',
90             '--foreground', self.mntdir])
91         self.assertEqual(args.mode, 'by_id')
92         self.mnt = arvados_fuse.command.Mount(args)
93         e = self.check_ent_type(arvados_fuse.MagicDirectory)
94         self.assertEqual(e.pdh_only, False)
95         self.assertEqual(True, self.mnt.listen_for_events)
96
97     @noexit
98     def test_by_pdh(self):
99         args = arvados_fuse.command.ArgumentParser().parse_args([
100             '--by-pdh',
101             '--foreground', self.mntdir])
102         self.assertEqual(args.mode, 'by_pdh')
103         self.mnt = arvados_fuse.command.Mount(args)
104         e = self.check_ent_type(arvados_fuse.MagicDirectory)
105         self.assertEqual(e.pdh_only, True)
106         self.assertEqual(False, self.mnt.listen_for_events)
107
108     @noexit
109     def test_by_tag(self):
110         args = arvados_fuse.command.ArgumentParser().parse_args([
111             '--by-tag',
112             '--foreground', self.mntdir])
113         self.assertEqual(args.mode, 'by_tag')
114         self.mnt = arvados_fuse.command.Mount(args)
115         e = self.check_ent_type(arvados_fuse.TagsDirectory)
116         self.assertEqual(True, self.mnt.listen_for_events)
117
118     @noexit
119     def test_collection(self, id_type='uuid'):
120         c = run_test_server.fixture('collections')['public_text_file']
121         cid = c[id_type]
122         args = arvados_fuse.command.ArgumentParser().parse_args([
123             '--collection', cid,
124             '--foreground', self.mntdir])
125         self.mnt = arvados_fuse.command.Mount(args)
126         e = self.check_ent_type(arvados_fuse.CollectionDirectory)
127         self.assertEqual(e.collection_locator, cid)
128         self.assertEqual(id_type == 'uuid', self.mnt.listen_for_events)
129
130     def test_collection_pdh(self):
131         self.test_collection('portable_data_hash')
132
133     @noexit
134     def test_home(self):
135         args = arvados_fuse.command.ArgumentParser().parse_args([
136             '--home',
137             '--foreground', self.mntdir])
138         self.assertEqual(args.mode, 'home')
139         self.mnt = arvados_fuse.command.Mount(args)
140         e = self.check_ent_type(arvados_fuse.ProjectDirectory)
141         self.assertEqual(e.project_object['uuid'],
142                          run_test_server.fixture('users')['active']['uuid'])
143         self.assertEqual(True, self.mnt.listen_for_events)
144
145     def test_mutually_exclusive_args(self):
146         cid = run_test_server.fixture('collections')['public_text_file']['uuid']
147         gid = run_test_server.fixture('groups')['aproject']['uuid']
148         for badargs in [
149                 ['--mount-tmp', 'foo', '--collection', cid],
150                 ['--mount-tmp', 'foo', '--project', gid],
151                 ['--collection', cid, '--project', gid],
152                 ['--by-id', '--project', gid],
153                 ['--mount-tmp', 'foo', '--by-id'],
154         ]:
155             with nostderr():
156                 with self.assertRaises(SystemExit):
157                     args = arvados_fuse.command.ArgumentParser().parse_args(
158                         badargs + ['--foreground', self.mntdir])
159                     arvados_fuse.command.Mount(args)
160     @noexit
161     def test_project(self):
162         uuid = run_test_server.fixture('groups')['aproject']['uuid']
163         args = arvados_fuse.command.ArgumentParser().parse_args([
164             '--project', uuid,
165             '--foreground', self.mntdir])
166         self.mnt = arvados_fuse.command.Mount(args)
167         e = self.check_ent_type(arvados_fuse.ProjectDirectory)
168         self.assertEqual(e.project_object['uuid'], uuid)
169
170     @noexit
171     def test_shared(self):
172         args = arvados_fuse.command.ArgumentParser().parse_args([
173             '--shared',
174             '--foreground', self.mntdir])
175         self.assertEqual(args.mode, 'shared')
176         self.mnt = arvados_fuse.command.Mount(args)
177         e = self.check_ent_type(arvados_fuse.SharedDirectory)
178         self.assertEqual(e.current_user['uuid'],
179                          run_test_server.fixture('users')['active']['uuid'])
180         self.assertEqual(True, self.mnt.listen_for_events)
181
182     def test_version_argument(self):
183         orig, sys.stderr = sys.stderr, io.BytesIO()
184         with self.assertRaises(SystemExit):
185             args = arvados_fuse.command.ArgumentParser().parse_args(['--version'])
186         self.assertRegexpMatches(sys.stderr.getvalue(), "[0-9]+\.[0-9]+\.[0-9]+")
187         sys.stderr = orig
188
189     @noexit
190     @mock.patch('arvados.events.subscribe')
191     def test_disable_event_listening(self, mock_subscribe):
192         args = arvados_fuse.command.ArgumentParser().parse_args([
193             '--disable-event-listening',
194             '--by-id',
195             '--foreground', self.mntdir])
196         self.mnt = arvados_fuse.command.Mount(args)
197         self.assertEqual(True, self.mnt.listen_for_events)
198         self.assertEqual(True, self.mnt.args.disable_event_listening)
199         with self.mnt:
200             pass
201         self.assertEqual(0, mock_subscribe.call_count)
202
203     @noexit
204     @mock.patch('arvados.events.subscribe')
205     def test_custom(self, mock_subscribe):
206         args = arvados_fuse.command.ArgumentParser().parse_args([
207             '--mount-tmp', 'foo',
208             '--mount-tmp', 'bar',
209             '--mount-home', 'my_home',
210             '--foreground', self.mntdir])
211         self.assertEqual(args.mode, None)
212         self.mnt = arvados_fuse.command.Mount(args)
213         self.check_ent_type(arvados_fuse.Directory)
214         self.check_ent_type(arvados_fuse.TmpCollectionDirectory, 'foo')
215         self.check_ent_type(arvados_fuse.TmpCollectionDirectory, 'bar')
216         e = self.check_ent_type(arvados_fuse.ProjectDirectory, 'my_home')
217         self.assertEqual(e.project_object['uuid'],
218                          run_test_server.fixture('users')['active']['uuid'])
219         self.assertEqual(True, self.mnt.listen_for_events)
220         with self.mnt:
221             pass
222         self.assertEqual(1, mock_subscribe.call_count)
223
224     @noexit
225     @mock.patch('arvados.events.subscribe')
226     def test_custom_no_listen(self, mock_subscribe):
227         args = arvados_fuse.command.ArgumentParser().parse_args([
228             '--mount-by-pdh', 'pdh',
229             '--mount-tmp', 'foo',
230             '--mount-tmp', 'bar',
231             '--foreground', self.mntdir])
232         self.mnt = arvados_fuse.command.Mount(args)
233         self.assertEqual(False, self.mnt.listen_for_events)
234         with self.mnt:
235             pass
236         self.assertEqual(0, mock_subscribe.call_count)
237
238     def test_custom_unsupported_layouts(self):
239         for name in ['.', '..', '', 'foo/bar', '/foo']:
240             with nostderr():
241                 with self.assertRaises(SystemExit):
242                     args = arvados_fuse.command.ArgumentParser().parse_args([
243                         '--mount-tmp', name,
244                         '--foreground', self.mntdir])
245                     arvados_fuse.command.Mount(args)
246
247 class MountErrorTest(unittest.TestCase):
248     def setUp(self):
249         self.mntdir = tempfile.mkdtemp()
250         run_test_server.run()
251         run_test_server.authorize_with("active")
252         self.logger = logging.getLogger("null")
253         self.logger.setLevel(logging.CRITICAL+1)
254
255     def tearDown(self):
256         if os.path.exists(self.mntdir):
257             # If the directory was not unmounted, this will raise an exception.
258             os.rmdir(self.mntdir)
259         run_test_server.reset()
260
261     def test_no_token(self):
262         del arvados.config._settings["ARVADOS_API_TOKEN"]
263         arvados.config._settings = {}
264         with self.assertRaises(SystemExit) as ex:
265             args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
266             arvados_fuse.command.Mount(args, logger=self.logger).run()
267         self.assertEqual(1, ex.exception.code)
268
269     def test_no_host(self):
270         del arvados.config._settings["ARVADOS_API_HOST"]
271         with self.assertRaises(SystemExit) as ex:
272             args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
273             arvados_fuse.command.Mount(args, logger=self.logger).run()
274         self.assertEqual(1, ex.exception.code)
275
276     def test_bogus_host(self):
277         arvados.config._settings["ARVADOS_API_HOST"] = "100::"
278         with self.assertRaises(SystemExit) as ex:
279             args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
280             arvados_fuse.command.Mount(args, logger=self.logger).run()
281         self.assertEqual(1, ex.exception.code)
282
283     def test_bogus_token(self):
284         arvados.config._settings["ARVADOS_API_TOKEN"] = "zzzzzzzzzzzzz"
285         with self.assertRaises(SystemExit) as ex:
286             args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
287             arvados_fuse.command.Mount(args, logger=self.logger).run()
288         self.assertEqual(1, ex.exception.code)
289
290     def test_bogus_mount_dir(self):
291         # All FUSE errors in llfuse.init() are raised as RuntimeError
292         # An easy error to trigger is to supply a nonexistent mount point,
293         # so test that one.
294         #
295         # Other possible errors that also raise RuntimeError (but are much
296         # harder to test automatically because they depend on operating
297         # system configuration):
298         #
299         # The user doesn't have permission to use FUSE
300         # The user specified --allow-other but user_allow_other is not set
301         # in /etc/fuse.conf
302         os.rmdir(self.mntdir)
303         with self.assertRaises(SystemExit) as ex:
304             args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
305             arvados_fuse.command.Mount(args, logger=self.logger).run()
306         self.assertEqual(1, ex.exception.code)
307
308     def test_unreadable_collection(self):
309         with self.assertRaises(SystemExit) as ex:
310             args = arvados_fuse.command.ArgumentParser().parse_args([
311                 "--collection", "zzzzz-4zz18-zzzzzzzzzzzzzzz", self.mntdir])
312             arvados_fuse.command.Mount(args, logger=self.logger).run()
313         self.assertEqual(1, ex.exception.code)
314
315     def test_unreadable_project(self):
316         with self.assertRaises(SystemExit) as ex:
317             args = arvados_fuse.command.ArgumentParser().parse_args([
318                 "--project", "zzzzz-j7d0g-zzzzzzzzzzzzzzz", self.mntdir])
319             arvados_fuse.command.Mount(args, logger=self.logger).run()
320         self.assertEqual(1, ex.exception.code)