1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
5 from __future__ import absolute_import
6 from __future__ import print_function
7 from six import assertRegex
10 import arvados_fuse.command
18 from . import run_test_server
24 from unittest import mock
27 """If argparse or arvados_fuse tries to exit, fail the test instead"""
28 class SystemExitCaught(Exception):
30 @functools.wraps(func)
31 def wrapper(*args, **kwargs):
33 return func(*args, **kwargs)
35 raise SystemExitCaught
38 @contextlib.contextmanager
40 with open(os.devnull, 'w') as dn:
41 orig, sys.stderr = sys.stderr, dn
48 class MountArgsTest(unittest.TestCase):
50 self.mntdir = tempfile.mkdtemp()
51 run_test_server.authorize_with('active')
56 def lookup(self, mnt, *path):
57 ent = mnt.operations.inodes[llfuse.ROOT_INODE]
62 @contextlib.contextmanager
63 def stderrMatches(self, stderr):
64 orig, sys.stderr = sys.stderr, stderr
70 def check_ent_type(self, cls, *path):
71 ent = self.lookup(self.mnt, *path)
72 self.assertEqual(ent.__class__, cls)
76 def test_default_all(self):
77 args = arvados_fuse.command.ArgumentParser().parse_args([
78 '--foreground', self.mntdir])
79 self.assertEqual(args.mode, None)
80 self.mnt = arvados_fuse.command.Mount(args)
81 e = self.check_ent_type(arvados_fuse.ProjectDirectory, 'home')
82 self.assertEqual(e.project_object['uuid'],
83 run_test_server.fixture('users')['active']['uuid'])
84 e = self.check_ent_type(arvados_fuse.MagicDirectory, 'by_id')
86 e = self.check_ent_type(arvados_fuse.StringFile, 'README')
87 readme = e.readfrom(0, -1).decode()
88 assertRegex(self, readme, r'active-user@arvados\.local')
89 assertRegex(self, readme, r'\n$')
91 e = self.check_ent_type(arvados_fuse.StringFile, 'by_id', 'README')
92 txt = e.readfrom(0, -1).decode()
93 assertRegex(self, txt, r'portable data hash')
94 assertRegex(self, txt, r'\n$')
98 args = arvados_fuse.command.ArgumentParser().parse_args([
100 '--foreground', self.mntdir])
101 self.assertEqual(args.mode, 'by_id')
102 self.mnt = arvados_fuse.command.Mount(args)
103 e = self.check_ent_type(arvados_fuse.MagicDirectory)
104 self.assertEqual(e.pdh_only, False)
105 self.assertEqual(True, self.mnt.listen_for_events)
108 def test_by_pdh(self):
109 args = arvados_fuse.command.ArgumentParser().parse_args([
111 '--foreground', self.mntdir])
112 self.assertEqual(args.mode, 'by_pdh')
113 self.mnt = arvados_fuse.command.Mount(args)
114 e = self.check_ent_type(arvados_fuse.MagicDirectory)
115 self.assertEqual(e.pdh_only, True)
116 self.assertEqual(False, self.mnt.listen_for_events)
119 def test_by_tag(self):
120 args = arvados_fuse.command.ArgumentParser().parse_args([
122 '--foreground', self.mntdir])
123 self.assertEqual(args.mode, 'by_tag')
124 self.mnt = arvados_fuse.command.Mount(args)
125 e = self.check_ent_type(arvados_fuse.TagsDirectory)
126 self.assertEqual(True, self.mnt.listen_for_events)
129 def test_collection(self, id_type='uuid'):
130 c = run_test_server.fixture('collections')['public_text_file']
132 args = arvados_fuse.command.ArgumentParser().parse_args([
134 '--foreground', self.mntdir])
135 self.mnt = arvados_fuse.command.Mount(args)
136 e = self.check_ent_type(arvados_fuse.CollectionDirectory)
137 self.assertEqual(e.collection_locator, cid)
138 self.assertEqual(id_type == 'uuid', self.mnt.listen_for_events)
140 def test_collection_pdh(self):
141 self.test_collection('portable_data_hash')
145 args = arvados_fuse.command.ArgumentParser().parse_args([
147 '--foreground', self.mntdir])
148 self.assertEqual(args.mode, 'home')
149 self.mnt = arvados_fuse.command.Mount(args)
150 e = self.check_ent_type(arvados_fuse.ProjectDirectory)
151 self.assertEqual(e.project_object['uuid'],
152 run_test_server.fixture('users')['active']['uuid'])
153 self.assertEqual(True, self.mnt.listen_for_events)
155 def test_mutually_exclusive_args(self):
156 cid = run_test_server.fixture('collections')['public_text_file']['uuid']
157 gid = run_test_server.fixture('groups')['aproject']['uuid']
159 ['--mount-tmp', 'foo', '--collection', cid],
160 ['--mount-tmp', 'foo', '--project', gid],
161 ['--collection', cid, '--project', gid],
162 ['--by-id', '--project', gid],
163 ['--mount-tmp', 'foo', '--by-id'],
166 with self.assertRaises(SystemExit):
167 args = arvados_fuse.command.ArgumentParser().parse_args(
168 badargs + ['--foreground', self.mntdir])
169 arvados_fuse.command.Mount(args)
171 def test_project(self):
172 uuid = run_test_server.fixture('groups')['aproject']['uuid']
173 args = arvados_fuse.command.ArgumentParser().parse_args([
175 '--foreground', self.mntdir])
176 self.mnt = arvados_fuse.command.Mount(args)
177 e = self.check_ent_type(arvados_fuse.ProjectDirectory)
178 self.assertEqual(e.project_object['uuid'], uuid)
181 def test_shared(self):
182 args = arvados_fuse.command.ArgumentParser().parse_args([
184 '--foreground', self.mntdir])
185 self.assertEqual(args.mode, 'shared')
186 self.mnt = arvados_fuse.command.Mount(args)
187 e = self.check_ent_type(arvados_fuse.SharedDirectory)
188 self.assertEqual(e.current_user['uuid'],
189 run_test_server.fixture('users')['active']['uuid'])
190 self.assertEqual(True, self.mnt.listen_for_events)
192 def test_version_argument(self):
193 # The argparse version action prints to stderr in Python 2 and stdout
194 # in Python 3.4 and up. Write both to the same stream so the test can pass
198 sys.stderr = io.StringIO()
199 sys.stdout = sys.stderr
201 with self.assertRaises(SystemExit):
202 args = arvados_fuse.command.ArgumentParser().parse_args(['--version'])
203 assertRegex(self, sys.stdout.getvalue(), "[0-9]+\.[0-9]+\.[0-9]+")
209 @mock.patch('arvados.events.subscribe')
210 def test_disable_event_listening(self, mock_subscribe):
211 args = arvados_fuse.command.ArgumentParser().parse_args([
212 '--disable-event-listening',
214 '--foreground', self.mntdir])
215 self.mnt = arvados_fuse.command.Mount(args)
216 self.assertEqual(True, self.mnt.listen_for_events)
217 self.assertEqual(True, self.mnt.args.disable_event_listening)
220 self.assertEqual(0, mock_subscribe.call_count)
223 @mock.patch('arvados.events.subscribe')
224 def test_custom(self, mock_subscribe):
225 args = arvados_fuse.command.ArgumentParser().parse_args([
226 '--mount-tmp', 'foo',
227 '--mount-tmp', 'bar',
228 '--mount-home', 'my_home',
229 '--foreground', self.mntdir])
230 self.assertEqual(args.mode, None)
231 self.mnt = arvados_fuse.command.Mount(args)
232 self.check_ent_type(arvados_fuse.Directory)
233 self.check_ent_type(arvados_fuse.TmpCollectionDirectory, 'foo')
234 self.check_ent_type(arvados_fuse.TmpCollectionDirectory, 'bar')
235 e = self.check_ent_type(arvados_fuse.ProjectDirectory, 'my_home')
236 self.assertEqual(e.project_object['uuid'],
237 run_test_server.fixture('users')['active']['uuid'])
238 self.assertEqual(True, self.mnt.listen_for_events)
241 self.assertEqual(1, mock_subscribe.call_count)
244 @mock.patch('arvados.events.subscribe')
245 def test_custom_no_listen(self, mock_subscribe):
246 args = arvados_fuse.command.ArgumentParser().parse_args([
247 '--mount-by-pdh', 'pdh',
248 '--mount-tmp', 'foo',
249 '--mount-tmp', 'bar',
250 '--foreground', self.mntdir])
251 self.mnt = arvados_fuse.command.Mount(args)
252 self.assertEqual(False, self.mnt.listen_for_events)
255 self.assertEqual(0, mock_subscribe.call_count)
257 def test_custom_unsupported_layouts(self):
258 for name in ['.', '..', '', 'foo/bar', '/foo']:
260 with self.assertRaises(SystemExit):
261 args = arvados_fuse.command.ArgumentParser().parse_args([
263 '--foreground', self.mntdir])
264 arvados_fuse.command.Mount(args)
267 @mock.patch('resource.setrlimit')
268 @mock.patch('resource.getrlimit')
269 def test_default_file_cache(self, getrlimit, setrlimit):
270 args = arvados_fuse.command.ArgumentParser().parse_args([
271 '--foreground', self.mntdir])
272 self.assertEqual(args.mode, None)
273 getrlimit.return_value = (1024, 1048576)
274 self.mnt = arvados_fuse.command.Mount(args)
275 setrlimit.assert_called_with(resource.RLIMIT_NOFILE, (10240, 1048576))
278 @mock.patch('resource.setrlimit')
279 @mock.patch('resource.getrlimit')
280 def test_small_file_cache(self, getrlimit, setrlimit):
281 args = arvados_fuse.command.ArgumentParser().parse_args([
282 '--foreground', '--file-cache=256000000', self.mntdir])
283 self.assertEqual(args.mode, None)
284 getrlimit.return_value = (1024, 1048576)
285 self.mnt = arvados_fuse.command.Mount(args)
286 setrlimit.assert_not_called()
289 @mock.patch('resource.setrlimit')
290 @mock.patch('resource.getrlimit')
291 def test_large_file_cache(self, getrlimit, setrlimit):
292 args = arvados_fuse.command.ArgumentParser().parse_args([
293 '--foreground', '--file-cache=256000000000', self.mntdir])
294 self.assertEqual(args.mode, None)
295 getrlimit.return_value = (1024, 1048576)
296 self.mnt = arvados_fuse.command.Mount(args)
297 setrlimit.assert_called_with(resource.RLIMIT_NOFILE, (30517, 1048576))
300 @mock.patch('resource.setrlimit')
301 @mock.patch('resource.getrlimit')
302 def test_file_cache_hard_limit(self, getrlimit, setrlimit):
303 args = arvados_fuse.command.ArgumentParser().parse_args([
304 '--foreground', '--file-cache=256000000000', self.mntdir])
305 self.assertEqual(args.mode, None)
306 getrlimit.return_value = (1024, 2048)
307 self.mnt = arvados_fuse.command.Mount(args)
308 setrlimit.assert_called_with(resource.RLIMIT_NOFILE, (2048, 2048))
310 class MountErrorTest(unittest.TestCase):
312 self.mntdir = tempfile.mkdtemp()
313 run_test_server.run()
314 run_test_server.authorize_with("active")
315 self.logger = logging.getLogger("null")
316 self.logger.setLevel(logging.CRITICAL+1)
319 if os.path.exists(self.mntdir):
320 # If the directory was not unmounted, this will raise an exception.
321 os.rmdir(self.mntdir)
322 run_test_server.reset()
324 def test_no_token(self):
325 del arvados.config._settings["ARVADOS_API_TOKEN"]
326 arvados.config._settings = {}
327 with self.assertRaises(SystemExit) as ex:
328 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
329 arvados_fuse.command.Mount(args, logger=self.logger).run()
330 self.assertEqual(1, ex.exception.code)
332 def test_no_host(self):
333 del arvados.config._settings["ARVADOS_API_HOST"]
334 with self.assertRaises(SystemExit) as ex:
335 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
336 arvados_fuse.command.Mount(args, logger=self.logger).run()
337 self.assertEqual(1, ex.exception.code)
339 def test_bogus_host(self):
340 arvados.config._settings["ARVADOS_API_HOST"] = "100::"
341 with self.assertRaises(SystemExit) as ex, mock.patch('time.sleep'):
342 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
343 arvados_fuse.command.Mount(args, logger=self.logger).run()
344 self.assertEqual(1, ex.exception.code)
346 def test_bogus_token(self):
347 arvados.config._settings["ARVADOS_API_TOKEN"] = "zzzzzzzzzzzzz"
348 with self.assertRaises(SystemExit) as ex:
349 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
350 arvados_fuse.command.Mount(args, logger=self.logger).run()
351 self.assertEqual(1, ex.exception.code)
353 def test_bogus_mount_dir(self):
354 # All FUSE errors in llfuse.init() are raised as RuntimeError
355 # An easy error to trigger is to supply a nonexistent mount point,
358 # Other possible errors that also raise RuntimeError (but are much
359 # harder to test automatically because they depend on operating
360 # system configuration):
362 # The user doesn't have permission to use FUSE
363 # The user specified --allow-other but user_allow_other is not set
365 os.rmdir(self.mntdir)
366 with self.assertRaises(SystemExit) as ex:
367 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
368 arvados_fuse.command.Mount(args, logger=self.logger).run()
369 self.assertEqual(1, ex.exception.code)
371 def test_unreadable_collection(self):
372 with self.assertRaises(SystemExit) as ex:
373 args = arvados_fuse.command.ArgumentParser().parse_args([
374 "--collection", "zzzzz-4zz18-zzzzzzzzzzzzzzz", self.mntdir])
375 arvados_fuse.command.Mount(args, logger=self.logger).run()
376 self.assertEqual(1, ex.exception.code)
378 def test_unreadable_project(self):
379 with self.assertRaises(SystemExit) as ex:
380 args = arvados_fuse.command.ArgumentParser().parse_args([
381 "--project", "zzzzz-j7d0g-zzzzzzzzzzzzzzz", self.mntdir])
382 arvados_fuse.command.Mount(args, logger=self.logger).run()
383 self.assertEqual(1, ex.exception.code)