[ARVADOS] updated: 2b1a709c1e3ff70d811804bfd9fa58a7231bd5cb

git at public.curoverse.com git at public.curoverse.com
Wed Feb 19 15:23:37 EST 2014


Summary of changes:
 sdk/python/{bin/arv-mount => arvados/fuse.py} |   51 +----
 sdk/python/bin/arv-mount                      |  326 +------------------------
 sdk/python/requirements.txt                   |    2 +-
 sdk/python/test_mount.py                      |   98 ++++++++
 4 files changed, 115 insertions(+), 362 deletions(-)
 copy sdk/python/{bin/arv-mount => arvados/fuse.py} (86%)
 mode change 100755 => 100644
 create mode 100644 sdk/python/test_mount.py

       via  2b1a709c1e3ff70d811804bfd9fa58a7231bd5cb (commit)
      from  08c4e44a6a431286408ce9984bc4be84dccb49f7 (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 2b1a709c1e3ff70d811804bfd9fa58a7231bd5cb
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Feb 19 15:24:43 2014 -0500

    Added basic unit test for fuse mount.

diff --git a/sdk/python/bin/arv-mount b/sdk/python/arvados/fuse.py
old mode 100755
new mode 100644
similarity index 86%
copy from sdk/python/bin/arv-mount
copy to sdk/python/arvados/fuse.py
index 91ba203..5a56e85
--- a/sdk/python/bin/arv-mount
+++ b/sdk/python/arvados/fuse.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 #
 # FUSE driver for Arvados Keep
 #
@@ -159,6 +157,14 @@ class Operations(llfuse.Operations):
         self._filehandles = {}
         self._filehandles_counter = 1
 
+        # Other threads that need to wait until the fuse driver
+        # is fully initialized should wait() on this event object.
+        self.initlock = threading.Event()
+
+    def init(self):
+        # Allow threads that are waiting for the driver to be finished
+        # initializing to continue
+        self.initlock.set()
 
     def access(self, inode, mode, ctx):
         return True
@@ -310,44 +316,3 @@ class Operations(llfuse.Operations):
     # and then everything works out.
     def create(self, p1, p2, p3, p4, p5):
         raise llfuse.FUSEError(errno.EROFS)
-
-
-if __name__ == '__main__':
-    # Handle command line parameters
-    parser = argparse.ArgumentParser(
-        description='Mount Keep data under the local filesystem.')
-    parser.add_argument('mountpoint', type=str, help="""Mount point.""")
-    parser.add_argument('--collection', type=str, help="""Collection locator""")
-    parser.add_argument('--debug', action='store_true', help="""Debug mode""")
-
-    args = parser.parse_args()
-
-    # Create the request handler
-    operations = Operations(os.getuid(), os.getgid())
-
-    if args.collection != None:
-        # Set up the request handler with the collection at the root
-        e = operations.inodes.add_entry(Directory(llfuse.ROOT_INODE))
-        operations.inodes.load_collection(e, arvados.CollectionReader(arvados.Keep.get(args.collection)))
-    else:
-        # Set up the request handler with the 'magic directory' at the root
-        operations.inodes.add_entry(MagicDirectory(llfuse.ROOT_INODE, operations.inodes))
-
-    # FUSE options, see mount.fuse(8)
-    opts = []
-
-    # Enable FUSE debugging (logs each FUSE request)
-    if args.debug:
-        opts += ['debug']
-    
-    # Initialize the fuse connection
-    llfuse.init(operations, args.mountpoint, opts)
-
-    # Run the main loop
-    try:
-        llfuse.main()
-    except:
-        llfuse.close(unmount=True)
-        raise
-
-    llfuse.close()
diff --git a/sdk/python/bin/arv-mount b/sdk/python/bin/arv-mount
index 91ba203..4a8233a 100755
--- a/sdk/python/bin/arv-mount
+++ b/sdk/python/bin/arv-mount
@@ -1,316 +1,6 @@
 #!/usr/bin/env python
 
-#
-# FUSE driver for Arvados Keep
-#
-
-import os
-import sys
-
-import llfuse
-import errno
-import stat
-import threading
-import arvados
-import argparse
-import pprint
-
-from time import time
-from llfuse import FUSEError
-
-class Directory(object):
-    '''Generic directory object, backed by a dict.
-    Consists of a set of entries with the key representing the filename
-    and the value referencing a File or Directory object.
-    '''
-
-    def __init__(self, parent_inode):
-        self.inode = None
-        self.parent_inode = parent_inode
-        self._entries = {}
-
-    def __getitem__(self, item):
-        return self._entries[item]
-
-    def __setitem__(self, key, item):
-        self._entries[key] = item
-
-    def __iter__(self):
-        return self._entries.iterkeys()
-
-    def items(self):
-        return self._entries.items()
-
-    def __contains__(self, k):
-        return k in self._entries
-
-    def size(self):
-        return 0
-
-class MagicDirectory(Directory):
-    '''A special directory that logically contains the set of all extant
-    keep locators.  When a file is referenced by lookup(), it is tested
-    to see if it is a valid keep locator to a manifest, and if so, loads the manifest
-    contents as a subdirectory of this directory with the locator as the directory name.
-    Since querying a list of all extant keep locators is impractical, only loaded collections 
-    are visible to readdir().'''
-
-    def __init__(self, parent_inode, inodes):
-        super(MagicDirectory, self).__init__(parent_inode)
-        self.inodes = inodes
-
-    def __contains__(self, k):
-        if k in self._entries:
-            return True
-        try:
-            if arvados.Keep.get(k):
-                return True
-            else:
-                return False
-        except Exception as e:
-            #print 'exception keep', e
-            return False
-
-    def __getitem__(self, item):
-        if item not in self._entries:
-            collection = arvados.CollectionReader(arvados.Keep.get(item))
-            self._entries[item] = self.inodes.add_entry(Directory(self.inode))
-            self.inodes.load_collection(self._entries[item], collection)
-        return self._entries[item]
-
-class File(object):
-    '''Wraps a StreamFileReader for use by Directory.'''
-
-    def __init__(self, parent_inode, reader):
-        self.inode = None
-        self.parent_inode = parent_inode
-        self.reader = reader
-
-    def size(self):
-        return self.reader.size()
-
-class FileHandle(object):
-    '''Connects a numeric file handle to a File or Directory object that has 
-    been opened by the client.'''
-
-    def __init__(self, fh, entry):
-        self.fh = fh
-        self.entry = entry
-
-class Inodes(object):
-    '''Manage the set of inodes.  This is the mapping from a numeric id 
-    to a concrete File or Directory object'''
-
-    def __init__(self):
-        self._entries = {}
-        self._counter = llfuse.ROOT_INODE
-
-    def __getitem__(self, item):
-        return self._entries[item]
-
-    def __setitem__(self, key, item):
-        self._entries[key] = item
-
-    def __iter__(self):
-        return self._entries.iterkeys()
-
-    def items(self):
-        return self._entries.items()
-
-    def __contains__(self, k):
-        return k in self._entries
-
-    def load_collection(self, parent_dir, collection):
-        '''parent_dir is the Directory object that will be populated by the collection.
-        collection is the arvados.CollectionReader to use as the source'''
-        for s in collection.all_streams():
-            cwd = parent_dir
-            for part in s.name().split('/'):
-                if part != '' and part != '.':
-                    if part not in cwd:
-                        cwd[part] = self.add_entry(Directory(cwd.inode))
-                    cwd = cwd[part]
-            for k, v in s.files().items():
-                cwd[k] = self.add_entry(File(cwd.inode, v))
-
-    def add_entry(self, entry):
-        entry.inode = self._counter
-        self._entries[entry.inode] = entry
-        self._counter += 1
-        return entry    
-
-class Operations(llfuse.Operations):
-    '''This is the main interface with llfuse.  The methods on this object are
-    called by llfuse threads to service FUSE events to query and read from 
-    the file system.
-
-    llfuse has its own global lock which is acquired before calling a request handler,
-    so request handlers do not run concurrently unless the lock is explicitly released 
-    with llfuse.lock_released.'''
-
-    def __init__(self, uid, gid):
-        super(Operations, self).__init__()
-
-        self.inodes = Inodes()
-        self.uid = uid
-        self.gid = gid
-        
-        # dict of inode to filehandle
-        self._filehandles = {}
-        self._filehandles_counter = 1
-
-
-    def access(self, inode, mode, ctx):
-        return True
-   
-    def getattr(self, inode):
-        e = self.inodes[inode]
-
-        entry = llfuse.EntryAttributes()
-        entry.st_ino = inode
-        entry.generation = 0
-        entry.entry_timeout = 300
-        entry.attr_timeout = 300
-
-        entry.st_mode = stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH
-        if isinstance(e, Directory):
-            entry.st_mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH | stat.S_IFDIR
-        else:
-            entry.st_mode |= stat.S_IFREG
-
-        entry.st_nlink = 1
-        entry.st_uid = self.uid
-        entry.st_gid = self.gid
-        entry.st_rdev = 0
-
-        entry.st_size = e.size()
-
-        entry.st_blksize = 1024
-        entry.st_blocks = e.size()/1024
-        if e.size()/1024 != 0:
-            entry.st_blocks += 1
-        entry.st_atime = 0
-        entry.st_mtime = 0
-        entry.st_ctime = 0
-
-        return entry
-
-    def lookup(self, parent_inode, name):
-        #print "lookup: parent_inode", parent_inode, "name", name
-        inode = None
-
-        if name == '.':
-            inode = parent_inode
-        else:
-            if parent_inode in self.inodes:
-                p = self.inodes[parent_inode]
-                if name == '..':
-                    inode = p.parent_inode
-                elif name in p:
-                    inode = p[name].inode
-
-        if inode != None:
-            return self.getattr(inode)
-        else:
-            raise llfuse.FUSEError(errno.ENOENT)
-   
-    def open(self, inode, flags):
-        if inode in self.inodes:
-            p = self.inodes[inode]
-        else:
-            raise llfuse.FUSEError(errno.ENOENT)
-
-        if (flags & os.O_WRONLY) or (flags & os.O_RDWR):
-            raise llfuse.FUSEError(errno.EROFS)
-
-        if isinstance(p, Directory):
-            raise llfuse.FUSEError(errno.EISDIR)
-
-        fh = self._filehandles_counter
-        self._filehandles_counter += 1
-        self._filehandles[fh] = FileHandle(fh, p)
-        return fh
-
-    def read(self, fh, off, size):
-        #print "read", fh, off, size
-        if fh in self._filehandles:
-            handle = self._filehandles[fh]
-        else:
-            raise llfuse.FUSEError(errno.EBADF)
-
-        try:
-            with llfuse.lock_released:
-                return handle.entry.reader.readfrom(off, size)
-        except:
-            raise llfuse.FUSEError(errno.EIO)
-
-    def release(self, fh):
-        if fh in self._filehandles:
-            del self._filehandles[fh]
-
-    def opendir(self, inode):
-        #print "opendir: inode", inode
-
-        if inode in self.inodes:
-            p = self.inodes[inode]
-        else:
-            raise llfuse.FUSEError(errno.ENOENT)
-
-        if not isinstance(p, Directory):
-            raise llfuse.FUSEError(errno.ENOTDIR)
-
-        fh = self._filehandles_counter
-        self._filehandles_counter += 1
-        if p.parent_inode in self.inodes:
-            parent = self.inodes[p.parent_inode]
-        else:
-            parent = None
-        self._filehandles[fh] = FileHandle(fh, [('.', p), ('..', parent)] + list(p.items()))
-        return fh
-
-    def readdir(self, fh, off):
-        #print "readdir: fh", fh, "off", off
-
-        if fh in self._filehandles:
-            handle = self._filehandles[fh]
-        else:
-            raise llfuse.FUSEError(errno.EBADF)
-
-        #print "handle.entry", handle.entry
-
-        e = off
-        while e < len(handle.entry):
-            yield (handle.entry[e][0], self.getattr(handle.entry[e][1].inode), e+1)
-            e += 1
-
-    def releasedir(self, fh):
-        del self._filehandles[fh]
-
-    def statfs(self):
-        st = llfuse.StatvfsData()
-        st.f_bsize = 4096
-        st.f_blocks = 0
-        st.f_files = 0
-
-        st.f_bfree = 0
-        st.f_bavail = 0
-
-        st.f_ffree = 0
-        st.f_favail = 0
-
-        st.f_frsize = 0
-        return st
-
-    # The llfuse documentation recommends only overloading functions that
-    # are actually implemented, as the default implementation will raise ENOSYS.
-    # However, there is a bug in the llfuse default implementation of create()
-    # "create() takes exactly 5 positional arguments (6 given)" which will crash
-    # arv-mount.
-    # The workaround is to implement it with the proper number of parameters,
-    # and then everything works out.
-    def create(self, p1, p2, p3, p4, p5):
-        raise llfuse.FUSEError(errno.EROFS)
-
+from arvados.fuse import * 
 
 if __name__ == '__main__':
     # Handle command line parameters
@@ -344,10 +34,10 @@ if __name__ == '__main__':
     llfuse.init(operations, args.mountpoint, opts)
 
     # Run the main loop
-    try:
-        llfuse.main()
-    except:
-        llfuse.close(unmount=True)
-        raise
-
-    llfuse.close()
+    #try:
+    llfuse.main()
+    #except:
+    #    llfuse.close(unmount=True)
+    #    raise
+    #
+    #llfuse.close(unmount=True)
diff --git a/sdk/python/requirements.txt b/sdk/python/requirements.txt
index 5aef1a8..16dcffe 100644
--- a/sdk/python/requirements.txt
+++ b/sdk/python/requirements.txt
@@ -2,4 +2,4 @@ google-api-python-client==1.2
 httplib2==0.8
 python-gflags==2.0
 urllib3==1.7.1
-llfuse=0.40
+llfuse==0.40
diff --git a/sdk/python/test_mount.py b/sdk/python/test_mount.py
new file mode 100644
index 0000000..c361de2
--- /dev/null
+++ b/sdk/python/test_mount.py
@@ -0,0 +1,98 @@
+import unittest
+import arvados
+import arvados.fuse as fuse
+import threading
+import time
+import os
+import llfuse
+import tempfile
+import shutil
+import subprocess
+import glob
+
+class FuseMountTest(unittest.TestCase):
+    def setUp(self):
+        self.keeptmp = tempfile.mkdtemp()
+        os.environ['KEEP_LOCAL_STORE'] = self.keeptmp
+
+        cw = arvados.CollectionWriter()
+
+        cw.start_new_file('thing1.txt')
+        cw.write("data 1")
+        cw.start_new_file('thing2.txt')
+        cw.write("data 2")
+        cw.start_new_stream('dir1')
+
+        cw.start_new_file('thing3.txt')
+        cw.write("data 3")
+        cw.start_new_file('thing4.txt')
+        cw.write("data 4")
+
+        cw.start_new_stream('dir2')
+        cw.start_new_file('thing5.txt')
+        cw.write("data 5")
+        cw.start_new_file('thing6.txt')
+        cw.write("data 6")
+
+        cw.start_new_stream('dir2/dir3')
+        cw.start_new_file('thing7.txt')
+        cw.write("data 7")
+
+        cw.start_new_file('thing8.txt')
+        cw.write("data 8")
+
+        self.testcollection = cw.finish()
+
+    def runTest(self):
+        # Create the request handler
+        operations = fuse.Operations(os.getuid(), os.getgid())
+        e = operations.inodes.add_entry(fuse.Directory(llfuse.ROOT_INODE))
+        operations.inodes.load_collection(e, arvados.CollectionReader(arvados.Keep.get(self.testcollection)))
+
+        self.mounttmp = tempfile.mkdtemp()
+
+        llfuse.init(operations, self.mounttmp, [])
+        t = threading.Thread(None, lambda: llfuse.main())
+        t.start()
+
+        # wait until the driver is finished initializing
+        operations.initlock.wait()
+
+        # now check some stuff
+        d1 = os.listdir(self.mounttmp)
+        d1.sort()
+        self.assertEqual(d1, ['dir1', 'dir2', 'thing1.txt', 'thing2.txt'])
+
+        d2 = os.listdir(os.path.join(self.mounttmp, 'dir1'))
+        d2.sort()
+        self.assertEqual(d2, ['thing3.txt', 'thing4.txt'])
+
+        d3 = os.listdir(os.path.join(self.mounttmp, 'dir2'))
+        d3.sort()
+        self.assertEqual(d3, ['dir3', 'thing5.txt', 'thing6.txt'])
+
+        d4 = os.listdir(os.path.join(self.mounttmp, 'dir2/dir3'))
+        d4.sort()
+        self.assertEqual(d4, ['thing7.txt', 'thing8.txt'])
+        
+        files = {'thing1.txt': 'data 1',
+                 'thing2.txt': 'data 2',
+                 'dir1/thing3.txt': 'data 3',
+                 'dir1/thing4.txt': 'data 4',
+                 'dir2/thing5.txt': 'data 5',
+                 'dir2/thing6.txt': 'data 6',         
+                 'dir2/dir3/thing7.txt': 'data 7',
+                 'dir2/dir3/thing8.txt': 'data 8'}
+
+        for k, v in files.items():
+            with open(os.path.join(self.mounttmp, k)) as f:
+                self.assertEqual(f.read(), v)
+        
+
+    def tearDown(self):
+        # llfuse.close is buggy, so use fusermount instead.
+        #llfuse.close(unmount=True)
+        subprocess.call(["fusermount", "-u", self.mounttmp])
+
+        os.rmdir(self.mounttmp)
+        shutil.rmtree(self.keeptmp)

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list