Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ a02b89cf

History | View | Annotate | Download (7.7 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""Module with helper classes and functions for daemons"""
23

    
24

    
25
import asyncore
26
import os
27
import select
28
import signal
29
import errno
30
import logging
31
import sched
32
import time
33

    
34
from ganeti import utils
35
from ganeti import constants
36
from ganeti import errors
37

    
38

    
39
class SchedulerBreakout(Exception):
40
  """Exception used to get out of the scheduler loop
41

42
  """
43

    
44

    
45
def AsyncoreDelayFunction(timeout):
46
  """Asyncore-compatible scheduler delay function.
47

48
  This is a delay function for sched that, rather than actually sleeping,
49
  executes asyncore events happening in the meantime.
50

51
  After an event has occurred, rather than returning, it raises a
52
  SchedulerBreakout exception, which will force the current scheduler.run()
53
  invocation to terminate, so that we can also check for signals. The main loop
54
  will then call the scheduler run again, which will allow it to actually
55
  process any due events.
56

57
  This is needed because scheduler.run() doesn't support a count=..., as
58
  asyncore loop, and the scheduler module documents throwing exceptions from
59
  inside the delay function as an allowed usage model.
60

61
  """
62
  asyncore.loop(timeout=timeout, count=1, use_poll=True)
63
  raise SchedulerBreakout()
64

    
65

    
66
class AsyncoreScheduler(sched.scheduler):
67
  """Event scheduler integrated with asyncore
68

69
  """
70
  def __init__(self, timefunc):
71
    sched.scheduler.__init__(self, timefunc, AsyncoreDelayFunction)
72

    
73

    
74
class Mainloop(object):
75
  """Generic mainloop for daemons
76

77
  """
78
  def __init__(self):
79
    """Constructs a new Mainloop instance.
80

81
    @ivar scheduler: A L{sched.scheduler} object, which can be used to register
82
    timed events
83

84
    """
85
    self._signal_wait = []
86
    self.scheduler = AsyncoreScheduler(time.time)
87

    
88
  @utils.SignalHandled([signal.SIGCHLD])
89
  @utils.SignalHandled([signal.SIGTERM])
90
  def Run(self, stop_on_empty=False, signal_handlers=None):
91
    """Runs the mainloop.
92

93
    @type stop_on_empty: bool
94
    @param stop_on_empty: Whether to stop mainloop once all I/O waiters
95
                          unregistered
96
    @type signal_handlers: dict
97
    @param signal_handlers: signal->L{utils.SignalHandler} passed by decorator
98

99
    """
100
    assert isinstance(signal_handlers, dict) and \
101
           len(signal_handlers) > 0, \
102
           "Broken SignalHandled decorator"
103
    running = True
104
    # Start actual main loop
105
    while running:
106
      # Stop if nothing is listening anymore
107
      if stop_on_empty and not (self._io_wait):
108
        break
109

    
110
      if not self.scheduler.empty():
111
        try:
112
          self.scheduler.run()
113
        except SchedulerBreakout:
114
          pass
115
      else:
116
        asyncore.loop(count=1, use_poll=True)
117

    
118
      # Check whether a signal was raised
119
      for sig in signal_handlers:
120
        handler = signal_handlers[sig]
121
        if handler.called:
122
          self._CallSignalWaiters(sig)
123
          running = (sig != signal.SIGTERM)
124
          handler.Clear()
125

    
126
  def _CallSignalWaiters(self, signum):
127
    """Calls all signal waiters for a certain signal.
128

129
    @type signum: int
130
    @param signum: Signal number
131

132
    """
133
    for owner in self._signal_wait:
134
      owner.OnSignal(signal.SIGCHLD)
135

    
136
  def RegisterSignal(self, owner):
137
    """Registers a receiver for signal notifications
138

139
    The receiver must support a "OnSignal(self, signum)" function.
140

141
    @type owner: instance
142
    @param owner: Receiver
143

144
    """
145
    self._signal_wait.append(owner)
146

    
147

    
148
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn):
149
  """Shared main function for daemons.
150

151
  @type daemon_name: string
152
  @param daemon_name: daemon name
153
  @type optionparser: L{optparse.OptionParser}
154
  @param optionparser: initialized optionparser with daemon-specific options
155
                       (common -f -d options will be handled by this module)
156
  @type options: object @param options: OptionParser result, should contain at
157
                 least the fork and the debug options
158
  @type dirs: list of strings
159
  @param dirs: list of directories that must exist for this daemon to work
160
  @type check_fn: function which accepts (options, args)
161
  @param check_fn: function that checks start conditions and exits if they're
162
                   not met
163
  @type exec_fn: function which accepts (options, args)
164
  @param exec_fn: function that's executed with the daemon's pid file held, and
165
                  runs the daemon itself.
166

167
  """
168
  optionparser.add_option("-f", "--foreground", dest="fork",
169
                          help="Don't detach from the current terminal",
170
                          default=True, action="store_false")
171
  optionparser.add_option("-d", "--debug", dest="debug",
172
                          help="Enable some debug messages",
173
                          default=False, action="store_true")
174
  if daemon_name in constants.DAEMONS_PORTS:
175
    # for networked daemons we also allow choosing the bind port and address.
176
    # by default we use the port provided by utils.GetDaemonPort, and bind to
177
    # 0.0.0.0 (which is represented by and empty bind address.
178
    port = utils.GetDaemonPort(daemon_name)
179
    optionparser.add_option("-p", "--port", dest="port",
180
                            help="Network port (%s default)." % port,
181
                            default=port, type="int")
182
    optionparser.add_option("-b", "--bind", dest="bind_address",
183
                            help="Bind address",
184
                            default="", metavar="ADDRESS")
185

    
186
  if daemon_name in constants.DAEMONS_SSL:
187
    default_cert, default_key = constants.DAEMONS_SSL[daemon_name]
188
    optionparser.add_option("--no-ssl", dest="ssl",
189
                            help="Do not secure HTTP protocol with SSL",
190
                            default=True, action="store_false")
191
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
192
                            help="SSL key",
193
                            default=default_key, type="string")
194
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
195
                            help="SSL certificate",
196
                            default=default_cert, type="string")
197

    
198
  multithread = utils.no_fork = daemon_name in constants.MULTITHREADED_DAEMONS
199

    
200
  options, args = optionparser.parse_args()
201

    
202
  if hasattr(options, 'ssl') and options.ssl:
203
    if not (options.ssl_cert and options.ssl_key):
204
      print >> sys.stderr, "Need key and certificate to use ssl"
205
      sys.exit(constants.EXIT_FAILURE)
206
    for fname in (options.ssl_cert, options.ssl_key):
207
      if not os.path.isfile(fname):
208
        print >> sys.stderr, "Need ssl file %s to run" % fname
209
        sys.exit(constants.EXIT_FAILURE)
210

    
211
  if check_fn is not None:
212
    check_fn(options, args)
213

    
214
  utils.EnsureDirs(dirs)
215

    
216
  if options.fork:
217
    utils.CloseFDs()
218
    utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
219

    
220
  utils.WritePidFile(daemon_name)
221
  try:
222
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
223
                       debug=options.debug,
224
                       stderr_logging=not options.fork,
225
                       multithreaded=multithread)
226
    logging.info("%s daemon startup" % daemon_name)
227
    exec_fn(options, args)
228
  finally:
229
    utils.RemovePidFile(daemon_name)
230