Add SLURM resources to form template
This commit is contained in:
parent
b8efa00a05
commit
11d878cecf
7 changed files with 506 additions and 112 deletions
|
@ -3,7 +3,8 @@
|
|||
from jinja2 import Environment, PackageLoader, select_autoescape
|
||||
|
||||
from .micromamba import get_envs
|
||||
from .resources import CPU, GPU, RAM
|
||||
from .resources import CPU, GPU_DEFAULTS, MEMORY, gpu_max_duration
|
||||
from .slurm import gres, sinfo
|
||||
|
||||
TEMPLATES = Environment(
|
||||
loader=PackageLoader('glicid_spawner'),
|
||||
|
@ -13,12 +14,18 @@ TEMPLATES = Environment(
|
|||
|
||||
def options_attrs(username: str) -> dict:
|
||||
"""Form options attributes."""
|
||||
slurm_sinfo = sinfo(username)
|
||||
|
||||
# Allocated 1h to any SLURM GPU resources not listed in GPUS
|
||||
gpus = gpu_max_duration(gres(slurm_sinfo), unknown_default=1)
|
||||
|
||||
return {
|
||||
'username': username,
|
||||
'envs': get_envs(username),
|
||||
'cpus': CPU,
|
||||
'rams': RAM,
|
||||
'gpus': GPU,
|
||||
'mems': MEMORY,
|
||||
'gpus': gpus,
|
||||
'sinfo': slurm_sinfo,
|
||||
}
|
||||
|
||||
|
||||
|
@ -30,26 +37,37 @@ def options_form(username: str) -> str:
|
|||
|
||||
def options_from_form(formdata) -> dict:
|
||||
"""Export options from default form."""
|
||||
# Resources choices indexes
|
||||
i_cpu = int(formdata['cpu'][0])
|
||||
i_ram = int(formdata['ram'][0])
|
||||
i_gpu = int(formdata['gpu'][0])
|
||||
# Parse form data response
|
||||
env = formdata['python-env'][0]
|
||||
cpu = int(formdata['cpu'][0])
|
||||
mem = int(formdata['mem'][0])
|
||||
gpu = formdata['gpu'][0]
|
||||
cluster = formdata.get('cluster', [None])[0]
|
||||
partition = formdata.get('partition', [None])[0]
|
||||
node = formdata.get('node', [None])[0]
|
||||
|
||||
duration = min(
|
||||
CPU[i_cpu].max_duration,
|
||||
RAM[i_ram].max_duration,
|
||||
GPU[i_gpu].max_duration,
|
||||
)
|
||||
# Compute max duration
|
||||
# If the value provided is not in the original list, runtime = 0 (except unknown GPU = 1h)
|
||||
runtime = min(CPU.get(cpu, 0), MEMORY.get(mem, 0), GPU_DEFAULTS.get(gpu, 1))
|
||||
|
||||
# Export options
|
||||
options = {
|
||||
'pyenv': formdata['python-env'][0],
|
||||
'nprocs': int(CPU[i_cpu].description),
|
||||
'memory': RAM[i_ram].description.replace(' ', ''),
|
||||
'runtime': f'{duration:02d}:00:00',
|
||||
'pyenv': env,
|
||||
'nprocs': cpu,
|
||||
'memory': f'{mem}GB',
|
||||
'runtime': f'{runtime:02d}:00:00',
|
||||
}
|
||||
|
||||
if i_gpu:
|
||||
options['gres'] = 'gpu:' + GPU[i_gpu].description.lower()
|
||||
if gpu != 'None':
|
||||
options['gres'] = f'gpu:{gpu.lower()}'
|
||||
|
||||
if cluster:
|
||||
options['cluster'] = cluster
|
||||
|
||||
if partition:
|
||||
options['partition'] = partition
|
||||
|
||||
if node:
|
||||
options['node'] = node
|
||||
|
||||
return options
|
||||
|
|
|
@ -15,10 +15,29 @@ input[type=radio]:checked+label {
|
|||
|
||||
.panel-heading .panel-title-toggle:before {
|
||||
font-family: 'FontAwesome';
|
||||
content: "\\f078";
|
||||
content: "\f078";
|
||||
color: lightgrey;
|
||||
display: inline-block;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.panel-heading .panel-title-toggle.collapsed:before {
|
||||
content: "\\f054";
|
||||
content: "\f054";
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.flex-item-2 {
|
||||
width: 49%;
|
||||
}
|
||||
.flex-item-4 {
|
||||
width: 24%;
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
<div class="form-group">
|
||||
<label for="cpu" class="col-sm-3 control-label">CPU:</label>
|
||||
<div class="col-sm-9">
|
||||
{%- for cpu in cpus -%}
|
||||
{%- for cpu, max_duration in cpus.items() -%}
|
||||
<div class="col-sm-2">
|
||||
<input type="radio" name="cpu" id="cpu_{{loop.index0}}" value="{{loop.index0}}"
|
||||
data-max-duration="{{cpu.max_duration}}"{% if loop.first %} checked{% endif %}>
|
||||
<label for="cpu_{{loop.index0}}" class="btn btn-default btn-block">
|
||||
{{ cpu.description }}
|
||||
<input type="radio" name="cpu" id="cpu_{{cpu}}" value="{{cpu}}"
|
||||
data-max-duration="{{max_duration}}"
|
||||
{%- if loop.first %} checked{% endif %}>
|
||||
<label for="cpu_{{cpu}}" class="btn btn-default btn-block">
|
||||
{{ cpu }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor -%}
|
||||
|
@ -16,14 +17,15 @@
|
|||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="ram" class="col-sm-3 control-label">Memory:</label>
|
||||
<label for="mem" class="col-sm-3 control-label">Memory:</label>
|
||||
<div class="col-sm-9">
|
||||
{%- for ram in rams -%}
|
||||
{%- for mem, max_duration in mems.items() -%}
|
||||
<div class="col-sm-2">
|
||||
<input type="radio" name="ram" id="ram_{{loop.index0}}" value="{{loop.index0}}"
|
||||
data-max-duration="{{ram.max_duration}}"{% if loop.first %} checked{% endif %}>
|
||||
<label for="ram_{{loop.index0}}" class="btn btn-default btn-block">
|
||||
{{ ram.description }}
|
||||
<input type="radio" name="mem" id="mem_{{mem}}" value="{{mem}}"
|
||||
data-max-duration="{{max_duration}}"
|
||||
{%- if loop.first %} checked{% endif %}>
|
||||
<label for="mem_{{mem}}" class="btn btn-default btn-block">
|
||||
{{ mem }} GB
|
||||
</label>
|
||||
</div>
|
||||
{% endfor -%}
|
||||
|
@ -33,17 +35,19 @@
|
|||
<div class="form-group">
|
||||
<label for="gpu" class="col-sm-3 control-label">GPU:</label>
|
||||
<div class="col-sm-9">
|
||||
{%- for gpu in gpus -%}
|
||||
{%- for gpu, max_duration in gpus.items() -%}
|
||||
<div class="col-sm-2">
|
||||
<input type="radio" name="gpu" id="gpu_{{loop.index0}}" value="{{loop.index0}}"
|
||||
data-max-duration="{{gpu.max_duration}}"{% if loop.first %} checked{% endif %}>
|
||||
<label for="gpu_{{loop.index0}}" class="btn btn-default btn-block">
|
||||
{{ gpu.description }}
|
||||
<input type="radio" name="gpu" id="gpu_{{gpu}}" value="{{gpu}}"
|
||||
data-max-duration="{{max_duration}}"
|
||||
{%- if loop.first %} checked{% endif %}>
|
||||
<label for="gpu_{{gpu}}" class="btn btn-default btn-block">
|
||||
{{ gpu }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor -%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -59,9 +63,9 @@
|
|||
|
||||
$('.resources input[type=radio]').change(function () {
|
||||
var cpu = $('input[name=cpu]:checked').data('max-duration');
|
||||
var ram = $('input[name=ram]:checked').data('max-duration');
|
||||
var mem = $('input[name=mem]:checked').data('max-duration');
|
||||
var gpu = $('input[name=gpu]:checked').data('max-duration');
|
||||
|
||||
$reservations_dropdown.text(Math.min(cpu, ram, gpu));
|
||||
$reservations_dropdown.text(Math.min(cpu, mem, gpu));
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -1,25 +1,23 @@
|
|||
<div class="panel-group" id="advanced-config" role="tablist" aria-multiselectable="true">
|
||||
<div id="cluster-config" class="panel-group" role="tablist" aria-multiselectable="true">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="heading">
|
||||
<h4 class="panel-title">
|
||||
<a class="panel-title-toggle collapsed" role="button" data-toggle="collapse"
|
||||
data-parent="#advanced-config" href="#advanced-config-collapse" aria-expanded="true" aria-controls="advanced-config-collapse">
|
||||
Advanced configuration
|
||||
data-parent="#cluster-config" href="#cluster-config-collapse" aria-expanded="true" aria-controls="cluster-config-collapse">
|
||||
Advanced cluster configuration
|
||||
</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="advanced-config-collapse" class="panel-collapse {# collapse #}" role="tabpanel" aria-labelledby="heading">
|
||||
<div id="cluster-config-collapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading">
|
||||
<div class="panel-body">
|
||||
<p>You can specify on which cluster/partion/node you want to start your Jupyter server:</p>
|
||||
|
||||
{# {% if slurm is not 'None' %} #}
|
||||
<div class="form-group">
|
||||
<label for="cluster" class="col-sm-3 control-label">Clusters:</label>
|
||||
<div class="col-sm-9">
|
||||
{%- for cluster in slurm -%}
|
||||
<div class="col-sm-6">
|
||||
<input type="radio" name="cluster" id="cluster_{{cluster}}" class="slurm_cluster"
|
||||
value="{{cluster}}">
|
||||
{% if 'N/A' not in sinfo %}
|
||||
<div class="form-group clusters">
|
||||
<label for="cluster" class="col-sm-3 control-label">Cluster:</label>
|
||||
<div class="col-sm-9 flex-container">
|
||||
{%- for cluster in sinfo.values() -%}
|
||||
<div class="flex-item-2 slurm-cluster" data-cluster="{{cluster}}">
|
||||
<input type="radio" name="cluster" id="cluster_{{cluster}}" value="{{cluster}}">
|
||||
<label for="cluster_{{cluster}}" class="btn btn-default btn-block">
|
||||
{{ cluster | capitalize }}
|
||||
</label>
|
||||
|
@ -27,40 +25,42 @@
|
|||
{% endfor -%}
|
||||
</div>
|
||||
</div>
|
||||
{# {% endif %} #}
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="partition" class="col-sm-3 control-label">Partitions:</label>
|
||||
<div class="col-sm-9" id="partitions-list"></div>
|
||||
<div class="form-group partitions{% if 'N/A' not in sinfo %} hidden{% endif %}">
|
||||
<label for="partition" class="col-sm-3 control-label">Partition:</label>
|
||||
<div class="col-sm-9 flex-container">
|
||||
{%- for cluster in sinfo.values() -%}
|
||||
{%- for partition in cluster -%}
|
||||
<div class="flex-item-4 slurm-partition"
|
||||
data-cluster="{{cluster}}" data-partition="{{partition}}"
|
||||
data-cpu="{{partition.max_idle_cpu}}" data-mem="{{partition.max_mem}}" data-gpu="{{partition.gpus}}">
|
||||
<input type="radio" name="partition" id="partition_{{cluster}}_{{partition}}" value="{{partition}}">
|
||||
<label for="partition_{{cluster}}_{{partition}}" class="btn btn-default btn-block">
|
||||
{{ partition | capitalize }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="node" class="col-sm-3 control-label">Nodes:</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-3">
|
||||
<input type="radio" name="node" id="node_0" value="0" checked>
|
||||
<label for="node_0" class="btn btn-default btn-block">
|
||||
cribbar033
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<input type="radio" name="node" id="node_1" value="1">
|
||||
<label for="node_1" class="btn btn-default btn-block">
|
||||
cribbar034
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<input type="radio" name="node" id="node_2" value="2">
|
||||
<label for="node_2" class="btn btn-default btn-block">
|
||||
cribbar035
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<input type="radio" name="node" id="node_3" value="3">
|
||||
<label for="node_3" class="btn btn-default btn-block">
|
||||
cribbar036
|
||||
<div class="form-group nodes hidden">
|
||||
<label for="node" class="col-sm-3 control-label">Node:</label>
|
||||
<div class="col-sm-9 flex-container">
|
||||
{%- for cluster in sinfo.values() -%}
|
||||
{%- for partition in cluster -%}
|
||||
{%- for node in partition -%}
|
||||
<div class="flex-item-4 slurm-node" data-cluster="{{cluster}}" data-partition="{{partition}}"
|
||||
data-node="{{node}}" data-cpu="{{node.cpu.idle}}" data-mem="{{node.mem}}" data-gpu="{{node.gpu}}">
|
||||
<input type="radio" name="node" id="node_{{cluster}}_{{partition}}_{{node}}" value="{{node}}">
|
||||
<label for="node_{{cluster}}_{{partition}}_{{node}}" class="btn btn-default btn-block">
|
||||
{{ node | capitalize }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -68,3 +68,98 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="not-enough-resources" class="panel panel-danger hidden">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">⚠️ Not enough ressources</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
Please change your resources request or retry later…
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var $form = $('form');
|
||||
var $clusters = $('.slurm-cluster');
|
||||
var $partitions = $('.slurm-partition');
|
||||
var $nodes = $('.slurm-node');
|
||||
|
||||
get_config = function(){
|
||||
// Convert form into object (https://stackoverflow.com/a/17784656)
|
||||
return $form
|
||||
.serializeArray()
|
||||
.reduce(function (data, o) {
|
||||
data[o.name] = o.value;
|
||||
return data;
|
||||
}, {});
|
||||
}
|
||||
|
||||
_toggle = function (el, cpu, mem, gpu, cluster, partition) {
|
||||
if (
|
||||
(parseInt(cpu) > parseInt(el.dataset.cpu)) |
|
||||
(parseInt(mem) > parseInt(el.dataset.mem)) |
|
||||
(!el.dataset.gpu.includes(gpu)) |
|
||||
(cluster !== undefined & cluster != el.dataset.cluster) |
|
||||
(partition !== undefined & partition != el.dataset.partition)
|
||||
) {
|
||||
el.classList.add('hidden');
|
||||
el.querySelector('input').checked = false;
|
||||
} else {
|
||||
el.classList.remove('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
not_enough_resources = function (err) {
|
||||
var $err = $('#not-enough-resources');
|
||||
var $submit = $('input[type=submit]');
|
||||
|
||||
if (err) {
|
||||
$submit.addClass('hidden');
|
||||
$err.removeClass('hidden');
|
||||
} else {
|
||||
$submit.removeClass('hidden');
|
||||
$err.addClass('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
toggle_config = function() {
|
||||
var config = get_config();
|
||||
|
||||
if (config['cluster'] !== undefined) {
|
||||
$('.partitions').removeClass('hidden');
|
||||
}
|
||||
if (config['partition'] !== undefined) {
|
||||
$('.nodes').removeClass('hidden');
|
||||
}
|
||||
|
||||
$partitions.each(function(_, el){
|
||||
_toggle(el, config['cpu'], config['mem'], config['gpu'], config['cluster']);
|
||||
})
|
||||
|
||||
$nodes.each(function(_, el){
|
||||
_toggle(el, config['cpu'], config['mem'], config['gpu'], config['cluster'], config['partition']);
|
||||
})
|
||||
|
||||
if ($partitions.not('.hidden').length == 0) {
|
||||
$('.partitions').addClass('hidden');
|
||||
} else {
|
||||
$('.partitions').removeClass('hidden');
|
||||
}
|
||||
|
||||
if (config['partition'] === undefined | $nodes.not('.hidden').length == 0) {
|
||||
$('.nodes').addClass('hidden');
|
||||
} else {
|
||||
$('.nodes').removeClass('hidden');
|
||||
}
|
||||
|
||||
if ($partitions.not('.hidden').length == 0 & $nodes.not('.hidden').length == 0) {
|
||||
not_enough_resources(true);
|
||||
} else {
|
||||
not_enough_resources(false);
|
||||
}
|
||||
}
|
||||
|
||||
toggle_config();
|
||||
$form.change(toggle_config);
|
||||
|
||||
</script>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue