Commit e5f16d01 authored by Elisabetta Giani's avatar Elisabetta Giani
Browse files

AT5-257: Implemented CmdInputArgsCheck decorator to check the On/Off/Standby

commands input argument.
parent 274caa24
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -40,3 +40,10 @@ class ObsState(IntEnum):
    PAUSED      = 4
    ABORTED     = 5
    FAULT       = 6

@unique
class CmdExecState(IntEnum):
    IDLE        = 0
    RUNNING     = 1
    QUEUED      = 2
+161 −12
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@ sys.path.insert(0, utils_path)

import tango
import cspcommons
from cspcommons import AdminMode
from cspcommons import AdminMode, CmdExecState
import functools

class AdminModeCheck(object):  
@@ -23,27 +23,176 @@ class AdminModeCheck(object):
        @functools.wraps(f)
        def admin_mode_check(*args, **kwargs):
            # get the  device instance
            dev = args[0]
            dev_instance = args[0]
            cmd_to_execute = self._args
            print("AdminModeCheck command to execute:", cmd_to_execute)
            # Check the AdminMode value: the command is callable only if the 
            # the administration mode is ONLINE or MAINTENACE
            
            if dev._admin_mode in [AdminMode.OFFLINE, AdminMode.NOTFITTED,
            if dev_instance._admin_mode in [AdminMode.OFFLINE, AdminMode.NOTFITTED,
                                   AdminMode.RESERVED]:
                # NOTE: when adminMode is NOT_FITTED, OFFLINE or RESERVED device itself
                # sets the TANGO state to DISABLE
                # Add only a check on State value to log a warning message if it
                # is different from DISABLE
                msg_args = (dev.get_state(), AdminMode(dev._admin_mode).name)
                if dev.get_state() != tango.DevState.DISABLE:
                    dev.dev_logging("is_On_allowed: incoherent device State {} "
                msg_args = (cmd_to_execute, dev_instance.get_state(), AdminMode(dev_instance._admin_mode).name)
                if dev_instance.get_state() != tango.DevState.DISABLE:
                    dev_instance.dev_logging("Incoherent device State {} "
                                 " with adminMode {}".format(*msg_args),
                                 tango.LogLevel.LOG_WARN)
                err_msg = ("On() command can't be issued when State is {} and"
                err_msg = ("{} command can't be issued when State is {} and"
                       " adminMode is {} ".format(*msg_args))
            
                tango.Except.throw_exception("Command not executable",
                tango.Except.throw_exception("Invalid adminMode value",
                                             err_msg,
                                         "On command execution",
                                             "AdminModeCheck decorator",
                                             tango.ErrSeverity.ERR)
            return f(*args, **kwargs)
        return admin_mode_check

class CmdInputArgsCheck(object):
    """
    Class designed to be a decorator for the CspMaster methods.
    The *decorator function* performs a check on the function input argument,
    detecting if the corresponding servers are running and the device
    administrative mode are in the right state to execute a command (i.e
    ONLINE/MAINTENACE)
    The *decorator function* accepts some parameters as input to customize
    its functionality
    """
    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):
            dev_instance = args[0]
            # get the information passed as arguments of the decorator
            attrs_to_subscribe  = self._args
            try:
                exec_cmd = self._kwargs['cmd_name']
            except KeyError as key_err:
                msg =("No key found for {}".fomat(str(key_err)))
                self.dev_logging(msg, tango.LogLevel.LOG_WARN)
                # TODO:  throw an exception
                return
            input_arg = args[1]
            device_list = []
            num_of_devices = len(input_arg)
            if num_of_devices == 0:
                # no input argument -> switch on all sub-elements
                num_of_devices = len(dev_instance._se_fqdn)
                device_list = dev_instance._se_fqdn
            else:
                if num_of_devices > len(dev_instance._se_fqdn):
                    # too many devices specified-> log the warning but go on
                    # with command execution
                    dev_instance.dev_logging("Too many input parameters", tango.LogLevel.LOG_WARN)
            device_list = input_arg
            print("CmdInputArgsCheck: devices {}  to check:".format(device_list))
            # If a sub-element device is already executing a power command throws an
            # exception (power commands have to be executed sequentially)
            # TODO: What to do if the required device is performing a software upgrade or is changing its 
            # adminMode? How can CSP.LMC detect this condition?
            if CmdExecState.RUNNING in dev_instance._cmd_execution_state.values():
                print("command {} is running!!".format(exec_cmd))
                # Note: need to check in the list of sub-elements, not in the device_list sent to the command!!!
                for device in dev_instance._se_fqdn:
                    for cmd_name, cmd_state in dev_instance._se_cmd_execution_state[device].items():
                        if cmd_state in [CmdExecState.RUNNING, CmdExecState.QUEUED]:
                            msg = ("Device {} is already executing the {} command"
                                    "Command state: {}".format(device, cmd_name,
                                                          dev_instance._se_cmd_execution_state[device][cmd_name]))
                            print(msg)
                            # TODO throw an exception if the command already running is not 
                            # the one requested
                            # return if the requested command is already running
                            return
            device_to_remove = []
            
            # buld the sub-element attribute name storing the command duration
            # expected value.
            cmd_time_attr_name = exec_cmd + "DurationExpected"
            for device in device_list:
                print("processing device:", device)
                # if the sub-element device server is not running or the adminMode
                # attribute is not ONLINE or MAINTENANCE, skip to next and add it to 
                # the list of the devices to remove from the input arg list
                if (not dev_instance._is_se_device_running(device) or 
                    dev_instance._se_admin_mode[device] not in [AdminMode.ONLINE,
                                                       AdminMode.MAINTENANCE]):
                    if device.find("cbf") > -1:
                        msg = ("Can't power-on device {} with"
                               " adminMode {}".format(device,  
                                                      AdminMode(dev_instance._se_admin_mode[device]).name))                                                                            
                        tango.Except.throw_exception("Command failure",
                                                      msg,
                                                      "On()",
                                                      tango.ErrSeverity.ERR)
                        
                        return
                    device_to_remove.append(device)
                    print("Devices to remove from the list:", device_to_remove)
                    continue
            for device in device_to_remove:
                device_list.remove(device)
            if not device_list:
                dev_instance.dev_logging("No device to power on", tango.LogLevel.LOG_INFO)
                return
            command_timeout = 0
            for device in device_list:
                # set to QUEUED. This state can be useful in case of command abort.
                dev_instance._se_cmd_execution_state[device][exec_cmd] = CmdExecState.QUEUED
                # reset the timeout attribute content
                dev_instance._timeout_expired[exec_cmd] = False
                # reset the timeout attribute content for each sub-element
                for cmd_name, _ in dev_instance._se_timeout_expired[device].items():
                    if dev_instance._se_timeout_expired[device][cmd_name]:
                        msg = ("Device {} expired on command {}."
                           " Clean the timeout counter".format(device, cmd_name))
                        print(msg)
                        dev_instance._se_timeout_expired[device][cmd_name] = False
                device_proxy = dev_instance._se_proxies[device]
                try:
                    # get the sub-element value for the onCommandDurationExpected attribute.
                    # If this attribute is not implemented at sub-element level,
                    # the default value is used.
                    # Note: if onCmdDurationExpected attribute is not implemented, the
                    # call device_proxy.onCmdDurationExpected throw an AttributeError exception 
                    # (not tango.DevFailed)
                    #dev_instance._se_cmd_duration_expected[device][exec_cmd] = device_proxy.onCmdDurationExpected
                    dev_instance._se_cmd_duration_expected[device][exec_cmd] = device_proxy.read_attribute(cmd_time_attr_name)
                except tango.DevFailed as tango_err:
                    # we get here if the attribute is not implemented
                    print(tango_err.args[0].desc)
                for attr in attrs_to_subscribe:
                    try:
                        if dev_instance._se_event_id[device][attr.lower()] == 0:
                            evt_id = device_proxy.subscribe_event(attr, tango.EventType.CHANGE_EVENT,
                                                 dev_instance._attributes_change_evt_cb, stateless=False)
                            dev_instance._se_event_id[device][attr.lower()] = evt_id
                    except tango.DevFailed as df:
                        print(df.args[0].desc)
            
                # evaluate the total timeout value to execute the whole command
                # note: if the xxxDurationExpected attribute read fails, it is used the default value 
                # returned from the defauld dictionary used to initialize the _se_cmd_duration_expected
                # attribute
                command_timeout +=  dev_instance._se_cmd_duration_expected[device][exec_cmd]
            # end of the for loop
            
            # use the greatest value for the onCommand duration expected.
            if command_timeout > dev_instance._cmd_duration_expected[exec_cmd]:
                dev_instance._cmd_duration_expected[exec_cmd] = command_timeout
                dev_instance.dev_logging("Modified the {} command Duration Expected value!!".format(exec_cmd),
                                tango.LogLevel.LOG_INFO)
            print("CmdInputArgsCheck end!!")
            return f(*args, **kwargs)
        return input_args_check
            
 No newline at end of file