diff --git a/SAS/TMSS/backend/src/tmss/exceptions.py b/SAS/TMSS/backend/src/tmss/exceptions.py index 9f3ef180834107cc187a7c8f552f6ebd03486df2..dbf961049ef18fd0c041d7be9673bdd581a3adf6 100644 --- a/SAS/TMSS/backend/src/tmss/exceptions.py +++ b/SAS/TMSS/backend/src/tmss/exceptions.py @@ -32,6 +32,9 @@ class SubtaskSchedulingException(SubtaskException, SchedulingException): class SubtaskSchedulingSpecificationException(SubtaskSchedulingException): pass +class SubtaskSchedulingPermissionException(SubtaskSchedulingException): + pass + class TaskSchedulingException(SchedulingException): pass diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0009_populate_subtask_allowed_state_transitions_extra.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0009_populate_subtask_allowed_state_transitions_extra.py new file mode 100644 index 0000000000000000000000000000000000000000..f8b5cb170bc1ea387d1eee09aca36b8f662ebfae --- /dev/null +++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0009_populate_subtask_allowed_state_transitions_extra.py @@ -0,0 +1,14 @@ + + +from django.db import migrations + +from lofar.sas.tmss.tmss.tmssapp.populate import * + +class Migration(migrations.Migration): + + dependencies = [ + ('tmssapp', '0008_populate_systemevent'), + ] + + operations = [migrations.RunPython(populate_subtask_allowed_state_transitions_extra)] + diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py index 629626766cc90f5e21e9398633b7bcc4b47ebf2c..6665dee08d582240a975f75abdd5ab1293f94d73 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py @@ -169,10 +169,8 @@ def populate_subtask_allowed_state_transitions(apps, schema_editor): SubtaskAllowedStateTransitions(old_state=None, new_state=DEFINING), SubtaskAllowedStateTransitions(old_state=DEFINING, new_state=DEFINED), SubtaskAllowedStateTransitions(old_state=DEFINED, new_state=SCHEDULING), - SubtaskAllowedStateTransitions(old_state=DEFINED, new_state=UNSCHEDULABLE), SubtaskAllowedStateTransitions(old_state=SCHEDULING, new_state=SCHEDULED), SubtaskAllowedStateTransitions(old_state=SCHEDULING, new_state=UNSCHEDULABLE), - SubtaskAllowedStateTransitions(old_state=UNSCHEDULABLE, new_state=DEFINED), SubtaskAllowedStateTransitions(old_state=SCHEDULED, new_state=STARTING), # this is an odd one, as most (all?) subtasks are queued before execution... SubtaskAllowedStateTransitions(old_state=SCHEDULED, new_state=QUEUEING), SubtaskAllowedStateTransitions(old_state=SCHEDULED, new_state=UNSCHEDULING), @@ -201,6 +199,21 @@ def populate_subtask_allowed_state_transitions(apps, schema_editor): SubtaskAllowedStateTransitions(old_state=FINISHING, new_state=CANCELLING) # in case feedback is not arriving, a user might choose to cancel a subtask even in this stage ]) +def populate_subtask_allowed_state_transitions_extra(apps, schema_editor): + '''populate the SubtaskAllowedStateTransitions table with additional allowed state transitions''' + DEFINED = SubtaskState.objects.get(value=SubtaskState.Choices.DEFINED.value) + CANCELLING = SubtaskState.objects.get(value=SubtaskState.Choices.CANCELLING.value) + ERROR = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value) + UNSCHEDULABLE = SubtaskState.objects.get(value=SubtaskState.Choices.UNSCHEDULABLE.value) + + SubtaskAllowedStateTransitions.objects.bulk_create([ + SubtaskAllowedStateTransitions(old_state=DEFINED, new_state=UNSCHEDULABLE), + SubtaskAllowedStateTransitions(old_state=DEFINED, new_state=ERROR), + SubtaskAllowedStateTransitions(old_state=UNSCHEDULABLE, new_state=DEFINED), + SubtaskAllowedStateTransitions(old_state=UNSCHEDULABLE, new_state=CANCELLING), + SubtaskAllowedStateTransitions(old_state=UNSCHEDULABLE, new_state=ERROR) + ]) + def populate_settings(apps, schema_editor): Setting.objects.create(name=SystemSettingFlag.objects.get(value='dynamic_scheduling_enabled'), value=isDevelopmentEnvironment()) diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py index 2ac41109773f9e3f823e3b6602f765774b2aa1e2..931bb1e0dd41ace8ff8ae9176932a442cfe669fa 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py @@ -993,6 +993,12 @@ def schedule_subtask(subtask: Subtask) -> Subtask: # set the subtask to state 'UNSCHEDULABLE' in case of a specification exception subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.UNSCHEDULABLE.value) subtask.save() + elif isinstance(e, SubtaskSchedulingPermissionException): + # we need some kind of permission to schedule this subtask + # ingest for example needs to get approval from the user. + # so, just wait for the user to give permission and try again. + # keep subtask state in DEFINED + pass else: # set the subtask to state 'ERROR'. subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value) @@ -1930,7 +1936,7 @@ def schedule_ingest_subtask(ingest_subtask: Subtask): scheduling_unit_blueprint = ingest_subtask.task_blueprint.scheduling_unit_blueprint # first() is fine because we assume an ingest subtask does not serve tasks across SU boundaries if scheduling_unit_blueprint.ingest_permission_required: if scheduling_unit_blueprint.ingest_permission_granted_since is None or scheduling_unit_blueprint.ingest_permission_granted_since > datetime.utcnow(): - raise SubtaskSchedulingException("Cannot schedule ingest subtask id=%d because it requires explicit permission and the permission has not been granted (yet)" % (ingest_subtask.pk,)) + raise SubtaskSchedulingPermissionException("Cannot schedule ingest subtask id=%d because it requires explicit permission and the permission has not been granted (yet)" % (ingest_subtask.pk,)) # step 1: set state to SCHEDULING ingest_subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.SCHEDULING.value) diff --git a/SAS/TMSS/backend/test/t_subtasks.py b/SAS/TMSS/backend/test/t_subtasks.py index 7f59d4fc4d911b06e9924b7d589b1d83bd541e15..d130aea001dd733fb99e1e884cb815cdfbacf6d2 100755 --- a/SAS/TMSS/backend/test/t_subtasks.py +++ b/SAS/TMSS/backend/test/t_subtasks.py @@ -729,6 +729,7 @@ class SubtaskAllowedStateTransitionsTest(unittest.TestCase): for state_value in (SubtaskState.Choices.DEFINING.value, SubtaskState.Choices.DEFINED.value, SubtaskState.Choices.SCHEDULING.value, + SubtaskState.Choices.UNSCHEDULABLE.value, SubtaskState.Choices.SCHEDULED.value, SubtaskState.Choices.QUEUEING.value, SubtaskState.Choices.QUEUED.value, @@ -747,6 +748,7 @@ class SubtaskAllowedStateTransitionsTest(unittest.TestCase): for intermediate_state_value in (SubtaskState.Choices.DEFINING.value, SubtaskState.Choices.SCHEDULING.value, SubtaskState.Choices.UNSCHEDULING.value, + SubtaskState.Choices.UNSCHEDULABLE.value, SubtaskState.Choices.QUEUEING.value, SubtaskState.Choices.STARTING.value, SubtaskState.Choices.STARTED.value, @@ -776,6 +778,7 @@ class SubtaskAllowedStateTransitionsTest(unittest.TestCase): def test_helper_method_set_subtask_state_following_allowed_transitions_cancel_path(self): for desired_end_state_value in (SubtaskState.Choices.CANCELLING.value,SubtaskState.Choices.CANCELLED.value): for state_value in (SubtaskState.Choices.DEFINED.value, + SubtaskState.Choices.UNSCHEDULABLE.value, SubtaskState.Choices.SCHEDULED.value, SubtaskState.Choices.QUEUED.value, SubtaskState.Choices.STARTED.value): diff --git a/SAS/TMSS/backend/test/t_tasks.py b/SAS/TMSS/backend/test/t_tasks.py index 63ab80d6203ecc66f3902f74b5420f10b8bb829a..1a127c3da5b53ee9b62b4d4130120527bb315e57 100755 --- a/SAS/TMSS/backend/test/t_tasks.py +++ b/SAS/TMSS/backend/test/t_tasks.py @@ -305,12 +305,23 @@ class TaskBlueprintStateTest(unittest.TestCase): ("finished", "finished") ], [ ("defining", "defined"), - ("error", "error") + ("error", "error") + ], [ + ("defining", "defined"), + ("defined", "schedulable"), + ("unschedulable","unschedulable"), + ("error", "error") ], [ ("defining", "defined"), ("defined", "schedulable"), ("cancelling", "cancelled"), ("cancelled", "cancelled") + ], [ + ("defining", "defined"), + ("defined", "schedulable"), + ("unschedulable","unschedulable"), + ("cancelling", "cancelled"), + ("cancelled", "cancelled") ]] for test_table in test_tables: @@ -356,7 +367,14 @@ class TaskBlueprintStateTest(unittest.TestCase): ], [ ("cancelling", "defined", "cancelled"), ("cancelled", "defined", "cancelled") - ] , [ + ], [ + ("unschedulable", "defined", "unschedulable"), + ("cancelling", "defined", "cancelled"), + ("cancelled", "defined", "cancelled"), + ], [ + ("error", "defined", "error") + ], [ + ("unschedulable","defined", "unschedulable"), ("error", "defined", "error") ], [ # qa finishing/finished should be not observed diff --git a/SAS/TMSS/backend/test/test_utils.py b/SAS/TMSS/backend/test/test_utils.py index 86ac97ee5d768f26f1ce624b8e9b9c7d18148e0f..9c8fe9aedcbabac601e3d9e8fee0ed30cdd59fdc 100644 --- a/SAS/TMSS/backend/test/test_utils.py +++ b/SAS/TMSS/backend/test/test_utils.py @@ -109,6 +109,7 @@ def set_subtask_state_following_allowed_transitions(subtask: typing.Union[Subtas SubtaskState.Choices.QUEUEING.value, SubtaskState.Choices.STARTING.value, SubtaskState.Choices.FINISHING.value, + SubtaskState.Choices.UNSCHEDULABLE.value, SubtaskState.Choices.CANCELLING.value): subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.ERROR.value) subtask.error_reason = 'set_subtask_state_following_allowed_transitions'