You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2021/06/30 19:35:23 UTC

[airavata-django-portal-sdk] 01/04: AIRAVATA-3475 Adds create_symlink() to user_storage module

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal-sdk.git

commit beddf0f82f7d0bbae6d661866c883f8eaa59a6ee
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Wed Jun 30 14:01:30 2021 -0400

    AIRAVATA-3475 Adds create_symlink() to user_storage module
---
 .../user_storage/__init__.py                       |  2 ++
 airavata_django_portal_sdk/user_storage/api.py     |  9 +++++++
 .../user_storage/backends/base.py                  |  9 +++++++
 .../backends/django_filesystem_provider.py         | 31 +++++++++++++++++++++-
 4 files changed, 50 insertions(+), 1 deletion(-)

diff --git a/airavata_django_portal_sdk/user_storage/__init__.py b/airavata_django_portal_sdk/user_storage/__init__.py
index c840aca..d4ef5db 100644
--- a/airavata_django_portal_sdk/user_storage/__init__.py
+++ b/airavata_django_portal_sdk/user_storage/__init__.py
@@ -1,5 +1,6 @@
 from .api import (
     copy_input_file,
+    create_symlink,
     create_user_dir,
     delete,
     delete_dir,
@@ -29,6 +30,7 @@ from .api import (
 
 __all__ = [
     'copy_input_file',
+    'create_symlink',
     'create_user_dir',
     'delete',
     'delete_dir',
diff --git a/airavata_django_portal_sdk/user_storage/api.py b/airavata_django_portal_sdk/user_storage/api.py
index 17732d8..d68dbe7 100644
--- a/airavata_django_portal_sdk/user_storage/api.py
+++ b/airavata_django_portal_sdk/user_storage/api.py
@@ -656,6 +656,15 @@ def create_user_dir(request, path="", dir_names=(), create_unique=False, storage
     return storage_resource_id, resource_path
 
 
+def create_symlink(request, src_path, dest_path, storage_resource_id=None):
+    """Create link named dest_path pointing to src_path on storage resource."""
+    if _is_remote_api():
+        logger.warning("create_symlink isn't supported in Remote API mode")
+        return
+    backend = get_user_storage_provider(request, storage_resource_id=storage_resource_id)
+    backend.create_symlink(src_path, dest_path)
+
+
 def get_rel_experiment_dir(request, experiment_id, storage_resource_id=None):
     """Return experiment data dir path relative to user's directory."""
     warnings.warn("Use 'list_experiment_dir' instead.", DeprecationWarning)
diff --git a/airavata_django_portal_sdk/user_storage/backends/base.py b/airavata_django_portal_sdk/user_storage/backends/base.py
index 95601fc..850b291 100644
--- a/airavata_django_portal_sdk/user_storage/backends/base.py
+++ b/airavata_django_portal_sdk/user_storage/backends/base.py
@@ -1,12 +1,14 @@
 
 class ProvidesDownloadUrl:
     """Mixin for UserStorageProvider that provides download url."""
+
     def get_download_url(self, resource_path):
         raise NotImplementedError()
 
 
 class ProvidesUploadUrl:
     """Mixin for UserStorageProvider that provides upload url."""
+
     def get_upload_url(self, resource_path):
         raise NotImplementedError()
 
@@ -65,6 +67,13 @@ class UserStorageProvider:
         """
         raise NotImplementedError()
 
+    def create_symlink(self, source_path, dest_path):
+        """
+        Create a symlink at dest_path in user's storage that points to
+        source_path, a filesystem path on the storage resource.
+        """
+        raise NotImplementedError()
+
     @property
     def username(self):
         return self.authz_token.claimsMap['userName']
diff --git a/airavata_django_portal_sdk/user_storage/backends/django_filesystem_provider.py b/airavata_django_portal_sdk/user_storage/backends/django_filesystem_provider.py
index 454fd19..09bd680 100644
--- a/airavata_django_portal_sdk/user_storage/backends/django_filesystem_provider.py
+++ b/airavata_django_portal_sdk/user_storage/backends/django_filesystem_provider.py
@@ -121,6 +121,17 @@ class DjangoFileSystemProvider(UserStorageProvider):
         datastore.create_user_dir(final_path)
         return self.storage_resource_id, datastore.path(final_path)
 
+    def create_symlink(self, source_path, dest_path):
+        datastore = self.datastore
+        if (datastore.symlink_exists(dest_path) and
+                os.path.realpath(source_path) == os.path.realpath(datastore.path(dest_path))):
+            logger.debug(f"symlink at {dest_path} already points to {source_path}")
+            return
+        elif datastore.exists(dest_path):
+            raise Exception(f"{dest_path} already exists, but isn't the expected symlink")
+        else:
+            datastore.create_symlink(source_path, dest_path)
+
     @property
     def datastore(self):
         directory = os.path.join(self.directory, self.username)
@@ -163,6 +174,15 @@ class _Datastore:
             logger.warning(f"Invalid path: {e}")
             return False
 
+    def symlink_exists(self, path):
+        """Check if symlink path exists in this data store."""
+        logger.debug(f"symlink_exists: {path}, {self.path(path)}")
+        try:
+            return self.storage.exists(path) and os.path.islink(self.path(path))
+        except SuspiciousFileOperation as e:
+            logger.warning(f"Invalid path: {e}")
+            return False
+
     def open(self, path):
         """Open path for user if it exists in this data store."""
         if self.exists(path):
@@ -189,6 +209,12 @@ class _Datastore:
         else:
             raise Exception("Directory {} already exists".format(path))
 
+    def create_symlink(self, source_path, dest_path):
+        user_data_storage = self.storage
+        full_path = user_data_storage.path(dest_path)
+        os.symlink(source_path, full_path)
+        return full_path
+
     def delete(self, path):
         """Delete file in this data store."""
         if self.file_exists(path):
@@ -200,7 +226,10 @@ class _Datastore:
 
     def delete_dir(self, path):
         """Delete entire directory in this data store."""
-        if self.dir_exists(path):
+        if self.symlink_exists(path):
+            user_path = self.path(path)
+            os.unlink(user_path)
+        elif self.dir_exists(path):
             user_path = self.path(path)
             shutil.rmtree(user_path)
         else: