4 # Copyright (C) 2006, 2007, 2008 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Module with helper classes and functions for daemons"""
34 from ganeti import utils
35 from ganeti import constants
36 from ganeti import errors
39 class SchedulerBreakout(Exception):
40 """Exception used to get out of the scheduler loop
45 def AsyncoreDelayFunction(timeout):
46 """Asyncore-compatible scheduler delay function.
48 This is a delay function for sched that, rather than actually sleeping,
49 executes asyncore events happening in the meantime.
51 After an event has occurred, rather than returning, it raises a
52 SchedulerBreakout exception, which will force the current scheduler.run()
53 invocation to terminate, so that we can also check for signals. The main loop
54 will then call the scheduler run again, which will allow it to actually
55 process any due events.
57 This is needed because scheduler.run() doesn't support a count=..., as
58 asyncore loop, and the scheduler module documents throwing exceptions from
59 inside the delay function as an allowed usage model.
62 asyncore.loop(timeout=timeout, count=1, use_poll=True)
63 raise SchedulerBreakout()
66 class AsyncoreScheduler(sched.scheduler):
67 """Event scheduler integrated with asyncore
70 def __init__(self, timefunc):
71 sched.scheduler.__init__(self, timefunc, AsyncoreDelayFunction)
74 class Mainloop(object):
75 """Generic mainloop for daemons
79 """Constructs a new Mainloop instance.
81 @ivar scheduler: A L{sched.scheduler} object, which can be used to register
85 self._signal_wait = []
86 self.scheduler = AsyncoreScheduler(time.time)
88 @utils.SignalHandled([signal.SIGCHLD])
89 @utils.SignalHandled([signal.SIGTERM])
90 def Run(self, stop_on_empty=False, signal_handlers=None):
93 @type stop_on_empty: bool
94 @param stop_on_empty: Whether to stop mainloop once all I/O waiters
96 @type signal_handlers: dict
97 @param signal_handlers: signal->L{utils.SignalHandler} passed by decorator
100 assert isinstance(signal_handlers, dict) and \
101 len(signal_handlers) > 0, \
102 "Broken SignalHandled decorator"
104 # Start actual main loop
106 # Stop if nothing is listening anymore
107 if stop_on_empty and not (self._io_wait):
110 if not self.scheduler.empty():
113 except SchedulerBreakout:
116 asyncore.loop(count=1, use_poll=True)
118 # Check whether a signal was raised
119 for sig in signal_handlers:
120 handler = signal_handlers[sig]
122 self._CallSignalWaiters(sig)
123 running = (sig != signal.SIGTERM)
126 def _CallSignalWaiters(self, signum):
127 """Calls all signal waiters for a certain signal.
130 @param signum: Signal number
133 for owner in self._signal_wait:
134 owner.OnSignal(signum)
136 def RegisterSignal(self, owner):
137 """Registers a receiver for signal notifications
139 The receiver must support a "OnSignal(self, signum)" function.
141 @type owner: instance
142 @param owner: Receiver
145 self._signal_wait.append(owner)
148 def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn):
149 """Shared main function for daemons.
151 @type daemon_name: string
152 @param daemon_name: daemon name
153 @type optionparser: L{optparse.OptionParser}
154 @param optionparser: initialized optionparser with daemon-specific options
155 (common -f -d options will be handled by this module)
156 @type options: object @param options: OptionParser result, should contain at
157 least the fork and the debug options
158 @type dirs: list of strings
159 @param dirs: list of directories that must exist for this daemon to work
160 @type check_fn: function which accepts (options, args)
161 @param check_fn: function that checks start conditions and exits if they're
163 @type exec_fn: function which accepts (options, args)
164 @param exec_fn: function that's executed with the daemon's pid file held, and
165 runs the daemon itself.
168 optionparser.add_option("-f", "--foreground", dest="fork",
169 help="Don't detach from the current terminal",
170 default=True, action="store_false")
171 optionparser.add_option("-d", "--debug", dest="debug",
172 help="Enable some debug messages",
173 default=False, action="store_true")
174 if daemon_name in constants.DAEMONS_PORTS:
175 # for networked daemons we also allow choosing the bind port and address.
176 # by default we use the port provided by utils.GetDaemonPort, and bind to
177 # 0.0.0.0 (which is represented by and empty bind address.
178 port = utils.GetDaemonPort(daemon_name)
179 optionparser.add_option("-p", "--port", dest="port",
180 help="Network port (%s default)." % port,
181 default=port, type="int")
182 optionparser.add_option("-b", "--bind", dest="bind_address",
184 default="", metavar="ADDRESS")
186 if daemon_name in constants.DAEMONS_SSL:
187 default_cert, default_key = constants.DAEMONS_SSL[daemon_name]
188 optionparser.add_option("--no-ssl", dest="ssl",
189 help="Do not secure HTTP protocol with SSL",
190 default=True, action="store_false")
191 optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
193 default=default_key, type="string")
194 optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
195 help="SSL certificate",
196 default=default_cert, type="string")
198 multithread = utils.no_fork = daemon_name in constants.MULTITHREADED_DAEMONS
200 options, args = optionparser.parse_args()
202 if hasattr(options, 'ssl') and options.ssl:
203 if not (options.ssl_cert and options.ssl_key):
204 print >> sys.stderr, "Need key and certificate to use ssl"
205 sys.exit(constants.EXIT_FAILURE)
206 for fname in (options.ssl_cert, options.ssl_key):
207 if not os.path.isfile(fname):
208 print >> sys.stderr, "Need ssl file %s to run" % fname
209 sys.exit(constants.EXIT_FAILURE)
211 if check_fn is not None:
212 check_fn(options, args)
214 utils.EnsureDirs(dirs)
218 utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
220 utils.WritePidFile(daemon_name)
222 utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
224 stderr_logging=not options.fork,
225 multithreaded=multithread)
226 logging.info("%s daemon startup" % daemon_name)
227 exec_fn(options, args)
229 utils.RemovePidFile(daemon_name)