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

yasithdev 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 f7ed91cf71 fix: load storage data-movement interfaces via correct FK 
column
f7ed91cf71 is described below

commit f7ed91cf7145485dc7a4c8710112e966b9d0ae95
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Tue Jun 9 00:21:29 2026 -0400

    fix: load storage data-movement interfaces via correct FK column
    
    StorageResourceEntity's @OneToMany joined on a RESOURCE_ID column the child 
entity never writes (it writes STORAGE_RESOURCE_ID, part of its composite @Id), 
so a storage resource never loaded its data-movement interfaces and the SFTP 
adaptor failed with 'No SCP data movement interface'. Joins on 
STORAGE_RESOURCE_ID (read-only, child-owned). Also includes dev-storage 
seed/infra fixes (DevStorageInitializer host + self-heal; sftp volume chown).
---
 .../orchestration/util/DevStorageInitializer.java  | 24 ++++++++++++++++++++--
 .../storage/model/StorageResourceEntity.java       | 12 ++++++++++-
 compose.yml                                        |  1 +
 conf/sftp/chown-storage.sh                         |  7 +++++++
 4 files changed, 41 insertions(+), 3 deletions(-)

diff --git 
a/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/DevStorageInitializer.java
 
b/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/DevStorageInitializer.java
index bb8324d134..a651c1e8eb 100644
--- 
a/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/DevStorageInitializer.java
+++ 
b/airavata-api/orchestration-service/src/main/java/org/apache/airavata/orchestration/util/DevStorageInitializer.java
@@ -28,6 +28,7 @@ import 
org.apache.airavata.model.appcatalog.gatewayprofile.proto.StoragePreferen
 import 
org.apache.airavata.model.appcatalog.storageresource.proto.StorageResourceDescription;
 import org.apache.airavata.model.credential.store.proto.SSHCredential;
 import org.apache.airavata.model.data.movement.proto.DMType;
+import org.apache.airavata.model.data.movement.proto.DataMovementProtocol;
 import org.apache.airavata.model.data.movement.proto.SCPDataMovement;
 import org.apache.airavata.model.data.movement.proto.SecurityProtocol;
 import org.apache.airavata.orchestration.service.RegistryServerHandler;
@@ -51,7 +52,9 @@ public class DevStorageInitializer {
 
     private static final Logger logger = 
LoggerFactory.getLogger(DevStorageInitializer.class);
 
-    private static final String STORAGE_HOST = "localhost";
+    // The storage-service runs inside the airavata-dind network and reaches 
the
+    // atmoz/sftp container by its compose service name, not "localhost".
+    private static final String STORAGE_HOST = "sftp";
     private static final String STORAGE_DESCRIPTION = "Dev SFTP storage 
(atmoz/sftp container)";
     private static final String SFTP_LOGIN_USER = "airavata";
     private static final String SFTP_FS_ROOT = "/storage";
@@ -80,7 +83,24 @@ public class DevStorageInitializer {
             var allNames = registryHandler.getAllStorageResourceNames();
             for (var entry : allNames.entrySet()) {
                 if (STORAGE_HOST.equals(entry.getValue())) {
-                    logger.info("Dev storage resource already exists: {} 
({})", entry.getValue(), entry.getKey());
+                    // Resource exists. Ensure it has an SCP data movement 
interface —
+                    // a partial prior init can leave it without one, which 
then fails
+                    // the SFTP adaptor with "No SCP data movement interface".
+                    String existingId = entry.getKey();
+                    boolean hasScp = 
registryHandler.getStorageResource(existingId).getDataMovementInterfacesList()
+                            .stream()
+                            .anyMatch(i -> i.getDataMovementProtocol() == 
DataMovementProtocol.SCP);
+                    if (!hasScp) {
+                        SCPDataMovement scpDm = SCPDataMovement.newBuilder()
+                                .setSecurityProtocol(SecurityProtocol.SSH_KEYS)
+                                .setSshPort(22)
+                                .build();
+                        registryHandler.addSCPDataMovementDetails(existingId, 
DMType.STORAGE_RESOURCE, 0, scpDm);
+                        logger.info("Healed dev storage resource {}: added 
missing SCP data movement interface",
+                                existingId);
+                    } else {
+                        logger.info("Dev storage resource already exists: {} 
({})", entry.getValue(), existingId);
+                    }
                     return;
                 }
             }
diff --git 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/StorageResourceEntity.java
 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/StorageResourceEntity.java
index 45de96f8ad..5630e1a75e 100644
--- 
a/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/StorageResourceEntity.java
+++ 
b/airavata-api/storage-service/src/main/java/org/apache/airavata/storage/model/StorageResourceEntity.java
@@ -51,12 +51,22 @@ public class StorageResourceEntity implements Serializable {
     @Column(name = "UPDATE_TIME")
     private Timestamp updateTime;
 
+    // The child (STORAGE_INTERFACE) already maps the FK as part of its 
composite
+    // @Id (DataMovementInterfaceEntity.resourceId -> STORAGE_RESOURCE_ID), so 
the
+    // association must join on that same column and be read-only here (the 
child
+    // owns it). Pointing this at a separate "RESOURCE_ID" column left the
+    // association always empty, which broke the SFTP storage adaptor ("No SCP
+    // data movement interface for storage resource").
     @OneToMany(
             targetEntity = DataMovementInterfaceEntity.class,
             cascade = CascadeType.ALL,
             orphanRemoval = true,
             fetch = FetchType.EAGER)
-    @JoinColumn(name = "RESOURCE_ID", referencedColumnName = 
"STORAGE_RESOURCE_ID")
+    @JoinColumn(
+            name = "STORAGE_RESOURCE_ID",
+            referencedColumnName = "STORAGE_RESOURCE_ID",
+            insertable = false,
+            updatable = false)
     private List<DataMovementInterfaceEntity> dataMovementInterfaces;
 
     public StorageResourceEntity() {}
diff --git a/compose.yml b/compose.yml
index 4a0aebed03..9b7efd8d55 100644
--- a/compose.yml
+++ b/compose.yml
@@ -169,6 +169,7 @@ services:
     restart: unless-stopped
     volumes:
       - ./conf/sftp/id_rsa.pub:/home/airavata/.ssh/keys/id_rsa.pub:ro
+      - ./conf/sftp/chown-storage.sh:/etc/sftp.d/chown-storage.sh:ro
       - sftp_data:/home/airavata/storage
     command: "airavata::1000"
     healthcheck:
diff --git a/conf/sftp/chown-storage.sh b/conf/sftp/chown-storage.sh
new file mode 100755
index 0000000000..0a7514265a
--- /dev/null
+++ b/conf/sftp/chown-storage.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+# atmoz/sftp runs /etc/sftp.d/*.sh as root on container startup, before sshd
+# drops to the chrooted user. The named volume mounted at 
/home/airavata/storage
+# is created root-owned by Docker, so the chrooted "airavata" user (uid 1000)
+# cannot create directories or write files there. Chown it on every startup so
+# the storage-service's SFTP adaptor can upload.
+chown -R airavata:airavata /home/airavata/storage

Reply via email to