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

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


The following commit(s) were added to refs/heads/develop by this push:
     new fa2a324538 Add getResourceStorageInfo API to retrieve disk usage from 
compute/storage resources
fa2a324538 is described below

commit fa2a3245382da39e9bdc3ba93cbdb5159e38a7f5
Author: lahiruj <[email protected]>
AuthorDate: Fri Nov 7 20:17:57 2025 -0500

    Add getResourceStorageInfo API to retrieve disk usage from compute/storage 
resources
---
 .../apache/airavata/agents/api/AgentAdaptor.java   |  31 +--
 .../api/server/handler/AiravataServerHandler.java  | 214 +++++++++++++++++++++
 .../airavata/helix/adaptor/SSHJAgentAdaptor.java   | 112 +++++++++++
 .../airavata/helix/adaptor/SSHJStorageAdaptor.java |   5 +
 .../airavata/helix/agent/ssh/SshAgentAdaptor.java  |   6 +
 .../core/support/adaptor/AdaptorSupportImpl.java   |  66 ++++++-
 .../helix/core/support/adaptor/AgentStore.java     |  33 ++++
 .../helix/task/api/support/AdaptorSupport.java     |  14 +-
 .../airavata_sdk/clients/api_server_client.py      |   1 +
 .../airavata-apis/airavata_api.thrift              |  23 ++-
 .../data-models/storage_resource_model.thrift      |  27 +++
 11 files changed, 509 insertions(+), 23 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 38375c714f..4a9b338991 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
@@ -19,6 +19,8 @@
 */
 package org.apache.airavata.agents.api;
 
+import org.apache.airavata.model.appcatalog.storageresource.StorageVolumeInfo;
+
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
@@ -31,30 +33,31 @@ import java.util.List;
  */
 public interface AgentAdaptor {
 
-    public void init(String computeResource, String gatewayId, String userId, 
String token) throws AgentException;
+    void init(String computeResource, String gatewayId, String userId, String 
token) throws AgentException;
+
+    void destroy();
 
-    public void destroy();
+    CommandOutput executeCommand(String command, String workingDirectory) 
throws AgentException;
 
-    public CommandOutput executeCommand(String command, String 
workingDirectory) throws AgentException;
+    void createDirectory(String path) throws AgentException;
 
-    public void createDirectory(String path) throws AgentException;
+    void createDirectory(String path, boolean recursive) throws AgentException;
 
-    public void createDirectory(String path, boolean recursive) throws 
AgentException;
+    void uploadFile(String localFile, String remoteFile) throws AgentException;
 
-    public void uploadFile(String localFile, String remoteFile) throws 
AgentException;
+    void uploadFile(InputStream localInStream, FileMetadata metadata, String 
remoteFile) throws AgentException;
 
-    public void uploadFile(InputStream localInStream, FileMetadata metadata, 
String remoteFile) throws AgentException;
+    void downloadFile(String remoteFile, String localFile) throws 
AgentException;
 
-    public void downloadFile(String remoteFile, String localFile) throws 
AgentException;
+    void downloadFile(String remoteFile, OutputStream localOutStream, 
FileMetadata metadata) throws AgentException;
 
-    public void downloadFile(String remoteFile, OutputStream localOutStream, 
FileMetadata metadata)
-            throws AgentException;
+    List<String> listDirectory(String path) throws AgentException;
 
-    public List<String> listDirectory(String path) throws AgentException;
+    Boolean doesFileExist(String filePath) throws AgentException;
 
-    public Boolean doesFileExist(String filePath) throws AgentException;
+    List<String> getFileNameFromExtension(String fileName, String parentPath) 
throws AgentException;
 
-    public List<String> getFileNameFromExtension(String fileName, String 
parentPath) throws AgentException;
+    FileMetadata getFileMetadata(String remoteFile) throws AgentException;
 
-    public FileMetadata getFileMetadata(String remoteFile) throws 
AgentException;
+    StorageVolumeInfo getStorageVolumeInfo(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 4be740eebd..e7a29ab87a 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
@@ -57,6 +57,7 @@ import 
org.apache.airavata.model.appcatalog.groupresourceprofile.GroupResourcePr
 import org.apache.airavata.model.appcatalog.parser.Parser;
 import org.apache.airavata.model.appcatalog.parser.ParsingTemplate;
 import 
org.apache.airavata.model.appcatalog.storageresource.StorageResourceDescription;
+import org.apache.airavata.model.appcatalog.storageresource.StorageVolumeInfo;
 import 
org.apache.airavata.model.appcatalog.userresourceprofile.UserComputeResourcePreference;
 import 
org.apache.airavata.model.appcatalog.userresourceprofile.UserResourceProfile;
 import 
org.apache.airavata.model.appcatalog.userresourceprofile.UserStoragePreference;
@@ -93,6 +94,9 @@ import org.apache.airavata.model.workspace.Notification;
 import org.apache.airavata.model.workspace.Project;
 import org.apache.airavata.registry.api.RegistryService;
 import org.apache.airavata.registry.api.exception.RegistryServiceException;
+import org.apache.airavata.agents.api.AgentAdaptor;
+import org.apache.airavata.agents.api.AgentException;
+import org.apache.airavata.helix.core.support.adaptor.AdaptorSupportImpl;
 import org.apache.airavata.service.security.GatewayGroupsInitializer;
 import org.apache.airavata.service.security.interceptor.SecurityCheck;
 import org.apache.airavata.sharing.registry.models.*;
@@ -3839,6 +3843,58 @@ public class AiravataServerHandler implements 
Airavata.Iface {
         }
     }
 
+    @Override
+    @SecurityCheck
+    public StorageVolumeInfo getResourceStorageInfo(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.ofNullable(regClient.getComputeResource(resourceId));
+            Optional<StorageResourceDescription> storageResourceOp = 
Optional.empty();
+
+            if (computeResourceOp.isEmpty()) {
+                storageResourceOp = 
Optional.ofNullable(regClient.getStorageResource(resourceId));
+            }
+
+            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.getStorageVolumeInfo(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.
@@ -7232,4 +7288,162 @@ public class AiravataServerHandler implements 
Airavata.Iface {
             return GatewayGroupsInitializer.initializeGatewayGroups(gatewayId);
         }
     }
+
+    /**
+     * To hold storage info context (login username, credential token, and 
adaptor)
+     */
+    private record StorageInfoContext(String loginUserName, String 
credentialToken, AgentAdaptor adaptor) {
+    }
+
+    private AiravataClientException clientException(AiravataErrorType 
errorType, String parameter) {
+        AiravataClientException exception = new AiravataClientException();
+        exception.setAiravataErrorType(errorType);
+        exception.setParameter(parameter);
+        return exception;
+    }
+
+    /**
+     * Resolves compute resource storage info context (login username, 
credential token, and adaptor).
+     * Handles user preference → group preference fallback for both login and 
credentials.
+     */
+    private StorageInfoContext resolveComputeStorageInfoContext(AuthzToken 
authzToken, String gatewayId, String userId, String resourceId) throws 
AgentException, TException {
+        String loginUserName = null;
+        boolean loginFromUserPref = false;
+        GroupComputeResourcePreference groupComputePref = null;
+        GroupResourceProfile groupResourceProfile = null;
+
+        UserComputeResourcePreference userComputePref = 
getUserComputeResourcePreference(authzToken, userId, gatewayId, resourceId);
+
+        if (userComputePref != null && userComputePref.getLoginUserName() != 
null && !userComputePref.getLoginUserName().trim().isEmpty()) {
+            loginUserName = userComputePref.getLoginUserName();
+            loginFromUserPref = true;
+            logger.debug("Using user preference login username: {}", 
loginUserName);
+
+        } else {
+            // Fallback to GroupComputeResourcePreference
+            List<GroupResourceProfile> groupResourceProfiles = 
getGroupResourceList(authzToken, gatewayId);
+            for (GroupResourceProfile groupProfile : groupResourceProfiles) {
+                List<GroupComputeResourcePreference> groupComputePrefs = 
groupProfile.getComputePreferences();
+
+                if (groupComputePrefs != null && !groupComputePrefs.isEmpty()) 
{
+                    for (GroupComputeResourcePreference groupPref : 
groupComputePrefs) {
+                        if 
(resourceId.equals(groupPref.getComputeResourceId()) && 
groupPref.getLoginUserName() != null && 
!groupPref.getLoginUserName().trim().isEmpty()) {
+                            loginUserName = groupPref.getLoginUserName();
+                            groupComputePref = groupPref;
+                            groupResourceProfile = groupProfile;
+                            logger.debug("Using login username from group 
compute resource preference for resource {}", resourceId);
+                            break;
+                        }
+                    }
+                }
+                if (loginUserName != null) {
+                    break;
+                }
+            }
+            if (loginUserName == null) {
+                logger.debug("No login username found for compute resource 
{}", resourceId);
+                throw new InvalidRequestException("No login username found for 
compute resource " + resourceId);
+            }
+        }
+
+        // Resolve credential token based on where login came from
+        String credentialToken;
+        if (loginFromUserPref) {
+            // Login username came from user preference. Use user preference 
token → user profile token
+            if (userComputePref != null && 
userComputePref.getResourceSpecificCredentialStoreToken() != null && 
!userComputePref.getResourceSpecificCredentialStoreToken().trim().isEmpty()) {
+                credentialToken = 
userComputePref.getResourceSpecificCredentialStoreToken();
+            } else {
+                UserResourceProfile userResourceProfile = 
getUserResourceProfile(authzToken, userId, gatewayId);
+                if (userResourceProfile == null || 
userResourceProfile.getCredentialStoreToken() == null || 
userResourceProfile.getCredentialStoreToken().trim().isEmpty()) {
+                    logger.error("No credential store token found for user {} 
in gateway {}", userId, gatewayId);
+                    throw 
clientException(AiravataErrorType.AUTHENTICATION_FAILURE, "No credential store 
token found for user " + userId + " in gateway " + gatewayId);
+                }
+                credentialToken = 
userResourceProfile.getCredentialStoreToken();
+            }
+        } else {
+            // Login username came from group preference. Use group preference 
token → group profile default token → user profile token (fallback)
+            if (groupComputePref != null && 
groupComputePref.getResourceSpecificCredentialStoreToken() != null && 
!groupComputePref.getResourceSpecificCredentialStoreToken().trim().isEmpty()) {
+                credentialToken = 
groupComputePref.getResourceSpecificCredentialStoreToken();
+
+            } else if (groupResourceProfile != null && 
groupResourceProfile.getDefaultCredentialStoreToken() != null && 
!groupResourceProfile.getDefaultCredentialStoreToken().trim().isEmpty()) {
+                credentialToken = 
groupResourceProfile.getDefaultCredentialStoreToken();
+
+            } else {
+                UserResourceProfile userResourceProfile = 
getUserResourceProfile(authzToken, userId, gatewayId);
+                if (userResourceProfile == null || 
userResourceProfile.getCredentialStoreToken() == null || 
userResourceProfile.getCredentialStoreToken().trim().isEmpty()) {
+                    logger.error("No credential store token found for user {} 
in gateway {}", userId, gatewayId);
+                    throw 
clientException(AiravataErrorType.AUTHENTICATION_FAILURE, "No credential store 
token found for compute resource " + resourceId);
+                }
+                credentialToken = 
userResourceProfile.getCredentialStoreToken();
+            }
+        }
+
+        AgentAdaptor adaptor = 
AdaptorSupportImpl.getInstance().fetchComputeSSHAdaptor(gatewayId, resourceId, 
credentialToken, userId, loginUserName);
+        logger.info("Resolved resource {} as compute resource to fetch storage 
details", resourceId);
+
+        return new StorageInfoContext(loginUserName, credentialToken, adaptor);
+    }
+
+    /**
+     * Resolves storage resource storage info context (login username, 
credential token, and adaptor).
+     * Handles user preference → gateway preference fallback for both login 
and credentials.
+     */
+    private StorageInfoContext resolveStorageStorageInfoContext(AuthzToken 
authzToken, String gatewayId, String userId, String resourceId) throws 
AgentException, TException {
+        UserStoragePreference userStoragePref = 
getUserStoragePreference(authzToken, userId, gatewayId, resourceId);
+        StoragePreference storagePref = 
getGatewayStoragePreference(authzToken, gatewayId, resourceId);
+
+        String loginUserName;
+        boolean loginFromUserPref;
+
+        if (userStoragePref != null && userStoragePref.getLoginUserName() != 
null && !userStoragePref.getLoginUserName().trim().isEmpty()) {
+            loginUserName = userStoragePref.getLoginUserName();
+            loginFromUserPref = true;
+            logger.debug("Using login username from user storage preference 
for resource {}", resourceId);
+
+        } else if (storagePref != null && storagePref.getLoginUserName() != 
null && !storagePref.getLoginUserName().trim().isEmpty()) {
+            loginUserName = storagePref.getLoginUserName();
+            loginFromUserPref = false;
+            logger.debug("Using login username from gateway storage preference 
for resource {}", resourceId);
+
+        } else {
+            logger.error("No login username found for storage resource {}", 
resourceId);
+            throw new InvalidRequestException("No login username found for 
storage resource " + resourceId);
+        }
+
+        // Resolve credential token based on where login came from
+        String credentialToken;
+        if (loginFromUserPref) {
+            // Login came from user preference. Use user preference token or 
user profile token
+            if (userStoragePref != null && 
userStoragePref.getResourceSpecificCredentialStoreToken() != null && 
!userStoragePref.getResourceSpecificCredentialStoreToken().trim().isEmpty()) {
+                credentialToken = 
userStoragePref.getResourceSpecificCredentialStoreToken();
+                logger.debug("Using login username from user preference for 
resource {}", resourceId);
+
+            } else {
+                UserResourceProfile userResourceProfile = 
getUserResourceProfile(authzToken, userId, gatewayId);
+                if (userResourceProfile == null || 
userResourceProfile.getCredentialStoreToken() == null || 
userResourceProfile.getCredentialStoreToken().trim().isEmpty()) {
+                    logger.error("No credential store token found for user {} 
in gateway {}", userId, gatewayId);
+                    throw 
clientException(AiravataErrorType.AUTHENTICATION_FAILURE, "No credential store 
token found for user " + userId + " in gateway " + gatewayId);
+                }
+                credentialToken = 
userResourceProfile.getCredentialStoreToken();
+            }
+        } else {
+            // Login came from gateway preference. Use gateway preference 
token or gateway profile token
+            if (storagePref != null && 
storagePref.getResourceSpecificCredentialStoreToken() != null && 
!storagePref.getResourceSpecificCredentialStoreToken().trim().isEmpty()) {
+                credentialToken = 
storagePref.getResourceSpecificCredentialStoreToken();
+
+            } else {
+                GatewayResourceProfile gatewayResourceProfile = 
getGatewayResourceProfile(authzToken, gatewayId);
+                if (gatewayResourceProfile == null || 
gatewayResourceProfile.getCredentialStoreToken() == null || 
gatewayResourceProfile.getCredentialStoreToken().trim().isEmpty()) {
+                    logger.error("No credential store token found for gateway 
{}", gatewayId);
+                    throw 
clientException(AiravataErrorType.AUTHENTICATION_FAILURE, "No credential store 
token found for gateway " + gatewayId);
+                }
+                credentialToken = 
gatewayResourceProfile.getCredentialStoreToken();
+            }
+        }
+
+        AgentAdaptor adaptor = 
AdaptorSupportImpl.getInstance().fetchStorageSSHAdaptor(gatewayId, resourceId, 
credentialToken, userId, loginUserName);
+        logger.info("Resolved resource {} as storage resource to fetch storage 
details", resourceId);
+
+        return new StorageInfoContext(loginUserName, credentialToken, adaptor);
+    }
 }
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 7b715f4cd3..410b57dc60 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.StorageVolumeInfo;
 import org.apache.airavata.model.credential.store.SSHCredential;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -593,4 +594,115 @@ public class SSHJAgentAdaptor implements AgentAdaptor {
         }
         return j == p.length();
     }
+
+    @Override
+    public StorageVolumeInfo getStorageVolumeInfo(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 dfCommand = "df -P -T -h '" + escapedLocation + "'";
+            String dfBytesCommand = "df -P -T '" + escapedLocation + "'";
+
+            CommandOutput dfHumanOutput = executeCommand(dfCommand, null);
+            CommandOutput dfBytesOutput = executeCommand(dfBytesCommand, null);
+
+            if (dfHumanOutput.getExitCode() != 0) {
+                logger.error("Failed to execute df command for location {}: 
{}", targetLocation, dfHumanOutput.getStdError());
+                throw new AgentException("Failed to execute df command for 
location " + targetLocation + ": " + dfHumanOutput.getStdError());
+            }
+
+            if (dfBytesOutput.getExitCode() != 0) {
+                logger.error("Failed to execute df command for location {}: 
{}", targetLocation, dfBytesOutput.getStdError());
+                throw new AgentException("Failed to execute df command for 
location " + targetLocation + ": " + dfBytesOutput.getStdError());
+            }
+
+            return parseDfOutput(dfHumanOutput.getStdOut(), 
dfBytesOutput.getStdOut(), targetLocation);
+
+        } catch (Exception e) {
+            logger.error("Error while retrieving storage volume info for 
location " + location, e);
+            throw new AgentException("Error while retrieving storage volume 
info for location " + location, e);
+        }
+    }
+
+    private StorageVolumeInfo parseDfOutput(String dfHumanOutput, String 
dfBytesOutput, String targetLocation) throws AgentException {
+        try {
+            // Parse df -P -T -h output (POSIX format with filesystem type)
+            String[] humanLines = dfHumanOutput.split("\n");
+            String[] bytesLines = dfBytesOutput.split("\n");
+
+            if (humanLines.length < 2 || bytesLines.length < 2) {
+                logger.error("Unexpected df output format while parsing 
storage volume info for location {}", targetLocation);
+                throw new AgentException("Unexpected df output format while 
parsing storage volume info for location " + targetLocation);
+            }
+
+            // Skip the header line and get the data line
+            String humanDataLine = humanLines[1].trim();
+            String bytesDataLine = bytesLines[1].trim();
+
+            // Split by whitespace. POSIX format uses fixed width columns 
separated by spaces
+            String[] humanFields = humanDataLine.split("\\s+");
+            String[] bytesFields = bytesDataLine.split("\\s+");
+
+            if (humanFields.length < 7 || bytesFields.length < 7) {
+                logger.error("Unexpected df output format - insufficient 
fields while parsing storage volume info for location {}", targetLocation);
+                throw new AgentException("Unexpected df output format - 
insufficient fields while parsing storage volume info for location " + 
targetLocation);
+            }
+
+            String filesystemType = humanFields[1]; // ext4, xfs, etc.
+            String totalSizeHuman = humanFields[2];
+            String usedSizeHuman = humanFields[3];
+            String availableSizeHuman = humanFields[4];
+            String capacityStr = humanFields[5].replace("%", "");
+
+            // If Mount point contains spaces
+            StringBuilder mountPointBuilder = new StringBuilder();
+            for (int i = 6; i < humanFields.length; i++) {
+                if (i > 6) {
+                    mountPointBuilder.append(" ");
+                }
+                mountPointBuilder.append(humanFields[i]);
+            }
+            String mountPoint = mountPointBuilder.toString();
+
+            // Parse bytes output. Same format but in 1024-byte blocks
+            long totalSizeBlocks = Long.parseLong(bytesFields[2]);
+            long usedSizeBlocks = Long.parseLong(bytesFields[3]);
+            long availableSizeBlocks = Long.parseLong(bytesFields[4]);
+
+            // Convert 1024-byte blocks to bytes
+            long totalSizeBytes = totalSizeBlocks * 1024L;
+            long usedSizeBytes = usedSizeBlocks * 1024L;
+            long availableSizeBytes = availableSizeBlocks * 1024L;
+
+            double percentageUsed = Double.parseDouble(capacityStr);
+
+            StorageVolumeInfo volumeInfo = new StorageVolumeInfo();
+            volumeInfo.setTotalSize(totalSizeHuman);
+            volumeInfo.setUsedSize(usedSizeHuman);
+            volumeInfo.setAvailableSize(availableSizeHuman);
+            volumeInfo.setTotalSizeBytes(totalSizeBytes);
+            volumeInfo.setUsedSizeBytes(usedSizeBytes);
+            volumeInfo.setAvailableSizeBytes(availableSizeBytes);
+            volumeInfo.setPercentageUsed(percentageUsed);
+            volumeInfo.setMountPoint(mountPoint);
+            volumeInfo.setFilesystemType(filesystemType);
+
+            return volumeInfo;
+
+        } catch (Exception e) {
+            logger.error("Error parsing df output: {} for location {}", 
e.getMessage(), targetLocation, e);
+            throw new AgentException("Error parsing df output: " + 
e.getMessage() + " for location " + targetLocation, e);
+        }
+    }
 }
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJStorageAdaptor.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJStorageAdaptor.java
index f9a0751b79..d309cdb632 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJStorageAdaptor.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/adaptor/SSHJStorageAdaptor.java
@@ -108,4 +108,9 @@ public class SSHJStorageAdaptor extends SSHJAgentAdaptor 
implements StorageResou
     public CommandOutput executeCommand(String command, String 
workingDirectory) throws AgentException {
         return super.executeCommand(command, workingDirectory);
     }
+
+    @Override
+    public 
org.apache.airavata.model.appcatalog.storageresource.StorageVolumeInfo 
getStorageVolumeInfo(String location) throws AgentException {
+        return super.getStorageVolumeInfo(location);
+    }
 }
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 a59be3c5fe..96a0eeed74 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.StorageVolumeInfo;
 import org.apache.airavata.model.credential.store.SSHCredential;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -540,6 +541,11 @@ public class SshAgentAdaptor implements AgentAdaptor {
         throw new AgentException("Operation not implemented");
     }
 
+    @Override
+    public StorageVolumeInfo getStorageVolumeInfo(String location) {
+        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/core/support/adaptor/AdaptorSupportImpl.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/core/support/adaptor/AdaptorSupportImpl.java
index b5c47b1b05..4fbbcc58a2 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/core/support/adaptor/AdaptorSupportImpl.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/core/support/adaptor/AdaptorSupportImpl.java
@@ -19,8 +19,9 @@
 */
 package org.apache.airavata.helix.core.support.adaptor;
 
-import java.util.Optional;
-import org.apache.airavata.agents.api.*;
+import org.apache.airavata.agents.api.AgentAdaptor;
+import org.apache.airavata.agents.api.AgentException;
+import org.apache.airavata.agents.api.StorageResourceAdaptor;
 import org.apache.airavata.helix.adaptor.SSHJAgentAdaptor;
 import org.apache.airavata.helix.adaptor.SSHJStorageAdaptor;
 import org.apache.airavata.helix.task.api.support.AdaptorSupport;
@@ -29,6 +30,8 @@ import 
org.apache.airavata.model.data.movement.DataMovementProtocol;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Optional;
+
 /**
  * TODO: Class level comments please
  *
@@ -131,4 +134,63 @@ public class AdaptorSupportImpl implements AdaptorSupport {
             }
         }
     }
+
+    @Override
+    public AgentAdaptor fetchComputeSSHAdaptor(String gatewayId, String 
resourceId, String authToken, String gatewayUserId, String loginUserName) 
throws AgentException {
+        String cacheKey = "compute-" + resourceId;
+
+        logger.debug("Fetching SSH adaptor for compute resource {} with token 
{} for gateway user {} with login username {}", resourceId, authToken, 
gatewayUserId, loginUserName);
+
+        Optional<AgentAdaptor> adaptorOp = agentStore.getSSHAdaptor(cacheKey, 
authToken, gatewayUserId, loginUserName);
+        if (adaptorOp.isPresent()) {
+            logger.debug("Reusing SSH adaptor for gateway {}, compute resource 
{}, gateway user {}, login username {}", gatewayId, resourceId, gatewayUserId, 
loginUserName);
+            return adaptorOp.get();
+
+        } else {
+            synchronized (this) {
+                adaptorOp = agentStore.getSSHAdaptor(cacheKey, authToken, 
gatewayUserId, loginUserName);
+                if (adaptorOp.isPresent()) {
+                    return adaptorOp.get();
+
+                } else {
+                    logger.debug("Could not find SSH adaptor for gateway {}, 
compute resource {}, gateway user {}, login username {}. Creating new one", 
gatewayId, resourceId, gatewayUserId, loginUserName);
+
+                    SSHJAgentAdaptor agentAdaptor = new SSHJAgentAdaptor();
+                    agentAdaptor.init(resourceId, gatewayId, loginUserName, 
authToken);
+
+                    agentStore.putSSHAdaptor(cacheKey, authToken, 
gatewayUserId, loginUserName, agentAdaptor);
+                    return agentAdaptor;
+                }
+            }
+        }
+    }
+
+    @Override
+    public StorageResourceAdaptor fetchStorageSSHAdaptor(String gatewayId, 
String resourceId, String authToken, String gatewayUserId, String 
loginUserName) throws AgentException {
+        String cacheKey = "storage-" + resourceId;
+
+        logger.debug("Fetching SSH adaptor for storage resource {} with token 
{} for gateway user {} with login username {}", resourceId, authToken, 
gatewayUserId, loginUserName);
+
+        Optional<AgentAdaptor> adaptorOp = agentStore.getSSHAdaptor(cacheKey, 
authToken, gatewayUserId, loginUserName);
+        if (adaptorOp.isPresent()) {
+            logger.debug("Reusing SSH adaptor for gateway {}, storage resource 
{}, gateway user {}, login username {}", gatewayId, resourceId, gatewayUserId, 
loginUserName);
+            return (StorageResourceAdaptor) adaptorOp.get();
+
+        } else {
+            synchronized (this) {
+                adaptorOp = agentStore.getSSHAdaptor(cacheKey, authToken, 
gatewayUserId, loginUserName);
+                if (adaptorOp.isPresent()) {
+                    return (StorageResourceAdaptor) adaptorOp.get();
+                } else {
+                    logger.debug("Could not find SSH adaptor for gateway {}, 
storage resource {}, gateway user {}, login username {}. Creating new one", 
gatewayId, resourceId, gatewayUserId, loginUserName);
+
+                    SSHJStorageAdaptor storageAdaptor = new 
SSHJStorageAdaptor();
+                    storageAdaptor.init(resourceId, gatewayId, loginUserName, 
authToken);
+
+                    agentStore.putSSHAdaptor(cacheKey, authToken, 
gatewayUserId, loginUserName, storageAdaptor);
+                    return storageAdaptor;
+                }
+            }
+        }
+    }
 }
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/helix/core/support/adaptor/AgentStore.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/core/support/adaptor/AgentStore.java
index 48d1c04eee..c9a0943686 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/core/support/adaptor/AgentStore.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/core/support/adaptor/AgentStore.java
@@ -40,6 +40,8 @@ public class AgentStore {
             new HashMap<>();
     private final Map<String, Map<DataMovementProtocol, Map<String, 
Map<String, StorageResourceAdaptor>>>>
             storageAdaptorCache = new HashMap<>();
+    // SSH adaptor cache: resourceId (with compute/storage prefix): auth 
token: gatewayUserId: loginUserName: adaptor
+    private final Map<String, Map<String, Map<String, Map<String, 
AgentAdaptor>>>> sshAdaptorCache = new HashMap<>();
 
     public Optional<AgentAdaptor> getAgentAdaptor(
             String computeResource, JobSubmissionProtocol submissionProtocol, 
String authToken, String userId) {
@@ -111,4 +113,35 @@ public class AgentStore {
                 tokenToUserMap.computeIfAbsent(authToken, k -> new 
HashMap<>());
         userToAdaptorMap.put(userId, storageResourceAdaptor);
     }
+
+    public Optional<AgentAdaptor> getSSHAdaptor(String resourceId, String 
authToken, String gatewayUserId, String loginUserName) {
+        Map<String, Map<String, Map<String, AgentAdaptor>>> 
tokenToGatewayUserMap = sshAdaptorCache.get(resourceId);
+
+        if (tokenToGatewayUserMap != null) {
+            Map<String, Map<String, AgentAdaptor>> gatewayUserToLoginUserMap = 
tokenToGatewayUserMap.get(authToken);
+
+            if (gatewayUserToLoginUserMap != null) {
+                Map<String, AgentAdaptor> loginUserToAdaptorMap = 
gatewayUserToLoginUserMap.get(gatewayUserId);
+
+                if (loginUserToAdaptorMap != null) {
+                    return 
Optional.ofNullable(loginUserToAdaptorMap.get(loginUserName));
+                } else {
+                    return Optional.empty();
+                }
+            } else {
+                return Optional.empty();
+            }
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    public void putSSHAdaptor(String resourceId, String authToken, String 
gatewayUserId, String loginUserName, AgentAdaptor adaptor) {
+
+        Map<String, Map<String, Map<String, AgentAdaptor>>> 
tokenToGatewayUserMap = sshAdaptorCache.computeIfAbsent(resourceId, k -> new 
HashMap<>());
+        Map<String, Map<String, AgentAdaptor>> gatewayUserToLoginUserMap = 
tokenToGatewayUserMap.computeIfAbsent(authToken, k -> new HashMap<>());
+        Map<String, AgentAdaptor> loginUserToAdaptorMap = 
gatewayUserToLoginUserMap.computeIfAbsent(gatewayUserId, k -> new HashMap<>());
+
+        loginUserToAdaptorMap.put(loginUserName, adaptor);
+    }
 }
diff --git 
a/airavata-api/src/main/java/org/apache/airavata/helix/task/api/support/AdaptorSupport.java
 
b/airavata-api/src/main/java/org/apache/airavata/helix/task/api/support/AdaptorSupport.java
index d87e9dec9f..7e27b2a62b 100644
--- 
a/airavata-api/src/main/java/org/apache/airavata/helix/task/api/support/AdaptorSupport.java
+++ 
b/airavata-api/src/main/java/org/apache/airavata/helix/task/api/support/AdaptorSupport.java
@@ -19,7 +19,9 @@
 */
 package org.apache.airavata.helix.task.api.support;
 
-import org.apache.airavata.agents.api.*;
+import org.apache.airavata.agents.api.AgentAdaptor;
+import org.apache.airavata.agents.api.AgentException;
+import org.apache.airavata.agents.api.StorageResourceAdaptor;
 import 
org.apache.airavata.model.appcatalog.computeresource.JobSubmissionProtocol;
 import org.apache.airavata.model.data.movement.DataMovementProtocol;
 
@@ -30,13 +32,17 @@ import 
org.apache.airavata.model.data.movement.DataMovementProtocol;
  * @since 1.0.0-SNAPSHOT
  */
 public interface AdaptorSupport {
-    public void initializeAdaptor();
+    void initializeAdaptor();
 
-    public AgentAdaptor fetchAdaptor(
+    AgentAdaptor fetchAdaptor(
             String gatewayId, String computeResource, JobSubmissionProtocol 
protocol, String authToken, String userId)
             throws Exception;
 
-    public StorageResourceAdaptor fetchStorageAdaptor(
+    StorageResourceAdaptor fetchStorageAdaptor(
             String gatewayId, String storageResourceId, DataMovementProtocol 
protocol, String authToken, String userId)
             throws AgentException;
+
+    AgentAdaptor fetchComputeSSHAdaptor(String gatewayId, String resourceId, 
String authToken, String gatewayUserId, String loginUserName) throws 
AgentException;
+
+    StorageResourceAdaptor fetchStorageSSHAdaptor(String gatewayId, String 
resourceId, String authToken, String gatewayUserId, String loginUserName) 
throws AgentException;
 }
diff --git 
a/dev-tools/airavata-python-sdk/airavata_sdk/clients/api_server_client.py 
b/dev-tools/airavata-python-sdk/airavata_sdk/clients/api_server_client.py
index cf6fd2edaa..08c9e15ee0 100644
--- a/dev-tools/airavata-python-sdk/airavata_sdk/clients/api_server_client.py
+++ b/dev-tools/airavata-python-sdk/airavata_sdk/clients/api_server_client.py
@@ -113,6 +113,7 @@ class APIServerClient:
         self.get_all_storage_resource_names = 
self.client.getAllStorageResourceNames
         self.update_storage_resource = self.client.updateStorageResource
         self.delete_storage_resource = self.client.deleteStorageResource
+        self.get_resource_storage_info = self.client.getResourceStorageInfo
         self.add_local_submission_details = 
self.client.addLocalSubmissionDetails
         self.update_local_submission_details = 
self.client.updateLocalSubmissionDetails
         self.get_local_job_submission = self.client.getLocalJobSubmission
diff --git a/thrift-interface-descriptions/airavata-apis/airavata_api.thrift 
b/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
index 122a2e6935..c5a95d325d 100644
--- a/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
+++ b/thrift-interface-descriptions/airavata-apis/airavata_api.thrift
@@ -3610,7 +3610,24 @@ service Airavata extends base_api.BaseAPI {
                                                                                
           2: airavata_errors.AiravataClientException ace,
                                                                                
           3: airavata_errors.AiravataSystemException ase,
                                                                                
           4: airavata_errors.AuthorizationException ae);
- //
- //End of API
- }
+
+ /**
+  * Get storage volume 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 StorageVolumeInfo containing disk usage information
+  */
+ storage_resource_model.StorageVolumeInfo getResourceStorageInfo(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 f6b65d80c4..8b41079379 100644
--- a/thrift-interface-descriptions/data-models/storage_resource_model.thrift
+++ b/thrift-interface-descriptions/data-models/storage_resource_model.thrift
@@ -52,3 +52,30 @@ struct StorageResourceDescription {
     6: optional i64 creationTime,
     7: optional i64 updateTime,
 }
+
+/**
+ * Storage Volume Information
+ *
+ * Contains disk usage information for a filesystem/mount point.
+ *
+ * totalSize: Total size in human-readable format (e.g., "100G", "500M")
+ * usedSize: Used size in human-readable format
+ * availableSize: Available size in human-readable format
+ * totalSizeBytes: Total size in bytes
+ * usedSizeBytes: Used size in bytes
+ * availableSizeBytes: Available size in bytes
+ * percentageUsed: Percentage used
+ * mountPoint: Mount point/filesystem path
+ * filesystemType: Filesystem type if available
+ */
+struct StorageVolumeInfo {
+    1: required string totalSize,
+    2: required string usedSize,
+    3: required string availableSize,
+    4: required i64 totalSizeBytes,
+    5: required i64 usedSizeBytes,
+    6: required i64 availableSizeBytes,
+    7: required double percentageUsed,
+    8: required string mountPoint,
+    9: optional string filesystemType,
+}


Reply via email to