diff --git a/.gitattributes b/.gitattributes index fc08600221760329c77f34bc1029e5883458ca02..bce0e9b734e849ec3a1c32e952a2cd05f953d272 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1806,8 +1806,8 @@ LCU/Maintenance/DBInterface/monitoringdb/models/component.py -text LCU/Maintenance/DBInterface/monitoringdb/models/component_error.py -text LCU/Maintenance/DBInterface/monitoringdb/models/element.py -text LCU/Maintenance/DBInterface/monitoringdb/models/element_error.py -text -LCU/Maintenance/DBInterface/monitoringdb/models/error_details.py -text LCU/Maintenance/DBInterface/monitoringdb/models/fixed_types.py -text +LCU/Maintenance/DBInterface/monitoringdb/models/log.py -text LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py -text LCU/Maintenance/DBInterface/monitoringdb/models/station.py -text LCU/Maintenance/DBInterface/monitoringdb/models/station_test.py -text @@ -1818,7 +1818,7 @@ LCU/Maintenance/DBInterface/monitoringdb/serializers/component.py -text LCU/Maintenance/DBInterface/monitoringdb/serializers/component_error.py -text LCU/Maintenance/DBInterface/monitoringdb/serializers/element.py -text LCU/Maintenance/DBInterface/monitoringdb/serializers/element_error.py -text -LCU/Maintenance/DBInterface/monitoringdb/serializers/error_details.py -text +LCU/Maintenance/DBInterface/monitoringdb/serializers/log.py -text LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py -text LCU/Maintenance/DBInterface/monitoringdb/serializers/station.py -text LCU/Maintenance/DBInterface/monitoringdb/serializers/station_tests.py -text @@ -1827,6 +1827,7 @@ LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py -text LCU/Maintenance/DBInterface/monitoringdb/tests/__init__.py -text LCU/Maintenance/DBInterface/monitoringdb/tests/common.py -text LCU/Maintenance/DBInterface/monitoringdb/tests/old_tests.py -text +LCU/Maintenance/DBInterface/monitoringdb/tests/t_django_insert.py -text LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_model_creation.py -text LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_parser.py -text LCU/Maintenance/DBInterface/monitoringdb/tests/test_generic_stationtestdata.csv -text @@ -1834,6 +1835,8 @@ LCU/Maintenance/DBInterface/monitoringdb/tests/test_multiple_tileerror_and_rcuer LCU/Maintenance/DBInterface/monitoringdb/urls.py -text LCU/Maintenance/DBInterface/monitoringdb/views/__init__.py -text LCU/Maintenance/DBInterface/monitoringdb/views/common.py -text +LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py -text +LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py -text LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py -text LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py -text LCU/Maintenance/MDB_WebView/CMakeLists.txt -text diff --git a/LCU/Maintenance/DBInterface/django_postgresql/settings.py b/LCU/Maintenance/DBInterface/django_postgresql/settings.py index b481e8f78fcfd933d35f5ffbdd3e1a0ff4211223..be5844ac85b5ee6180fa14a38f69937533dd2184 100644 --- a/LCU/Maintenance/DBInterface/django_postgresql/settings.py +++ b/LCU/Maintenance/DBInterface/django_postgresql/settings.py @@ -106,6 +106,10 @@ LOGGING = { 'serializers': { 'handlers': ['console'], 'level': 'DEBUG', + }, + 'controllers': { + 'handlers': ['console'], + 'level': 'DEBUG' } }, } @@ -121,7 +125,11 @@ DATABASES = { 'PASSWORD': 'fix-me', 'HOST': 'localhost', 'PORT': '', + 'TEST': { + 'NAME': 'test_monitoringdb' + } } + } # Password validation diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/component_error.py b/LCU/Maintenance/DBInterface/monitoringdb/models/component_error.py index ead4319aeed55a3cd51a267605d9576f83822d4e..de50e7bce8a361568d225af3448adc8163b0bf5d 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/models/component_error.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/models/component_error.py @@ -1,7 +1,7 @@ from django.db import models from .component import Component from .station_test import StationTest -from .error_details import ErrorDetails +from django.contrib.postgres.fields import JSONField class ComponentError(models.Model): @@ -9,7 +9,4 @@ class ComponentError(models.Model): component = models.ForeignKey(Component, on_delete=models.DO_NOTHING) station_test = models.ForeignKey(StationTest, on_delete=models.DO_NOTHING, related_name='component_errors') - details = models.ForeignKey(ErrorDetails, - on_delete=models.CASCADE, - related_name='details', - null=True) \ No newline at end of file + details = JSONField(default=dict) \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/element_error.py b/LCU/Maintenance/DBInterface/monitoringdb/models/element_error.py index 5200e709481f5321e0ef08c6a62157c95d75d232..a7a14dcf547a2f19972564861b8c98ffed6c57e0 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/models/element_error.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/models/element_error.py @@ -1,7 +1,7 @@ from django.db import models from .element import Element -from .error_details import ErrorDetails -from .station_test import StationTest +from django.contrib.postgres.fields import JSONField + from .component_error import ComponentError @@ -12,4 +12,4 @@ class ElementError(models.Model): related_name='failing_elements') type = models.CharField(max_length=50) - details = models.ForeignKey(ErrorDetails, on_delete=models.CASCADE, related_name='element_error_details') \ No newline at end of file + details = JSONField(default=dict) \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/error_details.py b/LCU/Maintenance/DBInterface/monitoringdb/models/error_details.py deleted file mode 100644 index ed14ff246a8b36a22e2d71ed57a5ac6b1f6b86e3..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/DBInterface/monitoringdb/models/error_details.py +++ /dev/null @@ -1,73 +0,0 @@ -from django.db import models - - -class ErrorDetails(models.Model): - x = models.NullBooleanField() - y = models.NullBooleanField() - - xval = models.FloatField(default=None, null=True) - yval = models.FloatField(default=None, null=True) - - xoff = models.FloatField(default=None, null=True) - yoff = models.FloatField(default=None, null=True) - - xval_no_delay = models.FloatField(default=None, null=True) - yval_no_delay = models.FloatField(default=None, null=True) - - xsb_no_delay = models.FloatField(default=None, null=True) - ysb_no_delay = models.FloatField(default=None, null=True) - - xref_no_delay = models.FloatField(default=None, null=True) - yref_no_delay = models.FloatField(default=None, null=True) - - xref = models.FloatField(default=None, null=True) - yref = models.FloatField(default=None, null=True) - - xsb_full_delay = models.FloatField(default=None, null=True) - ysb_full_delay = models.FloatField(default=None, null=True) - - xref_full_delay = models.FloatField(default=None, null=True) - yref_full_delay = models.FloatField(default=None, null=True) - - xval_full_delay = models.FloatField(default=None, null=True) - yval_full_delay = models.FloatField(default=None, null=True) - - xlimit = models.FloatField(default=None, null=True) - ylimit = models.FloatField(default=None, null=True) - - xproc = models.FloatField(default=None, null=True) - yproc = models.FloatField(default=None, null=True) - - xdiff = models.FloatField(default=None, null=True) - ydiff = models.FloatField(default=None, null=True) - - xmean = models.FloatField(default=None, null=True) - ymean = models.FloatField(default=None, null=True) - - tp = models.CharField(max_length=10, null=True, default=None) - mp = models.CharField(max_length=10, null=True, default=None) - ap = models.CharField(max_length=10, null=True, default=None) - bp = models.CharField(max_length=10, null=True, default=None) - - mp0 = models.CharField(max_length=10, null=True, default=None) - mp1 = models.CharField(max_length=10, null=True, default=None) - mp2 = models.CharField(max_length=10, null=True, default=None) - mp3 = models.CharField(max_length=10, null=True, default=None) - - ap0 = models.CharField(max_length=10, null=True, default=None) - ap1 = models.CharField(max_length=10, null=True, default=None) - ap2 = models.CharField(max_length=10, null=True, default=None) - ap3 = models.CharField(max_length=10, null=True, default=None) - - pcb = models.CharField(max_length=10, null=True, default=None) - - rcu5_0v = models.FloatField(default=None, null=True) - lba8_0v = models.FloatField(default=None, null=True) - hba48v = models.FloatField(default=None, null=True) - spu3_3v = models.FloatField(default=None, null=True) - - v1_2 = models.FloatField(default=None, null=True) - v2_5 = models.FloatField(default=None, null=True) - v3_3 = models.FloatField(default=None, null=True) - - error_code = models.CharField(max_length=10, null=True, default=None) \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/log.py b/LCU/Maintenance/DBInterface/monitoringdb/models/log.py new file mode 100644 index 0000000000000000000000000000000000000000..de8ba434bb0be45db43f9509bf7816328fbe6310 --- /dev/null +++ b/LCU/Maintenance/DBInterface/monitoringdb/models/log.py @@ -0,0 +1,9 @@ +import django.db.models as models + + +class ActionLog(models.Model): + entry_id = models.IntegerField(help_text='database id of the concerned object') + model_name = models.CharField(max_length=100) + who = models.CharField(max_length=100) + when = models.DateTimeField(auto_now_add=True) + what = models.CharField(max_length=1000) diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py b/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py index 4bf1a1cc149498529728b1c79531a06658c0f10b..860269b9b9b23b8d8888695f9bd404aa154dc0f2 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/models/rtsm.py @@ -3,7 +3,7 @@ In this file all the model regarding the real time station monitor output are co """ from django.db import models from django.contrib.postgres.fields import ArrayField - +from .station import Station from .fixed_types import STATION_TYPES @@ -11,54 +11,24 @@ from .fixed_types import STATION_TYPES class RTSMObservation(models.Model): observation_id = models.PositiveIntegerField(default=0) - station_name = models.CharField(max_length=6) - station_type = models.CharField(max_length=1, choices=STATION_TYPES) + station = models.ForeignKey(Station, on_delete=models.SET_NULL, null=True) - start_time = models.DateTimeField() - end_time = models.DateTimeField() + start_datetime = models.DateTimeField() + end_datetime = models.DateTimeField() samples = models.IntegerField(default=None, null=True) -class RTSMObservationSummary(RTSMObservation): - @property - def errors_found(self): - return RTSMErrorSummary.objects.defer('bad_spectrum', 'average_spectrum'). \ - filter(observation=self).values('error_type', 'rcu'). \ - annotate(count=models.Count('error_type', unique=True, output_field=models.FloatField())). \ - annotate(percentage=models.ExpressionWrapper(models.F('count') * 100. / models.F('observation__samples'), - output_field=models.FloatField())) - - class Meta: - proxy = True - - -class RTSMErrorSummary(models.Model): - rcu = models.SmallIntegerField(default=None, null=True) - mode = models.SmallIntegerField(default=None, null=True) - observation = models.ForeignKey(RTSMObservation, related_name='errors_summary', on_delete=models.CASCADE) - - error_type = models.CharField(max_length=10) - start_frequency = models.FloatField(default=None, null=True) - stop_frequency = models.FloatField(default=None, null=True) - time = models.DateTimeField() - - class Meta: - managed = False - db_table = 'monitoringdb_rtsmerror' - - class RTSMError(models.Model): + observation = models.ForeignKey(RTSMObservation, related_name='errors', on_delete=models.CASCADE) + rcu = models.SmallIntegerField(default=None, null=True) mode = models.SmallIntegerField(default=None, null=True) - observation = models.ForeignKey(RTSMObservation, related_name='errors', on_delete=models.CASCADE) - error_type = models.CharField(max_length=10) + error_type = models.CharField(max_length=50) start_frequency = models.FloatField(default=None, null=True) stop_frequency = models.FloatField(default=None, null=True) time = models.DateTimeField() - bad_spectrum = ArrayField(models.FloatField()) - average_spectrum = ArrayField(models.FloatField()) def __str__(self): return self.__unicode__() @@ -67,13 +37,26 @@ class RTSMError(models.Model): return self.__unicode__() def __unicode__(self): - return '%s - MODE %d, FROM %f MHz TO %f MHz RCU=(%d): %s' % (self.time, + return '%s - MODE %s, FROM %s MHz TO %s MHz RCU=(%s): %s' % (self.time, self.mode, self.start_frequency, self.stop_frequency, self.rcu, self.error_type,) - class Meta: - managed = True - db_table = 'monitoringdb_rtsmerror' + +class RTSMErrorSummary(models.Model): + observation = models.ForeignKey(RTSMObservation, related_name='errors_summary', on_delete=models.CASCADE) + percentage = models.FloatField(default=None, null=True) + rcu = models.SmallIntegerField(default=None, null=True) + mode = models.SmallIntegerField(default=None, null=True) + count = models.IntegerField(default=None, null=True) + error_type = models.CharField(max_length=50) + start_frequency = models.FloatField(default=None, null=True) + stop_frequency = models.FloatField(default=None, null=True) + + +class RTSMSpectrum(models.Model): + bad_spectrum = ArrayField(models.FloatField()) + average_spectrum = ArrayField(models.FloatField()) + rtsm_error = models.ForeignKey(RTSMError, related_name='spectra', on_delete=models.CASCADE) diff --git a/LCU/Maintenance/DBInterface/monitoringdb/models/station.py b/LCU/Maintenance/DBInterface/monitoringdb/models/station.py index ec6eaa599dfdf63ac3cd0588ddfa12a39855c403..2be2b8a8a949b2b58cff7d1e1c9edbaa07f27eb4 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/models/station.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/models/station.py @@ -3,6 +3,6 @@ from .fixed_types import STATION_TYPES class Station(models.Model): - location = models.CharField(max_length=50, null=True, blank=True) - name = models.CharField(max_length=10) - type = models.CharField(max_length=1, choices=STATION_TYPES) + location = models.CharField(max_length=50, null=True, blank=True, help_text='where the station is located') + name = models.CharField(max_length=10, help_text='name of the station') + type = models.CharField(max_length=1, choices=STATION_TYPES, help_text='station type one of [Core[C], Remote[R], International[I]]') diff --git a/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py b/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py index 132b355b3a70fe0ce45c9eec333a02306b08cba4..a6c7bf907930bdcd40d17dfdd9c052dd6f5754f3 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/rtsm_test_raw_parser.py @@ -1,6 +1,15 @@ from datetime import datetime import pytz +rtsm_error_types_to_error_types = dict(HN='HIGH_NOISE', + SN='SUMMATOR_NOISE', + CR='CABLE_REFLECTION', + LN='LOW_NOISE', + J='JITTER', + OSC='OSCILLATION', + FLAT='FLAT', + DOWN='DOWN', + SHORT='SHORT') def split_raw_rtsm_test(content): """ @@ -44,15 +53,15 @@ def preparse_rtsm_test_file(content): rcu, mode, observation_id, error_type, start_frequency, stop_frequency, time = value.split(',') result.update(rcu=int(rcu), mode=int(mode), - error_type=error_type, + error_type=rtsm_error_types_to_error_types[error_type], start_frequency=float(start_frequency), stop_frequency=float(stop_frequency), time=parse_data(time)) elif 'INFO' in key: observation_id, start_time, stop_time, samples = value.split(',') observation.update(observation_id=int(observation_id), - start_time=parse_data(start_time), - end_time=parse_data(stop_time), + start_datetime=parse_data(start_time), + end_datetime=parse_data(stop_time), samples=int(samples)) elif 'BAD' in key: values = [float(item) for item in value.lstrip('[').rstrip(']').strip().split(' ')] diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/component_error.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/component_error.py index 9e4129617a0bcbf3c01e578c287111b74398d90a..a1ef7be0fd47a7a8eb625bb08834b7b4f55d0ff2 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/component_error.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/component_error.py @@ -1,6 +1,13 @@ +import logging + from rest_framework.serializers import PrimaryKeyRelatedField, CharField, IntegerField -from ..models.component_error import ComponentError + from .utils import NotNullModelSerializer +from ..models.component import Component +from ..models.component_error import ComponentError +from .log import ActionLogSerializer + +logger = logging.getLogger(__name__) class ComponentErrorSerializer(NotNullModelSerializer): @@ -11,8 +18,60 @@ class ComponentErrorSerializer(NotNullModelSerializer): class Meta: model = ComponentError fields = '__all__' - depth = 2 + depth = 3 def __init__(self, *args, **kwargs): self.Meta.depth = kwargs.pop('depth', 2) super(ComponentErrorSerializer, self).__init__(*args, **kwargs) + + @staticmethod + def insert_component_error(station_test_entry, + station_entry, + component_error): + + component = component_error.pop('component') + + component_entry = Component.objects.filter(station=station_entry, + **component).first() + if component_entry is None: + logger.debug('Component entry is not present, inserting ...') + component_entry = Component(station=station_entry, + **component) + logger.debug(component_entry, component_error) + + + component_entry.save() + # Log the action + ActionLogSerializer.log_model_insert(component_entry) + + component_error_entry = ComponentError.objects.filter(component=component_entry, + station_test=station_test_entry, + **component_error).first() + if component_error_entry is None: + logger.debug('Component error entry is not present, inserting ...') + + component_error_entry = ComponentError(component=component_entry, + station_test=station_test_entry, + **component_error) + component_error_entry.save() + # Log the action + ActionLogSerializer.log_model_insert(component_error_entry) + + return component_error_entry + + @staticmethod + def insert_component_errors(station_test_entry, + station_entry, + component_errors): + results = [] + for component_error in component_errors: + try: + results.append(ComponentErrorSerializer.insert_component_error( + station_test_entry, station_entry, component_error)) + except Exception as e: + logger.exception( + 'Cannot insert component error %s' + ' for station_test_entry %s and station_entry %s: %s', + component_error, station_test_entry, station_entry, e) + raise e + return results diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/element.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/element.py index 61dbbd28835ec31479cb992467aa1a6962c60704..54a1edfb0bbbfb3af0aba70ce41cb866f83ec302 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/element.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/element.py @@ -1,5 +1,6 @@ from rest_framework.serializers import ModelSerializer from ..models.element import Element +from .log import ActionLogSerializer class ElementSerializer(ModelSerializer): @@ -7,3 +8,15 @@ class ElementSerializer(ModelSerializer): model = Element fields = '__all__' depth = 1 + + @staticmethod + def insert_element(component, element): + element_entry = Element.objects.filter(component=component, + **element).first() + if element_entry is None: + element_entry = Element(component=component, + **element) + element_entry.save() + # Log the action + ActionLogSerializer.log_model_insert(element_entry) + return element_entry \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/element_error.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/element_error.py index 787277e8245b4ceb1e6f0f4b4b15e51c0c493952..fea8dcbe6ce8679bae76aa564c8ff35b7b9480fa 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/element_error.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/element_error.py @@ -1,10 +1,65 @@ from rest_framework.serializers import ModelSerializer + +from .component_error import ComponentErrorSerializer +from .element import ElementSerializer from ..models.element_error import ElementError -from .error_details import ErrorDetailsSerializer +from .log import ActionLogSerializer +import logging + + +logger = logging.getLogger(__name__) -class ElementErrorSerializer(ModelSerializer): +class ElementErrorSerializer(ModelSerializer): class Meta: model = ElementError fields = '__all__' - depth = 1 \ No newline at end of file + depth = 1 + + @staticmethod + def insert_element_error(station_test_entry, + station_entry, + element_error): + component_error = element_error.pop('component_error') + + component_error_entry = ComponentErrorSerializer.insert_component_error(station_test_entry, + station_entry, + component_error) + component = component_error_entry.component + + element = element_error.pop('element') + element_error_details = element_error.pop('details') + + element_entry = ElementSerializer.insert_element(component, element) + + element_error_entry = ElementError.objects.filter(element=element_entry, + component_error=component_error_entry, + **element_error).first() + if element_error_entry is None: + element_error_entry = ElementError(element=element_entry, + details=element_error_details, + component_error=component_error_entry, + **element_error) + + element_error_entry.save() + # Log the action + ActionLogSerializer.log_model_insert(element_error_entry) + + return element_error_entry + + @staticmethod + def insert_element_errors(station_test_entry, + station_entry, + element_errors): + results = [] + for element_error in element_errors: + try: + results.append(ElementErrorSerializer.insert_element_error(station_test_entry, + station_entry, + element_error)) + except Exception as e: + logger.exception( + 'Cannot insert element error %s' + ' for station_test_entry %s and station_entry %s: %s', + element_error, station_test_entry, station_entry, e) + raise e diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/error_details.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/error_details.py deleted file mode 100644 index e1a8af9ee7903d1325eab08707ca169f6ead7d01..0000000000000000000000000000000000000000 --- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/error_details.py +++ /dev/null @@ -1,16 +0,0 @@ -from rest_framework.serializers import ModelSerializer -from ..models.error_details import ErrorDetails - -class ErrorDetailsSerializer(ModelSerializer): - class Meta: - model = ErrorDetails - fields = '__all__' - - def to_representation(self, instance): - result = super().to_representation(instance) - - result = {key: value - for key, value in - filter(lambda item: item[1] is not None, result.items())} - - return result \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/log.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/log.py new file mode 100644 index 0000000000000000000000000000000000000000..fbae920a4f1c0d62302eb6c3c2d002ba4a50960b --- /dev/null +++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/log.py @@ -0,0 +1,33 @@ +from rest_framework.serializers import ModelSerializer +from ..models.log import ActionLog +import logging + +logger = logging.getLogger(__name__) + + +class ActionLogSerializer(ModelSerializer): + class Meta: + model = ActionLog + fields = '__all__' + depth = 1 + + @staticmethod + def append_log(primary_key, model_name, who, what): + action_log = ActionLog(entry_id=primary_key, + model_name=model_name, + who=who, + what=what) + action_log.save() + + return action_log + + @staticmethod + def log_model_insert(element_entry, who='system'): + try: + ActionLogSerializer.append_log(element_entry.pk, + model_name=element_entry.__class__.__name__, + who=who, + what='Inserted') + except Exception as e: + logger.exception('Cannot log entry %s', element_entry) + raise e \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py index b26d5eef29363a01421349f1dc546f4a2ba33191..8cddf2b51b544d17f7ec1482e9f7c20ba0808ca4 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/rtsm.py @@ -3,78 +3,100 @@ from rest_framework import serializers import logging from django.db.transaction import atomic from ..exceptions import ItemAlreadyExists -from django.db.models import ObjectDoesNotExist +from .log import ActionLogSerializer +from .station import Station logger = logging.getLogger('serializers') -class RTSMErrorsAggregateSerializer(serializers.Serializer): - count = serializers.IntegerField() - percentage = serializers.FloatField() - error_type = serializers.CharField() - rcu = serializers.IntegerField() - - -class RTSMErrorSerializer(serializers.ModelSerializer): +class RTSMSpectrumSerializer(serializers.ModelSerializer): bad_spectrum = serializers.ListField(child=serializers.FloatField()) average_spectrum = serializers.ListField(child=serializers.FloatField()) class Meta: - model = RTSMError + model = RTSMSpectrum fields = '__all__' -class RTSMErrorSummarySerializer(serializers.ModelSerializer): - samples = serializers.IntegerField(source='observation.samples', read_only=True) - start_time = serializers.DateTimeField(source='observation.start_time', read_only=True) - end_time = serializers.DateTimeField(source='observation.start_time', read_only=True) - observation_id = serializers.IntegerField(source='observation.observation_id', read_only=True) - id = serializers.IntegerField() +class RTSMErrorSerializer(serializers.ModelSerializer): + observation = serializers.PrimaryKeyRelatedField(read_only=True) + + def __init__(self, *args, **kwargs): + self.Meta.depth = kwargs.pop('depth', 1) + super(RTSMErrorSerializer, self).__init__(*args, **kwargs) class Meta: - model = RTSMErrorSummary + model = RTSMError fields = '__all__' -class RTSMObservationSummarySerializer(serializers.ModelSerializer): - errors_found = RTSMErrorsAggregateSerializer(many=True) +class RTSMErrorSummarySerializer(serializers.ModelSerializer): + observation = serializers.PrimaryKeyRelatedField(read_only=True) class Meta: - model = RTSMObservationSummary + model = RTSMErrorSummary fields = '__all__' class RTSMObservationSerializer(serializers.ModelSerializer): + errors = RTSMErrorSerializer(many=True, depth=0) errors_summary = RTSMErrorSummarySerializer(many=True) class Meta: model = RTSMObservation fields = '__all__' + def compute_summary(self, RTSMObservation_entry): + summary_entries = RTSMObservation_entry.errors.defer('spectra'). \ + filter(observation=RTSMObservation_entry).values('error_type', 'mode', 'rcu', 'start_frequency', + 'stop_frequency'). \ + annotate(count=models.Count('error_type', unique=True, output_field=models.FloatField())). \ + annotate(percentage=models.ExpressionWrapper(models.F('count') * 100. / models.F('observation__samples'), + output_field=models.FloatField())) + for summary_entry in summary_entries: + RTSMErrorSummary(observation=RTSMObservation_entry, **summary_entry).save() + @atomic def create(self, validated_data): rtsm_errors = validated_data.pop('errors') - try: - start_time, station_name = validated_data['start_time'], validated_data['station_name'] - station_type = station_name[0].capitalize() - station_type = station_type if station_type in ['C', 'R'] else 'I' - validated_data.update(station_type=station_type) - element = RTSMObservation.objects.filter(start_time=start_time, - station_name=station_name).first() - - if element is None: - logger.info('rtsm not found inserting %s', validated_data) - else: - logger.info('RTSM test for station %s started at %s is present already. Skipping insertion', - start_time, station_name) - raise ItemAlreadyExists(element) - except ObjectDoesNotExist as e: + + # derive the station type from the station name + station_name = validated_data.pop('station_name') + station_type = station_name[0].capitalize() + station_type = station_type if station_type in ['C', 'R'] else 'I' + + station_entry = Station.objects.filter(name=station_name).first() + if station_entry is None: + station_entry = Station(name=station_name, type=station_type) + station_entry.save() + # searches if the RTSM is already present + observation_id = validated_data['observation_id'] + RTSMObservation_instance = RTSMObservation.objects.filter(observation_id=observation_id, + station=station_entry).first() + + if RTSMObservation_instance is None: logger.info('rtsm not found inserting %s', validated_data) - RTSMObservation_instance = RTSMObservation.objects.create(**validated_data) - RTSMObservation_instance.save() + RTSMObservation_instance = RTSMObservation.objects.create(station=station_entry, + **validated_data) + RTSMObservation_instance.save() + ActionLogSerializer.log_model_insert(RTSMObservation_instance) + else: + raise ItemAlreadyExists(RTSMObservation_instance) for rtsm_error in rtsm_errors: rtsm_error.update(observation=RTSMObservation_instance) + + average_spectrum = rtsm_error.pop('average_spectrum') + bad_spectrum = rtsm_error.pop('bad_spectrum') + rtsm_error_instance = RTSMError.objects.create(**rtsm_error) rtsm_error_instance.save() + ActionLogSerializer.log_model_insert(rtsm_error_instance) + + rtsm_spectrum_instance = RTSMSpectrum.objects.create(average_spectrum=average_spectrum, + bad_spectrum=bad_spectrum, + rtsm_error=rtsm_error_instance) + rtsm_spectrum_instance.save() + ActionLogSerializer.log_model_insert(rtsm_spectrum_instance) + self.compute_summary(RTSMObservation_instance) return RTSMObservation_instance diff --git a/LCU/Maintenance/DBInterface/monitoringdb/serializers/station_tests.py b/LCU/Maintenance/DBInterface/monitoringdb/serializers/station_tests.py index 6b5246a94f4031add41a18bf44bbb4a482b09643..7f9a54f9204f76668ebfa3f332af011b88d913dd 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/serializers/station_tests.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/serializers/station_tests.py @@ -1,10 +1,59 @@ from rest_framework.serializers import ModelSerializer from ..models.station_test import StationTest +from ..models.station import Station from .component_error import ComponentErrorSerializer +from .element_error import ElementErrorSerializer +from .log import ActionLogSerializer + +import logging + +logger = logging.getLogger(__name__) class StationTestSerializer(ModelSerializer): component_errors = ComponentErrorSerializer(many=True, read_only=True, depth=0) class Meta: model = StationTest fields = '__all__' - depth = 2 \ No newline at end of file + depth = 5 + + @staticmethod + def insert_station_test(station_test): + """ + Insert the station test in the database and if necessary the component errors + and the element errors + :param station_test: json representation of the station test + :type station_test: dict + :return: the station test entry inserted + :rtype: StationTest + """ + station = station_test.pop('station') + component_errors = station_test.pop('component_errors') + element_errors = station_test.pop('element_errors') + station_entry = Station.objects.filter(**station).first() + + if station_entry is None: + logger.debug('Station is not present, inserting ...') + station_entry = Station(**station) + station_entry.save() + # Log the action + ActionLogSerializer.log_model_insert(station_entry) + + station_test_entry = StationTest.objects.filter(**station_test).first() + if station_test_entry is None: + logger.debug('Station test is not present, inserting ...') + station_test_entry = StationTest(station=station_entry, + **station_test) + station_test_entry.save() + # Log the action + ActionLogSerializer.log_model_insert(station_test_entry) + + ComponentErrorSerializer.insert_component_errors(station_test_entry, + station_entry, + component_errors) + + ElementErrorSerializer.insert_element_errors(station_test_entry, + station_entry, + element_errors) + + return station_test_entry + diff --git a/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py b/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py index 3f4ff081edde65732f4984ccc8db68d06a339d6f..f47b1ae7c47fe063d27563a25738af2cc9f602e5 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/station_test_raw_parser.py @@ -179,7 +179,7 @@ def dict_from_component_error(content): component_error = dict(component=component, details=error_details, - type=error_type) + type=error_type.strip()) for element_error in element_errors: element_error['component_error'] = dict(component_error) diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tests/__init__.py b/LCU/Maintenance/DBInterface/monitoringdb/tests/__init__.py index a23cebb546df0dffde79ef4731888d2364a13007..c54420b6cdc7d12fff94d0c82beb9fb366380be4 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/tests/__init__.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/tests/__init__.py @@ -1 +1 @@ -__all__ = ['t_stationtest_model_creation', 't_stationtest_parser'] \ No newline at end of file +__all__ = ['t_stationtest_model_creation', 't_stationtest_parser', 't_django_insert'] \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tests/t_django_insert.py b/LCU/Maintenance/DBInterface/monitoringdb/tests/t_django_insert.py new file mode 100644 index 0000000000000000000000000000000000000000..b0561c23646dffa62c10a32bf84072d4615f06cb --- /dev/null +++ b/LCU/Maintenance/DBInterface/monitoringdb/tests/t_django_insert.py @@ -0,0 +1,38 @@ +from django.test import TestCase + +import lofar.maintenance.django_postgresql.settings as settings + +import lofar.maintenance.monitoringdb.serializers.component_error as component_error +from lofar.maintenance.monitoringdb.models.station import Station +from lofar.maintenance.monitoringdb.models.station_test import StationTest +from datetime import datetime + +from lofar.maintenance.monitoringdb.models.component_error import ComponentError + + +class TestStationTestInsert(TestCase): + def test_insert_station_test_no_component_errors(self): + pass + + +class TestComponentErrorInsert(TestCase): + def test_insert_component_error_with_details(self): + + station_entry = Station(location='Here', name='CS001C', type='C') + station_entry.save() + station_test_entry = StationTest(start_datetime=datetime(2017, 4, 12, 0,0,0), + end_datetime=datetime(2017, 4, 12, 1,0,0), + checks='Some checks', + station=station_entry + ) + station_test_entry.save() + test_data = {'component': {'component_id': 50, 'type': 'LBL'}, 'details': {'xoff': '11', 'yoff': '25', 'xval': 77.0, 'yval': 75.9}, 'type': 'DOWN'} + + component_error_entry = component_error.ComponentErrorSerializer.insert_component_error(station_test_entry, + station_entry, + test_data) + component_error_entry.save() + + id = component_error_entry.pk + + print(ComponentError.objects.get(pk=id).details) \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_model_creation.py b/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_model_creation.py index e198e8af58306f4f3efc9f0e22a71b7b44378c21..ebb74d66956186c32dac7206347a3ed2ad5e2e2e 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_model_creation.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_model_creation.py @@ -10,5 +10,6 @@ class TestStationTestModelCreation(unittest.TestCase): result = raw_parser.dict_from_raw_station_test(test) print(result) + if __name__ == '__main__': unittest.main() \ No newline at end of file diff --git a/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_parser.py b/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_parser.py index 039e4ae7009b2c84b9564d9aeecb30e954f35f73..0932b4634680837910a3b5fd5df2fd8c75754eec 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_parser.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/tests/t_stationtest_parser.py @@ -2,9 +2,7 @@ import unittest from .common import * - class TestStationTestsParser(unittest.TestCase): - def test_element_error_details_parsing(self): preparsed_element_error = ['20180508', 'HBA', '044', 'E_FAIL', {'x1': '99.5 140 101.6 99.8 140 102.4'}, diff --git a/LCU/Maintenance/DBInterface/monitoringdb/urls.py b/LCU/Maintenance/DBInterface/monitoringdb/urls.py index 44fc3e4b92cae9f095986241610c1f9ca033f8fd..bf97b6fc0efb2329c1b571d156c6874b331e46e7 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/urls.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/urls.py @@ -1,52 +1,47 @@ from django.conf.urls import url, include from rest_framework import routers +from .views.controllers import * from .views.station_test_views import * from .views.rtsm_views import * -from rest_framework.schemas import get_schema_view -from rest_framework.renderers import CoreJSONRenderer -from rest_framework import renderers -from openapi_codec import OpenAPICodec - - -class SwaggerRenderer(renderers.BaseRenderer): - media_type = 'application/openapi+json' - format = 'swagger' - - def render(self, data, media_type=None, renderer_context=None): - codec = OpenAPICodec() - return codec.dump(data) - - -schema_view = get_schema_view(title="Maintenance API", renderer_classes=[SwaggerRenderer, CoreJSONRenderer]) +from .views.logs_view import * +from rest_framework.documentation import include_docs_urls +log_router = routers.DefaultRouter() +log_router.register(r'action_log', ActionLogViewSet) station_test_router = routers.DefaultRouter() -#Station test -station_test_router.register(r'error_details', ErrorDetailsViewSet) +# Station test station_test_router.register(r'element_error', ElementErrorViewSet) station_test_router.register(r'element', ElementViewSet) station_test_router.register(r'component_error', ComponentErrorViewSet) station_test_router.register(r'component', ComponentViewSet) station_test_router.register(r'station', StationViewSet) -station_test_router.register(r'station_test', StationTestViewSet) +station_test_router.register(r'', StationTestViewSet) rtsm_router = routers.DefaultRouter() -#RTSM -rtsm_router.register(r'^/summary', RTSMObservationSummaryViewSet) -rtsm_router.register(r'^/errors_detailed', RTSMErrorsDetailedViewSet) -rtsm_router.register(r'^/errors', RTSMErrorsViewSet) -rtsm_router.register(r'^', RTSMObservationViewSet) +# RTSM +rtsm_router.register(r'errors', RTSMErrorsViewSet) +rtsm_router.register(r'spectra', RTSMSpectrumViewSet) +rtsm_router.register(r'', RTSMObservationViewSet) urlpatterns = [ + url(r'^api/stationtests/', include(station_test_router.urls)), - url(r'^api/rtsm', include(rtsm_router.urls)), + url(r'^api/rtsm/', include(rtsm_router.urls)), + url(r'^api/log/', include(log_router.urls)), url(r'^api/api-auth', include('rest_framework.urls', namespace='rest_framework')), - url(r'^api/stationtests/insert_raw', insert_raw_station_test), - url(r'^api/rtsm/insert_raw', insert_raw_rtsm_test), - url(r'^api/schema', schema_view) + url(r'^api/stationtests/raw/insert', insert_raw_station_test), + url(r'^api/rtsm/raw/insert', insert_raw_rtsm_test), + url(r'^api/view/ctrl_stationoverview', ControllerStationOverview.as_view()), + url(r'^api/view/ctrl_stationtestsummary', ControllerStationTestsSummary.as_view()), + url(r'^api/view/ctrl_latest_observation', ControllerLatestObservations.as_view()), + url(r'^api/view/ctrl_stationtest_statistics', ControllerStationTestStatistics.as_view()), + url(r'^api/view/ctrl_list_component_error_types', ControllerAllComponentErrorTypes.as_view()), + + url(r'^api/docs', include_docs_urls(title='Monitoring DB API')) ] diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py new file mode 100644 index 0000000000000000000000000000000000000000..7f1c3478f5757322a832036ccca3ac4f03d226d5 --- /dev/null +++ b/LCU/Maintenance/DBInterface/monitoringdb/views/controllers.py @@ -0,0 +1,621 @@ +import datetime +import logging +from collections import OrderedDict +from math import ceil + +import coreapi +import coreschema +from django.db.models import Count +from rest_framework import status +from rest_framework.response import Response +from rest_framework.schemas import ManualSchema +from rest_framework.views import APIView + +from lofar.maintenance.monitoringdb.models.component_error import ComponentError +from lofar.maintenance.monitoringdb.models.rtsm import RTSMErrorSummary +from lofar.maintenance.monitoringdb.models.rtsm import RTSMObservation +from lofar.maintenance.monitoringdb.models.station import Station +from lofar.maintenance.monitoringdb.models.station_test import StationTest + +logger = logging.getLogger(__name__) + + +def parse_date(date): + expected_format = '%Y-%m-%d' + try: + parsed_date = datetime.datetime.strptime(date, expected_format) + return parsed_date + except Exception as e: + raise ValueError('cannot parse %s with format %s - %s' % (date, expected_format, e)) + + +def parse_bool(boolean_value_str): + boolean_value_str = boolean_value_str.lower() + if boolean_value_str in ['t', 'true', '1']: + return True + elif boolean_value_str in ['f', 'false', '0']: + return False + else: + raise ValueError('%s is neither true or false' % boolean_value_str) + + +class ValidableReadOnlyView(APIView): + """ + Convenience APIView class to have the validation of the query parameters on a get http request + """ + + # Override this to make the schema validation work + fields = [] + + def compute_response(self): + raise NotImplementedError() + + @property + def schema(self): + return ManualSchema(fields=self.fields) + + def validate_query_parameters(self, request): + """ + Validated the request parameters and stores them as fields + :param request: the http request to the api call + :type request: rest_framework.request.Request + :raises ValueError: if the parameter is not valid + :raises KeyError: if the requested parameter is missing + """ + for field in self.fields: + if field.required and field.name not in request.query_params: + raise KeyError('%s parameter is missing' % field.name) + elif field.name not in request.query_params: + continue + else: + value = self.request.query_params.get(field.name) + if field.type: + self.__setattr__(field.name, field.type(value)) + else: + self.__setattr__(field.name, value) + errors = field.schema.validate(self.__getattribute__(field.name)) + for error in errors: + raise ValueError(error.text) + + def get(self, request): + try: + self.validate_query_parameters(request) + except ValueError as e: + return Response(status=status.HTTP_406_NOT_ACCEPTABLE, + data='Please specify the correct parameters: %s' % (e,)) + except KeyError as e: + return Response(status=status.HTTP_406_NOT_ACCEPTABLE, + data='Please specify all the required parameters: %s' % (e,)) + + try: + response = self.compute_response() + except Exception as e: + logger.exception(e) + return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR, + data='exception occurred: %s' % e) + return response + + +class ControllerStationOverview(ValidableReadOnlyView): + """ + Overview of the latest tests performed on the stations + """ + + station_group = 'A' + errors_only = 'true' + n_station_tests = 4 + n_rtsm = 4 + + fields = [ + coreapi.Field( + "station_group", + required=False, + location='query', + schema=coreschema.Enum(['C', 'R', 'I', 'A'], description= + 'Station group to select for choices are [C|R|I|ALL]', + ) + ), + coreapi.Field( + "n_station_tests", + required=False, + location='query', + type=int, + schema=coreschema.Integer(description='number of station tests to select', + minimum=1) + ), + coreapi.Field( + "n_rtsm", + required=False, + location='query', + type=int, + schema=coreschema.Integer(description='number of station tests to select', + minimum=1) + ), + coreapi.Field( + "errors_only", + required=False, + location='query', + type=parse_bool, + schema=coreschema.Boolean( + description='displays or not only the station with more than one error') + ) + ] + + def compute_response(self): + + station_entities = Station.objects.all() + for group in self.station_group: + if group is not 'A': + station_entities = station_entities.filter(type=group) + + # Since django preferes a ordered dict over a dict we make it happy... for now + response_payload = list() + for station_entity in station_entities: + station_payload = OrderedDict() + + station_payload['station_name'] = station_entity.name + + station_test_list = StationTest.objects.filter( + station__name=station_entity.name).order_by('-end_datetime')[:self.n_station_tests] + rtsm_list = RTSMObservation.objects.filter( + station__name=station_entity.name).order_by('-end_datetime')[:self.n_rtsm] + + station_payload['station_tests'] = list() + for station_test in station_test_list: + station_test_payload = OrderedDict() + component_errors = station_test.component_errors + + station_test_payload[ + 'total_component_errors'] = station_test.component_errors.count() + station_test_payload['start_datetime'] = station_test.start_datetime + station_test_payload['end_datetime'] = station_test.end_datetime + station_test_payload['checks'] = station_test.checks + component_errors_summary = component_errors. \ + values('component__type', 'type').annotate( + total=Count('type')).order_by('-total') + component_errors_summary_dict = OrderedDict() + for item in component_errors_summary: + item_component_type = item['component__type'] + item_error_type = item['type'] + item_error_total = item['total'] + + if item_component_type not in component_errors_summary_dict: + component_errors_summary_dict[item_component_type] = OrderedDict() + + component_errors_summary_dict[item_component_type][item_error_type] = \ + item_error_total + station_test_payload['component_error_summary'] = component_errors_summary_dict + + station_payload['station_tests'].append(station_test_payload) + + station_payload['rtsm'] = list() + for rtsm in rtsm_list: + rtsm_payload = OrderedDict() + rtsm_payload['observation_id'] = rtsm.observation_id + rtsm_payload['start_datetime'] = rtsm.start_datetime + rtsm_payload['end_datetime'] = rtsm.end_datetime + + unique_modes = [item['mode'] for item in + rtsm.errors_summary.values('mode').distinct()] + rtsm_payload['mode'] = unique_modes + rtsm_payload['total_component_errors'] = rtsm.errors_summary.count() + + errors_summary = OrderedDict() + + errors_summary_query = rtsm.errors_summary.values('error_type').annotate( + total=Count('error_type')) + + for error_summary in errors_summary_query: + errors_summary[error_summary['error_type']] = error_summary['total'] + + rtsm_payload['error_summary'] = errors_summary + station_payload['rtsm'].append(rtsm_payload) + + response_payload.append(station_payload) + if self.errors_only: + response_payload = filter( + lambda station_entry: + len(station_entry['station_tests']) + len(station_entry['rtsm']) > 0, + response_payload) + response_payload = sorted(response_payload, key=lambda item: item['station_name']) + return Response(status=status.HTTP_200_OK, data=response_payload) + + +class ControllerStationTestsSummary(ValidableReadOnlyView): + """ + Overview of the latest station tests performed on the stations # lookback days before now + """ + station_group = 'A' + errors_only = 'true' + lookback_time = 7 + + fields = [ + coreapi.Field( + "station_group", + required=False, + location='query', + schema=coreschema.Enum(['C', 'R', 'I', 'A'], + description='Station group to select for choices are [C|R|I|ALL]') + ), + coreapi.Field( + "errors_only", + required=False, + location='query', + type=parse_bool, + schema=coreschema.Boolean( + description='displays or not only the station with more than one error') + ), + coreapi.Field( + "lookback_time", + required=False, + type=int, + location='query', + schema=coreschema.Integer(description='number of days from now (default 7)', + minimum=1) + ) + ] + + def compute_response(self): + self.lookback_time = datetime.timedelta(days=self.lookback_time) + + station_test_list = StationTest.objects \ + .filter(start_datetime__gte=datetime.date.today() - self.lookback_time) \ + .order_by('-start_datetime', 'station__name') + for group in self.station_group: + if group is not 'A': + station_test_list = station_test_list.filter(station__type=group) + + # Since django preferes a ordered dict over a dict we make it happy... for now + response_payload = list() + + for station_test in station_test_list: + + station_test_payload = OrderedDict() + component_errors = station_test.component_errors + station_test_payload['station_name'] = station_test.station.name + + station_test_payload[ + 'total_component_errors'] = station_test.component_errors.count() + station_test_payload['date'] = station_test.start_datetime.strftime('%Y-%m-%d') + station_test_payload['start_datetime'] = station_test.start_datetime + station_test_payload['end_datetime'] = station_test.end_datetime + station_test_payload['checks'] = station_test.checks + component_errors_summary = component_errors. \ + values('component__type', 'type').annotate( + total=Count('type')).order_by('-total') + component_errors_summary_dict = OrderedDict() + for item in component_errors_summary: + item_component_type = item['component__type'] + item_error_type = item['type'] + item_error_total = item['total'] + + if item_component_type not in component_errors_summary_dict: + component_errors_summary_dict[item_component_type] = OrderedDict() + + component_errors_summary_dict[item_component_type][item_error_type] = \ + item_error_total + station_test_payload['component_error_summary'] = component_errors_summary_dict + + response_payload.append(station_test_payload) + + if self.errors_only: + response_payload = filter( + lambda station_test_entry: + station_test_entry['total_component_errors'] > 0, + response_payload) + + return Response(status=status.HTTP_200_OK, data=response_payload) + + +class ControllerLatestObservations(ValidableReadOnlyView): + """ + Overview of the latest observations performed on the stations + """ + + station_group = 'A' + errors_only = 'true' + + fields = [ + coreapi.Field( + "station_group", + required=False, + location='query', + schema=coreschema.Enum(['C', 'R', 'I', 'A'], description= + 'Station group to select for choices are [C|R|I|A]', + ) + ), + coreapi.Field( + "errors_only", + required=False, + location='query', + type=parse_bool, + schema=coreschema.Boolean( + description='displays or not only the station with more than one error') + ), + coreapi.Field( + "from_date", + required=True, + location='query', + schema=coreschema.String( + description='select rtsm from date (ex. YYYY-MM-DD)') + ) + ] + + def compute_response(self): + self.from_date = parse_date(self.from_date) + + filtered_entities = RTSMObservation.objects \ + .filter(start_datetime__gte=self.from_date) + if self.station_group != 'A': + filtered_entities = filtered_entities \ + .filter(station__type=self.station_group) + if self.errors_only: + filtered_entities = filtered_entities.exclude(errors_summary__isnull=True) + + errors_summary = filtered_entities \ + .values('observation_id', + 'station__name', + 'start_datetime', + 'end_datetime', + 'errors_summary__error_type', + 'errors_summary__mode') \ + .annotate(total=Count('errors_summary__error_type')) \ + .order_by('observation_id', 'station__name') + response = dict() + + for error_summary in errors_summary: + observation_id = error_summary['observation_id'] + station_name = error_summary['station__name'] + start_datetime = error_summary['start_datetime'] + end_datetime = error_summary['end_datetime'] + mode = error_summary['errors_summary__mode'] + error_type = error_summary['errors_summary__error_type'] + total = error_summary['total'] + + if observation_id not in response: + response[observation_id] = OrderedDict() + response[observation_id]['observation_id'] = observation_id + response[observation_id]['start_datetime'] = start_datetime + response[observation_id]['end_datetime'] = end_datetime + + response[observation_id]['total_component_errors'] = 0 + response[observation_id]['mode'] = list() + response[observation_id]['station_involved'] = dict() + + if total == 0: + continue + + response[observation_id]['total_component_errors'] += total + station_involved_summary = response[observation_id]['station_involved'] + response[observation_id]['mode'] += [mode] \ + if mode not in response[observation_id]['mode'] else [] + if station_name not in station_involved_summary: + station_involved_summary[station_name] = OrderedDict() + station_involved_summary[station_name]['station_name'] = station_name + station_involved_summary[station_name]['n_errors'] = 0 + station_involved_summary[station_name]['component_error_summary'] = OrderedDict() + + station_involved_summary[station_name]['n_errors'] += total + station_involved_summary[station_name]['component_error_summary'][error_type] = total + + response_payload = sorted(response.values(), + key=lambda item: item['start_datetime'], + reverse=True) + + return Response(status=status.HTTP_200_OK, data=response_payload) + + +class ControllerStationTestStatistics(ValidableReadOnlyView): + """ + +/views/ctrl_stationtest_statistics: + +parameters: +station_group [C|R|I|ALL] (optional, default ALL) +test_type [RTSM|STATIONTEST|BOTH] (optional, default BOTH) +from_date #DATE +to_date #DATE +averaging_interval: #TIMESPAN +result: +{ + start_date : #DATE, + end_date: #DATE, + averaging_interval: #INTERVAL, + error_per_station: [{ + time: #DATE, + station_name: <station_name> + n_errors: #nr_errors int + }, ...], + error_per_error_type: [{ + time: #DATE, + error_type: <error_type> + n_errors: #nr_errors int + }, ...], + }, + .... +] + """ + station_group = 'A' + test_type = 'B' + + fields = [ + coreapi.Field( + "test_type", + required=False, + location='query', + schema=coreschema.Enum(['R', 'S', 'B'], + description='select the type of test possible values are (R, RTSM),' + ' (S, Station test), (B, both)[DEFAULT=B]', + ) + ), + coreapi.Field( + "station_group", + required=False, + location='query', + schema=coreschema.Enum(['C', 'R', 'I', 'A'], description= + 'Station group to select for choices are [C|R|I|ALL]', + ) + ), + coreapi.Field( + "from_date", + required=True, + location='query', + schema=coreschema.String( + description='select tests from date (ex. YYYY-MM-DD)') + ), + coreapi.Field( + "to_date", + required=True, + location='query', + schema=coreschema.String( + description='select tests to date (ex. YYYY-MM-DD)') + ), + coreapi.Field( + "averaging_interval", + required=True, + location='query', + type=int, + schema=coreschema.Integer( + description='averaging interval in days') + ) + ] + + def compute_errors_per_station(self, from_date, to_date, central_time, station_group, + test_type): + + component_errors = ComponentError.objects.all() + rtsm_summary_errors = RTSMErrorSummary.objects.all() + + if station_group: + component_errors = component_errors.filter(station_test__station__type=station_group) + rtsm_summary_errors = rtsm_summary_errors.filter( + observation__station__type=station_group) + + station_test_results = [] + rtsm_results = [] + if test_type in ['S', 'B']: + station_test_results = component_errors. \ + filter(station_test__start_datetime__gt=from_date, + station_test__start_datetime__lt=to_date). \ + values('station_test__station__name'). \ + annotate(n_errors=Count('station_test__station__name')) + if test_type in ['R', 'B']: + rtsm_results = rtsm_summary_errors. \ + filter(observation__start_datetime__gt=from_date, + observation__start_datetime__lt=to_date). \ + values('observation__station__name'). \ + annotate(n_errors=Count('observation__station__name')) + + errors_per_station_in_bin = dict() + central_time_str = central_time.strftime('%Y-%m-%d') + if test_type in ['S', 'B']: + for result in station_test_results: + station_name = result['station_test__station__name'] + errors_per_station_in_bin[station_name] = dict(station_name=station_name, + n_errors=result['n_errors'], + time=central_time_str) + + if test_type in ['R', 'B']: + for result in rtsm_results: + station_name = result['observation__station__name'] + if station_name not in errors_per_station_in_bin: + errors_per_station_in_bin[station_name] = dict(station_name=station_name, + n_errors=result['n_errors'], + time=central_time_str) + else: + errors_per_station_in_bin[station_name]['n_errors'] += result['n_errors'] + + return errors_per_station_in_bin.values() + + def compute_errors_per_type(self, from_date, to_date, central_time, station_group, test_type): + + component_errors = ComponentError.objects.all() + rtsm_summary_errors = RTSMErrorSummary.objects.all() + + station_test_results = [] + rtsm_results = [] + + central_time_str = central_time.strftime('%Y-%m-%d') + if station_group: + component_errors = component_errors.filter(station_test__station__type=station_group) + rtsm_summary_errors = rtsm_summary_errors.filter( + observation__station__type=station_group) + + if test_type in ['S', 'B']: + station_test_results = component_errors. \ + filter(station_test__start_datetime__gt=from_date, + station_test__start_datetime__lt=to_date). \ + values('type'). \ + annotate(n_errors=Count('type')) + if test_type in ['R', 'B']: + rtsm_results = rtsm_summary_errors. \ + filter(observation__start_datetime__gt=from_date, + observation__start_datetime__lt=to_date). \ + values('error_type'). \ + annotate(n_errors=Count('error_type')) + + errors_per_error_type_in_bin = dict() + + if test_type in ['S', 'B']: + for result in station_test_results: + error_type = result['type'] + errors_per_error_type_in_bin[error_type] = dict(error_type=error_type, + n_errors=result['n_errors'], + time=central_time_str) + if test_type in ['R', 'B']: + for result in rtsm_results: + error_type = result['error_type'] + if error_type not in errors_per_error_type_in_bin: + errors_per_error_type_in_bin[error_type] = dict(error_type=error_type, + n_errors=result['n_errors'], + time=central_time_str) + else: + errors_per_error_type_in_bin[error_type]['n_errors'] += result['n_errors'] + + return errors_per_error_type_in_bin.values() + + def compute_response(self): + from_date = parse_date(self.from_date) + to_date = parse_date(self.to_date) + averaging_interval = datetime.timedelta(days=self.averaging_interval) + response_payload = OrderedDict() + + response_payload['start_date'] = from_date + response_payload['end_date'] = to_date + response_payload['averaging_interval'] = averaging_interval + + errors_per_station = [] + errors_per_type = [] + n_bins = int(ceil((to_date - from_date) / averaging_interval)) + + for i in range(n_bins): + if self.station_group is 'A': + station_group = None + else: + station_group = self.station_group + errors_per_station += self.compute_errors_per_station( + from_date=from_date + i * averaging_interval, + to_date=from_date + (i + 1) * averaging_interval, + central_time=from_date + (i + .5) * averaging_interval, + station_group=station_group, + test_type=self.test_type) + + errors_per_type += self.compute_errors_per_type( + from_date=from_date + i * averaging_interval, + to_date=from_date + (i + 1) * averaging_interval, + central_time=from_date + (i + .5) * averaging_interval, + station_group=station_group, + test_type=self.test_type) + + response_payload['errors_per_station'] = errors_per_station + response_payload['errors_per_type'] = errors_per_type + + return Response(status=status.HTTP_200_OK, data=response_payload) + + +class ControllerAllComponentErrorTypes(ValidableReadOnlyView): + schema = ManualSchema(description="Returns all distinct component errors", fields=[]) + + def compute_response(self): + data = [item['type'] for item in ComponentError.objects.values('type').distinct()] + return Response(status=status.HTTP_200_OK, data=data) diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py b/LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py new file mode 100644 index 0000000000000000000000000000000000000000..51b961d3ac7806b7a317819ebac15f3974fcda40 --- /dev/null +++ b/LCU/Maintenance/DBInterface/monitoringdb/views/logs_view.py @@ -0,0 +1,13 @@ +from rest_framework.viewsets import ReadOnlyModelViewSet +from rest_framework.schemas import SchemaGenerator +from lofar.maintenance.monitoringdb.serializers.log import ActionLogSerializer, ActionLog + + +class ActionLogViewSet(ReadOnlyModelViewSet): + """ + Action event log line + + """ + queryset = ActionLog.objects.all() + serializer_class = ActionLogSerializer + filter_fields = '__all__' diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py b/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py index bc84d8c4efcf005a6894a43247449dad0c829f60..5e0a2560ff7ca59bd3bd6d5f677de7789d38b363 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/views/rtsm_views.py @@ -1,62 +1,28 @@ from .common import * -from ..models.rtsm import RTSMObservation, RTSMError -from ..serializers.rtsm import RTSMObservationSerializer, RTSMErrorSummarySerializer, RTSMErrorSerializer,\ - RTSMObservationSummarySerializer +from ..models.rtsm import RTSMObservation, RTSMError, RTSMSpectrum +from ..serializers.rtsm import RTSMObservationSerializer, RTSMErrorSerializer, \ + RTSMSpectrumSerializer from ..rtsm_test_raw_parser import parse_rtsm_test -from rest_framework.serializers import Serializer -logger = logging.getLogger('views') +logger = logging.getLogger('views') -class RTSMErrorsDetailedViewSet(viewsets.ReadOnlyModelViewSet): - queryset = RTSMError.objects.all() - serializer_class = RTSMErrorSerializer - def get_queryset(self): - queryset = RTSMError.objects.all() - for key, param in self.request.query_params.items(): - if key in RESERVED_FILTER_NAME: - continue - queryset = queryset.filter(**{key: param}) - return queryset +class RTSMSpectrumViewSet(viewsets.ReadOnlyModelViewSet): + queryset = RTSMSpectrum.objects.all() + serializer_class = RTSMSpectrumSerializer + filter_fields = ['rtsm_error'] class RTSMErrorsViewSet(viewsets.ReadOnlyModelViewSet): queryset = RTSMError.objects.all() - serializer_class = RTSMErrorSummarySerializer - - def get_queryset(self): - queryset = RTSMError.objects.all() - for key, param in self.request.query_params.items(): - if key in RESERVED_FILTER_NAME: - continue - queryset = queryset.filter(**{key: param}) - return queryset + serializer_class = RTSMErrorSerializer + filter_fields = '__all__' class RTSMObservationViewSet(viewsets.ReadOnlyModelViewSet): queryset = RTSMObservation.objects.all() serializer_class = RTSMObservationSerializer - - def get_queryset(self): - queryset = RTSMObservation.objects.all() - for key, param in self.request.query_params.items(): - if key in RESERVED_FILTER_NAME: - continue - queryset = queryset.filter(**{key: param}) - return queryset - - -class RTSMObservationSummaryViewSet(viewsets.ReadOnlyModelViewSet): - queryset = RTSMObservation.objects.all() - serializer_class = RTSMObservationSummarySerializer - - def get_queryset(self): - queryset = RTSMObservation.objects.all() - for key, param in self.request.query_params.items(): - if key in RESERVED_FILTER_NAME: - continue - queryset = queryset.filter(**{key: param}) - return queryset + filter_fields = '__all__' @api_view(['POST']) @@ -82,15 +48,15 @@ def insert_raw_rtsm_test(request): entry.update(station_name=station_name) logger.info('RTSM parsed successfully for obsid %d and station %s', - entry['observation_id'], - entry['station_name']) + entry['observation_id'], + entry['station_name']) try: - rtsm_item = RTSMObservationSerializer().create(entry) + rtsm_item = RTSMObservationSerializer().create(dict(entry)) rtsm_id = rtsm_item.id except ItemAlreadyExists as e: return Response(status=status.HTTP_200_OK, - data='RTSM test was already inserted with id {}\n'.format(e.instance_id)) + data='RTSM test was already present with id {}\n'.format(e.instance_id)) logger.info('RTSM inserted successfully for obsid %d and station %s', entry['observation_id'], entry['station_name']) @@ -98,14 +64,14 @@ def insert_raw_rtsm_test(request): logger.exception("raw station info malformed %s: %s", request.data['content'], e) logger.info('station test malformed skipping insertion.') - return Response(status=status.HTTP_200_OK, data='skipping insertion RTSM malformed') + return Response(status=status.HTTP_406_NOT_ACCEPTABLE, data='skipping insertion RTSM malformed') except Exception as e: logger.exception("exception occurred while parsing raw station info %s: %s", request.data['content'], e) return Response(exception=True, data="the post message is not correct." + - " It has to be of the form \{'content':[RAWSTRING]\}: %s. Request provided %s" % ( - e, request), + " It has to be of the form {content:[RAWSTRING], station_name:[STATION_NAME]}: %s. Request provided %s" % ( + e, request), status=status.HTTP_400_BAD_REQUEST) else: return Response(exception=True, diff --git a/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py b/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py index d2a071832255a380bedca92d88cf8f2119d09edb..834bf3a1c2f48993765ba6eb7aafd5a24d7f8bb6 100644 --- a/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py +++ b/LCU/Maintenance/DBInterface/monitoringdb/views/station_test_views.py @@ -6,14 +6,12 @@ from ..models.component import Component from ..models.component_error import ComponentError from ..models.element import Element from ..models.element_error import ElementError -from ..models.error_details import ErrorDetails from ..models.station import Station from ..models.station_test import StationTest from ..serializers.component import ComponentSerializer from ..serializers.component_error import ComponentErrorSerializer from ..serializers.element import ElementSerializer from ..serializers.element_error import ElementErrorSerializer -from ..serializers.error_details import ErrorDetailsSerializer from ..serializers.station import StationSerializer from ..serializers.station_tests import StationTestSerializer from ..station_test_raw_parser import parse_raw_station_test @@ -21,68 +19,91 @@ from ..station_test_raw_parser import parse_raw_station_test logger = logging.getLogger('views') -class ComponentViewSet(ReadOnlyModelViewSet): - serializer_class = ComponentSerializer - queryset = Component.objects.all() - filter_fields = '__all__' - - class StationTestFilterSet(filters.FilterSet): station_name = filters.CharFilter(field_name='station__name', - lookup_expr='contains') + lookup_expr='contains', help_text='name of the station') station_type = filters.CharFilter(field_name='station__type', - lookup_expr='contains') + lookup_expr='contains', + help_text='selects the station type:' + + 'one of [Core[C], Remote[R], International[I]]') - from_date = filters.DateFilter(field_name='start_datetime', lookup_expr='gt') - to_date = filters.DateFilter(field_name='end_datetime', lookup_expr='lt') + from_date = filters.DateFilter(field_name='start_datetime', lookup_expr='gt', + help_text='select station tests from date time') + to_date = filters.DateFilter(field_name='end_datetime', lookup_expr='lt', + help_text='select station tests until date time') class ComponentErrorFilterSet(filters.FilterSet): - component_id = filters.NumberFilter(field_name='component', lookup_expr='component_id') - component_type = filters.CharFilter(field_name='component', lookup_expr='type') + component_id = filters.NumberFilter(field_name='component', lookup_expr='component_id', + help_text='select by component id') + component_type = filters.CharFilter(field_name='component', lookup_expr='type', + help_text='select by component type') station_name = filters.CharFilter(field_name='station_test__station', - lookup_expr='name__contains') + lookup_expr='name__contains', + help_text='station name with name like') station_type = filters.CharFilter(field_name='station_test__station', - lookup_expr='type__contains') + lookup_expr='type__contains', + help_text='station type like') - from_date = filters.DateFilter(field_name='station_test', lookup_expr='start_datetime__gt') - to_date = filters.DateFilter(field_name='station_test', lookup_expr='end_datetime__lt') + from_date = filters.DateFilter(field_name='station_test', lookup_expr='start_datetime__gt', + help_text='select component errors from date time') + to_date = filters.DateFilter(field_name='station_test', lookup_expr='end_datetime__lt', + help_text='select component errors until date time') type = filters.CharFilter(field_name='type', - lookup_expr='contains') + lookup_expr='contains', + help_text='component error type') class ElementErrorFilterSet(filters.FilterSet): - component_id = filters.NumberFilter(field_name='component_error__component', lookup_expr='component_id') - element_id = filters.NumberFilter(field_name='element', lookup_expr='element_id') + component_id = filters.NumberFilter(field_name='component_error__component', + lookup_expr='component_id', + help_text='id of the parent component') + element_id = filters.NumberFilter(field_name='element', lookup_expr='element_id', + help_text='element id') station_name = filters.CharFilter(field_name='component_error__station_test__station', - lookup_expr='name__contains') + lookup_expr='name__contains', + help_text='name of the station') station_type = filters.CharFilter(field_name='component_error__station_test__station', - lookup_expr='type__contains') + lookup_expr='type__contains', + help_text='station type') - from_date = filters.DateFilter(field_name='component_error__station_test', lookup_expr='start_datetime__gt') - to_date = filters.DateFilter(field_name='component_error__station_test', lookup_expr='end_datetime__lt') + from_date = filters.DateFilter(field_name='component_error__station_test', + lookup_expr='start_datetime__gt', + help_text='select element errors from date time') + to_date = filters.DateFilter(field_name='component_error__station_test', + lookup_expr='end_datetime__lt', + help_text='select element errors until date time') type = filters.CharFilter(field_name='type', - lookup_expr='contains') - - -class ComponentErrorViewSet(ReadOnlyModelViewSet): - serializer_class = ComponentErrorSerializer - queryset = ComponentError.objects.all().order_by('-station_test__start_datetime', - 'type', - 'component__component_id', - 'component__station__name') - filter_fields = '__all__' - filterset_class = ComponentErrorFilterSet + lookup_expr='contains', help_text='element error type') class StationViewSet(ReadOnlyModelViewSet): + """ + retrieve: + retrieve a specific station from the database + + list: + list all the stations present in the database + """ serializer_class = StationSerializer queryset = Station.objects.all() filter_fields = '__all__' +class ComponentViewSet(ReadOnlyModelViewSet): + serializer_class = ComponentSerializer + queryset = Component.objects.all() + filter_fields = '__all__' + + +class ElementViewSet(ReadOnlyModelViewSet): + serializer_class = ElementSerializer + queryset = Element.objects.all() + filter_fields = '__all__' + + class StationTestViewSet(ReadOnlyModelViewSet): serializer_class = StationTestSerializer queryset = StationTest.objects.all().order_by('-start_datetime', 'station__name') @@ -90,10 +111,14 @@ class StationTestViewSet(ReadOnlyModelViewSet): filterset_class = StationTestFilterSet -class ErrorDetailsViewSet(ReadOnlyModelViewSet): - serializer_class = ErrorDetailsSerializer - queryset = ErrorDetails.objects.all() +class ComponentErrorViewSet(ReadOnlyModelViewSet): + serializer_class = ComponentErrorSerializer + queryset = ComponentError.objects.all().order_by('-station_test__start_datetime', + 'type', + 'component__component_id', + 'component__station__name') filter_fields = '__all__' + filterset_class = ComponentErrorFilterSet class ElementErrorViewSet(ReadOnlyModelViewSet): @@ -106,146 +131,13 @@ class ElementErrorViewSet(ReadOnlyModelViewSet): filterset_class = ElementErrorFilterSet -class ElementViewSet(ReadOnlyModelViewSet): - serializer_class = ElementSerializer - queryset = Element.objects.all() - filter_fields = '__all__' - - -def insert_component_error(station_test_entry, - station_entry, - component_error): - - component = component_error.pop('component') - if 'details' in component_error: - component_error_details = component_error.pop('details') - else: - component_error_details = None - component_entry = Component.objects.filter(station=station_entry, - **component).first() - if component_entry is None: - logger.debug('Component entry is not present, inserting ...') - component_entry = Component(station=station_entry, - **component) - logger.debug(component_entry, component_error) - component_entry.save() - - component_error_entry = ComponentError.objects.filter(component=component_entry, - station_test=station_test_entry, - **component_error).first() - if component_error_entry is None: - logger.debug('Component error entry is not present, inserting ...') - if component_error_details: - component_error_details_entry = ErrorDetails(**component_error_details) - component_error_details_entry.save() - - component_error_entry = ComponentError(component=component_entry, - details=component_error_details_entry, - station_test=station_test_entry, - **component_error) - else: - component_error_entry = ComponentError(component=component_entry, - station_test=station_test_entry, - **component_error) - component_error_entry.save() - return component_error_entry - - -def insert_component_errors(station_test_entry, - station_entry, - component_errors): - for component_error in component_errors: - insert_component_error(station_test_entry, station_entry, component_error) - - -def insert_element(component, element): - element_entry = Element.objects.filter(component=component, - **element).first() - if element_entry is None: - element_entry = Element(component=component, - **element) - element_entry.save() - return element_entry - - -def insert_element_error(station_test_entry, - station_entry, - element_error): - component_error = element_error.pop('component_error') - - component_error_entry = insert_component_error(station_test_entry, - station_entry, - component_error) - component = component_error_entry.component - - element = element_error.pop('element') - element_error_details = element_error.pop('details') - - element_entry = insert_element(component, element) - - element_error_entry = ElementError.objects.filter(element=element_entry, - component_error=component_error_entry, - **element_error).first() - if element_error_entry is None: - element_error_details_entry = ErrorDetails(**element_error_details) - - element_error_details_entry.save() - - element_error_entry = ElementError(element=element_entry, - details=element_error_details_entry, - component_error=component_error_entry, - **element_error) - - element_error_entry.save() - - return element_error_entry - - -def insert_element_errors(station_test_entry, - station_entry, - element_errors): - for element in element_errors: - insert_element_error(station_test_entry, - station_entry, - element) - - -def insert_station_test(station_test): - station = station_test.pop('station') - component_errors = station_test.pop('component_errors') - element_errors = station_test.pop('element_errors') - station_entry = Station.objects.filter(**station).first() - - if station_entry is None: - logger.debug('Station is not present, inserting ...') - station_entry = Station(**station) - station_entry.save() - - station_test_entry = StationTest.objects.filter(**station_test).first() - if station_test_entry is None: - logger.debug('Station test is not present, inserting ...') - station_test_entry = StationTest(station=station_entry, - **station_test) - station_test_entry.save() - insert_component_errors(station_test_entry, - station_entry, - component_errors) - insert_element_errors(station_test_entry, - station_entry, - element_errors) - return (1,1) - - @api_view(['POST']) def insert_raw_station_test(request): """ This function is meant to parse a request of the form - { - "content": "[STATION TEST RAW TEXT]" - } + { content: [STATION TEST RAW TEXT] } parse the content field and create all the station_test entity related into the database - :param request: HTTP request - :return: + """ if request.method == 'POST': @@ -259,27 +151,22 @@ def insert_raw_station_test(request): raise Exception('cannot parse test {}'.format(content)) logger.info('data parsed successfully for test') station_test_ids = [] - station_test_ids_already_present = [] for entry in parsed_content: - existing_id, new_id = insert_station_test(entry) + station_test = StationTestSerializer.insert_station_test(entry) + station_test_ids.append(station_test.pk) except Exception as e: logger.exception("exception occurred while parsing raw station info: %s", e) return Response(exception=True, data="the post message is not correct." + " It has to be of the form \{'content':[RAWSTRING]\}: %s. Request provided %s" % ( - e, request), + e, request), status=status.HTTP_400_BAD_REQUEST) else: return Response(exception=True, data="the post message is not correct." + " It has to be of the form \{'content':[RAWSTRING]\}", status=status.HTTP_400_BAD_REQUEST) - logger.info('request processed correctly inserted %d of %d', len(station_test_ids), - len(station_test_ids) + len(station_test_ids_already_present)) + logger.info('Request processed correctly. Processed station test ids: %s', station_test_ids) return Response(status=status.HTTP_200_OK, - data='Station tests inserted with ids {} \n'.format(station_test_ids) + - 'Station tests already present with ids {}'.format( - station_test_ids_already_present - ) - ) + data='Station tests inserted with ids {} \n'.format(station_test_ids)) diff --git a/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py b/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py index 4b4ae3fff66afec29a9ae91ba15e4a7ed7bce59a..d4e7d6757761f810981ebd1a93a0cf63197c93e7 100644 --- a/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py +++ b/LCU/Maintenance/MDB_tools/cli/station_tests_watchdog.py @@ -172,7 +172,7 @@ def handle_event_file_created(mdb_address, event): def main(): setup_logging_framework() - station_tests_watchdog(sys.argv) + station_tests_watchdog(sys.argv[1:]) if __name__ == '__main__': diff --git a/LCU/Maintenance/MDB_tools/lib/client.py b/LCU/Maintenance/MDB_tools/lib/client.py index d333cbfd1ca84aadbb1d0355dca71ed767ff2f14..64036c4cfb3d9fd8ef4ba23401adbe2cba97ff7b 100644 --- a/LCU/Maintenance/MDB_tools/lib/client.py +++ b/LCU/Maintenance/MDB_tools/lib/client.py @@ -20,9 +20,9 @@ def read_stationtest_rtsm_output_to_dict(file_path): """ try: with open(file_path, 'r') as input_stream: - rtsm_filename_pattern = '(?P<station_name>\w*)_\d*_\d*.dat' + rtsm_filename_pattern = '(?P<station_name>\w*)_\d+_\d+\.dat$' result = dict(content=input_stream.read().strip('\n')) - if re.match(rtsm_filename_pattern, file_path): # Matches the filename to understand if it is a RTSM + if re.search(rtsm_filename_pattern, file_path): # Matches the filename to understand if it is a RTSM result.update(station_name=re.search(rtsm_filename_pattern, file_path).group('station_name')) return result except Exception as e: @@ -109,7 +109,7 @@ def send_stationtest_rtsm_content_to_address(address, content): :param content: content of the API call :return: True if the request was successful False otherwise """ - full_address = '/'.join([address, compose_api_url_for_given_test_type(content)]) + full_address = '/'.join([address, compose_api_url_for_given_test_type(content['content'])]) logger.info('performing request to address %s', full_address) logger.debug('request content %s', content) @@ -130,9 +130,9 @@ def compose_api_url_for_given_test_type(content): :return: the query needed to insert the raw results """ if is_station_test(content): - query = 'stationtests/insert_raw' + query = 'stationtests/raw/insert' elif is_rtsm_test(content): - query = 'rtsm/insert_raw' + query = 'rtsm/raw/insert' else: raise ValueError('The path format must refer to either an RTSM or a station test.') return query \ No newline at end of file diff --git a/LCU/Maintenance/MDB_tools/test/t_station_tests_watchdog_integration.py b/LCU/Maintenance/MDB_tools/test/t_station_tests_watchdog_integration.py index 21150f2f421e504ad72347477466067712269d0f..e7a00591d582f3af3846436734c3b4fb61906d4d 100644 --- a/LCU/Maintenance/MDB_tools/test/t_station_tests_watchdog_integration.py +++ b/LCU/Maintenance/MDB_tools/test/t_station_tests_watchdog_integration.py @@ -37,7 +37,10 @@ class TestStationTestsWatchdog(unittest.TestCase): :param path: the path to the file/directory to delete """ try: - os.remove(path) + if os.path.isfile(path): + os.remove(path) + else: + os.rmdir(path) except Exception as e: logger.exception('Cannot remove temporary file/directory %s : %s', path, e)