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