+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
import argparse
import arvados
import daemon
import arvados.commands._util as arv_cmd
from arvados_fuse import crunchstat
from arvados_fuse import *
+from arvados_fuse.unmount import unmount
+from arvados_fuse._version import __version__
class ArgumentParser(argparse.ArgumentParser):
def __init__(self):
mountpoint before --exec, or mark the end of your --exec arguments
with "--".
""")
+ self.add_argument('--version', action='version',
+ version="%s %s" % (sys.argv[0], __version__),
+ help='Print version and exit.')
self.add_argument('mountpoint', type=str, help="""Mount point.""")
self.add_argument('--allow-other', action='store_true',
help="""Let other users read the mount""")
+ self.add_argument('--subtype', type=str, metavar='STRING',
+ help="""Report mounted filesystem type as "fuse.STRING", instead of just "fuse".""")
mode = self.add_mutually_exclusive_group()
self.add_argument('--crunchstat-interval', type=float, help="Write stats to stderr every N seconds (default disabled)", default=0)
+ unmount = self.add_mutually_exclusive_group()
+ unmount.add_argument('--unmount', action='store_true', default=False,
+ help="Forcefully unmount the specified mountpoint (if it's a fuse mount) and exit. If --subtype is given, unmount only if the mount has the specified subtype. WARNING: This command can affect any kind of fuse mount, not just arv-mount.")
+ unmount.add_argument('--unmount-all', action='store_true', default=False,
+ help="Forcefully unmount every fuse mount at or below the specified path and exit. If --subtype is given, unmount only mounts that have the specified subtype. Exit non-zero if any other types of mounts are found at or below the given path. WARNING: This command can affect any kind of fuse mount, not just arv-mount.")
+ unmount.add_argument('--replace', action='store_true', default=False,
+ help="If a fuse mount is already present at mountpoint, forcefully unmount it before mounting")
self.add_argument('--unmount-timeout',
type=float, default=2.0,
help="Time to wait for graceful shutdown after --exec program exits and filesystem is unmounted")
class Mount(object):
def __init__(self, args, logger=logging.getLogger('arvados.arv-mount')):
+ self.daemon = False
self.logger = logger
self.args = args
self.listen_for_events = False
exit(1)
def __enter__(self):
+ if self.args.replace:
+ unmount(path=self.args.mountpoint,
+ timeout=self.args.unmount_timeout)
llfuse.init(self.operations, self.args.mountpoint, self._fuse_options())
+ if self.daemon:
+ daemon.DaemonContext(
+ working_directory=os.path.dirname(self.args.mountpoint),
+ files_preserve=range(
+ 3, resource.getrlimit(resource.RLIMIT_NOFILE)[1])
+ ).open()
if self.listen_for_events and not self.args.disable_event_listening:
self.operations.listen_for_events()
self.llfuse_thread = threading.Thread(None, lambda: self._llfuse_main())
return self
def __exit__(self, exc_type, exc_value, traceback):
+ if self.operations.events:
+ self.operations.events.close(timeout=self.args.unmount_timeout)
subprocess.call(["fusermount", "-u", "-z", self.args.mountpoint])
self.llfuse_thread.join(timeout=self.args.unmount_timeout)
if self.llfuse_thread.is_alive():
self.args.unmount_timeout)
def run(self):
- if self.args.exec_args:
+ if self.args.unmount or self.args.unmount_all:
+ unmount(path=self.args.mountpoint,
+ subtype=self.args.subtype,
+ timeout=self.args.unmount_timeout,
+ recursive=self.args.unmount_all)
+ elif self.args.exec_args:
self._run_exec()
else:
self._run_standalone()
if getattr(self.args, optname)]
# Increase default read/write size from 4KiB to 128KiB
opts += ["big_writes", "max_read=131072"]
+ if self.args.subtype:
+ opts += ["subtype="+self.args.subtype]
return opts
def _setup_logging(self):
# Configure a log handler based on command-line switches.
if self.args.logfile:
log_handler = logging.FileHandler(self.args.logfile)
+ log_handler.setFormatter(logging.Formatter(
+ '%(asctime)s %(name)s[%(process)d] %(levelname)s: %(message)s',
+ '%Y-%m-%d %H:%M:%S'))
else:
log_handler = None
if self.args.debug:
arvados.logger.setLevel(logging.DEBUG)
+ logging.getLogger('arvados.keep').setLevel(logging.DEBUG)
+ logging.getLogger('arvados.api').setLevel(logging.DEBUG)
+ logging.getLogger('arvados.collection').setLevel(logging.DEBUG)
self.logger.debug("arv-mount debugging enabled")
self.logger.info("enable write is %s", self.args.enable_write)
def _setup_api(self):
- self.api = arvados.safeapi.ThreadSafeApiCache(
- apiconfig=arvados.config.settings(),
- keep_params={
- 'block_cache': arvados.keep.KeepBlockCache(self.args.file_cache),
- 'num_retries': self.args.retries,
- })
+ try:
+ self.api = arvados.safeapi.ThreadSafeApiCache(
+ apiconfig=arvados.config.settings(),
+ keep_params={
+ 'block_cache': arvados.keep.KeepBlockCache(self.args.file_cache),
+ 'num_retries': self.args.retries,
+ })
+ except KeyError as e:
+ self.logger.error("Missing environment: %s", e)
+ exit(1)
# Do a sanity check that we have a working arvados host + token.
self.api.users().current().execute()
def _run_standalone(self):
try:
- llfuse.init(self.operations, self.args.mountpoint, self._fuse_options())
-
- if not self.args.foreground:
- self.daemon_ctx = daemon.DaemonContext(
- working_directory=os.path.dirname(self.args.mountpoint),
- files_preserve=range(
- 3, resource.getrlimit(resource.RLIMIT_NOFILE)[1]))
- self.daemon_ctx.open()
-
- # Subscribe to change events from API server
- if self.listen_for_events and not self.args.disable_event_listening:
- self.operations.listen_for_events()
-
- self._llfuse_main()
+ self.daemon = not self.args.foreground
+ with self:
+ self.llfuse_thread.join(timeout=None)
except Exception as e:
self.logger.exception('arv-mount: exception during mount: %s', e)
exit(getattr(e, 'errno', 1))