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