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

lahirujayathilake pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/master by this push:
     new 12cbf58b36 Storage directory size fetching api (#575)
12cbf58b36 is described below

commit 12cbf58b3649fe3430e47c17934f946b26646c10
Author: Dimuthu Wannipurage <[email protected]>
AuthorDate: Tue Nov 11 17:01:45 2025 -0500

    Storage directory size fetching api (#575)
    
    * Making experiment data path relative when the destination and source 
storage are different.
    
    * Enabling storage directory size listing api
    
    * Adding storage directory size listing api to python sdk
    
    * bump up the sdk version and spotless apply
    
    ---------
    
    Co-authored-by: lahiruj <[email protected]>
---
 .../apache/airavata/agents/api/AgentAdaptor.java   |   3 +
 .../api/server/handler/AiravataServerHandler.java  |  76 +++++
 .../airavata/helix/adaptor/SSHJAgentAdaptor.java   |  49 ++++
 .../airavata/helix/agent/ssh/SshAgentAdaptor.java  |   7 +
 .../helix/impl/task/staging/DataStagingTask.java   |   2 +
 .../impl/task/staging/OutputDataStagingTask.java   |   6 +-
 .../airavata/api/Airavata-remote                   |   7 +
 .../airavata-python-sdk/airavata/api/Airavata.py   | 325 ++++++++++++++++++++-
 .../airavata-python-sdk/airavata/model/__init__.py |   1 -
 .../model/appcatalog/storageresource/ttypes.py     |  85 ++++++
 .../airavata_experiments/airavata.py               |  61 ++--
 dev-tools/airavata-python-sdk/pyproject.toml       |   2 +-
 .../airavata-apis/airavata_api.thrift              |  52 ++--
 .../data-models/storage_resource_model.thrift      |  11 +
 14 files changed, 629 insertions(+), 58 deletions(-)

diff --git 
a/airavata-api/src/main/java/org/apache/airavata/agents/api/AgentAdaptor.java 
b/airavata-api/src/main/java/org/apache/airavata/agents/api/AgentAdaptor.java
index 245cad8c69..36baea7060 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/agents/api/AgentAdaptor.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/agents/api/AgentAdaptor.java
@@ -22,6 +22,7 @@ package org.apache.airavata.agents.api;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
+import 
org.apache.airavata.model.appcatalog.storageresource.StorageDirectoryInfo;
 import org.apache.airavata.model.appcatalog.storageresource.StorageVolumeInfo;
 
 /**
@@ -59,4 +60,6 @@ public interface AgentAdaptor {
     FileMetadata getFileMetadata(String remoteFile) throws AgentException;
 
     StorageVolumeInfo getStorageVolumeInfo(String location) throws 
AgentException;
+
+    StorageDirectoryInfo getStorageDirectoryInfo(String location) throws 
AgentException;
 }
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
 
b/airavata-api/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
index c8a89566b3..4da1eb7494 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/api/server/handler/AiravataServerHandler.java
@@ -59,6 +59,7 @@ import 
org.apache.airavata.model.appcatalog.groupresourceprofile.GroupComputeRes
 import 
org.apache.airavata.model.appcatalog.groupresourceprofile.GroupResourceProfile;
 import org.apache.airavata.model.appcatalog.parser.Parser;
 import org.apache.airavata.model.appcatalog.parser.ParsingTemplate;
+import 
org.apache.airavata.model.appcatalog.storageresource.StorageDirectoryInfo;
 import 
org.apache.airavata.model.appcatalog.storageresource.StorageResourceDescription;
 import org.apache.airavata.model.appcatalog.storageresource.StorageVolumeInfo;
 import 
org.apache.airavata.model.appcatalog.userresourceprofile.UserComputeResourcePreference;
@@ -3919,6 +3920,81 @@ public class AiravataServerHandler implements 
Airavata.Iface {
         }
     }
 
+    @Override
+    @SecurityCheck
+    public StorageDirectoryInfo getStorageDirectoryInfo(AuthzToken authzToken, 
String resourceId, String location)
+            throws TException {
+        String gatewayId = authzToken.getClaimsMap().get(Constants.GATEWAY_ID);
+        String userId = authzToken.getClaimsMap().get(Constants.USER_NAME);
+        RegistryService.Client regClient = registryClientPool.getResource();
+        StorageInfoContext context;
+
+        try {
+            Optional<ComputeResourceDescription> computeResourceOp = 
Optional.empty();
+            try {
+                ComputeResourceDescription computeResource = 
regClient.getComputeResource(resourceId);
+                if (computeResource != null) {
+                    computeResourceOp = Optional.of(computeResource);
+                }
+            } catch (TApplicationException e) {
+                // TApplicationException with "unknown result" means resource 
not found (null return for non-nullable
+                // type)
+                logger.debug("Compute resource {} not found 
(TApplicationException): {}", resourceId, e.getMessage());
+            }
+
+            Optional<StorageResourceDescription> storageResourceOp = 
Optional.empty();
+            if (computeResourceOp.isEmpty()) {
+                try {
+                    StorageResourceDescription storageResource = 
regClient.getStorageResource(resourceId);
+                    if (storageResource != null) {
+                        storageResourceOp = Optional.of(storageResource);
+                    }
+                } catch (TApplicationException e) {
+                    // TApplicationException with "unknown result" means 
resource not found (null return for
+                    // non-nullable type)
+                    logger.debug(
+                            "Storage resource {} not found 
(TApplicationException): {}", resourceId, e.getMessage());
+                }
+            }
+
+            if (computeResourceOp.isEmpty() && storageResourceOp.isEmpty()) {
+                logger.error(
+                        "Resource with ID {} not found as either compute 
resource or storage resource", resourceId);
+                throw new InvalidRequestException("Resource with ID '" + 
resourceId
+                        + "' not found as either compute resource or storage 
resource");
+            }
+
+            if (computeResourceOp.isPresent()) {
+                logger.debug("Found compute resource with ID {}. Resolving 
login username and credentials", resourceId);
+                context = resolveComputeStorageInfoContext(authzToken, 
gatewayId, userId, resourceId);
+            } else {
+                logger.debug("Found storage resource with ID {}. Resolving 
login username and credentials", resourceId);
+                context = resolveStorageStorageInfoContext(authzToken, 
gatewayId, userId, resourceId);
+            }
+
+            registryClientPool.returnResource(regClient);
+            regClient = null;
+
+            return context.adaptor.getStorageDirectoryInfo(location);
+
+        } catch (InvalidRequestException | AiravataClientException e) {
+            if (regClient != null) {
+                registryClientPool.returnResource(regClient);
+            }
+            logger.error("Error while retrieving storage resource.", e);
+            throw e;
+
+        } catch (Exception e) {
+            logger.error("Error while retrieving storage volume info for 
resource {}", resourceId, e);
+            registryClientPool.returnBrokenResource(regClient);
+
+            AiravataSystemException exception = new AiravataSystemException();
+            exception.setAiravataErrorType(AiravataErrorType.INTERNAL_ERROR);
+            exception.setMessage("Error while retrieving storage volume info. 
More info: " + e.getMessage());
+            throw exception;
+        }
+    }
+
     /**
      * Add a Local Job Submission details to a compute resource
      * App catalog will return a jobSubmissionInterfaceId which will be added 
to the jobSubmissionInterfaces.
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJAgentAdaptor.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJAgentAdaptor.java
index 9e9caaefc1..2845afb1ef 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJAgentAdaptor.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJAgentAdaptor.java
@@ -51,6 +51,7 @@ import 
org.apache.airavata.model.appcatalog.computeresource.ComputeResourceDescr
 import 
org.apache.airavata.model.appcatalog.computeresource.JobSubmissionInterface;
 import 
org.apache.airavata.model.appcatalog.computeresource.JobSubmissionProtocol;
 import org.apache.airavata.model.appcatalog.computeresource.SSHJobSubmission;
+import 
org.apache.airavata.model.appcatalog.storageresource.StorageDirectoryInfo;
 import org.apache.airavata.model.appcatalog.storageresource.StorageVolumeInfo;
 import org.apache.airavata.model.credential.store.SSHCredential;
 import org.slf4j.Logger;
@@ -645,6 +646,54 @@ public class SSHJAgentAdaptor implements AgentAdaptor {
         }
     }
 
+    @Override
+    public StorageDirectoryInfo getStorageDirectoryInfo(String location) 
throws AgentException {
+        try {
+            String targetLocation = location;
+            if (targetLocation == null || targetLocation.trim().isEmpty()) {
+                CommandOutput homeOutput = executeCommand("echo $HOME", null);
+
+                if (homeOutput.getExitCode() != 0
+                        || homeOutput.getStdOut() == null
+                        || homeOutput.getStdOut().trim().isEmpty()) {
+                    logger.error("Failed to determine user's home directory: 
{}", homeOutput.getStdError());
+                    throw new AgentException("Failed to determine user's home 
directory: " + homeOutput.getStdError());
+                }
+                targetLocation = homeOutput.getStdOut().trim();
+            }
+
+            // Escape location to prevent command injection and handle spaces
+            String escapedLocation = targetLocation.replace("'", "'\"'\"'");
+            String duKBytesCommand = "du -sk '" + escapedLocation + "'";
+
+            CommandOutput duKBytesOutput = executeCommand(duKBytesCommand, 
null);
+
+            if (duKBytesOutput.getExitCode() != 0) {
+                logger.error(
+                        "Failed to execute du -sk command for location {}: {}",
+                        targetLocation,
+                        duKBytesOutput.getStdError());
+                throw new AgentException("Failed to execute du -sk command for 
location " + targetLocation + ": "
+                        + duKBytesOutput.getStdError());
+            }
+
+            String outputKbStr = duKBytesOutput.getStdOut().trim();
+            logger.info("OutputKbStr: for du -ku {} is {}", location, 
outputKbStr);
+            String numberOfKBytesStr = outputKbStr.split(" ")[0];
+
+            long numberOfKBytes = Long.parseLong(numberOfKBytesStr);
+
+            StorageDirectoryInfo storageDirectoryInfo = new 
StorageDirectoryInfo();
+            storageDirectoryInfo.setTotalSizeBytes(numberOfKBytes * 1024);
+            storageDirectoryInfo.setTotalSize(numberOfKBytes + "kb");
+            return storageDirectoryInfo;
+
+        } catch (Exception e) {
+            logger.error("Error while retrieving storage directory info for 
location " + location, e);
+            throw new AgentException("Error while retrieving storage directory 
info for location " + location, e);
+        }
+    }
+
     private StorageVolumeInfo parseDfOutput(String dfHumanOutput, String 
dfBytesOutput, String targetLocation)
             throws AgentException {
         try {
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/helix/agent/ssh/SshAgentAdaptor.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/agent/ssh/SshAgentAdaptor.java
index 40096595b2..8ee4237fd7 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/agent/ssh/SshAgentAdaptor.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/agent/ssh/SshAgentAdaptor.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.UUID;
 import org.apache.airavata.agents.api.*;
 import org.apache.airavata.model.appcatalog.computeresource.*;
+import 
org.apache.airavata.model.appcatalog.storageresource.StorageDirectoryInfo;
 import org.apache.airavata.model.appcatalog.storageresource.StorageVolumeInfo;
 import org.apache.airavata.model.credential.store.SSHCredential;
 import org.slf4j.Logger;
@@ -547,6 +548,12 @@ public class SshAgentAdaptor implements AgentAdaptor {
                 "Operation not supported by SshAgentAdaptor. Use 
SSHJAgentAdaptor instead.");
     }
 
+    @Override
+    public StorageDirectoryInfo getStorageDirectoryInfo(String location) 
throws AgentException {
+        throw new UnsupportedOperationException(
+                "Operation not supported by SshAgentAdaptor. Use 
SSHJAgentAdaptor instead.");
+    }
+
     private static class DefaultUserInfo implements UserInfo, 
UIKeyboardInteractive {
 
         private String userName;
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/DataStagingTask.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/DataStagingTask.java
index 568dc82301..dacc55808a 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/DataStagingTask.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/DataStagingTask.java
@@ -202,6 +202,8 @@ public abstract class DataStagingTask extends AiravataTask {
         inputPath = (inputPath.endsWith(File.separator) ? inputPath : 
inputPath + File.separator);
         String experimentDataDir = getProcessModel().getExperimentDataDir();
         String filePath;
+        // TODO : This logic is extremely dangerous. This was implemented 
expecting the input and output storage are
+        // same.
         if (experimentDataDir != null && !experimentDataDir.isEmpty()) {
             if (!experimentDataDir.endsWith(File.separator)) {
                 experimentDataDir += File.separator;
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/OutputDataStagingTask.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/OutputDataStagingTask.java
index ba1e69e0e0..3aab8d3fa6 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/OutputDataStagingTask.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/impl/task/staging/OutputDataStagingTask.java
@@ -94,7 +94,7 @@ public class OutputDataStagingTask extends DataStagingTask {
                     StoragePreference outputStoragePref = 
getTaskContext().getOutputGatewayStorageResourcePreference();
                     String inputPath = 
outputStoragePref.getFileSystemRootLocation();
                     String destFilePath = buildDestinationFilePath(inputPath, 
sourceFileName);
-
+                    logger.info("Output storage path for task id " + 
getTaskId() + " is " + destFilePath);
                     destinationURI = new URI(
                             "file",
                             outputStoragePref.getLoginUserName(),
@@ -106,6 +106,10 @@ public class OutputDataStagingTask extends DataStagingTask 
{
 
                 } else {
                     destinationURI = new 
URI(dataStagingTaskModel.getDestination());
+                    logger.info(
+                            "Output data staging destination for task id {} is 
{}",
+                            getTaskId(),
+                            destinationURI.getPath());
                 }
 
                 if (logger.isDebugEnabled()) {
diff --git a/dev-tools/airavata-python-sdk/airavata/api/Airavata-remote 
b/dev-tools/airavata-python-sdk/airavata/api/Airavata-remote
index 173421d374..644d582be5 100755
--- a/dev-tools/airavata-python-sdk/airavata/api/Airavata-remote
+++ b/dev-tools/airavata-python-sdk/airavata/api/Airavata-remote
@@ -218,6 +218,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  bool removeParsingTemplate(AuthzToken authzToken, string 
templateId, string gatewayId)')
     print('   listAllParsingTemplates(AuthzToken authzToken, string 
gatewayId)')
     print('  StorageVolumeInfo getResourceStorageInfo(AuthzToken authzToken, 
string resourceId, string location)')
+    print('  StorageDirectoryInfo getStorageDirectoryInfo(AuthzToken 
authzToken, string resourceId, string location)')
     print('  string getAPIVersion()')
     print('')
     sys.exit(0)
@@ -1462,6 +1463,12 @@ elif cmd == 'getResourceStorageInfo':
         sys.exit(1)
     pp.pprint(client.getResourceStorageInfo(eval(args[0]), args[1], args[2],))
 
+elif cmd == 'getStorageDirectoryInfo':
+    if len(args) != 3:
+        print('getStorageDirectoryInfo requires 3 args')
+        sys.exit(1)
+    pp.pprint(client.getStorageDirectoryInfo(eval(args[0]), args[1], args[2],))
+
 elif cmd == 'getAPIVersion':
     if len(args) != 0:
         print('getAPIVersion requires 0 args')
diff --git a/dev-tools/airavata-python-sdk/airavata/api/Airavata.py 
b/dev-tools/airavata-python-sdk/airavata/api/Airavata.py
index f027db0933..89f1702a07 100644
--- a/dev-tools/airavata-python-sdk/airavata/api/Airavata.py
+++ b/dev-tools/airavata-python-sdk/airavata/api/Airavata.py
@@ -826,7 +826,7 @@ class Iface(airavata.base.api.BaseAPI.Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -914,7 +914,7 @@ class Iface(airavata.base.api.BaseAPI.Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -1127,7 +1127,7 @@ class Iface(airavata.base.api.BaseAPI.Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -1228,7 +1228,7 @@ class Iface(airavata.base.api.BaseAPI.Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -3859,6 +3859,23 @@ class Iface(airavata.base.api.BaseAPI.Iface):
         """
         pass
 
+    def getStorageDirectoryInfo(self, authzToken: 
airavata.model.security.ttypes.AuthzToken, resourceId: str, location: str) -> 
airavata.model.appcatalog.storageresource.ttypes.StorageDirectoryInfo:
+        """
+        Get storage directory information for a compute or storage resource.
+
+        @param authzToken
+        @param resourceId Can be either a compute resource ID or storage 
resource ID
+        @param location Optional path/mount point. If null/empty, defaults to 
user's home directory ($HOME)
+        @return StorageDirectoryInfo containing directory size information
+
+        Parameters:
+         - authzToken
+         - resourceId
+         - location
+
+        """
+        pass
+
 
 class Client(airavata.base.api.BaseAPI.Client, Iface):
     def __init__(self, iprot, oprot=None):
@@ -5825,7 +5842,7 @@ class Client(airavata.base.api.BaseAPI.Client, Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -6034,7 +6051,7 @@ class Client(airavata.base.api.BaseAPI.Client, Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -6526,7 +6543,7 @@ class Client(airavata.base.api.BaseAPI.Client, Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -6705,7 +6722,7 @@ class Client(airavata.base.api.BaseAPI.Client, Iface):
 
         @throws org.apache.airavata.model.error.AiravataClientException
            The following list of exceptions are thrown which Airavata Client 
can take corrective actions to resolve:
-             
+
              UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata 
as a one time administrative
                 step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
                 gateway registration steps and retry this request.
@@ -14256,6 +14273,57 @@ class Client(airavata.base.api.BaseAPI.Client, Iface):
             raise result.ae
         raise TApplicationException(TApplicationException.MISSING_RESULT, 
"getResourceStorageInfo failed: unknown result")
 
+    def getStorageDirectoryInfo(self, authzToken: 
airavata.model.security.ttypes.AuthzToken, resourceId: str, location: str) -> 
airavata.model.appcatalog.storageresource.ttypes.StorageDirectoryInfo:
+        """
+        Get storage directory information for a compute or storage resource.
+
+        @param authzToken
+        @param resourceId Can be either a compute resource ID or storage 
resource ID
+        @param location Optional path/mount point. If null/empty, defaults to 
user's home directory ($HOME)
+        @return StorageDirectoryInfo containing directory size information
+
+        Parameters:
+         - authzToken
+         - resourceId
+         - location
+
+        """
+        self.send_getStorageDirectoryInfo(authzToken, resourceId, location)
+        return self.recv_getStorageDirectoryInfo()
+
+    def send_getStorageDirectoryInfo(self, authzToken: 
airavata.model.security.ttypes.AuthzToken, resourceId: str, location: str):
+        self._oprot.writeMessageBegin('getStorageDirectoryInfo', 
TMessageType.CALL, self._seqid)
+        args = getStorageDirectoryInfo_args()
+        args.authzToken = authzToken
+        args.resourceId = resourceId
+        args.location = location
+        args.write(self._oprot)
+        self._oprot.writeMessageEnd()
+        self._oprot.trans.flush()
+
+    def recv_getStorageDirectoryInfo(self) -> 
airavata.model.appcatalog.storageresource.ttypes.StorageDirectoryInfo:
+        iprot = self._iprot
+        (fname, mtype, rseqid) = iprot.readMessageBegin()
+        if mtype == TMessageType.EXCEPTION:
+            x = TApplicationException()
+            x.read(iprot)
+            iprot.readMessageEnd()
+            raise x
+        result = getStorageDirectoryInfo_result()
+        result.read(iprot)
+        iprot.readMessageEnd()
+        if result.success is not None:
+            return result.success
+        if result.ire is not None:
+            raise result.ire
+        if result.ace is not None:
+            raise result.ace
+        if result.ase is not None:
+            raise result.ase
+        if result.ae is not None:
+            raise result.ae
+        raise TApplicationException(TApplicationException.MISSING_RESULT, 
"getStorageDirectoryInfo failed: unknown result")
+
 
 class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
     def __init__(self, handler):
@@ -14454,6 +14522,7 @@ class Processor(airavata.base.api.BaseAPI.Processor, 
Iface, TProcessor):
         self._processMap["removeParsingTemplate"] = 
Processor.process_removeParsingTemplate
         self._processMap["listAllParsingTemplates"] = 
Processor.process_listAllParsingTemplates
         self._processMap["getResourceStorageInfo"] = 
Processor.process_getResourceStorageInfo
+        self._processMap["getStorageDirectoryInfo"] = 
Processor.process_getStorageDirectoryInfo
         self._on_message_begin = None
 
     def on_message_begin(self, func):
@@ -21305,6 +21374,41 @@ class Processor(airavata.base.api.BaseAPI.Processor, 
Iface, TProcessor):
         oprot.writeMessageEnd()
         oprot.trans.flush()
 
+    def process_getStorageDirectoryInfo(self, seqid, iprot, oprot):
+        args = getStorageDirectoryInfo_args()
+        args.read(iprot)
+        iprot.readMessageEnd()
+        result = getStorageDirectoryInfo_result()
+        try:
+            result.success = 
self._handler.getStorageDirectoryInfo(args.authzToken, args.resourceId, 
args.location)
+            msg_type = TMessageType.REPLY
+        except TTransport.TTransportException:
+            raise
+        except airavata.api.error.ttypes.InvalidRequestException as ire:
+            msg_type = TMessageType.REPLY
+            result.ire = ire
+        except airavata.api.error.ttypes.AiravataClientException as ace:
+            msg_type = TMessageType.REPLY
+            result.ace = ace
+        except airavata.api.error.ttypes.AiravataSystemException as ase:
+            msg_type = TMessageType.REPLY
+            result.ase = ase
+        except airavata.api.error.ttypes.AuthorizationException as ae:
+            msg_type = TMessageType.REPLY
+            result.ae = ae
+        except TApplicationException as ex:
+            logging.exception('TApplication exception in handler')
+            msg_type = TMessageType.EXCEPTION
+            result = ex
+        except Exception:
+            logging.exception('Unexpected exception in handler')
+            msg_type = TMessageType.EXCEPTION
+            result = 
TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
+        oprot.writeMessageBegin("getStorageDirectoryInfo", msg_type, seqid)
+        result.write(oprot)
+        oprot.writeMessageEnd()
+        oprot.trans.flush()
+
 # HELPER FUNCTIONS AND STRUCTURES
 
 
@@ -61308,5 +61412,210 @@ getResourceStorageInfo_result.thrift_spec = (
     (3, TType.STRUCT, 'ase', 
[airavata.api.error.ttypes.AiravataSystemException, None], None, ),  # 3
     (4, TType.STRUCT, 'ae', [airavata.api.error.ttypes.AuthorizationException, 
None], None, ),  # 4
 )
+
+
+class getStorageDirectoryInfo_args(object):
+    """
+    Attributes:
+     - authzToken
+     - resourceId
+     - location
+
+    """
+    thrift_spec: typing.Any = None
+
+
+    def __init__(self, authzToken: airavata.model.security.ttypes.AuthzToken = 
None, resourceId: str = None, location: typing.Optional[str] = None,):
+        self.authzToken: airavata.model.security.ttypes.AuthzToken = authzToken
+        self.resourceId: str = resourceId
+        self.location: typing.Optional[str] = location
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, 
TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, [self.__class__, self.thrift_spec])
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRUCT:
+                    self.authzToken = 
airavata.model.security.ttypes.AuthzToken()
+                    self.authzToken.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRING:
+                    self.resourceId = iprot.readString().decode('utf-8', 
errors='replace') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 3:
+                if ftype == TType.STRING:
+                    self.location = iprot.readString().decode('utf-8', 
errors='replace') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        self.validate()
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, [self.__class__, 
self.thrift_spec]))
+            return
+        oprot.writeStructBegin('getStorageDirectoryInfo_args')
+        if self.authzToken is not None:
+            oprot.writeFieldBegin('authzToken', TType.STRUCT, 1)
+            self.authzToken.write(oprot)
+            oprot.writeFieldEnd()
+        if self.resourceId is not None:
+            oprot.writeFieldBegin('resourceId', TType.STRING, 2)
+            oprot.writeString(self.resourceId.encode('utf-8') if 
sys.version_info[0] == 2 else self.resourceId)
+            oprot.writeFieldEnd()
+        if self.location is not None:
+            oprot.writeFieldBegin('location', TType.STRING, 3)
+            oprot.writeString(self.location.encode('utf-8') if 
sys.version_info[0] == 2 else self.location)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.authzToken is None:
+            raise TProtocolException(message='Required field authzToken is 
unset!')
+        if self.resourceId is None:
+            raise TProtocolException(message='Required field resourceId is 
unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == 
other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+all_structs.append(getStorageDirectoryInfo_args)
+getStorageDirectoryInfo_args.thrift_spec = (
+    None,  # 0
+    (1, TType.STRUCT, 'authzToken', 
[airavata.model.security.ttypes.AuthzToken, None], None, ),  # 1
+    (2, TType.STRING, 'resourceId', 'UTF8', None, ),  # 2
+    (3, TType.STRING, 'location', 'UTF8', None, ),  # 3
+)
+
+
+class getStorageDirectoryInfo_result(object):
+    """
+    Attributes:
+     - success
+     - ire
+     - ace
+     - ase
+     - ae
+
+    """
+    thrift_spec: typing.Any = None
+
+
+    def __init__(self, success: 
typing.Optional[airavata.model.appcatalog.storageresource.ttypes.StorageDirectoryInfo]
 = None, ire: 
typing.Optional[airavata.api.error.ttypes.InvalidRequestException] = None, ace: 
typing.Optional[airavata.api.error.ttypes.AiravataClientException] = None, ase: 
typing.Optional[airavata.api.error.ttypes.AiravataSystemException] = None, ae: 
typing.Optional[airavata.api.error.ttypes.AuthorizationException] = None,):
+        self.success: 
typing.Optional[airavata.model.appcatalog.storageresource.ttypes.StorageDirectoryInfo]
 = success
+        self.ire: 
typing.Optional[airavata.api.error.ttypes.InvalidRequestException] = ire
+        self.ace: 
typing.Optional[airavata.api.error.ttypes.AiravataClientException] = ace
+        self.ase: 
typing.Optional[airavata.api.error.ttypes.AiravataSystemException] = ase
+        self.ae: 
typing.Optional[airavata.api.error.ttypes.AuthorizationException] = ae
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, 
TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, [self.__class__, self.thrift_spec])
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 0:
+                if ftype == TType.STRUCT:
+                    self.success = 
airavata.model.appcatalog.storageresource.ttypes.StorageDirectoryInfo()
+                    self.success.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 1:
+                if ftype == TType.STRUCT:
+                    self.ire = 
airavata.api.error.ttypes.InvalidRequestException.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRUCT:
+                    self.ace = 
airavata.api.error.ttypes.AiravataClientException.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 3:
+                if ftype == TType.STRUCT:
+                    self.ase = 
airavata.api.error.ttypes.AiravataSystemException.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            elif fid == 4:
+                if ftype == TType.STRUCT:
+                    self.ae = 
airavata.api.error.ttypes.AuthorizationException.read(iprot)
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        self.validate()
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, [self.__class__, 
self.thrift_spec]))
+            return
+        oprot.writeStructBegin('getStorageDirectoryInfo_result')
+        if self.success is not None:
+            oprot.writeFieldBegin('success', TType.STRUCT, 0)
+            self.success.write(oprot)
+            oprot.writeFieldEnd()
+        if self.ire is not None:
+            oprot.writeFieldBegin('ire', TType.STRUCT, 1)
+            self.ire.write(oprot)
+            oprot.writeFieldEnd()
+        if self.ace is not None:
+            oprot.writeFieldBegin('ace', TType.STRUCT, 2)
+            self.ace.write(oprot)
+            oprot.writeFieldEnd()
+        if self.ase is not None:
+            oprot.writeFieldBegin('ase', TType.STRUCT, 3)
+            self.ase.write(oprot)
+            oprot.writeFieldEnd()
+        if self.ae is not None:
+            oprot.writeFieldBegin('ae', TType.STRUCT, 4)
+            self.ae.write(oprot)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == 
other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+all_structs.append(getStorageDirectoryInfo_result)
+getStorageDirectoryInfo_result.thrift_spec = (
+    (0, TType.STRUCT, 'success', 
[airavata.model.appcatalog.storageresource.ttypes.StorageDirectoryInfo, None], 
None, ),  # 0
+    (1, TType.STRUCT, 'ire', 
[airavata.api.error.ttypes.InvalidRequestException, None], None, ),  # 1
+    (2, TType.STRUCT, 'ace', 
[airavata.api.error.ttypes.AiravataClientException, None], None, ),  # 2
+    (3, TType.STRUCT, 'ase', 
[airavata.api.error.ttypes.AiravataSystemException, None], None, ),  # 3
+    (4, TType.STRUCT, 'ae', [airavata.api.error.ttypes.AuthorizationException, 
None], None, ),  # 4
+)
 fix_spec(all_structs)
 del all_structs
diff --git a/dev-tools/airavata-python-sdk/airavata/model/__init__.py 
b/dev-tools/airavata-python-sdk/airavata/model/__init__.py
index adefd8e51f..e69de29bb2 100644
--- a/dev-tools/airavata-python-sdk/airavata/model/__init__.py
+++ b/dev-tools/airavata-python-sdk/airavata/model/__init__.py
@@ -1 +0,0 @@
-__all__ = ['ttypes', 'constants']
diff --git 
a/dev-tools/airavata-python-sdk/airavata/model/appcatalog/storageresource/ttypes.py
 
b/dev-tools/airavata-python-sdk/airavata/model/appcatalog/storageresource/ttypes.py
index 5481af5ae1..6a929fddf4 100644
--- 
a/dev-tools/airavata-python-sdk/airavata/model/appcatalog/storageresource/ttypes.py
+++ 
b/dev-tools/airavata-python-sdk/airavata/model/appcatalog/storageresource/ttypes.py
@@ -351,6 +351,85 @@ class StorageVolumeInfo(object):
 
     def __ne__(self, other):
         return not (self == other)
+
+
+class StorageDirectoryInfo(object):
+    """
+    Provides Directory Size Information of a given storage
+
+    totalSize: Total size in human-readable format (e.g., "100G", "500M")
+    totalSizeBytes: Total size in bytes
+
+    Attributes:
+     - totalSize
+     - totalSizeBytes
+
+    """
+    thrift_spec: typing.Any = None
+
+
+    def __init__(self, totalSize: str = None, totalSizeBytes: int = None,):
+        self.totalSize: str = totalSize
+        self.totalSizeBytes: int = totalSizeBytes
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, 
TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, [self.__class__, self.thrift_spec])
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRING:
+                    self.totalSize = iprot.readString().decode('utf-8', 
errors='replace') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.I64:
+                    self.totalSizeBytes = iprot.readI64()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        self.validate()
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, [self.__class__, 
self.thrift_spec]))
+            return
+        oprot.writeStructBegin('StorageDirectoryInfo')
+        if self.totalSize is not None:
+            oprot.writeFieldBegin('totalSize', TType.STRING, 1)
+            oprot.writeString(self.totalSize.encode('utf-8') if 
sys.version_info[0] == 2 else self.totalSize)
+            oprot.writeFieldEnd()
+        if self.totalSizeBytes is not None:
+            oprot.writeFieldBegin('totalSizeBytes', TType.I64, 2)
+            oprot.writeI64(self.totalSizeBytes)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.totalSize is None:
+            raise TProtocolException(message='Required field totalSize is 
unset!')
+        if self.totalSizeBytes is None:
+            raise TProtocolException(message='Required field totalSizeBytes is 
unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == 
other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
 all_structs.append(StorageResourceDescription)
 StorageResourceDescription.thrift_spec = (
     None,  # 0
@@ -375,5 +454,11 @@ StorageVolumeInfo.thrift_spec = (
     (8, TType.STRING, 'mountPoint', 'UTF8', None, ),  # 8
     (9, TType.STRING, 'filesystemType', 'UTF8', None, ),  # 9
 )
+all_structs.append(StorageDirectoryInfo)
+StorageDirectoryInfo.thrift_spec = (
+    None,  # 0
+    (1, TType.STRING, 'totalSize', 'UTF8', None, ),  # 1
+    (2, TType.I64, 'totalSizeBytes', None, None, ),  # 2
+)
 fix_spec(all_structs)
 del all_structs
diff --git a/dev-tools/airavata-python-sdk/airavata_experiments/airavata.py 
b/dev-tools/airavata-python-sdk/airavata_experiments/airavata.py
index 24961dcfc1..fa596feeaa 100644
--- a/dev-tools/airavata-python-sdk/airavata_experiments/airavata.py
+++ b/dev-tools/airavata-python-sdk/airavata_experiments/airavata.py
@@ -63,7 +63,7 @@ class AiravataOperator:
       input_file_name: str,
       uploaded_storage_path: str,
   ) -> str:
-    
+
     dataProductModel = DataProductModel(
       gatewayId=gateway_id,
       ownerName=self.user_id,
@@ -88,7 +88,7 @@ class AiravataOperator:
       description: str,
       gateway_id: str,
   ) -> ExperimentModel:
-    
+
     execution_id = self.get_app_interface_id(application_name)
     project_id = self.get_project_id(project_name)
     return ExperimentModel(
@@ -100,13 +100,13 @@ class AiravataOperator:
       experimentType=ExperimentType.SINGLE_APPLICATION,
       executionId=execution_id
     )
-  
+
   def get_resource_host_id(self, resource_name):
     resources = 
self.api_server_client.get_all_compute_resource_names(self.airavata_token)
     resource_id = next((k for k in resources if k.startswith(resource_name)), 
None)
     assert resource_id is not None, f"Compute resource {resource_name} not 
found"
     return resource_id
-  
+
   def configure_computation_resource_scheduling(
       self,
       experiment_model: ExperimentModel,
@@ -154,19 +154,19 @@ class AiravataOperator:
 
   def default_gateway_id(self):
     return self.settings.GATEWAY_ID
-  
+
   def default_gateway_data_store_dir(self):
     return self.settings.GATEWAY_DATA_STORE_DIR
-  
+
   def default_sftp_port(self):
     return self.settings.SFTP_PORT
-  
+
   def default_sr_hostname(self):
     return self.settings.STORAGE_RESOURCE_HOST
-  
+
   def connection_svc_url(self):
     return f"{self.settings.API_SERVER_URL}/api/v1"
-  
+
   def filemgr_svc_url(self):
     return self.settings.FILE_SVC_URL
 
@@ -186,7 +186,7 @@ class AiravataOperator:
 
     """
     return self.api_server_client.get_experiment(self.airavata_token, 
experiment_id)
-  
+
   def get_process_id(self, experiment_id: str) -> str:
     """
     Get process id by experiment id
@@ -247,7 +247,7 @@ class AiravataOperator:
     grp_id = next((grp.groupResourceProfileId for grp in grps if 
grp.groupResourceProfileName == group), None)
     assert grp_id is not None, f"Group resource profile {group} not found"
     return str(grp_id)
-  
+
   def get_group_resource_profile(self, group_id: str):
     grp = 
self.api_server_client.get_group_resource_profile(self.airavata_token, 
group_id) # type: ignore
     return grp
@@ -327,7 +327,7 @@ class AiravataOperator:
       paths = sftp_connector.put(local_files, remote_dir)
       logger.info(f"{len(paths)} Local files uploaded to remote dir: %s", 
remote_dir)
       return paths
-    
+
     # step = post-staging file upload
     elif process_id is not None and agent_ref is not None:
       assert len(local_files) == 1, f"Expected 1 file, got {len(local_files)}"
@@ -362,7 +362,7 @@ class AiravataOperator:
     # step = unknown
     else:
       raise ValueError("Invalid arguments for upload_files")
-    
+
     # file manager service fallback
     assert process_id is not None, f"Expected process_id, got {process_id}"
     file = local_files[0]
@@ -445,7 +445,7 @@ class AiravataOperator:
             f.write(content)
           return path.as_posix()
         time.sleep(1)
-    
+
     # file manager service fallback
     assert process_id is not None, f"Expected process_id, got {process_id}"
     url_path = os.path.join(process_id, remote_file)
@@ -621,6 +621,9 @@ class AiravataOperator:
         experiment_name=experiment_name,
     )
     abs_path = (mount_point / exp_dir.lstrip("/")).as_posix().rstrip("/") + "/"
+    if input_sr_id != output_sr_id:
+      abs_path = self.user_id + "/" + project + "/" + experiment_name
+
     print("[AV] exp_dir:", exp_dir)
     print("[AV] abs_path:", abs_path)
 
@@ -641,7 +644,7 @@ class AiravataOperator:
 
     def register_input_file(file: Path) -> str:
       return str(self.register_input_file(file.name, input_sr_host, 
input_sr_id, gateway_id, file.name, abs_path))
-    
+
     # set up experiment inputs
     print("[AV] Setting up experiment inputs...")
     files_to_upload = list[Path]()
@@ -716,16 +719,16 @@ class AiravataOperator:
       except Exception as status_err:
         if "FAILED" in str(status_err) or "terminal state" in str(status_err):
           raise status_err
-      
+
       try:
         process_id = self.get_process_id(ex_id)
       except:
         pass
-      
+
       if process_id is None:
         time.sleep(2)
         wait_count_process += 1
-    
+
     if process_id is None:
       raise Exception(f"[AV] Experiment {experiment_name} timeout waiting for 
process to begin")
     print(f"[AV] Experiment {experiment_name} EXECUTING with pid: 
{process_id}")
@@ -746,16 +749,16 @@ class AiravataOperator:
       except Exception as status_err:
         if "FAILED" in str(status_err) or "terminal state" in str(status_err):
           raise status_err
-      
+
       try:
         job_id, job_state = self.get_task_status(ex_id)
       except:
         pass
-      
+
       if job_id in [None, "N/A"]:
         time.sleep(2)
         wait_count_task += 1
-    
+
     if job_id in [None, "N/A"]:
       raise Exception(f"[AV] Experiment {experiment_name} timeout waiting for 
task to begin")
     assert job_state is not None, f"Job state is None for job id: {job_id}"
@@ -781,7 +784,7 @@ class AiravataOperator:
     status = self.api_server_client.terminate_experiment(
         self.airavata_token, experiment_id, self.default_gateway_id())
     return status
-  
+
   def execute_py(self, project: str, libraries: list[str], code: str, 
agent_id: str, pid: str, runtime_args: dict, cold_start: bool = True) -> str | 
None:
     # lambda to send request
     try:
@@ -817,7 +820,7 @@ class AiravataOperator:
         if data["agentUp"]:
           break
         time.sleep(1)
-      
+
       print(f"[av] Agent {agent_id} found! creating environment...")
       res = requests.post(f"{self.connection_svc_url()}/setup/env", json={
         "agentId": agent_id,
@@ -853,18 +856,18 @@ class AiravataOperator:
           response = str(data["responseString"])
           break
         time.sleep(1)
-      
+
       print(f"[av] Agent {agent_id} code executed! response: {response}")
       return response
-    
+
     except Exception as e:
       print(f"[av] Remote execution failed! {e}")
       return None
-    
+
   def get_available_groups(self, gateway_id: str = "default"):
     grps: list[GroupResourceProfile] = 
self.api_server_client.get_group_resource_list(self.airavata_token, 
gatewayId=gateway_id)
     return grps
-  
+
   def get_available_runtimes(self, group: str, gateway_id: str = "default"):
     grps = self.get_available_groups(gateway_id)
     grp_id, gcr_prefs, gcr_policies = next(((x.groupResourceProfileId, 
x.computePreferences, x.computeResourcePolicies) for x in grps if 
str(x.groupResourceProfileName).strip() == group.strip()), (None, None, None))
@@ -896,13 +899,13 @@ class AiravataOperator:
         )
         runtimes.append(runtime)
     return runtimes
-  
+
   def get_task_status(self, experiment_id: str) -> tuple[str, JobState]:
     job_details: dict[str, JobStatus] = 
self.api_server_client.get_job_statuses(self.airavata_token, experiment_id) # 
type: ignore
     job_id = job_state = None
     for job_id, v in job_details.items():
       job_state = v.jobState
     return job_id or "N/A", job_state or JobState.UNKNOWN
-  
+
   JobState = JobState
 
diff --git a/dev-tools/airavata-python-sdk/pyproject.toml 
b/dev-tools/airavata-python-sdk/pyproject.toml
index 601b05f601..e673998406 100644
--- a/dev-tools/airavata-python-sdk/pyproject.toml
+++ b/dev-tools/airavata-python-sdk/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
 
 [project]
 name = "airavata-python-sdk"
-version = "2.2.5"
+version = "2.2.6"
 description = "Apache Airavata Python SDK"
 readme = "README.md"
 license = "Apache-2.0"
diff --git a/thrift-interface-descriptions/airavata-apis/airavata_api.thrift 
b/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
index c5a95d325d..964a55ceb5 100644
--- a/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
+++ b/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
@@ -20,7 +20,7 @@
 
 /**
  * Application Programming Interface definition for Apache Airavata Services.
- *   this parent thrift file is contains all service interfaces. The data 
models are 
+ *   this parent thrift file is contains all service interfaces. The data 
models are
  *   described in respective thrift files.
 */
 
@@ -102,7 +102,7 @@ service Airavata extends base_api.BaseAPI {
    *
    * @param gateway
    *    The gateway data model.
-   * 
+   *
    * @return gatewayId
    *   Th unique identifier of the  newly registered gateway.
    *
@@ -673,13 +673,13 @@ service Airavata extends base_api.BaseAPI {
    *
    * @throws org.apache.airavata.model.error.InvalidRequestException
    *    For any incorrect forming of the request itself.
-   * 
+   *
    * @throws org.apache.airavata.model.error.ExperimentNotFoundException
    *    If the specified experiment is not previously created, then an 
Experiment Not Found Exception is thrown.
-   * 
+   *
    * @throws org.apache.airavata.model.error.AiravataClientException
    *    The following list of exceptions are thrown which Airavata Client can 
take corrective actions to resolve:
-   *      
+   *
    *      UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata as 
a one time administrative
    *         step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
    *         gateway registration steps and retry this request.
@@ -811,13 +811,13 @@ service Airavata extends base_api.BaseAPI {
    *
    * @throws org.apache.airavata.model.error.InvalidRequestException
    *    For any incorrect forming of the request itself.
-   * 
+   *
    * @throws org.apache.airavata.model.error.ExperimentNotFoundException
    *    If the specified experiment is not previously created, then an 
Experiment Not Found Exception is thrown.
-   * 
+   *
    * @throws org.apache.airavata.model.error.AiravataClientException
    *    The following list of exceptions are thrown which Airavata Client can 
take corrective actions to resolve:
-   *      
+   *
    *      UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata as 
a one time administrative
    *         step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
    *         gateway registration steps and retry this request.
@@ -888,13 +888,13 @@ service Airavata extends base_api.BaseAPI {
    *
    * @throws org.apache.airavata.model.error.InvalidRequestException
    *    For any incorrect forming of the request itself.
-   * 
+   *
    * @throws org.apache.airavata.model.error.ExperimentNotFoundException
    *    If the specified experiment is not previously created, then an 
Experiment Not Found Exception is thrown.
-   * 
+   *
    * @throws org.apache.airavata.model.error.AiravataClientException
    *    The following list of exceptions are thrown which Airavata Client can 
take corrective actions to resolve:
-   *      
+   *
    *      UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata as 
a one time administrative
    *         step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
    *         gateway registration steps and retry this request.
@@ -1076,7 +1076,7 @@ service Airavata extends base_api.BaseAPI {
    *
    * Clone an Existing Experiment
    * Existing specified experiment is cloned and a new name is provided. A 
copy of the experiment configuration is made and is persisted with new metadata.
-   *   The client has to subsequently update this configuration if needed and 
launch the cloned experiment. 
+   *   The client has to subsequently update this configuration if needed and 
launch the cloned experiment.
    *
    * @param newExperimentName
    *    experiment name that should be used in the cloned experiment
@@ -1094,13 +1094,13 @@ service Airavata extends base_api.BaseAPI {
    *
    * @throws org.apache.airavata.model.error.InvalidRequestException
    *    For any incorrect forming of the request itself.
-   * 
+   *
    * @throws org.apache.airavata.model.error.ExperimentNotFoundException
    *    If the specified experiment is not previously created, then an 
Experiment Not Found Exception is thrown.
-   * 
+   *
    * @throws org.apache.airavata.model.error.AiravataClientException
    *    The following list of exceptions are thrown which Airavata Client can 
take corrective actions to resolve:
-   *      
+   *
    *      UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata as 
a one time administrative
    *         step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
    *         gateway registration steps and retry this request.
@@ -1196,13 +1196,13 @@ service Airavata extends base_api.BaseAPI {
    *
    * @throws org.apache.airavata.model.error.InvalidRequestException
    *    For any incorrect forming of the request itself.
-   * 
+   *
    * @throws org.apache.airavata.model.error.ExperimentNotFoundException
    *    If the specified experiment is not previously created, then an 
Experiment Not Found Exception is thrown.
-   * 
+   *
    * @throws org.apache.airavata.model.error.AiravataClientException
    *    The following list of exceptions are thrown which Airavata Client can 
take corrective actions to resolve:
-   *      
+   *
    *      UNKNOWN_GATEWAY_ID - If a Gateway is not registered with Airavata as 
a one time administrative
    *         step, then Airavata Registry will not have a provenance area 
setup. The client has to follow
    *         gateway registration steps and retry this request.
@@ -3627,6 +3627,22 @@ service Airavata extends base_api.BaseAPI {
                                                                          3: 
airavata_errors.AiravataSystemException ase,
                                                                          4: 
airavata_errors.AuthorizationException ae)
 
+/**
+  * Get storage directory information for a compute or storage resource.
+  *
+  * @param authzToken
+  * @param resourceId Can be either a compute resource ID or storage resource 
ID
+  * @param location Optional path/mount point. If null/empty, defaults to 
user's home directory ($HOME)
+  * @return StorageDirectoryInfo containing directory size information
+  */
+ storage_resource_model.StorageDirectoryInfo getStorageDirectoryInfo(1: 
required security_model.AuthzToken authzToken,
+                                                                 2: required 
string resourceId,
+                                                                 3: optional 
string location)
+                                                                 throws (1: 
airavata_errors.InvalidRequestException ire,
+                                                                         2: 
airavata_errors.AiravataClientException ace,
+                                                                         3: 
airavata_errors.AiravataSystemException ase,
+                                                                         4: 
airavata_errors.AuthorizationException ae)
+
 //
 //End of API
 }
diff --git 
a/thrift-interface-descriptions/data-models/storage_resource_model.thrift 
b/thrift-interface-descriptions/data-models/storage_resource_model.thrift
index 8b41079379..9d6fa4d70e 100644
--- a/thrift-interface-descriptions/data-models/storage_resource_model.thrift
+++ b/thrift-interface-descriptions/data-models/storage_resource_model.thrift
@@ -79,3 +79,14 @@ struct StorageVolumeInfo {
     8: required string mountPoint,
     9: optional string filesystemType,
 }
+
+/**
+ * Provides Directory Size Information of a given storage
+ *
+ * totalSize: Total size in human-readable format (e.g., "100G", "500M")
+ * totalSizeBytes: Total size in bytes
+ */
+struct StorageDirectoryInfo {
+    1: required string totalSize,
+    2: required i64 totalSizeBytes,
+}

Reply via email to