diff --git a/.gitattributes b/.gitattributes index 7f2e3c6ceedbecfea7dec07e74461dd98857c169..23413022bb48edeb4527613aada83afc50740858 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4488,8 +4488,14 @@ MAC/Navigator2/scripts/readStationConfigs.ctl -text MAC/Navigator2/scripts/readStationConnections.ctl -text MAC/Navigator2/scripts/setSumAlerts.ctl -text MAC/Navigator2/scripts/transferMPs.ctl -text +MAC/Services/src/ObservationControl2.py -text +MAC/Services/src/config.py -text +MAC/Services/src/observationcontrol2 -text +MAC/Services/src/observationcontrol2.ini -text MAC/Services/src/pipelinecontrol -text MAC/Services/src/pipelinecontrol.ini -text +MAC/Services/test/tObservationControl2.py -text +MAC/Services/test/tObservationControl2.sh -text MAC/Services/test/tPipelineControl.sh eol=lf MAC/Test/APL/PVSSproject/colorDB/Lofar[!!-~]colors -text svneol=native#application/octet-stream MAC/Test/APL/PVSSproject/colorDB/colorDB_de -text svneol=native#application/octet-stream diff --git a/MAC/Services/src/CMakeLists.txt b/MAC/Services/src/CMakeLists.txt index 692b7b5ce4ab157ee88c3d60c29c3c350723c0ab..665e7d6dbc5f1f87ab3718f73b795d833828bf9f 100644 --- a/MAC/Services/src/CMakeLists.txt +++ b/MAC/Services/src/CMakeLists.txt @@ -2,14 +2,18 @@ lofar_add_bin_scripts( pipelinecontrol + observationcontrol2 ) python_install( PipelineControl.py + ObservationControl2.py + config.py DESTINATION lofar/mac ) # supervisord config files install(FILES pipelinecontrol.ini + observationcontrol2.ini DESTINATION etc/supervisord.d) diff --git a/MAC/Services/src/ObservationControl2.py b/MAC/Services/src/ObservationControl2.py new file mode 100644 index 0000000000000000000000000000000000000000..82d4be7867eb849ff6de1c37da60dd38da95044e --- /dev/null +++ b/MAC/Services/src/ObservationControl2.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# +# Copyright (C) 2016 +# 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 os +import logging +from fabric import tasks +from optparse import OptionParser + +from fabric.api import env, run +from lofar.messaging import Service +from lofar.messaging import setQpidLogLevel +from lofar.common.util import waitForInterrupt +from lofar.messaging.Service import MessageHandlerInterface +from lofar.mac.config import DEFAULT_OBSERVATION_CONTROL_BUS_NAME, DEFAULT_OBSERVATION_CONTROL_SERVICE_NAME + +logger = logging.getLogger(__name__) + + +class ObservationControlHandler(MessageHandlerInterface): + def __init__(self, **kwargs): + super(ObservationControlHandler, self).__init__(**kwargs) + + self.service2MethodMap = { + 'AbortObservation': self.abort_observation + } + + env.hosts = ["localhost"] + + if os.environ.has_key("LOFARENV"): + lofar_environment = os.environ['LOFARENV'] + + if lofar_environment == "PRODUCTION": + env.hosts = ["mcu001.control.lofar"] + elif lofar_environment == "TEST": + env.hosts = ["mcu099.control.lofar"] + + def abort_observation_task(self, sas_id): + logger.info("trying to abort ObservationControl for SAS ID: %s", sas_id) + + killed = False + + pid_line = run('pidof ObservationControl') + pids = pid_line.split(' ') + + for pid in pids: + pid_sas_id = run("ps -p %s --no-heading -o command | awk -F[{}] '{ print $2; }'" % pid) + if pid_sas_id == sas_id: + logger.info("Killing ObservationControl with PID: %s for SAS ID: %s", pid, sas_id) + run('kill -SIGINT %s' % pid) + killed = True + + return killed + + def abort_observation(self, sas_id): + result = tasks.execute(self.abort_observation_task, sas_id) + aborted = True in result.values() + return {'aborted': aborted} + + def handle_message(self, msg): + pass + + +def create_service(bus_name=DEFAULT_OBSERVATION_CONTROL_BUS_NAME, service_name=DEFAULT_OBSERVATION_CONTROL_SERVICE_NAME, + broker=None, verbose=False): + return Service(service_name, + ObservationControlHandler, + busname=bus_name, + broker=broker, + use_service_methods=True, + numthreads=4, + handler_args={}, + verbose=verbose) + + +def main(): + # make sure we run in UTC timezone + import os + os.environ['TZ'] = 'UTC' + + # Check the invocation arguments + parser = OptionParser("%prog [options]", + description='runs the observationcontrol service') + parser.add_option('-q', '--broker', dest='broker', type='string', default=None, help='Address of the qpid broker, default: localhost') + parser.add_option("-b", "--busname", dest="busname", type="string", default=DEFAULT_OBSERVATION_CONTROL_BUS_NAME, help="Name of the bus exchange on the qpid broker, default: %s" % DEFAULT_OBSERVATION_CONTROL_BUS_NAME) + parser.add_option("-s", "--servicename", dest="servicename", type="string", default=DEFAULT_OBSERVATION_CONTROL_SERVICE_NAME, help="Name for this service, default: %s" % DEFAULT_OBSERVATION_CONTROL_SERVICE_NAME) + parser.add_option('-V', '--verbose', dest='verbose', action='store_true', help='verbose logging') + (options, args) = parser.parse_args() + + setQpidLogLevel(logging.INFO) + logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', + level=logging.DEBUG if options.verbose else logging.INFO) + + with create_service(bus_name=options.busname, + service_name=options.servicename, + broker=options.broker, + verbose=options.verbose): + waitForInterrupt() + + +if __name__ == '__main__': + main() diff --git a/MAC/Services/src/config.py b/MAC/Services/src/config.py new file mode 100644 index 0000000000000000000000000000000000000000..9a72a7e412a9de1436828f4026e5cb36c3947865 --- /dev/null +++ b/MAC/Services/src/config.py @@ -0,0 +1,24 @@ +#!/usr/bin/python +# +# Copyright (C) 2016 +# 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.messaging import adaptNameToEnvironment + +DEFAULT_OBSERVATION_CONTROL_BUS_NAME = adaptNameToEnvironment('lofar.mac.command') +DEFAULT_OBSERVATION_CONTROL_SERVICE_NAME = 'ObservationControl2' diff --git a/MAC/Services/src/observationcontrol2 b/MAC/Services/src/observationcontrol2 new file mode 100644 index 0000000000000000000000000000000000000000..7c08d7da4f548b53e289169f35a6a875f89c0ff7 --- /dev/null +++ b/MAC/Services/src/observationcontrol2 @@ -0,0 +1,27 @@ +#!/usr/bin/python +# +# Copyright (C) 2016 +# 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/>. + +''' +runs the ObservationControl service +''' +from lofar.mac import ObservationControl2 + +if __name__ == '__main__': + ObservationControl2.main() diff --git a/MAC/Services/src/observationcontrol2.ini b/MAC/Services/src/observationcontrol2.ini new file mode 100644 index 0000000000000000000000000000000000000000..ba538bf84860db403624c00d2fc4891b9b6f3df5 --- /dev/null +++ b/MAC/Services/src/observationcontrol2.ini @@ -0,0 +1,8 @@ +[program:observationcontrol2] +command=/bin/bash -c 'source $LOFARROOT/lofarinit.sh;exec observationcontrol2' +user=lofarsys +stopsignal=INT ; KeyboardInterrupt +stopasgroup=true ; bash does not propagate signals +stdout_logfile=%(program_name)s.log +redirect_stderr=true +stderr_logfile=NONE diff --git a/MAC/Services/test/tObservationControl2.py b/MAC/Services/test/tObservationControl2.py new file mode 100644 index 0000000000000000000000000000000000000000..85e8892b3e18fc1cbd26bca508ee90af7f1c5276 --- /dev/null +++ b/MAC/Services/test/tObservationControl2.py @@ -0,0 +1,122 @@ +import unittest +import mock +import os + +from lofar.mac.ObservationControl2 import ObservationControlHandler + + +class TestObservationControlHandler(unittest.TestCase): + pid1 = "1000" + pid2 = "2000" + + sas_id = "100" + + test_host = "mcu099.control.lofar" + production_host = "mcu001.control.lofar" + local_host = "localhost" + + def _run_side_effect(self, cmd): + if cmd.startswith("ps -p %s" % self.pid1): + return self.sas_id + elif cmd.startswith("ps -p %s" % self.pid2): + return self.sas_id + "10" + elif cmd.startswith("pidof"): + return "%s %s" % (self.pid1, self.pid2) + elif cmd.startswith("kill"): + return "" + + def setUp(self): + fabric_run_pathcher = mock.patch('lofar.mac.ObservationControl2.run') + self.addCleanup(fabric_run_pathcher.stop) + self.fabric_run_mock = fabric_run_pathcher.start() + + self.fabric_run_mock.side_effect = self._run_side_effect + + fabric_tasks_pathcher = mock.patch('lofar.mac.ObservationControl2.tasks') + self.addCleanup(fabric_tasks_pathcher.stop) + self.fabric_tasks_mock = fabric_tasks_pathcher.start() + + fabric_env_pathcher = mock.patch('lofar.mac.ObservationControl2.env') + self.addCleanup(fabric_env_pathcher.stop) + self.fabric_env_mock = fabric_env_pathcher.start() + + logger_patcher = mock.patch('lofar.mac.ObservationControl2.logger') + self.addCleanup(logger_patcher.stop) + self.logger_mock = logger_patcher.start() + + self.observation_control_handler = ObservationControlHandler() + + def test_abort_observation_task_should_run_pidof_ObservationControl(self): + self.observation_control_handler.abort_observation_task(self.sas_id) + + self.fabric_run_mock.assert_any_call('pidof ObservationControl') + + def test_abort_observation_tasks_should_run_ps_to_find_sas_id_on_command(self): + self.observation_control_handler.abort_observation_task(self.sas_id) + + self.fabric_run_mock.assert_any_call( + "ps -p %s --no-heading -o command | awk -F[{}] '{ print $2; }'" % self.pid1) + self.fabric_run_mock.assert_any_call( + "ps -p %s --no-heading -o command | awk -F[{}] '{ print $2; }'" % self.pid2) + + def test_abort_observation_task_should_run_kill_when_sas_id_matches(self): + self.observation_control_handler.abort_observation_task(self.sas_id) + + self.fabric_run_mock.assert_any_call('kill -SIGINT %s' % self.pid1) + + @mock.patch.dict(os.environ, {'LOFARENV': 'TEST'}) + def test_observation_control_should_select_test_host_if_lofar_environment_is_test(self): + ObservationControlHandler() + + self.assertEqual(self.fabric_env_mock.hosts, [self.test_host]) + + @mock.patch.dict(os.environ, {'LOFARENV': 'PRODUCTION'}) + def test_observation_control_should_select_production_host_if_lofar_environment_is_production(self): + ObservationControlHandler() + + self.assertEqual(self.fabric_env_mock.hosts, [self.production_host]) + + def test_observation_control_should_select_local_host_if_no_lofar_environment_is_set(self): + ObservationControlHandler() + + self.assertEqual(self.fabric_env_mock.hosts, [self.local_host]) + + def test_abort_observation_should_execute_abort_observation_task_on_localhost(self): + self.observation_control_handler.abort_observation(self.sas_id) + + self.fabric_tasks_mock.execute.assert_any_call(self.observation_control_handler.abort_observation_task, + self.sas_id) + + def test_abort_observation_task_should_return_false_on_unknown_sas_id(self): + self.assertFalse(self.observation_control_handler.abort_observation_task("unknown")) + + def test_abort_observation_task_should_return_true_on_known_sas_id(self): + self.assertTrue(self.observation_control_handler.abort_observation_task(self.sas_id)) + + def test_abort_observation_task_should_log_call(self): + self.observation_control_handler.abort_observation_task(self.sas_id) + + self.logger_mock.info.assert_any_call("trying to abort ObservationControl for SAS ID: %s", self.sas_id) + + def test_abort_observation_taks_should_log_the_kill(self): + self.observation_control_handler.abort_observation_task(self.sas_id) + + self.logger_mock.info.assert_any_call( + "Killing ObservationControl with PID: %s for SAS ID: %s", self.pid1, self.sas_id) + + def test_abort_observation_should_return_aborted_true_if_execute_returns_true(self): + self.fabric_tasks_mock.execute.return_value = {'localhost': True} + + result = self.observation_control_handler.abort_observation(self.sas_id) + + self.assertTrue(result['aborted']) + + def test_abort_observation_should_return_aborted_false_if_execute_returns_false(self): + self.fabric_tasks_mock.execute.return_value = {'localhost': False} + + result = self.observation_control_handler.abort_observation(self.sas_id) + + self.assertFalse(result['aborted']) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/MAC/Services/test/tObservationControl2.sh b/MAC/Services/test/tObservationControl2.sh new file mode 100644 index 0000000000000000000000000000000000000000..6634f8db752024d72592c6d3c690b367522ee263 --- /dev/null +++ b/MAC/Services/test/tObservationControl2.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./runctest.sh tObservationControl2 \ No newline at end of file