diff --git a/MANIFEST.in b/MANIFEST.in index e5bdd3ff2..07dfcbac6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ recursive-include coldfront/core/field_of_science/management/commands/data * recursive-include coldfront/plugins/iquota/templates * recursive-include coldfront/plugins/system_monitor/templates * +recursive-include coldfront/plugins/slurm/templates * recursive-include coldfront/templates * recursive-include coldfront/static * recursive-include coldfront/core/portal/templates * diff --git a/coldfront/config/plugins/slurm.py b/coldfront/config/plugins/slurm.py index 56f690c9e..b971aa628 100644 --- a/coldfront/config/plugins/slurm.py +++ b/coldfront/config/plugins/slurm.py @@ -9,3 +9,11 @@ SLURM_NOOP = ENV.bool('SLURM_NOOP', False) SLURM_IGNORE_USERS = ENV.list('SLURM_IGNORE_USERS', default=['root']) SLURM_IGNORE_ACCOUNTS = ENV.list('SLURM_IGNORE_ACCOUNTS', default=[]) +SLURM_SUBMISSION_INFO = ENV.list('SLURM_SUBMISSION_INFO', default=['account']) +SLURM_DISPLAY_SHORT_OPTION_NAMES = ENV.bool('SLURM_DISPLAY_SHORT_OPTION_NAMES', default=False) +SLURM_SHORT_OPTION_NAMES = ENV.dict('SLURM_SHORT_OPTION_NAMES', default={ + 'qos': 'q', + 'account': 'A', + 'clusters': 'M', + 'partition': 'p', +}) \ No newline at end of file diff --git a/coldfront/config/urls.py b/coldfront/config/urls.py index 353e3602e..df2105901 100644 --- a/coldfront/config/urls.py +++ b/coldfront/config/urls.py @@ -27,6 +27,8 @@ path('research-output/', include('coldfront.core.research_output.urls')), ] +if 'coldfront.plugins.slurm' in settings.INSTALLED_APPS: + urlpatterns.append(path('slurm/', include('coldfront.plugins.slurm.urls'))) if 'coldfront.plugins.iquota' in settings.INSTALLED_APPS: urlpatterns.append(path('iquota/', include('coldfront.plugins.iquota.urls'))) diff --git a/coldfront/core/allocation/templates/allocation/allocation_detail.html b/coldfront/core/allocation/templates/allocation/allocation_detail.html index b459f0488..36dd7996d 100644 --- a/coldfront/core/allocation/templates/allocation/allocation_detail.html +++ b/coldfront/core/allocation/templates/allocation/allocation_detail.html @@ -234,6 +234,10 @@

{{attribute}}

{% endif %} +{% if display_slurm_submission_info %} + {% include "slurm/slurm_submission_info_div.html" %} +{% endif %} +
diff --git a/coldfront/core/allocation/views.py b/coldfront/core/allocation/views.py index 216c3531c..7882fe244 100644 --- a/coldfront/core/allocation/views.py +++ b/coldfront/core/allocation/views.py @@ -5,6 +5,7 @@ from dateutil.relativedelta import relativedelta from django import forms +from django.conf import settings from django.contrib import messages from django.contrib.auth import get_user_model from django.contrib.auth.models import User @@ -167,6 +168,7 @@ def get(self, request, *args, **kwargs): context = self.get_context_data() context['form'] = form context['allocation'] = allocation_obj + context['display_slurm_submission_info'] = 'coldfront.plugins.slurm' in settings.INSTALLED_APPS return self.render_to_response(context) def post(self, request, *args, **kwargs): diff --git a/coldfront/core/portal/templates/portal/extra_app_templates.html b/coldfront/core/portal/templates/portal/extra_app_templates.html index 8a46581ea..83b5e010b 100644 --- a/coldfront/core/portal/templates/portal/extra_app_templates.html +++ b/coldfront/core/portal/templates/portal/extra_app_templates.html @@ -5,3 +5,7 @@ {% if 'coldfront.plugins.system_monitor' in EXTRA_APPS %} {% include "system_monitor/system_monitor_div.html" %} {% endif %} + +{% if 'coldfront.plugins.slurm' in EXTRA_APPS and user.is_authenticated %} + {% include "slurm/all_slurm_submission_info_div.html" %} +{% endif %} \ No newline at end of file diff --git a/coldfront/plugins/slurm/templates/slurm/all_slurm_submission_info.html b/coldfront/plugins/slurm/templates/slurm/all_slurm_submission_info.html new file mode 100644 index 000000000..f39954505 --- /dev/null +++ b/coldfront/plugins/slurm/templates/slurm/all_slurm_submission_info.html @@ -0,0 +1,62 @@ +{% load slurm_tags %} + +{% if slurm_submission_info %} +
+
+
+ Submitting Slurm Jobs +
+
+ {% for project_pk, allocations in slurm_submission_info.items %} +
+

{{ project_titles|dictitem:project_pk }}

+
+

Interactive Jobs

+
+
+ + {% for resources in allocations %} + {% for resources_name, submit_info in resources.items %} + {% if submit_info %} + + + + + {% endif %} + {% endfor %} + {% endfor %} +
{{ resources_name }} + srun + {% for slurm_submit_option, slurm_submit_option_value in submit_info.items %} + {{ slurm_submit_option }} {{ slurm_submit_option_value }} + {% endfor %} + <other options> +
+
+
+

Batch Jobs

+
+ {% for resources in allocations %} + {% for resources_name, submit_info in resources.items %} + {% if submit_info %} +
+
+ {{ resources_name }} +
+
    + {% for slurm_submit_option, slurm_submit_option_value in submit_info.items %} +
  • + #SBATCH {{ slurm_submit_option }} {{ slurm_submit_option_value }} +
  • + {% endfor %} +
+
+ {% endif %} + {% endfor %} + {% endfor %} +
+ {% endfor %} +
+
+
+{% endif %} \ No newline at end of file diff --git a/coldfront/plugins/slurm/templates/slurm/all_slurm_submission_info_div.html b/coldfront/plugins/slurm/templates/slurm/all_slurm_submission_info_div.html new file mode 100644 index 000000000..94cc6733a --- /dev/null +++ b/coldfront/plugins/slurm/templates/slurm/all_slurm_submission_info_div.html @@ -0,0 +1,19 @@ + +
+
+ + \ No newline at end of file diff --git a/coldfront/plugins/slurm/templates/slurm/slurm_submission_info.html b/coldfront/plugins/slurm/templates/slurm/slurm_submission_info.html new file mode 100644 index 000000000..d0e94181d --- /dev/null +++ b/coldfront/plugins/slurm/templates/slurm/slurm_submission_info.html @@ -0,0 +1,42 @@ +{% if slurm_submission_info %} +
+
+

Submitting Slurm Jobs

+
+
+

Interactive Jobs

+
+
    + {% for resources_name, submit_info in slurm_submission_info.items %} + {% if submit_info %} +
  • + srun + {% for slurm_submit_option, submit_info_value in submit_info.items %} + {{ slurm_submit_option }} {{ submit_info_value }} + {% endfor %} + <other options> +
  • + {% endif %} + {% endfor %} +
+
+

Batch Jobs

+
+
+ {% for resources_name, submit_info in slurm_submission_info.items %} + {% if submit_info %} +
+
    + {% for slurm_submit_option, submit_info_value in submit_info.items %} +
  • + #SBATCH {{ slurm_submit_option }} {{ submit_info_value }} +
  • + {% endfor %} +
+
+ {% endif %} + {% endfor %} +
+
+
+{% endif %} \ No newline at end of file diff --git a/coldfront/plugins/slurm/templates/slurm/slurm_submission_info_div.html b/coldfront/plugins/slurm/templates/slurm/slurm_submission_info_div.html new file mode 100644 index 000000000..905abb1c7 --- /dev/null +++ b/coldfront/plugins/slurm/templates/slurm/slurm_submission_info_div.html @@ -0,0 +1,19 @@ + +
+
+ + \ No newline at end of file diff --git a/coldfront/plugins/slurm/templatetags/__init__.py b/coldfront/plugins/slurm/templatetags/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/coldfront/plugins/slurm/templatetags/slurm_tags.py b/coldfront/plugins/slurm/templatetags/slurm_tags.py new file mode 100644 index 000000000..74883110e --- /dev/null +++ b/coldfront/plugins/slurm/templatetags/slurm_tags.py @@ -0,0 +1,7 @@ +from django import template + +register = template.Library() + +@register.filter +def dictitem(dictionary, key): + return dictionary.get(key) diff --git a/coldfront/plugins/slurm/urls.py b/coldfront/plugins/slurm/urls.py new file mode 100644 index 000000000..e243bb39f --- /dev/null +++ b/coldfront/plugins/slurm/urls.py @@ -0,0 +1,8 @@ +from django.urls import path + +from coldfront.plugins.slurm.views import get_all_slurm_submission_info, get_slurm_submission_info + +urlpatterns = [ + path('all-slurm-submission-info/', get_all_slurm_submission_info, name='all-slurm-submission-info'), + path('slurm-submission-info/', get_slurm_submission_info, name='slurm-submission-info') +] diff --git a/coldfront/plugins/slurm/views.py b/coldfront/plugins/slurm/views.py new file mode 100644 index 000000000..9e3957264 --- /dev/null +++ b/coldfront/plugins/slurm/views.py @@ -0,0 +1,103 @@ +from django.shortcuts import render +from django.http import HttpResponse +from coldfront.core.allocation.models import Allocation +from coldfront.core.allocation.models import Project +from coldfront.core.utils.common import import_from_settings + +SLURM_SUBMISSION_INFO = import_from_settings('SLURM_SUBMISSION_INFO', ['account']) +SLURM_DISPLAY_SHORT_OPTION_NAMES = import_from_settings('SLURM_DISPLAY_SHORT_OPTION_NAMES', False) +SLURM_SHORT_OPTION_NAMES = import_from_settings('SLURM_SHORT_OPTION_NAMES', {}) + + +def get_all_slurm_submission_info(request): + if not request.user.is_authenticated: + return HttpResponse('401 Unauthorized', status=401) + + project_list = Project.objects.filter( + status__name='Active', + projectuser__user=request.user, + projectuser__status__name='Active', + ) + + project_titles = {} + slurm_submission_info = {} + for project_obj in project_list: + project_titles[project_obj.pk] = project_obj.title + slurm_submission_info[project_obj.pk] = [] + allocation_list = project_obj.allocation_set.filter( + status__name__in=['Active', 'Renewal Requested', ], + allocationuser__user=request.user, + allocationuser__status__name='Active', + ) + for allocation_obj in allocation_list: + slurm_submission_info[project_obj.pk].append(get_slurm_submission_info_from_allocation(allocation_obj)) + + context = {} + context['slurm_submission_info'] = slurm_submission_info + context['project_titles'] = project_titles + return render(request, 'slurm/all_slurm_submission_info.html', context) + + +def get_slurm_submission_info(request): + allocation_obj = Allocation.objects.get(pk=request.POST.get('allocation_pk')) + slurm_submission_info = get_slurm_submission_info_from_allocation(allocation_obj) + return render(request, 'slurm/slurm_submission_info.html', {'slurm_submission_info': slurm_submission_info}) + + +def get_slurm_submission_info_from_allocation(allocation_obj): + submit_options = {} + resource_obj = allocation_obj.get_parent_resource + resource_type = resource_obj.resource_type.name + if resource_type == 'Cluster Partition': + cluster_obj = resource_obj.parent_resource + if 'clusters' in SLURM_SUBMISSION_INFO: + submit_options['clusters'] = cluster_obj.resourceattribute_set.get(resource_attribute_type__name='slurm_cluster').value + if 'partition' in SLURM_SUBMISSION_INFO: + submit_options['partition'] = resource_obj.name.lower() + elif resource_type != 'Cluster': + return {} + + slurm_account = allocation_obj.allocationattribute_set.filter(allocation_attribute_type__name='slurm_account_name') + if slurm_account.exists(): + if 'account' in SLURM_SUBMISSION_INFO: + submit_options['account'] = slurm_account[0].value + + slurm_specs = resource_obj.resourceattribute_set.filter(resource_attribute_type__name='slurm_specs') + submit_options = get_slurm_submission_info_from_slurm_specs(slurm_specs, submit_options) + slurm_specs = allocation_obj.allocationattribute_set.filter(allocation_attribute_type__name='slurm_specs') + submit_options = get_slurm_submission_info_from_slurm_specs(slurm_specs, submit_options) + + if SLURM_DISPLAY_SHORT_OPTION_NAMES: + submit_short_options = {} + for option, value in submit_options.items(): + short_option = SLURM_SHORT_OPTION_NAMES.get(option) + if short_option: + submit_short_options['-' + short_option] = value + else: + submit_short_options['--' + option] = value + + slurm_submission_info = submit_short_options + else: + submit_long_options = {} + for option, value in submit_options.items(): + submit_long_options['--' + option] = value + + slurm_submission_info = submit_long_options + + return {resource_obj.name: slurm_submission_info} + + +def get_slurm_submission_info_from_slurm_specs(slurm_specs, submit_options): + if slurm_specs.exists(): + specs = slurm_specs[0].value.replace('+', '').split(':') + for spec in specs: + spec_split = spec.split('=') + # Expanded attributes should be skipped + if len(spec_split) > 2: + continue + option, value = spec_split + option = option.lower() + if option in SLURM_SUBMISSION_INFO: + submit_options[option] = value + + return submit_options \ No newline at end of file