Merge branch 'master' into 3187-pipeline-instance-page
[arvados.git] / services / fuse / arvados_fuse / __init__.py
index 607a9413fcf4bb56e2158a21676485d47264537a..e40b88ce9e24db679a4e1b9bb685aaafbd5d11d2 100644 (file)
@@ -19,16 +19,10 @@ import logging
 import time
 import calendar
 import threading
 import time
 import calendar
 import threading
+from arvados.util import portable_data_hash_pattern, uuid_pattern, collection_uuid_pattern, group_uuid_pattern, user_uuid_pattern, link_uuid_pattern
 
 _logger = logging.getLogger('arvados.arvados_fuse')
 
 
 _logger = logging.getLogger('arvados.arvados_fuse')
 
-portable_data_hash_pattern = re.compile(r'[0-9a-f]{32}\+\d+')
-uuid_pattern = re.compile(r'[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}')
-collection_uuid_pattern = re.compile(r'[a-z0-9]{5}-4zz18-[a-z0-9]{15}')
-group_uuid_pattern = re.compile(r'[a-z0-9]{5}-j7d0g-[a-z0-9]{15}')
-user_uuid_pattern = re.compile(r'[a-z0-9]{5}-tpzed-[a-z0-9]{15}')
-link_uuid_pattern = re.compile(r'[a-z0-9]{5}-o0j2j-[a-z0-9]{15}')
-
 class SafeApi(object):
     '''Threadsafe wrapper for API object.  This stores and returns a different api
     object per thread, because httplib2 which underlies apiclient is not
 class SafeApi(object):
     '''Threadsafe wrapper for API object.  This stores and returns a different api
     object per thread, because httplib2 which underlies apiclient is not
@@ -37,30 +31,36 @@ class SafeApi(object):
 
     def __init__(self, config):
         self.host = config.get('ARVADOS_API_HOST')
 
     def __init__(self, config):
         self.host = config.get('ARVADOS_API_HOST')
-        self.token = config.get('ARVADOS_API_TOKEN')
+        self.api_token = config.get('ARVADOS_API_TOKEN')
         self.insecure = config.flag_is_true('ARVADOS_API_HOST_INSECURE')
         self.local = threading.local()
         self.insecure = config.flag_is_true('ARVADOS_API_HOST_INSECURE')
         self.local = threading.local()
+        self.block_cache = arvados.KeepBlockCache()
 
     def localapi(self):
         if 'api' not in self.local.__dict__:
 
     def localapi(self):
         if 'api' not in self.local.__dict__:
-            self.local.api = arvados.api('v1', False, self.host, self.token, self.insecure)
+            self.local.api = arvados.api('v1', False, self.host,
+                                         self.api_token, self.insecure)
         return self.local.api
 
         return self.local.api
 
-    def collections(self):
-        return self.localapi().collections()
+    def localkeep(self):
+        if 'keep' not in self.local.__dict__:
+            self.local.keep = arvados.KeepClient(api_client=self.localapi(), block_cache=self.block_cache)
+        return self.local.keep
 
 
-    def links(self):
-        return self.localapi().links()
+    def __getattr__(self, name):
+        # Proxy nonexistent attributes to the local API client.
+        try:
+            return getattr(self.localapi(), name)
+        except AttributeError:
+            return super(SafeApi, self).__getattr__(name)
 
 
-    def groups(self):
-        return self.localapi().groups()
 
 
-    def users(self):
-        return self.localapi().users()
-        
 def convertTime(t):
     '''Parse Arvados timestamp to unix time.'''
 def convertTime(t):
     '''Parse Arvados timestamp to unix time.'''
-    return calendar.timegm(time.strptime(t, "%Y-%m-%dT%H:%M:%SZ"))
+    try:
+        return calendar.timegm(time.strptime(t, "%Y-%m-%dT%H:%M:%SZ"))
+    except (TypeError, ValueError):
+        return 0
 
 def sanitize_filename(dirty):
     '''Remove troublesome characters from filenames.'''
 
 def sanitize_filename(dirty):
     '''Remove troublesome characters from filenames.'''
@@ -89,39 +89,35 @@ class FreshBase(object):
         self._stale = True
         self._poll = False
         self._last_update = time.time()
         self._stale = True
         self._poll = False
         self._last_update = time.time()
+        self._atime = time.time()
         self._poll_time = 60
 
     # Mark the value as stale
     def invalidate(self):
         self._stale = True
 
         self._poll_time = 60
 
     # Mark the value as stale
     def invalidate(self):
         self._stale = True
 
-    # Test if the entries dict is stale
+    # Test if the entries dict is stale.
     def stale(self):
         if self._stale:
             return True
         if self._poll:
     def stale(self):
         if self._stale:
             return True
         if self._poll:
-            return (self._last_update + self._poll_time) < time.time()
+            return (self._last_update + self._poll_time) < self._atime
         return False
 
     def fresh(self):
         self._stale = False
         self._last_update = time.time()
 
         return False
 
     def fresh(self):
         self._stale = False
         self._last_update = time.time()
 
-    def ctime(self):
-        return 0
-
-    def mtime(self):
-        return 0
-
+    def atime(self):
+        return self._atime
 
 class File(FreshBase):
     '''Base for file objects.'''
 
 
 class File(FreshBase):
     '''Base for file objects.'''
 
-    def __init__(self, parent_inode, _ctime=0, _mtime=0):
+    def __init__(self, parent_inode, _mtime=0):
         super(File, self).__init__()
         self.inode = None
         self.parent_inode = parent_inode
         super(File, self).__init__()
         self.inode = None
         self.parent_inode = parent_inode
-        self._ctime = _ctime
         self._mtime = _mtime
 
     def size(self):
         self._mtime = _mtime
 
     def size(self):
@@ -130,9 +126,6 @@ class File(FreshBase):
     def readfrom(self, off, size):
         return ''
 
     def readfrom(self, off, size):
         return ''
 
-    def ctime(self):
-        return self._ctime
-
     def mtime(self):
         return self._mtime
 
     def mtime(self):
         return self._mtime
 
@@ -140,8 +133,8 @@ class File(FreshBase):
 class StreamReaderFile(File):
     '''Wraps a StreamFileReader as a file.'''
 
 class StreamReaderFile(File):
     '''Wraps a StreamFileReader as a file.'''
 
-    def __init__(self, parent_inode, reader, _ctime, _mtime):
-        super(StreamReaderFile, self).__init__(parent_inode, _ctime, _mtime)
+    def __init__(self, parent_inode, reader, _mtime):
+        super(StreamReaderFile, self).__init__(parent_inode, _mtime)
         self.reader = reader
 
     def size(self):
         self.reader = reader
 
     def size(self):
@@ -156,27 +149,26 @@ class StreamReaderFile(File):
 
 class StringFile(File):
     '''Wrap a simple string as a file'''
 
 class StringFile(File):
     '''Wrap a simple string as a file'''
-    def __init__(self, parent_inode, contents, _ctime, _mtime):
-        super(StringFile, self).__init__(parent_inode, _ctime, _mtime)
+    def __init__(self, parent_inode, contents, _mtime):
+        super(StringFile, self).__init__(parent_inode, _mtime)
         self.contents = contents
 
     def size(self):
         return len(self.contents)
 
     def readfrom(self, off, size):
         self.contents = contents
 
     def size(self):
         return len(self.contents)
 
     def readfrom(self, off, size):
-        return self.contents[off:(off+size)]    
+        return self.contents[off:(off+size)]
 
 
 class ObjectFile(StringFile):
     '''Wrap a dict as a serialized json object.'''
 
     def __init__(self, parent_inode, obj):
 
 
 class ObjectFile(StringFile):
     '''Wrap a dict as a serialized json object.'''
 
     def __init__(self, parent_inode, obj):
-        super(ObjectFile, self).__init__(parent_inode, "", 0, 0)
+        super(ObjectFile, self).__init__(parent_inode, "", 0)
         self.uuid = obj['uuid']
         self.update(obj)
 
     def update(self, obj):
         self.uuid = obj['uuid']
         self.update(obj)
 
     def update(self, obj):
-        self._ctime = convertTime(obj['created_at']) if 'created_at' in obj else 0
         self._mtime = convertTime(obj['modified_at']) if 'modified_at' in obj else 0
         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
 
         self._mtime = convertTime(obj['modified_at']) if 'modified_at' in obj else 0
         self.contents = json.dumps(obj, indent=4, sort_keys=True) + "\n"
 
@@ -196,6 +188,7 @@ class Directory(FreshBase):
             raise Exception("parent_inode should be an int")
         self.parent_inode = parent_inode
         self._entries = {}
             raise Exception("parent_inode should be an int")
         self.parent_inode = parent_inode
         self._entries = {}
+        self._mtime = time.time()
 
     #  Overriden by subclasses to implement logic to update the entries dict
     #  when the directory is stale
 
     #  Overriden by subclasses to implement logic to update the entries dict
     #  when the directory is stale
@@ -252,6 +245,7 @@ class Directory(FreshBase):
 
         oldentries = self._entries
         self._entries = {}
 
         oldentries = self._entries
         self._entries = {}
+        changed = False
         for i in items:
             name = sanitize_filename(fn(i))
             if name:
         for i in items:
             name = sanitize_filename(fn(i))
             if name:
@@ -264,11 +258,17 @@ class Directory(FreshBase):
                     ent = new_entry(i)
                     if ent is not None:
                         self._entries[name] = self.inodes.add_entry(ent)
                     ent = new_entry(i)
                     if ent is not None:
                         self._entries[name] = self.inodes.add_entry(ent)
+                        changed = True
 
         # delete any other directory entries that were not in found in 'items'
 
         # delete any other directory entries that were not in found in 'items'
-        for i in oldentries:            
+        for i in oldentries:
             llfuse.invalidate_entry(self.inode, str(i))
             self.inodes.del_entry(oldentries[i])
             llfuse.invalidate_entry(self.inode, str(i))
             self.inodes.del_entry(oldentries[i])
+            changed = True
+
+        if changed:
+            self._mtime = time.time()
+
         self.fresh()
 
     def clear(self):
         self.fresh()
 
     def clear(self):
@@ -282,14 +282,18 @@ class Directory(FreshBase):
             self.inodes.del_entry(oldentries[n])
         self.invalidate()
 
             self.inodes.del_entry(oldentries[n])
         self.invalidate()
 
+    def mtime(self):
+        return self._mtime
+
 
 class CollectionDirectory(Directory):
     '''Represents the root of a directory tree holding a collection.'''
 
 
 class CollectionDirectory(Directory):
     '''Represents the root of a directory tree holding a collection.'''
 
-    def __init__(self, parent_inode, inodes, api, collection):
+    def __init__(self, parent_inode, inodes, api, num_retries, collection):
         super(CollectionDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
         super(CollectionDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
+        self.num_retries = num_retries
         self.collection_object_file = None
         self.collection_object = None
         if isinstance(collection, dict):
         self.collection_object_file = None
         self.collection_object = None
         if isinstance(collection, dict):
@@ -307,7 +311,9 @@ class CollectionDirectory(Directory):
             self.collection_object_file.update(self.collection_object)
 
         self.clear()
             self.collection_object_file.update(self.collection_object)
 
         self.clear()
-        collection = arvados.CollectionReader(self.collection_object["manifest_text"], self.api)
+        collection = arvados.CollectionReader(
+            self.collection_object["manifest_text"], self.api,
+            self.api.localkeep(), num_retries=self.num_retries)
         for s in collection.all_streams():
             cwd = self
             for part in s.name().split('/'):
         for s in collection.all_streams():
             cwd = self
             for part in s.name().split('/'):
@@ -317,7 +323,7 @@ class CollectionDirectory(Directory):
                         cwd._entries[partname] = self.inodes.add_entry(Directory(cwd.inode))
                     cwd = cwd._entries[partname]
             for k, v in s.files().items():
                         cwd._entries[partname] = self.inodes.add_entry(Directory(cwd.inode))
                     cwd = cwd._entries[partname]
             for k, v in s.files().items():
-                cwd._entries[sanitize_filename(k)] = self.inodes.add_entry(StreamReaderFile(cwd.inode, v, self.ctime(), self.mtime()))        
+                cwd._entries[sanitize_filename(k)] = self.inodes.add_entry(StreamReaderFile(cwd.inode, v, self.mtime()))
 
     def update(self):
         try:
 
     def update(self):
         try:
@@ -325,7 +331,9 @@ class CollectionDirectory(Directory):
                 return True
 
             with llfuse.lock_released:
                 return True
 
             with llfuse.lock_released:
-                new_collection_object = self.api.collections().get(uuid=self.collection_locator).execute()
+                new_collection_object = self.api.collections().get(
+                    uuid=self.collection_locator
+                    ).execute(num_retries=self.num_retries)
                 if "portable_data_hash" not in new_collection_object:
                     new_collection_object["portable_data_hash"] = new_collection_object["uuid"]
             # end with llfuse.lock_released, re-acquire lock
                 if "portable_data_hash" not in new_collection_object:
                     new_collection_object["portable_data_hash"] = new_collection_object["uuid"]
             # end with llfuse.lock_released, re-acquire lock
@@ -341,11 +349,15 @@ class CollectionDirectory(Directory):
             else:
                 _logger.error("arv-mount %s: error", self.collection_locator)
                 _logger.exception(detail)
             else:
                 _logger.error("arv-mount %s: error", self.collection_locator)
                 _logger.exception(detail)
+        except arvados.errors.ArgumentError as detail:
+            _logger.warning("arv-mount %s: error %s", self.collection_locator, detail)
+            if self.collection_object is not None and "manifest_text" in self.collection_object:
+                _logger.warning("arv-mount manifest_text is: %s", self.collection_object["manifest_text"])
         except Exception as detail:
             _logger.error("arv-mount %s: error", self.collection_locator)
         except Exception as detail:
             _logger.error("arv-mount %s: error", self.collection_locator)
-            if "manifest_text" in self.collection_object:
+            if self.collection_object is not None and "manifest_text" in self.collection_object:
                 _logger.error("arv-mount manifest_text is: %s", self.collection_object["manifest_text"])
                 _logger.error("arv-mount manifest_text is: %s", self.collection_object["manifest_text"])
-            _logger.exception(detail)                
+            _logger.exception(detail)
         return False
 
     def __getitem__(self, item):
         return False
 
     def __getitem__(self, item):
@@ -364,10 +376,6 @@ class CollectionDirectory(Directory):
         else:
             return super(CollectionDirectory, self).__contains__(k)
 
         else:
             return super(CollectionDirectory, self).__contains__(k)
 
-    def ctime(self):
-        self.checkupdate()
-        return convertTime(self.collection_object["created_at"]) if self.collection_object is not None and 'created_at' in self.collection_object else 0
-
     def mtime(self):
         self.checkupdate()
         return convertTime(self.collection_object["modified_at"]) if self.collection_object is not None and 'modified_at' in self.collection_object else 0
     def mtime(self):
         self.checkupdate()
         return convertTime(self.collection_object["modified_at"]) if self.collection_object is not None and 'modified_at' in self.collection_object else 0
@@ -383,13 +391,16 @@ class MagicDirectory(Directory):
     to readdir().
     '''
 
     to readdir().
     '''
 
-    def __init__(self, parent_inode, inodes, api):
+    def __init__(self, parent_inode, inodes, api, num_retries):
         super(MagicDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
         super(MagicDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
+        self.num_retries = num_retries
+        # Have to defer creating readme_file because at this point we don't
+        # yet have an inode assigned.
         self.readme_file = None
 
         self.readme_file = None
 
-    def __contains__(self, k):
+    def create_readme(self):
         if self.readme_file is None:
             text = '''This directory provides access to Arvados collections as subdirectories listed
 by uuid (in the form 'zzzzz-4zz18-1234567890abcde') or portable data hash (in
         if self.readme_file is None:
             text = '''This directory provides access to Arvados collections as subdirectories listed
 by uuid (in the form 'zzzzz-4zz18-1234567890abcde') or portable data hash (in
@@ -400,9 +411,12 @@ specific collection subdirectory (such as trying to 'cd' into it), at which
 point the collection will actually be looked up on the server and the directory
 will appear if it exists.
 '''
 point the collection will actually be looked up on the server and the directory
 will appear if it exists.
 '''
-            self.readme_file = self.inodes.add_entry(StringFile(self.inode, text, 0, 0))
+            self.readme_file = self.inodes.add_entry(StringFile(self.inode, text, time.time()))
             self._entries["README"] = self.readme_file
 
             self._entries["README"] = self.readme_file
 
+    def __contains__(self, k):
+        self.create_readme()
+
         if k in self._entries:
             return True
 
         if k in self._entries:
             return True
 
@@ -410,7 +424,8 @@ will appear if it exists.
             return False
 
         try:
             return False
 
         try:
-            e = self.inodes.add_entry(CollectionDirectory(self.inode, self.inodes, self.api, k))
+            e = self.inodes.add_entry(CollectionDirectory(
+                    self.inode, self.inodes, self.api, self.num_retries, k))
             if e.update():
                 self._entries[k] = e
                 return True
             if e.update():
                 self._entries[k] = e
                 return True
@@ -420,6 +435,10 @@ will appear if it exists.
             _logger.debug('arv-mount exception keep %s', e)
             return False
 
             _logger.debug('arv-mount exception keep %s', e)
             return False
 
+    def items(self):
+        self.create_readme()
+        return self._entries.items()
+
     def __getitem__(self, item):
         if item in self:
             return self._entries[item]
     def __getitem__(self, item):
         if item in self:
             return self._entries[item]
@@ -445,24 +464,25 @@ class RecursiveInvalidateDirectory(Directory):
 class TagsDirectory(RecursiveInvalidateDirectory):
     '''A special directory that contains as subdirectories all tags visible to the user.'''
 
 class TagsDirectory(RecursiveInvalidateDirectory):
     '''A special directory that contains as subdirectories all tags visible to the user.'''
 
-    def __init__(self, parent_inode, inodes, api, poll_time=60):
+    def __init__(self, parent_inode, inodes, api, num_retries, poll_time=60):
         super(TagsDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
         super(TagsDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
-        #try:
-        #    arvados.events.subscribe(self.api, [['object_uuid', 'is_a', 'arvados#link']], lambda ev: self.invalidate())
-        #except:
+        self.num_retries = num_retries
         self._poll = True
         self._poll_time = poll_time
 
     def update(self):
         with llfuse.lock_released:
         self._poll = True
         self._poll_time = poll_time
 
     def update(self):
         with llfuse.lock_released:
-            tags = self.api.links().list(filters=[['link_class', '=', 'tag']], select=['name'], distinct = True).execute()
+            tags = self.api.links().list(
+                filters=[['link_class', '=', 'tag']],
+                select=['name'], distinct=True
+                ).execute(num_retries=self.num_retries)
         if "items" in tags:
             self.merge(tags['items'],
                        lambda i: i['name'] if 'name' in i else i['uuid'],
                        lambda a, i: a.tag == i,
         if "items" in tags:
             self.merge(tags['items'],
                        lambda i: i['name'] if 'name' in i else i['uuid'],
                        lambda a, i: a.tag == i,
-                       lambda i: TagDirectory(self.inode, self.inodes, self.api, i['name'], poll=self._poll, poll_time=self._poll_time))
+                       lambda i: TagDirectory(self.inode, self.inodes, self.api, self.num_retries, i['name'], poll=self._poll, poll_time=self._poll_time))
 
 
 class TagDirectory(Directory):
 
 
 class TagDirectory(Directory):
@@ -470,46 +490,51 @@ class TagDirectory(Directory):
     to the user that are tagged with a particular tag.
     '''
 
     to the user that are tagged with a particular tag.
     '''
 
-    def __init__(self, parent_inode, inodes, api, tag, poll=False, poll_time=60):
+    def __init__(self, parent_inode, inodes, api, num_retries, tag,
+                 poll=False, poll_time=60):
         super(TagDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
         super(TagDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
+        self.num_retries = num_retries
         self.tag = tag
         self._poll = poll
         self._poll_time = poll_time
 
     def update(self):
         with llfuse.lock_released:
         self.tag = tag
         self._poll = poll
         self._poll_time = poll_time
 
     def update(self):
         with llfuse.lock_released:
-            taggedcollections = self.api.links().list(filters=[['link_class', '=', 'tag'],
-                                                   ['name', '=', self.tag],
-                                                   ['head_uuid', 'is_a', 'arvados#collection']],
-                                          select=['head_uuid']).execute()
+            taggedcollections = self.api.links().list(
+                filters=[['link_class', '=', 'tag'],
+                         ['name', '=', self.tag],
+                         ['head_uuid', 'is_a', 'arvados#collection']],
+                select=['head_uuid']
+                ).execute(num_retries=self.num_retries)
         self.merge(taggedcollections['items'],
                    lambda i: i['head_uuid'],
                    lambda a, i: a.collection_locator == i['head_uuid'],
         self.merge(taggedcollections['items'],
                    lambda i: i['head_uuid'],
                    lambda a, i: a.collection_locator == i['head_uuid'],
-                   lambda i: CollectionDirectory(self.inode, self.inodes, self.api, i['head_uuid']))
+                   lambda i: CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid']))
 
 
 
 
-class ProjectDirectory(RecursiveInvalidateDirectory):
+class ProjectDirectory(Directory):
     '''A special directory that contains the contents of a project.'''
 
     '''A special directory that contains the contents of a project.'''
 
-    def __init__(self, parent_inode, inodes, api, project_object, poll=False, poll_time=60):
+    def __init__(self, parent_inode, inodes, api, num_retries, project_object,
+                 poll=False, poll_time=60):
         super(ProjectDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
         super(ProjectDirectory, self).__init__(parent_inode)
         self.inodes = inodes
         self.api = api
+        self.num_retries = num_retries
         self.project_object = project_object
         self.project_object = project_object
-        self.project_object_file = ObjectFile(self.inode, self.project_object)
-        self.inodes.add_entry(self.project_object_file)
+        self.project_object_file = None
         self.uuid = project_object['uuid']
 
     def createDirectory(self, i):
         if collection_uuid_pattern.match(i['uuid']):
         self.uuid = project_object['uuid']
 
     def createDirectory(self, i):
         if collection_uuid_pattern.match(i['uuid']):
-            return CollectionDirectory(self.inode, self.inodes, self.api, i)
+            return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i)
         elif group_uuid_pattern.match(i['uuid']):
         elif group_uuid_pattern.match(i['uuid']):
-            return ProjectDirectory(self.inode, self.inodes, self.api, i, self._poll, self._poll_time)
+            return ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i, self._poll, self._poll_time)
         elif link_uuid_pattern.match(i['uuid']):
             if i['head_kind'] == 'arvados#collection' or portable_data_hash_pattern.match(i['head_uuid']):
         elif link_uuid_pattern.match(i['uuid']):
             if i['head_kind'] == 'arvados#collection' or portable_data_hash_pattern.match(i['head_uuid']):
-                return CollectionDirectory(self.inode, self.inodes, self.api, i['head_uuid'])
+                return CollectionDirectory(self.inode, self.inodes, self.api, self.num_retries, i['head_uuid'])
             else:
                 return None
         elif uuid_pattern.match(i['uuid']):
             else:
                 return None
         elif uuid_pattern.match(i['uuid']):
@@ -518,6 +543,10 @@ class ProjectDirectory(RecursiveInvalidateDirectory):
             return None
 
     def update(self):
             return None
 
     def update(self):
+        if self.project_object_file == None:
+            self.project_object_file = ObjectFile(self.inode, self.project_object)
+            self.inodes.add_entry(self.project_object_file)
+
         def namefn(i):
             if 'name' in i:
                 if i['name'] is None or len(i['name']) == 0:
         def namefn(i):
             if 'name' in i:
                 if i['name'] is None or len(i['name']) == 0:
@@ -530,7 +559,7 @@ class ProjectDirectory(RecursiveInvalidateDirectory):
                     return i['name']
                 elif 'kind' in i and i['kind'].startswith('arvados#'):
                     # something else
                     return i['name']
                 elif 'kind' in i and i['kind'].startswith('arvados#'):
                     # something else
-                    return "{}.{}".format(i['name'], i['kind'][8:])                    
+                    return "{}.{}".format(i['name'], i['kind'][8:])
             else:
                 return None
 
             else:
                 return None
 
@@ -545,14 +574,20 @@ class ProjectDirectory(RecursiveInvalidateDirectory):
 
         with llfuse.lock_released:
             if group_uuid_pattern.match(self.uuid):
 
         with llfuse.lock_released:
             if group_uuid_pattern.match(self.uuid):
-                self.project_object = self.api.groups().get(uuid=self.uuid).execute()
+                self.project_object = self.api.groups().get(
+                    uuid=self.uuid).execute(num_retries=self.num_retries)
             elif user_uuid_pattern.match(self.uuid):
             elif user_uuid_pattern.match(self.uuid):
-                self.project_object = self.api.users().get(uuid=self.uuid).execute()
+                self.project_object = self.api.users().get(
+                    uuid=self.uuid).execute(num_retries=self.num_retries)
 
 
-            contents = arvados.util.list_all(self.api.groups().contents, uuid=self.uuid)
+            contents = arvados.util.list_all(self.api.groups().contents,
+                                             self.num_retries, uuid=self.uuid)
             # Name links will be obsolete soon, take this out when there are no more pre-#3036 in use.
             # Name links will be obsolete soon, take this out when there are no more pre-#3036 in use.
-            contents += arvados.util.list_all(self.api.links().list, filters=[['tail_uuid', '=', self.uuid], ['link_class', '=', 'name']])
-            
+            contents += arvados.util.list_all(
+                self.api.links().list, self.num_retries,
+                filters=[['tail_uuid', '=', self.uuid],
+                         ['link_class', '=', 'name']])
+
         # end with llfuse.lock_released, re-acquire lock
 
         self.merge(contents,
         # end with llfuse.lock_released, re-acquire lock
 
         self.merge(contents,
@@ -573,31 +608,25 @@ class ProjectDirectory(RecursiveInvalidateDirectory):
         else:
             return super(ProjectDirectory, self).__contains__(k)
 
         else:
             return super(ProjectDirectory, self).__contains__(k)
 
-    def ctime(self):
-        return convertTime(self.project_object["created_at"]) if "created_at" in self.project_object else 0
-
-    def mtime(self):
-        return convertTime(self.project_object["modified_at"]) if "modified_at" in self.project_object  else 0
-
 
 
-class SharedDirectory(RecursiveInvalidateDirectory):
+class SharedDirectory(Directory):
     '''A special directory that represents users or groups who have shared projects with me.'''
 
     '''A special directory that represents users or groups who have shared projects with me.'''
 
-    def __init__(self, parent_inode, inodes, api, exclude, poll=False, poll_time=60):
+    def __init__(self, parent_inode, inodes, api, num_retries, exclude,
+                 poll=False, poll_time=60):
         super(SharedDirectory, self).__init__(parent_inode)
         super(SharedDirectory, self).__init__(parent_inode)
-        self.current_user = api.users().current().execute()
         self.inodes = inodes
         self.api = api
         self.inodes = inodes
         self.api = api
-
-        # try:
-        #     arvados.events.subscribe(self.api, [], lambda ev: self.invalidate())
-        # except:
+        self.num_retries = num_retries
+        self.current_user = api.users().current().execute(num_retries=num_retries)
         self._poll = True
         self._poll_time = poll_time
 
     def update(self):
         with llfuse.lock_released:
         self._poll = True
         self._poll_time = poll_time
 
     def update(self):
         with llfuse.lock_released:
-            all_projects = arvados.util.list_all(self.api.groups().list, filters=[['group_class','=','project']])
+            all_projects = arvados.util.list_all(
+                self.api.groups().list, self.num_retries,
+                filters=[['group_class','=','project']])
             objects = {}
             for ob in all_projects:
                 objects[ob['uuid']] = ob
             objects = {}
             for ob in all_projects:
                 objects[ob['uuid']] = ob
@@ -609,8 +638,12 @@ class SharedDirectory(RecursiveInvalidateDirectory):
                     roots.append(ob)
                     root_owners[ob['owner_uuid']] = True
 
                     roots.append(ob)
                     root_owners[ob['owner_uuid']] = True
 
-            lusers = arvados.util.list_all(self.api.users().list, filters=[['uuid','in', list(root_owners)]])
-            lgroups = arvados.util.list_all(self.api.groups().list, filters=[['uuid','in', list(root_owners)]])
+            lusers = arvados.util.list_all(
+                self.api.users().list, self.num_retries,
+                filters=[['uuid','in', list(root_owners)]])
+            lgroups = arvados.util.list_all(
+                self.api.groups().list, self.num_retries,
+                filters=[['uuid','in', list(root_owners)]])
 
             users = {}
             groups = {}
 
             users = {}
             groups = {}
@@ -639,7 +672,7 @@ class SharedDirectory(RecursiveInvalidateDirectory):
             self.merge(contents.items(),
                        lambda i: i[0],
                        lambda a, i: a.uuid == i[1]['uuid'],
             self.merge(contents.items(),
                        lambda i: i[0],
                        lambda a, i: a.uuid == i[1]['uuid'],
-                       lambda i: ProjectDirectory(self.inode, self.inodes, self.api, i[1], poll=self._poll, poll_time=self._poll_time))
+                       lambda i: ProjectDirectory(self.inode, self.inodes, self.api, self.num_retries, i[1], poll=self._poll, poll_time=self._poll_time))
         except Exception as e:
             _logger.exception(e)
 
         except Exception as e:
             _logger.exception(e)
 
@@ -744,12 +777,10 @@ class Operations(llfuse.Operations):
         entry.st_size = e.size()
 
         entry.st_blksize = 512
         entry.st_size = e.size()
 
         entry.st_blksize = 512
-        entry.st_blocks = (e.size()/512)
-        if e.size()/512 != 0:
-            entry.st_blocks += 1
-        entry.st_atime = 0
-        entry.st_mtime = e.mtime()
-        entry.st_ctime = e.ctime()
+        entry.st_blocks = (e.size()/512)+1
+        entry.st_atime = int(e.atime())
+        entry.st_mtime = int(e.mtime())
+        entry.st_ctime = int(e.mtime())
 
         return entry
 
 
         return entry
 
@@ -765,7 +796,7 @@ class Operations(llfuse.Operations):
                 p = self.inodes[parent_inode]
                 if name == '..':
                     inode = p.parent_inode
                 p = self.inodes[parent_inode]
                 if name == '..':
                     inode = p.parent_inode
-                elif name in p:
+                elif isinstance(p, Directory) and name in p:
                     inode = p[name].inode
 
         if inode != None:
                     inode = p[name].inode
 
         if inode != None:
@@ -797,10 +828,17 @@ class Operations(llfuse.Operations):
         else:
             raise llfuse.FUSEError(errno.EBADF)
 
         else:
             raise llfuse.FUSEError(errno.EBADF)
 
+        # update atime
+        handle.entry._atime = time.time()
+
         try:
             with llfuse.lock_released:
                 return handle.entry.readfrom(off, size)
         try:
             with llfuse.lock_released:
                 return handle.entry.readfrom(off, size)
-        except:
+        except arvados.errors.NotFoundError as e:
+            _logger.warning("Block not found: " + str(e))
+            raise llfuse.FUSEError(errno.EIO)
+        except Exception as e:
+            _logger.exception(e)
             raise llfuse.FUSEError(errno.EIO)
 
     def release(self, fh):
             raise llfuse.FUSEError(errno.EIO)
 
     def release(self, fh):
@@ -825,6 +863,9 @@ class Operations(llfuse.Operations):
         else:
             raise llfuse.FUSEError(errno.EIO)
 
         else:
             raise llfuse.FUSEError(errno.EIO)
 
+        # update atime
+        p._atime = time.time()
+
         self._filehandles[fh] = FileHandle(fh, [('.', p), ('..', parent)] + list(p.items()))
         return fh
 
         self._filehandles[fh] = FileHandle(fh, [('.', p), ('..', parent)] + list(p.items()))
         return fh