Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ 627ad739

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

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

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

160 69b99987 Michael Hanselmann
  @ivar scheduler: A sched.scheduler object, which can be used to register
161 69b99987 Michael Hanselmann
    timed events
162 69b99987 Michael Hanselmann

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

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

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

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

205 b14b975f Michael Hanselmann
    @type signum: int
206 b14b975f Michael Hanselmann
    @param signum: Signal number
207 b14b975f Michael Hanselmann

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

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

217 821d9e43 Michael Hanselmann
    @type owner: instance
218 821d9e43 Michael Hanselmann
    @param owner: Receiver
219 821d9e43 Michael Hanselmann

220 821d9e43 Michael Hanselmann
    """
221 821d9e43 Michael Hanselmann
    self._signal_wait.append(owner)
222 b11c9e5c Michael Hanselmann
223 04ccf5e9 Guido Trotter
224 30dabd03 Michael Hanselmann
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn,
225 0648750e Michael Hanselmann
                multithreaded=False,
226 0648750e Michael Hanselmann
                default_ssl_cert=None, default_ssl_key=None):
227 04ccf5e9 Guido Trotter
  """Shared main function for daemons.
228 04ccf5e9 Guido Trotter

229 04ccf5e9 Guido Trotter
  @type daemon_name: string
230 04ccf5e9 Guido Trotter
  @param daemon_name: daemon name
231 69b99987 Michael Hanselmann
  @type optionparser: optparse.OptionParser
232 04ccf5e9 Guido Trotter
  @param optionparser: initialized optionparser with daemon-specific options
233 04ccf5e9 Guido Trotter
                       (common -f -d options will be handled by this module)
234 04ccf5e9 Guido Trotter
  @type dirs: list of strings
235 04ccf5e9 Guido Trotter
  @param dirs: list of directories that must exist for this daemon to work
236 04ccf5e9 Guido Trotter
  @type check_fn: function which accepts (options, args)
237 04ccf5e9 Guido Trotter
  @param check_fn: function that checks start conditions and exits if they're
238 04ccf5e9 Guido Trotter
                   not met
239 04ccf5e9 Guido Trotter
  @type exec_fn: function which accepts (options, args)
240 04ccf5e9 Guido Trotter
  @param exec_fn: function that's executed with the daemon's pid file held, and
241 04ccf5e9 Guido Trotter
                  runs the daemon itself.
242 30dabd03 Michael Hanselmann
  @type multithreaded: bool
243 30dabd03 Michael Hanselmann
  @param multithreaded: Whether the daemon uses threads
244 0648750e Michael Hanselmann
  @type default_ssl_cert: string
245 0648750e Michael Hanselmann
  @param default_ssl_cert: Default SSL certificate path
246 0648750e Michael Hanselmann
  @type default_ssl_key: string
247 0648750e Michael Hanselmann
  @param default_ssl_key: Default SSL key path
248 04ccf5e9 Guido Trotter

249 04ccf5e9 Guido Trotter
  """
250 04ccf5e9 Guido Trotter
  optionparser.add_option("-f", "--foreground", dest="fork",
251 04ccf5e9 Guido Trotter
                          help="Don't detach from the current terminal",
252 04ccf5e9 Guido Trotter
                          default=True, action="store_false")
253 04ccf5e9 Guido Trotter
  optionparser.add_option("-d", "--debug", dest="debug",
254 04ccf5e9 Guido Trotter
                          help="Enable some debug messages",
255 04ccf5e9 Guido Trotter
                          default=False, action="store_true")
256 0a71aa17 Michael Hanselmann
257 04ccf5e9 Guido Trotter
  if daemon_name in constants.DAEMONS_PORTS:
258 0a71aa17 Michael Hanselmann
    default_bind_address = "0.0.0.0"
259 0a71aa17 Michael Hanselmann
    default_port = utils.GetDaemonPort(daemon_name)
260 0a71aa17 Michael Hanselmann
261 0a71aa17 Michael Hanselmann
    # For networked daemons we allow choosing the port and bind address
262 04ccf5e9 Guido Trotter
    optionparser.add_option("-p", "--port", dest="port",
263 0a71aa17 Michael Hanselmann
                            help="Network port (default: %s)" % default_port,
264 0a71aa17 Michael Hanselmann
                            default=default_port, type="int")
265 04ccf5e9 Guido Trotter
    optionparser.add_option("-b", "--bind", dest="bind_address",
266 0a71aa17 Michael Hanselmann
                            help=("Bind address (default: %s)" %
267 0a71aa17 Michael Hanselmann
                                  default_bind_address),
268 0a71aa17 Michael Hanselmann
                            default=default_bind_address, metavar="ADDRESS")
269 04ccf5e9 Guido Trotter
270 0648750e Michael Hanselmann
  if default_ssl_key is not None and default_ssl_cert is not None:
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 0648750e Michael Hanselmann
                            help=("SSL key path (default: %s)" %
276 0648750e Michael Hanselmann
                                  default_ssl_key),
277 0648750e Michael Hanselmann
                            default=default_ssl_key, type="string",
278 0648750e Michael Hanselmann
                            metavar="SSL_KEY_PATH")
279 3b1b0cb6 Guido Trotter
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
280 0648750e Michael Hanselmann
                            help=("SSL certificate path (default: %s)" %
281 0648750e Michael Hanselmann
                                  default_ssl_cert),
282 0648750e Michael Hanselmann
                            default=default_ssl_cert, type="string",
283 0648750e Michael Hanselmann
                            metavar="SSL_CERT_PATH")
284 3b1b0cb6 Guido Trotter
285 30dabd03 Michael Hanselmann
  # Disable the use of fork(2) if the daemon uses threads
286 30dabd03 Michael Hanselmann
  utils.no_fork = multithreaded
287 04ccf5e9 Guido Trotter
288 04ccf5e9 Guido Trotter
  options, args = optionparser.parse_args()
289 04ccf5e9 Guido Trotter
290 0648750e Michael Hanselmann
  if getattr(options, "ssl", False):
291 0648750e Michael Hanselmann
    ssl_paths = {
292 0648750e Michael Hanselmann
      "certificate": options.ssl_cert,
293 0648750e Michael Hanselmann
      "key": options.ssl_key,
294 0648750e Michael Hanselmann
      }
295 0648750e Michael Hanselmann
296 0648750e Michael Hanselmann
    for name, path in ssl_paths.iteritems():
297 0648750e Michael Hanselmann
      if not os.path.isfile(path):
298 0648750e Michael Hanselmann
        print >> sys.stderr, "SSL %s file '%s' was not found" % (name, path)
299 3b1b0cb6 Guido Trotter
        sys.exit(constants.EXIT_FAILURE)
300 3b1b0cb6 Guido Trotter
301 0648750e Michael Hanselmann
    # TODO: By initiating http.HttpSslParams here we would only read the files
302 0648750e Michael Hanselmann
    # once and have a proper validation (isfile returns False on directories)
303 0648750e Michael Hanselmann
    # at the same time.
304 0648750e Michael Hanselmann
305 3b1b0cb6 Guido Trotter
  if check_fn is not None:
306 3b1b0cb6 Guido Trotter
    check_fn(options, args)
307 3b1b0cb6 Guido Trotter
308 04ccf5e9 Guido Trotter
  utils.EnsureDirs(dirs)
309 04ccf5e9 Guido Trotter
310 04ccf5e9 Guido Trotter
  if options.fork:
311 04ccf5e9 Guido Trotter
    utils.CloseFDs()
312 04ccf5e9 Guido Trotter
    utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
313 04ccf5e9 Guido Trotter
314 04ccf5e9 Guido Trotter
  utils.WritePidFile(daemon_name)
315 04ccf5e9 Guido Trotter
  try:
316 04ccf5e9 Guido Trotter
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
317 04ccf5e9 Guido Trotter
                       debug=options.debug,
318 04ccf5e9 Guido Trotter
                       stderr_logging=not options.fork,
319 30dabd03 Michael Hanselmann
                       multithreaded=multithreaded)
320 099c52ad Iustin Pop
    logging.info("%s daemon startup", daemon_name)
321 04ccf5e9 Guido Trotter
    exec_fn(options, args)
322 04ccf5e9 Guido Trotter
  finally:
323 04ccf5e9 Guido Trotter
    utils.RemovePidFile(daemon_name)