From 2dae7649be56cdf44c2dea088dea8e21949684d4 Mon Sep 17 00:00:00 2001 From: Alexander Lenz Date: Mon, 8 Aug 2016 11:12:19 +0200 Subject: [PATCH] Fix/Improve startup/init. Change-Id: Ia4b90c75a8cf89f4069bd8984a0e0c88f9ffa832 --- bin/{server.py => secop-server} | 52 +++----------- etc/secop-server | 124 ++++++++++++++++++++++++++++++++ requirements.txt | 3 +- src/loggers/__init__.py | 7 ++ src/server.py | 44 ++++-------- 5 files changed, 153 insertions(+), 77 deletions(-) rename bin/{server.py => secop-server} (63%) create mode 100755 etc/secop-server diff --git a/bin/server.py b/bin/secop-server similarity index 63% rename from bin/server.py rename to bin/secop-server index 0b3cced..fbb18ea 100755 --- a/bin/server.py +++ b/bin/secop-server @@ -48,15 +48,10 @@ def parseArgv(argv): action='store_true', default=False) loggroup.add_argument("-q", "--quiet", help="suppress non-error messages", action='store_true', default=False) - parser.add_argument("action", - help="What to do: (re)start, status or stop", - choices=['start', 'status', 'stop', 'restart'], - default="status") parser.add_argument("name", + type=str, help="Name of the instance.\n" - " Uses etc/name.cfg for configuration\n" - "may be omitted to mean ALL (which are configured)", - nargs='?', default='') + " Uses etc/name.cfg for configuration\n",) parser.add_argument('-d', '--daemonize', action='store_true', @@ -73,45 +68,16 @@ def main(argv=None): loglevel = 'debug' if args.verbose else ('error' if args.quiet else 'info') loggers.initLogging('secop', loglevel, path.join(log_path)) - logger = loggers.log - srvNames = [] + srv = Server(args.name, basepath) - if not args.name: - print('No name given, iterating over all specified servers') - for dirpath, dirs, files in os.walk(etc_path): - for fn in files: - if fn.endswith('.cfg'): - srvNames.append(fn[:-4]) - else: - logger.debug('configfile with strange extension found: %r' - % path.basename(fn)) - # ignore subdirs! - while (dirs): - dirs.pop() + if args.daemonize: + srv.start() else: - srvNames = [args.name] - - srvs = [] - for entry in srvNames: - srv = Server(entry, basepath) - srvs.append(srv) - - if args.action == "restart": - srv.stop() - srv.start() - elif args.action == "start": - if len(srvNames) > 1 or args.daemonize: - srv.start() - else: - srv.run() - elif args.action == "stop": - srv.stop() - elif args.action == "status": - if srv.isAlive(): - logger.info("Server %s is running." % entry) - else: - logger.info("Server %s is DEAD!" % entry) + try: + srv.run() + except KeyboardInterrupt: + pass if __name__ == '__main__': diff --git a/etc/secop-server b/etc/secop-server new file mode 100755 index 0000000..b0ce626 --- /dev/null +++ b/etc/secop-server @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +### BEGIN INIT INFO +# Provides: secop-server +# Required-Start: $local_fs $remote_fs $network $named $time +# Required-Stop: $local_fs $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Description: Secop servers +### END INIT INFO +# ***************************************************************************** +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free Software +# Foundation; either version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Module authors: +# Alexander Lenz +# +# ***************************************************************************** + +import sys +import argparse +import os.path +import glob +import signal + + +CFG_DIR='/etc/secop' +PID_DIR = '/var/run' +SRV_EXEC = 'secop-server' + +inplace_basedir = os.path.join(os.path.dirname(__file__), '..') +if os.path.isfile(os.path.join(inplace_basedir, 'src', '__init__.py')): + CFG_DIR = os.path.join(inplace_basedir, 'etc') + PID_DIR = os.path.join(inplace_basedir, 'pid') + SRV_EXEC = os.path.join(inplace_basedir, 'bin/secop-server') + + +def parseArgv(argv): + parser = argparse.ArgumentParser(description='Manage a SECoP server') + parser.add_argument('action', + type=str, + choices=['start', 'stop', 'restart', 'status'], + help='What to do with the desired server', ) + parser.add_argument('name', + nargs='*', + help='Name of the instance.\n' + ' Uses etc/name.cfg for configuration\n',) + return parser.parse_args() + + +def getServerNames(): + return sorted([os.path.basename(entry)[:-4] + for entry in glob.glob(os.path.join(CFG_DIR, '*.cfg'))]) + + +def getSrvPid(name): + pidfile = os.path.join(PID_DIR, '%s.pid' % name) + if not os.path.isfile(pidfile): + return None + with open(pidfile) as f: + return int(f.read()) + + +def startServer(name): + pid = getSrvPid(name) + + if pid: + print('%s: already running (pid: %s)' % (name, pid)) + else: + print('%s: starting ...' % name) + os.system('%s -d %s' % (SRV_EXEC, name)) + + +def stopServer(name): + pid = getSrvPid(name) + if pid: + print('%s: stopping ...' % name) + os.kill(pid, signal.SIGTERM) + else: + print('%s: already stopped' % name) + + +def determineServerStatus(name): + pid = getSrvPid(name) + + if pid: + print('%s: running (pid: %s)' % (name, pid)) + else: + print('%s: dead' % name) + +def main(argv=None): + if argv is None: + argv = sys.argv + + args = parseArgv(argv[1:]) + + actionMap = { + 'start' : startServer, + 'stop': stopServer, + 'status': determineServerStatus, + } + + srvs = args.name + if not srvs: + srvs = getServerNames() + + for entry in srvs: + actionMap[args.action](entry) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/requirements.txt b/requirements.txt index ad46ddb..8ab1206 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,8 +2,7 @@ markdown>=2.6 # general stuff psutil -# daemonizing not yet functional (logging problems) -#daemonize +python-daemon # for zmq #pyzmq>=13.1.0 diff --git a/src/loggers/__init__.py b/src/loggers/__init__.py index 2daac0e..e0f1d7c 100644 --- a/src/loggers/__init__.py +++ b/src/loggers/__init__.py @@ -87,6 +87,13 @@ class SecopLogger(Logger): return child + def getLogfileStreams(self): + result = [] + for entry in self._collectHandlers(): + if isinstance(entry, LogfileHandler): + result.append(entry.stream) + return result + def _collectHandlers(self): result = [] diff --git a/src/server.py b/src/server.py index 661d8d6..10bc610 100644 --- a/src/server.py +++ b/src/server.py @@ -26,9 +26,8 @@ import os import psutil import ConfigParser -# apt install python-daemon !!!do not use pip install daemon <- wrong version! -import daemon -#from daemon import pidlockfile +from daemon import DaemonContext +from daemon.pidfile import TimeoutPIDLockFile import loggers from lib import read_pidfile, write_pidfile, get_class, check_pidfile @@ -54,39 +53,20 @@ class Server(object): self._interface = None def start(self): - pidfile = pidlockfile.TimeoutPIDLockFile(self._pidfile, 3) + piddir = os.path.dirname(self._pidfile) + if not os.path.isdir(piddir): + os.makedirs(piddir) + pidfile = TimeoutPIDLockFile(self._pidfile) - with daemon.DaemonContext( - # files_preserve=[logFileHandler.stream], - pidfile=pidfile, - ): - try: - # write_pidfile(pidfilename, os.getpid()) - self.run() - except Exception as e: - self.log.exception(e) + if pidfile.is_locked(): + self.log.error('Pidfile already exists. Exiting') - def stop(self): - pid = read_pidfile(self._pidfile) - if pid is None: - # already dead/not started yet - return - # get process for this pid - for proc in psutil.process_iter(): - if proc.pid == pid: - break - proc.terminate() - proc.wait(3) - proc.kill() - - def isAlive(self): - if check_pidfile(self._pidfile): - return True - return False + with DaemonContext(working_directory=self._workdir, + pidfile=pidfile, + files_preserve=self.log.getLogfileStreams()): + self.run() def run(self): - # write pidfile before doing actual things - write_pidfile(self._pidfile, os.getpid()) self._processCfg() self.log.info('startup done, handling transport messages')