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
19 from . import run_test_server
26 """If argparse or arvados_fuse tries to exit, fail the test instead"""
27 class SystemExitCaught(Exception):
29 @functools.wraps(func)
30 def wrapper(*args, **kwargs):
32 return func(*args, **kwargs)
34 raise SystemExitCaught
37 @contextlib.contextmanager
39 with open(os.devnull, 'w') as dn:
40 orig, sys.stderr = sys.stderr, dn
47 class MountArgsTest(unittest.TestCase):
49 self.mntdir = tempfile.mkdtemp()
50 run_test_server.authorize_with('active')
55 def lookup(self, mnt, *path):
56 ent = mnt.operations.inodes[llfuse.ROOT_INODE]
61 @contextlib.contextmanager
62 def stderrMatches(self, stderr):
63 orig, sys.stderr = sys.stderr, stderr
69 def check_ent_type(self, cls, *path):
70 ent = self.lookup(self.mnt, *path)
71 self.assertEqual(ent.__class__, cls)
75 def test_default_all(self):
76 args = arvados_fuse.command.ArgumentParser().parse_args([
77 '--foreground', self.mntdir])
78 self.assertEqual(args.mode, None)
79 self.mnt = arvados_fuse.command.Mount(args)
80 e = self.check_ent_type(arvados_fuse.ProjectDirectory, 'home')
81 self.assertEqual(e.project_object['uuid'],
82 run_test_server.fixture('users')['active']['uuid'])
83 e = self.check_ent_type(arvados_fuse.MagicDirectory, 'by_id')
85 e = self.check_ent_type(arvados_fuse.StringFile, 'README')
86 readme = e.readfrom(0, -1).decode()
87 assertRegex(self, readme, r'active-user@arvados\.local')
88 assertRegex(self, readme, r'\n$')
90 e = self.check_ent_type(arvados_fuse.StringFile, 'by_id', 'README')
91 txt = e.readfrom(0, -1).decode()
92 assertRegex(self, txt, r'portable data hash')
93 assertRegex(self, txt, r'\n$')
97 args = arvados_fuse.command.ArgumentParser().parse_args([
99 '--foreground', self.mntdir])
100 self.assertEqual(args.mode, 'by_id')
101 self.mnt = arvados_fuse.command.Mount(args)
102 e = self.check_ent_type(arvados_fuse.MagicDirectory)
103 self.assertEqual(e.pdh_only, False)
104 self.assertEqual(True, self.mnt.listen_for_events)
107 def test_by_pdh(self):
108 args = arvados_fuse.command.ArgumentParser().parse_args([
110 '--foreground', self.mntdir])
111 self.assertEqual(args.mode, 'by_pdh')
112 self.mnt = arvados_fuse.command.Mount(args)
113 e = self.check_ent_type(arvados_fuse.MagicDirectory)
114 self.assertEqual(e.pdh_only, True)
115 self.assertEqual(False, self.mnt.listen_for_events)
118 def test_by_tag(self):
119 args = arvados_fuse.command.ArgumentParser().parse_args([
121 '--foreground', self.mntdir])
122 self.assertEqual(args.mode, 'by_tag')
123 self.mnt = arvados_fuse.command.Mount(args)
124 e = self.check_ent_type(arvados_fuse.TagsDirectory)
125 self.assertEqual(True, self.mnt.listen_for_events)
128 def test_collection(self, id_type='uuid'):
129 c = run_test_server.fixture('collections')['public_text_file']
131 args = arvados_fuse.command.ArgumentParser().parse_args([
133 '--foreground', self.mntdir])
134 self.mnt = arvados_fuse.command.Mount(args)
135 e = self.check_ent_type(arvados_fuse.CollectionDirectory)
136 self.assertEqual(e.collection_locator, cid)
137 self.assertEqual(id_type == 'uuid', self.mnt.listen_for_events)
139 def test_collection_pdh(self):
140 self.test_collection('portable_data_hash')
144 args = arvados_fuse.command.ArgumentParser().parse_args([
146 '--foreground', self.mntdir])
147 self.assertEqual(args.mode, 'home')
148 self.mnt = arvados_fuse.command.Mount(args)
149 e = self.check_ent_type(arvados_fuse.ProjectDirectory)
150 self.assertEqual(e.project_object['uuid'],
151 run_test_server.fixture('users')['active']['uuid'])
152 self.assertEqual(True, self.mnt.listen_for_events)
154 def test_mutually_exclusive_args(self):
155 cid = run_test_server.fixture('collections')['public_text_file']['uuid']
156 gid = run_test_server.fixture('groups')['aproject']['uuid']
158 ['--mount-tmp', 'foo', '--collection', cid],
159 ['--mount-tmp', 'foo', '--project', gid],
160 ['--collection', cid, '--project', gid],
161 ['--by-id', '--project', gid],
162 ['--mount-tmp', 'foo', '--by-id'],
165 with self.assertRaises(SystemExit):
166 args = arvados_fuse.command.ArgumentParser().parse_args(
167 badargs + ['--foreground', self.mntdir])
168 arvados_fuse.command.Mount(args)
170 def test_project(self):
171 uuid = run_test_server.fixture('groups')['aproject']['uuid']
172 args = arvados_fuse.command.ArgumentParser().parse_args([
174 '--foreground', self.mntdir])
175 self.mnt = arvados_fuse.command.Mount(args)
176 e = self.check_ent_type(arvados_fuse.ProjectDirectory)
177 self.assertEqual(e.project_object['uuid'], uuid)
180 def test_shared(self):
181 args = arvados_fuse.command.ArgumentParser().parse_args([
183 '--foreground', self.mntdir])
184 self.assertEqual(args.mode, 'shared')
185 self.mnt = arvados_fuse.command.Mount(args)
186 e = self.check_ent_type(arvados_fuse.SharedDirectory)
187 self.assertEqual(e.current_user['uuid'],
188 run_test_server.fixture('users')['active']['uuid'])
189 self.assertEqual(True, self.mnt.listen_for_events)
191 def test_version_argument(self):
192 # The argparse version action prints to stderr in Python 2 and stdout
193 # in Python 3.4 and up. Write both to the same stream so the test can pass
197 sys.stderr = io.StringIO()
198 sys.stdout = sys.stderr
200 with self.assertRaises(SystemExit):
201 args = arvados_fuse.command.ArgumentParser().parse_args(['--version'])
202 assertRegex(self, sys.stdout.getvalue(), "[0-9]+\.[0-9]+\.[0-9]+")
208 @mock.patch('arvados.events.subscribe')
209 def test_disable_event_listening(self, mock_subscribe):
210 args = arvados_fuse.command.ArgumentParser().parse_args([
211 '--disable-event-listening',
213 '--foreground', self.mntdir])
214 self.mnt = arvados_fuse.command.Mount(args)
215 self.assertEqual(True, self.mnt.listen_for_events)
216 self.assertEqual(True, self.mnt.args.disable_event_listening)
219 self.assertEqual(0, mock_subscribe.call_count)
222 @mock.patch('arvados.events.subscribe')
223 def test_custom(self, mock_subscribe):
224 args = arvados_fuse.command.ArgumentParser().parse_args([
225 '--mount-tmp', 'foo',
226 '--mount-tmp', 'bar',
227 '--mount-home', 'my_home',
228 '--foreground', self.mntdir])
229 self.assertEqual(args.mode, None)
230 self.mnt = arvados_fuse.command.Mount(args)
231 self.check_ent_type(arvados_fuse.Directory)
232 self.check_ent_type(arvados_fuse.TmpCollectionDirectory, 'foo')
233 self.check_ent_type(arvados_fuse.TmpCollectionDirectory, 'bar')
234 e = self.check_ent_type(arvados_fuse.ProjectDirectory, 'my_home')
235 self.assertEqual(e.project_object['uuid'],
236 run_test_server.fixture('users')['active']['uuid'])
237 self.assertEqual(True, self.mnt.listen_for_events)
240 self.assertEqual(1, mock_subscribe.call_count)
243 @mock.patch('arvados.events.subscribe')
244 def test_custom_no_listen(self, mock_subscribe):
245 args = arvados_fuse.command.ArgumentParser().parse_args([
246 '--mount-by-pdh', 'pdh',
247 '--mount-tmp', 'foo',
248 '--mount-tmp', 'bar',
249 '--foreground', self.mntdir])
250 self.mnt = arvados_fuse.command.Mount(args)
251 self.assertEqual(False, self.mnt.listen_for_events)
254 self.assertEqual(0, mock_subscribe.call_count)
256 def test_custom_unsupported_layouts(self):
257 for name in ['.', '..', '', 'foo/bar', '/foo']:
259 with self.assertRaises(SystemExit):
260 args = arvados_fuse.command.ArgumentParser().parse_args([
262 '--foreground', self.mntdir])
263 arvados_fuse.command.Mount(args)
266 @mock.patch('resource.setrlimit')
267 @mock.patch('resource.getrlimit')
268 def test_default_file_cache(self, getrlimit, setrlimit):
269 args = arvados_fuse.command.ArgumentParser().parse_args([
270 '--foreground', self.mntdir])
271 self.assertEqual(args.mode, None)
272 getrlimit.return_value = (1024, 1048576)
273 self.mnt = arvados_fuse.command.Mount(args)
274 setrlimit.assert_called_with(resource.RLIMIT_NOFILE, (10240, 1048576))
277 @mock.patch('resource.setrlimit')
278 @mock.patch('resource.getrlimit')
279 def test_small_file_cache(self, getrlimit, setrlimit):
280 args = arvados_fuse.command.ArgumentParser().parse_args([
281 '--foreground', '--file-cache=256000000', self.mntdir])
282 self.assertEqual(args.mode, None)
283 getrlimit.return_value = (1024, 1048576)
284 self.mnt = arvados_fuse.command.Mount(args)
285 setrlimit.assert_not_called()
288 @mock.patch('resource.setrlimit')
289 @mock.patch('resource.getrlimit')
290 def test_large_file_cache(self, getrlimit, setrlimit):
291 args = arvados_fuse.command.ArgumentParser().parse_args([
292 '--foreground', '--file-cache=256000000000', self.mntdir])
293 self.assertEqual(args.mode, None)
294 getrlimit.return_value = (1024, 1048576)
295 self.mnt = arvados_fuse.command.Mount(args)
296 setrlimit.assert_called_with(resource.RLIMIT_NOFILE, (30517, 1048576))
299 @mock.patch('resource.setrlimit')
300 @mock.patch('resource.getrlimit')
301 def test_file_cache_hard_limit(self, getrlimit, setrlimit):
302 args = arvados_fuse.command.ArgumentParser().parse_args([
303 '--foreground', '--file-cache=256000000000', self.mntdir])
304 self.assertEqual(args.mode, None)
305 getrlimit.return_value = (1024, 2048)
306 self.mnt = arvados_fuse.command.Mount(args)
307 setrlimit.assert_called_with(resource.RLIMIT_NOFILE, (2048, 2048))
309 class MountErrorTest(unittest.TestCase):
311 self.mntdir = tempfile.mkdtemp()
312 run_test_server.run()
313 run_test_server.authorize_with("active")
314 self.logger = logging.getLogger("null")
315 self.logger.setLevel(logging.CRITICAL+1)
318 if os.path.exists(self.mntdir):
319 # If the directory was not unmounted, this will raise an exception.
320 os.rmdir(self.mntdir)
321 run_test_server.reset()
323 def test_no_token(self):
324 del arvados.config._settings["ARVADOS_API_TOKEN"]
325 arvados.config._settings = {}
326 with self.assertRaises(SystemExit) as ex:
327 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
328 arvados_fuse.command.Mount(args, logger=self.logger).run()
329 self.assertEqual(1, ex.exception.code)
331 def test_no_host(self):
332 del arvados.config._settings["ARVADOS_API_HOST"]
333 with self.assertRaises(SystemExit) as ex:
334 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
335 arvados_fuse.command.Mount(args, logger=self.logger).run()
336 self.assertEqual(1, ex.exception.code)
338 def test_bogus_host(self):
339 arvados.config._settings["ARVADOS_API_HOST"] = "100::"
340 with self.assertRaises(SystemExit) as ex, mock.patch('time.sleep'):
341 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
342 arvados_fuse.command.Mount(args, logger=self.logger).run()
343 self.assertEqual(1, ex.exception.code)
345 def test_bogus_token(self):
346 arvados.config._settings["ARVADOS_API_TOKEN"] = "zzzzzzzzzzzzz"
347 with self.assertRaises(SystemExit) as ex:
348 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
349 arvados_fuse.command.Mount(args, logger=self.logger).run()
350 self.assertEqual(1, ex.exception.code)
352 def test_bogus_mount_dir(self):
353 # All FUSE errors in llfuse.init() are raised as RuntimeError
354 # An easy error to trigger is to supply a nonexistent mount point,
357 # Other possible errors that also raise RuntimeError (but are much
358 # harder to test automatically because they depend on operating
359 # system configuration):
361 # The user doesn't have permission to use FUSE
362 # The user specified --allow-other but user_allow_other is not set
364 os.rmdir(self.mntdir)
365 with self.assertRaises(SystemExit) as ex:
366 args = arvados_fuse.command.ArgumentParser().parse_args([self.mntdir])
367 arvados_fuse.command.Mount(args, logger=self.logger).run()
368 self.assertEqual(1, ex.exception.code)
370 def test_unreadable_collection(self):
371 with self.assertRaises(SystemExit) as ex:
372 args = arvados_fuse.command.ArgumentParser().parse_args([
373 "--collection", "zzzzz-4zz18-zzzzzzzzzzzzzzz", self.mntdir])
374 arvados_fuse.command.Mount(args, logger=self.logger).run()
375 self.assertEqual(1, ex.exception.code)
377 def test_unreadable_project(self):
378 with self.assertRaises(SystemExit) as ex:
379 args = arvados_fuse.command.ArgumentParser().parse_args([
380 "--project", "zzzzz-j7d0g-zzzzzzzzzzzzzzz", self.mntdir])
381 arvados_fuse.command.Mount(args, logger=self.logger).run()
382 self.assertEqual(1, ex.exception.code)