Including Globus in Slurm workflows

Including Globus in Slurm workflows

Sometimes, a Slurm workflow may need input data from outside of the O2 active file systems. If the data is accessible by Globus, you can add Globus to the workflow, so that data transfer can be automatically started, and once the data transfer is finished, downstream data analysis will automatically start.

O2 active file systems include /home/, /n/data1, /n/data2, /n/data3, /n/groups and /n/scratch.

O2 can not directly access /n/files/ and /n/standby, but they are accessible by Globus.

Here is how you can set it up:

First, create conda env for Globus software, then login Globus to save the login token and approve data access permission:

# login to the cluster ssh o2.hms.harvard.edu # load conda module module load conda/miniforge3/24.11.3-0 # create globus env is not done yet srun -p interactive --mem 4G -t 2:0:0 --pty bash mamba create -n globusEnv python=3.11 globus-cli -y # activate globus env conda activate globusEnv # log in to globus. The tocken will be saved in ~/.config/globus/tokens.json globus login # find globus endpoint uuid using globus.org # this is good for O2 actitive storage and also good for /n/files and /n/standby SRC_ENDPOINT="b0718922-7031-11e9-b7f8-0a37f382de32" # grant consent for the Globus CLI to access the data. # click the link and autherize the scrope. # If you see an error, refresh and try again. globus session consent "urn:globus:auth:scope:transfer.api.globus.org:all[*https://auth.globus.org/scopes/${SRC_ENDPOINT}/data_access]"

 

Then prepare testing data, and start the workflow:

# login to the cluster ssh o2.hms.harvard.edu # load conda module module load conda/miniforge3/24.11.3-0 # find globus endpoint uuid using globus.org # this is good for O2 actitive storage and also good for /n/files and /n/standby # so you can use this ID except for guest collection shared by your colaberators SRC_ENDPOINT="b0718922-7031-11e9-b7f8-0a37f382de32" DST_ENDPOINT="b0718922-7031-11e9-b7f8-0a37f382de32" # make test data directory mkdir -p /home/$USER/globus echo "This is a test file for Globus transfer." > /home/$USER/globus/test_file.txt # set source and destination paths. # you can use any path on O2 actitive storage, /n/files and /n/standby SRC_PATH="/home/$USER/globus" DST_PATH="/home/$USER/globus.copy" # notes: globus works differently than rsync or cp # 1. the ending '/' does not matter. # 2 if the source does not exist, globus will fail. # 3 if the destiation folder's parent folder does not exist, globus will fail. # 4 if the destination folder does not exist, it will be created. # 5 blobus does not copy the source folder itself, but only copy the contents of the source folder. # remove the destinaton if it exists. [ -d "$DST_PATH" ] && rm -r "$DST_PATH" # check source path and make sure it exists globus ls "${SRC_ENDPOINT}:${SRC_PATH}" > /dev/null 2>&1 || { echo "Error: Source path does not exist: ${SRC_ENDPOINT}:${SRC_PATH}." } # check destination parent path and make sure it exists globus ls "${DST_ENDPOINT}:$(dirname "$DST_PATH")" > /dev/null 2>&1 || { echo "Error: Destination parent path does not exist: ${DST_ENDPOINT}:$(dirname "$DST_PATH")." } # testing consent / permissions with dry-run globus transfer "${SRC_ENDPOINT}:${SRC_PATH}" "${DST_ENDPOINT}:${DST_PATH}" \ --recursive --dry-run > /dev/null 2>&1 || { echo "Error: Failed to test transfer permissions: ${SRC_ENDPOINT}:${SRC_PATH} -> ${DST_ENDPOINT}:${DST_PATH}." } # copy the three scripts (globusJob.sh, globusAfter.sh, globusWorkflow.sh with content as below): cp /n/groups/shared_databases/rcbio/globus/globus*.sh . # run workflow and submit jobs to Slurm bash globusWorkflow.sh

 

Bellow are the three scripts:

globusJob.sh

#!/bin/bash #SBATCH -p short #SBATCH --job-name=globus_transfer #SBATCH --output=globus_transfer_%j.out #SBATCH --error=globus_transfer_%j.err #SBATCH --time=01:00:00 #SBATCH --mem=512M #set -x set -euo pipefail usage() { echo "Usage: $0 <src_endpoint_id> <src_path> <dst_endpoint_id> <dst_path>" } log() { echo "[$(date +"%Y-%m-%d %H:%M:%S")] $*" } if [[ $# -ne 4 ]]; then usage exit 2 fi if ! command -v globus >/dev/null 2>&1; then echo "Error: globus CLI is not available in PATH." >&2 exit 127 fi if ! command -v python3 >/dev/null 2>&1; then echo "Error: python3 is required to parse Globus JSON output." >&2 exit 127 fi readonly SRC="${1}:${2}" readonly DST="${3}:${4}" readonly WAIT_TIMEOUT="${GLOBUS_WAIT_TIMEOUT:-7200}" readonly WAIT_POLL_INTERVAL="${GLOBUS_WAIT_POLL_INTERVAL:-60}" log "Starting Globus transfer..." transfer_json="$(globus transfer "$SRC" "$DST" --recursive --format json)" TASK_ID="$(python3 -c 'import json, sys; print(json.load(sys.stdin)["task_id"])' <<< "$transfer_json")" if [[ -z "$TASK_ID" ]]; then echo "Error: failed to extract task_id from Globus response." >&2 printf '%s\n' "$transfer_json" >&2 exit 1 fi log "Globus task ID: $TASK_ID" log "Waiting for Globus transfer to complete (timeout=${WAIT_TIMEOUT}s, poll=${WAIT_POLL_INTERVAL}s)..." # refresh globus tokens every 10 hour to avoid expiration during long transfers while true; do globus refresh --all sleep 36000 done & globus task wait "$TASK_ID" --timeout "$WAIT_TIMEOUT" --polling-interval "$WAIT_POLL_INTERVAL" log "Globus transfer completed successfully."

 

globusAfter.sh

#!/bin/bash #SBATCH -p short #SBATCH --output=post_transfer_%j.out #SBATCH --error=post_transfer_%j.err #SBATCH --time=01:00:00 #SBATCH --ntasks=1 #SBATCH --cpus-per-task=1 #SBATCH --mem=512M #set -x set -euo pipefail log() { echo "[$(date +"%Y-%m-%d %H:%M:%S")] $*" } on_error() { local exit_code="$1" local line_no="$2" echo "Error: post-transfer job failed at line ${line_no} (exit code ${exit_code})." >&2 } trap 'on_error "$?" "$LINENO"' ERR log "Running job after Globus transfer..." # Add your post-transfer commands below. # Example: # bash /path/to/analysis.sh input1 input2 log "Done with job after Globus transfer."

 

globusWorkflow.sh:

#!/bin/bash module load conda/miniforge3/24.11.3-0 # activate globus env conda activate globusEnv # find globus endpoint uuid using globus.org SRC_ENDPOINT="b0718922-7031-11e9-b7f8-0a37f382de32" DST_ENDPOINT="b0718922-7031-11e9-b7f8-0a37f382de32" # set source and destination paths. SRC_PATH="/home/$USER/globus" DST_PATH="/home/$USER/globus.copy" # submit transfer job TRANSFER_JOB_ID=$(sbatch --parsable globusJob.sh "${SRC_ENDPOINT}" "${SRC_PATH}" "${DST_ENDPOINT}" "${DST_PATH}") # make sure the transfer job is submitted successfully echo "Submitted Globus transfer job: ${TRANSFER_JOB_ID}" # submit dependent job that will run after the transfer job completes successfully DEPENDENT_JOB_ID=$(sbatch --parsable --dependency=afterok:${TRANSFER_JOB_ID} globusAfter.sh) echo "Submitted dependent job: ${DEPENDENT_JOB_ID}. Dependent job will start only if transfer job ${TRANSFER_JOB_ID} succeeds."