diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a39700f45ba2d88ca87545cc25b2b9a7cdeab53d..4bbc1e0d0ccd6916c4175d0d9404b9779b80eff3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,12 +56,11 @@ list_dependencies: - public linting: - image: nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:latest - tags: - - docker-executor stage: linting script: - - make lint + - pip3 install pylint2junit + - pylint --output-format=parseable cspse | tee ./build/code_analysis.stdout + - pylint --output-format=pylint2junit.JunitReporter cspse > ./build/reports/linting.xml; when: always artifacts: paths: @@ -98,4 +97,4 @@ create ci metrics: # Gitlab CI badges creation: END artifacts: paths: - - ./build \ No newline at end of file + - ./build diff --git a/.pylintrc b/.pylintrc index eae6a06eec1ab98f68a4c854b6b56a5cb1c7bc63..a708e2a2cfaca4c804bd0405a9556a04b13bc2ac 100644 --- a/.pylintrc +++ b/.pylintrc @@ -28,7 +28,7 @@ limit-inference-results=100 # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. -load-plugins=pylint_junit +load-plugins= # Pickle collected data for later comparisons. persistent=yes @@ -95,7 +95,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # Set the output format. Available formats are text, parseable, colorized, json # and msvs (visual studio). You can also give a reporter class, e.g. # mypackage.mymodule.MyReporterClass. -output-format=junit +output-format=text # Tells whether to display a full report or only the messages. reports=yes diff --git a/Makefile b/Makefile deleted file mode 100644 index 462303c2ea953f4823124ee905744a602d4b6fb1..0000000000000000000000000000000000000000 --- a/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -# Use bash shell with pipefail option enabled so that the return status of a -# piped command is the value of the last (rightmost) commnand to exit with a -# non-zero status. This lets us pipe output into tee but still exit on test -# failures. -SHELL = /bin/bash -.SHELLFLAGS = -o pipefail -c - -all: test lint - -# The following steps copy across useful output to this volume which can -# then be extracted to form the CI summary for the test procedure. -test: - - python setup.py test | tee ./build/setup_py_test.stdout; \ - mv coverage.xml ./build/reports/code-coverage.xml; - -# The following steps copy across useful output to this volume which can -# then be extracted to form the CI summary for the test procedure. -lint: - - # FIXME pylint needs to run twice since there is no way go gather the text and junit xml output at the same time - pip3 install pylint2junit; \ - pylint --output-format=parseable ska_python_skeleton | tee ./build/code_analysis.stdout; \ - pylint --output-format=pylint2junit.JunitReporter ska_python_skeleton > ./build/reports/linting.xml; - - -.PHONY: all test lint diff --git a/README.md b/README.md index 94e87282c993f831eb41e4861abb141f39354eb9..3a0aceee92155c677ee2a4cc7458ba78434efe28 100644 --- a/README.md +++ b/README.md @@ -1,78 +1,76 @@ -SKA Python Skeleton Project -=========================== +SKA CSP-LMC-SUBELEMENT Abstract Class +===================================== [![Documentation Status](https://readthedocs.org/projects/ska-python-skeleton/badge/?version=latest)](https://developer.skatelescope.org/projects/skeleton/en/latest/?badge=latest) -Briefly describe your project here +## Table of contents +* [Description](#description) +* [Getting started](#getting-started) +* [Repository](#repository) +* [Prerequisities](#prerequisities) +* [Running tests](#running-tests) +* [Known bugs](#known-bugs) +* [Troubleshooting](#troubleshooting) +* [License](#license) -Requirements ------------- +## Description -The system used for development needs to have Python 3 and `pip` installed. +This project contains the `CSP.LMC.SUBELEMENT` prototype. It includes a +single class: -Install -------- +* the `CspSubElementMaster` device: based on the `CspMaster` class. The +`CspSubElementMaster` represents a primary point of contact for CSP +SubElement Monitor and Control. It implements CSP SubElement state and +mode indicators and a limited set of housekeeping commands. +It is intended to connect to the various subcomponent of SubElement. This +can be accomplished directly or by means of a _caching_ device, called +`Rack`. Of course it is a device collector and can or cannot correspond to a +physical rack. -**Always** use a virtual environment. [Pipenv](https://pipenv.readthedocs.io/en/latest/) is now Python's officially -recommended method, but we are not using it for installing requirements when building on the CI Pipeline. You are encouraged to use your preferred environment isolation (i.e. `pip`, `conda` or `pipenv` while developing locally. -For working with `Pipenv`, follow these steps at the project root: +## Getting started -First, ensure that `~/.local/bin` is in your `PATH` with: -```bash -> echo $PATH -``` +The project can be found in the SKA gitlab repository. + +To get a local copy of the project: -In case `~/.local/bin` is not part of your `PATH` variable, under Linux add it with: ```bash -> export PATH=~/.local/bin:$PATH +git clone https://gitlab.com/ska-telescope/csp-lmc-subelement.git ``` -or the equivalent in your particular OS. +## Prerequisities + +* A TANGO development environment properly configured, as described in [SKA developer portal](https://developer.skatelescope.org/en/latest/tools/tango-devenv-setup.html) + +* [SKA Base classes](https://gitlab.com/ska-telescope/lmc-base-classes) + + +## Repository organization + +The `CSP.LMC.SUBELEMENT` repository is organized in a single code tree. The +hierarchy contains: + +* _cspse_: contains the specific project TANGO Device Class files +* _pogo_: contains the POGO files of the TANGO Device Classes of the project +* _docker_: contains the `docker`, `docker-compose` and `dsconfig` configuration files as well as the Makefile to generate the docker image and run the tests. +* _tests_: contains the test -Then proceed to install pipenv and the required environment packages: +## Running tests + +To run the internal test go to `tests` directory and execute: ```bash -> pip install pipenv # if you don't have pipenv already installed on your system -> pipenv install -> pipenv shell +make test ``` -You will now be inside a pipenv shell with your virtual environment ready. - -Use `exit` to exit the pipenv environment. - - -Testing -------- - -* Put tests into the `tests` folder -* Use [PyTest](https://pytest.org) as the testing framework - - Reference: [PyTest introduction](http://pythontesting.net/framework/pytest/pytest-introduction/) -* Run tests with `python setup.py test` - - Configure PyTest in `setup.py` and `setup.cfg` -* Running the test creates the `htmlcov` folder - - Inside this folder a rundown of the issues found will be accessible using the `index.html` file -* All the tests should pass before merging the code - - Code analysis - ------------- - * Use [Pylint](https://www.pylint.org) as the code analysis framework - * By default it uses the [PEP8 style guide](https://www.python.org/dev/peps/pep-0008/) - * Use the provided `code-analysis.sh` script in order to run the code analysis in the `module` and `tests` - * Code analysis should be run by calling `pylint ska_python_skeleton`. All pertaining options reside under the `.pylintrc` file. - * Code analysis should only raise document related warnings (i.e. `#FIXME` comments) before merging the code - -Writing documentation - -------------------- - * The documentation generator for this project is derived from SKA's [SKA Developer Portal repository](https://github.com/ska-telescope/developer.skatelescope.org) - * The documentation can be edited under `./docs/src` - * If you want to include only your README.md file, create a symbolic link inside the `./docs/src` directory if the existing one does not work: - ```bash -$ cd docs/src -$ ln -s ../../README.md README.md -``` - * In order to build the documentation for this specific project, execute the following under `./docs`: - ```bash -$ make html -``` -* The documentation can then be consulted by opening the file `./docs/build/html/index.html` +## Known bugs + +_Still none_ + +## Troubleshooting + +TBD + +## License +See the LICENSE file for details. + + diff --git a/cspse/__init__.py b/cspse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cspse/lmc/__init__.py b/cspse/lmc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1daa8afc6388e8fe8e23169bc724c10f4eb12e52 --- /dev/null +++ b/cspse/lmc/__init__.py @@ -0,0 +1,6 @@ +__all__ = ( + "CspSubElementMaster" +) + +from .subelement_master import CspSubElementMaster + diff --git a/cspse/lmc/decorators.py b/cspse/lmc/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..44364402606d31825fef0e46c586ca35bc1f8aff --- /dev/null +++ b/cspse/lmc/decorators.py @@ -0,0 +1,108 @@ +import functools +import tango + +tasks = {} + +# note: f.__name__ is of type is_XXX_allowed +# f.__name__[3:-8] select the command name +# this decorator build a dictionary with the command name as key and +# the handler as value. +task = lambda f:tasks.setdefault(f.__name__[3:-8], f) + +@task +def is_standby_allowed(device_instance): + """ + Allowed method for Standby method. + Command *Standby* is allowed when the device *State* is ON or + STANDBY. + + :return: True if the method is allowed, otherwise False. + """ + if device_instance.get_state() in [tango.DevState.ON, + tango.DevState.STANDBY]: + return True + return False + +@task +def is_on_allowed(device_instance): + """ + Allowed method for On method. + Command *On* is allowed when the device *State* is ON or + STANDBY. + + :return: True if the method is allowed, otherwise False. + """ + if device_instance.get_state() in [tango.DevState.ON, + tango.DevState.STANDBY]: + return True + return False + +@task +def is_off_allowed(device_instance): + """ + Allowed method for Off method. + Command *Off* is allowed when the device *State* is OFF or + STANDBY. + + :return: True if the method is allowed, otherwise False. + """ + if device_instance.get_state() in [tango.DevState.OFF, + tango.DevState.STANDBY]: + return True + return False + +def is_command_allowed(device_instance, cmd_name): + """ + Call the allowed method for the command name specified + as input argument + :param device_istance: the TANGO device instance + :param cmd_name: the name of command to execute + + :return: True/False + """ + tasks[cmd_name](device_instance) + +class IsCommandAllowed(object): + """ + Class designed to be a decorator for the Master power methods. + The *decorator function* performs a check on the input argument + to control if the command is issued on the whole sub-element. + If this is the case, it checks the State of the sub-element Master + device and rejects the command accordingly to the State + machine setting. + + :raise: tango.DevFailed exception if the command can't be executed + """ + def __init__(self, *args, **kwargs): + # store the decorators parameters: + # args: the list of sub-element attributes to subscribe, to track the + # sub-element command progress and detect a command timeout + # kwargs: a dictionary: key ="cmd_name", + # value = command to execute ('on', 'off'...) + self._args = args + self._kwargs = kwargs + + def __call__(self, f): + @functools.wraps(f) + def input_args_check(*args, **kwargs): + # the Master device instance + dev_instance = args[0] + # the command name + cmd_to_exec = f.__name__ + # the command input argument + input_arg = args[1] + #device_list = input_arg + # Note: device list is a reference to args[1]: changing + # device_list content, args[1] changes accordingly! + num_of_devices = len(input_arg) + if num_of_devices == 0: + # check the device State: if it not the proper value the command is + # not executed + if not is_command_allowed(dev_instance, cmd_to_exec.lower()): + msg = "Command {} can't be executed when the device is {}".format(cmd_to_exec, + dev_instance.get_state()) + tango.Except.throw_exception("Command failure",msg, + "IsCommandAllowed decorator", + tango.ErrSeverity.ERR) + return f(*args, **kwargs) + return input_args_check diff --git a/cspse/lmc/release.py b/cspse/lmc/release.py new file mode 100755 index 0000000000000000000000000000000000000000..9e0117bcf595da924d8cb116dfba55c54daaf7bf --- /dev/null +++ b/cspse/lmc/release.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the CspSubElementMaster project +# +# +# +# Distributed under the terms of the BSD-3-Clause license. +# See LICENSE.txt for more info. + +"""Release information for Python Package""" + +name = """csp-lmc-subelement""" +version = "0.1.0" +version_info = version.split(".") +description = """SKA CSP Sub-element LMC""" +author = "INAF-OAA" +author_email = "elisabetta.giani@inaf.it" +license = """BSD-3-Clause""" +url = """https://gitlab.com/ska-telescope/csp-lmc.git""" +copyright = """INAF, SKA Telescope""" diff --git a/cspse/lmc/subelement_master.py b/cspse/lmc/subelement_master.py new file mode 100644 index 0000000000000000000000000000000000000000..fc4454c196d50b3a8593d658092ee79606fa592c --- /dev/null +++ b/cspse/lmc/subelement_master.py @@ -0,0 +1,613 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the CspSubElementMaster project +# +# INAF-SKA Telescope +# +# Distributed under the terms of the GPL license. +# See LICENSE.txt for more info. + +""" CSP.LMC Sub-element Master + +A base class for the Master of a SKA Sub-element. +""" + +# PyTango imports +import tango +from tango import DebugIt +from tango.server import run +from tango.server import Device +from tango.server import attribute, command +from tango.server import device_property +from tango import AttrQuality, DispLevel, DevState +from tango import AttrWriteType, PipeWriteType +from collections import defaultdict +# Additional import +# PROTECTED REGION ID(CspSubElementMaster.additionnal_import) ENABLED START # +from ska.base import SKAMaster +from ska.base.control_model import HealthState, AdminMode, LoggingLevel +from csp_lmc_common.utils.cspcommons import CmdExecState +from csp_lmc_common.utils.decorators import AdminModeCheck +from .decorators import IsCommandAllowed +from . import release +# PROTECTED REGION END # // CspSubElementMaster.additionnal_import + +__all__ = ["CspSubElementMaster", "main"] + + +class CspSubElementMaster(SKAMaster): + """ + A base class for the Master of a SKA Sub-element. + + **Properties:** + + - Device Property + Racks + - The list with the FQDNs of the sub-element racks devices. + - Type:'DevVarStringArray' + """ + # PROTECTED REGION ID(CspSubElementMaster.class_variable) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.class_variable + + # ----------------- + # Device Properties + # ----------------- + + Racks = device_property( + dtype='DevVarStringArray', + ) + + # ---------- + # Attributes + # ---------- + + numOfDevCompletedTask = attribute( + dtype='DevUShort', + label="Number of devices that completed the task", + doc="Number of devices that completed the task", + ) + + onCmdFailure = attribute( + dtype='DevBoolean', + label="CBF command failure flag", + #polling_period=1000, + doc="Failure flag set when the On command fails with error(s).", + ) + + onFailureMessage = attribute( + dtype='DevString', + label="On execution failure message", + doc="Failure message when the On command fails with error(s).", + ) + + offCmdFailure = attribute( + dtype='DevBoolean', + label="Off execution failure flag", + doc="Failure flag set when the Off command fails with error(s).", + ) + + offFailureMessage = attribute( + dtype='DevString', + label="Off execution failure message", + doc="Failure message when the Off command fails with error(s).", + ) + + standbyCmdFailure = attribute( + dtype='DevBoolean', + label="Standby execution failure message", + doc="Failure flag set when the Standby command fails with error(s).", + ) + + standbyFailureMessage = attribute( + dtype='DevString', + label="Standby execution failure message", + doc="Failure message when the Standby command fails with error(s).", + ) + + adminMode = attribute( + dtype=AdminMode, + access=AttrWriteType.READ_WRITE, + memorized=True, + doc=("The admin mode reported for this device. It may interpret the current" + " device condition and condition of all managed devices to set this." + " Most possibly an aggregate attribute."), + ) + + onCommandProgress = attribute( + dtype='DevUShort', + label="Progress percentage for the On command", + abs_change=10, + max_value=100, + min_value=0, + doc=("Percentage progress implemented for commands that result in state/mode" + " transitions for a large number of components and/or are executed in " + "stages (e.g power up, power down)"), + ) + + offCommandProgress = attribute( + dtype='DevUShort', + label="Progress percentage for the Off command", + abs_change=10, + max_value=100, + min_value=0, + doc=("Percentage progress implemented for commands that result in state/mode transitions" + " for a large number of components and/or are executed in stages" + " (e.g power up, power down)"), + ) + + standbyCommandProgress = attribute( + dtype='DevUShort', + label="Progress percentage for the Standby command", + abs_change=10, + max_value=100, + min_value=0, + doc=("Percentage progress implemented for commands that result in state/mode" + " transitions for a large number of components and/or are executed in" + " stages (e.g power up, power down)"), + ) + + onCmdDurationExpected = attribute( + dtype='DevUShort', + access=AttrWriteType.READ_WRITE, + label="Expected duration (sec) of the On command execution", + abs_change=0, + max_value=100, + min_value=0, + memorized=True, + doc="Set/Report the duration expected for the On command execution", + ) + + offCmdDurationExpected = attribute( + dtype='DevUShort', + access=AttrWriteType.READ_WRITE, + label="Expected duration (sec) of the Off command", + abs_change=0, + max_value=100, + min_value=0, + memorized=True, + doc="Set/Report the duration expected for the Off command execution", + ) + + standbyCmdDurationExpected = attribute( + dtype='DevUShort', + access=AttrWriteType.READ_WRITE, + label="Expected duration (sec) of the Standby command", + abs_change=0, + max_value=100, + min_value=0, + memorized=True, + doc="Set/Report the duration expected for the Standby command", + ) + + onCmdDurationMeasured = attribute( + dtype='DevUShort', + label="Measured duration (sec) of the On command execution", + abs_change=0, + max_value=100, + min_value=0, + doc="Report the measured duration of the On command execution", + ) + + offCmdDurationMeasured = attribute( + dtype='DevUShort', + label="Measured duration (sec) of the Off command", + abs_change=0, + max_value=100, + min_value=0, + doc="Report the measured duration of the Off command execution", + ) + + standbyCmdDurationMeasured = attribute( + dtype='DevUShort', + label="Measured duration (sec) of the Standby command", + abs_change=0, + max_value=100, + min_value=0, + doc="Report the measured duration of the Standby command", + ) + + onCmdTimeoutExpired = attribute( + dtype='DevBoolean', + label="On execution timeout flag", + doc="Signal the occurence of a timeout during the execution of the on command.", + ) + + offCmdTimeoutExpired = attribute( + dtype='DevBoolean', + label="Off execution timeout flag", + doc="Signal the occurence of a timeout during the execution of the Off command.", + ) + + standbyCmdTimeoutExpired = attribute( + dtype='DevBoolean', + label="Standby execution timeout flag.", + doc="Signal the occurence of a timeout during the execution of the Standby command.", + ) + + listOfDevCompletedTask = attribute( + dtype=('DevString',), + max_dim_x=100, + label="List of devices that completed the task", + doc="List of devices that completed the task", + ) + + listOfComponents = attribute( + dtype=('DevString',), + max_dim_x=100, + label="List of sub-element components", + doc="The list o the FQDNs of the sub-element components.", + ) + + # --------------- + # General methods + # --------------- + + def init_device(self): + """Initialises the attributes and properties of the CspSubElementMaster.""" + SKAMaster.init_device(self) + self.set_state(tango.DevState.INIT) + # PROTECTED REGION ID(CspSubElementMaster.init_device) ENABLED START # + # _cmd_execution_state: implement the execution state of a long-running + # command for the whole CSP. Setting this attribute prevent the execution + # of the same command while it is already running. + # implemented as a default dictionary: + # keys: command name + # values:command state + self._cmd_execution_state = defaultdict(lambda: CmdExecState.IDLE) + # _cmd_progress: report the execution progress of a long-running command + # implemented as a dictionary: + # keys: command name ('on', 'off'..) + # values: the percentage + self._cmd_progress = defaultdict(lambda: 0) + + # _cmd_duration_expected: store the duration (in sec.) configured for + # a long-running command + # Implemented asdefault dictionary + # keys: command name ('on', 'off',..) + # values: the duration (in sec) + self._cmd_duration_expected = defaultdict(lambda: 30) + + # _cmd_duration_measured: report the measured duration (in sec.) for + # a long-running command + # Implemented as default dictionary + # keys: command name ('on', 'off',..) + # values: the duration (in sec) + self._cmd_duration_measured = defaultdict(lambda: 0) + + # _timeout_expired: report the timeout flag + # Implemented as a dictionary + # keys: command name ('on', 'off', 'standby'..) + # values: True/False + self._timeout_expired = defaultdict(lambda: False) + + # _failure_raised: report the failure flag + # Implemented as a dictionary + # keys: command name ('on', 'off', 'standby'..) + # values: True/False + self._failure_raised = defaultdict(lambda: False) + + # _failure_message: report the failure message + # Implemented as a dictionary + # keys: command name ('on', 'off', 'standby'..) + # values: the message + self._failure_message = defaultdict(lambda: '') + + # _list_dev_completed_task: for each long-running command report the list + # of subordinate sub-element components that completed the task + # Implemented as a dictionary + # keys: the command name ('on', 'off',...) + # values: the list of components + self._list_dev_completed_task = defaultdict(lambda: []) + + # _list_of_components: report the list of subordinate + # sub-element components. + # Implemented as a list of FQDNs + self._list_of_components = [] + + # _num_dev_completed_task: for each long-running command report the number + # of subordinate components that completed the task + # Implemented as a dictionary + # keys: the command name ('on', 'off',...) + # values: the number of components + self._num_dev_completed_task = defaultdict(lambda:0) + + # the last executed command + self._last_executed_command = "none" + + # PROTECTED REGION END # // CspSubElementMaster.init_device + + def always_executed_hook(self): + """Method always executed before any TANGO command is executed.""" + # PROTECTED REGION ID(CspSubElementMaster.always_executed_hook) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.always_executed_hook + + def delete_device(self): + """Hook to delete resources allocated in init_device. + + This method allows for any memory or other resources allocated in the + init_device method to be released. This method is called by the device + destructor and by the device Init command. + """ + # PROTECTED REGION ID(CspSubElementMaster.delete_device) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.delete_device + # ------------------ + # Attributes methods + # ------------------ + + def read_numOfDevCompletedTask(self): + # PROTECTED REGION ID(CspSubElementMaster.numOfDevCompletedTask_read) ENABLED START # + """Return the numOfDevCompletedTask attribute.""" + return self._num_dev_completed_task[self._last_executed_command] + # PROTECTED REGION END # // CspSubElementMaster.numOfDevCompletedTask_read + + def read_onCmdFailure(self): + # PROTECTED REGION ID(CspSubElementMaster.onCmdFailure_read) ENABLED START # + """Return the onCmdFailure attribute.""" + return self._failure_raised['on'] + # PROTECTED REGION END # // CspSubElementMaster.onCmdFailure_read + + def read_onFailureMessage(self): + # PROTECTED REGION ID(CspSubElementMaster.onFailureMessage_read) ENABLED START # + """Return the onFailureMessage attribute.""" + return self._failure_message['on'] + # PROTECTED REGION END # // CspSubElementMaster.onFailureMessage_read + + def read_offCmdFailure(self): + # PROTECTED REGION ID(CspSubElementMaster.offCmdFailure_read) ENABLED START # + """Return the offCmdFailure attribute.""" + return self._failure_raised['off'] + # PROTECTED REGION END # // CspSubElementMaster.offCmdFailure_read + + def read_offFailureMessage(self): + # PROTECTED REGION ID(CspSubElementMaster.offFailureMessage_read) ENABLED START # + """Return the offFailureMessage attribute.""" + return self._failure_message['off'] + # PROTECTED REGION END # // CspSubElementMaster.offFailureMessage_read + + def read_standbyCmdFailure(self): + # PROTECTED REGION ID(CspSubElementMaster.standbyCmdFailure_read) ENABLED START # + """Return the standbyCmdFailure attribute.""" + return self._failure_raised['standby'] + # PROTECTED REGION END # // CspSubElementMaster.standbyCmdFailure_read + + def read_standbyFailureMessage(self): + # PROTECTED REGION ID(CspSubElementMaster.standbyFailureMessage_read) ENABLED START # + """Return the standbyFailureMessage attribute.""" + return self._failure_message['standby'] + # PROTECTED REGION END # // CspSubElementMaster.standbyFailureMessage_read + + def read_adminMode(self): + # PROTECTED REGION ID(CspSubElementMaster.adminMode_read) ENABLED START # + """Return the adminMode attribute.""" + return self._admin_mode + # PROTECTED REGION END # // CspSubElementMaster.adminMode_read + + def write_adminMode(self, value): + # PROTECTED REGION ID(CspSubElementMaster.adminMode_write) ENABLED START # + """Set the adminMode attribute.""" + self._admin_mode = value + if self._admin_mode not in [AdminMode.ONLINE, AdminMode.MAINTENANCE]: + self.set_state(tango.DevState.DISABLE) + # PROTECTED REGION END # // CspSubElementMaster.adminMode_write + + def read_onCommandProgress(self): + # PROTECTED REGION ID(CspSubElementMaster.onCommandProgress_read) ENABLED START # + """Return the onCommandProgress attribute.""" + return self._cmd_progress['on'] + # PROTECTED REGION END # // CspSubElementMaster.onCommandProgress_read + + def read_offCommandProgress(self): + # PROTECTED REGION ID(CspSubElementMaster.offCommandProgress_read) ENABLED START # + """Return the offCommandProgress attribute.""" + return self._cmd_progress['off'] + # PROTECTED REGION END # // CspSubElementMaster.offCommandProgress_read + + def read_standbyCommandProgress(self): + # PROTECTED REGION ID(CspSubElementMaster.standbyCommandProgress_read) ENABLED START # + """Return the standbyCommandProgress attribute.""" + return self._cmd_progress['standby'] + # PROTECTED REGION END # // CspSubElementMaster.standbyCommandProgress_read + + def read_onCmdDurationExpected(self): + # PROTECTED REGION ID(CspSubElementMaster.onCmdDurationExpected_read) ENABLED START # + """Return the onCmdDurationExpected attribute.""" + return self._cmd_duration_expected['on'] + # PROTECTED REGION END # // CspSubElementMaster.onCmdDurationExpected_read + + def write_onCmdDurationExpected(self, value): + # PROTECTED REGION ID(CspSubElementMaster.onCmdDurationExpected_write) ENABLED START # + """Set the onCmdDurationExpected attribute.""" + self._cmd_duration_expected['on'] = value + # PROTECTED REGION END # // CspSubElementMaster.onCmdDurationExpected_write + + def read_offCmdDurationExpected(self): + # PROTECTED REGION ID(CspSubElementMaster.offCmdDurationExpected_read) ENABLED START # + """Return the offCmdDurationExpected attribute.""" + return self._cmd_duration_expected['off'] + # PROTECTED REGION END # // CspSubElementMaster.offCmdDurationExpected_read + + def write_offCmdDurationExpected(self, value): + # PROTECTED REGION ID(CspSubElementMaster.offCmdDurationExpected_write) ENABLED START # + """Set the offCmdDurationExpected attribute.""" + self._cmd_duration_expected['off'] = value + # PROTECTED REGION END # // CspSubElementMaster.offCmdDurationExpected_write + + def read_standbyCmdDurationExpected(self): + # PROTECTED REGION ID(CspSubElementMaster.standbyCmdDurationExpected_read) ENABLED START # + """Return the standbyCmdDurationExpected attribute.""" + return self._cmd_duration_expected['standby'] + # PROTECTED REGION END # // CspSubElementMaster.standbyCmdDurationExpected_read + + def write_standbyCmdDurationExpected(self, value): + # PROTECTED REGION ID(CspSubElementMaster.standbyCmdDurationExpected_write) ENABLED START # + """Set the standbyCmdDurationExpected attribute.""" + self._cmd_duration_expected['standby'] = value + # PROTECTED REGION END # // CspSubElementMaster.standbyCmdDurationExpected_write + + def read_onCmdDurationMeasured(self): + # PROTECTED REGION ID(CspSubElementMaster.onCmdDurationMeasured_read) ENABLED START # + """Return the onCmdDurationMeasured attribute.""" + return self._cmd_duration_measured['on'] + # PROTECTED REGION END # // CspSubElementMaster.onCmdDurationMeasured_read + + def read_offCmdDurationMeasured(self): + # PROTECTED REGION ID(CspSubElementMaster.offCmdDurationMeasured_read) ENABLED START # + """Return the offCmdDurationMeasured attribute.""" + return self._cmd_duration_measured['off'] + # PROTECTED REGION END # // CspSubElementMaster.offCmdDurationMeasured_read + + def read_standbyCmdDurationMeasured(self): + # PROTECTED REGION ID(CspSubElementMaster.standbyCmdDurationMeasured_read) ENABLED START # + """Return the standbyCmdDurationMeasured attribute.""" + return self._cmd_duration_measured['standby'] + # PROTECTED REGION END # // CspSubElementMaster.standbyCmdDurationMeasured_read + + def read_onCmdTimeoutExpired(self): + # PROTECTED REGION ID(CspSubElementMaster.onCmdTimeoutExpired_read) ENABLED START # + """Return the onCmdTimeoutExpired attribute.""" + return self._timeout_expired['on'] + # PROTECTED REGION END # // CspSubElementMaster.onCmdTimeoutExpired_read + + def read_offCmdTimeoutExpired(self): + # PROTECTED REGION ID(CspSubElementMaster.offCmdTimeoutExpired_read) ENABLED START # + """Return the offCmdTimeoutExpired attribute.""" + return self._timeout_expired['off'] + + # PROTECTED REGION END # // CspSubElementMaster.offCmdTimeoutExpired_read + + def read_standbyCmdTimeoutExpired(self): + # PROTECTED REGION ID(CspSubElementMaster.standbyCmdTimeoutExpired_read) ENABLED START # + """Return the standbyCmdTimeoutExpired attribute.""" + return self._timeout_expired['standby'] + # PROTECTED REGION END # // CspSubElementMaster.standbyCmdTimeoutExpired_read + + def read_listOfDevCompletedTask(self): + # PROTECTED REGION ID(CspSubElementMaster.listOfDevCompletedTask_read) ENABLED START # + """Return the listOfDevCompletedTask attribute.""" + return self._list_dev_completed_task[self._last_executed_command] + # PROTECTED REGION END # // CspSubElementMaster.listOfDevCompletedTask_read + + def read_listOfComponents(self): + # PROTECTED REGION ID(CspSubElementMaster.listOfComponents_read) ENABLED START # + """Return the listOfComponents attribute.""" + return self._list_of_components + # PROTECTED REGION END # // CspSubElementMaster.listOfComponents_read + + # -------- + # Commands + # -------- + + @command( + dtype_in='DevVarStringArray', + doc_in="The list of sub-element components FQDNs to switch-on or an empty list to switch-on the whole " + "CSP Sub-element." + " " + "If the array length is 0, the command applies to the whole CSP Sub-Element. If the " + "array length is > 1, each array element specifies the FQDN of the" + "CSP SubElement component to switch ON.", + ) + @DebugIt() + @IsCommandAllowed() + @AdminModeCheck('On') + def On(self, argin): + # PROTECTED REGION ID(CspSubElementMaster.On) ENABLED START # + """ + Switch-on the CSP sub-element components specified by the input argument. If no argument is + specified, the command is issued on all the CSP sub-element components. + The command is executed if the *AdminMode* is ONLINE or *MAINTENANCE*. + If the AdminMode is *OFFLINE*, *NOT-FITTED* or *RESERVED*, the method throws an + exception. + The CSP sub-element components can be organized as tango-groups or controlled + by some tango device drivers which actas as 'cache'. We call these devices + 'racks', even if they can contro a different phisical arrangement. + + :param argin: The list of sub-element components FQDNs: if the array length is 0 (empty input \ + list), the command applies to the whole CSP Sub-Element. If the array length is > 1, \ + each array element specifies the FQDN of the CSP SubElement component to switch ON. + :type: 'DevVarStringArray' + :return: None + """ + pass + # PROTECTED REGION END # // CspSubElementMaster.On + + @command( + dtype_in='DevVarStringArray', + doc_in="If the array length is 0, the command applies to the whole" + "CSP Sub-element." + "If the array length is > 1, each array element specifies the FQDN of the" + "CSP SubElement component to switch OFF.", + ) + @DebugIt() + @IsCommandAllowed() + @AdminModeCheck('Off') + def Off(self, argin): + # PROTECTED REGION ID(CspSubElementMaster.Off) ENABLED START # + """ + Switch-off the CSP sub-element components specified by the input argument. + If no argument is specified, the command is issued to all the CSP + sub-element components. + The CSP sub-element components can be organized as tango-groups or controlled + by some tango device drivers which actas as 'cache'. We call these devices + 'racks', even if they can contro a different phisical arrangement. + + + :param argin: The list of sub-element components FQDNs: if the array length is 0 (no list \ + specified), the command applies to the whole CSP sub-element. If the array length \ + is > 1, each array element specifies the FQDN of a CSP SubElement component to switch \ + off. + :type: 'DevVarStringArray' + :return: None + """ + pass + # PROTECTED REGION END # // CspSubElementMaster.Off + + @command( + dtype_in='DevVarStringArray', + doc_in="If the array length is 0, the command applies to the whole" + "CSP sub-element." + "If the array length is > 1, each array element specifies the FQDN of the" + "CSP SubElement icomponent to put in STANDBY mode.", + ) + @DebugIt() + @IsCommandAllowed() + @AdminModeCheck('Standby') + def Standby(self, argin): + # PROTECTED REGION ID(CspSubElementMaster.Standby) ENABLED START # + """ + Transit the CSP Sub-element or one or more CSP SubElement components from ON/OFF to + STANDBY. + + :param argin: The list of sub-element components FQDNs: if the array length is 0 (no list \ + specified), the command applies to the whole CSP sub-element. If the array length \ + is > 1, each array element specifies the FQDN of a CSP SubElement component to put \ + in STANDBY mode. + :type: 'DevVarStringArray' + :return: None + """ + pass + # PROTECTED REGION END # // CspSubElementMaster.Standby + + @command( + ) + @DebugIt() + def Upgrade(self): + # PROTECTED REGION ID(CspSubElementMaster.Upgrade) ENABLED START # + """ + :return: None + """ + pass + # PROTECTED REGION END # // CspSubElementMaster.Upgrade + +# ---------- +# Run server +# ---------- + +def main(args=None, **kwargs): + """Main function of the CspSubElementMaster module.""" + # PROTECTED REGION ID(CspSubElementMaster.main) ENABLED START # + return run((CspSubElementMaster,), args=args, **kwargs) + # PROTECTED REGION END # // CspSubElementMaster.main + + +if __name__ == '__main__': + main() diff --git a/docker-requirements.txt b/docker-requirements.txt index 040cecbfef9cfaef9896136256805468d4f361b8..92273eca33dc10bc011bc079abbf7191fa200da5 100644 --- a/docker-requirements.txt +++ b/docker-requirements.txt @@ -1,3 +1,4 @@ +--extra-index-url https://nexus.engageska-portugal.pt/repository/pypi/simple docutils markupsafe pygments @@ -13,4 +14,5 @@ sphinx-autobuild sphinx-rtd-theme sphinxcontrib-websupport pipdeptree -pylint_junit \ No newline at end of file +pylint_junit +csp-lmc-common >= 0.5.3 diff --git a/docker/.make/.make-release-support b/docker/.make/.make-release-support new file mode 100644 index 0000000000000000000000000000000000000000..f1f3a2cb9fb3ea0ce3a173a35c3c9fe316d75156 --- /dev/null +++ b/docker/.make/.make-release-support @@ -0,0 +1,106 @@ +#!/bin/bash +# +# Copyright 2015 Xebia Nederland B.V. +# Modifications copyright 2019 SKA Organisation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +function hasChanges() { + test -n "$(git status -s .)" +} + +function getRelease() { + awk -F= '/^release=/{print $2}' .release +} + +function getBaseTag() { + sed -n -e "s/^tag=\(.*\)$(getRelease)\$/\1/p" .release +} + +function getTag() { + if [ -z "$1" ] ; then + awk -F= '/^tag/{print $2}' .release + else + echo "$(getBaseTag)$1" + fi +} + +function setRelease() { + if [ -n "$1" ] ; then + sed -i.x -e "s/^tag=.*/tag=$(getTag $1)/" .release + sed -i.x -e "s/^release=.*/release=$1/g" .release + rm -f .release.x + runPreTagCommand "$1" + else + echo "ERROR: missing release version parameter " >&2 + return 1 + fi +} + +function runPreTagCommand() { + if [ -n "$1" ] ; then + COMMAND=$(sed -n -e "s/@@RELEASE@@/$1/g" -e 's/^pre_tag_command=\(.*\)/\1/p' .release) + if [ -n "$COMMAND" ] ; then + if ! OUTPUT=$(bash -c "$COMMAND" 2>&1) ; then echo $OUTPUT >&2 && exit 1 ; fi + fi + else + echo "ERROR: missing release version parameter " >&2 + return 1 + fi +} + +function tagExists() { + tag=${1:-$(getTag)} + test -n "$tag" && test -n "$(git tag | grep "^$tag\$")" +} + +function differsFromRelease() { + tag=$(getTag) + ! tagExists $tag || test -n "$(git diff --shortstat -r $tag .)" +} + +function getVersion() { + result=$(getRelease) + + if differsFromRelease; then + result="$result-$(git log -n 1 --format=%h .)" + fi + + if hasChanges ; then + result="$result-dirty" + fi + echo $result +} + +function nextPatchLevel() { + version=${1:-$(getRelease)} + major_and_minor=$(echo $version | cut -d. -f1,2) + patch=$(echo $version | cut -d. -f3) + version=$(printf "%s.%d" $major_and_minor $(($patch + 1))) + echo $version +} + +function nextMinorLevel() { + version=${1:-$(getRelease)} + major=$(echo $version | cut -d. -f1); + minor=$(echo $version | cut -d. -f2); + version=$(printf "%d.%d.0" $major $(($minor + 1))) ; + echo $version +} + +function nextMajorLevel() { + version=${1:-$(getRelease)} + major=$(echo $version | cut -d. -f1); + version=$(printf "%d.0.0" $(($major + 1))) + echo $version +} diff --git a/docker/.make/Makefile.mk b/docker/.make/Makefile.mk new file mode 100644 index 0000000000000000000000000000000000000000..c8196593ee69d4cdb75be9779299b1955cd6025c --- /dev/null +++ b/docker/.make/Makefile.mk @@ -0,0 +1,137 @@ +# +# Copyright 2015 Xebia Nederland B.V. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +ifeq ($(strip $(PROJECT)),) + NAME=$(shell basename $(CURDIR)) +else + NAME=$(PROJECT) +endif + +RELEASE_SUPPORT := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))/.make-release-support + +ifeq ($(strip $(DOCKER_REGISTRY_HOST)),) + DOCKER_REGISTRY_HOST = nexus.engageska-portugal.pt +endif + +ifeq ($(strip $(DOCKER_REGISTRY_USER)),) + DOCKER_REGISTRY_USER = ska-docker +endif + +IMAGE=$(DOCKER_REGISTRY_HOST)/$(DOCKER_REGISTRY_USER)/$(NAME) + +#VERSION = release version + git sha +VERSION=$(shell . $(RELEASE_SUPPORT) ; getVersion) + +#BASE_VERSION +BASE_VERSION=$(shell . $(RELEASE_SUPPORT) ; getRelease) + +#TAG = project name + release version +TAG=$(shell . $(RELEASE_SUPPORT); getTag) + +#DEFAULT_TAG = image name + BASE_VERSION +DEFAULT_TAG=$(IMAGE):$(BASE_VERSION) + + +SHELL=/bin/bash + +DOCKER_BUILD_CONTEXT=.. +DOCKER_FILE_PATH=Dockerfile + +.PHONY: pre-build docker-build post-build build release patch-release minor-release major-release tag check-status check-release showver \ + push pre-push do-push post-push + +build: pre-build docker-build post-build ## build the application image + +pre-build: + +post-build: + +pre-push: + +post-push: + +docker-build: .release + @echo "Building image: $(IMAGE):$(VERSION)" + @echo "NAME: $(NAME)" + docker build $(DOCKER_BUILD_ARGS) -t $(IMAGE):$(VERSION) $(DOCKER_BUILD_CONTEXT) -f $(DOCKER_FILE_PATH) --build-arg DOCKER_REGISTRY_HOST=$(DOCKER_REGISTRY_HOST) --build-arg DOCKER_REGISTRY_USER=$(DOCKER_REGISTRY_USER) + @DOCKER_MAJOR=$(shell docker -v | sed -e 's/.*version //' -e 's/,.*//' | cut -d\. -f1) ; \ + DOCKER_MINOR=$(shell docker -v | sed -e 's/.*version //' -e 's/,.*//' | cut -d\. -f2) ; \ + if [ $$DOCKER_MAJOR -eq 1 ] && [ $$DOCKER_MINOR -lt 10 ] ; then \ + echo docker tag -f $(IMAGE):$(VERSION) $(IMAGE):latest ;\ + docker tag -f $(IMAGE):$(VERSION) $(IMAGE):latest ;\ + else \ + echo docker tag $(IMAGE):$(VERSION) $(IMAGE):latest ;\ + docker tag $(IMAGE):$(VERSION) $(IMAGE):latest ; \ + fi + +release: check-status check-release build push + +push: pre-push do-push post-push ## push the image to the Docker registry + +do-push: ## Push the image tagged as $(IMAGE):$(VERSION) and $(DEFAULT_TAG) + @echo -e "Tagging: $(IMAGE):$(VERSION) -> $(DEFAULT_TAG)" + docker tag $(IMAGE):$(VERSION) $(DEFAULT_TAG) + @echo -e "Pushing: $(IMAGE):$(VERSION)" + docker push $(IMAGE):$(VERSION) + @echo -e "Pushing: $(DEFAULT_TAG)" + docker push $(DEFAULT_TAG) + +tag_latest: do-push ## Tag the images as latest + @echo "Tagging: $(DEFAULT_TAG) -> $(IMAGE):latest" + @docker tag $(DEFAULT_TAG) $(IMAGE):latest + +push_latest: tag_latest ## Push the image tagged as :latest + @echo "Pushing: $(IMAGE):latest" + @docker push $(IMAGE):latest + +snapshot: build push + +showver: .release + @. $(RELEASE_SUPPORT); getVersion + +bump-patch-release: VERSION := $(shell . $(RELEASE_SUPPORT); nextPatchLevel) +bump-patch-release: .release tag + +bump-minor-release: VERSION := $(shell . $(RELEASE_SUPPORT); nextMinorLevel) +bump-minor-release: .release tag + +bump-major-release: VERSION := $(shell . $(RELEASE_SUPPORT); nextMajorLevel) +bump-major-release: .release tag + +patch-release: tag-patch-release release + @echo $(VERSION) + +minor-release: tag-minor-release release + @echo $(VERSION) + +major-release: tag-major-release release + @echo $(VERSION) + +tag: TAG=$(shell . $(RELEASE_SUPPORT); getTag $(VERSION)) +tag: check-status +# @. $(RELEASE_SUPPORT) ; ! tagExists $(TAG) || (echo "ERROR: tag $(TAG) for version $(VERSION) already tagged in git" >&2 && exit 1) ; + @. $(RELEASE_SUPPORT) ; setRelease $(VERSION) +# git add . +# git commit -m "bumped to version $(VERSION)" ; +# git tag $(TAG) ; +# @ if [ -n "$(shell git remote -v)" ] ; then git push --tags ; else echo 'no remote to push tags to' ; fi + +check-status: + @. $(RELEASE_SUPPORT) ; ! hasChanges || (echo "ERROR: there are still outstanding changes" >&2 && exit 1) ; + +check-release: .release + @. $(RELEASE_SUPPORT) ; tagExists $(TAG) || (echo "ERROR: version not yet tagged in git. make [minor,major,patch]-release." >&2 && exit 1) ; + @. $(RELEASE_SUPPORT) ; ! differsFromRelease $(TAG) || (echo "ERROR: current directory differs from tagged $(TAG). make [minor,major,patch]-release." ; exit 1) diff --git a/docker/.release b/docker/.release new file mode 100644 index 0000000000000000000000000000000000000000..87dca79f7d33c662b775fc4365b82a6e9e2af259 --- /dev/null +++ b/docker/.release @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the CSP.LMC prototype project +# +# +# Distributed under the terms of the BSD-3-Clause license. +# See LICENSE.txt for more info. + +"""Release information for Python Package""" + +name = """csplmc-subelement""" +version = "0.1.0" +version_info = version.split(".") +description = """SKA CSP.LMC Subelement Classes""" +author = "E.G" +author_email = "elisabetta.giani@inaf.it" +license = """BSD-3-Clause""" +url = """www.tango-controls.org""" +copyright = """""" + +release=0.1.0 +tag=csp-lmc-subelement-0.1.0 diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..11f75668e4981db977a215ee9cf095f711561453 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,11 @@ +FROM nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:9.3.1 AS buildenv +FROM nexus.engageska-portugal.pt/ska-docker/ska-python-runtime:9.3.1 AS runtime + +# create ipython profile to so that itango doesn't fail if ipython hasn't run yet +RUN ipython profile create +#RUN python3 -m pip install --user pytest-forked +ENV PATH=/home/tango/.local/bin:$PATH +#install csp-lmc-common with dependencies +RUN python3 -m pip install -e . --user --extra-index-url https://nexus.engageska-portugal.pt/repository/pypi/simple + +CMD ["/venv/bin/python", "/app/cspse/lmc/SubElementMaster.py" ] diff --git a/docker/Makefile b/docker/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..540912c95d020cc24b529552240d9d024f54ffbb --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,197 @@ +# +# Project makefile for a Tango project. You should normally only need to modify +# DOCKER_REGISTRY_USER and PROJECT below. +# + +# +# DOCKER_REGISTRY_HOST, DOCKER_REGISTRY_USER and PROJECT are combined to define +# the Docker tag for this project. The definition below inherits the standard +# value for DOCKER_REGISTRY_HOST (=rnexus.engageska-portugal.pt) and overwrites +# DOCKER_REGISTRY_USER and PROJECT to give a final Docker tag of +# nexus.engageska-portugal.pt/tango-example/csplmc +# + +DOCKER_REGISTRY_USER:=ska-docker +PROJECT = csp-lmc-subelement + +# +# include makefile to pick up the standard Make targets, e.g., 'make build' +# build, 'make push' docker push procedure, etc. The other Make targets +# ('make interactive', 'make test', etc.) are defined in this file. +# +include .make/Makefile.mk + +# +# IMAGE_TO_TEST defines the tag of the Docker image to test +# +IMAGE_TO_TEST = $(DOCKER_REGISTRY_HOST)/$(DOCKER_REGISTRY_USER)/$(PROJECT):latest + +# +# CACHE_VOLUME is the name of the Docker volume used to cache eggs and wheels +# used during the test procedure. The volume is not used during the build +# procedure +# +CACHE_VOLUME = $(PROJECT)-test-cache + +# optional docker run-time arguments +DOCKER_RUN_ARGS = + +# +# Never use the network=host mode when running CI jobs, and add extra +# distinguishing identifiers to the network name and container names to +# prevent collisions with jobs from the same project running at the same +# time. +# +ifneq ($(CI_JOB_ID),) +NETWORK_MODE := tangonet-$(CI_JOB_ID) +CONTAINER_NAME_PREFIX := $(PROJECT)-$(CI_JOB_ID)- +else +CONTAINER_NAME_PREFIX := $(PROJECT)- +endif + +COMPOSE_FILES := $(wildcard *.yml) +COMPOSE_FILE_ARGS := $(foreach yml,$(COMPOSE_FILES),-f $(yml)) + +ifeq ($(OS),Windows_NT) + $(error Sorry, Windows is not supported yet) +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Linux) + DISPLAY ?= :0.0 + NETWORK_MODE ?= host + XAUTHORITY_MOUNT := /tmp/.X11-unix:/tmp/.X11-unix + XAUTHORITY ?= /hosthome/.Xauthority + # /bin/sh (=dash) does not evaluate 'docker network' conditionals correctly + SHELL := /bin/bash + endif + ifeq ($(UNAME_S),Darwin) + IF_INTERFACE := $(shell netstat -nr | awk '{ if ($$1 ~/default/) { print $$6} }') + DISPLAY := $(shell ifconfig $(IF_INTERFACE) | awk '{ if ($$1 ~/inet$$/) { print $$2} }'):0 + # network_mode = host doesn't work on MacOS, so fix to the internal network + NETWORK_MODE := tangonet + XAUTHORITY_MOUNT := $(HOME):/hosthome:ro + XAUTHORITY := /hosthome/.Xauthority + endif +endif + +# +# When running in network=host mode, point devices at a port on the host +# machine rather than at the container. +# +ifeq ($(NETWORK_MODE),host) +TANGO_HOST := $(shell hostname):10000 +MYSQL_HOST := $(shell hostname):3306 +else +# distinguish the bridge network from others by adding the project name +NETWORK_MODE := $(NETWORK_MODE)-$(PROJECT) +TANGO_HOST := $(CONTAINER_NAME_PREFIX)databaseds:10000 +MYSQL_HOST := $(CONTAINER_NAME_PREFIX)tangodb:3306 +endif + + +DOCKER_COMPOSE_ARGS := DISPLAY=$(DISPLAY) XAUTHORITY=$(XAUTHORITY) TANGO_HOST=$(TANGO_HOST) \ + NETWORK_MODE=$(NETWORK_MODE) XAUTHORITY_MOUNT=$(XAUTHORITY_MOUNT) MYSQL_HOST=$(MYSQL_HOST) \ + DOCKER_REGISTRY_HOST=$(DOCKER_REGISTRY_HOST) DOCKER_REGISTRY_USER=$(DOCKER_REGISTRY_USER) \ + CONTAINER_NAME_PREFIX=$(CONTAINER_NAME_PREFIX) COMPOSE_IGNORE_ORPHANS=true + +# +# Defines a default make target so that help is printed if make is called +# without a target +# +.DEFAULT_GOAL := help + +# +# defines a function to copy the ./test-harness directory into the container +# and then runs the requested make target in the container. The container is: +# +# 1. attached to the network of the docker-compose test system +# 2. uses a persistent volume to cache Python eggs and wheels so that fewer +# downloads are required +# 3. uses a transient volume as a working directory, in which untarred files +# and test output can be written in the container and subsequently copied +# to the host +# +make = tar -c test-harness/ | \ + docker run -i --rm --network=$(NETWORK_MODE) \ + -e TANGO_HOST=$(TANGO_HOST) \ + -v $(CACHE_VOLUME):/home/tango/.cache \ + -v /build -w /build -u tango $(DOCKER_RUN_ARGS) $(IMAGE_TO_TEST) \ + bash -c "sudo chown -R tango:tango /build && \ + tar x --strip-components 1 --warning=all && \ + make TANGO_HOST=$(TANGO_HOST) $1" + +test: DOCKER_RUN_ARGS = --volumes-from=$(BUILD) +test: build up ## test the application + @echo "BUILD: $(BUILD)" + $(INIT_CACHE) + $(call make,test); \ + status=$$?; \ + rm -fr build; \ + #docker-compose $(COMPOSE_FILE_ARGS) logs; + docker cp $(BUILD):/build .; \ + docker rm -f -v $(BUILD); \ + $(MAKE) down; \ + exit $$status + +lint: DOCKER_RUN_ARGS = --volumes-from=$(BUILD) +lint: build up ## lint the application (static code analysis) + $(INIT_CACHE) + $(call make,lint); \ + status=$$?; \ + docker cp $(BUILD):/build .; \ + $(MAKE) down; \ + exit $$status + +pull: ## download the application image + docker pull $(IMAGE_TO_TEST) + +up: build ## start develop/test environment +ifneq ($(NETWORK_MODE),host) + docker network inspect $(NETWORK_MODE) &> /dev/null || ([ $$? -ne 0 ] && docker network create $(NETWORK_MODE)) +endif + #$(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) pull + #to pull only the mid-cbf-mcs image remove comment on row below. + #docker pull $(DOCKER_REGISTRY_HOST)/$(DOCKER_REGISTRY_USER)/mid-cbf-mcs:latest + $(DOCKER_COMPOSE_ARGS) docker-compose -f se-tangodb.yml up -d + # put a sleep to wait TANGO DB + @sleep 10 + $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) up -d + +piplock: build ## overwrite Pipfile.lock with the image version + docker run $(IMAGE_TO_TEST) cat /app/Pipfile.lock > $(CURDIR)/Pipfile.lock + +interactive: up +interactive: ## start an interactive session using the project image (caution: R/W mounts source directory to /app) + docker run --rm -it -p 3000:3000 --name=$(CONTAINER_NAME_PREFIX)dev -e TANGO_HOST=$(TANGO_HOST) --network=$(NETWORK_MODE) \ + -v $(CURDIR):/app $(IMAGE_TO_TEST) /bin/bash + +down: ## stop develop/test environment and any interactive session + docker ps | grep $(CONTAINER_NAME_PREFIX)dev && docker stop $(PROJECT)-dev || true + $(DOCKER_COMPOSE_ARGS) docker-compose $(COMPOSE_FILE_ARGS) down +ifneq ($(NETWORK_MODE),host) + docker network inspect $(NETWORK_MODE) &> /dev/null && ([ $$? -eq 0 ] && docker network rm $(NETWORK_MODE)) || true +endif + +dsconfigdump: up ## dump the entire configuration to the file dsconfig.json + docker exec -it $(CONTAINER_NAME_PREFIX)dsconfigdump python -m dsconfig.dump + docker exec -it $(CONTAINER_NAME_PREFIX)dsconfigdump python -m dsconfig.dump > dsconfig.json + +dsconfigadd: up ## Add a configuration json file (environment variable DSCONFIG_JSON_FILE) to the database + -docker exec -it $(CONTAINER_NAME_PREFIX)dsconfigdump json2tango -u -w -a $(DSCONFIG_JSON_FILE) + +dsconfigcheck: up ## check a json file (environment variable DSCONFIG_JSON_FILE) according to the project lib-maxiv-dsconfig json schema + -docker exec -it $(CONTAINER_NAME_PREFIX)dsconfigdump json2tango -a $(DSCONFIG_JSON_FILE) + +help: ## show this help. + @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: all test up down help + +# Creates Docker volume for use as a cache, if it doesn't exist already +INIT_CACHE = \ + docker volume ls | grep $(CACHE_VOLUME) || \ + docker create --name $(CACHE_VOLUME) -v $(CACHE_VOLUME):/cache $(IMAGE_TO_TEST) + +# http://cakoose.com/wiki/gnu_make_thunks +BUILD_GEN = $(shell docker create -v /build $(IMAGE_TO_TEST)) +BUILD = $(eval BUILD := $(BUILD_GEN))$(BUILD) diff --git a/docker/config/csplmc_dsconfig.json b/docker/config/csplmc_dsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..29d976ddfa1244ce65eae47a50dbf041444c2aef --- /dev/null +++ b/docker/config/csplmc_dsconfig.json @@ -0,0 +1,190 @@ +{ + "servers": { + "CspSubElementMaster": { + "master": { + "CspSubElementMaster": { + "subelement/sub_elt/master": { + "attribute_properties": { + "adminMode": { + "abs_change": [ + "-1", + "1" + ], + "__value": [ + "0" + ] + }, + "healthState": { + "abs_change": [ + "-1", + "1" + ] + } + }, + "properties": { + "Rack": [ + "subelement/rack/01", + "subelement/rack/02", + "subelement/rack/03", + "subelement/rack/04" + ] + } + } + } + } + }, + "CspCapabilityMonitor": { + "searchbeams": { + "CspCapabilityMonitor": { + "subelement/capability/pss_pipeline_monitor": { + "attribute_properties": { + "adminMode": { + "__value": [ + "0" + ] + } + }, + "properties": { + "CapabilityDevices": [ + "subelement/pipelines/0001", + "subelement/pipelines/0002", + "subelement/pipelines/0003" + ] + } + } + } + }, + "timingbeams": { + "CspCapabilityMonitor": { + "subelement/capability/pstbeams_monitor": { + "attribute_properties": { + "adminMode": { + "__value": [ + "0" + ] + } + }, + "properties": { + "CapabilityDevices": [ + "subelement/pstbeam/01", + "subelement/pstbeam/02", + "subelement/pstbeam/03" + ] + } + } + } + }, + "vlbibeams": { + "CspCapabilityMonitor": { + "subelement/capability/vlbi_beams_monitor": { + "attribute_properties": { + "adminMode": { + "__value": [ + "0" + ] + } + }, + "properties": { + "CapabilityDevices": [ + "subelement/vlbi_beam/01", + "subelement/vlbi_beam/02", + "subelement/vlbi_beam/03" + ] + } + } + } + } + }, + "CspSubElementSubarray": { + "subarray1": { + "CspSubElementSubarray": { + "subelement/sub_elt/subarray_01": { + "attribute_properties": { + "adminMode": { + "abs_change": [ + "-1", + "1" + ], + "__value": [ + "0" + ] + }, + "healthState": { + "abs_change": [ + "-1", + "1" + ] + }, + "obsState": { + "abs_change": [ + "-1", + "1" + ] + } + }, + "properties": { + "SubID": [ + "1" + ] + } + } + } + }, + "subarray2": { + "SubElementSubarray": { + "subelement/sub_elt/subarray_02": { + "attribute_properties": { + "adminMode": { + "abs_change": [ + "-1", + "1" + ], + "__value": [ + "0" + ] + }, + "healthState": { + "abs_change": [ + "-1", + "1" + ] + }, + "obsState": { + "abs_change": [ + "-1", + "1" + ] + } + }, + "properties": { + "SubID": [ + "2" + ] + } + } + } + } + }, + "DataBaseds": { + "2": { + "DataBase": { + "sys/database/2": {} + } + } + }, + "TangoAccessControl": { + "1": { + "TangoAccessControl": { + "sys/access_control/1": {} + } + } + }, + "TangoTest": { + "test": { + "TangoTest": { + "sys/tg_test/1": {} + } + } + } + } +} diff --git a/docker/se-lmc.yml b/docker/se-lmc.yml new file mode 100644 index 0000000000000000000000000000000000000000..af6db57a2492da01ab2e6d467551c0db3822d105 --- /dev/null +++ b/docker/se-lmc.yml @@ -0,0 +1,67 @@ +# +# Docker compose file for TANGO database and database device server +# +# Defines: +# - tangodb: MariaDB database with TANGO schema +# - databaseds: TANGO database device server +# - rsyslog-csplmc: rsyslog service for logger +# - cspmaster: CspMaster device +# +# Requires: +# - None +# +version: '2.2' + +services: + se_dsconfig: + image: nexus.engageska-portugal.pt/ska-docker/tango-dsconfig:latest + network_mode: ${NETWORK_MODE} + container_name: ${CONTAINER_NAME_PREFIX}se_dsconfig + depends_on: + - databaseds + environment: + - TANGO_HOST=${TANGO_HOST} + command: > + sh -c "wait-for-it.sh ${TANGO_HOST} --timeout=60 --strict -- + json2tango -w -a -u data/config/csplmc_dsconfig.json && sleep infinity" + volumes: + - .:/data + + sesubarray01: + image: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}/csp-lmc-subelement:latest + network_mode: ${NETWORK_MODE} + container_name: ${CONTAINER_NAME_PREFIX}sesubarray01 + depends_on: + - se_dsconfig + - semaster + environment: + - TANGO_HOST=${TANGO_HOST} + command: > + sh -c "wait-for-it.sh ${TANGO_HOST} --timeout=30 --strict -- + retry --max=5 -- tango_admin --ping-device subelement/sub_elt/master &&\ + /venv/bin/python -m cspse.lmc.SubElementSubarray subarray1" + sesubarray02: + image: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}/csp-lmc-subelement:latest + network_mode: ${NETWORK_MODE} + container_name: ${CONTAINER_NAME_PREFIX}sesubarray02 + depends_on: + - se_dsconfig + - semaster + environment: + - TANGO_HOST=${TANGO_HOST} + command: > + sh -c "wait-for-it.sh ${TANGO_HOST} --timeout=30 --strict -- + retry --max=5 -- tango_admin --ping-device subelement/sub_elt/master &&\ + /venv/bin/python -m cspse.lmc.SubElementSubarray subarray2" + + semaster: + image: ${DOCKER_REGISTRY_HOST}/${DOCKER_REGISTRY_USER}/csp-lmc-subelement:latest + network_mode: ${NETWORK_MODE} + container_name: ${CONTAINER_NAME_PREFIX}semaster + depends_on: + - se_dsconfig + environment: + - TANGO_HOST=${TANGO_HOST} + command: > + sh -c "wait-for-it.sh ${TANGO_HOST} --timeout=30 --strict -- CspSubElementMaster master" + diff --git a/docker/se-tangodb.yml b/docker/se-tangodb.yml new file mode 100644 index 0000000000000000000000000000000000000000..72090df60df3c46b64a78aa810ee6c5b07ca5629 --- /dev/null +++ b/docker/se-tangodb.yml @@ -0,0 +1,50 @@ +# +# Docker compose file for TANGO database and database device server +# +# Defines: +# - tangodb: MariaDB database with TANGO schema +# - databaseds: TANGO database device server +# +# Requires: +# - None +# +version: '2.2' +volumes: + se-tangodb: {} + +services: + tangodb: + image: nexus.engageska-portugal.pt/ska-docker/tango-db:latest + network_mode: ${NETWORK_MODE} + container_name: ${CONTAINER_NAME_PREFIX}tangodb + environment: + - MYSQL_ROOT_PASSWORD=secret + - MYSQL_DATABASE=tango + - MYSQL_USER=tango + - MYSQL_PASSWORD=tango + volumes: + - se-tangodb:/var/lib/mysql + + databaseds: + image: nexus.engageska-portugal.pt/ska-docker/tango-cpp:latest + depends_on: + - tangodb + network_mode: ${NETWORK_MODE} + container_name: ${CONTAINER_NAME_PREFIX}databaseds + environment: + - MYSQL_HOST=${MYSQL_HOST} + - MYSQL_DATABASE=tango + - MYSQL_USER=tango + - MYSQL_PASSWORD=tango + - TANGO_HOST=${TANGO_HOST} + entrypoint: + - /usr/local/bin/wait-for-it.sh + - ${MYSQL_HOST} + - --timeout=70 + - --strict + - -- + - /usr/local/bin/DataBaseds + - "2" + - -ORBendPoint + - giop:tcp::10000 + diff --git a/docker/test-harness/Makefile b/docker/test-harness/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3e479777dec8959d2bb4ff7e7daeea71290c262f --- /dev/null +++ b/docker/test-harness/Makefile @@ -0,0 +1,31 @@ +# Use bash shell with pipefail option enabled so that the return status of a +# piped command is the value of the last (rightmost) commnand to exit with a +# non-zero status. This lets us pipe output into tee but still exit on test +# failures. +SHELL = /bin/bash +.SHELLFLAGS = -o pipefail -c + +all: test lint + +# wait for the device to be available before beginning the test +# A temporary volume is mounted at /build when 'make test' is executing. +# The following steps copy across useful output to this volume which can +# then be extracted to form the CI summary for the test procedure. +test: + # option -k 'dir_name' excludes 'dir_name' contents + cd /app && python setup.py test | tee setup_py_test.stdout + mkdir -p /build/reports && \ + if [ -d /build ]; then \ + mv /app/setup_py_test.stdout /build/csp-lmc-subelement-setup-test.stdout; \ + mv /app/htmlcov /build/csp-lmc-subelement_htmlcov; \ + mv /app/build/reports/csp-lmc-subelement-unit-tests.xml /build/reports; \ + mv /app/coverage.xml /build; \ + fi; +lint: + pip3 install pylint2junit; \ + mkdir -p /build/reports; \ + cd /app && pylint --output-format=parseable cspse/lmc | tee /build/csp-lmc-subelement-code-analysis.stdout; \ + cd /app && pylint --output-format=pylint2junit.JunitReporter cspse/lmc > /build/reports/csp-lmc-subelement-linting.xml; + +.PHONY: all test lint + diff --git a/docker/test-harness/README.md b/docker/test-harness/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a3c9a496bbd5104392d08378de98af67f349183f --- /dev/null +++ b/docker/test-harness/README.md @@ -0,0 +1,3 @@ +This directory is uploaded to the container when 'make test' is executed. Files +in this directory will be found inside /build once uploaded to the container. + diff --git a/docs/src/conf.py b/docs/src/conf.py index 8ee1cc513e6a55fa19f443f71a616d088548b9e4..0071d7d097fec3f29aa6c44ab8f96417401ee89e 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -17,11 +17,17 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # +autodoc_mock_imports = ['PyTango', 'tango', 'tango.server','run', 'command', + 'future', 'future.utils', 'logging', 'logging.handlers', 'ska', + 'ska.base', 'ska.base.control_model', 'SKAMaster', 'SKASubarray','numpy', + 'csp_lmc_common' + ] +autodoc_member_order = 'bysource' + import os import sys -sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath('../../')) import sphinx_rtd_theme - def setup(app): app.add_stylesheet('css/custom.css') app.add_javascript('js/github.js') @@ -45,7 +51,8 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', - 'recommonmark'] + 'recommonmark' + ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -60,9 +67,9 @@ source_suffix = ['.rst', '.md'] master_doc = 'index' # General information about the project. -project = 'developer.skatelescope.org' -copyright = '2018, SKA Organization' -author = 'Marco Bartolini' +project = 'Csp:LMC Subelement Classes' +copyright = '2020, INAF-SKA Organization' +author = 'C.Baffa, E.Giani' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -172,8 +179,8 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'developerskatelescopeorg.tex', 'developer.skatelescope.org Documentation', - 'Marco Bartolini', 'manual'), + (master_doc, 'CspLMCSubElement.tex', 'CSP.LMC Subelement TANGO Classes Documentation', + 'SKA Organization', 'manual'), ] @@ -182,7 +189,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'developerskatelescopeorg', 'developer.skatelescope.org Documentation', + (master_doc, 'csplmc-subelement', 'CSP.LMC Subelement TANGO Classes Documentation', [author], 1) ] @@ -193,8 +200,8 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'developerskatelescopeorg', 'developer.skatelescope.org Documentation', - author, 'developerskatelescopeorg', 'One line description of project.', + (master_doc, 'CSP.LMC-SubElement', 'CSP.LMC Subelement TANGO Classes Documentation', + author, 'CSP.LMC-SubElement', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/src/index.rst b/docs/src/index.rst index 30526882f71f8d4468b8e40bc640379496b9c70e..4b79b177d51d6bdb6afe890d89fa246efee8661c 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -13,6 +13,10 @@ :caption: Home :hidden: +SKA CSP-LMC-SUBELEMENT Abstract Class +===================================== + +This project contains the `CSP.LMC.SUBELEMENT` abstract classes prototype. .. README ============================================================= @@ -20,7 +24,7 @@ .. toctree:: :maxdepth: 2 - :caption: Readme + :caption: Introduction ../../README @@ -30,15 +34,18 @@ .. toctree:: :maxdepth: 2 - :caption: Package name + :caption: Csp.LMC SubElement TANGO Classes :hidden: package/guide -Project-name documentation HEADING -================================== +Detailed Abstract Class Documentation +===================================== + -These are all the packages, functions and scripts that form part of the project. +We report here the detailed descriptions of the component that form part of the project. +This project includes few classes: the `CspSubElementMaster`, the `CspSubElementSubarray` and +the `Rack` device drivers. - :doc:`package/guide` diff --git a/docs/src/package/guide.rst b/docs/src/package/guide.rst index 9b3c93ede725b65624354dfa6829919d0de18748..92df00ee167b5d96ff3ff27865cd9104b5b44e9c 100644 --- a/docs/src/package/guide.rst +++ b/docs/src/package/guide.rst @@ -2,31 +2,23 @@ .. _package-guide: .. todo:: - - Insert todo's here + - write documentation for private and protected attributes and methods of + the CSP.LMC Subelement classes. -************************** -Package-name documentation -************************** +************************ +Public API documentation +************************ -This section describes requirements and guidelines. - -Subtitle -======== - -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. .. Automatic API Documentation section. Generating the API from the docstrings. Modify / change the directory structure as you like -Public API Documentation -```````````````````````` -Functions ---------- - -.. automodule:: ska_python_skeleton.ska_python_skeleton +CspSubElementMaster TANGO Class +-------------------------------- +.. automodule:: cspse.lmc.subelement_master :members: + :member-order: -Classes -------- -.. autoclass:: ska_python_skeleton.ska_python_skeleton.SKA - :noindex: - :members: +CspSubElementSubarray TANGO Class +--------------------------------- +.. todo:: + - write documentation for CSP.LMC SubElementSubarray Class diff --git a/pogo/CspSubElementMaster.xmi b/pogo/CspSubElementMaster.xmi new file mode 100644 index 0000000000000000000000000000000000000000..0abe206bc1527317ac3396b899b2ee31bb20d166 --- /dev/null +++ b/pogo/CspSubElementMaster.xmi @@ -0,0 +1,439 @@ + + + + + + + + + + + + + 4 + + + + + 4 + + + + + + + + + + + + + 16 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.cfg b/setup.cfg index 3f0083ccde8be19b671d6e977b83151763b5c1ea..ed35f2e6ee67a2831adc20130554f1d07af8983b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,12 +7,24 @@ test=pytest [coverage:run] branch = True -source = ska_python_skeleton +source = cspse [tool:pytest] testpaths = tests -addopts = --cov --json-report --json-report-file=htmlcov/report.json --cov-report term --cov-report html --cov-report xml --pylint --pylint-error-types=EF --junitxml=./build/reports/unit-tests.xml +addopts = --verbose + --cov=cspse + --json-report + --json-report-file=htmlcov/report.json + --cov-report=term + --cov-report=html + --cov-report=xml + --junitxml=build/reports/csp-lmc-subelement-unit-tests.xml +junit_family=legacy +console_output_style = progress + +[coverage:report] +show_missing = True # Define `python setup.py build_sphinx` [build_sphinx] source-dir = docs diff --git a/setup.py b/setup.py index 9a9bf5815a08e7614932c6c078d1fde0abaa0a1a..25947e60b6af751f95f34c693e69441b56e50bae 100644 --- a/setup.py +++ b/setup.py @@ -1,39 +1,49 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from setuptools import setup +import os +import sys +from setuptools import setup, find_packages + +setup_dir = os.path.dirname(os.path.abspath(__file__)) + +# make sure we use latest info from local code +sys.path.insert(0, setup_dir) + +INFO = {} with open('README.md') as readme_file: - readme = readme_file.read() + long_description = readme_file.read() +RELEASE_FILENAME = os.path.join(setup_dir, 'cspse','lmc','release.py') +exec(open(RELEASE_FILENAME).read(), INFO) setup( - name='ska_python_skeleton', - version='0.0.0', - description="", - long_description=readme + '\n\n', - author="Your Name", - author_email='your.email@mail.com', - url='https://github.com/ska-telescope/ska-python-skeleton', - packages=[ - 'ska_python_skeleton', - ], - package_dir={'ska_python_skeleton': - 'ska_python_skeleton'}, + name=INFO['name'], + version=INFO['version'], + description=INFO['description'], + author=INFO['author'], + author_email=INFO['author_email'], + packages=find_packages(), + license=INFO['license'], + url=INFO['url'], + long_description=long_description, + keywords="csp lmc ska tango", include_package_data=True, - license="BSD license", zip_safe=False, classifiers=[ - 'Development Status :: 2 - Pre-Alpha', + 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', + 'Operating System :: POSIX :: Linux', + 'License :: Other/Proprietary License', 'Natural Language :: English', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', ], test_suite='tests', - install_requires=[], # FIXME: add your package's dependencies to this list + install_requires=[ + 'pytango >=9.3.1', + 'future', + 'csp-lmc-common > 0.5.0', + ], setup_requires=[ # dependency for `python setup.py test` 'pytest-runner', @@ -46,7 +56,13 @@ setup( 'pytest-cov', 'pytest-json-report', 'pycodestyle', + 'mock' ], + entry_points={ + "console_scripts": [ + "CspSubElementMaster=cspse.lmc.subelement_master:main", + ] + }, extras_require={ 'dev': ['prospector[with_pyroma]', 'yapf', 'isort'], } diff --git a/ska_python_skeleton/__init__.py b/ska_python_skeleton/__init__.py deleted file mode 100644 index 82e49f1ad3ed1e40ba856944b5ae80363654a869..0000000000000000000000000000000000000000 --- a/ska_python_skeleton/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- - -"""Module init code.""" - - -__version__ = '0.0.0' - -__author__ = 'Your Name' -__email__ = 'your.email@mail.com' diff --git a/ska_python_skeleton/ska_python_skeleton.py b/ska_python_skeleton/ska_python_skeleton.py deleted file mode 100644 index 7dd613e2c0084d4a358082667939c3fb70b994b7..0000000000000000000000000000000000000000 --- a/ska_python_skeleton/ska_python_skeleton.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 -*- - -#"""Define function placeholders and test function examples.""" - - -# TODO: Replace all the following code with the desired functionality for the package -def function_example(): - """Example: function outside of a class""" - - -# TODO: Replace all the following code with the desired functionality for the package -class SKA: - """Define class, methods etc""" - - something = 0 - - def example(self): - """Example: Define non return function for subsequent test.""" - - def example_2(self): - """Example: Define function for subsequent test with specific return value.""" - return 2 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..69c7a6dbf67e4c7c6752195d1f5eca61a2f79728 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,65 @@ +""" +A module defining a list of fixtures that are shared across tests. +""" +import importlib +import pytest + +from tango.test_context import DeviceTestContext + + +@pytest.fixture(scope="class") +def tango_context(request): + """Creates and returns a TANGO DeviceTestContext object. + + Parameters + ---------- + request: _pytest.fixtures.SubRequest + A request object gives access to the requesting test context. + """ + test_properties = { + 'CspSubElementMaster': { + 'SkaLevel': '4', + 'LoggingTargetsDefault': '', + 'GroupDefinitions': '', + 'NrSubarrays': '16', + 'CapabilityTypes': '', + 'MaxCapabilities': ['VCC:197', 'FSP:27'] + }, + + 'CspSubElementSubarray': { + 'CapabilityTypes': ['VCC','FSP'], + 'LoggingTargetsDefault': '', + 'GroupDefinitions': '', + 'SkaLevel': '4', + 'SubID': '1', + }, + } + + # This fixture is used to decorate classes like "TestSKABaseDevice" or + # "TestSKALogger". We drop the first "Test" from the string to get the + # class name of the device under test. + # Similarly, the test module is like "test_base_device.py". We drop the + # first "test_" to get the module name + test_class_name = request.cls.__name__ + class_name = test_class_name.split('Test', 1)[-1] + print("class_name:", class_name) + module = importlib.import_module("cspse.lmc", class_name) + class_type = getattr(module, class_name) + + tango_context = DeviceTestContext(class_type, properties=test_properties.get(class_name)) + tango_context.start() + yield tango_context + tango_context.stop() + + +@pytest.fixture(scope="function") +def initialize_device(tango_context): + """Re-initializes the device. + + Parameters + ---------- + tango_context: tango.test_context.DeviceTestContext + Context to run a device without a database. + """ + print("Sono qui") + yield tango_context.device.Init() diff --git a/tests/test_se_master.py b/tests/test_se_master.py new file mode 100644 index 0000000000000000000000000000000000000000..a598ec38a983f0c8591894c47c96549c8f7b0cd6 --- /dev/null +++ b/tests/test_se_master.py @@ -0,0 +1,287 @@ +# +# -*- coding: utf-8 -*- +# +# This file is part of the CspSubElementMaster project +# +"""Contain the tests for the Master.""" + +# Standard imports +import sys +import os +import time + +# Imports +import re +import pytest +from tango import DevState +from tango import DevFailed + +# PROTECTED REGION ID(CspSubElementMaster.test_additional_imports) ENABLED START # +from ska.base.control_model import AdminMode, ControlMode, HealthState, SimulationMode, TestMode +# PROTECTED REGION END # // CspSubElementMaster.test_additional_imports +# Device test case +# PROTECTED REGION ID(CspSubElementMaster.test_CspSubElementMaster_decorators) ENABLED START # +@pytest.mark.usefixtures("tango_context") +# PROTECTED REGION END # // CspSubElementMaster.test_CspSubElementMaster_decorators +class TestCspSubElementMaster(object): + """Test case for packet generation.""" + + capabilities = ['FSP:27', 'VCC:197'] + properties = { + 'SkaLevel': '4', + 'LoggingTargetsDefault': '', + 'GroupDefinitions': '', + 'NrSubarrays': '16', + 'CapabilityTypes': '', + 'MaxCapabilities': ['FSP:27', 'VCC:197'] + } + + @classmethod + def mocking(cls): + """Mock external libraries.""" + # Example : Mock numpy + # cls.numpy = CspSubElementMaster.numpy = MagicMock() + # PROTECTED REGION ID(CspSubElementMaster.test_mocking) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_mocking + + def test_properties(self, tango_context): + # Test the properties + # PROTECTED REGION ID(CspSubElementMaster.test_properties) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_properties + pass + + # PROTECTED REGION ID(CspSubElementMaster.test_State_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_State_decorators + def test_State(self, tango_context): + """Test for State""" + # PROTECTED REGION ID(CspSubElementMaster.test_State) ENABLED START # + assert tango_context.device.State() == DevState.INIT + # PROTECTED REGION END # // CspSubElementMaster.test_State + + # PROTECTED REGION ID(CspSubElementMaster.test_Status_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_Status_decorators + def test_Status(self, tango_context): + """Test for Status""" + # PROTECTED REGION ID(CspSubElementMaster.test_Status) ENABLED START # + assert tango_context.device.Status() == "The device is in INIT state." + # PROTECTED REGION END # // CspSubElementMaster.test_Status + + # PROTECTED REGION ID(CspSubElementMaster.test_GetVersionInfo_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_GetVersionInfo_decorators + def test_GetVersionInfo(self, tango_context): + """Test for GetVersionInfo""" + # PROTECTED REGION ID(CspSubElementMaster.test_GetVersionInfo) ENABLED START # + versionPattern = re.compile( + r'CspSubElementMaster, lmcbaseclasses, [0-9].[0-9].[0-9], ' + r'A set of generic base devices for SKA Telescope.') + versionInfo = tango_context.device.GetVersionInfo() + assert (re.match(versionPattern, versionInfo[0])) != None + # PROTECTED REGION END # // CspSubElementMaster.test_GetVersionInfo + + + # PROTECTED REGION ID(CspSubElementMaster.test_buildState_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_buildState_decorators + def test_buildState(self, tango_context): + """Test for buildState""" + # PROTECTED REGION ID(CspSubElementMaster.test_buildState) ENABLED START # + buildPattern = re.compile( + r'lmcbaseclasses, [0-9].[0-9].[0-9], ' + r'A set of generic base devices for SKA Telescope') + assert (re.match(buildPattern, tango_context.device.buildState)) != None + # PROTECTED REGION END # // CspSubElementMaster.test_buildState + + # PROTECTED REGION ID(CspSubElementMaster.test_versionId_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_versionId_decorators + def test_versionId(self, tango_context): + """Test for versionId""" + # PROTECTED REGION ID(CspSubElementMaster.test_versionId) ENABLED START # + versionIdPattern = re.compile(r'[0-9].[0-9].[0-9]') + assert (re.match(versionIdPattern, tango_context.device.versionId)) != None + # PROTECTED REGION END # // CspSubElementMaster.test_versionId + + # PROTECTED REGION ID(CspSubElementMaster.test_healthState_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_healthState_decorators + def test_healthState(self, tango_context): + """Test for healthState""" + # PROTECTED REGION ID(CspSubElementMaster.test_healthState) ENABLED START # + assert tango_context.device.healthState == HealthState.OK + # PROTECTED REGION END # // CspSubElementMaster.test_healthState + + # PROTECTED REGION ID(CspSubElementMaster.test_adminMode_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_adminMode_decorators + def test_adminMode(self, tango_context): + """Test for adminMode""" + # PROTECTED REGION ID(CspSubElementMaster.test_adminMode) ENABLED START # + assert tango_context.device.adminMode == AdminMode.ONLINE + # PROTECTED REGION END # // CspSubElementMaster.test_adminMode + + # PROTECTED REGION ID(CspSubElementMaster.test_write_adminMode_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_adminMode_decorators + def test_write_adminMode(self, tango_context): + """Test for adminMode""" + # PROTECTED REGION ID(CspSubElementMaster.test_adminMode) ENABLED START # + tango_context.device.adminMode = AdminMode.OFFLINE + time.sleep(2) + assert tango_context.device.adminMode == AdminMode.OFFLINE + assert tango_context.device.State() == DevState.DISABLE + # PROTECTED REGION END # // CspSubElementMaster.test_adminMode + + # PROTECTED REGION ID(CspSubElementMaster.test_On_invalid_adminModedecorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_On_invalid_adminMode decorators + def test_On_invalid_adminMode(self, tango_context): + """Test On command with offline adminmode""" + # PROTECTED REGION ID(CspSubElementMaster.test_On_invalid_adminMode) ENABLED START # + with pytest.raises(DevFailed) as df: + argin = [""] + tango_context.device.On(argin) + assert "On command can't" in str(df.value.args[0].desc) + # PROTECTED REGION END # // CspSubElementMaster.test_On_invalid_adminMode + + # PROTECTED REGION ID(CspSubElementMaster.test_Off_invalid_adminModedecorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_Off_invalid_adminMode decorators + def test_Off_invalid_adminMode(self, tango_context): + """Test Off command with offline adminmode""" + # PROTECTED REGION ID(CspSubElementMaster.test_Off_invalid_adminMode) ENABLED START # + with pytest.raises(DevFailed) as df: + argin = [""] + tango_context.device.Off(argin) + assert "Off command can't" in str(df.value.args[0].desc) + # PROTECTED REGION END # // CspSubElementMaster.test_Off_invalid_adminMode + + # PROTECTED REGION ID(CspSubElementMaster.test_Standby_invalid_adminModedecorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_Standby_invalid_adminMode decorators + def test_Standby_invalid_adminMode(self, tango_context): + """Test Standby command with offline adminmode""" + # PROTECTED REGION ID(CspSubElementMaster.test_Standby_invalid_adminMode) ENABLED START # + with pytest.raises(DevFailed) as df: + argin = [""] + tango_context.device.Standby(argin) + assert "Standby command can't" in str(df.value.args[0].desc) + # PROTECTED REGION END # // CspSubElementMaster.test_Standby_invalid_adminMode + + # PROTECTED REGION ID(CspSubElementMaster.test_On_invalid_Statedecorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_On_invalid_State decorators + def test_On_invalid_State(self, tango_context): + """Test On command with offline adminmode""" + # PROTECTED REGION ID(CspSubElementMaster.test_On_invalid_State) ENABLED START # + # set the adminMode to ONLINE + # reinitialize the device to return to + # adminMode =ONLINE and State=INIT + tango_context.device.Init() + time.sleep(2) + assert tango_context.device.adminMode == AdminMode.ONLINE + assert tango_context.device.State() == DevState.INIT + with pytest.raises(DevFailed) as df: + argin = [] + tango_context.device.On(argin) + assert "Command On can't" in str(df.value.args[0].desc) + # PROTECTED REGION END # // CspSubElementMaster.test_On_invalid_State + + # PROTECTED REGION ID(CspSubElementMaster.test_Off_invalid_Statedecorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_Off_invalid_State decorators + def test_Off_invalid_State(self, tango_context): + """Test Off command with offline adminmode""" + # PROTECTED REGION ID(CspSubElementMaster.test_Off_invalid_State) ENABLED START # + assert tango_context.device.adminMode == AdminMode.ONLINE + with pytest.raises(DevFailed) as df: + argin = [] + tango_context.device.Off(argin) + assert "Command Off can't" in str(df.value.args[0].desc) + # PROTECTED REGION END # // CspSubElementMaster.test_Off_invalid_State + + # PROTECTED REGION ID(CspSubElementMaster.test_Standby_invalid_Statedecorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_Standby_invalid_State decorators + def test_Standby_invalid_State(self, tango_context): + """Test Standby command with offline adminmode""" + # PROTECTED REGION ID(CspSubElementMaster.test_Standby_invalid_State) ENABLED START # + assert tango_context.device.adminMode == AdminMode.ONLINE + with pytest.raises(DevFailed) as df: + argin = [] + tango_context.device.Standby(argin) + assert "Command Standby can't" in str(df.value.args[0].desc) + # PROTECTED REGION END # // CspSubElementMaster.test_Standby_invalid_State + + # PROTECTED REGION ID(CspSubElementMaster.test_controlMode_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_controlMode_decorators + def test_controlMode(self, tango_context): + """Test for controlMode""" + # PROTECTED REGION ID(CspSubElementMaster.test_controlMode) ENABLED START # + assert tango_context.device.controlMode == ControlMode.REMOTE + # PROTECTED REGION END # // CspSubElementMaster.test_controlMode + + # PROTECTED REGION ID(CspSubElementMaster.test_maxCapabilities_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_maxCapabilities_decorators + def test_maxCapabilities(self, tango_context): + """Test for maxCapabilities""" + # PROTECTED REGION ID(CspSubElementMaster.test_maxCapabilities) ENABLED START # + assert tango_context.device.maxCapabilities == ('FSP:27', 'VCC:197') + # PROTECTED REGION END # // CspSubElementMaster.test_maxCapabilities + + # PROTECTED REGION ID(CspSubElementMaster.test_availableCapabilities_decorators) ENABLED START # + # PROTECTED REGION END # // CspSubElementMaster.test_availableCapabilities_decorators + def test_availableCapabilities(self, tango_context): + """Test for availableCapabilities""" + # PROTECTED REGION ID(CspSubElementMaster.test_availableCapabilities) ENABLED START # + assert tango_context.device.availableCapabilities == ('FSP:27', 'VCC:197') + # PROTECTED REGION END # // CspSubElementMaster.test_availableCapabilities + + def test_num_of_dev_completed_task(self, tango_context): + """Test numOfCompletedTask attribute""" + assert tango_context.device.numOfDevCompletedTask == 0 + + def test_list_of_completed_task(self, tango_context): + """Test listOfCompletedTask attribute""" + assert tango_context.device.listOfDevCompletedTask == None + + def test_list_of_components(self, tango_context): + """Test listOfComponents attribute""" + assert tango_context.device.listOfComponents == None + + def test_timeout_expired(self, tango_context): + """Test xxxTimeoutExpired flags attribute""" + assert not tango_context.device.onCmdTimeoutExpired + assert not tango_context.device.offCmdTimeoutExpired + assert not tango_context.device.standbyCmdTimeoutExpired + + def test_failure_raised(self, tango_context): + """Test xxxCmdFailure attributes""" + assert not tango_context.device.onCmdFailure + assert not tango_context.device.offCmdFailure + assert not tango_context.device.standbyCmdFailure + + def test_failure_message(self, tango_context): + """Test xxxFailureMessage attributes""" + assert not tango_context.device.onFailureMessage + assert not tango_context.device.offFailureMessage + assert not tango_context.device.standbyFailureMessage + + def test_command_progress(self, tango_context): + """Test xxxCommandProgress attributes""" + assert tango_context.device.onCommandProgress == 0 + assert tango_context.device.offCommandProgress == 0 + assert tango_context.device.standbyCommandProgress == 0 + + def test_command_duration_measured(self, tango_context): + """Test xxxCmdDurationMeasured attributes""" + assert tango_context.device.onCmdDurationMeasured == 0 + assert tango_context.device.offCmdDurationMeasured == 0 + assert tango_context.device.standbyCmdDurationMeasured == 0 + + def test_command_duration_expected(self, tango_context): + """Test xxxCmdDurationExpected attributes""" + assert tango_context.device.onCmdDurationExpected == 30 + assert tango_context.device.offCmdDurationExpected == 30 + assert tango_context.device.standbyCmdDurationExpected == 30 + + def test_set_command_duration_expected(self, tango_context): + """Test xxxCmdDurationExpected attributes""" + tango_context.device.onCmdDurationExpected = 20 + tango_context.device.offCmdDurationExpected = 20 + tango_context.device.standbyCmdDurationExpected = 20 + # wait to let the polling thread update the attrs value + time.sleep(3) + assert tango_context.device.onCmdDurationExpected == 20 + assert tango_context.device.offCmdDurationExpected == 20 + assert tango_context.device.standbyCmdDurationExpected == 20 + + diff --git a/tests/test_ska_skeleton.py b/tests/test_ska_skeleton.py deleted file mode 100644 index 553d1790b64aa674ab19afa33393738b8729d40e..0000000000000000000000000000000000000000 --- a/tests/test_ska_skeleton.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Tests for the ska_python_skeleton module.""" -import pytest - -from ska_python_skeleton import ska_python_skeleton - - -# TODO: Replace all the following examples with tests for the ska_python_skeleton package code -def test_something(): - """Example: Assert with no defined return value.""" - assert True - - -def test_with_error(): - """Example: Assert raising error.""" - with pytest.raises(ValueError): - # Do something that raises a ValueError - raise ValueError - - -# Fixture example -@pytest.fixture -def an_object(): - """Example: Define fixture for subsequent test.""" - return {} - - -def test_ska_python_skeleton(an_object): - """Example: Assert fixture return value.""" - assert an_object == {} - - -def test_package(): - """Example: Assert the ska_python_skeleton package code.""" - assert ska_python_skeleton.function_example() is None - foo = ska_python_skeleton.SKA() - assert foo.example_2() == 2 - assert foo.example() is None