diff --git a/SAS/TMSS/src/remakemigrations.py b/SAS/TMSS/src/remakemigrations.py index 6a4ee430ffd683388eb4c0ba5523dfc4d89d4c39..86d8f07c6e91a29cb8a89d67d06cd6c24a990411 100755 --- a/SAS/TMSS/src/remakemigrations.py +++ b/SAS/TMSS/src/remakemigrations.py @@ -75,6 +75,11 @@ class Migration(migrations.Migration): # Start SubTask id with 2 000 000 to avoid overlap with 'old' (test/production) OTDB operations = [ migrations.RunSQL('ALTER SEQUENCE tmssapp_SubTask_id_seq RESTART WITH 2000000;'), + migrations.RunSQL('DROP VIEW IF EXISTS tmssapp_taskblueprintsummary; ' + 'CREATE OR REPLACE VIEW tmssapp_taskblueprintsummary AS ' + 'SELECT tmssapp_taskblueprint.id AS taskblueprint_id, tmssapp_subtask.id AS subtask_id, tmssapp_subtask.state_id AS substate, tmssapp_subtasktemplate.type_id AS subtask_type' + ' FROM tmssapp_subtask LEFT JOIN tmssapp_taskblueprint ON tmssapp_taskblueprint.id = tmssapp_subtask.task_blueprint_id' + ' LEFT JOIN tmssapp_subtasktemplate ON tmssapp_subtasktemplate.id = tmssapp_subtask.specifications_template_id;'), migrations.RunPython(populate_choices), migrations.RunPython(populate_settings), migrations.RunPython(populate_misc), diff --git a/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py index 92baffd4c15a8c025d234eeffed61ae9f443fabf..89390eddeed337b2210f88f827034711f75a3a47 100644 --- a/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py +++ b/SAS/TMSS/src/tmss/tmssapp/migrations/0002_populate.py @@ -16,6 +16,11 @@ class Migration(migrations.Migration): # Start SubTask id with 2 000 000 to avoid overlap with 'old' (test/production) OTDB operations = [ migrations.RunSQL('ALTER SEQUENCE tmssapp_SubTask_id_seq RESTART WITH 2000000;'), + migrations.RunSQL('DROP VIEW IF EXISTS tmssapp_taskblueprintsummary; ' + 'CREATE OR REPLACE VIEW tmssapp_taskblueprintsummary AS ' + 'SELECT tmssapp_taskblueprint.id AS taskblueprint_id, tmssapp_subtask.id AS subtask_id, tmssapp_subtask.state_id AS substate, tmssapp_subtasktemplate.type_id AS subtask_type' + ' FROM tmssapp_subtask LEFT JOIN tmssapp_taskblueprint ON tmssapp_taskblueprint.id = tmssapp_subtask.task_blueprint_id' + ' LEFT JOIN tmssapp_subtasktemplate ON tmssapp_subtasktemplate.id = tmssapp_subtask.specifications_template_id;'), migrations.RunPython(populate_choices), migrations.RunPython(populate_settings), migrations.RunPython(populate_misc), diff --git a/SAS/TMSS/src/tmss/tmssapp/models/specification.py b/SAS/TMSS/src/tmss/tmssapp/models/specification.py index 357a1ae43ab549751af1e48c70c9872e974491e8..9409d02ad795ebba42c6da85046af79f9cd6f710 100644 --- a/SAS/TMSS/src/tmss/tmssapp/models/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/models/specification.py @@ -359,11 +359,25 @@ class DefaultTaskTemplate(BasicCommon): class TaskRelationSelectionTemplate(Template): pass + class DefaultTaskRelationSelectionTemplate(BasicCommon): name = CharField(max_length=128, unique=True) template = ForeignKey("TaskRelationSelectionTemplate", on_delete=PROTECT) +# +# DatabaseView objects +# +class TaskBlueprintSummary(Model): + taskblueprint_id = IntegerField() + subtask_id = IntegerField() + substate = CharField(max_length=128) + subtask_type = CharField(max_length=128) + + class Meta: + managed = False + db_table = 'tmssapp_taskblueprintsummary' + # # Instance Objects # @@ -808,6 +822,36 @@ class TaskBlueprint(NamedCommon): else: return None + @property + def status(self): + """ + Return the taskblueprint status which is derived from the subtasks status + See https://support.astron.nl/confluence/display/TMSS/Specification+Flow#SpecificationFlow-TaskBlueprints + The status is displayed as extra field in rest api of the taskblueprint + """ + total_nbr_subtasks = self.subtasks.all().count() + total_nbr_observation_subtasks = TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, subtask_type='observation').count() + + if total_nbr_subtasks == 0 or \ + TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, substate='defining').count() > 0: + status = "defined" + elif TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, substate='finished').count() == total_nbr_subtasks: + status = "finished" + elif TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, substate__in=('cancelling','cancelled')).count() > 0: + status = "cancelled" + elif TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, substate='error').count() > 0: + status = "error" + elif TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, substate__in=('finishing','finished'), subtask_type='observation').count() == total_nbr_observation_subtasks \ + and total_nbr_observation_subtasks > 0: + status = "observed" + elif TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, substate__in=('starting','started','queueing','queued','finishing','finished')).count() > 0: + status = "started" + elif TaskBlueprintSummary.objects.filter(taskblueprint_id=self.id, substate='scheduled').count() > 0: + status = "scheduled" + else: + status = "schedulable" + return status + class TaskRelationDraft(BasicCommon): selection_doc = JSONField(help_text='Filter for selecting dataproducts from the output role.') diff --git a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py index 951340e2ed08c9d6568579f58e5d10b062ec97c6..4ef2c8936688f0714e944b7f5a9af147dd3e34df 100644 --- a/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py +++ b/SAS/TMSS/src/tmss/tmssapp/serializers/specification.py @@ -334,7 +334,8 @@ class TaskBlueprintSerializer(RelationalHyperlinkedModelSerializer): class Meta: model = models.TaskBlueprint fields = '__all__' - extra_fields = ['subtasks', 'produced_by', 'consumed_by', 'first_to_connect', 'second_to_connect', 'duration', 'start_time', 'stop_time', 'relative_start_time', 'relative_stop_time'] + extra_fields = ['subtasks', 'produced_by', 'consumed_by', 'first_to_connect', 'second_to_connect', 'duration', + 'start_time', 'stop_time', 'relative_start_time', 'relative_stop_time', 'status'] class TaskRelationDraftSerializer(RelationalHyperlinkedModelSerializer): diff --git a/SAS/TMSS/test/t_subtasks.py b/SAS/TMSS/test/t_subtasks.py index 17210063f2e24e31a19a3a1f05edee0375c409d7..ca94d9eb33be0377cbbd5aa1d3a3cb3623be411f 100755 --- a/SAS/TMSS/test/t_subtasks.py +++ b/SAS/TMSS/test/t_subtasks.py @@ -115,6 +115,8 @@ def create_scheduling_relation_task_blueprint_for_testing(first_task_blueprint, placement=models.SchedulingRelationPlacement.objects.get(value='before'), time_offset=60) return task_scheduling_rel_obj + + class SubTasksCreationFromSubTask(unittest.TestCase): def test_create_qafile_subtask_from_observation_subtask_failed(self): diff --git a/SAS/TMSS/test/t_tasks.py b/SAS/TMSS/test/t_tasks.py index ae878f68ad6712aab49ab8d974d4aa8a1416712f..498552d07bfe7776e3350e3f6617fd5988df60fc 100755 --- a/SAS/TMSS/test/t_tasks.py +++ b/SAS/TMSS/test/t_tasks.py @@ -255,6 +255,69 @@ class CreationFromTaskDraft(unittest.TestCase): self.assertEqual(0, task_blueprint.subtasks.count()) +class TaskBlueprintStateTest(unittest.TestCase): + """ + Test the Task Blueprint State which is derived from the SubTask states. + The result of each possible combination of these states will be checked (using 2 subtasks) + See https://support.astron.nl/confluence/display/TMSS/Specification+Flow#SpecificationFlow-TaskBlueprints + """ + + def test_state_whith_no_subtasks(self): + """ + Test the taskblueprint state when subtasks are not instantiated. + the expected state should be 'defined' + """ + task_blueprint_data = TaskBlueprint_test_data(name="Task Blueprint No Subtasks") + task_blueprint = models.TaskBlueprint.objects.create(**task_blueprint_data) + self.assertEqual("defined", task_blueprint.status) + + def test_states_with_observation_and_pipeline_subtask(self): + """ + Test the taskblueprint state when two subtasks are instantiated, an observation and a pipeline. + See next table where every row represents: + Substate(Obs), Substate(Pipeline), Expected TaskBlueprint State + """ + test_table = [ + ("defining", "defining", "defined"), + ("defining", "defined", "defined"), + ("defined", "defined", "schedulable"), + ("scheduling", "defined", "schedulable"), + ("scheduled", "defined", "scheduled"), + ("starting", "defined", "started"), + ("started", "defined", "started"), + ("queueing", "defined", "started"), + ("queued", "defined", "started"), + ("finishing", "defined", "observed"), + ("finished", "defined", "observed"), + ("defined", "finishing", "started"), # The difference is here ...not observed + ("defined", "finished", "started"), # The difference is here ...not observed + ("finished", "finished", "finished"), + ("cancelling", "defined", "cancelled"), + ("cancelled", "defined", "cancelled"), + ("error", "defined", "error") + ] + # Create taskblueprint + task_blueprint_data = TaskBlueprint_test_data(name="Task Blueprint With Subtasks") + task_blueprint = models.TaskBlueprint.objects.create(**task_blueprint_data) + # Create observation and pipeline subtask related to taskblueprint + subtask_data = Subtask_test_data(task_blueprint, state=models.SubtaskState.objects.get(value="defined"), + subtask_template=models.SubtaskTemplate.objects.get(name='observation control')) + subtask_obs = models.Subtask.objects.create(**subtask_data) + subtask_data = Subtask_test_data(task_blueprint, state=models.SubtaskState.objects.get(value="defined"), + subtask_template=models.SubtaskTemplate.objects.get(name='pipeline control')) + subtask_pipe = models.Subtask.objects.create(**subtask_data) + + # Do the actual test + for test_item in test_table: + state_obs, state_pipe, expected_task_state = test_item + logger.info("Expected test result of substates observation='%s' and pipeline='%s' should be '%s'" % (state_obs, state_pipe, expected_task_state)) + subtask_obs.state = models.SubtaskState.objects.get(value=state_obs) + subtask_obs.save() + subtask_pipe.state = models.SubtaskState.objects.get(value=state_pipe) + subtask_pipe.save() + self.assertEqual(expected_task_state, task_blueprint.status) + + if __name__ == "__main__": os.environ['TZ'] = 'UTC' unittest.main()