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