Source code for sardana.tango.macroserver.MacroServer

#!/usr/bin/env python

##############################################################################
##
# This file is part of Sardana
##
# http://www.sardana-controls.org/
##
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
##
# Sardana is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
##
# Sardana is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
##
# You should have received a copy of the GNU Lesser General Public License
# along with Sardana.  If not, see <http://www.gnu.org/licenses/>.
##
##############################################################################

"""The MacroServer tango module"""

import os.path
from getpass import getuser

import taurus
from PyTango import (
    READ,
    READ_WRITE,
    SCALAR,
    SPECTRUM,
    DebugIt,
    DevEncoded,
    DevLong,
    DevState,
    DevString,
    DevVarStringArray,
    DevVoid,
    Except,
    Util,
)
from taurus.core.util.codecs import CodecFactory

from sardana import SardanaServer, State
from sardana.macroserver.macroserver import MacroServer as MS
from sardana.macroserver.msexception import MacroServerException
from sardana.tango.core.SardanaDevice import SardanaDevice, SardanaDeviceClass


[docs] class MacroServer(SardanaDevice): """The MacroServer tango class""" ElementsCache = None EnvironmentCache = None def __init__(self, cl, name): self._macro_server = None SardanaDevice.__init__(self, cl, name)
[docs] def init(self, name): SardanaDevice.init(self, name) if self._alias is None: self._alias = Util.instance().get_ds_inst_name() self._macro_server = ms = MS(self.get_full_name(), self.alias) ms.add_listener(self.on_macro_server_changed)
@property def macro_server(self): return self._macro_server
[docs] def delete_device(self): SardanaDevice.delete_device(self) self._macro_server.clear_log_report() # Workaround for bug #494. factory = taurus.Factory("tango") for attr in list(factory.tango_attrs.values()): attr.cleanUp()
[docs] def init_device(self): SardanaDevice.init_device(self) self.set_change_event("State", True, False) self.set_change_event("Status", True, False) self.set_change_event("TypeList", True, False) self.set_change_event("DoorList", True, False) self.set_change_event("MacroList", True, False) self.set_change_event("MacroLibList", True, False) self.set_change_event("RecorderLibList", True, False) self.set_change_event("RecorderList", True, False) self.set_change_event("Elements", True, False) self.set_change_event("Environment", True, False) dev_class = self.get_device_class() self.get_device_properties(dev_class) self.EnvironmentDb = self._calculate_name(self.EnvironmentDb) self.LogReportFilename = self._calculate_name(self.LogReportFilename) macro_server = self.macro_server macro_server.set_python_path(self.PythonPath) macro_server.set_max_parallel_macros(self.MaxParallelMacros) # if it is not possible to store/retrieve the environment from the # current path then setup a new unique path and store the environment # there forever try: macro_server.set_environment_db(self.EnvironmentDb) except Exception as exc: self.error("Failed to set environment DB to %s", self.EnvironmentDb) self.debug("Details:", exc_info=1) if "redis" in self.EnvironmentDb: msg = "Aborting MacroServer start: cannot connect to Redis at {}. Exception: {}".format( self.EnvironmentDb, exc ) Except.throw_exception("InitError", msg, "init_device") import tempfile env_db = os.path.join(tempfile.mkdtemp(), MacroServerClass.DefaultEnvRelDir) env_db = self._calculate_name(env_db) db = Util.instance().get_database() db.put_device_property(self.get_name(), dict(EnvironmentDb=env_db)) self.EnvironmentDb = env_db macro_server.set_environment_db(self.EnvironmentDb) try: macro_server.set_log_report(self.LogReportFilename, self.LogReportFormat) except Exception: self.error("Failed to setup log report to %s", self.LogReportFilename) self.debug("Details:", exc_info=1) macro_server.set_recorder_path(self.RecorderPath) macro_server.set_macro_path(self.MacroPath) self.set_state(DevState.ON)
[docs] def sardana_init_hook(self): self.macro_server.set_pool_names(self.PoolNames)
def _calculate_name(self, name): if name is None: return None util = Util.instance() return name % { "ds_name": util.get_ds_name().lower(), "ds_exec_name": util.get_ds_exec_name(), "ds_inst_name": util.get_ds_inst_name().lower(), }
[docs] def on_macro_server_changed(self, evt_src, evt_type, evt_value): # during server startup and shutdown avoid processing element # creation events if SardanaServer.server_state != State.Running: return evt_name = evt_type.name.lower() multi_attr = self.get_device_attr() elems_attr = multi_attr.get_attr_by_name("Elements") if evt_name == "poolelementschanged": # force the element list cache to be rebuild next time someone reads # the element list self.ElementsCache = None value = CodecFactory().getCodec("utf8_json").encode(("", evt_value)) self.set_attribute(elems_attr, value=value) # self.push_change_event('Elements', *evt_value.value) elif evt_name in ("elementcreated", "elementdeleted"): # force the element list cache to be rebuild next time someone reads # the element list self.ElementsCache = None elem = evt_value value = {} if "created" in evt_name: key = "new" else: key = "del" json_elem = elem.serialize(pool=self.pool.full_name) value[key] = (json_elem,) value = CodecFactory().getCodec("utf8_json").encode(("", value)) self.set_attribute(elems_attr, value=value) # self.push_change_event('Elements', *value) elif evt_name == "elementschanged": # force the element list cache to be rebuild next time someone reads # the element list self.ElementsCache = None ms_name = self.macro_server.full_name new_values, changed_values, deleted_values = [], [], [] for elem in evt_value["new"]: json_elem = elem.serialize(macro_server=ms_name) new_values.append(json_elem) for elem in evt_value["change"]: json_elem = elem.serialize(macro_server=ms_name) changed_values.append(json_elem) for elem in evt_value["del"]: json_elem = elem.serialize(macro_server=ms_name) deleted_values.append(json_elem) value = {"new": new_values, "change": changed_values, "del": deleted_values} value = CodecFactory().getCodec("utf8_json").encode(("", value)) self.set_attribute(elems_attr, value=value) # self.push_change_event('Elements', *value) elif evt_name == "environmentchanged": self.EnvironmentCache = None env_attr = multi_attr.get_attr_by_name("Environment") value = CodecFactory().getCodec("pickle").encode(("", evt_value)) self.set_attribute(env_attr, value=value)
[docs] def always_executed_hook(self): pass
def read_attr_hardware(self, data): pass
[docs] def read_DoorList(self, attr): door_names = self.macro_server.get_door_names() attr.set_value(door_names)
[docs] @DebugIt() def read_MacroList(self, attr): macro_names = self.macro_server.get_macro_names() attr.set_value(macro_names)
[docs] def read_MacroLibList(self, attr): macro_lib_names = self.macro_server.get_macro_lib_names() attr.set_value(macro_lib_names)
[docs] def read_RecorderLibList(self, attr): recorder_lib_names = self.macro_server.get_recorder_lib_names() attr.set_value(recorder_lib_names)
[docs] def read_RecorderList(self, attr): recorder_names = self.macro_server.get_recorder_class_names() attr.set_value(recorder_names)
[docs] def read_TypeList(self, attr): type_names = self.macro_server.get_data_type_names_with_asterisc() attr.set_value(type_names)
# @DebugIt()
[docs] def getElements(self, cache=True): value = self.ElementsCache if cache and value is not None: return value elements = self.macro_server.get_elements_info() value = dict(new=elements) value = CodecFactory().getCodec("utf8_json").encode(("", value)) if SardanaServer.server_state == State.Running: # Only cache value when the server is running # If a client is reading Elements during server startup, # the list might be incomplete and we don't want to cache that self.ElementsCache = value return value
# @DebugIt()
[docs] def read_Elements(self, attr): fmt, data = self.getElements() attr.set_value(fmt, data)
[docs] def is_Elements_allowed(self, req_type): return SardanaServer.server_state == State.Running
is_DoorList_allowed = is_MacroList_allowed = is_MacroLibList_allowed = ( is_RecorderLibList_allowed ) = is_RecorderList_allowed = is_TypeList_allowed = is_Elements_allowed
[docs] def GetMacroInfo(self, macro_names): """Get macro information Returns a list of strings containing macro information. Each string is a JSON encoded. Args: macro_names (list(str)): macro(s) name(s) Returns: list(str): macro(s) information """ macro_server = self.macro_server codec = CodecFactory().getCodec("json") ret = [] for _, macro in list(macro_server.get_macros().items()): if macro.name in macro_names: ret.append(codec.encode(("", macro.serialize()))[1]) return ret
[docs] def ReloadMacro(self, macro_names): """ReloadMacro(list<string> macro_names):""" try: for macro_name in macro_names: self.macro_server.reload_macro(macro_name) except MacroServerException as mse: Except.throw_exception(mse.type, mse.msg, "ReloadMacro") return ["OK"]
[docs] def ReloadMacroLib(self, lib_names): """ReloadMacroLib(sequence<string> lib_names):""" try: for lib_name in lib_names: self.macro_server.reload_macro_lib(lib_name) except MacroServerException as mse: Except.throw_exception(mse.type, mse.msg, "ReloadMacroLib") return ["OK"]
[docs] def GetMacroCode(self, argin): """GetMacroCode(<module name> [, <macro name>]) -> full filename, code, line_nb""" ret = self.macro_server.get_or_create_macro_lib(*argin) return list(map(str, ret))
[docs] def SetMacroCode(self, argin): lib_name, code = argin[:2] auto_reload = True if len(argin) > 2: auto_reload = argin[2].lower() in ("true", "yes") self.macro_server.set_macro_lib(lib_name, code, auto_reload=auto_reload)
# @DebugIt()
[docs] def getEnvironment(self, cache=True): value = self.EnvironmentCache if cache and value is not None: return value env = self.macro_server.get_env() value = dict(new=env) value = CodecFactory().getCodec("pickle").encode(("", value)) self.EnvironmentCache = value return value
[docs] def read_Environment(self, attr): fmt, data = self.getEnvironment() attr.set_value(fmt, data)
[docs] def write_Environment(self, attr): data = attr.get_write_value() data = CodecFactory().getCodec("pickle").decode(data)[1] self.macro_server.change_env(data)
[docs] def is_Environment_allowed(self, req_type): return True
[docs] class MacroServerClass(SardanaDeviceClass): """MacroServer Tango class class""" # Class Properties class_property_list = {} DefaultEnvBaseDir = "/tmp/tango-%s" % getuser() DefaultEnvRelDir = "%(ds_exec_name)s/%(ds_inst_name)s/macroserver.properties" DefaultLogReportFormat = "%(levelname)-8s %(asctime)s: %(message)s" # Device Properties device_property_list = { "PoolNames": [DevVarStringArray, "Sardana device pool device names", []], "MacroPath": [ DevVarStringArray, "list of directories to search for macros (path separators " "can be '\n' or character conventionally used by the OS to" "separate search path components, such as ':' for POSIX" "or ';' for Windows)", [], ], "RecorderPath": [ DevVarStringArray, "list of directories to search for recorders (path separators " "can be '\n' or character conventionally used by the OS to" "separate search path components, such as ':' for POSIX" "or ';' for Windows)", [], ], "PythonPath": [ DevVarStringArray, "list of directories to be appended to sys.path at startup (path " "separators can be '\n' or ':')", [], ], "MaxParallelMacros": [ DevLong, "Maximum number of macros that can execute concurrently.", [10], ], "EnvironmentDb": [ DevString, "The environment database (usually a plain file).", os.path.join(DefaultEnvBaseDir, DefaultEnvRelDir), ], "LogReportFilename": [ DevString, "Filename (absolute) which contains user log reports [default: " "None, meaning don't store log report messages]. The system will " "save old log files by appending extensions to the filename. The " "extensions are date-and-time based, using the strftime " "format %Y-%m-%d_%H-%M-%S or a leading portion thereof, " "depending on the rollover interval.", None, ], "LogReportFormat": [ DevString, "Log report format [default: '%s']" % DefaultLogReportFormat, DefaultLogReportFormat, ], "LogstashHost": [ DevString, "Hostname where Logstash runs. " "This property has been included in Sardana on a provisional " "basis. Backwards incompatible changes (up to and including " "its removal) may occur if deemed necessary by the " "core developers.", None, ], "LogstashPort": [ DevLong, "Port on which Logstash will listen on events [default: 12345]. " "This property has been included in Sardana on a provisional " "basis. Backwards incompatible changes (up to and including " "its removal) may occur if deemed necessary by the " "core developers.", 12345, ], "LogstashCacheDbPath": [ DevString, "Path to the Logstash cache database [default: None]. " "It is advised not to use the database cache, as it may " "have negative effects on logging performance. See #895. " "This property has been included in Sardana on a provisional " "basis. Backwards incompatible changes (up to and including " "its removal) may occur if deemed necessary by the " "core developers.", None, ], } # Command definitions cmd_list = { "GetMacroInfo": [ [DevVarStringArray, "Macro(s) name(s)"], [DevVarStringArray, "Macro(s) description(s)"], ], "ReloadMacro": [ [DevVarStringArray, "Macro(s) name(s)"], [ DevVarStringArray, "[OK] if successfull or a traceback " "if there was a error (one string with complete traceback of " "each error)", ], ], "ReloadMacroLib": [ [DevVarStringArray, "MacroLib(s) name(s)"], [ DevVarStringArray, "[OK] if successfull or a traceback " "if there was a error (one string with complete traceback of " "each error)", ], ], "GetMacroCode": [ [DevVarStringArray, "<MacroLib name> [, <Macro name>]"], [ DevVarStringArray, "result is a sequence of 3 strings:\n" "<full path and file name>, <code>, <line number>", ], ], "SetMacroCode": [ [ DevVarStringArray, "<MacroLib name>, <code> [, <Auto reload>=True]\n" "- if macro lib is a simple module name:\n" " - if it exists, it is overwritten, otherwise a new python " "file is created in the directory of the first element in " "the MacroPath property" "- if macro lib is the full path name:\n" " - if path is not in the MacroPath, an exception is thrown" " - if file exists it is overwritten otherwise a new file " "is created", ], [DevVoid, ""], ], } # Attribute definitions attr_list = { "DoorList": [[DevString, SPECTRUM, READ, 256]], "MacroList": [[DevString, SPECTRUM, READ, 4096]], "MacroLibList": [[DevString, SPECTRUM, READ, 1024]], "RecorderLibList": [[DevString, SPECTRUM, READ, 1024]], "RecorderList": [[DevString, SPECTRUM, READ, 1024]], "TypeList": [[DevString, SPECTRUM, READ, 256]], "Elements": [ [DevEncoded, SCALAR, READ], { "label": "Elements", "description": "the list of all elements (a JSON encoded dict)", }, ], "Environment": [ [DevEncoded, SCALAR, READ_WRITE], { "label": "Environment", "description": "The macro server environment (a JSON encoded dict)", }, ], }