Add working directory field in options form

This commit is contained in:
Benoît Seignovert 2024-02-26 14:35:30 +01:00
parent 0273f449b8
commit c67e8742c8
Signed by: Benoît Seignovert
GPG key ID: F5D8895227D18A0B
8 changed files with 71 additions and 8 deletions

View file

@ -11,7 +11,7 @@ from traceback import format_exc
from flask import Flask, render_template, request from flask import Flask, render_template, request
from glicid_spawner.form import TEMPLATES, options_from_form from glicid_spawner.form import TEMPLATES, options_from_form
from glicid_spawner.micromamba import MicromambaEnv from glicid_spawner.micromamba import MicromambaEnv
from glicid_spawner.resources import CPU, MEMORY, gpu_max_duration from glicid_spawner.resources import CPU, MEMORY, get_folders, gpu_max_duration
from glicid_spawner.slurm import gres, sinfo_from_file from glicid_spawner.slurm import gres, sinfo_from_file
from livereload import Server from livereload import Server
@ -22,6 +22,7 @@ ENVS = [
MicromambaEnv('USER', 'bar', f'/{USERNAME}/envs/bar'), MicromambaEnv('USER', 'bar', f'/{USERNAME}/envs/bar'),
MicromambaEnv('GLOBAL', 'baz', '/global/envs/baz'), MicromambaEnv('GLOBAL', 'baz', '/global/envs/baz'),
] ]
FOLDERS = get_folders(USERNAME)
# Dummy SLURM config # Dummy SLURM config
DATA = Path(__file__).parent / '..' / 'tests' / 'data' DATA = Path(__file__).parent / '..' / 'tests' / 'data'
@ -38,6 +39,7 @@ GPU_MULTI_CLUSTER = gpu_max_duration(gres(SLURM_MULTI_CLUSTER))
# Format dummy options # Format dummy options
OPTIONS = { OPTIONS = {
'username': USERNAME, 'username': USERNAME,
'folders': FOLDERS,
'envs': ENVS, 'envs': ENVS,
'cpus': CPU, 'cpus': CPU,
'mems': MEMORY, 'mems': MEMORY,

View file

@ -3,7 +3,7 @@
from jinja2 import Environment, PackageLoader, select_autoescape from jinja2 import Environment, PackageLoader, select_autoescape
from .micromamba import get_envs from .micromamba import get_envs
from .resources import CPU, GPU_DEFAULTS, MEMORY, gpu_max_duration from .resources import CPU, GPU_DEFAULTS, MEMORY, get_folders, gpu_max_duration
from .slurm import gres, sinfo from .slurm import gres, sinfo
TEMPLATES = Environment( TEMPLATES = Environment(
@ -21,6 +21,7 @@ def options_attrs(username: str) -> dict:
return { return {
'username': username, 'username': username,
'folders': get_folders(username),
'envs': get_envs(username), 'envs': get_envs(username),
'cpus': CPU, 'cpus': CPU,
'mems': MEMORY, 'mems': MEMORY,
@ -38,6 +39,7 @@ def options_form(username: str) -> str:
def options_from_form(formdata) -> dict: def options_from_form(formdata) -> dict:
"""Export options from default form.""" """Export options from default form."""
# Parse form data response # Parse form data response
workdir = formdata['workdir'][0]
env = formdata['python-env'][0] env = formdata['python-env'][0]
cpu = int(formdata['cpu'][0]) cpu = int(formdata['cpu'][0])
mem = int(formdata['mem'][0]) mem = int(formdata['mem'][0])
@ -52,6 +54,7 @@ def options_from_form(formdata) -> dict:
# Export options # Export options
options = { options = {
'workdir': workdir,
'pyenv': env, 'pyenv': env,
'nprocs': cpu, 'nprocs': cpu,
'memory': f'{mem}GB', 'memory': f'{mem}GB',

View file

@ -37,3 +37,15 @@ def gpu_max_duration(gpus: list, unknown_default=1) -> dict:
defaults = {gpu: duration for gpu, duration in GPU_DEFAULTS.items() if gpu in gpus} defaults = {gpu: duration for gpu, duration in GPU_DEFAULTS.items() if gpu in gpus}
unknowns = {gpu: unknown_default for gpu in gpus if gpu not in GPU_DEFAULTS} unknowns = {gpu: unknown_default for gpu in gpus if gpu not in GPU_DEFAULTS}
return defaults | unknowns return defaults | unknowns
def get_folders(username: str) -> list:
"""List of folders accessible to the users as a working directory."""
return [
f'/home/{username}',
f'/scratch/nautilus/users/{username}',
f'/scratch/waves/users/{username}',
'/scratch/nautilus/projects',
'/scratch/waves/projects',
'/LAB-DATA/',
]

View file

@ -5,6 +5,7 @@
<div class="form-horizontal"> <div class="form-horizontal">
{% include "views/username.jinja" %} {% include "views/username.jinja" %}
{% include "views/chdir.jinja" %}
{% include "views/envs.jinja" %} {% include "views/envs.jinja" %}
{% include "views/resources.jinja" %} {% include "views/resources.jinja" %}
{% include "views/slurm.jinja" %} {% include "views/slurm.jinja" %}

View file

@ -0,0 +1,13 @@
<div class="form-group">
<label for="workdir" class="col-sm-3 control-label">Working directory:</label>
<div class="col-sm-9">
<div class="input-group">
<div class="input-group-addon"><span class="fa fa-briefcase"></span></div>
<select class="form-control" name="workdir">
{%- for folder in folders -%}
<option value="{{ folder }}">{{ folder }}</option>
{% endfor -%}
</select>
</div>
</div>
</div>

View file

@ -1,10 +1,13 @@
<div class="form-group"> <div class="form-group">
<label for="python-env" class="col-sm-3 control-label">Python environment:</label> <label for="python-env" class="col-sm-3 control-label">Python environment:</label>
<div class="col-sm-9"> <div class="col-sm-9">
<select class="form-control" name="python-env"> <div class="input-group">
{%- for pyenv in envs -%} <div class="input-group-addon"><span class="fa fa-bar-chart"></span></div>
<option value="{{ pyenv.path }}">{{ pyenv.name }} ({{ pyenv.scope | upper }})</option> <select class="form-control" name="python-env">
{% endfor -%} {%- for pyenv in envs -%}
</select> <option value="{{ pyenv.path }}">{{ pyenv.name }} ({{ pyenv.scope | upper }})</option>
{% endfor -%}
</select>
</div>
</div> </div>
</div> </div>

View file

@ -48,6 +48,15 @@ def test_options_attrs(mock_cluster):
assert options['username'] == 'john-doe' assert options['username'] == 'john-doe'
assert options['folders'] == [
'/home/john-doe',
'/scratch/nautilus/users/john-doe',
'/scratch/waves/users/john-doe',
'/scratch/nautilus/projects',
'/scratch/waves/projects',
'/LAB-DATA/',
]
assert [env.path for env in options['envs']] == [ assert [env.path for env in options['envs']] == [
'/john-doe/envs/foo', '/john-doe/envs/foo',
'/john-doe/envs/bar', '/john-doe/envs/bar',
@ -243,6 +252,7 @@ def test_options_from_form():
"""Test options from form parser.""" """Test options from form parser."""
# No GPU # No GPU
formdata = { formdata = {
'workdir': ['/home/john-doe/'],
'python-env': ['/john-doe/envs/foo'], 'python-env': ['/john-doe/envs/foo'],
'cpu': ['1'], 'cpu': ['1'],
'mem': ['4'], 'mem': ['4'],
@ -250,6 +260,7 @@ def test_options_from_form():
} }
assert options_from_form(formdata) == { assert options_from_form(formdata) == {
'workdir': '/home/john-doe/',
'pyenv': '/john-doe/envs/foo', 'pyenv': '/john-doe/envs/foo',
'nprocs': 1, 'nprocs': 1,
'memory': '4GB', 'memory': '4GB',
@ -258,6 +269,7 @@ def test_options_from_form():
# With GPU (in defaults list) # With GPU (in defaults list)
formdata = { formdata = {
'workdir': ['/scratch/nautilus/users/john-doe/'],
'python-env': ['/john-doe/envs/bar'], 'python-env': ['/john-doe/envs/bar'],
'cpu': ['2'], 'cpu': ['2'],
'mem': ['16'], 'mem': ['16'],
@ -266,6 +278,7 @@ def test_options_from_form():
} }
assert options_from_form(formdata) == { assert options_from_form(formdata) == {
'workdir': '/scratch/nautilus/users/john-doe/',
'pyenv': '/john-doe/envs/bar', 'pyenv': '/john-doe/envs/bar',
'nprocs': 2, 'nprocs': 2,
'memory': '16GB', 'memory': '16GB',
@ -276,6 +289,7 @@ def test_options_from_form():
# With unknown GPU (default 1h allocation) # With unknown GPU (default 1h allocation)
formdata = { formdata = {
'workdir': ['/scratch/waves/projects/'],
'python-env': ['/global/envs/baz'], 'python-env': ['/global/envs/baz'],
'cpu': ['8'], 'cpu': ['8'],
'mem': ['16'], 'mem': ['16'],
@ -284,6 +298,7 @@ def test_options_from_form():
} }
assert options_from_form(formdata) == { assert options_from_form(formdata) == {
'workdir': '/scratch/waves/projects/',
'pyenv': '/global/envs/baz', 'pyenv': '/global/envs/baz',
'nprocs': 8, 'nprocs': 8,
'memory': '16GB', 'memory': '16GB',
@ -294,6 +309,7 @@ def test_options_from_form():
# Invalid CPU request (0h allocated) # Invalid CPU request (0h allocated)
formdata = { formdata = {
'workdir': ['/LAD-DATA/'],
'python-env': ['/global/envs/qux'], 'python-env': ['/global/envs/qux'],
'cpu': ['128'], 'cpu': ['128'],
'mem': ['4096'], 'mem': ['4096'],
@ -302,6 +318,7 @@ def test_options_from_form():
} }
assert options_from_form(formdata) == { assert options_from_form(formdata) == {
'workdir': '/LAD-DATA/',
'pyenv': '/global/envs/qux', 'pyenv': '/global/envs/qux',
'nprocs': 128, 'nprocs': 128,
'memory': '4096GB', 'memory': '4096GB',

View file

@ -1,6 +1,6 @@
"""Test resources module.""" """Test resources module."""
from glicid_spawner.resources import CPU, GPU_DEFAULTS, MEMORY, gpu_max_duration from glicid_spawner.resources import CPU, GPU_DEFAULTS, MEMORY, get_folders, gpu_max_duration
def test_default_resources(): def test_default_resources():
@ -22,3 +22,15 @@ def test_gpu_max_duration():
# Sorted by defaults order, then unknowns # Sorted by defaults order, then unknowns
assert list(gpu) == ['None', 'A100', 'T4', 'K80'] assert list(gpu) == ['None', 'A100', 'T4', 'K80']
assert list(gpu.values()) == [24, 1, 3, 3] assert list(gpu.values()) == [24, 1, 3, 3]
def test_resources_workdir():
"""Test resources working directories."""
assert get_folders('john-doe') == [
'/home/john-doe',
'/scratch/nautilus/users/john-doe',
'/scratch/waves/users/john-doe',
'/scratch/nautilus/projects',
'/scratch/waves/projects',
'/LAB-DATA/',
]