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: