Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ adb6d685

History | View | Annotate | Download (18.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 b66ab629 Guido Trotter
import asynchat
27 743b53d4 René Nussbaumer
import grp
28 3b1b0cb6 Guido Trotter
import os
29 743b53d4 René Nussbaumer
import pwd
30 821d9e43 Michael Hanselmann
import signal
31 04ccf5e9 Guido Trotter
import logging
32 a02b89cf Guido Trotter
import sched
33 a02b89cf Guido Trotter
import time
34 5f3269fc Guido Trotter
import socket
35 6ddf5c8f Guido Trotter
import select
36 c124045f Iustin Pop
import sys
37 821d9e43 Michael Hanselmann
38 821d9e43 Michael Hanselmann
from ganeti import utils
39 04ccf5e9 Guido Trotter
from ganeti import constants
40 a02b89cf Guido Trotter
from ganeti import errors
41 a02b89cf Guido Trotter
42 a02b89cf Guido Trotter
43 743b53d4 René Nussbaumer
_DEFAULT_RUN_USER = "root"
44 743b53d4 René Nussbaumer
_DEFAULT_RUN_GROUP = "root"
45 743b53d4 René Nussbaumer
46 743b53d4 René Nussbaumer
47 a02b89cf Guido Trotter
class SchedulerBreakout(Exception):
48 a02b89cf Guido Trotter
  """Exception used to get out of the scheduler loop
49 a02b89cf Guido Trotter

50 a02b89cf Guido Trotter
  """
51 a02b89cf Guido Trotter
52 a02b89cf Guido Trotter
53 a02b89cf Guido Trotter
def AsyncoreDelayFunction(timeout):
54 a02b89cf Guido Trotter
  """Asyncore-compatible scheduler delay function.
55 a02b89cf Guido Trotter

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

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

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

69 a02b89cf Guido Trotter
  """
70 a02b89cf Guido Trotter
  asyncore.loop(timeout=timeout, count=1, use_poll=True)
71 a02b89cf Guido Trotter
  raise SchedulerBreakout()
72 a02b89cf Guido Trotter
73 a02b89cf Guido Trotter
74 a02b89cf Guido Trotter
class AsyncoreScheduler(sched.scheduler):
75 a02b89cf Guido Trotter
  """Event scheduler integrated with asyncore
76 a02b89cf Guido Trotter

77 a02b89cf Guido Trotter
  """
78 a02b89cf Guido Trotter
  def __init__(self, timefunc):
79 a02b89cf Guido Trotter
    sched.scheduler.__init__(self, timefunc, AsyncoreDelayFunction)
80 821d9e43 Michael Hanselmann
81 821d9e43 Michael Hanselmann
82 b11780bb Guido Trotter
class GanetiBaseAsyncoreDispatcher(asyncore.dispatcher):
83 b11780bb Guido Trotter
  """Base Ganeti Asyncore Dispacher
84 b11780bb Guido Trotter

85 b11780bb Guido Trotter
  """
86 b11780bb Guido Trotter
  # this method is overriding an asyncore.dispatcher method
87 b11780bb Guido Trotter
  def handle_error(self):
88 b11780bb Guido Trotter
    """Log an error in handling any request, and proceed.
89 b11780bb Guido Trotter

90 b11780bb Guido Trotter
    """
91 b11780bb Guido Trotter
    logging.exception("Error while handling asyncore request")
92 b11780bb Guido Trotter
93 b11780bb Guido Trotter
  # this method is overriding an asyncore.dispatcher method
94 b11780bb Guido Trotter
  def writable(self):
95 b11780bb Guido Trotter
    """Most of the time we don't want to check for writability.
96 b11780bb Guido Trotter

97 b11780bb Guido Trotter
    """
98 b11780bb Guido Trotter
    return False
99 b11780bb Guido Trotter
100 b11780bb Guido Trotter
101 a4b605ae Guido Trotter
def FormatAddress(family, address):
102 a4b605ae Guido Trotter
  """Format a client's address
103 a4b605ae Guido Trotter

104 a4b605ae Guido Trotter
  @type family: integer
105 a4b605ae Guido Trotter
  @param family: socket family (one of socket.AF_*)
106 a4b605ae Guido Trotter
  @type address: family specific (usually tuple)
107 a4b605ae Guido Trotter
  @param address: address, as reported by this class
108 a4b605ae Guido Trotter

109 a4b605ae Guido Trotter
  """
110 a4b605ae Guido Trotter
  if family == socket.AF_INET and len(address) == 2:
111 a4b605ae Guido Trotter
    return "%s:%d" % address
112 a4b605ae Guido Trotter
  elif family == socket.AF_UNIX and len(address) == 3:
113 a4b605ae Guido Trotter
    return "pid=%s, uid=%s, gid=%s" % address
114 a4b605ae Guido Trotter
  else:
115 a4b605ae Guido Trotter
    return str(address)
116 a4b605ae Guido Trotter
117 a4b605ae Guido Trotter
118 a4b605ae Guido Trotter
class AsyncStreamServer(GanetiBaseAsyncoreDispatcher):
119 a4b605ae Guido Trotter
  """A stream server to use with asyncore.
120 a4b605ae Guido Trotter

121 a4b605ae Guido Trotter
  Each request is accepted, and then dispatched to a separate asyncore
122 a4b605ae Guido Trotter
  dispatcher to handle.
123 a4b605ae Guido Trotter

124 a4b605ae Guido Trotter
  """
125 a4b605ae Guido Trotter
126 a4b605ae Guido Trotter
  _REQUEST_QUEUE_SIZE = 5
127 a4b605ae Guido Trotter
128 a4b605ae Guido Trotter
  def __init__(self, family, address):
129 a4b605ae Guido Trotter
    """Constructor for AsyncUnixStreamSocket
130 a4b605ae Guido Trotter

131 a4b605ae Guido Trotter
    @type family: integer
132 a4b605ae Guido Trotter
    @param family: socket family (one of socket.AF_*)
133 a4b605ae Guido Trotter
    @type address: address family dependent
134 a4b605ae Guido Trotter
    @param address: address to bind the socket to
135 a4b605ae Guido Trotter

136 a4b605ae Guido Trotter
    """
137 a4b605ae Guido Trotter
    GanetiBaseAsyncoreDispatcher.__init__(self)
138 a4b605ae Guido Trotter
    self.family = family
139 a4b605ae Guido Trotter
    self.create_socket(self.family, socket.SOCK_STREAM)
140 a4b605ae Guido Trotter
    self.set_reuse_addr()
141 a4b605ae Guido Trotter
    self.bind(address)
142 a4b605ae Guido Trotter
    self.listen(self._REQUEST_QUEUE_SIZE)
143 a4b605ae Guido Trotter
144 a4b605ae Guido Trotter
  # this method is overriding an asyncore.dispatcher method
145 a4b605ae Guido Trotter
  def handle_accept(self):
146 a4b605ae Guido Trotter
    """Accept a new client connection.
147 a4b605ae Guido Trotter

148 a4b605ae Guido Trotter
    Creates a new instance of the handler class, which will use asyncore to
149 a4b605ae Guido Trotter
    serve the client.
150 a4b605ae Guido Trotter

151 a4b605ae Guido Trotter
    """
152 a4b605ae Guido Trotter
    accept_result = utils.IgnoreSignals(self.accept)
153 a4b605ae Guido Trotter
    if accept_result is not None:
154 a4b605ae Guido Trotter
      connected_socket, client_address = accept_result
155 a4b605ae Guido Trotter
      if self.family == socket.AF_UNIX:
156 a4b605ae Guido Trotter
        # override the client address, as for unix sockets nothing meaningful
157 a4b605ae Guido Trotter
        # is passed in from accept anyway
158 a4b605ae Guido Trotter
        client_address = utils.GetSocketCredentials(connected_socket)
159 a4b605ae Guido Trotter
      logging.info("Accepted connection from %s",
160 a4b605ae Guido Trotter
                   FormatAddress(self.family, client_address))
161 a4b605ae Guido Trotter
      self.handle_connection(connected_socket, client_address)
162 a4b605ae Guido Trotter
163 a4b605ae Guido Trotter
  def handle_connection(self, connected_socket, client_address):
164 a4b605ae Guido Trotter
    """Handle an already accepted connection.
165 a4b605ae Guido Trotter

166 a4b605ae Guido Trotter
    """
167 a4b605ae Guido Trotter
    raise NotImplementedError
168 a4b605ae Guido Trotter
169 a4b605ae Guido Trotter
170 b66ab629 Guido Trotter
class AsyncTerminatedMessageStream(asynchat.async_chat):
171 b66ab629 Guido Trotter
  """A terminator separated message stream asyncore module.
172 b66ab629 Guido Trotter

173 b66ab629 Guido Trotter
  Handles a stream connection receiving messages terminated by a defined
174 b66ab629 Guido Trotter
  separator. For each complete message handle_message is called.
175 b66ab629 Guido Trotter

176 b66ab629 Guido Trotter
  """
177 b66ab629 Guido Trotter
  def __init__(self, connected_socket, peer_address, terminator, family):
178 b66ab629 Guido Trotter
    """AsyncTerminatedMessageStream constructor.
179 b66ab629 Guido Trotter

180 b66ab629 Guido Trotter
    @type connected_socket: socket.socket
181 b66ab629 Guido Trotter
    @param connected_socket: connected stream socket to receive messages from
182 b66ab629 Guido Trotter
    @param peer_address: family-specific peer address
183 b66ab629 Guido Trotter
    @type terminator: string
184 b66ab629 Guido Trotter
    @param terminator: terminator separating messages in the stream
185 b66ab629 Guido Trotter
    @type family: integer
186 b66ab629 Guido Trotter
    @param family: socket family
187 b66ab629 Guido Trotter

188 b66ab629 Guido Trotter
    """
189 b66ab629 Guido Trotter
    # python 2.4/2.5 uses conn=... while 2.6 has sock=... we have to cheat by
190 b66ab629 Guido Trotter
    # using a positional argument rather than a keyword one.
191 b66ab629 Guido Trotter
    asynchat.async_chat.__init__(self, connected_socket)
192 b66ab629 Guido Trotter
    self.connected_socket = connected_socket
193 b66ab629 Guido Trotter
    # on python 2.4 there is no "family" attribute for the socket class
194 b66ab629 Guido Trotter
    # FIXME: when we move to python 2.5 or above remove the family parameter
195 b66ab629 Guido Trotter
    #self.family = self.connected_socket.family
196 b66ab629 Guido Trotter
    self.family = family
197 b66ab629 Guido Trotter
    self.peer_address = peer_address
198 b66ab629 Guido Trotter
    self.terminator = terminator
199 b66ab629 Guido Trotter
    self.set_terminator(terminator)
200 b66ab629 Guido Trotter
    self.ibuffer = []
201 b66ab629 Guido Trotter
    self.next_incoming_message = 0
202 b66ab629 Guido Trotter
203 b66ab629 Guido Trotter
  # this method is overriding an asynchat.async_chat method
204 b66ab629 Guido Trotter
  def collect_incoming_data(self, data):
205 b66ab629 Guido Trotter
    self.ibuffer.append(data)
206 b66ab629 Guido Trotter
207 b66ab629 Guido Trotter
  # this method is overriding an asynchat.async_chat method
208 b66ab629 Guido Trotter
  def found_terminator(self):
209 b66ab629 Guido Trotter
    message = "".join(self.ibuffer)
210 b66ab629 Guido Trotter
    self.ibuffer = []
211 b66ab629 Guido Trotter
    message_id = self.next_incoming_message
212 b66ab629 Guido Trotter
    self.next_incoming_message += 1
213 b66ab629 Guido Trotter
    self.handle_message(message, message_id)
214 b66ab629 Guido Trotter
215 b66ab629 Guido Trotter
  def handle_message(self, message, message_id):
216 b66ab629 Guido Trotter
    """Handle a terminated message.
217 b66ab629 Guido Trotter

218 b66ab629 Guido Trotter
    @type message: string
219 b66ab629 Guido Trotter
    @param message: message to handle
220 b66ab629 Guido Trotter
    @type message_id: integer
221 b66ab629 Guido Trotter
    @param message_id: stream's message sequence number
222 b66ab629 Guido Trotter

223 b66ab629 Guido Trotter
    """
224 b66ab629 Guido Trotter
    pass
225 b66ab629 Guido Trotter
    # TODO: move this method to raise NotImplementedError
226 b66ab629 Guido Trotter
    # raise NotImplementedError
227 b66ab629 Guido Trotter
228 b66ab629 Guido Trotter
  def close_log(self):
229 b66ab629 Guido Trotter
    logging.info("Closing connection from %s",
230 b66ab629 Guido Trotter
                 FormatAddress(self.family, self.peer_address))
231 b66ab629 Guido Trotter
    self.close()
232 b66ab629 Guido Trotter
233 b66ab629 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
234 b66ab629 Guido Trotter
  def handle_expt(self):
235 b66ab629 Guido Trotter
    self.close_log()
236 b66ab629 Guido Trotter
237 b66ab629 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
238 b66ab629 Guido Trotter
  def handle_error(self):
239 b66ab629 Guido Trotter
    """Log an error in handling any request, and proceed.
240 b66ab629 Guido Trotter

241 b66ab629 Guido Trotter
    """
242 b66ab629 Guido Trotter
    logging.exception("Error while handling asyncore request")
243 b66ab629 Guido Trotter
    self.close_log()
244 b66ab629 Guido Trotter
245 b66ab629 Guido Trotter
246 b11780bb Guido Trotter
class AsyncUDPSocket(GanetiBaseAsyncoreDispatcher):
247 5f3269fc Guido Trotter
  """An improved asyncore udp socket.
248 5f3269fc Guido Trotter

249 5f3269fc Guido Trotter
  """
250 5f3269fc Guido Trotter
  def __init__(self):
251 5f3269fc Guido Trotter
    """Constructor for AsyncUDPSocket
252 5f3269fc Guido Trotter

253 5f3269fc Guido Trotter
    """
254 b11780bb Guido Trotter
    GanetiBaseAsyncoreDispatcher.__init__(self)
255 5f3269fc Guido Trotter
    self._out_queue = []
256 5f3269fc Guido Trotter
    self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
257 5f3269fc Guido Trotter
258 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
259 5f3269fc Guido Trotter
  def handle_connect(self):
260 5f3269fc Guido Trotter
    # Python thinks that the first udp message from a source qualifies as a
261 5f3269fc Guido Trotter
    # "connect" and further ones are part of the same connection. We beg to
262 5f3269fc Guido Trotter
    # differ and treat all messages equally.
263 5f3269fc Guido Trotter
    pass
264 5f3269fc Guido Trotter
265 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
266 5f3269fc Guido Trotter
  def handle_read(self):
267 6e7e58b4 Guido Trotter
    recv_result = utils.IgnoreSignals(self.recvfrom,
268 6e7e58b4 Guido Trotter
                                      constants.MAX_UDP_DATA_SIZE)
269 6e7e58b4 Guido Trotter
    if recv_result is not None:
270 6e7e58b4 Guido Trotter
      payload, address = recv_result
271 6e7e58b4 Guido Trotter
      ip, port = address
272 6e7e58b4 Guido Trotter
      self.handle_datagram(payload, ip, port)
273 5f3269fc Guido Trotter
274 5f3269fc Guido Trotter
  def handle_datagram(self, payload, ip, port):
275 5f3269fc Guido Trotter
    """Handle an already read udp datagram
276 5f3269fc Guido Trotter

277 5f3269fc Guido Trotter
    """
278 5f3269fc Guido Trotter
    raise NotImplementedError
279 5f3269fc Guido Trotter
280 5f3269fc Guido Trotter
  # this method is overriding an asyncore.dispatcher method
281 5f3269fc Guido Trotter
  def writable(self):
282 5f3269fc Guido Trotter
    # We should check whether we can write to the socket only if we have
283 5f3269fc Guido Trotter
    # something scheduled to be written
284 5f3269fc Guido Trotter
    return bool(self._out_queue)
285 5f3269fc Guido Trotter
286 48bf6352 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
287 5f3269fc Guido Trotter
  def handle_write(self):
288 3660fcf5 Guido Trotter
    if not self._out_queue:
289 3660fcf5 Guido Trotter
      logging.error("handle_write called with empty output queue")
290 3660fcf5 Guido Trotter
      return
291 3660fcf5 Guido Trotter
    (ip, port, payload) = self._out_queue[0]
292 232144d0 Guido Trotter
    utils.IgnoreSignals(self.sendto, payload, 0, (ip, port))
293 3660fcf5 Guido Trotter
    self._out_queue.pop(0)
294 3660fcf5 Guido Trotter
295 5f3269fc Guido Trotter
  def enqueue_send(self, ip, port, payload):
296 5f3269fc Guido Trotter
    """Enqueue a datagram to be sent when possible
297 5f3269fc Guido Trotter

298 5f3269fc Guido Trotter
    """
299 c8eded0b Guido Trotter
    if len(payload) > constants.MAX_UDP_DATA_SIZE:
300 c8eded0b Guido Trotter
      raise errors.UdpDataSizeError('Packet too big: %s > %s' % (len(payload),
301 c8eded0b Guido Trotter
                                    constants.MAX_UDP_DATA_SIZE))
302 5f3269fc Guido Trotter
    self._out_queue.append((ip, port, payload))
303 5f3269fc Guido Trotter
304 6ddf5c8f Guido Trotter
  def process_next_packet(self, timeout=0):
305 6ddf5c8f Guido Trotter
    """Process the next datagram, waiting for it if necessary.
306 6ddf5c8f Guido Trotter

307 6ddf5c8f Guido Trotter
    @type timeout: float
308 6ddf5c8f Guido Trotter
    @param timeout: how long to wait for data
309 6ddf5c8f Guido Trotter
    @rtype: boolean
310 6ddf5c8f Guido Trotter
    @return: True if some data has been handled, False otherwise
311 6ddf5c8f Guido Trotter

312 6ddf5c8f Guido Trotter
    """
313 1b429e2a Iustin Pop
    result = utils.WaitForFdCondition(self, select.POLLIN, timeout)
314 1b429e2a Iustin Pop
    if result is not None and result & select.POLLIN:
315 3660fcf5 Guido Trotter
      self.handle_read()
316 6ddf5c8f Guido Trotter
      return True
317 6ddf5c8f Guido Trotter
    else:
318 6ddf5c8f Guido Trotter
      return False
319 6ddf5c8f Guido Trotter
320 5f3269fc Guido Trotter
321 495ba852 Guido Trotter
class AsyncAwaker(GanetiBaseAsyncoreDispatcher):
322 495ba852 Guido Trotter
  """A way to notify the asyncore loop that something is going on.
323 495ba852 Guido Trotter

324 495ba852 Guido Trotter
  If an asyncore daemon is multithreaded when a thread tries to push some data
325 495ba852 Guido Trotter
  to a socket, the main loop handling asynchronous requests might be sleeping
326 495ba852 Guido Trotter
  waiting on a select(). To avoid this it can create an instance of the
327 495ba852 Guido Trotter
  AsyncAwaker, which other threads can use to wake it up.
328 495ba852 Guido Trotter

329 495ba852 Guido Trotter
  """
330 495ba852 Guido Trotter
  def __init__(self, signal_fn=None):
331 495ba852 Guido Trotter
    """Constructor for AsyncAwaker
332 495ba852 Guido Trotter

333 495ba852 Guido Trotter
    @type signal_fn: function
334 495ba852 Guido Trotter
    @param signal_fn: function to call when awaken
335 495ba852 Guido Trotter

336 495ba852 Guido Trotter
    """
337 495ba852 Guido Trotter
    GanetiBaseAsyncoreDispatcher.__init__(self)
338 495ba852 Guido Trotter
    assert signal_fn == None or callable(signal_fn)
339 495ba852 Guido Trotter
    (self.in_socket, self.out_socket) = socket.socketpair(socket.AF_UNIX,
340 495ba852 Guido Trotter
                                                          socket.SOCK_STREAM)
341 495ba852 Guido Trotter
    self.in_socket.setblocking(0)
342 b628191f Guido Trotter
    self.in_socket.shutdown(socket.SHUT_WR)
343 b628191f Guido Trotter
    self.out_socket.shutdown(socket.SHUT_RD)
344 495ba852 Guido Trotter
    self.set_socket(self.in_socket)
345 495ba852 Guido Trotter
    self.need_signal = True
346 495ba852 Guido Trotter
    self.signal_fn = signal_fn
347 495ba852 Guido Trotter
    self.connected = True
348 495ba852 Guido Trotter
349 495ba852 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
350 495ba852 Guido Trotter
  def handle_read(self):
351 495ba852 Guido Trotter
    utils.IgnoreSignals(self.recv, 4096)
352 495ba852 Guido Trotter
    if self.signal_fn:
353 495ba852 Guido Trotter
      self.signal_fn()
354 495ba852 Guido Trotter
    self.need_signal = True
355 495ba852 Guido Trotter
356 495ba852 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
357 495ba852 Guido Trotter
  def close(self):
358 495ba852 Guido Trotter
    asyncore.dispatcher.close(self)
359 495ba852 Guido Trotter
    self.out_socket.close()
360 495ba852 Guido Trotter
361 495ba852 Guido Trotter
  def signal(self):
362 495ba852 Guido Trotter
    """Signal the asyncore main loop.
363 495ba852 Guido Trotter

364 495ba852 Guido Trotter
    Any data we send here will be ignored, but it will cause the select() call
365 495ba852 Guido Trotter
    to return.
366 495ba852 Guido Trotter

367 495ba852 Guido Trotter
    """
368 495ba852 Guido Trotter
    # Yes, there is a race condition here. No, we don't care, at worst we're
369 495ba852 Guido Trotter
    # sending more than one wakeup token, which doesn't harm at all.
370 495ba852 Guido Trotter
    if self.need_signal:
371 495ba852 Guido Trotter
      self.need_signal = False
372 495ba852 Guido Trotter
      self.out_socket.send("\0")
373 495ba852 Guido Trotter
374 495ba852 Guido Trotter
375 821d9e43 Michael Hanselmann
class Mainloop(object):
376 821d9e43 Michael Hanselmann
  """Generic mainloop for daemons
377 821d9e43 Michael Hanselmann

378 69b99987 Michael Hanselmann
  @ivar scheduler: A sched.scheduler object, which can be used to register
379 69b99987 Michael Hanselmann
    timed events
380 69b99987 Michael Hanselmann

381 821d9e43 Michael Hanselmann
  """
382 821d9e43 Michael Hanselmann
  def __init__(self):
383 b14b975f Michael Hanselmann
    """Constructs a new Mainloop instance.
384 b14b975f Michael Hanselmann

385 b14b975f Michael Hanselmann
    """
386 821d9e43 Michael Hanselmann
    self._signal_wait = []
387 a02b89cf Guido Trotter
    self.scheduler = AsyncoreScheduler(time.time)
388 821d9e43 Michael Hanselmann
389 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGCHLD])
390 9b739173 Guido Trotter
  @utils.SignalHandled([signal.SIGTERM])
391 f59dce3e Guido Trotter
  @utils.SignalHandled([signal.SIGINT])
392 69b99987 Michael Hanselmann
  def Run(self, signal_handlers=None):
393 b14b975f Michael Hanselmann
    """Runs the mainloop.
394 b14b975f Michael Hanselmann

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

398 b14b975f Michael Hanselmann
    """
399 9b739173 Guido Trotter
    assert isinstance(signal_handlers, dict) and \
400 9b739173 Guido Trotter
           len(signal_handlers) > 0, \
401 9b739173 Guido Trotter
           "Broken SignalHandled decorator"
402 9b739173 Guido Trotter
    running = True
403 9b739173 Guido Trotter
    # Start actual main loop
404 9b739173 Guido Trotter
    while running:
405 a02b89cf Guido Trotter
      if not self.scheduler.empty():
406 a02b89cf Guido Trotter
        try:
407 a02b89cf Guido Trotter
          self.scheduler.run()
408 a02b89cf Guido Trotter
        except SchedulerBreakout:
409 a02b89cf Guido Trotter
          pass
410 a02b89cf Guido Trotter
      else:
411 a02b89cf Guido Trotter
        asyncore.loop(count=1, use_poll=True)
412 9b739173 Guido Trotter
413 9b739173 Guido Trotter
      # Check whether a signal was raised
414 9b739173 Guido Trotter
      for sig in signal_handlers:
415 9b739173 Guido Trotter
        handler = signal_handlers[sig]
416 9b739173 Guido Trotter
        if handler.called:
417 9b739173 Guido Trotter
          self._CallSignalWaiters(sig)
418 f59dce3e Guido Trotter
          running = sig not in (signal.SIGTERM, signal.SIGINT)
419 9b739173 Guido Trotter
          handler.Clear()
420 a570e2a8 Guido Trotter
421 b14b975f Michael Hanselmann
  def _CallSignalWaiters(self, signum):
422 b14b975f Michael Hanselmann
    """Calls all signal waiters for a certain signal.
423 b14b975f Michael Hanselmann

424 b14b975f Michael Hanselmann
    @type signum: int
425 b14b975f Michael Hanselmann
    @param signum: Signal number
426 b14b975f Michael Hanselmann

427 b14b975f Michael Hanselmann
    """
428 b14b975f Michael Hanselmann
    for owner in self._signal_wait:
429 a9fe7232 Guido Trotter
      owner.OnSignal(signum)
430 821d9e43 Michael Hanselmann
431 821d9e43 Michael Hanselmann
  def RegisterSignal(self, owner):
432 821d9e43 Michael Hanselmann
    """Registers a receiver for signal notifications
433 821d9e43 Michael Hanselmann

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

436 821d9e43 Michael Hanselmann
    @type owner: instance
437 821d9e43 Michael Hanselmann
    @param owner: Receiver
438 821d9e43 Michael Hanselmann

439 821d9e43 Michael Hanselmann
    """
440 821d9e43 Michael Hanselmann
    self._signal_wait.append(owner)
441 b11c9e5c Michael Hanselmann
442 04ccf5e9 Guido Trotter
443 30dabd03 Michael Hanselmann
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn,
444 1c54156d Luca Bigliardi
                multithreaded=False, console_logging=False,
445 743b53d4 René Nussbaumer
                default_ssl_cert=None, default_ssl_key=None,
446 743b53d4 René Nussbaumer
                user=_DEFAULT_RUN_USER, group=_DEFAULT_RUN_GROUP):
447 04ccf5e9 Guido Trotter
  """Shared main function for daemons.
448 04ccf5e9 Guido Trotter

449 04ccf5e9 Guido Trotter
  @type daemon_name: string
450 04ccf5e9 Guido Trotter
  @param daemon_name: daemon name
451 69b99987 Michael Hanselmann
  @type optionparser: optparse.OptionParser
452 04ccf5e9 Guido Trotter
  @param optionparser: initialized optionparser with daemon-specific options
453 04ccf5e9 Guido Trotter
                       (common -f -d options will be handled by this module)
454 5a062513 Guido Trotter
  @type dirs: list of (string, integer)
455 5a062513 Guido Trotter
  @param dirs: list of directories that must be created if they don't exist,
456 5a062513 Guido Trotter
               and the permissions to be used to create them
457 04ccf5e9 Guido Trotter
  @type check_fn: function which accepts (options, args)
458 04ccf5e9 Guido Trotter
  @param check_fn: function that checks start conditions and exits if they're
459 04ccf5e9 Guido Trotter
                   not met
460 04ccf5e9 Guido Trotter
  @type exec_fn: function which accepts (options, args)
461 04ccf5e9 Guido Trotter
  @param exec_fn: function that's executed with the daemon's pid file held, and
462 04ccf5e9 Guido Trotter
                  runs the daemon itself.
463 30dabd03 Michael Hanselmann
  @type multithreaded: bool
464 30dabd03 Michael Hanselmann
  @param multithreaded: Whether the daemon uses threads
465 ff917534 Luca Bigliardi
  @type console_logging: boolean
466 ff917534 Luca Bigliardi
  @param console_logging: if True, the daemon will fall back to the system
467 ff917534 Luca Bigliardi
                          console if logging fails
468 0648750e Michael Hanselmann
  @type default_ssl_cert: string
469 0648750e Michael Hanselmann
  @param default_ssl_cert: Default SSL certificate path
470 0648750e Michael Hanselmann
  @type default_ssl_key: string
471 0648750e Michael Hanselmann
  @param default_ssl_key: Default SSL key path
472 743b53d4 René Nussbaumer
  @param user: Default user to run as
473 743b53d4 René Nussbaumer
  @type user: string
474 743b53d4 René Nussbaumer
  @param group: Default group to run as
475 743b53d4 René Nussbaumer
  @type group: string
476 04ccf5e9 Guido Trotter

477 04ccf5e9 Guido Trotter
  """
478 04ccf5e9 Guido Trotter
  optionparser.add_option("-f", "--foreground", dest="fork",
479 04ccf5e9 Guido Trotter
                          help="Don't detach from the current terminal",
480 04ccf5e9 Guido Trotter
                          default=True, action="store_false")
481 04ccf5e9 Guido Trotter
  optionparser.add_option("-d", "--debug", dest="debug",
482 04ccf5e9 Guido Trotter
                          help="Enable some debug messages",
483 04ccf5e9 Guido Trotter
                          default=False, action="store_true")
484 551b6283 Iustin Pop
  optionparser.add_option("--syslog", dest="syslog",
485 551b6283 Iustin Pop
                          help="Enable logging to syslog (except debug"
486 551b6283 Iustin Pop
                          " messages); one of 'no', 'yes' or 'only' [%s]" %
487 551b6283 Iustin Pop
                          constants.SYSLOG_USAGE,
488 551b6283 Iustin Pop
                          default=constants.SYSLOG_USAGE,
489 551b6283 Iustin Pop
                          choices=["no", "yes", "only"])
490 0a71aa17 Michael Hanselmann
491 04ccf5e9 Guido Trotter
  if daemon_name in constants.DAEMONS_PORTS:
492 0a71aa17 Michael Hanselmann
    default_bind_address = "0.0.0.0"
493 0a71aa17 Michael Hanselmann
    default_port = utils.GetDaemonPort(daemon_name)
494 0a71aa17 Michael Hanselmann
495 0a71aa17 Michael Hanselmann
    # For networked daemons we allow choosing the port and bind address
496 04ccf5e9 Guido Trotter
    optionparser.add_option("-p", "--port", dest="port",
497 0a71aa17 Michael Hanselmann
                            help="Network port (default: %s)" % default_port,
498 0a71aa17 Michael Hanselmann
                            default=default_port, type="int")
499 04ccf5e9 Guido Trotter
    optionparser.add_option("-b", "--bind", dest="bind_address",
500 0a71aa17 Michael Hanselmann
                            help=("Bind address (default: %s)" %
501 0a71aa17 Michael Hanselmann
                                  default_bind_address),
502 0a71aa17 Michael Hanselmann
                            default=default_bind_address, metavar="ADDRESS")
503 04ccf5e9 Guido Trotter
504 0648750e Michael Hanselmann
  if default_ssl_key is not None and default_ssl_cert is not None:
505 3b1b0cb6 Guido Trotter
    optionparser.add_option("--no-ssl", dest="ssl",
506 3b1b0cb6 Guido Trotter
                            help="Do not secure HTTP protocol with SSL",
507 3b1b0cb6 Guido Trotter
                            default=True, action="store_false")
508 3b1b0cb6 Guido Trotter
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
509 0648750e Michael Hanselmann
                            help=("SSL key path (default: %s)" %
510 0648750e Michael Hanselmann
                                  default_ssl_key),
511 0648750e Michael Hanselmann
                            default=default_ssl_key, type="string",
512 0648750e Michael Hanselmann
                            metavar="SSL_KEY_PATH")
513 3b1b0cb6 Guido Trotter
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
514 0648750e Michael Hanselmann
                            help=("SSL certificate path (default: %s)" %
515 0648750e Michael Hanselmann
                                  default_ssl_cert),
516 0648750e Michael Hanselmann
                            default=default_ssl_cert, type="string",
517 0648750e Michael Hanselmann
                            metavar="SSL_CERT_PATH")
518 3b1b0cb6 Guido Trotter
519 30dabd03 Michael Hanselmann
  # Disable the use of fork(2) if the daemon uses threads
520 30dabd03 Michael Hanselmann
  utils.no_fork = multithreaded
521 04ccf5e9 Guido Trotter
522 04ccf5e9 Guido Trotter
  options, args = optionparser.parse_args()
523 04ccf5e9 Guido Trotter
524 0648750e Michael Hanselmann
  if getattr(options, "ssl", False):
525 0648750e Michael Hanselmann
    ssl_paths = {
526 0648750e Michael Hanselmann
      "certificate": options.ssl_cert,
527 0648750e Michael Hanselmann
      "key": options.ssl_key,
528 0648750e Michael Hanselmann
      }
529 0648750e Michael Hanselmann
530 0648750e Michael Hanselmann
    for name, path in ssl_paths.iteritems():
531 0648750e Michael Hanselmann
      if not os.path.isfile(path):
532 0648750e Michael Hanselmann
        print >> sys.stderr, "SSL %s file '%s' was not found" % (name, path)
533 3b1b0cb6 Guido Trotter
        sys.exit(constants.EXIT_FAILURE)
534 3b1b0cb6 Guido Trotter
535 0648750e Michael Hanselmann
    # TODO: By initiating http.HttpSslParams here we would only read the files
536 0648750e Michael Hanselmann
    # once and have a proper validation (isfile returns False on directories)
537 0648750e Michael Hanselmann
    # at the same time.
538 0648750e Michael Hanselmann
539 3b1b0cb6 Guido Trotter
  if check_fn is not None:
540 3b1b0cb6 Guido Trotter
    check_fn(options, args)
541 3b1b0cb6 Guido Trotter
542 04ccf5e9 Guido Trotter
  utils.EnsureDirs(dirs)
543 04ccf5e9 Guido Trotter
544 04ccf5e9 Guido Trotter
  if options.fork:
545 743b53d4 René Nussbaumer
    try:
546 743b53d4 René Nussbaumer
      uid = pwd.getpwnam(user).pw_uid
547 743b53d4 René Nussbaumer
      gid = grp.getgrnam(group).gr_gid
548 743b53d4 René Nussbaumer
    except KeyError:
549 743b53d4 René Nussbaumer
      raise errors.ConfigurationError("User or group not existing on system:"
550 743b53d4 René Nussbaumer
                                      " %s:%s" % (user, group))
551 04ccf5e9 Guido Trotter
    utils.CloseFDs()
552 743b53d4 René Nussbaumer
    utils.Daemonize(constants.DAEMONS_LOGFILES[daemon_name], uid, gid)
553 04ccf5e9 Guido Trotter
554 04ccf5e9 Guido Trotter
  utils.WritePidFile(daemon_name)
555 04ccf5e9 Guido Trotter
  try:
556 04ccf5e9 Guido Trotter
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
557 04ccf5e9 Guido Trotter
                       debug=options.debug,
558 04ccf5e9 Guido Trotter
                       stderr_logging=not options.fork,
559 e7307f08 Michael Hanselmann
                       multithreaded=multithreaded,
560 551b6283 Iustin Pop
                       program=daemon_name,
561 ff917534 Luca Bigliardi
                       syslog=options.syslog,
562 ff917534 Luca Bigliardi
                       console_logging=console_logging)
563 099c52ad Iustin Pop
    logging.info("%s daemon startup", daemon_name)
564 04ccf5e9 Guido Trotter
    exec_fn(options, args)
565 04ccf5e9 Guido Trotter
  finally:
566 04ccf5e9 Guido Trotter
    utils.RemovePidFile(daemon_name)