Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ c124045f

History | View | Annotate | Download (10.5 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 select
28 821d9e43 Michael Hanselmann
import signal
29 821d9e43 Michael Hanselmann
import errno
30 04ccf5e9 Guido Trotter
import logging
31 a02b89cf Guido Trotter
import sched
32 a02b89cf Guido Trotter
import time
33 5f3269fc Guido Trotter
import socket
34 c124045f Iustin Pop
import sys
35 821d9e43 Michael Hanselmann
36 821d9e43 Michael Hanselmann
from ganeti import utils
37 04ccf5e9 Guido Trotter
from ganeti import constants
38 a02b89cf Guido Trotter
from ganeti import errors
39 a02b89cf Guido Trotter
40 a02b89cf Guido Trotter
41 a02b89cf Guido Trotter
class SchedulerBreakout(Exception):
42 a02b89cf Guido Trotter
  """Exception used to get out of the scheduler loop
43 a02b89cf Guido Trotter

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

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

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

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

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

71 a02b89cf Guido Trotter
  """
72 a02b89cf Guido Trotter
  def __init__(self, timefunc):
73 a02b89cf Guido Trotter
    sched.scheduler.__init__(self, timefunc, AsyncoreDelayFunction)
74 821d9e43 Michael Hanselmann
75 821d9e43 Michael Hanselmann
76 5f3269fc Guido Trotter
class AsyncUDPSocket(asyncore.dispatcher):
77 5f3269fc Guido Trotter
  """An improved asyncore udp socket.
78 5f3269fc Guido Trotter

79 5f3269fc Guido Trotter
  """
80 5f3269fc Guido Trotter
  def __init__(self):
81 5f3269fc Guido Trotter
    """Constructor for AsyncUDPSocket
82 5f3269fc Guido Trotter

83 5f3269fc Guido Trotter
    """
84 5f3269fc Guido Trotter
    asyncore.dispatcher.__init__(self)
85 5f3269fc Guido Trotter
    self._out_queue = []
86 5f3269fc Guido Trotter
    self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
87 5f3269fc Guido Trotter
88 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
89 5f3269fc Guido Trotter
  def handle_connect(self):
90 5f3269fc Guido Trotter
    # Python thinks that the first udp message from a source qualifies as a
91 5f3269fc Guido Trotter
    # "connect" and further ones are part of the same connection. We beg to
92 5f3269fc Guido Trotter
    # differ and treat all messages equally.
93 5f3269fc Guido Trotter
    pass
94 5f3269fc Guido Trotter
95 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
96 5f3269fc Guido Trotter
  def handle_read(self):
97 5f3269fc Guido Trotter
    try:
98 5f3269fc Guido Trotter
      try:
99 c8eded0b Guido Trotter
        payload, address = self.recvfrom(constants.MAX_UDP_DATA_SIZE)
100 5f3269fc Guido Trotter
      except socket.error, err:
101 5f3269fc Guido Trotter
        if err.errno == errno.EINTR:
102 5f3269fc Guido Trotter
          # we got a signal while trying to read. no need to do anything,
103 5f3269fc Guido Trotter
          # handle_read will be called again if there is data on the socket.
104 5f3269fc Guido Trotter
          return
105 5f3269fc Guido Trotter
        else:
106 5f3269fc Guido Trotter
          raise
107 5f3269fc Guido Trotter
      ip, port = address
108 5f3269fc Guido Trotter
      self.handle_datagram(payload, ip, port)
109 5f3269fc Guido Trotter
    except:
110 5f3269fc Guido Trotter
      # we need to catch any exception here, log it, but proceed, because even
111 5f3269fc Guido Trotter
      # if we failed handling a single request, we still want to continue.
112 5f3269fc Guido Trotter
      logging.error("Unexpected exception", exc_info=True)
113 5f3269fc Guido Trotter
114 5f3269fc Guido Trotter
  def handle_datagram(self, payload, ip, port):
115 5f3269fc Guido Trotter
    """Handle an already read udp datagram
116 5f3269fc Guido Trotter

117 5f3269fc Guido Trotter
    """
118 5f3269fc Guido Trotter
    raise NotImplementedError
119 5f3269fc Guido Trotter
120 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
121 5f3269fc Guido Trotter
  def writable(self):
122 5f3269fc Guido Trotter
    # We should check whether we can write to the socket only if we have
123 5f3269fc Guido Trotter
    # something scheduled to be written
124 5f3269fc Guido Trotter
    return bool(self._out_queue)
125 5f3269fc Guido Trotter
126 5f3269fc Guido Trotter
  def handle_write(self):
127 5f3269fc Guido Trotter
    try:
128 5f3269fc Guido Trotter
      if not self._out_queue:
129 5f3269fc Guido Trotter
        logging.error("handle_write called with empty output queue")
130 5f3269fc Guido Trotter
        return
131 5f3269fc Guido Trotter
      (ip, port, payload) = self._out_queue[0]
132 5f3269fc Guido Trotter
      try:
133 5f3269fc Guido Trotter
        self.sendto(payload, 0, (ip, port))
134 5f3269fc Guido Trotter
      except socket.error, err:
135 5f3269fc Guido Trotter
        if err.errno == errno.EINTR:
136 5f3269fc Guido Trotter
          # we got a signal while trying to write. no need to do anything,
137 5f3269fc Guido Trotter
          # handle_write will be called again because we haven't emptied the
138 5f3269fc Guido Trotter
          # _out_queue, and we'll try again
139 5f3269fc Guido Trotter
          return
140 5f3269fc Guido Trotter
        else:
141 5f3269fc Guido Trotter
          raise
142 5f3269fc Guido Trotter
      self._out_queue.pop(0)
143 5f3269fc Guido Trotter
    except:
144 5f3269fc Guido Trotter
      # we need to catch any exception here, log it, but proceed, because even
145 5f3269fc Guido Trotter
      # if we failed sending a single datagram we still want to continue.
146 5f3269fc Guido Trotter
      logging.error("Unexpected exception", exc_info=True)
147 5f3269fc Guido Trotter
148 5f3269fc Guido Trotter
  def enqueue_send(self, ip, port, payload):
149 5f3269fc Guido Trotter
    """Enqueue a datagram to be sent when possible
150 5f3269fc Guido Trotter

151 5f3269fc Guido Trotter
    """
152 c8eded0b Guido Trotter
    if len(payload) > constants.MAX_UDP_DATA_SIZE:
153 c8eded0b Guido Trotter
      raise errors.UdpDataSizeError('Packet too big: %s > %s' % (len(payload),
154 c8eded0b Guido Trotter
                                    constants.MAX_UDP_DATA_SIZE))
155 5f3269fc Guido Trotter
    self._out_queue.append((ip, port, payload))
156 5f3269fc Guido Trotter
157 5f3269fc Guido Trotter
158 821d9e43 Michael Hanselmann
class Mainloop(object):
159 821d9e43 Michael Hanselmann
  """Generic mainloop for daemons
160 821d9e43 Michael Hanselmann

161 821d9e43 Michael Hanselmann
  """
162 821d9e43 Michael Hanselmann
  def __init__(self):
163 b14b975f Michael Hanselmann
    """Constructs a new Mainloop instance.
164 b14b975f Michael Hanselmann

165 a02b89cf Guido Trotter
    @ivar scheduler: A L{sched.scheduler} object, which can be used to register
166 a02b89cf Guido Trotter
    timed events
167 a02b89cf Guido Trotter

168 b14b975f Michael Hanselmann
    """
169 821d9e43 Michael Hanselmann
    self._signal_wait = []
170 a02b89cf Guido Trotter
    self.scheduler = AsyncoreScheduler(time.time)
171 821d9e43 Michael Hanselmann
172 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGCHLD])
173 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGTERM])
174 9b739173 Guido Trotter
  def Run(self, stop_on_empty=False, signal_handlers=None):
175 b14b975f Michael Hanselmann
    """Runs the mainloop.
176 b14b975f Michael Hanselmann

177 b14b975f Michael Hanselmann
    @type stop_on_empty: bool
178 b14b975f Michael Hanselmann
    @param stop_on_empty: Whether to stop mainloop once all I/O waiters
179 b14b975f Michael Hanselmann
                          unregistered
180 9b739173 Guido Trotter
    @type signal_handlers: dict
181 9b739173 Guido Trotter
    @param signal_handlers: signal->L{utils.SignalHandler} passed by decorator
182 b14b975f Michael Hanselmann

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

213 b14b975f Michael Hanselmann
    @type signum: int
214 b14b975f Michael Hanselmann
    @param signum: Signal number
215 b14b975f Michael Hanselmann

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

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

225 821d9e43 Michael Hanselmann
    @type owner: instance
226 821d9e43 Michael Hanselmann
    @param owner: Receiver
227 821d9e43 Michael Hanselmann

228 821d9e43 Michael Hanselmann
    """
229 821d9e43 Michael Hanselmann
    self._signal_wait.append(owner)
230 b11c9e5c Michael Hanselmann
231 04ccf5e9 Guido Trotter
232 04ccf5e9 Guido Trotter
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn):
233 04ccf5e9 Guido Trotter
  """Shared main function for daemons.
234 04ccf5e9 Guido Trotter

235 04ccf5e9 Guido Trotter
  @type daemon_name: string
236 04ccf5e9 Guido Trotter
  @param daemon_name: daemon name
237 04ccf5e9 Guido Trotter
  @type optionparser: L{optparse.OptionParser}
238 04ccf5e9 Guido Trotter
  @param optionparser: initialized optionparser with daemon-specific options
239 04ccf5e9 Guido Trotter
                       (common -f -d options will be handled by this module)
240 04ccf5e9 Guido Trotter
  @type options: object @param options: OptionParser result, should contain at
241 04ccf5e9 Guido Trotter
                 least the fork and the debug options
242 04ccf5e9 Guido Trotter
  @type dirs: list of strings
243 04ccf5e9 Guido Trotter
  @param dirs: list of directories that must exist for this daemon to work
244 04ccf5e9 Guido Trotter
  @type check_fn: function which accepts (options, args)
245 04ccf5e9 Guido Trotter
  @param check_fn: function that checks start conditions and exits if they're
246 04ccf5e9 Guido Trotter
                   not met
247 04ccf5e9 Guido Trotter
  @type exec_fn: function which accepts (options, args)
248 04ccf5e9 Guido Trotter
  @param exec_fn: function that's executed with the daemon's pid file held, and
249 04ccf5e9 Guido Trotter
                  runs the daemon itself.
250 04ccf5e9 Guido Trotter

251 04ccf5e9 Guido Trotter
  """
252 04ccf5e9 Guido Trotter
  optionparser.add_option("-f", "--foreground", dest="fork",
253 04ccf5e9 Guido Trotter
                          help="Don't detach from the current terminal",
254 04ccf5e9 Guido Trotter
                          default=True, action="store_false")
255 04ccf5e9 Guido Trotter
  optionparser.add_option("-d", "--debug", dest="debug",
256 04ccf5e9 Guido Trotter
                          help="Enable some debug messages",
257 04ccf5e9 Guido Trotter
                          default=False, action="store_true")
258 04ccf5e9 Guido Trotter
  if daemon_name in constants.DAEMONS_PORTS:
259 04ccf5e9 Guido Trotter
    # for networked daemons we also allow choosing the bind port and address.
260 04ccf5e9 Guido Trotter
    # by default we use the port provided by utils.GetDaemonPort, and bind to
261 04ccf5e9 Guido Trotter
    # 0.0.0.0 (which is represented by and empty bind address.
262 04ccf5e9 Guido Trotter
    port = utils.GetDaemonPort(daemon_name)
263 04ccf5e9 Guido Trotter
    optionparser.add_option("-p", "--port", dest="port",
264 04ccf5e9 Guido Trotter
                            help="Network port (%s default)." % port,
265 04ccf5e9 Guido Trotter
                            default=port, type="int")
266 04ccf5e9 Guido Trotter
    optionparser.add_option("-b", "--bind", dest="bind_address",
267 04ccf5e9 Guido Trotter
                            help="Bind address",
268 04ccf5e9 Guido Trotter
                            default="", metavar="ADDRESS")
269 04ccf5e9 Guido Trotter
270 3b1b0cb6 Guido Trotter
  if daemon_name in constants.DAEMONS_SSL:
271 3b1b0cb6 Guido Trotter
    default_cert, default_key = constants.DAEMONS_SSL[daemon_name]
272 3b1b0cb6 Guido Trotter
    optionparser.add_option("--no-ssl", dest="ssl",
273 3b1b0cb6 Guido Trotter
                            help="Do not secure HTTP protocol with SSL",
274 3b1b0cb6 Guido Trotter
                            default=True, action="store_false")
275 3b1b0cb6 Guido Trotter
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
276 3b1b0cb6 Guido Trotter
                            help="SSL key",
277 3b1b0cb6 Guido Trotter
                            default=default_key, type="string")
278 3b1b0cb6 Guido Trotter
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
279 3b1b0cb6 Guido Trotter
                            help="SSL certificate",
280 3b1b0cb6 Guido Trotter
                            default=default_cert, type="string")
281 3b1b0cb6 Guido Trotter
282 04ccf5e9 Guido Trotter
  multithread = utils.no_fork = daemon_name in constants.MULTITHREADED_DAEMONS
283 04ccf5e9 Guido Trotter
284 04ccf5e9 Guido Trotter
  options, args = optionparser.parse_args()
285 04ccf5e9 Guido Trotter
286 3b1b0cb6 Guido Trotter
  if hasattr(options, 'ssl') and options.ssl:
287 3b1b0cb6 Guido Trotter
    if not (options.ssl_cert and options.ssl_key):
288 3b1b0cb6 Guido Trotter
      print >> sys.stderr, "Need key and certificate to use ssl"
289 3b1b0cb6 Guido Trotter
      sys.exit(constants.EXIT_FAILURE)
290 3b1b0cb6 Guido Trotter
    for fname in (options.ssl_cert, options.ssl_key):
291 3b1b0cb6 Guido Trotter
      if not os.path.isfile(fname):
292 3b1b0cb6 Guido Trotter
        print >> sys.stderr, "Need ssl file %s to run" % fname
293 3b1b0cb6 Guido Trotter
        sys.exit(constants.EXIT_FAILURE)
294 3b1b0cb6 Guido Trotter
295 3b1b0cb6 Guido Trotter
  if check_fn is not None:
296 3b1b0cb6 Guido Trotter
    check_fn(options, args)
297 3b1b0cb6 Guido Trotter
298 04ccf5e9 Guido Trotter
  utils.EnsureDirs(dirs)
299 04ccf5e9 Guido Trotter
300 04ccf5e9 Guido Trotter
  if options.fork:
301 04ccf5e9 Guido Trotter
    utils.CloseFDs()
302 04ccf5e9 Guido Trotter
    utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
303 04ccf5e9 Guido Trotter
304 04ccf5e9 Guido Trotter
  utils.WritePidFile(daemon_name)
305 04ccf5e9 Guido Trotter
  try:
306 04ccf5e9 Guido Trotter
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
307 04ccf5e9 Guido Trotter
                       debug=options.debug,
308 04ccf5e9 Guido Trotter
                       stderr_logging=not options.fork,
309 04ccf5e9 Guido Trotter
                       multithreaded=multithread)
310 04ccf5e9 Guido Trotter
    logging.info("%s daemon startup" % daemon_name)
311 04ccf5e9 Guido Trotter
    exec_fn(options, args)
312 04ccf5e9 Guido Trotter
  finally:
313 04ccf5e9 Guido Trotter
    utils.RemovePidFile(daemon_name)