xenserver: monitor-external-ids should run with --monitor
[sliver-openvswitch.git] / python / ovs / daemon.py
1 # Copyright (c) 2010 Nicira Networks
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import errno
16 import fcntl
17 import logging
18 import os
19 import resource
20 import signal
21 import sys
22 import time
23
24 import ovs.dirs
25 import ovs.fatal_signal
26 #import ovs.lockfile
27 import ovs.process
28 import ovs.socket_util
29 import ovs.timeval
30 import ovs.util
31
32 # --detach: Should we run in the background?
33 _detach = False
34
35 # --pidfile: Name of pidfile (null if none).
36 _pidfile = None
37
38 # --overwrite-pidfile: Create pidfile even if one already exists and is locked?
39 _overwrite_pidfile = False
40
41 # --no-chdir: Should we chdir to "/"?
42 _chdir = True
43
44 # --monitor: Should a supervisory process monitor the daemon and restart it if
45 # it dies due to an error signal?
46 _monitor = False
47
48 # File descriptor used by daemonize_start() and daemonize_complete().
49 _daemonize_fd = None
50
51 RESTART_EXIT_CODE = 5
52
53 def make_pidfile_name(name):
54     """Returns the file name that would be used for a pidfile if 'name' were
55     provided to set_pidfile()."""
56     if name is None or name == "":
57         return "%s/%s.pid" % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME)
58     else:
59         return ovs.util.abs_file_name(ovs.dirs.RUNDIR, name)
60
61 def set_pidfile(name):
62     """Sets up a following call to daemonize() to create a pidfile named
63     'name'.  If 'name' begins with '/', then it is treated as an absolute path.
64     Otherwise, it is taken relative to ovs.util.RUNDIR, which is
65     $(prefix)/var/run by default.
66     
67     If 'name' is null, then ovs.util.PROGRAM_NAME followed by ".pid" is
68     used."""
69     global _pidfile
70     _pidfile = make_pidfile_name(name)
71
72 def get_pidfile():
73     """Returns an absolute path to the configured pidfile, or None if no
74     pidfile is configured.  The caller must not modify or free the returned
75     string."""
76     return _pidfile
77
78 def set_no_chdir():
79     """Sets that we do not chdir to "/"."""
80     global _chdir
81     _chdir = False
82
83 def is_chdir_enabled():
84     """Will we chdir to "/" as part of daemonizing?"""
85     return _chdir
86
87 def ignore_existing_pidfile():
88     """Normally, die_if_already_running() will terminate the program with a
89     message if a locked pidfile already exists.  If this function is called,
90     die_if_already_running() will merely log a warning."""
91     global _overwrite_pidfile
92     _overwrite_pidfile = True
93
94 def set_detach():
95     """Sets up a following call to daemonize() to detach from the foreground
96     session, running this process in the background."""
97     global _detach
98     _detach = True
99
100 def get_detach():
101     """Will daemonize() really detach?"""
102     return _detach
103
104 def set_monitor():
105     """Sets up a following call to daemonize() to fork a supervisory process to
106     monitor the daemon and restart it if it dies due to an error signal."""
107     global _monitor
108     _monitor = True
109
110 def _already_running():
111     """If a pidfile has been configured and that pidfile already exists and is
112     locked by a running process, returns True.  Otherwise, returns False."""
113     if _pidfile is not None:
114         try:
115             file = open(_pidfile, "r+")
116             try:
117                 try:
118                     fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
119                 except IOError, e:
120                     if e.errno in [errno.EACCES, errno.EAGAIN]:
121                         return True
122                     logging.error("error locking %s (%s)"
123                                   % (_pidfile, os.strerror(e.errno)))
124                     return False
125             finally:
126                 # This releases the lock, which we don't really want.
127                 file.close()
128         except IOError, e:
129             if e.errno == errno.ENOENT:
130                 return False
131             logging.error("error opening %s (%s)"
132                           % (_pidfile, os.strerror(e.errno)))
133     return False
134
135 def die_if_already_running():
136     """If a locked pidfile exists, issue a warning message and, unless
137     ignore_existing_pidfile() has been called, terminate the program."""
138     if _already_running():
139         if not _overwrite_pidfile:
140             sys.stderr.write("%s: already running\n" % get_pidfile())
141             sys.exit(1)
142         else:
143             logging.warn("%s: %s already running"
144                          % (get_pidfile(), ovs.util.PROGRAM_NAME))
145
146 def _make_pidfile():
147     """If a pidfile has been configured, creates it and stores the running
148     process's pid in it.  Ensures that the pidfile will be deleted when the
149     process exits."""
150     if _pidfile is not None:
151         # Create pidfile via temporary file, so that observers never see an
152         # empty pidfile or an unlocked pidfile.
153         pid = os.getpid()
154         tmpfile = "%s.tmp%d" % (_pidfile, pid)
155         ovs.fatal_signal.add_file_to_unlink(tmpfile)
156
157         try:
158             # This is global to keep Python from garbage-collecting and
159             # therefore closing our file after this function exits.  That would
160             # unlock the lock for us, and we don't want that.
161             global file
162
163             file = open(tmpfile, "w")
164         except IOError, e:
165             logging.error("%s: create failed: %s"
166                           % (tmpfile, os.strerror(e.errno)))
167             return
168             
169         try:
170             fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
171         except IOError, e:
172             logging.error("%s: fcntl failed: %s"
173                           % (tmpfile, os.strerror(e.errno)))
174             file.close()
175             return
176
177         try:
178             file.write("%s\n" % pid)
179             file.flush()
180             ovs.fatal_signal.add_file_to_unlink(_pidfile)
181         except OSError, e:
182             logging.error("%s: write failed: %s"
183                           % (tmpfile, os.strerror(e.errno)))
184             file.close()
185             return
186             
187         try:
188             os.rename(tmpfile, _pidfile)
189         except OSError, e:
190             ovs.fatal_signal.remove_file_to_unlink(_pidfile)
191             logging.error("failed to rename \"%s\" to \"%s\": %s"
192                           % (tmpfile, _pidfile, os.strerror(e.errno)))
193             file.close()
194             return
195
196 def daemonize():
197     """If configured with set_pidfile() or set_detach(), creates the pid file
198     and detaches from the foreground session."""
199     daemonize_start()
200     daemonize_complete()
201
202 def _waitpid(pid, options):
203     while True:
204         try:
205             return os.waitpid(pid, options)
206         except OSError, e:
207             if e.errno == errno.EINTR:
208                 pass
209             return -e.errno, 0
210
211 def _fork_and_wait_for_startup():
212     try:
213         rfd, wfd = os.pipe()
214     except OSError, e:
215         sys.stderr.write("pipe failed: %s\n" % os.strerror(e.errno))
216         sys.exit(1)
217
218     try:
219         pid = os.fork()
220     except OSError, e:
221         sys.stderr.write("could not fork: %s\n" % os.strerror(e.errno))
222         sys.exit(1)
223
224     if pid > 0:
225         # Running in parent process.
226         os.close(wfd)
227         ovs.fatal_signal.fork()
228         try:
229             s = os.read(rfd, 1)
230         except OSError, e:
231             s = ""
232         if len(s) != 1:
233             retval, status = _waitpid(pid, 0)
234             if (retval == pid and
235                 os.WIFEXITED(status) and os.WEXITSTATUS(status)):
236                 # Child exited with an error.  Convey the same error to
237                 # our parent process as a courtesy.
238                 sys.exit(os.WEXITSTATUS(status))
239             else:
240                 sys.stderr.write("fork child failed to signal startup\n")
241                 sys.exit(1)
242
243         os.close(rfd)
244     else:
245         # Running in parent process.
246         os.close(rfd)
247         ovs.timeval.postfork()
248         #ovs.lockfile.postfork()
249
250         global _daemonize_fd
251         _daemonize_fd = wfd
252     return pid
253
254 def _fork_notify_startup(fd):
255     if fd is not None:
256         error, bytes_written = ovs.socket_util.write_fully(fd, "0")
257         if error:
258             sys.stderr.write("could not write to pipe\n")
259             sys.exit(1)
260         os.close(fd)
261
262 def _should_restart(status):
263     global RESTART_EXIT_CODE
264
265     if os.WIFEXITED(status) and os.WEXITSTATUS(status) == RESTART_EXIT_CODE:
266         return True
267
268     if os.WIFSIGNALED(status):
269         for signame in ("SIGABRT", "SIGALRM", "SIGBUS", "SIGFPE", "SIGILL",
270                         "SIGPIPE", "SIGSEGV", "SIGXCPU", "SIGXFSZ"):
271             if (signame in signal.__dict__ and
272                 os.WTERMSIG(status) == signal.__dict__[signame]):
273                 return True
274     return False
275
276 def _monitor_daemon(daemon_pid):
277     # XXX should log daemon's stderr output at startup time
278     # XXX should use setproctitle module if available
279     last_restart = None
280     while True:
281         retval, status = _waitpid(daemon_pid, 0)
282         if retval < 0:
283             sys.stderr.write("waitpid failed\n")
284             sys.exit(1)
285         elif retval == daemon_pid:
286             status_msg = ("pid %d died, %s"
287                           % (daemon_pid, ovs.process.status_msg(status)))
288             
289             if _should_restart(status):
290                 if os.WCOREDUMP(status):
291                     # Disable further core dumps to save disk space.
292                     try:
293                         resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
294                     except resource.error:
295                         logging.warning("failed to disable core dumps")
296
297                 # Throttle restarts to no more than once every 10 seconds.
298                 if (last_restart is not None and
299                     ovs.timeval.msec() < last_restart + 10000):
300                     logging.warning("%s, waiting until 10 seconds since last "
301                                     "restart" % status_msg)
302                     while True:
303                         now = ovs.timeval.msec()
304                         wakeup = last_restart + 10000
305                         if now > wakeup:
306                             break
307                         print "sleep %f" % ((wakeup - now) / 1000.0)
308                         time.sleep((wakeup - now) / 1000.0)
309                 last_restart = ovs.timeval.msec()
310
311                 logging.error("%s, restarting" % status_msg)
312                 daemon_pid = _fork_and_wait_for_startup()
313                 if not daemon_pid:
314                     break
315             else:
316                 logging.info("%s, exiting" % status_msg)
317                 sys.exit(0)
318
319    # Running in new daemon process.
320
321 def _close_standard_fds():
322     """Close stdin, stdout, stderr.  If we're started from e.g. an SSH session,
323     then this keeps us from holding that session open artificially."""
324     null_fd = ovs.socket_util.get_null_fd()
325     if null_fd >= 0:
326         os.dup2(null_fd, 0)
327         os.dup2(null_fd, 1)
328         os.dup2(null_fd, 2)
329
330 def daemonize_start():
331     """If daemonization is configured, then starts daemonization, by forking
332     and returning in the child process.  The parent process hangs around until
333     the child lets it know either that it completed startup successfully (by
334     calling daemon_complete()) or that it failed to start up (by exiting with a
335     nonzero exit code)."""
336     
337     if _detach:
338         if _fork_and_wait_for_startup() > 0:
339             # Running in parent process.
340             sys.exit(0)
341         # Running in daemon or monitor process.
342
343     if _monitor:
344         saved_daemonize_fd = _daemonize_fd
345         daemon_pid = _fork_and_wait_for_startup()
346         if daemon_pid > 0:
347             # Running in monitor process.
348             _fork_notify_startup(saved_daemonize_fd)
349             _close_standard_fds()
350             _monitor_daemon(daemon_pid)
351         # Running in daemon process
352     
353     _make_pidfile()
354
355 def daemonize_complete():
356     """If daemonization is configured, then this function notifies the parent
357     process that the child process has completed startup successfully."""
358     _fork_notify_startup(_daemonize_fd)
359
360     if _detach:
361         os.setsid()
362         if _chdir:
363             os.chdir("/")
364         _close_standard_fds()
365
366 def usage():
367     sys.stdout.write("""
368 Daemon options:
369    --detach                run in background as daemon
370    --no-chdir              do not chdir to '/'
371    --pidfile[=FILE]        create pidfile (default: %s/%s.pid)
372    --overwrite-pidfile     with --pidfile, start even if already running
373 """ % (ovs.dirs.RUNDIR, ovs.util.PROGRAM_NAME))
374
375 def read_pidfile(pidfile):
376     """Opens and reads a PID from 'pidfile'.  Returns the nonnegative PID if
377     successful, otherwise a negative errno value."""
378     try:
379         file = open(pidfile, "r")
380     except IOError, e:
381         logging.warning("%s: open: %s" % (pidfile, os.strerror(e.errno)))
382         return -e.errno
383
384     # Python fcntl doesn't directly support F_GETLK so we have to just try
385     # to lock it.  If we get a conflicting lock that's "success"; otherwise
386     # the file is not locked.
387     try:
388         fcntl.lockf(file, fcntl.LOCK_EX | fcntl.LOCK_NB)
389         # File isn't locked if we get here, so treat that as an error.
390         logging.warning("%s: pid file is not locked" % pidfile)
391         try:
392             # As a side effect, this drops the lock.
393             file.close()
394         except IOError:
395             pass
396         return -errno.ESRCH
397     except IOError, e:
398         if e.errno not in [errno.EACCES, errno.EAGAIN]:
399             logging.warn("%s: fcntl: %s" % (pidfile, os.strerror(e.errno)))
400             return -e.errno
401
402     try:
403         try:
404             return int(file.readline())
405         except IOError, e:
406             logging.warning("%s: read: %s" % (pidfile, e.strerror))
407             return -e.errno
408         except ValueError:
409             logging.warning("%s does not contain a pid" % pidfile)
410             return -errno.EINVAL
411     finally:
412         try:
413             file.close()
414         except IOError:
415             pass
416
417 # XXX Python's getopt does not support options with optional arguments, so we
418 # have to separate --pidfile (with no argument) from --pidfile-name (with an
419 # argument).  Need to write our own getopt I guess.
420 LONG_OPTIONS = ["detach", "no-chdir", "pidfile", "pidfile-name=",
421                 "overwrite-pidfile", "monitor"]
422
423 def parse_opt(option, arg):
424     if option == '--detach':
425         set_detach()
426     elif option == '--no-chdir':
427         set_no_chdir()
428     elif option == '--pidfile':
429         set_pidfile(None)
430     elif option == '--pidfile-name':
431         set_pidfile(arg)
432     elif option == '--overwrite-pidfile':
433         ignore_existing_pidfile()
434     elif option == '--monitor':
435         set_monitor()
436     else:
437         return False
438     return True