You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by di...@apache.org on 2026/09/12 04:28:59 UTC
(airavata) branch refactor-single-module updated: Create ssh data dir before moving data
This is an automated email from the ASF dual-hosted git repository.
DImuthuUpe pushed a commit to branch refactor-single-module
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/refactor-single-module by this push:
new 37a2d299a0 Create ssh data dir before moving data
37a2d299a0 is described below
commit 37a2d299a0e5c6a3f5e11ae65630ce40627def60
Author: DImuthuUpe <di...@gmail.com>
AuthorDate: Sat Sep 12 00:28:53 2026 -0400
Create ssh data dir before moving data
---
internal/orchestration/data.go | 16 ++++++++++++
internal/orchestration/ssh.go | 57 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+)
diff --git a/internal/orchestration/data.go b/internal/orchestration/data.go
index 4e6e12ab5e..fd619cfa55 100644
--- a/internal/orchestration/data.go
+++ b/internal/orchestration/data.go
@@ -6,6 +6,7 @@ import (
"os"
"github.com/google/uuid"
+ "path/filepath"
datamodel "github.com/apache/airavata/api/data/model"
)
@@ -67,6 +68,13 @@ func (a *ExecutionEngine) copyData(ctx context.Context, executionContext *Execut
return nil, err
}
+ // Ensure the destination directory exists before attempting the SCP upload.
+ if err := CreateDirectorySSH(ctx, *s.HostName, *s.Port, *s.LoginUser, *s.SSHKey, filepath.Dir(*dataStagingTask.DestinationPath)); err != nil {
+ slog.Error("Failed to create destination directory through SSH", "taskId", dataStagingTask.ID, "processId", *dataStagingTask.ProcessID,
+ "destinationDirectory", filepath.Dir(*dataStagingTask.DestinationPath), "error", err)
+ return nil, err
+ }
+
err = UploadFileToSCP(ctx, *s.HostName, *s.Port, *s.LoginUser, *s.SSHKey, localTempDataPath, *dataStagingTask.DestinationPath)
if err != nil {
slog.Error("Failed to upload file through SCP", "taskId", dataStagingTask.ID, "processId", *dataStagingTask.ProcessID,
@@ -84,6 +92,14 @@ func (a *ExecutionEngine) copyData(ctx context.Context, executionContext *Execut
return nil, err
}
+ // Ensure the destination directory exists before attempting the SCP upload.
+ if err := CreateDirectorySSH(ctx, s.SlurmCluster.HeadnodeHost, s.SlurmCluster.HeadnodePort, s.LoginUser, *s.SSHKey,
+ filepath.Dir(*dataStagingTask.DestinationPath)); err != nil {
+ slog.Error("Failed to create destination directory on HPC through SSH", "taskId", dataStagingTask.ID, "processId", *dataStagingTask.ProcessID,
+ "destinationDirectory", filepath.Dir(*dataStagingTask.DestinationPath), "error", err)
+ return nil, err
+ }
+
err = UploadFileToSCP(ctx, s.SlurmCluster.HeadnodeHost, s.SlurmCluster.HeadnodePort, s.LoginUser,
*s.SSHKey, localTempDataPath, *dataStagingTask.DestinationPath)
if err != nil {
diff --git a/internal/orchestration/ssh.go b/internal/orchestration/ssh.go
index 75d94c8891..35009f6f7a 100644
--- a/internal/orchestration/ssh.go
+++ b/internal/orchestration/ssh.go
@@ -82,6 +82,23 @@ func UploadFileToSCP(ctx context.Context, host string, port int, username string
return nil
}
+// Creates remotePath on the host, including any missing parent directories, and does
+// nothing if it is already there.
+func CreateDirectorySSH(ctx context.Context, host string, port int, username string, key credmodel.SSHKey, remotePath string) error {
+ target := fmt.Sprintf("ssh mkdir %s@%s:%s", username, host, remotePath)
+
+ if strings.TrimSpace(remotePath) == "" {
+ return fmt.Errorf("%s: no remote directory was named", target)
+ }
+
+ if err := runSSHCommand(ctx, host, port, username, key, "mkdir -p "+shellQuote(remotePath), target); err != nil {
+ return err
+ }
+
+ slog.Info("Created directory over SSH", "host", host, "remotePath", remotePath)
+ return nil
+}
+
// Reads the mode, size and name a remote scp announces for a single remote file,
// without transferring its contents.
func GetSCPFileMetadata(ctx context.Context, host string, port int, username string, key credmodel.SSHKey, remotePath string) (*FileMetadata, error) {
@@ -130,6 +147,46 @@ const scpOK = 0x00
// attempt quickly rather than hold a worker slot until the OS gives up on the socket.
const scpDialTimeout = 30 * time.Second
+// runSSHCommand runs one command on the host and waits for it to finish, folding
+// whatever the command wrote to stderr into the error when it fails.
+func runSSHCommand(ctx context.Context, host string, port int, username string, key credmodel.SSHKey, command, target string) error {
+ client, err := dialSSH(ctx, host, port, username, key)
+ if err != nil {
+ return fmt.Errorf("%s: %w", target, err)
+ }
+ defer client.Close()
+
+ session, err := client.NewSession()
+ if err != nil {
+ return fmt.Errorf("%s: opening session: %w", target, err)
+ }
+ defer session.Close()
+
+ var stderr bytes.Buffer
+ session.Stderr = &stderr
+
+ done := make(chan struct{})
+ defer close(done)
+ go func() {
+ select {
+ case <-ctx.Done():
+ session.Close()
+ case <-done:
+ }
+ }()
+
+ if err := session.Run(command); err != nil {
+ if ctxErr := ctx.Err(); ctxErr != nil {
+ return fmt.Errorf("%s: %w", target, ctxErr)
+ }
+ if remote := strings.TrimSpace(stderr.String()); remote != "" {
+ return fmt.Errorf("%s: %w (remote: %s)", target, err, remote)
+ }
+ return fmt.Errorf("%s: %w", target, err)
+ }
+ return nil
+}
+
type scpTransfer struct {
client *ssh.Client
session *ssh.Session