Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ 48bf6352

History | View | Annotate | Download (11.4 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 48bf6352 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
130 5f3269fc Guido Trotter
  def handle_write(self):
131 5f3269fc Guido Trotter
    try:
132 5f3269fc Guido Trotter
      if not self._out_queue:
133 5f3269fc Guido Trotter
        logging.error("handle_write called with empty output queue")
134 5f3269fc Guido Trotter
        return
135 5f3269fc Guido Trotter
      (ip, port, payload) = self._out_queue[0]
136 5f3269fc Guido Trotter
      try:
137 5f3269fc Guido Trotter
        self.sendto(payload, 0, (ip, port))
138 5f3269fc Guido Trotter
      except socket.error, err:
139 5f3269fc Guido Trotter
        if err.errno == errno.EINTR:
140 5f3269fc Guido Trotter
          # we got a signal while trying to write. no need to do anything,
141 5f3269fc Guido Trotter
          # handle_write will be called again because we haven't emptied the
142 5f3269fc Guido Trotter
          # _out_queue, and we'll try again
143 5f3269fc Guido Trotter
          return
144 5f3269fc Guido Trotter
        else:
145 5f3269fc Guido Trotter
          raise
146 5f3269fc Guido Trotter
      self._out_queue.pop(0)
147 7260cfbe Iustin Pop
    except: # pylint: disable-msg=W0702
148 5f3269fc Guido Trotter
      # we need to catch any exception here, log it, but proceed, because even
149 5f3269fc Guido Trotter
      # if we failed sending a single datagram we still want to continue.
150 5f3269fc Guido Trotter
      logging.error("Unexpected exception", exc_info=True)
151 5f3269fc Guido Trotter
152 5f3269fc Guido Trotter
  def enqueue_send(self, ip, port, payload):
153 5f3269fc Guido Trotter
    """Enqueue a datagram to be sent when possible
154 5f3269fc Guido Trotter

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

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

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

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

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

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

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

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

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

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

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

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

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

249 04ccf5e9 Guido Trotter
  @type daemon_name: string
250 04ccf5e9 Guido Trotter
  @param daemon_name: daemon name
251 69b99987 Michael Hanselmann
  @type optionparser: optparse.OptionParser
252 04ccf5e9 Guido Trotter
  @param optionparser: initialized optionparser with daemon-specific options
253 04ccf5e9 Guido Trotter
                       (common -f -d options will be handled by this module)
254 5a062513 Guido Trotter
  @type dirs: list of (string, integer)
255 5a062513 Guido Trotter
  @param dirs: list of directories that must be created if they don't exist,
256 5a062513 Guido Trotter
               and the permissions to be used to create them
257 04ccf5e9 Guido Trotter
  @type check_fn: function which accepts (options, args)
258 04ccf5e9 Guido Trotter
  @param check_fn: function that checks start conditions and exits if they're
259 04ccf5e9 Guido Trotter
                   not met
260 04ccf5e9 Guido Trotter
  @type exec_fn: function which accepts (options, args)
261 04ccf5e9 Guido Trotter
  @param exec_fn: function that's executed with the daemon's pid file held, and
262 04ccf5e9 Guido Trotter
                  runs the daemon itself.
263 ff917534 Luca Bigliardi
  @type console_logging: boolean
264 ff917534 Luca Bigliardi
  @param console_logging: if True, the daemon will fall back to the system
265 ff917534 Luca Bigliardi
                          console if logging fails
266 04ccf5e9 Guido Trotter

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