From 85a341d645b57b7cd88a26ed2ea0a314704240ea Mon Sep 17 00:00:00 2001 From: Jordan Augé Date: Fri, 24 Feb 2017 14:58:01 +0100 Subject: Initial commit: vICN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I7ce66c4e84a6a1921c63442f858b49e083adc7a7 Signed-off-by: Jordan Augé --- netmodel/util/daemon.py | 245 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 netmodel/util/daemon.py (limited to 'netmodel/util/daemon.py') diff --git a/netmodel/util/daemon.py b/netmodel/util/daemon.py new file mode 100644 index 00000000..29683a54 --- /dev/null +++ b/netmodel/util/daemon.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017 Cisco and/or its affiliates. +# +# 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. +# + +# see also: http://www.jejik.com/files/examples/daemon3x.py + +# This is used to import the daemon package instead of the local module which is +# named identically... +from __future__ import absolute_import + +import os +import sys +import time +import logging +import atexit +import signal +import lockfile +import traceback + +log = logging.getLogger(__name__) + +class Daemon: + + #-------------------------------------------------------------------------- + # Checks + #-------------------------------------------------------------------------- + + def check_python_daemon(self): + """ + Check whether python-daemon is properly installed. + Returns: + True iiff everything is fine. + """ + # http://www.python.org/dev/peps/pep-3143/ + ret = False + try: + import daemon + getattr(daemon, "DaemonContext") + ret = True + except AttributeError as e: + # daemon and python-daemon conflict with each other + log.critical("Please install python-daemon instead of daemon." \ + "Remove daemon first.") + except ImportError: + log.critical("Please install python-daemon.") + return ret + + #-------------------------------------------------------------------------- + # Initialization + #-------------------------------------------------------------------------- + + def __init__(self, name, + uid = os.getuid(), + gid = os.getgid(), + working_directory = '/', + debug_mode = False, + no_daemon = False, + pid_filename = None + ): + self._name = name + self._uid = uid + self._gid = gid + self._working_directory = working_directory + self._debug_mode = debug_mode + self._no_daemon = no_daemon + self._pid_filename = pid_filename if pid_filename \ + else '/var/run/{}.pid'.format(name) + + # Reference which file descriptors must remain opened while + # daemonizing (for instance the file descriptor related to + # the logger, a socket file created before daemonization etc.) + self._files_to_keep = list() + self._lock_file = None + + self.initialize() + + #------------------------------------------------------------------------ + + + def remove_pid_file(self): + """ + (Internal usage) + Remove the PID file + """ + if os.path.exists(self._pid_filename) == True: + log.info("Removing %s" % self._pid_filename) + os.remove(self._pid_filename) + + if self._lock_file and self._lock_file.is_locked(): + self._lock_file.release() + + def make_pid_file(self): + """ + Create a PID file if required in which we store the PID of the daemon + if needed + """ + if self._pid_filename and not self._no_daemon: + atexit.register(self.remove_pid_file) + open(self._pid_filename, "w+").write("%s\n" % str(os.getpid())) + + def get_pid_from_pid_file(self): + """ + Retrieve the PID of the daemon thanks to the pid file. + Returns: + An integer containing the PID of this daemon. + None if the pid file is not readable or does not exists + """ + pid = None + if self._pid_filename: + try: + f_pid = file(self._pid_filename, "r") + pid = int(f_pid.read().strip()) + f_pid.close() + except IOError: + pid = None + return pid + + def make_lock_file(self): + """ + Prepare the lock file required to manage the pid file. + Initialize self.lock_file + Returns: + True iif successful. + """ + if self._pid_filename and not self._no_daemon: + log.debug("Daemonizing using pid file '%s'" % self._pid_filename) + self.lock_file = lockfile.FileLock(self._pid_filename) + if self.lock_file.is_locked() == True: + log.error("'%s' is already running ('%s' is locked)." % \ + (self._name, self._pid_filename)) + return False + self.lock_file.acquire() + else: + self.lock_file = None + return True + + def start(self): + """ + Start the daemon. + """ + # Check whether daemon module is properly installed + if self.check_python_daemon() == False: + self._terminate() + import daemon + + # Prepare self.lock_file + if not self.make_lock_file(): + sys.exit(1) + + # We might need to preserve a few files from logging handlers + files_to_keep = list() + #for handler in log.handlers: + # preserve_files + + if self._no_daemon: + self.main() + return + + # Prepare the daemon context + dcontext = daemon.DaemonContext( + detach_process = not self._no_daemon, + working_directory = self._working_directory, + pidfile = self.lock_file, + stdin = sys.stdin, + stdout = sys.stdout, + stderr = sys.stderr, + uid = self._uid, + gid = self._gid, + files_preserve = files_to_keep + ) + + # Prepare signal handling to stop properly if the daemon is killed + # Note that signal.SIGKILL can't be handled: + # http://crunchtools.com/unixlinux-signals-101/ + dcontext.signal_map = { + signal.SIGTERM : self.signal_handler, + signal.SIGQUIT : self.signal_handler, + signal.SIGINT : self.signal_handler + } + + with dcontext: + log.info("Entering daemonization") + self.make_pid_file() + + try: + self.main() + except Exception as e: + log.error("Unhandled exception in start: %s" % e) + log.error(traceback.format_exc()) + finally: + self._terminate() + + def signal_handler(self, signal_id, frame): + """ + (Internal use) + Stop the daemon (signal handler) + Args: + signal_id: The integer identifying the signal + (see also "man 7 signal") + Example: 15 if the received signal is signal.SIGTERM + frame: + """ + self._terminate() + + def _terminate(self): + """ + Stops gracefully the daemon. + Note: + The lockfile should implicitly released by the daemon package. + """ + log.info("Stopping %s" % self.__class__.__name__) + self.terminate() + self.remove_pid_file() + self.leave() + + def leave(self): + """ + Overload this method if you use twisted (see xmlrpc.py) + """ + sys.exit(0) + + # Overload these... + + def initialize(self): + pass + + def main(self): + raise NotImplementedError + + def terminate(self): + pass -- cgit 1.2.3-korg