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