From 495ac9dada9b542b6f4c797b6ee94574173daa5f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 7 Jul 2026 21:23:28 +0000 Subject: [PATCH] Add multi-node job support to swm-core Implement #SWM nodes directive to allow users to request multiple nodes for cloud jobs: - Add 'nodes' directive parsing in wm_jobscript.erl - Add unit tests for multi-node job script parsing - Add example multi-node.job script showing usage - Add comprehensive JOB_DIRECTIVES.md documentation This integrates with the multi-node partition feature in swm-cloud-gate (feature/multi-node-jobs branch). The gate already supports creating partitions with multiple compute nodes, this change enables users to request multi-node partitions via job script directives. --- HOWTO/JOB_DIRECTIVES.md | 153 ++++++++++++++++++++++++ priv/examples/jobscripts/multi-node.job | 21 ++++ src/srv/user/wm_jobscript.erl | 30 +++++ 3 files changed, 204 insertions(+) create mode 100644 HOWTO/JOB_DIRECTIVES.md create mode 100644 priv/examples/jobscripts/multi-node.job diff --git a/HOWTO/JOB_DIRECTIVES.md b/HOWTO/JOB_DIRECTIVES.md new file mode 100644 index 0000000..444eb58 --- /dev/null +++ b/HOWTO/JOB_DIRECTIVES.md @@ -0,0 +1,153 @@ +# Job Script Directives + +Job scripts in Sky Port use special directives prefixed with `#SWM` to specify job requirements and configuration. + +## Available Directives + +### Resource Requirements + +#### nodes +Specify the number of nodes to allocate for the job. +```bash +#SWM nodes +``` +**Example:** +```bash +#SWM nodes 3 +``` +This will request a partition with 3 compute nodes. The nodes will be named: +- `swm--main` (primary/manager node) +- `swm--node0` (first extra compute node) +- `swm--node1` (second extra compute node) + +**Default:** 1 node + +**Note:** Multi-node job support requires the gate to support the multi-node partition feature (available in swm-cloud-gate branch `feature/multi-node-jobs`). + +#### flavor +Specify the cloud instance flavor/size. +```bash +#SWM flavor +``` + +#### gpus +Request GPU resources. +```bash +#SWM gpus +``` + +### Image Configuration + +#### cloud-image +Specify the cloud VM image to use. +```bash +#SWM cloud-image +``` + +#### container-image +Specify the Docker container image to run the job in. +```bash +#SWM container-image +``` + +### Job Metadata + +#### name +Set a human-readable name for the job. +```bash +#SWM name +``` + +#### comment +Add a description/comment for the job. +```bash +#SWM comment +``` + +#### account +Specify the account to use for billing. +```bash +#SWM account +``` + +### Input/Output + +#### stdin +Specify the standard input file. +```bash +#SWM stdin +``` + +#### stdout +Specify where to redirect standard output. +```bash +#SWM stdout +``` + +#### stderr +Specify where to redirect standard error. +```bash +#SWM stderr +``` + +#### workdir +Set the working directory for the job. +```bash +#SWM workdir +``` + +#### input-files +Specify input files to be transferred to the job. +```bash +#SWM input-files ... +``` + +#### output-files +Specify output files to be transferred back after job completion. +```bash +#SWM output-files ... +``` + +### Networking + +#### ports +Specify ports to forward from the remote node. +```bash +#SWM ports ,,... +``` + +#### submission-address +Specify the submission address. +```bash +#SWM submission-address
+``` + +### Job Behavior + +#### relocatable +Mark the job as relocatable (can be migrated between nodes). +```bash +#SWM relocatable +``` + +## Complete Multi-Node Example + +```bash +#!/bin/bash + +#SWM name Multi-Node MPI Job +#SWM nodes 4 +#SWM flavor Standard_D4s_v3 +#SWM cloud-image ubuntu22.04 +#SWM container-image mpi/ubuntu:latest +#SWM relocatable +#SWM comment Distributed MPI computation across 4 nodes + +echo "Starting multi-node job on $(hostname)" +echo "Job ID: $SWM_JOBID" + +# Your MPI or distributed application code here +mpirun -n 16 ./my_parallel_app + +echo "Job completed" +``` diff --git a/priv/examples/jobscripts/multi-node.job b/priv/examples/jobscripts/multi-node.job new file mode 100644 index 0000000..6f4c3b6 --- /dev/null +++ b/priv/examples/jobscripts/multi-node.job @@ -0,0 +1,21 @@ +#!/bin/bash + +#SWM name Multi-Node Job Example +#SWM nodes 3 +#SWM relocatable +#SWM comment Example job script demonstrating multi-node job submission + +# This job requests 3 nodes from the cloud provider +# The nodes will be named: swm--node0, swm--node1, swm--node2 +# where swm--node0 is the main/master node + +echo "Hello from multi-node job $SWM_JOBID" +echo "Running on node: $(hostname)" +date + +# Your multi-node application code here +# For example, MPI applications, distributed computing tasks, etc. + +sleep 60 + +echo "Multi-node job completed" diff --git a/src/srv/user/wm_jobscript.erl b/src/srv/user/wm_jobscript.erl index 187989f..d906d05 100644 --- a/src/srv/user/wm_jobscript.erl +++ b/src/srv/user/wm_jobscript.erl @@ -102,6 +102,8 @@ parse_line(Ws, Job) when hd(Ws) == "flavor", length(Ws) > 1 -> add_requested_resource("flavor", lists:flatten(tl(Ws)), Job); parse_line(Ws, Job) when hd(Ws) == "gpus", length(Ws) > 1 -> add_requested_resource("gpus", list_to_integer(lists:flatten(tl(Ws))), [], Job); +parse_line(Ws, Job) when hd(Ws) == "nodes", length(Ws) > 1 -> + add_requested_resource("node", list_to_integer(lists:flatten(tl(Ws))), [], Job); parse_line(Ws, Job) when hd(Ws) == "account", length(Ws) > 1 -> wm_entity:set({account_id, get_account_id(lists:flatten(tl(Ws)))}, Job); parse_line(Ws, Job) when hd(Ws) == "name", length(Ws) > 1 -> @@ -135,3 +137,31 @@ parse_line(Ws, Job) -> get_account_id(AccountName) -> {ok, Account} = wm_conf:select(account, {name, AccountName}), wm_entity:get(id, Account). + +%% ============================================================================ +%% Tests +%% ============================================================================ + +-ifdef(EUNIT). + +-include_lib("eunit/include/eunit.hrl"). + +% ./rebar3 eunit --module=wm_jobscript +-spec parse_nodes_directive_test() -> ok. +parse_nodes_directive_test() -> + JobScript = "#!/bin/bash\n#SWM nodes 3\necho hello", + Job = parse(JobScript), + Resources = wm_entity:get(request, Job), + NodeResource = lists:keyfind("node", 2, Resources), + ?assertMatch(#resource{name = "node", count = 3}, NodeResource). + +-spec parse_single_node_default_test() -> ok. +parse_single_node_default_test() -> + JobScript = "#!/bin/bash\necho hello", + Job = parse(JobScript), + Resources = wm_entity:get(request, Job), + % When no nodes directive is present, default should be added elsewhere + % This test just ensures parsing doesn't crash + ?assertEqual([], Resources). + +-endif.