2024-01-29 17:47:47 +01:00
|
|
|
"""GLiCID spawner module."""
|
|
|
|
|
2024-02-22 17:41:58 +01:00
|
|
|
import asyncio
|
2024-02-21 15:11:14 +01:00
|
|
|
import re
|
|
|
|
|
2024-02-22 17:41:58 +01:00
|
|
|
from batchspawner import JobStatus, SlurmSpawner
|
2024-02-26 14:54:56 +01:00
|
|
|
from traitlets import Bool, Integer, Unicode, default
|
2024-01-30 16:15:18 +01:00
|
|
|
|
2024-02-14 10:02:07 +01:00
|
|
|
from .form import options_form, options_from_form
|
2024-02-22 17:41:58 +01:00
|
|
|
from .progress import ElapseTime, get_progress
|
2024-01-29 17:47:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
class GlicidSpawner(SlurmSpawner):
|
|
|
|
"""Glicid SLURM Spawner."""
|
|
|
|
|
2024-02-26 14:54:56 +01:00
|
|
|
disable_user_config = Bool(
|
|
|
|
True,
|
|
|
|
help='Disable per-user configuration of single-user servers.',
|
|
|
|
).tag(config=True)
|
|
|
|
|
|
|
|
notebook_dir = Unicode(
|
|
|
|
'/',
|
|
|
|
help='Path to the notebook directory for the single-user server.',
|
|
|
|
).tag(config=True)
|
|
|
|
|
|
|
|
@default('default_url')
|
|
|
|
def _default_url_default(self) -> str:
|
|
|
|
"""The URL the single-user server should start in."""
|
|
|
|
return '/tree' + self.user_options.get('workdir', '/home/{username}')
|
|
|
|
|
2024-02-21 15:11:14 +01:00
|
|
|
batchspawner_singleuser_cmd = Unicode(
|
2024-02-12 15:18:17 +01:00
|
|
|
'glicid-spawner-singleuser',
|
2024-02-21 15:11:14 +01:00
|
|
|
help='Spawner singleuser command.',
|
|
|
|
).tag(config=True)
|
|
|
|
|
2024-02-26 09:55:15 +01:00
|
|
|
req_job_name = Unicode(
|
|
|
|
'jupyterhub_glicid',
|
|
|
|
help='SLURM job name',
|
|
|
|
).tag(config=True)
|
|
|
|
|
2024-02-21 15:11:14 +01:00
|
|
|
req_qos = Unicode(
|
|
|
|
'short',
|
|
|
|
help='QoS name to submit job to resource manager',
|
2024-02-12 15:18:17 +01:00
|
|
|
).tag(config=True)
|
|
|
|
|
2024-02-21 15:11:14 +01:00
|
|
|
slurm_job_id_re = Unicode(r'(\d+)(?:;(\w+))?').tag(config=True)
|
|
|
|
|
|
|
|
def parse_job_id(self, output):
|
|
|
|
"""Parse job id with cluster name support.
|
|
|
|
|
|
|
|
If cluster name is present, `job_id` will be a string
|
|
|
|
and suffix with `-M job_cluster` name.
|
|
|
|
|
|
|
|
"""
|
|
|
|
for job_id, job_cluster in re.findall(self.slurm_job_id_re, output):
|
|
|
|
return f'{job_id} -M {job_cluster}' if job_cluster else int(job_id)
|
|
|
|
|
|
|
|
self.log.error(f'GlicidSpawner unable to parse job ID from text: {output}')
|
|
|
|
return None
|
2024-02-14 10:48:56 +01:00
|
|
|
|
2024-02-21 15:11:14 +01:00
|
|
|
@default('options_form')
|
|
|
|
def _options_form_default(self) -> str:
|
2024-01-30 16:15:18 +01:00
|
|
|
"""JupyterHub rendered form template."""
|
2024-02-14 10:02:07 +01:00
|
|
|
return options_form(self.user.name)
|
2024-01-29 17:47:47 +01:00
|
|
|
|
2024-01-30 16:15:18 +01:00
|
|
|
def options_from_form(self, formdata) -> dict:
|
2024-02-08 17:50:36 +01:00
|
|
|
"""Export options from form."""
|
2024-02-14 10:02:07 +01:00
|
|
|
return options_from_form(formdata)
|
2024-02-22 17:41:58 +01:00
|
|
|
|
|
|
|
progress_rate = Integer(
|
|
|
|
5, help='Interval in seconds at which progress is polled for messages'
|
|
|
|
).tag(config=True)
|
|
|
|
|
|
|
|
async def progress(self):
|
|
|
|
"""Progress bar feedback."""
|
|
|
|
elapse_time = 0
|
|
|
|
|
|
|
|
while True:
|
|
|
|
if self.state_isrunning():
|
|
|
|
job_status = JobStatus.RUNNING
|
|
|
|
elapse_time += self.progress_rate
|
|
|
|
elif self.state_ispending():
|
|
|
|
job_status = JobStatus.PENDING
|
|
|
|
else:
|
|
|
|
job_status = JobStatus.NOTFOUND
|
|
|
|
|
|
|
|
yield get_progress(job_status, elapse_time)
|
|
|
|
|
|
|
|
if elapse_time >= ElapseTime.CONNECT:
|
|
|
|
break
|
|
|
|
|
|
|
|
await asyncio.sleep(self.progress_rate)
|