Skip to content

Commit 2f47020

Browse files
committed
Merge remote-tracking branch 'origin/main' into checkout
2 parents fb3d2dd + 7cb689f commit 2f47020

24 files changed

Lines changed: 1091 additions & 74 deletions

.deploy/local/slurm/README.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Local Dockerized Slurm for Clustron
2+
3+
A self-contained Slurm cluster in Docker so the backend's `internal/slurm`
4+
functions (account/user/association management and job submission) can be
5+
exercised against a **real `slurmrestd`** during local development.
6+
7+
> **"Slurm can't run in a container" — only half true.** Production *compute
8+
> nodes* are awkward to containerize (they want cgroups, GPUs, MPI, privileged
9+
> host access). But a single-host **dev/test** cluster — `slurmctld` +
10+
> `slurmdbd` + `slurmrestd` + a dynamic compute node — runs in Docker just fine.
11+
> This stack is built on the well-maintained
12+
> [giovtorres/slurm-docker-cluster](https://github.com/giovtorres/slurm-docker-cluster)
13+
> image and needs **no source compile** (it pulls a prebuilt image).
14+
15+
## What you get
16+
17+
| Container | Role | Port |
18+
| --- | --- | --- |
19+
| `mysql` (MariaDB) | slurmdbd accounting storage | internal |
20+
| `slurmdbd` | accounting daemon; generates the shared `jwt_hs256.key` | internal `6819` |
21+
| `slurmctld` | controller; mints JWTs via `scontrol token` | internal `6817` |
22+
| `slurmrestd` | **REST API with JWT auth** (`X-SLURM-USER-TOKEN`) | **host `6820`** |
23+
| `cpu-worker` | one dynamic compute node so a partition exists | internal `6818` |
24+
25+
The backend talks to `slurmrestd` exactly as it does in dev/stage:
26+
`POST /slurmdb/<ver>/accounts_association`, `GET /slurm/<ver>/jobs`, etc., authenticating
27+
with a JWT in the `X-SLURM-USER-TOKEN` header.
28+
29+
## Prerequisites
30+
31+
- Docker + Docker Compose (v2). On Windows, run these from your **WSL** shell.
32+
- Outbound network on first run (to pull `giovtorres/slurm-docker-cluster` and
33+
`mariadb`).
34+
35+
## Quick start
36+
37+
From `clustron-backend/`:
38+
39+
```bash
40+
make slurm-up # == ./.deploy/local/slurm/deploy.sh
41+
make slurm-token # prints a root JWT (== ./.deploy/local/slurm/mint-token.sh root)
42+
```
43+
44+
`make slurm-up` waits for every daemon to become healthy and then prints which
45+
REST API versions the running image serves, e.g.:
46+
47+
```
48+
/slurm/v0.0.43
49+
/slurm/v0.0.44
50+
/slurmdb/v0.0.43
51+
/slurmdb/v0.0.44
52+
```
53+
54+
### Point the backend at it
55+
56+
Copy the `slurm:` block from [`config.slurm-local.yaml`](./config.slurm-local.yaml)
57+
into your `clustron-backend/config.yaml`, then paste the `make slurm-token`
58+
output into `slurm_root_token`:
59+
60+
```yaml
61+
slurm: # MUST be nested under `slurm:`
62+
slurm_restful_base_url: "http://localhost:6820"
63+
slurm_restful_version: "v0.0.44" # pick one from the deploy.sh output
64+
slurm_root_token: "eyJhbGciOi..." # from `make slurm-token`
65+
```
66+
67+
By default `make slurm-token` mints a 1-hour (3600s) root token. For an
68+
effectively unexpired token, pass a long lifespan via `SLURM_LIFESPAN`:
69+
70+
```bash
71+
make slurm-token SLURM_LIFESPAN=infinite # exp ~year 2094 — paste once, forget
72+
```
73+
74+
`SLURM_USER` and `SLURM_LIFESPAN` are forwarded to `mint-token.sh` (defaults
75+
`root` / `3600`), so `./.deploy/local/slurm/mint-token.sh root infinite` is the
76+
equivalent direct call.
77+
78+
Now `make run` the backend. Creating a group will create a matching Slurm
79+
account (see below).
80+
81+
## The group → account flow
82+
83+
`group.Service.Create` runs a saga: **DB group → LDAP base group → LDAP admin
84+
group → Slurm account**. The Slurm step calls `slurm.CreateAccountAssociation`
85+
(the `sacctmgr add account` equivalent — `POST /slurmdb/<ver>/accounts_association`)
86+
with the group's LDAP CN as the account name, so the account is created **with**
87+
its cluster association and is usable for jobs. (The bare `/accounts` endpoint
88+
creates an account with no association.) If it fails, the saga compensates the
89+
earlier steps (LDAP groups deleted) and the DB transaction rolls back.
90+
91+
Verify the REST path the backend uses, without the backend, with:
92+
93+
```bash
94+
./smoke-test.sh # create + delete "clustron-smoke", asserting both
95+
# override the API version if needed:
96+
SLURM_RESTFUL_VERSION=v0.0.44 ./smoke-test.sh myaccount
97+
```
98+
99+
Inspect accounts and their associations directly inside the cluster:
100+
101+
```bash
102+
docker compose exec slurmctld sacctmgr -i show account
103+
docker compose exec slurmctld sacctmgr -i show assoc account=clustron-smoke
104+
```
105+
106+
> **Heads-up — group creation now depends on Slurm.** Because the account step
107+
> is part of the saga, group creation will **fail** if `slurmrestd` is
108+
> unreachable or `slurm_root_token` is missing/expired. Keep this stack up (and
109+
> the token fresh) while working on groups, or expect group creation to error.
110+
111+
## Per-user job tokens (out of scope here)
112+
113+
The backend's *job* endpoints need a per-user JWT from an external
114+
[slurm-token-helper](https://github.com/NYCU-SDC/slurm-token-helper)
115+
(`GET /api/token/{username}`). That integration is deferred to a separate task —
116+
this stack is scoped to the group → Slurm-account flow, which uses
117+
`slurm_root_token` and needs no helper. For ad-hoc job testing you can mint a
118+
per-user token straight from `scontrol`:
119+
120+
```bash
121+
./mint-token.sh alice 3600
122+
curl -H "X-SLURM-USER-TOKEN: $(./mint-token.sh alice)" http://localhost:6820/slurm/v0.0.44/jobs
123+
```
124+
125+
## Slurm version / REST API version
126+
127+
`SLURM_VERSION` (default `25.11.4`) selects the image tag and therefore which
128+
OpenAPI plugins `slurmrestd` exposes. The backend's `slurm_restful_version`
129+
**must** match one of them. The project uses **`v0.0.44`**, which Slurm 25.11
130+
serves. SchedMD removes old plugin versions over time, so if you need a specific
131+
version, confirm it in the `make slurm-up` output and pin `SLURM_VERSION`
132+
accordingly:
133+
134+
```bash
135+
SLURM_VERSION=25.05.6 make slurm-up # if you specifically need an older API
136+
```
137+
138+
## Teardown
139+
140+
```bash
141+
make slurm-down # == ./cleanup.sh ; stops everything and wipes volumes
142+
```
143+
144+
This removes the MariaDB and Slurm state volumes, resetting all accounts.
145+
146+
## Files
147+
148+
| File | Purpose |
149+
| --- | --- |
150+
| `compose.yaml` | the cluster definition (prebuilt image, no build) |
151+
| `deploy.sh` / `cleanup.sh` | bring up (with version probe) / tear down |
152+
| `mint-token.sh` | mint a root or per-user JWT via `scontrol token` |
153+
| `smoke-test.sh` | create, read, and delete an account via the backend's REST paths (asserts deletion) |
154+
| `config.slurm-local.yaml` | the `slurm_*` block to paste into `config.yaml` |
155+
156+
## Troubleshooting
157+
158+
**`sinfo` shows the node `down` / jobs stay `PENDING` ("Required node not
159+
available") after a host or Docker restart.** The compute node is dynamic
160+
(`slurmd -Z`); when its container restarts, slurmctld marks it
161+
`down` ("Node unexpectedly rebooted") and the image's `ReturnToService=1` only
162+
auto-returns *non-responsive* nodes. Just re-run:
163+
164+
```bash
165+
make slurm-up # resumes any down nodes and sets ReturnToService=2
166+
```
167+
168+
or fix it by hand:
169+
170+
```bash
171+
docker exec clustron-slurm-slurmctld scontrol update nodename=c1 state=resume
172+
```
173+
174+
**`srun` fails / hangs when run as root.** Slurm refuses to run jobs as root or
175+
SlurmUser. Submit as a normal user inside the cluster:
176+
177+
```bash
178+
docker exec clustron-slurm-slurmctld su slurm -s /bin/bash -c "srun -N1 hostname"
179+
```
180+
181+
## Notes / caveats
182+
183+
- Compute is minimal: one dynamic node, jobs may sit `PENDING`. Accounting and
184+
job *submission/listing* work; this stack is not for running real workloads.
185+
- `slurmctld`, `slurmrestd` and the worker run `privileged: true` (Slurm needs
186+
it). Fine for local dev; do not copy this layout to production.
187+
- The cluster is independent of the core `.deploy/local` stack (separate Compose
188+
project `clustron-slurm-local`), so `make prepare`/`make run` are unaffected.

.deploy/local/slurm/cleanup.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env bash
2+
# Stop the cluster and wipe its volumes (resets the slurmdbd accounting database).
3+
set -euo pipefail
4+
cd "$(dirname "$0")"
5+
echo ":: Removing Clustron local Slurm cluster and volumes ..."
6+
docker compose down -v

.deploy/local/slurm/compose.yaml

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Local Dockerized Slurm cluster for Clustron backend development.
2+
#
3+
# Self-contained: uses the prebuilt giovtorres/slurm-docker-cluster image
4+
# (no source compile). Brings up slurmdbd + slurmctld + slurmrestd (JWT auth on
5+
# :6820) + one dynamic compute node, backed by MariaDB for accounting.
6+
#
7+
# This lets the backend's internal/slurm functions (CreateAccount, CreateUser,
8+
# CreateAssociation, job submission, ...) talk to a real slurmrestd.
9+
#
10+
# Quick start (from clustron-backend/):
11+
# make slurm-up # or: ./.deploy/local/slurm/deploy.sh
12+
# make slurm-token # mint a root JWT for slurm_root_token
13+
# See README.md in this directory for full details.
14+
15+
name: clustron-slurm-local
16+
17+
x-slurm-image: &slurm-image giovtorres/slurm-docker-cluster:${SLURM_VERSION:-25.11.4}
18+
19+
services:
20+
mysql:
21+
image: mariadb:12
22+
hostname: mysql
23+
container_name: clustron-slurm-mysql
24+
environment:
25+
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
26+
MYSQL_DATABASE: slurm_acct_db
27+
MYSQL_USER: slurm
28+
MYSQL_PASSWORD: password
29+
volumes:
30+
- var_lib_mysql:/var/lib/mysql
31+
networks:
32+
- slurm-network
33+
healthcheck:
34+
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
35+
interval: 10s
36+
timeout: 5s
37+
retries: 5
38+
start_period: 30s
39+
40+
slurmdbd:
41+
image: *slurm-image
42+
init: true
43+
command: ["slurmdbd"]
44+
container_name: clustron-slurm-slurmdbd
45+
hostname: slurmdbd
46+
environment:
47+
MYSQL_USER: slurm
48+
MYSQL_PASSWORD: password
49+
volumes:
50+
- etc_munge:/etc/munge
51+
- etc_slurm:/etc/slurm
52+
- var_log_slurm:/var/log/slurm
53+
expose:
54+
- "6819"
55+
depends_on:
56+
mysql:
57+
condition: service_healthy
58+
networks:
59+
- slurm-network
60+
healthcheck:
61+
test: ["CMD-SHELL", "pidof slurmdbd"]
62+
interval: 10s
63+
timeout: 5s
64+
retries: 5
65+
start_period: 20s
66+
67+
slurmctld:
68+
image: *slurm-image
69+
init: true
70+
command: ["slurmctld"]
71+
container_name: clustron-slurm-slurmctld
72+
hostname: slurmctld
73+
privileged: true
74+
working_dir: /data
75+
volumes:
76+
- etc_munge:/etc/munge
77+
- etc_slurm:/etc/slurm
78+
- slurm_jobdir:/data
79+
- var_log_slurm:/var/log/slurm
80+
expose:
81+
- "6817"
82+
depends_on:
83+
slurmdbd:
84+
condition: service_healthy
85+
networks:
86+
- slurm-network
87+
healthcheck:
88+
test: ["CMD-SHELL", "scontrol ping"]
89+
interval: 10s
90+
timeout: 5s
91+
retries: 5
92+
start_period: 20s
93+
94+
slurmrestd:
95+
image: *slurm-image
96+
init: true
97+
command: ["slurmrestd"]
98+
container_name: clustron-slurm-slurmrestd
99+
hostname: slurmrestd
100+
privileged: true
101+
volumes:
102+
- etc_munge:/etc/munge
103+
- etc_slurm:/etc/slurm
104+
- var_log_slurm:/var/log/slurm
105+
ports:
106+
# Host:Container. The backend's slurm_restful_base_url points here.
107+
- "6820:6820"
108+
depends_on:
109+
slurmctld:
110+
condition: service_healthy
111+
networks:
112+
- slurm-network
113+
healthcheck:
114+
test: ["CMD-SHELL", "test -S /var/run/slurmrestd/slurmrestd.socket"]
115+
interval: 10s
116+
timeout: 5s
117+
retries: 5
118+
start_period: 20s
119+
120+
cpu-worker:
121+
image: *slurm-image
122+
init: true
123+
command: ["slurmd-cpu"]
124+
working_dir: /data
125+
privileged: true
126+
environment:
127+
COMPOSE_PROJECT_NAME: clustron-slurm-local
128+
volumes:
129+
- etc_munge:/etc/munge
130+
- etc_slurm:/etc/slurm
131+
- slurm_jobdir:/data
132+
- var_log_slurm:/var/log/slurm
133+
expose:
134+
- "6818"
135+
depends_on:
136+
slurmctld:
137+
condition: service_healthy
138+
networks:
139+
- slurm-network
140+
healthcheck:
141+
test: ["CMD-SHELL", "pidof slurmd"]
142+
interval: 10s
143+
timeout: 5s
144+
retries: 5
145+
start_period: 20s
146+
147+
volumes:
148+
etc_munge:
149+
etc_slurm:
150+
var_log_slurm:
151+
slurm_jobdir:
152+
var_lib_mysql:
153+
154+
networks:
155+
slurm-network:
156+
driver: bridge
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Slurm settings for the local Dockerized cluster (.deploy/local/slurm).
2+
#
3+
# IMPORTANT: these keys must be NESTED under a top-level `slurm:` key — the
4+
# backend maps them via `Slurm slurm.Config `yaml:"slurm"`` in internal/config.
5+
# Flat/top-level keys are silently ignored (you'll get empty base URL + version
6+
# and a `Post "/slurmdb//accounts": unsupported protocol scheme ""` error).
7+
#
8+
# Copy this whole `slurm:` block into your clustron-backend/config.yaml.
9+
#
10+
# Do NOT also set SLURM_* env vars: setting even one of them replaces the entire
11+
# slurm config struct (config.Merge is per-top-level-field), wiping the others.
12+
# Put these in ONE place — here, or as env vars.
13+
#
14+
# slurm_restful_version: must be a version the running image serves. Slurm
15+
# 25.11 serves v0.0.42 / v0.0.43 / v0.0.44; `make slurm-up` prints the list.
16+
# slurm_root_token: paste the output of `make slurm-token`. Tokens expire
17+
# (default 1h) — re-mint when you start getting 401s.
18+
#
19+
# (slurm_token_helper_url is only needed for the per-user job endpoints, which
20+
# are out of scope here; it's defined-but-not-required, so it's omitted.)
21+
22+
slurm:
23+
slurm_restful_base_url: "http://localhost:6820"
24+
slurm_restful_version: "v0.0.44"
25+
slurm_root_token: "PASTE_OUTPUT_OF_make_slurm-token"

0 commit comments

Comments
 (0)