[ARVADOS] updated: e0e0c312ddf1f60736cc1ca7488bb36f8618440c

git at public.curoverse.com git at public.curoverse.com
Wed Feb 3 12:52:11 EST 2016


Summary of changes:
 services/fuse/arvados_fuse/command.py   | 95 +++++++++++++++++----------------
 services/fuse/tests/integration_test.py |  4 +-
 services/fuse/tests/mount_test_base.py  | 31 ++++++-----
 services/fuse/tests/test_exec.py        | 60 +++++++++++++++++++++
 tools/crunchstat-summary/MANIFEST.in    |  1 +
 5 files changed, 132 insertions(+), 59 deletions(-)
 create mode 100644 services/fuse/tests/test_exec.py

       via  e0e0c312ddf1f60736cc1ca7488bb36f8618440c (commit)
       via  c7e3518e66369b931821b802160bd014cf82b218 (commit)
       via  ce8f8ab963a12ca7f89c79cb7d6f5d19fe15b03c (commit)
       via  a3dfad4606eb8cff8bb66533a38c47d0d8c158fa (commit)
       via  1dbf2e031ecbe21aefebe8b297fb5d5e222234a7 (commit)
      from  48f61a5af0d0e6617ea545d716c5e0b132790198 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit e0e0c312ddf1f60736cc1ca7488bb36f8618440c
Merge: 1dbf2e0 c7e3518
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Feb 3 12:51:54 2016 -0500

    Merge branch '8288-arv-mount-deadlock' refs #8288


commit c7e3518e66369b931821b802160bd014cf82b218
Author: Tom Clegg <tom at curoverse.com>
Date:   Tue Feb 2 16:46:35 2016 -0500

    8288: Do not call operations.destroy() as a last resort, just abandon the llfuse thread.

diff --git a/services/fuse/arvados_fuse/command.py b/services/fuse/arvados_fuse/command.py
index ae0c920..6d53004 100644
--- a/services/fuse/arvados_fuse/command.py
+++ b/services/fuse/arvados_fuse/command.py
@@ -123,9 +123,8 @@ class Mount(object):
         if self.llfuse_thread.is_alive():
             self.logger.warning("Mount.__exit__:"
                                 " llfuse thread still alive %fs after umount"
-                                " -- resorting to operations.destroy()",
+                                " -- abandoning and exiting anyway",
                                 self.args.unmount_timeout)
-            self.operations.destroy()
 
     def run(self):
         if self.args.exec_args:
diff --git a/services/fuse/tests/mount_test_base.py b/services/fuse/tests/mount_test_base.py
index 91a4929..c79daf8 100644
--- a/services/fuse/tests/mount_test_base.py
+++ b/services/fuse/tests/mount_test_base.py
@@ -68,12 +68,11 @@ class MountTestBase(unittest.TestCase):
         del self.pool
 
         subprocess.call(["fusermount", "-u", "-z", self.mounttmp])
-        self.llfuse_thread.join(timeout=0.1)
+        self.llfuse_thread.join(timeout=1)
         if self.llfuse_thread.is_alive():
             logger.warning("MountTestBase.tearDown():"
-                           " llfuse thread still alive 100ms after umount"
-                           " -- resorting to operations.destroy()")
-            self.operations.destroy()
+                           " llfuse thread still alive 1s after umount"
+                           " -- abandoning and exiting anyway")
 
         os.rmdir(self.mounttmp)
         if self.keeptmp:

commit ce8f8ab963a12ca7f89c79cb7d6f5d19fe15b03c
Author: Tom Clegg <tom at curoverse.com>
Date:   Mon Feb 1 03:01:31 2016 -0500

    8288: Add test case for --exec mode.

diff --git a/services/fuse/tests/test_exec.py b/services/fuse/tests/test_exec.py
new file mode 100644
index 0000000..66013a4
--- /dev/null
+++ b/services/fuse/tests/test_exec.py
@@ -0,0 +1,60 @@
+import arvados_fuse.command
+import json
+import multiprocessing
+import os
+import run_test_server
+import tempfile
+import unittest
+
+try:
+    from shlex import quote
+except:
+    from pipes import quote
+
+def try_exec(mnt, cmd):
+    try:
+        arvados_fuse.command.Mount(
+            arvados_fuse.command.ArgumentParser().parse_args([
+                '--read-write',
+                '--mount-tmp=zzz',
+                '--unmount-timeout=0.1',
+                mnt,
+                '--exec'] + cmd)).run()
+    except SystemExit:
+        pass
+    else:
+        raise AssertionError('should have exited')
+
+
+class ExecMode(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        run_test_server.run()
+        run_test_server.run_keep(enforce_permissions=True, num_servers=2)
+        run_test_server.authorize_with('active')
+
+    @classmethod
+    def tearDownClass(cls):
+        run_test_server.stop_keep(num_servers=2)
+
+    def setUp(self):
+        self.mnt = tempfile.mkdtemp()
+        _, self.okfile = tempfile.mkstemp()
+        self.pool = multiprocessing.Pool(1)
+
+    def tearDown(self):
+        self.pool.terminate()
+        self.pool.join()
+        os.rmdir(self.mnt)
+        os.unlink(self.okfile)
+
+    def test_exec(self):
+        self.pool.apply(try_exec, (self.mnt, [
+            'sh', '-c',
+            'echo -n foo >{}; cp {} {}'.format(
+                quote(os.path.join(self.mnt, 'zzz', 'foo.txt')),
+                quote(os.path.join(self.mnt, 'zzz', '.arvados#collection')),
+                quote(os.path.join(self.okfile)))]))
+        self.assertRegexpMatches(
+            json.load(open(self.okfile))['manifest_text'],
+            r' 0:3:foo.txt\n')

commit a3dfad4606eb8cff8bb66533a38c47d0d8c158fa
Author: Tom Clegg <tom at curoverse.com>
Date:   Sun Jan 31 21:43:30 2016 -0500

    8288: Give fusermount -u a chance to work before resorting to operations.destroy().
    
    Log a warning when resorting to operations.destroy().
    
    De-duplicate setup/teardown code so more of the --exec code path is exercised in tests.

diff --git a/services/fuse/arvados_fuse/command.py b/services/fuse/arvados_fuse/command.py
index 71623a5..ae0c920 100644
--- a/services/fuse/arvados_fuse/command.py
+++ b/services/fuse/arvados_fuse/command.py
@@ -82,6 +82,10 @@ class ArgumentParser(argparse.ArgumentParser):
 
         self.add_argument('--crunchstat-interval', type=float, help="Write stats to stderr every N seconds (default disabled)", default=0)
 
+        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")
+
         self.add_argument('--exec', type=str, nargs=argparse.REMAINDER,
                             dest="exec_args", metavar=('command', 'args', '...', '--'),
                             help="""Mount, run a command, then unmount and exit""")
@@ -108,13 +112,20 @@ class Mount(object):
         llfuse.init(self.operations, self.args.mountpoint, self._fuse_options())
         if self.args.mode != 'by_pdh':
             self.operations.listen_for_events()
-        t = threading.Thread(None, lambda: llfuse.main())
-        t.start()
+        self.llfuse_thread = threading.Thread(None, lambda: self._llfuse_main())
+        self.llfuse_thread.daemon = True
+        self.llfuse_thread.start()
         self.operations.initlock.wait()
 
     def __exit__(self, exc_type, exc_value, traceback):
         subprocess.call(["fusermount", "-u", "-z", self.args.mountpoint])
-        self.operations.destroy()
+        self.llfuse_thread.join(timeout=self.args.unmount_timeout)
+        if self.llfuse_thread.is_alive():
+            self.logger.warning("Mount.__exit__:"
+                                " llfuse thread still alive %fs after umount"
+                                " -- resorting to operations.destroy()",
+                                self.args.unmount_timeout)
+            self.operations.destroy()
 
     def run(self):
         if self.args.exec_args:
@@ -277,63 +288,56 @@ From here, the following directories are available:
 '''.format(api_host, user_email)
 
     def _run_exec(self):
-        # Initialize the fuse connection
-        llfuse.init(self.operations, self.args.mountpoint, self._fuse_options())
-
-        # Subscribe to change events from API server
-        if self.args.mode != 'by_pdh':
-            self.operations.listen_for_events()
-
-        t = threading.Thread(None, lambda: llfuse.main())
-        t.start()
-
-        # wait until the driver is finished initializing
-        self.operations.initlock.wait()
-
         rc = 255
-        try:
-            sp = subprocess.Popen(self.args.exec_args, shell=False)
-
-            # forward signals to the process.
-            signal.signal(signal.SIGINT, lambda signum, frame: sp.send_signal(signum))
-            signal.signal(signal.SIGTERM, lambda signum, frame: sp.send_signal(signum))
-            signal.signal(signal.SIGQUIT, lambda signum, frame: sp.send_signal(signum))
-
-            # wait for process to complete.
-            rc = sp.wait()
-
-            # restore default signal handlers.
-            signal.signal(signal.SIGINT, signal.SIG_DFL)
-            signal.signal(signal.SIGTERM, signal.SIG_DFL)
-            signal.signal(signal.SIGQUIT, signal.SIG_DFL)
-        except Exception as e:
-            self.logger.exception(
-                'arv-mount: exception during exec %s', self.args.exec_args)
+        with self:
             try:
-                rc = e.errno
-            except AttributeError:
-                pass
-        finally:
-            subprocess.call(["fusermount", "-u", "-z", self.args.mountpoint])
-            self.operations.destroy()
+                sp = subprocess.Popen(self.args.exec_args, shell=False)
+
+                # forward signals to the process.
+                signal.signal(signal.SIGINT, lambda signum, frame: sp.send_signal(signum))
+                signal.signal(signal.SIGTERM, lambda signum, frame: sp.send_signal(signum))
+                signal.signal(signal.SIGQUIT, lambda signum, frame: sp.send_signal(signum))
+
+                # wait for process to complete.
+                rc = sp.wait()
+
+                # restore default signal handlers.
+                signal.signal(signal.SIGINT, signal.SIG_DFL)
+                signal.signal(signal.SIGTERM, signal.SIG_DFL)
+                signal.signal(signal.SIGQUIT, signal.SIG_DFL)
+            except Exception as e:
+                self.logger.exception(
+                    'arv-mount: exception during exec %s', self.args.exec_args)
+                try:
+                    rc = e.errno
+                except AttributeError:
+                    pass
         exit(rc)
 
     def _run_standalone(self):
         try:
             llfuse.init(self.operations, self.args.mountpoint, self._fuse_options())
 
-            if not (self.args.exec_args or 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]))
+            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
             self.operations.listen_for_events()
 
-            llfuse.main()
+            self._llfuse_main()
         except Exception as e:
             self.logger.exception('arv-mount: exception during mount: %s', e)
             exit(getattr(e, 'errno', 1))
-        finally:
-            self.operations.destroy()
         exit(0)
+
+    def _llfuse_main(self):
+        try:
+            llfuse.main()
+        except:
+            llfuse.close(unmount=False)
+            raise
+        llfuse.close()
diff --git a/services/fuse/tests/integration_test.py b/services/fuse/tests/integration_test.py
index faa4a55..5a45bfc 100644
--- a/services/fuse/tests/integration_test.py
+++ b/services/fuse/tests/integration_test.py
@@ -62,7 +62,9 @@ class IntegrationTest(unittest.TestCase):
             def wrapper(self, *args, **kwargs):
                 with arvados_fuse.command.Mount(
                         arvados_fuse.command.ArgumentParser().parse_args(
-                            argv + ['--foreground', self.mnt])):
+                            argv + ['--foreground',
+                                    '--unmount-timeout=0.1',
+                                    self.mnt])):
                     return func(self, *args, **kwargs)
             return wrapper
         return decorator
diff --git a/services/fuse/tests/mount_test_base.py b/services/fuse/tests/mount_test_base.py
index 44ec199..91a4929 100644
--- a/services/fuse/tests/mount_test_base.py
+++ b/services/fuse/tests/mount_test_base.py
@@ -37,6 +37,16 @@ class MountTestBase(unittest.TestCase):
         run_test_server.authorize_with("admin")
         self.api = api if api else arvados.safeapi.ThreadSafeApiCache(arvados.config.settings())
 
+    # This is a copy of Mount's method.  TODO: Refactor MountTestBase
+    # to use a Mount instead of copying its code.
+    def _llfuse_main(self):
+        try:
+            llfuse.main()
+        except:
+            llfuse.close(unmount=False)
+            raise
+        llfuse.close()
+
     def make_mount(self, root_class, **root_kwargs):
         self.operations = fuse.Operations(
             os.getuid(), os.getgid(),
@@ -45,7 +55,9 @@ class MountTestBase(unittest.TestCase):
         self.operations.inodes.add_entry(root_class(
             llfuse.ROOT_INODE, self.operations.inodes, self.api, 0, **root_kwargs))
         llfuse.init(self.operations, self.mounttmp, [])
-        threading.Thread(None, llfuse.main).start()
+        self.llfuse_thread = threading.Thread(None, lambda: self._llfuse_main())
+        self.llfuse_thread.daemon = True
+        self.llfuse_thread.start()
         # wait until the driver is finished initializing
         self.operations.initlock.wait()
         return self.operations.inodes[llfuse.ROOT_INODE]
@@ -55,17 +67,13 @@ class MountTestBase(unittest.TestCase):
         self.pool.join()
         del self.pool
 
-        # llfuse.close is buggy, so use fusermount instead.
-        #llfuse.close(unmount=True)
-
-        count = 0
-        success = 1
-        while (count < 9 and success != 0):
-          success = subprocess.call(["fusermount", "-u", self.mounttmp])
-          time.sleep(0.1)
-          count += 1
-
-        self.operations.destroy()
+        subprocess.call(["fusermount", "-u", "-z", self.mounttmp])
+        self.llfuse_thread.join(timeout=0.1)
+        if self.llfuse_thread.is_alive():
+            logger.warning("MountTestBase.tearDown():"
+                           " llfuse thread still alive 100ms after umount"
+                           " -- resorting to operations.destroy()")
+            self.operations.destroy()
 
         os.rmdir(self.mounttmp)
         if self.keeptmp:

commit 1dbf2e031ecbe21aefebe8b297fb5d5e222234a7
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Feb 3 12:50:31 2016 -0500

    8123: Install chartjs.js asset file.
    
    ...during "setup.py install" too, not just when installing via
    package.
    
    refs #8123

diff --git a/tools/crunchstat-summary/MANIFEST.in b/tools/crunchstat-summary/MANIFEST.in
index 4db8152..325ee59 100644
--- a/tools/crunchstat-summary/MANIFEST.in
+++ b/tools/crunchstat-summary/MANIFEST.in
@@ -1 +1,2 @@
 include agpl-3.0.txt
+include crunchstat_summary/chartjs.js

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list