3147: PySDK tests use mock>=1.0 and easier mock side_effect.
[arvados.git] / services / fuse / bin / arv-mount
1 #!/usr/bin/env python
2
3 import argparse
4 import arvados
5 import daemon
6 import logging
7 import os
8 import signal
9 import subprocess
10 import time
11
12 from arvados_fuse import *
13
14 logger = logging.getLogger('arvados.arv-mount')
15
16 if __name__ == '__main__':
17     # Handle command line parameters
18     parser = argparse.ArgumentParser(
19         description='''Mount Keep data under the local filesystem.  Default mode is --home''',
20         epilog="""
21 Note: When using the --exec feature, you must either specify the
22 mountpoint before --exec, or mark the end of your --exec arguments
23 with "--".
24 """)
25     parser.add_argument('mountpoint', type=str, help="""Mount point.""")
26     parser.add_argument('--allow-other', action='store_true',
27                         help="""Let other users read the mount""")
28
29     mount_mode = parser.add_mutually_exclusive_group()
30
31     mount_mode.add_argument('--all', action='store_true', help="""Mount a subdirectory for each mode: home, shared, by_tag, by_id (default).""")
32     mount_mode.add_argument('--home', action='store_true', help="""Mount only the user's home project.""")
33     mount_mode.add_argument('--shared', action='store_true', help="""Mount only list of projects shared with the user.""")
34     mount_mode.add_argument('--by-tag', action='store_true',
35                             help="""Mount subdirectories listed by tag.""")
36     mount_mode.add_argument('--by-id', action='store_true',
37                             help="""Mount subdirectories listed by portable data hash or uuid.""")
38     mount_mode.add_argument('--project', type=str, help="""Mount a specific project.""")
39     mount_mode.add_argument('--collection', type=str, help="""Mount only the specified collection.""")
40
41     parser.add_argument('--debug', action='store_true', help="""Debug mode""")
42     parser.add_argument('--logfile', help="""Write debug logs and errors to the specified file (default stderr).""")
43     parser.add_argument('--foreground', action='store_true', help="""Run in foreground (default is to daemonize unless --exec specified)""", default=False)
44     parser.add_argument('--exec', type=str, nargs=argparse.REMAINDER,
45                         dest="exec_args", metavar=('command', 'args', '...', '--'),
46                         help="""Mount, run a command, then unmount and exit""")
47
48     args = parser.parse_args()
49     args.mountpoint = os.path.realpath(args.mountpoint)
50     if args.logfile:
51         args.logfile = os.path.realpath(args.logfile)
52
53     # Daemonize as early as possible, so we don't accidentally close
54     # file descriptors we're using.
55     if not (args.exec_args or args.foreground):
56         os.chdir(args.mountpoint)
57         daemon_ctx = daemon.DaemonContext(working_directory='.')
58         daemon_ctx.open()
59     else:
60         daemon_ctx = None
61
62     # Configure a logger based on command-line switches.
63     # If we're using a contemporary Python SDK (mid-August 2014),
64     # configure the arvados hierarchy logger.
65     # Otherwise, configure the program root logger.
66     base_logger = getattr(arvados, 'logger', None)
67
68     if args.logfile:
69         log_handler = logging.FileHandler(args.logfile)
70     elif daemon_ctx:
71         log_handler = logging.NullHandler()
72     elif base_logger:
73         log_handler = arvados.log_handler
74     else:
75         log_handler = logging.StreamHandler()
76
77     if base_logger is None:
78         base_logger = logging.getLogger()
79     else:
80         base_logger.removeHandler(arvados.log_handler)
81     base_logger.addHandler(log_handler)
82
83     if args.debug:
84         base_logger.setLevel(logging.DEBUG)
85         logger.debug("arv-mount debugging enabled")
86
87     try:
88         # Create the request handler
89         operations = Operations(os.getuid(), os.getgid())
90         api = SafeApi(arvados.config)
91
92         usr = api.users().current().execute()
93         now = time.time()
94         if args.by_id:
95             # Set up the request handler with the 'magic directory' at the root
96             operations.inodes.add_entry(MagicDirectory(llfuse.ROOT_INODE, operations.inodes, api))
97         elif args.by_tag:
98             operations.inodes.add_entry(TagsDirectory(llfuse.ROOT_INODE, operations.inodes, api))
99         elif args.shared:
100             operations.inodes.add_entry(SharedDirectory(llfuse.ROOT_INODE, operations.inodes, api, usr))
101         elif args.home:
102             operations.inodes.add_entry(ProjectDirectory(llfuse.ROOT_INODE, operations.inodes, api, usr))
103         elif args.collection != None:
104             # Set up the request handler with the collection at the root
105             operations.inodes.add_entry(CollectionDirectory(llfuse.ROOT_INODE, operations.inodes, api, args.collection))
106         elif args.project != None:            
107             operations.inodes.add_entry(ProjectDirectory(llfuse.ROOT_INODE, operations.inodes, api, api.groups().get(uuid=args.project).execute()))
108         else:
109             e = operations.inodes.add_entry(Directory(llfuse.ROOT_INODE))
110
111             e._entries['home'] = operations.inodes.add_entry(ProjectDirectory(e.inode, operations.inodes, api, usr))
112             e._entries['shared'] = operations.inodes.add_entry(SharedDirectory(e.inode, operations.inodes, api, usr))
113             e._entries['by_tag'] = operations.inodes.add_entry(TagsDirectory(e.inode, operations.inodes, api))
114             e._entries['by_id'] = operations.inodes.add_entry(MagicDirectory(e.inode, operations.inodes, api))
115
116             text = '''
117 Welcome to Arvados!  This directory provides file system access to files and objects 
118 available on the Arvados installation located at '{}' 
119 using credentials for user '{}'.
120
121 From here, the following directories are available:
122
123   by_id/     Access to Keep collections by uuid or portable data hash (see by_id/README for details).
124   by_tag/    Access to Keep collections organized by tag.
125   home/      The contents of your home project.
126   shared/    Projects shared with you.
127 '''.format(arvados.config.get('ARVADOS_API_HOST'), usr['email'])
128
129             e._entries["README"] = operations.inodes.add_entry(StringFile(e.inode, text, now))
130
131
132     except Exception:
133         logger.exception("arv-mount: exception during API setup")
134         exit(1)
135
136     # FUSE options, see mount.fuse(8)
137     opts = [optname for optname in ['allow_other', 'debug']
138             if getattr(args, optname)]
139
140     if args.exec_args:
141         # Initialize the fuse connection
142         llfuse.init(operations, args.mountpoint, opts)
143
144         t = threading.Thread(None, lambda: llfuse.main())
145         t.start()
146
147         # wait until the driver is finished initializing
148         operations.initlock.wait()
149
150         rc = 255
151         try:
152             sp = subprocess.Popen(args.exec_args, shell=False)
153
154             # forward signals to the process.
155             signal.signal(signal.SIGINT, lambda signum, frame: sp.send_signal(signum))
156             signal.signal(signal.SIGTERM, lambda signum, frame: sp.send_signal(signum))
157             signal.signal(signal.SIGQUIT, lambda signum, frame: sp.send_signal(signum))
158
159             # wait for process to complete.
160             rc = sp.wait()
161
162             # restore default signal handlers.
163             signal.signal(signal.SIGINT, signal.SIG_DFL)
164             signal.signal(signal.SIGTERM, signal.SIG_DFL)
165             signal.signal(signal.SIGQUIT, signal.SIG_DFL)
166         except Exception as e:
167             logger.exception('arv-mount: exception during exec %s',
168                              args.exec_args)
169             try:
170                 rc = e.errno
171             except AttributeError:
172                 pass
173         finally:
174             subprocess.call(["fusermount", "-u", "-z", args.mountpoint])
175
176         exit(rc)
177     else:
178         try:
179             llfuse.init(operations, args.mountpoint, opts)
180             llfuse.main()
181         except Exception as e:
182             logger.exception('arv-mount: exception during mount')
183             exit(getattr(e, 'errno', 1))