Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ fdad8c4d

History | View | Annotate | Download (11.1 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 821d9e43 Michael Hanselmann
import errno
29 04ccf5e9 Guido Trotter
import logging
30 a02b89cf Guido Trotter
import sched
31 a02b89cf Guido Trotter
import time
32 5f3269fc Guido Trotter
import socket
33 6ddf5c8f Guido Trotter
import select
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 8216580d Guido Trotter
  def do_read(self):
96 8216580d Guido Trotter
    try:
97 8216580d Guido Trotter
      payload, address = self.recvfrom(constants.MAX_UDP_DATA_SIZE)
98 8216580d Guido Trotter
    except socket.error, err:
99 8216580d Guido Trotter
      if err.errno == errno.EINTR:
100 8216580d Guido Trotter
        # we got a signal while trying to read. no need to do anything,
101 8216580d Guido Trotter
        # handle_read will be called again if there is data on the socket.
102 8216580d Guido Trotter
        return
103 8216580d Guido Trotter
      else:
104 8216580d Guido Trotter
        raise
105 8216580d Guido Trotter
    ip, port = address
106 8216580d Guido Trotter
    self.handle_datagram(payload, ip, port)
107 8216580d Guido Trotter
108 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
109 5f3269fc Guido Trotter
  def handle_read(self):
110 5f3269fc Guido Trotter
    try:
111 8216580d Guido Trotter
      self.do_read()
112 7260cfbe Iustin Pop
    except: # pylint: disable-msg=W0702
113 5f3269fc Guido Trotter
      # we need to catch any exception here, log it, but proceed, because even
114 5f3269fc Guido Trotter
      # if we failed handling a single request, we still want to continue.
115 5f3269fc Guido Trotter
      logging.error("Unexpected exception", exc_info=True)
116 5f3269fc Guido Trotter
117 5f3269fc Guido Trotter
  def handle_datagram(self, payload, ip, port):
118 5f3269fc Guido Trotter
    """Handle an already read udp datagram
119 5f3269fc Guido Trotter

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

154 5f3269fc Guido Trotter
    """
155 c8eded0b Guido Trotter
    if len(payload) > constants.MAX_UDP_DATA_SIZE:
156 c8eded0b Guido Trotter
      raise errors.UdpDataSizeError('Packet too big: %s > %s' % (len(payload),
157 c8eded0b Guido Trotter
                                    constants.MAX_UDP_DATA_SIZE))
158 5f3269fc Guido Trotter
    self._out_queue.append((ip, port, payload))
159 5f3269fc Guido Trotter
160 6ddf5c8f Guido Trotter
  def process_next_packet(self, timeout=0):
161 6ddf5c8f Guido Trotter
    """Process the next datagram, waiting for it if necessary.
162 6ddf5c8f Guido Trotter

163 6ddf5c8f Guido Trotter
    @type timeout: float
164 6ddf5c8f Guido Trotter
    @param timeout: how long to wait for data
165 6ddf5c8f Guido Trotter
    @rtype: boolean
166 6ddf5c8f Guido Trotter
    @return: True if some data has been handled, False otherwise
167 6ddf5c8f Guido Trotter

168 6ddf5c8f Guido Trotter
    """
169 1b429e2a Iustin Pop
    result = utils.WaitForFdCondition(self, select.POLLIN, timeout)
170 1b429e2a Iustin Pop
    if result is not None and result & select.POLLIN:
171 6ddf5c8f Guido Trotter
      self.do_read()
172 6ddf5c8f Guido Trotter
      return True
173 6ddf5c8f Guido Trotter
    else:
174 6ddf5c8f Guido Trotter
      return False
175 6ddf5c8f Guido Trotter
176 5f3269fc Guido Trotter
177 821d9e43 Michael Hanselmann
class Mainloop(object):
178 821d9e43 Michael Hanselmann
  """Generic mainloop for daemons
179 821d9e43 Michael Hanselmann

180 69b99987 Michael Hanselmann
  @ivar scheduler: A sched.scheduler object, which can be used to register
181 69b99987 Michael Hanselmann
    timed events
182 69b99987 Michael Hanselmann

183 821d9e43 Michael Hanselmann
  """
184 821d9e43 Michael Hanselmann
  def __init__(self):
185 b14b975f Michael Hanselmann
    """Constructs a new Mainloop instance.
186 b14b975f Michael Hanselmann

187 b14b975f Michael Hanselmann
    """
188 821d9e43 Michael Hanselmann
    self._signal_wait = []
189 a02b89cf Guido Trotter
    self.scheduler = AsyncoreScheduler(time.time)
190 821d9e43 Michael Hanselmann
191 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGCHLD])
192 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGTERM])
193 69b99987 Michael Hanselmann
  def Run(self, signal_handlers=None):
194 b14b975f Michael Hanselmann
    """Runs the mainloop.
195 b14b975f Michael Hanselmann

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

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

225 b14b975f Michael Hanselmann
    @type signum: int
226 b14b975f Michael Hanselmann
    @param signum: Signal number
227 b14b975f Michael Hanselmann

228 b14b975f Michael Hanselmann
    """
229 b14b975f Michael Hanselmann
    for owner in self._signal_wait:
230 a9fe7232 Guido Trotter
      owner.OnSignal(signum)
231 821d9e43 Michael Hanselmann
232 821d9e43 Michael Hanselmann
  def RegisterSignal(self, owner):
233 821d9e43 Michael Hanselmann
    """Registers a receiver for signal notifications
234 821d9e43 Michael Hanselmann

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

237 821d9e43 Michael Hanselmann
    @type owner: instance
238 821d9e43 Michael Hanselmann
    @param owner: Receiver
239 821d9e43 Michael Hanselmann

240 821d9e43 Michael Hanselmann
    """
241 821d9e43 Michael Hanselmann
    self._signal_wait.append(owner)
242 b11c9e5c Michael Hanselmann
243 04ccf5e9 Guido Trotter
244 04ccf5e9 Guido Trotter
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn):
245 04ccf5e9 Guido Trotter
  """Shared main function for daemons.
246 04ccf5e9 Guido Trotter

247 04ccf5e9 Guido Trotter
  @type daemon_name: string
248 04ccf5e9 Guido Trotter
  @param daemon_name: daemon name
249 69b99987 Michael Hanselmann
  @type optionparser: optparse.OptionParser
250 04ccf5e9 Guido Trotter
  @param optionparser: initialized optionparser with daemon-specific options
251 04ccf5e9 Guido Trotter
                       (common -f -d options will be handled by this module)
252 04ccf5e9 Guido Trotter
  @type dirs: list of strings
253 04ccf5e9 Guido Trotter
  @param dirs: list of directories that must exist for this daemon to work
254 04ccf5e9 Guido Trotter
  @type check_fn: function which accepts (options, args)
255 04ccf5e9 Guido Trotter
  @param check_fn: function that checks start conditions and exits if they're
256 04ccf5e9 Guido Trotter
                   not met
257 04ccf5e9 Guido Trotter
  @type exec_fn: function which accepts (options, args)
258 04ccf5e9 Guido Trotter
  @param exec_fn: function that's executed with the daemon's pid file held, and
259 04ccf5e9 Guido Trotter
                  runs the daemon itself.
260 04ccf5e9 Guido Trotter

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