Source code for taurus.core.tango.starter

#!/usr/bin/env python

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus 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.
#
# Taurus 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 Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""This module provides a very simple API for starting and killing device
servers

It is not a replacement of the Tango Starter Device Server since this is much
more limited in scope.
"""

import os
import time
import subprocess
import PyTango
from taurus.core.util.log import Logger

__all__ = ["Starter", "ProcessStarter"]
__docformat__ = "restructuredtext"

_log = Logger("Starter")


[docs]class Starter(object): """Abstract class for managing (starting, stopping, registering and removing) a Tango Device Server. Derived classes should provide the methods for starting and stopping a device. """ def __init__(self, ds_name): """ :param ds_name: Device Server name in the form "server/instance" :type ds_name: str """ self.ds_name = ds_name self.dserver_name = "dserver/%s" % ds_name try: self.dserver = PyTango.DeviceProxy(self.dserver_name) self.serverExisted = True except PyTango.DevFailed: # not registered? self.dserver = None self.serverExisted = False self._addedDevices = []
[docs] def hardKill(self): raise NotImplementedError("hardKill method is mandatory")
[docs] def terminate(self): raise NotImplementedError("terminate method is mandatory")
[docs] def start(self): raise NotImplementedError("start method is mandatory")
[docs] def stopDs(self, synch=True, hard_kill=False, wait_seconds=10): if hard_kill: _log.info("Hard killing server %s..." % self.ds_name) self.hardKill() else: _log.info("Stopping server %s..." % self.ds_name) self.terminate() if not synch: return _log.debug("Waiting for server %s to get stopped" % self.ds_name) try: self.process.wait(wait_seconds) except subprocess.TimeoutExpired: _log.warning( "Server %s did not stop within %d seconds" % (self.ds_name, wait_seconds) ) else: _log.info("Server %s has been stopped" % self.ds_name)
[docs] def startDs(self, synch=True, wait_seconds=10): if self.isRunning(): _log.warning("Server already running") return _log.info("Starting server %s..." % self.ds_name) self.start() if not synch: return for i in range(wait_seconds): _log.debug( "Waiting for server %s to get started... %d" % (self.ds_name, i) ) if self.isRunning(): _log.info("Server %s has been started" % self.ds_name) ############################################################## # Workaround to avoid race conditions # TODO: Find root cause of race condition and fix _wait = float(os.environ.get("TAURUS_STARTER_WAIT", 0)) if _wait: _log.info("Waiting %g s after start" % _wait) time.sleep(_wait) ############################################################## return else: time.sleep(1) _log.warning( "Server %s did not start within %d seconds" % (self.ds_name, wait_seconds) )
[docs] def addNewDevice(self, device, klass=None): """ Register a device of this server in the DB (register the server if not present) e.g. to create Starter in an init script:: addNewDevice('sys/tg_test/foobar', klass='TangoTest') :param klass: class name. If None passed, it defaults to the server name (without instance name) """ if device in self._addedDevices: _log.warning("%s already added. Skipping" % device) return if klass is None: klass = self.ds_name.split("/")[0] # in case the device is already defined, skipping... db = PyTango.Database() try: db.import_device(device) _log.warning("%s already exists. Skipping" % device) return except Exception: pass # register the device, # in case the server did not exist before this will define it dev_info = PyTango.DbDevInfo() dev_info.name = device dev_info.klass = klass dev_info.server = self.ds_name db.add_device(dev_info) # create proxy to dserver self.dserver = PyTango.DeviceProxy(self.dserver_name) # keep track of added devices self._addedDevices.append(device)
[docs] def cleanDb(self, force=False): """removes devices which have been added by :meth:`addNewDevice` and then removes the server if it was registered by this starter (or, if force is True, it removes the server in any case) :param force: force removing of the Server even if it was not registered within this starter :type force: bool """ for device in self._addedDevices: PyTango.Database().delete_device(device) _log.info("Deleted device %s" % device) if (self.serverExisted or len(self._addedDevices) == 0) and not force: msg = ( "%s was not registered by this starter. Not removing. " + "Use %s.cleanDb(force=True) to force cleanup" ) % (self.ds_name, self.__class__.__name__) _log.warning(msg) else: self.stopDs(hard_kill=True) PyTango.Database().delete_server(self.ds_name) _log.info("Deleted Server %s" % self.ds_name)
[docs] def isRunning(self): # TODO: In case the sleeps in startDS and stopDS need to be re-added, # we should study another implementation for this method. if self.dserver is None: return False try: self.dserver.ping() except PyTango.DevFailed: return False return True
[docs]class ProcessStarter(Starter): """A :class:`Starter` which uses subprocess to start and stop a device server. """ def __init__(self, execname, ds_name): """ :param execname: path to the executable to launch the server :type execname: str :param ds_name: Device Server name in the form "server/instance" :type ds_name: str """ super(ProcessStarter, self).__init__(ds_name) self.ds_instance = ds_name.split("/")[1] self.exec_name = os.path.abspath(execname) self.process = None
[docs] def start(self): dev_null = open(os.devnull, "wb") args = [self.exec_name, self.ds_instance] self.process = subprocess.Popen(args, stdout=dev_null, stderr=dev_null)
[docs] def terminate(self): if self.process: try: self.dserver.Kill() except Exception: self.process.terminate() else: _log.warning("Process not started, cannot terminate it.")
[docs] def hardKill(self): if self.process: try: self.dserver.Kill() # no hard kill in Tango DS, simply kill it except Exception: self.process.kill() else: _log.warning("Process not started, cannot terminate it.")
if __name__ == "__main__": from taurus.test.resource import getResourcePath exe = getResourcePath("taurus.core.tango.test.res", "TangoSchemeTest") s = ProcessStarter(exe, "TangoSchemeTest/test_removeme") devname = "testing/tangoschemetest/temp-1" s.addNewDevice(devname, klass="TangoSchemeTest") s.startDs() try: print("Is running:", s.isRunning()) print("ping:", PyTango.DeviceProxy(devname).ping()) except Exception as e: print(e) s.stopDs() s.cleanDb(force=False)