Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ e22af31d

History | View | Annotate | Download (10.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 5f3269fc Guido Trotter
class AsyncUDPSocket(asyncore.dispatcher):
76 5f3269fc Guido Trotter
  """An improved asyncore udp socket.
77 5f3269fc Guido Trotter

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

82 5f3269fc Guido Trotter
    """
83 5f3269fc Guido Trotter
    asyncore.dispatcher.__init__(self)
84 5f3269fc Guido Trotter
    self._out_queue = []
85 5f3269fc Guido Trotter
    self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
86 5f3269fc Guido Trotter
87 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
88 5f3269fc Guido Trotter
  def handle_connect(self):
89 5f3269fc Guido Trotter
    # Python thinks that the first udp message from a source qualifies as a
90 5f3269fc Guido Trotter
    # "connect" and further ones are part of the same connection. We beg to
91 5f3269fc Guido Trotter
    # differ and treat all messages equally.
92 5f3269fc Guido Trotter
    pass
93 5f3269fc Guido Trotter
94 3660fcf5 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
95 3660fcf5 Guido Trotter
  def handle_read(self):
96 232144d0 Guido Trotter
    payload, address = utils.IgnoreSignals(self.recvfrom,
97 232144d0 Guido Trotter
                                           constants.MAX_UDP_DATA_SIZE)
98 8216580d Guido Trotter
    ip, port = address
99 8216580d Guido Trotter
    self.handle_datagram(payload, ip, port)
100 8216580d Guido Trotter
101 5f3269fc Guido Trotter
  def handle_datagram(self, payload, ip, port):
102 5f3269fc Guido Trotter
    """Handle an already read udp datagram
103 5f3269fc Guido Trotter

104 5f3269fc Guido Trotter
    """
105 5f3269fc Guido Trotter
    raise NotImplementedError
106 5f3269fc Guido Trotter
107 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
108 5f3269fc Guido Trotter
  def writable(self):
109 5f3269fc Guido Trotter
    # We should check whether we can write to the socket only if we have
110 5f3269fc Guido Trotter
    # something scheduled to be written
111 5f3269fc Guido Trotter
    return bool(self._out_queue)
112 5f3269fc Guido Trotter
113 48bf6352 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
114 5f3269fc Guido Trotter
  def handle_write(self):
115 3660fcf5 Guido Trotter
    if not self._out_queue:
116 3660fcf5 Guido Trotter
      logging.error("handle_write called with empty output queue")
117 3660fcf5 Guido Trotter
      return
118 3660fcf5 Guido Trotter
    (ip, port, payload) = self._out_queue[0]
119 232144d0 Guido Trotter
    utils.IgnoreSignals(self.sendto, payload, 0, (ip, port))
120 3660fcf5 Guido Trotter
    self._out_queue.pop(0)
121 3660fcf5 Guido Trotter
122 3660fcf5 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
123 3660fcf5 Guido Trotter
  def handle_error(self):
124 3660fcf5 Guido Trotter
    """Log an error in handling any request, and proceed.
125 3660fcf5 Guido Trotter

126 3660fcf5 Guido Trotter
    """
127 3660fcf5 Guido Trotter
    logging.exception("Error while handling asyncore request")
128 5f3269fc Guido Trotter
129 5f3269fc Guido Trotter
  def enqueue_send(self, ip, port, payload):
130 5f3269fc Guido Trotter
    """Enqueue a datagram to be sent when possible
131 5f3269fc Guido Trotter

132 5f3269fc Guido Trotter
    """
133 c8eded0b Guido Trotter
    if len(payload) > constants.MAX_UDP_DATA_SIZE:
134 c8eded0b Guido Trotter
      raise errors.UdpDataSizeError('Packet too big: %s > %s' % (len(payload),
135 c8eded0b Guido Trotter
                                    constants.MAX_UDP_DATA_SIZE))
136 5f3269fc Guido Trotter
    self._out_queue.append((ip, port, payload))
137 5f3269fc Guido Trotter
138 6ddf5c8f Guido Trotter
  def process_next_packet(self, timeout=0):
139 6ddf5c8f Guido Trotter
    """Process the next datagram, waiting for it if necessary.
140 6ddf5c8f Guido Trotter

141 6ddf5c8f Guido Trotter
    @type timeout: float
142 6ddf5c8f Guido Trotter
    @param timeout: how long to wait for data
143 6ddf5c8f Guido Trotter
    @rtype: boolean
144 6ddf5c8f Guido Trotter
    @return: True if some data has been handled, False otherwise
145 6ddf5c8f Guido Trotter

146 6ddf5c8f Guido Trotter
    """
147 1b429e2a Iustin Pop
    result = utils.WaitForFdCondition(self, select.POLLIN, timeout)
148 1b429e2a Iustin Pop
    if result is not None and result & select.POLLIN:
149 3660fcf5 Guido Trotter
      self.handle_read()
150 6ddf5c8f Guido Trotter
      return True
151 6ddf5c8f Guido Trotter
    else:
152 6ddf5c8f Guido Trotter
      return False
153 6ddf5c8f Guido Trotter
154 5f3269fc Guido Trotter
155 821d9e43 Michael Hanselmann
class Mainloop(object):
156 821d9e43 Michael Hanselmann
  """Generic mainloop for daemons
157 821d9e43 Michael Hanselmann

158 69b99987 Michael Hanselmann
  @ivar scheduler: A sched.scheduler object, which can be used to register
159 69b99987 Michael Hanselmann
    timed events
160 69b99987 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 b14b975f Michael Hanselmann
    """
166 821d9e43 Michael Hanselmann
    self._signal_wait = []
167 a02b89cf Guido Trotter
    self.scheduler = AsyncoreScheduler(time.time)
168 821d9e43 Michael Hanselmann
169 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGCHLD])
170 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGTERM])
171 69b99987 Michael Hanselmann
  def Run(self, signal_handlers=None):
172 b14b975f Michael Hanselmann
    """Runs the mainloop.
173 b14b975f Michael Hanselmann

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

177 b14b975f Michael Hanselmann
    """
178 9b739173 Guido Trotter
    assert isinstance(signal_handlers, dict) and \
179 9b739173 Guido Trotter
           len(signal_handlers) > 0, \
180 9b739173 Guido Trotter
           "Broken SignalHandled decorator"
181 9b739173 Guido Trotter
    running = True
182 9b739173 Guido Trotter
    # Start actual main loop
183 9b739173 Guido Trotter
    while running:
184 a02b89cf Guido Trotter
      if not self.scheduler.empty():
185 a02b89cf Guido Trotter
        try:
186 a02b89cf Guido Trotter
          self.scheduler.run()
187 a02b89cf Guido Trotter
        except SchedulerBreakout:
188 a02b89cf Guido Trotter
          pass
189 a02b89cf Guido Trotter
      else:
190 a02b89cf Guido Trotter
        asyncore.loop(count=1, use_poll=True)
191 9b739173 Guido Trotter
192 9b739173 Guido Trotter
      # Check whether a signal was raised
193 9b739173 Guido Trotter
      for sig in signal_handlers:
194 9b739173 Guido Trotter
        handler = signal_handlers[sig]
195 9b739173 Guido Trotter
        if handler.called:
196 9b739173 Guido Trotter
          self._CallSignalWaiters(sig)
197 9b739173 Guido Trotter
          running = (sig != signal.SIGTERM)
198 9b739173 Guido Trotter
          handler.Clear()
199 a570e2a8 Guido Trotter
200 b14b975f Michael Hanselmann
  def _CallSignalWaiters(self, signum):
201 b14b975f Michael Hanselmann
    """Calls all signal waiters for a certain signal.
202 b14b975f Michael Hanselmann

203 b14b975f Michael Hanselmann
    @type signum: int
204 b14b975f Michael Hanselmann
    @param signum: Signal number
205 b14b975f Michael Hanselmann

206 b14b975f Michael Hanselmann
    """
207 b14b975f Michael Hanselmann
    for owner in self._signal_wait:
208 a9fe7232 Guido Trotter
      owner.OnSignal(signum)
209 821d9e43 Michael Hanselmann
210 821d9e43 Michael Hanselmann
  def RegisterSignal(self, owner):
211 821d9e43 Michael Hanselmann
    """Registers a receiver for signal notifications
212 821d9e43 Michael Hanselmann

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

215 821d9e43 Michael Hanselmann
    @type owner: instance
216 821d9e43 Michael Hanselmann
    @param owner: Receiver
217 821d9e43 Michael Hanselmann

218 821d9e43 Michael Hanselmann
    """
219 821d9e43 Michael Hanselmann
    self._signal_wait.append(owner)
220 b11c9e5c Michael Hanselmann
221 04ccf5e9 Guido Trotter
222 ff917534 Luca Bigliardi
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn,
223 ff917534 Luca Bigliardi
                console_logging=False):
224 04ccf5e9 Guido Trotter
  """Shared main function for daemons.
225 04ccf5e9 Guido Trotter

226 04ccf5e9 Guido Trotter
  @type daemon_name: string
227 04ccf5e9 Guido Trotter
  @param daemon_name: daemon name
228 69b99987 Michael Hanselmann
  @type optionparser: optparse.OptionParser
229 04ccf5e9 Guido Trotter
  @param optionparser: initialized optionparser with daemon-specific options
230 04ccf5e9 Guido Trotter
                       (common -f -d options will be handled by this module)
231 5a062513 Guido Trotter
  @type dirs: list of (string, integer)
232 5a062513 Guido Trotter
  @param dirs: list of directories that must be created if they don't exist,
233 5a062513 Guido Trotter
               and the permissions to be used to create them
234 04ccf5e9 Guido Trotter
  @type check_fn: function which accepts (options, args)
235 04ccf5e9 Guido Trotter
  @param check_fn: function that checks start conditions and exits if they're
236 04ccf5e9 Guido Trotter
                   not met
237 04ccf5e9 Guido Trotter
  @type exec_fn: function which accepts (options, args)
238 04ccf5e9 Guido Trotter
  @param exec_fn: function that's executed with the daemon's pid file held, and
239 04ccf5e9 Guido Trotter
                  runs the daemon itself.
240 ff917534 Luca Bigliardi
  @type console_logging: boolean
241 ff917534 Luca Bigliardi
  @param console_logging: if True, the daemon will fall back to the system
242 ff917534 Luca Bigliardi
                          console if logging fails
243 04ccf5e9 Guido Trotter

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