From 3c7a58a5c389287b80e8f345cba6b20ca05c969e Mon Sep 17 00:00:00 2001 From: Roy de Goei <goei@astron.nl> Date: Thu, 25 Mar 2021 17:39:03 +0100 Subject: [PATCH] TMSS-687: precalculation service created. Also added in tmss_test_environment --- SAS/TMSS/backend/services/CMakeLists.txt | 2 + .../precalculations_service/CMakeLists.txt | 10 + .../bin/CMakeLists.txt | 4 + .../bin/tmss_precalculations_service | 24 ++ .../bin/tmss_precalculations_service.ini | 9 + .../lib/CMakeLists.txt | 10 + .../lib/precalculations_service.py | 130 +++++++++ .../test/CMakeLists.txt | 7 + .../test/t_precalculations_service.py | 246 ++++++++++++++++++ .../test/t_precalculations_service.run | 6 + .../test/t_precalculations_service.sh | 3 + .../backend/src/tmss/tmssapp/conversions.py | 1 + SAS/TMSS/backend/src/tmss/tmssapp/populate.py | 2 +- SAS/TMSS/backend/test/test_utils.py | 97 ++----- 14 files changed, 475 insertions(+), 76 deletions(-) create mode 100644 SAS/TMSS/backend/services/precalculations_service/CMakeLists.txt create mode 100644 SAS/TMSS/backend/services/precalculations_service/bin/CMakeLists.txt create mode 100755 SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service create mode 100644 SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service.ini create mode 100644 SAS/TMSS/backend/services/precalculations_service/lib/CMakeLists.txt create mode 100644 SAS/TMSS/backend/services/precalculations_service/lib/precalculations_service.py create mode 100644 SAS/TMSS/backend/services/precalculations_service/test/CMakeLists.txt create mode 100644 SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.py create mode 100755 SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.run create mode 100755 SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.sh diff --git a/SAS/TMSS/backend/services/CMakeLists.txt b/SAS/TMSS/backend/services/CMakeLists.txt index 2fb200270ac..de9c7990be1 100644 --- a/SAS/TMSS/backend/services/CMakeLists.txt +++ b/SAS/TMSS/backend/services/CMakeLists.txt @@ -6,4 +6,6 @@ lofar_add_package(TMSSPostgresListenerService tmss_postgres_listener) lofar_add_package(TMSSWebSocketService websocket) lofar_add_package(TMSSWorkflowService workflow_service) lofar_add_package(TMSSLTAAdapter tmss_lta_adapter) +lofar_add_package(TMSSPreCalculationsService precalculations_service) + diff --git a/SAS/TMSS/backend/services/precalculations_service/CMakeLists.txt b/SAS/TMSS/backend/services/precalculations_service/CMakeLists.txt new file mode 100644 index 00000000000..1c52667c78f --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/CMakeLists.txt @@ -0,0 +1,10 @@ +lofar_package(TMSSPreCalculationsService 0.1) + +lofar_find_package(PythonInterp 3.4 REQUIRED) + +IF(NOT SKIP_TMSS_BUILD) + add_subdirectory(lib) + add_subdirectory(test) +ENDIF(NOT SKIP_TMSS_BUILD) + +add_subdirectory(bin) \ No newline at end of file diff --git a/SAS/TMSS/backend/services/precalculations_service/bin/CMakeLists.txt b/SAS/TMSS/backend/services/precalculations_service/bin/CMakeLists.txt new file mode 100644 index 00000000000..80db184789d --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/bin/CMakeLists.txt @@ -0,0 +1,4 @@ +lofar_add_bin_scripts(tmss_precalculations_service) + +# supervisord config files +lofar_add_sysconf_files(tmss_precalculations_service.ini DESTINATION supervisord.d) diff --git a/SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service b/SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service new file mode 100755 index 00000000000..2bcfee690f1 --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service @@ -0,0 +1,24 @@ +#!/usr/bin/python3 + +# Copyright (C) 2012-2015 ASTRON (Netherlands Institute for Radio Astronomy) +# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. + + +from lofar.sas.tmss.services.precalculations_service import main + +if __name__ == "__main__": + main() diff --git a/SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service.ini b/SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service.ini new file mode 100644 index 00000000000..af4fd3b1951 --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/bin/tmss_precalculations_service.ini @@ -0,0 +1,9 @@ +[program:tmss_precalculations_service] +command=docker run --rm --net=host -u 7149:7149 -v /opt/lofar/var/log:/opt/lofar/var/log -v /tmp/tmp -v /etc/passwd:/etc/passwd:ro -v /etc/group:/etc/group:ro -v /localhome/lofarsys:/localhome/lofarsys -e HOME=/localhome/lofarsys -e USER=lofarsys nexus.cep4.control.lofar:18080/tmss_django:latest /bin/bash -c 'source ~/.lofar/.lofar_env;source $LOFARROOT/lofarinit.sh;exec tmss_websocket_service' +user=lofarsys +stopsignal=INT ; KeyboardInterrupt +stopasgroup=true ; bash does not propagate signals +stdout_logfile=%(program_name)s.log +redirect_stderr=true +stderr_logfile=NONE +stdout_logfile_maxbytes=0 diff --git a/SAS/TMSS/backend/services/precalculations_service/lib/CMakeLists.txt b/SAS/TMSS/backend/services/precalculations_service/lib/CMakeLists.txt new file mode 100644 index 00000000000..31845d50643 --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/lib/CMakeLists.txt @@ -0,0 +1,10 @@ +lofar_find_package(PythonInterp 3.4 REQUIRED) +include(PythonInstall) + +set(_py_files + precalculations_service.py + ) + +python_install(${_py_files} + DESTINATION lofar/sas/tmss/services) + diff --git a/SAS/TMSS/backend/services/precalculations_service/lib/precalculations_service.py b/SAS/TMSS/backend/services/precalculations_service/lib/precalculations_service.py new file mode 100644 index 00000000000..94dbc28ff57 --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/lib/precalculations_service.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2012-2015 ASTRON (Netherlands Institute for Radio Astronomy) +# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. + +import logging +logger = logging.getLogger(__name__) + +import os +import threading +import datetime +from datetime import timedelta +import time +from lofar.common.util import waitForInterrupt + +# Default values of parameters +INTERVAL_TIME_SECONDS = 12 * 60 * 60 # 12 hours for now +NBR_DAYS_CALCULATE_AHEAD = 365 # 1 year +NBR_DAYS_BEFORE_TODAY = 1 + + +def execute_populate_calculations(nbr_days_calculate_ahead, start_date): + """ + Execute the populate of calculations (sunrise/sunset) for given number of days stating at give date + :param nbr_days_calculate_ahead: Number of days to calculated + :param start_date: The date to start calculate + :return next_date: The next_date to process + """ + logger.info("execute_populate_calculations %s for %d days" % (start_date, nbr_days_calculate_ahead)) + # Import here otherwise you get + # "django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings." + from lofar.sas.tmss.tmss.tmssapp.populate import populate_calculations + + populate_calculations(nbr_days=nbr_days_calculate_ahead, start_date=start_date) + # Return the next_date to process + next_date = start_date + datetime.timedelta(days=nbr_days_calculate_ahead) + return next_date + + +class TMSSPreCalculationsServiceJob(threading.Thread): + def __init__(self, interval, execute, *args, **kwargs): + threading.Thread.__init__(self) + self.daemon = False + self.stopped = threading.Event() + self.interval = interval + self.execute = execute + self.args = args + self.kwargs = kwargs + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + def stop(self): + self.stopped.set() + self.join() + + def run(self): + start_time = time.time() + next_date = self.execute(*self.args, **self.kwargs) + # determine remaining time for exact heartbeat of the interval time + remaining_wait_time_in_sec = self.interval.total_seconds() - (time.time() - start_time) + while not self.stopped.wait(remaining_wait_time_in_sec): + self.kwargs["nbr_days_calculate_ahead"] = 1 + self.kwargs["start_date"] = next_date + start_time = time.time() + next_date = self.execute(*self.args, **self.kwargs) + remaining_wait_time_in_sec = self.interval.total_seconds() - (time.time() - start_time) + + +def create_calculation_service_job(interval_time, nbr_days_calculate_ahead, nbr_days_before_today): + start_date = datetime.date.today() - datetime.timedelta(days=nbr_days_before_today) + return TMSSPreCalculationsServiceJob(interval=timedelta(seconds=interval_time), + execute=execute_populate_calculations, + nbr_days_calculate_ahead=nbr_days_calculate_ahead, start_date=start_date) + + +def main(): + # make sure we run in UTC timezone + os.environ['TZ'] = 'UTC' + + from optparse import OptionParser, OptionGroup + from lofar.common import dbcredentials + + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) + + # Check the invocation arguments + parser = OptionParser('%prog [options]', + description='run the tmss_workflow_service which forwards TMSS events to the workflow engine.') + + parser.add_option('-i', '--interval_time', dest='interval_time', type='int', default=INTERVAL_TIME_SECONDS, + help='The time between next calculation, default: %default') + parser.add_option('-d', '--nbr_days_calculate_ahead', dest='nbr_days_calculate_ahead', type='int', default=NBR_DAYS_CALCULATE_AHEAD, + help='The number of days to calculate the sunset/sunrise ahead, default: %default') + parser.add_option('-b', '--nbr_days_before_today', dest='nbr_days_before_today', type='int', default=NBR_DAYS_BEFORE_TODAY, + help='The number of days to calculate the sunset/sunrise before today (so yesterday=1), default: %default') + + group = OptionGroup(parser, 'Django options') + parser.add_option_group(group) + group.add_option('-C', '--credentials', dest='dbcredentials', type='string', default=os.environ.get('TMSS_DBCREDENTIALS', 'TMSS'), help='django dbcredentials name, default: %default') + + (options, args) = parser.parse_args() + from lofar.sas.tmss.tmss import setup_and_check_tmss_django_database_connection_and_exit_on_error + setup_and_check_tmss_django_database_connection_and_exit_on_error(options.dbcredentials) + + job = create_calculation_service_job(options.interval_time, options.nbr_days_calculate_ahead, options.nbr_days_before_today) + job.start() + waitForInterrupt() + job.stop() + + +if __name__ == '__main__': + main() + diff --git a/SAS/TMSS/backend/services/precalculations_service/test/CMakeLists.txt b/SAS/TMSS/backend/services/precalculations_service/test/CMakeLists.txt new file mode 100644 index 00000000000..0b9f8a89855 --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/test/CMakeLists.txt @@ -0,0 +1,7 @@ +# $Id: CMakeLists.txt 32679 2015-10-26 09:31:56Z schaap $ + +if(BUILD_TESTING) + include(LofarCTest) + + lofar_add_test(t_precalculations_service) +endif() diff --git a/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.py b/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.py new file mode 100644 index 00000000000..0648bfd45fd --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2012-2015 ASTRON (Netherlands Institute for Radio Astronomy) +# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands +# +# This file is part of the LOFAR software suite. +# The LOFAR software suite is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The LOFAR software suite is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>. + +import unittest +import time +import datetime +import logging +logger = logging.getLogger('lofar.' + __name__) +logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO) + + +from lofar.sas.tmss.test.test_utils import TMSSTestEnvironment + +from lofar.sas.tmss.services.precalculations_service import create_calculation_service_job +from lofar.common.test_utils import integration_test, exit_with_skipped_code_if_skip_integration_tests + +exit_with_skipped_code_if_skip_integration_tests() + + + +@integration_test +class TestPreCalculationService(unittest.TestCase): + """ + Tests for the TMSSPreCalculationsServiceJob + It will check the number of items created of the StationTimeline model based on the input of the service to start + It will not check the content of the sunrise/sunset data of the StationTimeline model itself + Note that 1 day calculation will take about 6 seconds + """ + + @classmethod + def setUpClass(cls) -> None: + """ + Populate schema to be able to retrieve all stations + """ + cls.tmss_test_env = TMSSTestEnvironment(populate_schemas=True) + cls.tmss_test_env.start() + cls.test_data_creator = cls.tmss_test_env.create_test_data_creator() + + @classmethod + def tearDownClass(cls) -> None: + cls.tmss_test_env.stop() + + def setUp(self) -> None: + """ + Start every testcase with 'clean' StationTimeline model + """ + from lofar.sas.tmss.tmss.tmssapp.models.calculations import StationTimeline + StationTimeline.objects.all().delete() + + def test_all_stations_calculated_for_one_day(self): + """ + Test if creating and starting, followed by stopping the (pre)calculation service results in 'one day' + of StationTimeline data for all stations + Note that 1 day calculation will take about 6 seconds + """ + # Import here otherwise you get + # "django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings." + from lofar.sas.tmss.tmss.tmssapp.conversions import get_all_stations + from lofar.sas.tmss.tmss.tmssapp.models.calculations import StationTimeline + + nbr_stations = len(get_all_stations()) + # Initially there should be no data + self.assertEqual(0, len(StationTimeline.objects.all())) + # Now we are going to create and start the calculation service with a wait time of 60 sec, + # nbr days to calculate ahead is 1 and nbr days before today 1 -> so only 'yesterday' should be created + job = create_calculation_service_job(60, 1, 1) + job.start() + job.stop() + # Check what have been created + st_objects = StationTimeline.objects.all() + self.assertEqual(nbr_stations, len(st_objects)) + # lets check with the timestamp of today, that should be zero + st_objects = StationTimeline.objects.filter(timestamp=datetime.date.today()) + self.assertEqual(0, len(st_objects)) + # lets check with the timestamp in future, that should be zero + st_objects = StationTimeline.objects.filter(timestamp__gt=datetime.date.today()) + self.assertEqual(0, len(st_objects)) + # lets check with the timestamp yesterday, that should be equal to the number of all stations + st_objects = StationTimeline.objects.filter(timestamp=datetime.date.today()-datetime.timedelta(days=1)) + self.assertEqual(nbr_stations, len(st_objects)) + + def test_all_stations_calculated_for_multiple_days_with_one_trigger(self): + """ + Test if creating and starting, followed by stopping the (pre)calculation service results in 'multiple day' + of StationTimeline data for all stations + Note that 4 days calculation will take about 30 seconds + """ + # Import here otherwise you get + # "django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings." + from lofar.sas.tmss.tmss.tmssapp.conversions import get_all_stations + from lofar.sas.tmss.tmss.tmssapp.models.calculations import StationTimeline + + nbr_stations = len(get_all_stations()) + # Initially there should be no data + self.assertEqual(0, len(StationTimeline.objects.all())) + # Now we are going to create and start the calculation service with a interval of 60 sec, + # nbr days to calculate ahead is 4 and nbr days before today 2 -> so 'day before yesterday, 'yesterday', + # 'today' and 'tomorrow' should be created + job = create_calculation_service_job(60, 4, 2) + job.start() + job.stop() + # Check what have been created + st_objects = StationTimeline.objects.all() + self.assertEqual(4*nbr_stations, len(st_objects)) + # lets check with the timestamp of today, that should be equal to the number of all stations + st_objects = StationTimeline.objects.filter(timestamp=datetime.date.today()) + self.assertEqual(nbr_stations, len(st_objects)) + # lets check with the timestamp in future, that should be equal to the number of all stations + st_objects = StationTimeline.objects.filter(timestamp__gt=datetime.date.today()) + self.assertEqual(nbr_stations, len(st_objects)) + # lets check with the timestamp in the past, that should be equal to the 2 times number of all stations + st_objects = StationTimeline.objects.filter(timestamp__lt=datetime.date.today()) + self.assertEqual(2*nbr_stations, len(st_objects)) + + def test_all_stations_calculated_for_multiple_days(self): + """ + Test if creating and starting, waiting for period (25 seconds), followed by stopping the (pre)calculation service results + in 'multiple day' of StationTimeline data for all stations. + It will test the scheduler with interval of 10 seconds, so three days should be calculated + """ + # Import here otherwise you get + # "django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings." + from lofar.sas.tmss.tmss.tmssapp.conversions import get_all_stations + from lofar.sas.tmss.tmss.tmssapp.models.calculations import StationTimeline + + nbr_stations = len(get_all_stations()) + # Initially there should be no data + self.assertEqual(0, len(StationTimeline.objects.all())) + # Now we are going to create and start the calculation service with a interval of 10 sec (smaller will not make sense), + # nbr days to calculate ahead is 1 and nbr days before today 0 -> so it start with 'today' and after 10 seconds + # 'tomorrow' etc.., + job = create_calculation_service_job(10, 1, 0) + job.start() + time.sleep(25) + job.stop() + # Check what have been created with interval of 10 seconds we should have three days + st_objects = StationTimeline.objects.all() + self.assertEqual(3*nbr_stations, len(st_objects)) + # lets check with the timestamp of today, that should be equal to the number of all stations + st_objects = StationTimeline.objects.filter(timestamp=datetime.date.today()) + self.assertEqual(nbr_stations, len(st_objects)) + # lets check with the timestamp in future, that should be equal to 2 times the number of all stations + st_objects = StationTimeline.objects.filter(timestamp__gt=datetime.date.today()) + self.assertEqual(2*nbr_stations, len(st_objects)) + # lets check with the timestamp in the past, that should be equal to zero + st_objects = StationTimeline.objects.filter(timestamp__lt=datetime.date.today()) + self.assertEqual(0, len(st_objects)) + + def test_all_stations_calculated_for_when_interval_time_is_too_small(self): + """ + Check that if the interval time < calculation time it does not lead to exception + Test if creating and starting, waiting for period (10 seconds), followed by stopping the (pre)calculation service results + in 'multiple day' of StationTimeline data for all stations. + It will test the scheduler with interval of 2 seconds, which smaller than ~6 seconds + Stopping after 10 seconds makes 2 days calculated + """ + # Import here otherwise you get + # "django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings." + from lofar.sas.tmss.tmss.tmssapp.conversions import get_all_stations + from lofar.sas.tmss.tmss.tmssapp.models.calculations import StationTimeline + + nbr_stations = len(get_all_stations()) + # Initially there should be no data + self.assertEqual(0, len(StationTimeline.objects.all())) + # Now we are going to create and start the calculation service with an interval of 2 sec + # nbr days to calculate ahead is 1 and nbr days before today 0 -> so it start with 'today' and after ~6 seconds + # 'tomorrow' etc.. + job = create_calculation_service_job(2, 1, 0) + job.start() + time.sleep(10) + job.stop() + # Check what have been created with interval of 2 seconds we should have two days + st_objects = StationTimeline.objects.all() + self.assertEqual(2 * nbr_stations, len(st_objects)) + # lets check with the timestamp of today, that should be equal to the number of all stations + st_objects = StationTimeline.objects.filter(timestamp=datetime.date.today()) + self.assertEqual(nbr_stations, len(st_objects)) + # lets check with the timestamp in future, that should be equal to the number of all stations + st_objects = StationTimeline.objects.filter(timestamp__gt=datetime.date.today()) + self.assertEqual(nbr_stations, len(st_objects)) + # lets check with the timestamp in the past, that should be equal to zero + st_objects = StationTimeline.objects.filter(timestamp__lt=datetime.date.today()) + self.assertEqual(0, len(st_objects)) + + +@integration_test +class TestPreCalculationServiceWithoutSchemas(unittest.TestCase): + """ + Tests for creation/start/stop of the TMSSPreCalculationsServiceJob when NO schemas exist + Check that no exception occur + """ + @classmethod + def setUpClass(cls) -> None: + """ + Do not populate schemas + """ + cls.tmss_test_env = TMSSTestEnvironment(populate_schemas=False) + cls.tmss_test_env.start() + cls.test_data_creator = cls.tmss_test_env.create_test_data_creator() + + @classmethod + def tearDownClass(cls) -> None: + cls.tmss_test_env.stop() + + def test_no_stations_calculated(self): + """ + Test when no stations exist, the StationTimeLine is empty + """ + # Import here otherwise you get + # "django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings." + from lofar.sas.tmss.tmss.tmssapp.conversions import get_all_stations + from lofar.sas.tmss.tmss.tmssapp.models.calculations import StationTimeline + + nbr_stations = len(get_all_stations()) + self.assertEqual(0, nbr_stations) + self.assertEqual(0, len(StationTimeline.objects.all())) + # Now we are going to create and start the calculation service with a interval time of 60 sec, + # nbr days to calculate ahead is 1 and nbr days before today 1 -> + # nothing will be created because there are no stations + job = create_calculation_service_job(60, 1, 1) + job.start() + job.stop() + nbr_stations = len(get_all_stations()) + self.assertEqual(0, nbr_stations) + self.assertEqual(0, len(StationTimeline.objects.all())) + + +if __name__ == '__main__': + #run the unit tests + unittest.main() \ No newline at end of file diff --git a/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.run b/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.run new file mode 100755 index 00000000000..187c3bf1e7b --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.run @@ -0,0 +1,6 @@ +#!/bin/bash + +# Run the unit test +source python-coverage.sh +python_coverage_test "*tmss*" t_precalculations_service.py + diff --git a/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.sh b/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.sh new file mode 100755 index 00000000000..54b180d5254 --- /dev/null +++ b/SAS/TMSS/backend/services/precalculations_service/test/t_precalculations_service.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./runctest.sh t_precalculations_service \ No newline at end of file diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py index 8292f5d6c79..d6d2e3a1b0d 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/conversions.py @@ -31,6 +31,7 @@ def create_astroplan_observer_for_station(station: str) -> 'Observer': # default angle to the horizon at which the sunset/sunrise starts and ends, as per LOFAR definition. SUN_SET_RISE_ANGLE_TO_HORIZON = Angle(10, unit=astropy.units.deg) # default n_grid_points; higher is more precise but very costly; astropy defaults to 150, errors now can be in the minutes, increase if this is not good enough +# TODO: To be considered, now we store the sunset/sunrise data in advanced, we can increase the number of points!! SUN_SET_RISE_PRECISION = 30 diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py index 3b3147b6774..90ebaae046d 100644 --- a/SAS/TMSS/backend/src/tmss/tmssapp/populate.py +++ b/SAS/TMSS/backend/src/tmss/tmssapp/populate.py @@ -498,4 +498,4 @@ def populate_calculations(nbr_days=3, start_date=date.today()): lst_timestamps.append(dt) timestamps_and_stations_to_sun_rise_and_set(tuple(lst_timestamps), tuple(get_all_stations()), create_when_not_found=True) - logger.info("Done in %.1fs", (datetime.utcnow()-starttime_for_logging).total_seconds()) + logger.info("Populate sunrise and sunset done in %.1fs", (datetime.utcnow()-starttime_for_logging).total_seconds()) diff --git a/SAS/TMSS/backend/test/test_utils.py b/SAS/TMSS/backend/test/test_utils.py index 9f6b7c1889f..c03be87745c 100644 --- a/SAS/TMSS/backend/test/test_utils.py +++ b/SAS/TMSS/backend/test/test_utils.py @@ -286,6 +286,7 @@ class TMSSTestEnvironment: start_pipeline_control: bool=False, start_websocket: bool=False, start_feedback_service: bool=False, start_workflow_service: bool=False, enable_viewflow: bool=False, + start_precalculations_service: bool=False, ldap_dbcreds_id: str=None, db_dbcreds_id: str=None, client_dbcreds_id: str=None): self._exchange = exchange self._broker = broker @@ -332,6 +333,9 @@ class TMSSTestEnvironment: self.workflow_service = None os.environ['TMSS_ENABLE_VIEWFLOW'] = str(bool(self.enable_viewflow)) + self._start_precalculations_service = start_precalculations_service + self.precalculations_service = None + # Check for correct Django version, should be at least 3.0 if django.VERSION[0] < 3: print("\nWARNING: YOU ARE USING DJANGO VERSION '%s', WHICH WILL NOT SUPPORT ALL FEATURES IN TMSS!\n" % @@ -430,6 +434,8 @@ class TMSSTestEnvironment: except Exception as e: logger.exception(e) + + # wait for all services to be fully started in their background threads for thread in service_threads: thread.join() @@ -447,6 +453,14 @@ class TMSSTestEnvironment: logger.info("started TMSSTestEnvironment ldap/database/django + services + schemas + data in %.1fs", (datetime.datetime.utcnow()-starttime).total_seconds()) + # next service does not have a buslistener, it is just a simple time scheduler and currently rely and + # populated stations schema to retrieve all stations + if self._start_precalculations_service: + from lofar.sas.tmss.services.precalculations_service import create_calculation_service_job + # For testpurposes we can use a smaller range and higher interval frequency + self.precalculations_service = \ + create_calculation_service_job(wait_time_seconds=60, nbr_days_calculate_ahead=3, nbr_days_before_today=1) + self.precalculations_service.start() def stop(self): if self.workflow_service is not None: @@ -477,6 +491,10 @@ class TMSSTestEnvironment: self.ra_test_environment.stop() self.ra_test_environment = None + if self.precalculations_service is not None: + self.precalculations_service.stop() + self.precalculations_service = None + self.django_server.stop() self.ldap_server.stop() self.database.destroy() @@ -518,6 +536,7 @@ class TMSSTestEnvironment: from lofar.sas.tmss.test.tmss_test_data_rest import TMSSRESTTestDataCreator return TMSSRESTTestDataCreator(self.django_server.url, (self.django_server.ldap_dbcreds.user, self.django_server.ldap_dbcreds.password)) + def main_test_database(): """instantiate, run and destroy a test postgress django database""" os.environ['TZ'] = 'UTC' @@ -550,6 +569,7 @@ def main_test_database(): print("Press Ctrl-C to exit (and remove the test database automatically)") waitForInterrupt() + def main_test_environment(): """instantiate, run and destroy a full tmss test environment (postgress database, ldap server, django server)""" from optparse import OptionParser, OptionGroup @@ -583,9 +603,9 @@ def main_test_environment(): group.add_option('-V', '--viewflow_service', dest='viewflow_service', action='store_true', help='Enable the viewflow service. Implies --viewflow_app and --eventmessages') group.add_option('-w', '--websockets', dest='websockets', action='store_true', help='Enable json updates pushed via websockets') group.add_option('-f', '--feedbackservice', dest='feedbackservice', action='store_true', help='Enable feedbackservice to handle feedback from observations/pipelines which comes in via the (old qpid) otdb messagebus.') + group.add_option('-C', '--precalculations_service', dest='precalculations_service', action='store_true', help='Enable the PreCalculations service') group.add_option('--all', dest='all', action='store_true', help='Enable/Start all the services, upload schemas and testdata') group.add_option('--simulate', dest='simulate', action='store_true', help='Simulate a run of the first example scheduling_unit (implies --data and --eventmessages and --ra_test_environment)') - group.add_option('--calculation_service', dest='calculation_service', action='store_true', help='Enable the (Pre-)Calculations service') group = OptionGroup(parser, 'Messaging options') parser.add_option_group(group) @@ -623,6 +643,7 @@ def main_test_environment(): start_feedback_service=options.feedbackservice or options.all, enable_viewflow=options.viewflow_app or options.viewflow_service or options.all, start_workflow_service=options.viewflow_service or options.all, + start_precalculations_service=options.precalculations_service or options.all, ldap_dbcreds_id=options.LDAP_ID, db_dbcreds_id=options.DB_ID, client_dbcreds_id=options.REST_CLIENT_ID) as tmss_test_env: # print some nice info for the user to use the test servers... @@ -657,16 +678,6 @@ def main_test_environment(): except KeyboardInterrupt: return - # This is just a 'simple' timing service - if options.calculation_service: - stop_event = threading.Event() - create_calculation_service(stop_event=stop_event) - try: - stop_event.wait() - except KeyboardInterrupt: - print("KeyboardInterrupt") - return - waitForInterrupt() @@ -969,70 +980,6 @@ def main_scheduling_unit_blueprint_simulator(): pass -def create_calculation_service(stop_event: threading.Event): - """ - First implementation of a simple time scheduler, starting with one major task and then run once a day - It created and start the populate of sunset/sunrise calculations - Should be organised a little bit differently and better!!!!!!! - """ - print("create_calculation_service") - - import threading, time, signal - from datetime import timedelta - # Import here otherwise you get - # "django.core.exceptions.ImproperlyConfigured: Requested setting INSTALLED_APPS, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings." - from lofar.sas.tmss.tmss.tmssapp.populate import populate_calculations - - # Some parameters, currently as constants - WAIT_TIME_SECONDS = 300 - NBR_DAYS_CALCULATE_AHEAD = 100 - NBR_DAYS_BEFORE_TODAY = 1 - - - class ProgramKilled(Exception): - pass - - def populate_calculations_once_a_day(): - days_offset = NBR_DAYS_CALCULATE_AHEAD - NBR_DAYS_BEFORE_TODAY - populate_calculations(nbr_days=1, start_date=datetime.date.today() + datetime.timedelta(days=days_offset)) - - def signal_handler(signum, frame): - raise ProgramKilled - - class Job(threading.Thread): - def __init__(self, interval, execute, *args, **kwargs): - threading.Thread.__init__(self) - self.daemon = False - self.stopped = threading.Event() - self.interval = interval - self.execute = execute - self.args = args - self.kwargs = kwargs - - def stop(self): - self.stopped.set() - self.join() - - def run(self): - while not self.stopped.wait(self.interval.total_seconds()): - self.execute(*self.args, **self.kwargs) - - signal.signal(signal.SIGTERM, signal_handler) - signal.signal(signal.SIGINT, signal_handler) - job = Job(interval=timedelta(seconds=WAIT_TIME_SECONDS), execute=populate_calculations_once_a_day) - job.start() - - # Start one day before today - populate_calculations(nbr_days=NBR_DAYS_CALCULATE_AHEAD, start_date=datetime.date.today() - datetime.timedelta(days=NBR_DAYS_BEFORE_TODAY)) - - print("Execute Every %d sec" % WAIT_TIME_SECONDS ) - while True: - try: - time.sleep(10) - except ProgramKilled: - print("Program killed: running cleanup code") - job.stop() - break if __name__ == '__main__': -- GitLab