FIX: show cred: shows only creds from myslice platform
[unfold.git] / manifold / util / daemon.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Daemon: superclass used to implement a daemon easily
5 #
6 # Copyright (C)2009-2012, UPMC Paris Universitas
7 # Authors:
8 #   Marc-Olivier Buob <marc-olivier.buob@lip6.fr>
9
10 # see also: http://www.jejik.com/files/examples/daemon3x.py
11
12 # This is used to import the daemon package instead of the local module which is
13 # named identically...
14 from __future__ import absolute_import
15
16 from manifold.util.singleton    import Singleton
17 from manifold.util.log          import Log
18 from manifold.util.options      import Options
19
20 import atexit, os, signal, lockfile, logging, sys
21
22 class Daemon(object):
23     __metaclass__ = Singleton
24
25     DEFAULTS = {
26         # Running
27         "uid"                 : os.getuid(),
28         "gid"                 : os.getgid(),
29         "working_directory"   : "/",
30         "debugmode"           : False,
31         "no_daemon"           : False,
32         "pid_filename"        : "/var/run/%s.pid" % Options().get_name()
33     }
34     
35     #-------------------------------------------------------------------------
36     # Checks 
37     #-------------------------------------------------------------------------
38
39     def check_python_daemon(self):
40         """
41         \brief Check whether python-daemon is properly installed
42         \return True if everything is file, False otherwise
43         """
44         # http://www.python.org/dev/peps/pep-3143/    
45         ret = False 
46         try:
47             import daemon
48             getattr(daemon, "DaemonContext")
49             ret = True 
50         except AttributeError, e:
51             print e
52             # daemon and python-daemon conflict with each other
53             Log.critical("Please install python-daemon instead of daemon. Remove daemon first.")
54         except ImportError:
55             Log.critical("Please install python-daemon - easy_install python-daemon.")
56         return ret
57
58     #------------------------------------------------------------------------
59     # Initialization 
60     #------------------------------------------------------------------------
61
62     def make_handler_rsyslog(self, rsyslog_host, rsyslog_port, log_level):
63         """
64         \brief (Internal usage) Prepare logging via rsyslog
65         \param rsyslog_host The hostname of the rsyslog server
66         \param rsyslog_port The port of the rsyslog server
67         \param log_level Log level
68         """
69         # Prepare the handler
70         shandler = handlers.SysLogHandler(
71             (rsyslog_host, rsyslog_port),
72             facility = handlers.SysLogHandler.LOG_DAEMON
73         )
74
75         # The log file must remain open while daemonizing 
76         self.files_to_keep.append(shandler.socket)
77         self.prepare_handler(shandler, log_level)
78         return shandler
79
80     def make_handler_locallog(self, log_filename, log_level):
81         """
82         \brief (Internal usage) Prepare local logging
83         \param log_filename The file in which we write the logs
84         \param log_level Log level
85         """
86         # Create directory in which we store the log file
87         log_dir = os.path.dirname(log_filename)
88         if not os.path.exists(log_dir):
89             try:
90                 os.makedirs(log_dir)
91             except OSError, why: 
92                 log_error("OS error: %s" % why)
93
94         # Prepare the handler
95         shandler = logging.handlers.RotatingFileHandler(
96             log_filename,
97             backupCount = 0
98         )
99
100         # The log file must remain open while daemonizing 
101         self.files_to_keep.append(shandler.stream)
102         self.prepare_handler(shandler, log_level)
103         return shandler
104
105     def prepare_handler(self, shandler, log_level):
106         """
107         \brief (Internal usage)
108         \param shandler Handler used to log information
109         \param log_level Log level
110         """
111         shandler.setLevel(log_level)
112         formatter = logging.Formatter("%(asctime)s: %(name)s: %(levelname)s %(message)s")
113         shandler.setFormatter(formatter)
114         self.log.addHandler(shandler)
115         self.log.setLevel(getattr(logging, log_level, logging.INFO))
116
117     def __init__(
118         self,
119         #daemon_name,
120         terminate_callback = None
121         #uid               = os.getuid(),
122         #gid               = os.getgid(),
123         #working_directory = "/",
124         #pid_filename      = None,
125         #no_daemon         = False,
126         #debug             = False,
127         #log               = None,        # logging.getLogger("plop")
128         #rsyslog_host      = "localhost", # Pass None if no rsyslog server
129         #rsyslog_port      = 514,
130         #log_file          = None,
131         #log_level         = logging.INFO
132    ):
133         """
134         \brief Constructor
135         \param daemon_name The name of the daemon
136         \param uid UID used to run the daemon
137         \param gid GID used to run the daemon
138         \param working_directory Working directory used to run the daemon.
139             Example: /var/lib/foo/
140         \param pid_filename Absolute path of the PID file
141             Example: /var/run/foo.pid
142             (ignored if no_daemon == True)
143         \param no_daemon Do not detach the daemon from the terminal
144         \param debug Run daemon in debug mode
145         \param log The logger, pass None if unused
146             Example: logging.getLogger('foo'))
147         \param rsyslog_host Rsyslog hostname, pass None if unused.
148             If rsyslog_host is set to None, log are stored locally
149         \param rsyslog_port Rsyslog port
150         \param log_file Absolute path of the local log file.
151             Example: /var/log/foo.log)
152         \param log_level Log level
153             Example: logging.INFO
154         """
155
156         # Daemon parameters
157         #self.daemon_name        = daemon_name
158         self.terminate_callback = terminate_callback
159         #Options().uid               = uid
160         #Options().gid               = gid
161         #Options().working_directory = working_directory
162         #self.pid_filename      = None if no_daemon else pid_filename
163         #Options().no_daemon         = no_daemon
164         #Options().lock_file         = None
165         #Options().debug             = debug
166         #self.log               = log 
167         #self.rsyslog_host      = rsyslog_host
168         #self.rsyslog_port      = rsyslog_port 
169         #self.log_file          = log_file 
170         #self.log_level         = log_level
171
172         # Reference which file descriptors must remain opened while
173         # daemonizing (for instance the file descriptor related to
174         # the logger)
175         self.files_to_keep = []
176
177         # Initialize self.log (require self.files_to_keep)
178         #if self.log: # for debugging by using stdout, log may be equal to None
179         #    if rsyslog_host:
180         #        shandler = self.make_handler_rsyslog(
181         #            rsyslog_host,
182         #            rsyslog_port,
183         #            log_level
184         #        )
185         #    elif log_file:
186         #        shandler = self.make_handler_locallog(
187         #            log_file,
188         #            log_level
189         #        )
190
191     @classmethod
192     def init_options(self):
193         opt = Options()
194
195         opt.add_option(
196             "--uid", dest = "uid",
197             help = "UID used to run the dispatcher.",
198             default = self.DEFAULTS['uid']
199         )
200         opt.add_option(
201             "--gid", dest = "gid",
202             help = "GID used to run the dispatcher.",
203             default = self.DEFAULTS['gid']
204         )
205         opt.add_option(
206             "-w", "--working-directory", dest = "working_directory",
207             help = "Working directory.",
208             default = self.DEFAULTS['working_directory']
209         )
210         opt.add_option(
211             "-D", "--debugmode", action = "store_false", dest = "debugmode",
212             help = "Daemon debug mode (useful for developers).",
213             default = self.DEFAULTS['debugmode']
214         )
215         opt.add_option(
216             "-n", "--no-daemon", action = "store_true", dest = "no_daemon",
217             help = "Run as daemon (detach from terminal).",
218             default = self.DEFAULTS["no_daemon"]
219         )
220         opt.add_option(
221             "-i", "--pid-file", dest = "pid_filename",
222             help = "Absolute path to the pid-file to use when running as daemon.",
223             default = self.DEFAULTS['pid_filename']
224         )
225
226         
227
228     #------------------------------------------------------------------------
229     # Daemon stuff 
230     #------------------------------------------------------------------------
231
232     def remove_pid_file(self):
233         """
234         \brief Remove the pid file (internal usage)
235         """
236         # The lock file is implicitely released while removing the pid file
237         Log.debug("Removing %s" % Options().pid_filename)
238         if os.path.exists(Options().pid_filename) == True:
239             os.remove(Options().pid_filename)
240
241     def make_pid_file(self):
242         """
243         \brief Create a pid file in which we store the PID of the daemon if needed 
244         """
245         if Options().pid_filename and Options().no_daemon == False:
246             atexit.register(self.remove_pid_file)
247             file(Options().pid_filename, "w+").write("%s\n" % str(os.getpid()))
248
249     def get_pid_from_pid_file(self):
250         """
251         \brief Retrieve the PID of the daemon thanks to the pid file.
252         \return None if the pid file is not readable or does not exists
253         """
254         pid = None
255         if Options().pid_filename:
256             try:
257                 f_pid = file(Options().pid_filename, "r")
258                 pid = int(f_pid.read().strip())
259                 f_pid.close()
260             except IOError:
261                 pid = None
262         return pid
263
264     def make_lock_file(self):
265         """
266         \brief Prepare the lock file required to manage the pid file
267             Initialize Options().lock_file
268         """
269         if Options().pid_filename and Options().no_daemon == False:
270             Log.debug("Daemonizing using pid file '%s'" % Options().pid_filename)
271             Options().lock_file = lockfile.FileLock(Options().pid_filename)
272             if Options().lock_file.is_locked() == True:
273                 log_error("'%s' is already running ('%s' is locked)." % (Options().get_name(), Options().pid_filename))
274                 self.terminate()
275             Options().lock_file.acquire()
276         else:
277             Options().lock_file = None
278
279     def start(self):
280         """
281         \brief Start the daemon
282         """
283         # Check whether daemon module is properly installed
284         if self.check_python_daemon() == False:
285             self.terminate()
286         import daemon
287
288         # Prepare Options().lock_file
289         self.make_lock_file()
290
291         # Prepare the daemon context
292         dcontext = daemon.DaemonContext(
293             detach_process     = (not Options().no_daemon),
294             working_directory  = Options().working_directory,
295             pidfile            = Options().lock_file if not Options().no_daemon else None,
296             stdin              = sys.stdin,
297             stdout             = sys.stdout,
298             stderr             = sys.stderr,
299             uid                = Options().uid,
300             gid                = Options().gid,
301             files_preserve     = Log().files_to_keep
302         )
303
304         # Prepare signal handling to stop properly if the daemon is killed 
305         # Note that signal.SIGKILL can't be handled:
306         # http://crunchtools.com/unixlinux-signals-101/
307         dcontext.signal_map = {
308             signal.SIGTERM : self.signal_handler,
309             signal.SIGQUIT : self.signal_handler,
310             signal.SIGINT  : self.signal_handler
311         }
312
313         if Options().debugmode == True:
314             self.main()
315         else:
316             with dcontext:
317                 self.make_pid_file()
318                 try:
319                     self.main()
320                 except Exception, why:
321                     Log.error("Unhandled exception in start: %s" % why)
322
323     def signal_handler(self, signal_id, frame):
324         """
325         \brief Stop the daemon (signal handler)
326             The lockfile is implicitly released by the daemon package
327         \param signal_id The integer identifying the signal
328             (see also "man 7 signal")
329             Example: 15 if the received signal is signal.SIGTERM
330         \param frame
331         """
332         self.terminate()
333
334     def stop(self):
335         Log.debug("Stopping '%s'" % self.daemon_name)
336
337     def terminate(self):
338         if self.terminate_callback:
339             self.terminate_callback()
340         else:
341             sys.exit(0)
342
343 Daemon.init_options()