Skip to content

Commit 6bcb70b

Browse files
committed
Basic implementation of jinja2 templating
- If the batch_script or any other string has {{ or {% in it, treat it as a jinja2 template and do advanced formatting. - Allows for optional options to be more easily supported. - Initial implementation for SlurmSpawner only. - Closes: #46 (slurm + {nprocs}) - Closes: #70 (slurm + {nprocs}) - Closes: #38 (templates / fallbacks)
1 parent 3fda206 commit 6bcb70b

File tree

3 files changed

+31
-13
lines changed

3 files changed

+31
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Common attributes of batch submission / resource manager environments will inclu
3737
* job names instead of PIDs
3838

3939
`BatchSpawnerBase` provides several general mechanisms:
40-
* configurable traits `req_foo` that are exposed as `{foo}` in job template scripts
40+
* configurable traits `req_foo` that are exposed as `{foo}` in job template scripts. Templates (submit scripts in particular) may also use the full power of [jinja2](http://jinja.pocoo.org/). Templates are automatically detected if a `{{` or `{%` is present, otherwise str.format() used.
4141
* configurable command templates for submitting/querying/cancelling jobs
4242
* a generic concept of job-ID and ID-based job state tracking
4343
* overrideable hooks for subclasses to plug in logic at numerous points

batchspawner/batchspawner.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import xml.etree.ElementTree as ET
2222

23+
from jinja2 import Template
24+
2325
from tornado import gen
2426
from tornado.process import Subprocess
2527
from subprocess import CalledProcessError
@@ -63,6 +65,20 @@ def run_command(cmd, input=None, env=None):
6365
out = out.decode().strip()
6466
return out
6567

68+
def format_template(template, *args, **kwargs):
69+
"""Format a template, either using jinja2 or str.format().
70+
71+
Use jinja2 if the template is a jinja2.Template, or contains '{{' or
72+
'{%'. Otherwise, use str.format() for backwards compatability with
73+
old scripts (but you can't mix them).
74+
"""
75+
if isinstance(template, Template):
76+
return template.render(*args, **kwargs)
77+
elif '{{' in template or '{%' in template:
78+
return Template(template).render(*args, **kwargs)
79+
return template.format(*args, **kwargs)
80+
81+
6682
class BatchSpawnerBase(Spawner):
6783
"""Base class for spawners using resource manager batch job submission mechanisms
6884
@@ -177,11 +193,11 @@ def cmd_formatted_for_batch(self):
177193
@gen.coroutine
178194
def submit_batch_script(self):
179195
subvars = self.get_req_subvars()
180-
cmd = self.batch_submit_cmd.format(**subvars)
196+
cmd = format_template(self.batch_submit_cmd, **subvars)
181197
subvars['cmd'] = self.cmd_formatted_for_batch()
182198
if hasattr(self, 'user_options'):
183199
subvars.update(self.user_options)
184-
script = self.batch_script.format(**subvars)
200+
script = format_template(self.batch_script, **subvars)
185201
self.log.info('Spawner submitting job using ' + cmd)
186202
self.log.info('Spawner submitted script:\n' + script)
187203
out = yield run_command(cmd, input=script, env=self.get_env())
@@ -207,7 +223,7 @@ def read_job_state(self):
207223
return self.job_status
208224
subvars = self.get_req_subvars()
209225
subvars['job_id'] = self.job_id
210-
cmd = self.batch_query_cmd.format(**subvars)
226+
cmd = format_template(self.batch_query_cmd, **subvars)
211227
self.log.debug('Spawner querying job: ' + cmd)
212228
try:
213229
out = yield run_command(cmd)
@@ -226,7 +242,7 @@ def read_job_state(self):
226242
def cancel_batch_job(self):
227243
subvars = self.get_req_subvars()
228244
subvars['job_id'] = self.job_id
229-
cmd = self.batch_cancel_cmd.format(**subvars)
245+
cmd = format_template(self.batch_cancel_cmd, **subvars)
230246
self.log.info('Cancelling job ' + self.job_id + ': ' + cmd)
231247
yield run_command(cmd)
232248

@@ -473,18 +489,19 @@ class SlurmSpawner(UserEnvMixin,BatchSpawnerRegexStates):
473489
).tag(config=True)
474490

475491
batch_script = Unicode("""#!/bin/bash
476-
#SBATCH --partition={partition}
477-
#SBATCH --time={runtime}
478-
#SBATCH --output={homedir}/jupyterhub_slurmspawner_%j.log
492+
#SBATCH --output={{homedir}}/jupyterhub_slurmspawner_%j.log
479493
#SBATCH --job-name=spawner-jupyterhub
480-
#SBATCH --workdir={homedir}
481-
#SBATCH --mem={memory}
482-
#SBATCH --export={keepvars}
494+
#SBATCH --workdir={{homedir}}
495+
#SBATCH --export={{keepvars}}
483496
#SBATCH --get-user-env=L
484-
#SBATCH {options}
497+
{% if partition %}#SBATCH --partition={{partition}}
498+
{% endif %}{% if runtime %}#SBATCH --time={{runtime}}
499+
{% endif %}{% if memory %}#SBATCH --mem={{memory}}
500+
{% endif %}{% if nprocs %}#SBATCH --cpus-per-task={{nprocs}}
501+
{% endif %}{% if options %}#SBATCH {{options}}{% endif %}
485502
486503
which jupyterhub-singleuser
487-
{cmd}
504+
{{cmd}}
488505
""").tag(config=True)
489506
# outputs line like "Submitted batch job 209"
490507
batch_submit_cmd = Unicode('sudo -E -u {username} sbatch --parsable').tag(config=True)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
jinja2
12
jupyterhub>=0.5

0 commit comments

Comments
 (0)