LSF Job Modification Workflow (bmod)
Spillbox LSF Job Modification Workflow (bmod)
Overview
This workflow lets you take an existing LSF job that was submitted on the local (client) cluster and redirect it to run on the remote Spillbox cluster — without the user having to resubmit. The job is modified in-place using bmod, transparently wiring in Spillbox’s pre/post exec hooks, snapshot, and download steps.
Two scripts drive this:
| Script | Purpose |
|---|---|
modify_lsf_job.sh |
Modifies held jobs to run via Spillbox on the remote cluster |
reverse_modify_lsf.sh |
Undoes a modification and restores jobs to their original state |
Prerequisites
- LSF must be sourced (
LSF_ENVDIRset) bsub,bmod,bresumemust be inPATH- A Spillbox project config file must exist on both the client side and the server side — see Client vs. server config files below for the two paths and which values each one needs
- The operator must have
sudoaccess to run commands as other users (or run as the same user)
Client vs. server config files
Two separate copies of the same-named config file (spillbox_singularity_config.<project>) are needed — one in each data center — and each only needs the values that data center’s scripts actually use.
Client side (where you run modify_lsf_job.sh)
Path: terraform/etc/spillbox_singularity_config.<project>
export SPILLBOX_SERVER=S10.10.10.10:8080:C192.168.1.1:8080
export SPILLBOX_SITE=mysite
export SPILLBOX_REMOTE_WORKDIR=/workdir
Used by: modify_lsf_job.sh only. The client side never actually runs a job’s container — it just submits jobs and tells them where to go, so it only needs to know which server to burst to (SPILLBOX_SERVER), which site this is (SPILLBOX_SITE), and the remote working directory (SPILLBOX_REMOTE_WORKDIR).
Server side (where the job actually runs)
Path: $SPILLBOX_REMOTE_WORKDIR/.spillbox/sites/local/<SITE>/terraform/etc/spillbox_singularity_config.<project>
export SPILLBOX_SERVER=S10.10.10.10:8080:C192.168.1.1:8080
export SPILLBOX_SITE=mysite
export SPILLBOX_SINGULARITY_EXECUTABLE=/workdir/singularity_wrapper
export SPILLBOX_HYBRID_MOUNT_PATHS=ALL
export SPILLBOX_HYBRID_OPTION="-download"
export SPILLBOX_REMOTE_WORKDIR=/workdir
# Other optional variables (see Environment Variables Reference below for the full list)
#export SPILLBOX_DOWNLOAD_INTERVAL=10
#export SPILLBOX_HOLD_EXCLUDE_SIZE=1024
Used by: singularity_preexec.sh, singularity_jobstarter.sh, singularity_postexec.sh — the three scripts that actually run when a job executes in its container, so this copy needs the extra runtime/mount settings the client-side copy doesn’t.
Note: “client” and “server” are roles for a given burst direction, not fixed machine identities — if jobs ever burst the other way, the roles and the S/C prefixes in SPILLBOX_SERVER flip accordingly.
Quick example
# 1. User submits job on hold
bsub -H -q normal -o /results/myjob.out myjob.sh
# Job <54321> is submitted to queue <normal>.
# 2. Operator modifies it to run via Spillbox
terraform/bin/modify_lsf_job.sh \
-project default \
-localq normal \
-sendq sendq \
54321
# Output:
# Processing Input: 54321
# SUCCESS: Job 54321 updated.
# USER: jsmith
# Job <54322> is submitted to queue <normal>. <- download job
# 3. If something goes wrong and you want to revert
terraform/bin/reverse_modify_lsf.sh -localq normal 54321
# SUCCESS: 54321 reversed
That’s the whole workflow in miniature. The sections below walk through each step in detail, and the Reference section at the bottom covers every flag, environment variable, and internal detail for when you need to look something up.
Workflow
Step 1 — User submits job in HOLD state
The user (or an automated system) submits the job to the local queue with -H to hold it before it runs:
bsub -H -q normal myjob.sh
The job sits in PSUSP/PEND state — it will not run until resumed.
Step 2 — Operator runs modify_lsf_job.sh
Script location: terraform/bin/modify_lsf_job.sh
terraform/bin/modify_lsf_job.sh \
-project <projectname> \
-localq <local_queue> \
-sendq <send_queue> \
[-queue <queue_names>] \
[<job_ids_or_usernames>]
This is the minimum you need: a project name, a local queue, a remote send queue, and the job IDs (or usernames) to modify. There are more optional flags for less common cases — interactive jobs, archiving for later resubmission, delaying the start, and so on — covered in the full flag reference below.
Job targets (comma-separated, mixed is OK):
# By job ID
terraform/bin/modify_lsf_job.sh -project default -localq normal -sendq sendq 12345
# By job array
terraform/bin/modify_lsf_job.sh -project default -localq normal -sendq sendq "12345[1-10]"
# By username (modifies all jobs for that user in the local queue)
terraform/bin/modify_lsf_job.sh -project default -localq normal -sendq sendq jsmith
# By queue name (modifies EVERY job currently in that queue, across ALL
# users — not just the one running the script). A queue must be given
# with -queue; a bare name is always taken as a username.
terraform/bin/modify_lsf_job.sh -project default -localq normal -sendq sendq -queue batchq
# Multiple, mixed (comma-separated, no spaces)
terraform/bin/modify_lsf_job.sh -project default -localq normal -sendq sendq 12345,67890,jsmith
# Job IDs or usernames together with a queue
terraform/bin/modify_lsf_job.sh -project default -localq normal -sendq sendq -queue batchq 12345,jsmith
A queue given with -queue modifies every job currently in that queue.
Step 3 — Job runs on remote cluster
Spillbox’s singularity_preexec.sh and singularity_postexec.sh handle:
- Staging input data to the remote site
- Running the job inside a container (Singularity/Docker)
- Writing output/error back to the original paths
The download job then pulls results back automatically.
Automatic mode: the hold queue and the crontab
Instead of running modify_lsf_job.sh by hand, the customer can have Spillbox run it on a
schedule. Users submit jobs to a hold queue, and a crontab entry moves every job in that queue
to the send queue. Users only change the queue name; nothing else.
Turning it on. It is off by default.
- In
etc/spillbox_configsetcron_lockfileto an absolute path the user runningrun_server.shcan write to, for examplecron_lockfile="/var/lib/spillbox/cron.lock". - Start (or restart) the server with
run_server.sh. It copiesetc/spillbox_cron.crontab.exampleto<cron_lockfile>.crontabthe first time, with every entry commented out. - Edit
<cron_lockfile>.crontab: take the#off themodify_lsf_job.shline and set the project name, queues and schedule. The scheduler runs in the web GUI spld (gui.log), not the main one.
The crontab is an ordinary crontab: one job per line as <schedule> <command>, # starts a
comment, NAME=value sets an environment variable for every job. The schedule is five fields
(minute hour day-of-month month day-of-week) or @hourly, @daily; there is no seconds field.
The customer decides how often by editing the schedule. A # in front of a line turns it off.
# every 5 minutes
*/5 * * * * sudo -n -u lsfadmin /opt/spillbox/terraform/bin/modify_lsf_job.sh -project amd -localq normal -sendq send_queue -queue hold
Changing it while running. The file is re-read every second; no restart is needed. A job that is still running when the file changes is not started a second time, and a run is skipped while the last one is still going. If an edit leaves a line that is not valid, the scheduler keeps the previous schedule and logs the line.
Times are UTC; set CRONWRAPPER_TZ=America/Los_Angeles (or another zone) in the environment of
spld to change that. The user running spld needs passwordless sudo to the LSF account if the entry
uses sudo -n -u. Every run (start, finish time, exit code, output) is logged in gui.log.
More detail: pkg/cronwrapper/README.md in splpipe.
Reversing a modification
If a modified job gets stuck in pending state on the remote cluster and you want to undo the modification and bring it back to its original local state:
Script location: terraform/bin/reverse_modify_lsf.sh
terraform/bin/reverse_modify_lsf.sh -localq <local_queue> <job_ids>
Example:
terraform/bin/reverse_modify_lsf.sh -localq normal 12345,67890
reverse_modify_lsf.sh reads the encoded original values from the job’s current pre-exec command (where modify_lsf_job.sh stored them) and uses bmod to restore:
- Original job name
- Queue set to the
-localqvalue - Original working directory
- Original output/error files
- Original pre/post exec commands
- Original resource requirements
- Original dependencies
- Original app profile
It skips internal Spillbox jobs (spillbox_snapshot, spillbox_download_*) automatically.
Reference
modify_lsf_job.sh flag reference
| Flag | Required | Description |
|---|---|---|
-project |
Yes | Spillbox project name (used to find config file terraform/etc/spillbox_singularity_config.<project>) |
-localq |
Yes | Local LSF queue to use for the download and sleep jobs, and to find a user’s jobs when a username is given (the snapshot job goes to -sendq) |
-sendq |
Yes | Queue to send jobs to on the remote Spillbox cluster |
-app |
No | LSF application profile to apply on the remote cluster |
-localR |
No | Resource requirement for local snapshot/download jobs |
-remoteR |
No | Resource requirement to set on the remote job (replaces the job’s original -R) |
-remoteRA |
No | Resource requirement to append to the job’s original -R instead of replacing it; takes priority over -remoteR if both are given |
-spillboxlocalq |
Only for interactive jobs | Local queue override for interactive (bsub -I) jobs — script exits with an error if an interactive job is modified without this |
-spillboxsendq |
Only for interactive jobs | Send queue override for interactive (bsub -I) jobs — same requirement as -spillboxlocalq |
-archive |
No | Before running bmod, capture the job’s original state (queue, name, output/error paths, cwd, resource requirement, app, dependencies) and, once bmod succeeds, write it to ~/.config/spillbox/archive/job_<id>_<timestamp>.sh, so it can later be resubmitted as a fresh job via -resubmit |
-delay <seconds> |
No | After the snapshot job is submitted, also submit a sleep <seconds> job (depending on the first modified user job starting) that the remaining modified jobs wait on; default 0 skips this sleep job entirely |
-nosla |
No | After a successful bmod, also run bmod -slan to remove any SLA attached to the job |
-queue <queue_names> |
No | Modify every job currently in these LSF queues (comma-separated), from all users. The script exits with an error if a name is not a queue. A queue name given without -queue is taken as a username |
-downloadinterval <minutes\|hold> |
No | Overrides SPILLBOX_DOWNLOAD_INTERVAL for this run only, taking priority over the project config file’s value; hold = hold until explicitly released, a number = hold that many minutes |
Skipping users with recent jobs — SPLAPP_SUBMIT_TIME. Set this in the environment of the run
(or in the project config file) to a job age, not a clock time: seconds, or a number with an
m, h or d suffix (3600, 30m, 2h, 1d). If any of a user’s jobs selected in this run
was submitted more recently than that, all of that user’s jobs are skipped — including their
older ones — and no snapshot, sleep or download job is submitted for them. Other users are
modified as usual, so only users whose jobs are all older than the age are processed. Unset or empty = no limit (default); any other value stops the script with
an error. modify_slurm_job.sh reads it the same way.
SPLAPP_SUBMIT_TIME=2h terraform/bin/modify_lsf_job.sh -project default -localq normal -sendq sendq -queue holdq
# USER=jsmith has a job submitted less than SPLAPP_SUBMIT_TIME=2h ago; skipping all their jobs
# Skipping 12345 : user jsmith has a job submitted less than SPLAPP_SUBMIT_TIME=2h ago
Separate mode — resubmitting an archived job (not part of the normal modify flow above; mutually exclusive with <job_ids_or_usernames> and -queue):
terraform/bin/modify_lsf_job.sh -project <projectname> -resubmit <job_id> [-hold]
| Flag | Required | Description |
|---|---|---|
-resubmit <job_id> |
— | Resubmits a job previously saved by -archive, reading it back from ~/.config/spillbox/archive/job_<job_id>_*.sh and submitting it as a brand-new job via bsub, with the original queue/output/error/cwd/resource/app restored (dependencies are not restored) from the archive file |
-hold |
No | Only meaningful with -resubmit — submits the resubmitted job with bsub -H (held) instead of letting it run immediately |
Options updated by modify_lsf_job.sh on the job
When modify_lsf_job.sh runs bmod, it updates the following on each job:
| Option | What gets set | Notes |
|---|---|---|
-q |
sendq (remote queue) |
Changed from local queue to Spillbox send queue |
-J |
<project>_<rand>_<original_name> |
Renamed to track the project; skipped for job arrays |
-app |
Value from -app flag |
Optional; if not provided, app is left unchanged |
-E |
singularity_preexec.sh with all env vars |
Replaces any existing pre-exec with Spillbox’s wrapper |
-Ep |
singularity_postexec.sh |
Replaces any existing post-exec with Spillbox’s wrapper |
-o |
$REMOTEDIR/%J_%I.out |
Remote log directory on the Spillbox cluster |
-e |
$REMOTEDIR/%J_%I.err |
Remote log directory on the Spillbox cluster |
-cwd |
/tmp |
Actual working dir is passed via SPLAPP_EXEC_CWD in pre-exec |
-w |
done(<snapshot_job>) && <original_deps> |
Adds dependency on snapshot job; preserves existing deps |
-R |
Value from -remoteR flag, or original -R plus -remoteRA |
Optional; only set if -remoteR or -remoteRA is provided |
All original values (name, pre/post exec, output, cwd, dependencies, resource requirements, app) are encoded into the pre-exec command so they can be restored by reverse_modify_lsf.sh.
What modify_lsf_job.sh does internally
For each job it processes:
- Reads existing job state via
bjobs: - Job name, pre/post exec commands, output/error files, working directory
- Existing dependencies (
bjdepinfo) - App profile, resource requirements
-
Whether it’s interactive
-
Submits a snapshot job (
spillbox_snapshot) — this runs first on the remote cluster and snapshots the project filesystem so the job has the right data when it starts. Only one snapshot job is submitted per user per run (shared across all that user’s jobs). -
Encodes original job state — all original values (name, pre/post exec, output, cwd, dependencies, resource requirements, app) are encoded as environment variables inside the new pre-exec command string, so they can be recovered later by
reverse_modify_lsf.sh. -
Calls
bmodto modify the held job: - Changes queue to
sendq(remote cluster) - Replaces pre-exec with Spillbox’s
singularity_preexec.sh(carries all encoded original values) - Replaces post-exec with Spillbox’s
singularity_postexec.sh - Redirects output/error to remote log directory:
$SPILLBOX_REMOTE_WORKDIR/.spillbox/customers/<user>/<project>-<site>/stacks/modules/docker-nfs/install/spillbox_splfs/LOG/LSFLOG/ - Sets working directory to
/tmp(actual workdir restored at runtime viaSPLAPP_EXEC_CWD) -
Adds dependency on the snapshot job
-
Calls
bresume— releases the job so it can now run via Spillbox. -
Submits a download job (
spillbox_download_<project>) — runs after all modified jobs complete, downloads results back to the local cluster.
Environment Variables Reference
Variables set in the project config file (terraform/etc/spillbox_singularity_config.<projectname>):
| Variable | Description |
|---|---|
SPILLBOX_SERVER |
Connection string to the Spillbox server, required. Either plain <host>:<port> (single-server), or S<host>:<port>:C<host>:<port> for two-server setups (S = server side, C = client side) |
SPILLBOX_SITE |
Unique on-prem site ID, used in .spillbox/sites/local/<SITE>/ paths |
SPILLBOX_SINGULARITY_EXECUTABLE |
Path to the compiled, setuid singularity_wrapper binary built from site/singularity_wrapper.c by lsf_bapp_setup.sh, used when runtime_container=singularity |
SPILLBOX_DOCKER_EXECUTABLE |
Path to the compiled, setuid docker_wrapper binary built from site/docker_wrapper.c by lsf_bapp_setup.sh, used when runtime_container=docker |
SPILLBOX_HYBRID_MOUNT_PATHS |
Which on-prem paths get hybrid-mounted into the job (ALL = everything) |
SPILLBOX_HYBRID_OPTION |
How hybrid paths behave; -download = fetch data in, not just reference it |
SPILLBOX_REMOTE_WORKDIR |
Cloud-side job workdir; required — modify_lsf_job.sh/modify_slurm_job.sh error out if it’s unset |
SPILLBOX_DOWNLOAD_INTERVAL |
hold = hold until explicitly released; a number = hold that many minutes then auto-release; unset/0 = no hold, sync immediately (default). See Notes below. |
SPILLBOX_LARGEDIR_OPT |
Entry-count threshold for “large directory” handling |
SPILLBOX_LARGEDIR_ENTRY_SIZE |
Expected avg entry size hint for large-directory handling |
SPILLBOX_LARGEDIR_PAT |
Regex matching dirs that get large-directory handling |
SPILLBOX_HOLD_EXCLUDE_PAT |
Regex of files excluded from project snapshot/hold |
SPILLBOX_HOLD_EXCLUDE_SIZE |
Size threshold above which files are excluded from snapshot/hold |
SPLAPP_SUBMIT_TIME |
Job age (seconds, or m/h/d suffix); modify_lsf_job.sh and modify_slurm_job.sh skip every job of a user who has any job submitted more recently than this. Unset = no limit. See the flag reference above. |
Example (terraform/etc/spillbox_singularity_config.<projectname>):
# --- Required ---
# Single server
export SPILLBOX_SERVER=10.10.10.10:8080
# Two-server setup: S<remote_ip>:<port>:C<client_ip>:<port>
# S = remote/server-side IP, C = local/client-side IP
# Use when the Spillbox server has separate IPs for each network
# export SPILLBOX_SERVER=S10.10.10.10:8080:C192.168.1.1:8080
export SPILLBOX_SITE=mysite # unique on-prem site ID
export SPILLBOX_REMOTE_WORKDIR=/workdir # cloud-side job working directory, required
# --- Container executable: set the one matching runtime_container ---
export SPILLBOX_SINGULARITY_EXECUTABLE=/workdir/singularity_wrapper # compiled setuid wrapper, used when runtime_container=singularity
export SPILLBOX_DOCKER_EXECUTABLE=/workdir/docker_wrapper # compiled setuid wrapper, used when runtime_container=docker
# --- Hybrid mount behavior ---
export SPILLBOX_HYBRID_MOUNT_PATHS=ALL # which on-prem paths get hybrid-mounted ("ALL" = everything)
export SPILLBOX_HYBRID_OPTION="-download" # fetch data in, not just reference it
# --- Optional: uncomment and adjust only if the defaults don't fit ---
#export SPILLBOX_DOWNLOAD_INTERVAL=10 # "hold" = hold until released, a number = hold that many minutes, unset/0 = no hold (default)
#export SPILLBOX_LARGEDIR_OPT=100000 # entry-count threshold for "large directory" handling
#export SPILLBOX_LARGEDIR_ENTRY_SIZE=88 # expected avg entry size hint for large-directory handling
#export SPILLBOX_LARGEDIR_PAT='/cache/sync[0-9]+/[^/]+' # regex matching dirs that get large-directory handling
#export SPILLBOX_HOLD_EXCLUDE_PAT='/runpointsfile|/foo_\d+$' # regex of files excluded from project snapshot/hold
#export SPILLBOX_HOLD_EXCLUDE_SIZE=1024 # size (bytes) above which files are excluded from snapshot/hold
#export SPLAPP_SUBMIT_TIME=300 # skip all jobs of a user with any job newer than this (seconds, or 30m/2h/1d)
Notes
- Jobs are processed in parallel — large batches of job IDs run concurrently.
- A queue name as input modifies every job currently in that queue, including other users’ jobs, not just the one running the script — the operator needs
sudorights to act as those other users. Each affected user gets their own separate snapshot/download job (never combined), and if a user doesn’t already have the given-projectset up, it’s created for them on first use. - If a user already has a
spillbox_download_<project>job running, that job is skipped (incremental mode, currently disabled). - Interactive jobs (
bsub -I) require-spillboxlocalqand-spillboxsendqto be explicitly set. - Spaces in resource requirements, pre/post exec, and dependencies are encoded as
-s_b-o_x-internally to survive shell quoting throughbmod. - The
SNAPSHOT_TIMEis stamped at the start of themodify_lsf_job.shrun and shared across all jobs in that batch so they all refer to the same snapshot. - Hold time is a minimum, not exact — files release within N to N+1 minutes (checked once a minute).
- Overview
- Prerequisites
- Client vs. server config files
- Client side (where you run modify_lsf_job.sh)
- Server side (where the job actually runs)
- Quick example
- Workflow
- Step 1 — User submits job in HOLD state
- Step 2 — Operator runs modify_lsf_job.sh
- Step 3 — Job runs on remote cluster
- Automatic mode: the hold queue and the crontab
- Reversing a modification
- Reference
- modify_lsf_job.sh flag reference
- Options updated by modify_lsf_job.sh on the job
- What modify_lsf_job.sh does internally
- Environment Variables Reference
- Notes