From 2d0f487fd4010d9fdc2cbe9fb2a4cef9f06ff25a Mon Sep 17 00:00:00 2001
From: Jan David Mol <mol@astron.nl>
Date: Tue, 6 Apr 2021 13:07:01 +0200
Subject: [PATCH] TMSS-696: Added priority_rank and priority_queue fields to
 Scheduling Unit, to allow priority adjustments at SU level within a project.

---
 .../tmss/tmssapp/migrations/0001_initial.py   | 30 ++++++++++++++++++-
 .../src/tmss/tmssapp/models/specification.py  | 11 +++++++
 SAS/TMSS/backend/src/tmss/tmssapp/populate.py |  2 +-
 .../tmss/tmssapp/serializers/specification.py |  6 ++++
 .../tmss/tmssapp/viewsets/specification.py    |  5 ++++
 SAS/TMSS/backend/src/tmss/urls.py             |  1 +
 6 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
index c6b66991f08..fc070c79167 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.0.9 on 2021-03-23 17:08
+# Generated by Django 3.0.9 on 2021-03-29 13:02
 
 from django.conf import settings
 import django.contrib.postgres.fields
@@ -6,6 +6,7 @@ import django.contrib.postgres.fields.jsonb
 import django.contrib.postgres.indexes
 from django.db import migrations, models
 import django.db.models.deletion
+import lofar.sas.tmss.tmss.tmssapp.models.common
 import lofar.sas.tmss.tmss.tmssapp.models.specification
 
 
@@ -98,6 +99,7 @@ class Migration(migrations.Migration):
             options={
                 'abstract': False,
             },
+            bases=(lofar.sas.tmss.tmss.tmssapp.models.common.RefreshFromDbInvalidatesCachedPropertiesMixin, models.Model),
         ),
         migrations.CreateModel(
             name='CycleQuota',
@@ -378,6 +380,15 @@ class Migration(migrations.Migration):
                 'abstract': False,
             },
         ),
+        migrations.CreateModel(
+            name='PriorityQueueType',
+            fields=[
+                ('value', models.CharField(max_length=128, primary_key=True, serialize=False, unique=True)),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
         migrations.CreateModel(
             name='Project',
             fields=[
@@ -398,6 +409,7 @@ class Migration(migrations.Migration):
             options={
                 'abstract': False,
             },
+            bases=(lofar.sas.tmss.tmss.tmssapp.models.common.RefreshFromDbInvalidatesCachedPropertiesMixin, models.Model),
         ),
         migrations.CreateModel(
             name='ProjectCategory',
@@ -433,6 +445,7 @@ class Migration(migrations.Migration):
             fields=[
                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
             ],
+            bases=(lofar.sas.tmss.tmss.tmssapp.models.common.RefreshFromDbInvalidatesCachedPropertiesMixin, models.Model),
         ),
         migrations.CreateModel(
             name='ProjectRole',
@@ -608,10 +621,12 @@ class Migration(migrations.Migration):
                 ('output_data_allowed_to_be_ingested', models.BooleanField(default=False, help_text='boolean (default FALSE), which blocks Ingest Tasks from starting if OFF. When toggled ON, backend must scan for startable Ingest Tasks.')),
                 ('output_pinned', models.BooleanField(default=False, help_text='boolean (default FALSE), which blocks deleting unpinned dataproducts. When toggled ON, backend must pick SUB up for deletion. It also must when dataproducts are unpinned.')),
                 ('results_accepted', models.BooleanField(default=False, help_text='boolean (default NULL), which records whether the results were accepted, allowing the higher-level accounting to be adjusted.')),
+                ('priority_rank', models.FloatField(default=0.0, help_text='Priority of this scheduling unit w.r.t. other scheduling units within the same queue and project.')),
             ],
             options={
                 'abstract': False,
             },
+            bases=(lofar.sas.tmss.tmss.tmssapp.models.common.RefreshFromDbInvalidatesCachedPropertiesMixin, models.Model),
         ),
         migrations.CreateModel(
             name='SchedulingUnitDraft',
@@ -626,10 +641,12 @@ class Migration(migrations.Migration):
                 ('generator_instance_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Parameter value that generated this run draft (NULLable).', null=True)),
                 ('scheduling_constraints_doc', django.contrib.postgres.fields.jsonb.JSONField(help_text='Scheduling Constraints for this run.', null=True)),
                 ('ingest_permission_required', models.BooleanField(default=False, help_text='Explicit permission is needed before the task.')),
+                ('priority_rank', models.FloatField(default=0.0, help_text='Priority of this scheduling unit w.r.t. other scheduling units within the same queue and project.')),
             ],
             options={
                 'abstract': False,
             },
+            bases=(lofar.sas.tmss.tmss.tmssapp.models.common.RefreshFromDbInvalidatesCachedPropertiesMixin, models.Model),
         ),
         migrations.CreateModel(
             name='SchedulingUnitObservingStrategyTemplate',
@@ -791,6 +808,7 @@ class Migration(migrations.Migration):
                 ('do_cancel', models.BooleanField(help_text='Cancel this task.')),
                 ('output_pinned', models.BooleanField(default=False, help_text='True if the output of this task is pinned to disk, that is, forbidden to be removed.')),
             ],
+            bases=(lofar.sas.tmss.tmss.tmssapp.models.common.RefreshFromDbInvalidatesCachedPropertiesMixin, models.Model),
         ),
         migrations.CreateModel(
             name='TaskConnectorType',
@@ -1173,6 +1191,11 @@ class Migration(migrations.Migration):
             name='observation_strategy_template',
             field=models.ForeignKey(help_text='Observation Strategy Template used to create the requirements_doc.', null=True, on_delete=django.db.models.deletion.PROTECT, to='tmssapp.SchedulingUnitObservingStrategyTemplate'),
         ),
+        migrations.AddField(
+            model_name='schedulingunitdraft',
+            name='priority_queue',
+            field=models.ForeignKey(default='A', help_text='Priority queue of this scheduling unit. Queues provide a strict ordering between scheduling units.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.PriorityQueueType'),
+        ),
         migrations.AddField(
             model_name='schedulingunitdraft',
             name='requirements_template',
@@ -1193,6 +1216,11 @@ class Migration(migrations.Migration):
             name='draft',
             field=models.ForeignKey(help_text='Scheduling Unit Draft which this run instantiates.', on_delete=django.db.models.deletion.PROTECT, related_name='scheduling_unit_blueprints', to='tmssapp.SchedulingUnitDraft'),
         ),
+        migrations.AddField(
+            model_name='schedulingunitblueprint',
+            name='priority_queue',
+            field=models.ForeignKey(default='A', help_text='Priority queue of this scheduling unit. Queues provide a strict ordering between scheduling units.', on_delete=django.db.models.deletion.PROTECT, to='tmssapp.PriorityQueueType'),
+        ),
         migrations.AddField(
             model_name='schedulingunitblueprint',
             name='requirements_template',
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py
index 140b298db57..21c56bb84ca 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/specification.py
@@ -156,6 +156,13 @@ class TaskType(AbstractChoice):
         OTHER = 'other'
 
 
+class PriorityQueueType(AbstractChoice):
+    """Defines the possible priority queues for SchedulingUnits.
+    The items in the Choices class below are automagically populated into the database via a data migration."""
+    class Choices(Enum):
+        A = "A"
+        B = "B"
+
 # concrete models
 
 class Setting(BasicCommon):
@@ -387,6 +394,8 @@ class SchedulingUnitDraft(RefreshFromDbInvalidatesCachedPropertiesMixin, NamedCo
     scheduling_constraints_doc = JSONField(help_text='Scheduling Constraints for this run.', null=True)
     scheduling_constraints_template = ForeignKey('SchedulingConstraintsTemplate', on_delete=CASCADE, null=True, help_text='Schema used for scheduling_constraints_doc.')
     ingest_permission_required = BooleanField(default=False, help_text='Explicit permission is needed before the task.')
+    priority_rank = FloatField(null=False, default=0.0, help_text='Priority of this scheduling unit w.r.t. other scheduling units within the same queue and project.')
+    priority_queue = ForeignKey('PriorityQueueType', null=False, on_delete=PROTECT, default="A", help_text='Priority queue of this scheduling unit. Queues provide a strict ordering between scheduling units.')
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
         if self.requirements_doc is not None and self.requirements_template_id and self.requirements_template.schema is not None:
@@ -463,6 +472,8 @@ class SchedulingUnitBlueprint(RefreshFromDbInvalidatesCachedPropertiesMixin, Nam
     output_data_allowed_to_be_ingested = BooleanField(default=False, help_text='boolean (default FALSE), which blocks Ingest Tasks from starting if OFF. When toggled ON, backend must scan for startable Ingest Tasks.')
     output_pinned = BooleanField(default=False, help_text='boolean (default FALSE), which blocks deleting unpinned dataproducts. When toggled ON, backend must pick SUB up for deletion. It also must when dataproducts are unpinned.')
     results_accepted = BooleanField(default=False, help_text='boolean (default NULL), which records whether the results were accepted, allowing the higher-level accounting to be adjusted.')
+    priority_rank = FloatField(null=False, default=0.0, help_text='Priority of this scheduling unit w.r.t. other scheduling units within the same queue and project.')
+    priority_queue = ForeignKey('PriorityQueueType', null=False, on_delete=PROTECT, default="A", help_text='Priority queue of this scheduling unit. Queues provide a strict ordering between scheduling units.')
 
     def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
         annotate_validate_add_defaults_to_doc_using_template(self, 'requirements_doc', 'requirements_template')
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
index 13b671a4c57..684280c9ad3 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py
@@ -43,7 +43,7 @@ def populate_choices(apps, schema_editor):
     '''
     choice_classes = [Role, IOType, Datatype, Dataformat, CopyReason,
                       SubtaskState, SubtaskType, StationType, Algorithm, SchedulingRelationPlacement,
-                      Flag, ProjectCategory, PeriodCategory, Quantity, TaskType, ProjectRole]
+                      Flag, ProjectCategory, PeriodCategory, Quantity, TaskType, ProjectRole, PriorityQueueType]
 
     # upload choices in parallel
     with ThreadPoolExecutor() as executor:
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py
index 8e219472088..fc23e9e9424 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/serializers/specification.py
@@ -369,6 +369,12 @@ class TaskTypeSerializer(DynamicRelationalHyperlinkedModelSerializer):
         fields = '__all__'
 
 
+class PriorityQueueTypeSerializer(DynamicRelationalHyperlinkedModelSerializer):
+    class Meta:
+        model = models.PriorityQueueType
+        fields = '__all__'
+
+
 class ReservationStrategyTemplateSerializer(DynamicRelationalHyperlinkedModelSerializer):
     template = JSONEditorField(schema_source="reservation_template.schema")
 
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py
index 620742eaa77..49ddf7a0971 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/viewsets/specification.py
@@ -1081,3 +1081,8 @@ class TaskTypeViewSet(LOFARViewSet):
     queryset = models.TaskType.objects.all()
     serializer_class = serializers.TaskTypeSerializer
 
+
+class PriorityQueueTypeViewSet(LOFARViewSet):
+    queryset = models.PriorityQueueType.objects.all()
+    serializer_class = serializers.PriorityQueueTypeSerializer
+
diff --git a/SAS/TMSS/backend/src/tmss/urls.py b/SAS/TMSS/backend/src/tmss/urls.py
index a73ba904d3a..e45c9db4013 100644
--- a/SAS/TMSS/backend/src/tmss/urls.py
+++ b/SAS/TMSS/backend/src/tmss/urls.py
@@ -126,6 +126,7 @@ router.register(r'period_category', viewsets.PeriodCategoryViewSet)
 router.register(r'project_category', viewsets.ProjectCategoryViewSet)
 router.register(r'quantity', viewsets.QuantityViewSet)
 router.register(r'task_type', viewsets.TaskTypeViewSet)
+router.register(r'priority_queue_type', viewsets.PriorityQueueTypeViewSet)
 
 # templates
 router.register(r'common_schema_template', viewsets.CommonSchemaTemplateViewSet)
-- 
GitLab