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