diff --git a/SAS/TMSS/backend/test/t_reports.py b/SAS/TMSS/backend/test/t_reports.py index bedd0bedca50a69570ceaf7f381c187c19c54e35..2c2c591907444221d88b5889e23c0b3d7e138132 100755 --- a/SAS/TMSS/backend/test/t_reports.py +++ b/SAS/TMSS/backend/test/t_reports.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 - +import datetime # Copyright (C) 2018 ASTRON (Netherlands Institute for Radio Astronomy) # P.O. Box 2, 7990 AA Dwingeloo, The Netherlands # @@ -21,6 +21,7 @@ import os import time import unittest import requests +from datetime import datetime import logging logger = logging.getLogger('lofar.'+__name__) @@ -57,6 +58,9 @@ from lofar.common.json_utils import get_default_json_object_for_schema from dateutil.relativedelta import relativedelta from lofar.sas.tmss.test.test_utils import set_subtask_state_following_allowed_transitions +from django.contrib.auth import get_user_model +User = get_user_model() + class ReportTest(unittest.TestCase): @classmethod @@ -183,7 +187,7 @@ class ReportTest(unittest.TestCase): set_subtask_state_following_allowed_transitions(subtask, status) @classmethod - def _create_simple_finished_observation_SUB_for_project(cls, project:models.Project, duration=600, start_time=None, accepted=None) -> None: + def _create_simple_finished_observation_SUB_for_project(cls, project:models.Project, duration=600, start_time=None, accepted=None) -> models.SchedulingUnitBlueprint: """ Help method to create a SUB with an observation with a default duration of 600s set to finished. """ @@ -226,6 +230,7 @@ class ReportTest(unittest.TestCase): dataformat=models.Dataformat.objects.get(value="Beamformed"), datatype=models.Datatype.objects.get(value="time series"))) cls._set_dataproducts_size_and_set_subtask_status(obs_subtask) + return su_blueprint @classmethod def setup_project_report(cls, project:models.Project) -> None: @@ -571,6 +576,100 @@ class ReportTest(unittest.TestCase): # Assert contains_overlapping_observations is False self.assertEqual(result['contains_overlapping_observations'], False) +class FailureReportTest(unittest.TestCase): + + def test_create_failure_report(self): + # setup, create project with three SUs, each containing single observation + project = models.Project.objects.create(**Project_test_data(name='failure_report_project')) + su_1 = ReportTest._create_simple_finished_observation_SUB_for_project(project=project, + duration=6666, + start_time=datetime(2023, 1, 31, 23, 59)) + su_2 = ReportTest._create_simple_finished_observation_SUB_for_project(project=project, + duration=3333, + start_time=datetime(2023, 3, 31, 23, 59)) + su_3 = ReportTest._create_simple_finished_observation_SUB_for_project(project=project, + duration=4444, + start_time=datetime(2023, 5, 31, 23, 59)) + + # setup, add system events, each affecting one of the SUs + template = models.SystemEventTemplate.get_latest(name="affectedhardware") + event_1 = models.SystemEvent.objects.create(name='event 1', + issue_type=models.SystemEventType.objects.get(value=models.SystemEventType.Choices.CEP.value), + issue_subtype=models.SystemEventSubtype.objects.get(value=models.SystemEventSubtype.Choices.CRASH.value), + severity=models.SystemEventSeverity.objects.get(value=models.SystemEventSeverity.Choices.FAILURE.value), + start=datetime(year=2023, month=1, day=31, hour=23), + stop=datetime(year=2023, month=2, day=1, hour=2), + affected_hardware_doc={}, + affected_hardware_template=template, + created_by=User.objects.first(), + status=models.SystemEventStatus.objects.get(value=models.SystemEventStatus.Choices.OPEN.value), + ) + event_1.affected_tasks.set(su_1.task_blueprints.all()) + event_1.save() + + event_2 = models.SystemEvent.objects.create(name='event 2', + issue_type=models.SystemEventType.objects.get(value=models.SystemEventType.Choices.HUMAN.value), + issue_subtype=models.SystemEventSubtype.objects.get(value=models.SystemEventSubtype.Choices.DATALOSS.value), + severity=models.SystemEventSeverity.objects.get(value=models.SystemEventSeverity.Choices.FAILURE.value), + start=datetime(year=2023, month=3, day=31, hour=23), + stop=datetime(year=2023, month=4, day=1, hour=2), + affected_hardware_doc={}, + affected_hardware_template=template, + created_by=User.objects.first(), + status=models.SystemEventStatus.objects.get(value=models.SystemEventStatus.Choices.ANALYSED.value), + ) + event_2.affected_tasks.set(su_2.task_blueprints.all()) + event_2.save() + + + event_3 = models.SystemEvent.objects.create(name='event 3', + issue_type=models.SystemEventType.objects.get(value=models.SystemEventType.Choices.HUMAN.value), + issue_subtype=models.SystemEventSubtype.objects.get(value=models.SystemEventSubtype.Choices.NOISY.value), + severity=models.SystemEventSeverity.objects.get(value=models.SystemEventSeverity.Choices.MAJOR.value), + start=datetime(year=2023, month=5, day=31, hour=23), + stop=datetime(year=2023, month=6, day=1, hour=2), + affected_hardware_doc={}, + affected_hardware_template=template, + created_by=User.objects.first(), + status=models.SystemEventStatus.objects.get(value=models.SystemEventStatus.Choices.CLOSED.value), + ) + event_3.affected_tasks.set(su_3.task_blueprints.all()) + event_3.save() + + + # generate the report + with tmss_test_env.create_tmss_client() as client: + start = datetime(year=2023, month=1, day=1) + stop = datetime(year=2024, month=1, day=1) + report = client.get_path_as_json_object('/failure_report/?start=%s&stop=%s' % (start.isoformat(), stop.isoformat())) + + # assertions, SUs 1 and 2 should be in here, SU 3 should be ignored due to lack of severity + # check grand total + self.assertIn('total_duration_lost', report) + self.assertEqual(report['total_duration_lost'], 21600) # 2 failure events of 3 hours each + self.assertAlmostEqual(report['total_percent_of_wall_time_lost'], 0.0684931506849315) + + # check aggregates + self.assertIn('human', str(report['system_event_summary']['by_issue_type'])) # event 1 + for d in report['system_event_summary']['by_issue_type']: + self.assertEqual(d['count'], 1) # only event 1 (but not 3) is accounted for + self.assertNotIn('noisy', str(report['system_event_summary']['by_issue_subtype'])) # event 3 missing + + # check histogram (events get binned correctly?) + self.assertEqual(report['lost_observing_time_histogram']['2023-01-01'], 3600) # event 1 + self.assertEqual(report['lost_observing_time_histogram']['2023-02-01'], 7200) # event 1 + self.assertEqual(report['lost_observing_time_histogram']['2023-03-01'], 3600) # event 2 + self.assertEqual(report['lost_observing_time_histogram']['2023-04-01'], 7200) # event 2 + self.assertNotIn('2023-05-01', report['lost_observing_time_histogram']) # event 3 missing + self.assertNotIn('2023-06-01', report['lost_observing_time_histogram']) # event 3 missing + + # check SUs + self.assertEqual(len(report['failed_scheduling_units']), 2) + for unit in report['failed_scheduling_units']: + self.assertIn(unit['on_sky_duration'], [6666, 3333]) + self.assertEqual(unit['project'], 'failure_report_project') + self.assertEqual(len(unit['subtasks']), 1) + if __name__ == "__main__": os.environ['TZ'] = 'UTC'