Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ 72087dcd

History | View | Annotate | Download (11.6 kB)

1 821d9e43 Michael Hanselmann
#
2 821d9e43 Michael Hanselmann
#
3 821d9e43 Michael Hanselmann
4 821d9e43 Michael Hanselmann
# Copyright (C) 2006, 2007, 2008 Google Inc.
5 821d9e43 Michael Hanselmann
#
6 821d9e43 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 821d9e43 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 821d9e43 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 821d9e43 Michael Hanselmann
# (at your option) any later version.
10 821d9e43 Michael Hanselmann
#
11 821d9e43 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 821d9e43 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 821d9e43 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 821d9e43 Michael Hanselmann
# General Public License for more details.
15 821d9e43 Michael Hanselmann
#
16 821d9e43 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 821d9e43 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 821d9e43 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 821d9e43 Michael Hanselmann
# 02110-1301, USA.
20 821d9e43 Michael Hanselmann
21 821d9e43 Michael Hanselmann
22 821d9e43 Michael Hanselmann
"""Module with helper classes and functions for daemons"""
23 821d9e43 Michael Hanselmann
24 821d9e43 Michael Hanselmann
25 112d240d Guido Trotter
import asyncore
26 3b1b0cb6 Guido Trotter
import os
27 821d9e43 Michael Hanselmann
import signal
28 04ccf5e9 Guido Trotter
import logging
29 a02b89cf Guido Trotter
import sched
30 a02b89cf Guido Trotter
import time
31 5f3269fc Guido Trotter
import socket
32 6ddf5c8f Guido Trotter
import select
33 c124045f Iustin Pop
import sys
34 821d9e43 Michael Hanselmann
35 821d9e43 Michael Hanselmann
from ganeti import utils
36 04ccf5e9 Guido Trotter
from ganeti import constants
37 a02b89cf Guido Trotter
from ganeti import errors
38 a02b89cf Guido Trotter
39 a02b89cf Guido Trotter
40 a02b89cf Guido Trotter
class SchedulerBreakout(Exception):
41 a02b89cf Guido Trotter
  """Exception used to get out of the scheduler loop
42 a02b89cf Guido Trotter

43 a02b89cf Guido Trotter
  """
44 a02b89cf Guido Trotter
45 a02b89cf Guido Trotter
46 a02b89cf Guido Trotter
def AsyncoreDelayFunction(timeout):
47 a02b89cf Guido Trotter
  """Asyncore-compatible scheduler delay function.
48 a02b89cf Guido Trotter

49 a02b89cf Guido Trotter
  This is a delay function for sched that, rather than actually sleeping,
50 a02b89cf Guido Trotter
  executes asyncore events happening in the meantime.
51 a02b89cf Guido Trotter

52 a02b89cf Guido Trotter
  After an event has occurred, rather than returning, it raises a
53 a02b89cf Guido Trotter
  SchedulerBreakout exception, which will force the current scheduler.run()
54 a02b89cf Guido Trotter
  invocation to terminate, so that we can also check for signals. The main loop
55 a02b89cf Guido Trotter
  will then call the scheduler run again, which will allow it to actually
56 a02b89cf Guido Trotter
  process any due events.
57 a02b89cf Guido Trotter

58 a02b89cf Guido Trotter
  This is needed because scheduler.run() doesn't support a count=..., as
59 a02b89cf Guido Trotter
  asyncore loop, and the scheduler module documents throwing exceptions from
60 a02b89cf Guido Trotter
  inside the delay function as an allowed usage model.
61 a02b89cf Guido Trotter

62 a02b89cf Guido Trotter
  """
63 a02b89cf Guido Trotter
  asyncore.loop(timeout=timeout, count=1, use_poll=True)
64 a02b89cf Guido Trotter
  raise SchedulerBreakout()
65 a02b89cf Guido Trotter
66 a02b89cf Guido Trotter
67 a02b89cf Guido Trotter
class AsyncoreScheduler(sched.scheduler):
68 a02b89cf Guido Trotter
  """Event scheduler integrated with asyncore
69 a02b89cf Guido Trotter

70 a02b89cf Guido Trotter
  """
71 a02b89cf Guido Trotter
  def __init__(self, timefunc):
72 a02b89cf Guido Trotter
    sched.scheduler.__init__(self, timefunc, AsyncoreDelayFunction)
73 821d9e43 Michael Hanselmann
74 821d9e43 Michael Hanselmann
75 b11780bb Guido Trotter
class GanetiBaseAsyncoreDispatcher(asyncore.dispatcher):
76 b11780bb Guido Trotter
  """Base Ganeti Asyncore Dispacher
77 b11780bb Guido Trotter

78 b11780bb Guido Trotter
  """
79 b11780bb Guido Trotter
  # this method is overriding an asyncore.dispatcher method
80 b11780bb Guido Trotter
  def handle_error(self):
81 b11780bb Guido Trotter
    """Log an error in handling any request, and proceed.
82 b11780bb Guido Trotter

83 b11780bb Guido Trotter
    """
84 b11780bb Guido Trotter
    logging.exception("Error while handling asyncore request")
85 b11780bb Guido Trotter
86 b11780bb Guido Trotter
  # this method is overriding an asyncore.dispatcher method
87 b11780bb Guido Trotter
  def writable(self):
88 b11780bb Guido Trotter
    """Most of the time we don't want to check for writability.
89 b11780bb Guido Trotter

90 b11780bb Guido Trotter
    """
91 b11780bb Guido Trotter
    return False
92 b11780bb Guido Trotter
93 b11780bb Guido Trotter
94 b11780bb Guido Trotter
class AsyncUDPSocket(GanetiBaseAsyncoreDispatcher):
95 5f3269fc Guido Trotter
  """An improved asyncore udp socket.
96 5f3269fc Guido Trotter

97 5f3269fc Guido Trotter
  """
98 5f3269fc Guido Trotter
  def __init__(self):
99 5f3269fc Guido Trotter
    """Constructor for AsyncUDPSocket
100 5f3269fc Guido Trotter

101 5f3269fc Guido Trotter
    """
102 b11780bb Guido Trotter
    GanetiBaseAsyncoreDispatcher.__init__(self)
103 5f3269fc Guido Trotter
    self._out_queue = []
104 5f3269fc Guido Trotter
    self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
105 5f3269fc Guido Trotter
106 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
107 5f3269fc Guido Trotter
  def handle_connect(self):
108 5f3269fc Guido Trotter
    # Python thinks that the first udp message from a source qualifies as a
109 5f3269fc Guido Trotter
    # "connect" and further ones are part of the same connection. We beg to
110 5f3269fc Guido Trotter
    # differ and treat all messages equally.
111 5f3269fc Guido Trotter
    pass
112 5f3269fc Guido Trotter
113 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
114 5f3269fc Guido Trotter
  def handle_read(self):
115 232144d0 Guido Trotter
    payload, address = utils.IgnoreSignals(self.recvfrom,
116 232144d0 Guido Trotter
                                           constants.MAX_UDP_DATA_SIZE)
117 8216580d Guido Trotter
    ip, port = address
118 8216580d Guido Trotter
    self.handle_datagram(payload, ip, port)
119 5f3269fc Guido Trotter
120 5f3269fc Guido Trotter
  def handle_datagram(self, payload, ip, port):
121 5f3269fc Guido Trotter
    """Handle an already read udp datagram
122 5f3269fc Guido Trotter

123 5f3269fc Guido Trotter
    """
124 5f3269fc Guido Trotter
    raise NotImplementedError
125 5f3269fc Guido Trotter
126 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
127 5f3269fc Guido Trotter
  def writable(self):
128 5f3269fc Guido Trotter
    # We should check whether we can write to the socket only if we have
129 5f3269fc Guido Trotter
    # something scheduled to be written
130 5f3269fc Guido Trotter
    return bool(self._out_queue)
131 5f3269fc Guido Trotter
132 48bf6352 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
133 5f3269fc Guido Trotter
  def handle_write(self):
134 3660fcf5 Guido Trotter
    if not self._out_queue:
135 3660fcf5 Guido Trotter
      logging.error("handle_write called with empty output queue")
136 3660fcf5 Guido Trotter
      return
137 3660fcf5 Guido Trotter
    (ip, port, payload) = self._out_queue[0]
138 232144d0 Guido Trotter
    utils.IgnoreSignals(self.sendto, payload, 0, (ip, port))
139 3660fcf5 Guido Trotter
    self._out_queue.pop(0)
140 3660fcf5 Guido Trotter
141 5f3269fc Guido Trotter
  def enqueue_send(self, ip, port, payload):
142 5f3269fc Guido Trotter
    """Enqueue a datagram to be sent when possible
143 5f3269fc Guido Trotter

144 5f3269fc Guido Trotter
    """
145 c8eded0b Guido Trotter
    if len(payload) > constants.MAX_UDP_DATA_SIZE:
146 c8eded0b Guido Trotter
      raise errors.UdpDataSizeError('Packet too big: %s > %s' % (len(payload),
147 c8eded0b Guido Trotter
                                    constants.MAX_UDP_DATA_SIZE))
148 5f3269fc Guido Trotter
    self._out_queue.append((ip, port, payload))
149 5f3269fc Guido Trotter
150 6ddf5c8f Guido Trotter
  def process_next_packet(self, timeout=0):
151 6ddf5c8f Guido Trotter
    """Process the next datagram, waiting for it if necessary.
152 6ddf5c8f Guido Trotter

153 6ddf5c8f Guido Trotter
    @type timeout: float
154 6ddf5c8f Guido Trotter
    @param timeout: how long to wait for data
155 6ddf5c8f Guido Trotter
    @rtype: boolean
156 6ddf5c8f Guido Trotter
    @return: True if some data has been handled, False otherwise
157 6ddf5c8f Guido Trotter

158 6ddf5c8f Guido Trotter
    """
159 1b429e2a Iustin Pop
    result = utils.WaitForFdCondition(self, select.POLLIN, timeout)
160 1b429e2a Iustin Pop
    if result is not None and result & select.POLLIN:
161 3660fcf5 Guido Trotter
      self.handle_read()
162 6ddf5c8f Guido Trotter
      return True
163 6ddf5c8f Guido Trotter
    else:
164 6ddf5c8f Guido Trotter
      return False
165 6ddf5c8f Guido Trotter
166 5f3269fc Guido Trotter
167 821d9e43 Michael Hanselmann
class Mainloop(object):
168 821d9e43 Michael Hanselmann
  """Generic mainloop for daemons
169 821d9e43 Michael Hanselmann

170 69b99987 Michael Hanselmann
  @ivar scheduler: A sched.scheduler object, which can be used to register
171 69b99987 Michael Hanselmann
    timed events
172 69b99987 Michael Hanselmann

173 821d9e43 Michael Hanselmann
  """
174 821d9e43 Michael Hanselmann
  def __init__(self):
175 b14b975f Michael Hanselmann
    """Constructs a new Mainloop instance.
176 b14b975f Michael Hanselmann

177 b14b975f Michael Hanselmann
    """
178 821d9e43 Michael Hanselmann
    self._signal_wait = []
179 a02b89cf Guido Trotter
    self.scheduler = AsyncoreScheduler(time.time)
180 821d9e43 Michael Hanselmann
181 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGCHLD])
182 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGTERM])
183 f59dce3e Guido Trotter
  @utils.SignalHandled([signal.SIGINT])
184 69b99987 Michael Hanselmann
  def Run(self, signal_handlers=None):
185 b14b975f Michael Hanselmann
    """Runs the mainloop.
186 b14b975f Michael Hanselmann

187 9b739173 Guido Trotter
    @type signal_handlers: dict
188 9b739173 Guido Trotter
    @param signal_handlers: signal->L{utils.SignalHandler} passed by decorator
189 b14b975f Michael Hanselmann

190 b14b975f Michael Hanselmann
    """
191 9b739173 Guido Trotter
    assert isinstance(signal_handlers, dict) and \
192 9b739173 Guido Trotter
           len(signal_handlers) > 0, \
193 9b739173 Guido Trotter
           "Broken SignalHandled decorator"
194 9b739173 Guido Trotter
    running = True
195 9b739173 Guido Trotter
    # Start actual main loop
196 9b739173 Guido Trotter
    while running:
197 a02b89cf Guido Trotter
      if not self.scheduler.empty():
198 a02b89cf Guido Trotter
        try:
199 a02b89cf Guido Trotter
          self.scheduler.run()
200 a02b89cf Guido Trotter
        except SchedulerBreakout:
201 a02b89cf Guido Trotter
          pass
202 a02b89cf Guido Trotter
      else:
203 a02b89cf Guido Trotter
        asyncore.loop(count=1, use_poll=True)
204 9b739173 Guido Trotter
205 9b739173 Guido Trotter
      # Check whether a signal was raised
206 9b739173 Guido Trotter
      for sig in signal_handlers:
207 9b739173 Guido Trotter
        handler = signal_handlers[sig]
208 9b739173 Guido Trotter
        if handler.called:
209 9b739173 Guido Trotter
          self._CallSignalWaiters(sig)
210 f59dce3e Guido Trotter
          running = sig not in (signal.SIGTERM, signal.SIGINT)
211 9b739173 Guido Trotter
          handler.Clear()
212 a570e2a8 Guido Trotter
213 b14b975f Michael Hanselmann
  def _CallSignalWaiters(self, signum):
214 b14b975f Michael Hanselmann
    """Calls all signal waiters for a certain signal.
215 b14b975f Michael Hanselmann

216 b14b975f Michael Hanselmann
    @type signum: int
217 b14b975f Michael Hanselmann
    @param signum: Signal number
218 b14b975f Michael Hanselmann

219 b14b975f Michael Hanselmann
    """
220 b14b975f Michael Hanselmann
    for owner in self._signal_wait:
221 a9fe7232 Guido Trotter
      owner.OnSignal(signum)
222 821d9e43 Michael Hanselmann
223 821d9e43 Michael Hanselmann
  def RegisterSignal(self, owner):
224 821d9e43 Michael Hanselmann
    """Registers a receiver for signal notifications
225 821d9e43 Michael Hanselmann

226 821d9e43 Michael Hanselmann
    The receiver must support a "OnSignal(self, signum)" function.
227 821d9e43 Michael Hanselmann

228 821d9e43 Michael Hanselmann
    @type owner: instance
229 821d9e43 Michael Hanselmann
    @param owner: Receiver
230 821d9e43 Michael Hanselmann

231 821d9e43 Michael Hanselmann
    """
232 821d9e43 Michael Hanselmann
    self._signal_wait.append(owner)
233 b11c9e5c Michael Hanselmann
234 04ccf5e9 Guido Trotter
235 30dabd03 Michael Hanselmann
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn,
236 1c54156d Luca Bigliardi
                multithreaded=False, console_logging=False,
237 0648750e Michael Hanselmann
                default_ssl_cert=None, default_ssl_key=None):
238 04ccf5e9 Guido Trotter
  """Shared main function for daemons.
239 04ccf5e9 Guido Trotter

240 04ccf5e9 Guido Trotter
  @type daemon_name: string
241 04ccf5e9 Guido Trotter
  @param daemon_name: daemon name
242 69b99987 Michael Hanselmann
  @type optionparser: optparse.OptionParser
243 04ccf5e9 Guido Trotter
  @param optionparser: initialized optionparser with daemon-specific options
244 04ccf5e9 Guido Trotter
                       (common -f -d options will be handled by this module)
245 5a062513 Guido Trotter
  @type dirs: list of (string, integer)
246 5a062513 Guido Trotter
  @param dirs: list of directories that must be created if they don't exist,
247 5a062513 Guido Trotter
               and the permissions to be used to create them
248 04ccf5e9 Guido Trotter
  @type check_fn: function which accepts (options, args)
249 04ccf5e9 Guido Trotter
  @param check_fn: function that checks start conditions and exits if they're
250 04ccf5e9 Guido Trotter
                   not met
251 04ccf5e9 Guido Trotter
  @type exec_fn: function which accepts (options, args)
252 04ccf5e9 Guido Trotter
  @param exec_fn: function that's executed with the daemon's pid file held, and
253 04ccf5e9 Guido Trotter
                  runs the daemon itself.
254 30dabd03 Michael Hanselmann
  @type multithreaded: bool
255 30dabd03 Michael Hanselmann
  @param multithreaded: Whether the daemon uses threads
256 ff917534 Luca Bigliardi
  @type console_logging: boolean
257 ff917534 Luca Bigliardi
  @param console_logging: if True, the daemon will fall back to the system
258 ff917534 Luca Bigliardi
                          console if logging fails
259 0648750e Michael Hanselmann
  @type default_ssl_cert: string
260 0648750e Michael Hanselmann
  @param default_ssl_cert: Default SSL certificate path
261 0648750e Michael Hanselmann
  @type default_ssl_key: string
262 0648750e Michael Hanselmann
  @param default_ssl_key: Default SSL key path
263 04ccf5e9 Guido Trotter

264 04ccf5e9 Guido Trotter
  """
265 04ccf5e9 Guido Trotter
  optionparser.add_option("-f", "--foreground", dest="fork",
266 04ccf5e9 Guido Trotter
                          help="Don't detach from the current terminal",
267 04ccf5e9 Guido Trotter
                          default=True, action="store_false")
268 04ccf5e9 Guido Trotter
  optionparser.add_option("-d", "--debug", dest="debug",
269 04ccf5e9 Guido Trotter
                          help="Enable some debug messages",
270 04ccf5e9 Guido Trotter
                          default=False, action="store_true")
271 551b6283 Iustin Pop
  optionparser.add_option("--syslog", dest="syslog",
272 551b6283 Iustin Pop
                          help="Enable logging to syslog (except debug"
273 551b6283 Iustin Pop
                          " messages); one of 'no', 'yes' or 'only' [%s]" %
274 551b6283 Iustin Pop
                          constants.SYSLOG_USAGE,
275 551b6283 Iustin Pop
                          default=constants.SYSLOG_USAGE,
276 551b6283 Iustin Pop
                          choices=["no", "yes", "only"])
277 0a71aa17 Michael Hanselmann
278 04ccf5e9 Guido Trotter
  if daemon_name in constants.DAEMONS_PORTS:
279 0a71aa17 Michael Hanselmann
    default_bind_address = "0.0.0.0"
280 0a71aa17 Michael Hanselmann
    default_port = utils.GetDaemonPort(daemon_name)
281 0a71aa17 Michael Hanselmann
282 0a71aa17 Michael Hanselmann
    # For networked daemons we allow choosing the port and bind address
283 04ccf5e9 Guido Trotter
    optionparser.add_option("-p", "--port", dest="port",
284 0a71aa17 Michael Hanselmann
                            help="Network port (default: %s)" % default_port,
285 0a71aa17 Michael Hanselmann
                            default=default_port, type="int")
286 04ccf5e9 Guido Trotter
    optionparser.add_option("-b", "--bind", dest="bind_address",
287 0a71aa17 Michael Hanselmann
                            help=("Bind address (default: %s)" %
288 0a71aa17 Michael Hanselmann
                                  default_bind_address),
289 0a71aa17 Michael Hanselmann
                            default=default_bind_address, metavar="ADDRESS")
290 04ccf5e9 Guido Trotter
291 0648750e Michael Hanselmann
  if default_ssl_key is not None and default_ssl_cert is not None:
292 3b1b0cb6 Guido Trotter
    optionparser.add_option("--no-ssl", dest="ssl",
293 3b1b0cb6 Guido Trotter
                            help="Do not secure HTTP protocol with SSL",
294 3b1b0cb6 Guido Trotter
                            default=True, action="store_false")
295 3b1b0cb6 Guido Trotter
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
296 0648750e Michael Hanselmann
                            help=("SSL key path (default: %s)" %
297 0648750e Michael Hanselmann
                                  default_ssl_key),
298 0648750e Michael Hanselmann
                            default=default_ssl_key, type="string",
299 0648750e Michael Hanselmann
                            metavar="SSL_KEY_PATH")
300 3b1b0cb6 Guido Trotter
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
301 0648750e Michael Hanselmann
                            help=("SSL certificate path (default: %s)" %
302 0648750e Michael Hanselmann
                                  default_ssl_cert),
303 0648750e Michael Hanselmann
                            default=default_ssl_cert, type="string",
304 0648750e Michael Hanselmann
                            metavar="SSL_CERT_PATH")
305 3b1b0cb6 Guido Trotter
306 30dabd03 Michael Hanselmann
  # Disable the use of fork(2) if the daemon uses threads
307 30dabd03 Michael Hanselmann
  utils.no_fork = multithreaded
308 04ccf5e9 Guido Trotter
309 04ccf5e9 Guido Trotter
  options, args = optionparser.parse_args()
310 04ccf5e9 Guido Trotter
311 0648750e Michael Hanselmann
  if getattr(options, "ssl", False):
312 0648750e Michael Hanselmann
    ssl_paths = {
313 0648750e Michael Hanselmann
      "certificate": options.ssl_cert,
314 0648750e Michael Hanselmann
      "key": options.ssl_key,
315 0648750e Michael Hanselmann
      }
316 0648750e Michael Hanselmann
317 0648750e Michael Hanselmann
    for name, path in ssl_paths.iteritems():
318 0648750e Michael Hanselmann
      if not os.path.isfile(path):
319 0648750e Michael Hanselmann
        print >> sys.stderr, "SSL %s file '%s' was not found" % (name, path)
320 3b1b0cb6 Guido Trotter
        sys.exit(constants.EXIT_FAILURE)
321 3b1b0cb6 Guido Trotter
322 0648750e Michael Hanselmann
    # TODO: By initiating http.HttpSslParams here we would only read the files
323 0648750e Michael Hanselmann
    # once and have a proper validation (isfile returns False on directories)
324 0648750e Michael Hanselmann
    # at the same time.
325 0648750e Michael Hanselmann
326 3b1b0cb6 Guido Trotter
  if check_fn is not None:
327 3b1b0cb6 Guido Trotter
    check_fn(options, args)
328 3b1b0cb6 Guido Trotter
329 04ccf5e9 Guido Trotter
  utils.EnsureDirs(dirs)
330 04ccf5e9 Guido Trotter
331 04ccf5e9 Guido Trotter
  if options.fork:
332 04ccf5e9 Guido Trotter
    utils.CloseFDs()
333 04ccf5e9 Guido Trotter
    utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
334 04ccf5e9 Guido Trotter
335 04ccf5e9 Guido Trotter
  utils.WritePidFile(daemon_name)
336 04ccf5e9 Guido Trotter
  try:
337 04ccf5e9 Guido Trotter
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
338 04ccf5e9 Guido Trotter
                       debug=options.debug,
339 04ccf5e9 Guido Trotter
                       stderr_logging=not options.fork,
340 e7307f08 Michael Hanselmann
                       multithreaded=multithreaded,
341 551b6283 Iustin Pop
                       program=daemon_name,
342 ff917534 Luca Bigliardi
                       syslog=options.syslog,
343 ff917534 Luca Bigliardi
                       console_logging=console_logging)
344 099c52ad Iustin Pop
    logging.info("%s daemon startup", daemon_name)
345 04ccf5e9 Guido Trotter
    exec_fn(options, args)
346 04ccf5e9 Guido Trotter
  finally:
347 04ccf5e9 Guido Trotter
    utils.RemovePidFile(daemon_name)