Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ 577c90a3

History | View | Annotate | Download (9 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 os
26
import select
27
import signal
28
import errno
29
import logging
30

    
31
from ganeti import utils
32
from ganeti import constants
33

    
34

    
35
class Mainloop(object):
36
  """Generic mainloop for daemons
37

38
  """
39
  def __init__(self):
40
    """Constructs a new Mainloop instance.
41

42
    """
43
    self._io_wait = {}
44
    self._io_wait_add = []
45
    self._io_wait_remove = []
46
    self._signal_wait = []
47

    
48
  def Run(self, handle_sigchld=True, handle_sigterm=True, stop_on_empty=False):
49
    """Runs the mainloop.
50

51
    @type handle_sigchld: bool
52
    @param handle_sigchld: Whether to install handler for SIGCHLD
53
    @type handle_sigterm: bool
54
    @param handle_sigterm: Whether to install handler for SIGTERM
55
    @type stop_on_empty: bool
56
    @param stop_on_empty: Whether to stop mainloop once all I/O waiters
57
                          unregistered
58

59
    """
60
    poller = select.poll()
61

    
62
    # Setup signal handlers
63
    if handle_sigchld:
64
      sigchld_handler = utils.SignalHandler([signal.SIGCHLD])
65
    else:
66
      sigchld_handler = None
67
    try:
68
      if handle_sigterm:
69
        sigterm_handler = utils.SignalHandler([signal.SIGTERM])
70
      else:
71
        sigterm_handler = None
72

    
73
      try:
74
        running = True
75

    
76
        # Start actual main loop
77
        while running:
78
          # Entries could be added again afterwards, hence removing first
79
          if self._io_wait_remove:
80
            for fd in self._io_wait_remove:
81
              try:
82
                poller.unregister(fd)
83
              except KeyError:
84
                pass
85
              try:
86
                del self._io_wait[fd]
87
              except KeyError:
88
                pass
89
            self._io_wait_remove = []
90

    
91
          # Add new entries
92
          if self._io_wait_add:
93
            for (owner, fd, conditions) in self._io_wait_add:
94
              self._io_wait[fd] = owner
95
              poller.register(fd, conditions)
96
            self._io_wait_add = []
97

    
98
          # Stop if nothing is listening anymore
99
          if stop_on_empty and not (self._io_wait):
100
            break
101

    
102
          # Wait for I/O events
103
          try:
104
            io_events = poller.poll(None)
105
          except select.error, err:
106
            # EINTR can happen when signals are sent
107
            if err.args and err.args[0] in (errno.EINTR,):
108
              io_events = None
109
            else:
110
              raise
111

    
112
          if io_events:
113
            # Check for I/O events
114
            for (evfd, evcond) in io_events:
115
              owner = self._io_wait.get(evfd, None)
116
              if owner:
117
                owner.OnIO(evfd, evcond)
118

    
119
          # Check whether signal was raised
120
          if sigchld_handler and sigchld_handler.called:
121
            self._CallSignalWaiters(signal.SIGCHLD)
122
            sigchld_handler.Clear()
123

    
124
          if sigterm_handler and sigterm_handler.called:
125
            self._CallSignalWaiters(signal.SIGTERM)
126
            running = False
127
            sigterm_handler.Clear()
128
      finally:
129
        # Restore signal handlers
130
        if sigterm_handler:
131
          sigterm_handler.Reset()
132
    finally:
133
      if sigchld_handler:
134
        sigchld_handler.Reset()
135

    
136
  def _CallSignalWaiters(self, signum):
137
    """Calls all signal waiters for a certain signal.
138

139
    @type signum: int
140
    @param signum: Signal number
141

142
    """
143
    for owner in self._signal_wait:
144
      owner.OnSignal(signal.SIGCHLD)
145

    
146
  def RegisterIO(self, owner, fd, condition):
147
    """Registers a receiver for I/O notifications
148

149
    The receiver must support a "OnIO(self, fd, conditions)" function.
150

151
    @type owner: instance
152
    @param owner: Receiver
153
    @type fd: int
154
    @param fd: File descriptor
155
    @type condition: int
156
    @param condition: ORed field of conditions to be notified
157
                      (see select module)
158

159
    """
160
    # select.Poller also supports file() like objects, but we don't.
161
    assert isinstance(fd, (int, long)), \
162
      "Only integers are supported for file descriptors"
163

    
164
    self._io_wait_add.append((owner, fd, condition))
165

    
166
  def UnregisterIO(self, fd):
167
    """Unregister a file descriptor.
168

169
    It'll be unregistered the next time the mainloop checks for it.
170

171
    @type fd: int
172
    @param fd: File descriptor
173

174
    """
175
    # select.Poller also supports file() like objects, but we don't.
176
    assert isinstance(fd, (int, long)), \
177
      "Only integers are supported for file descriptors"
178

    
179
    self._io_wait_remove.append(fd)
180

    
181
  def RegisterSignal(self, owner):
182
    """Registers a receiver for signal notifications
183

184
    The receiver must support a "OnSignal(self, signum)" function.
185

186
    @type owner: instance
187
    @param owner: Receiver
188

189
    """
190
    self._signal_wait.append(owner)
191

    
192

    
193
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn):
194
  """Shared main function for daemons.
195

196
  @type daemon_name: string
197
  @param daemon_name: daemon name
198
  @type optionparser: L{optparse.OptionParser}
199
  @param optionparser: initialized optionparser with daemon-specific options
200
                       (common -f -d options will be handled by this module)
201
  @type options: object @param options: OptionParser result, should contain at
202
                 least the fork and the debug options
203
  @type dirs: list of strings
204
  @param dirs: list of directories that must exist for this daemon to work
205
  @type check_fn: function which accepts (options, args)
206
  @param check_fn: function that checks start conditions and exits if they're
207
                   not met
208
  @type exec_fn: function which accepts (options, args)
209
  @param exec_fn: function that's executed with the daemon's pid file held, and
210
                  runs the daemon itself.
211

212
  """
213
  optionparser.add_option("-f", "--foreground", dest="fork",
214
                          help="Don't detach from the current terminal",
215
                          default=True, action="store_false")
216
  optionparser.add_option("-d", "--debug", dest="debug",
217
                          help="Enable some debug messages",
218
                          default=False, action="store_true")
219
  if daemon_name in constants.DAEMONS_PORTS:
220
    # for networked daemons we also allow choosing the bind port and address.
221
    # by default we use the port provided by utils.GetDaemonPort, and bind to
222
    # 0.0.0.0 (which is represented by and empty bind address.
223
    port = utils.GetDaemonPort(daemon_name)
224
    optionparser.add_option("-p", "--port", dest="port",
225
                            help="Network port (%s default)." % port,
226
                            default=port, type="int")
227
    optionparser.add_option("-b", "--bind", dest="bind_address",
228
                            help="Bind address",
229
                            default="", metavar="ADDRESS")
230

    
231
  if daemon_name in constants.DAEMONS_SSL:
232
    default_cert, default_key = constants.DAEMONS_SSL[daemon_name]
233
    optionparser.add_option("--no-ssl", dest="ssl",
234
                            help="Do not secure HTTP protocol with SSL",
235
                            default=True, action="store_false")
236
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
237
                            help="SSL key",
238
                            default=default_key, type="string")
239
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
240
                            help="SSL certificate",
241
                            default=default_cert, type="string")
242

    
243
  multithread = utils.no_fork = daemon_name in constants.MULTITHREADED_DAEMONS
244

    
245
  options, args = optionparser.parse_args()
246

    
247
  if hasattr(options, 'ssl') and options.ssl:
248
    if not (options.ssl_cert and options.ssl_key):
249
      print >> sys.stderr, "Need key and certificate to use ssl"
250
      sys.exit(constants.EXIT_FAILURE)
251
    for fname in (options.ssl_cert, options.ssl_key):
252
      if not os.path.isfile(fname):
253
        print >> sys.stderr, "Need ssl file %s to run" % fname
254
        sys.exit(constants.EXIT_FAILURE)
255

    
256
  if check_fn is not None:
257
    check_fn(options, args)
258

    
259
  utils.EnsureDirs(dirs)
260

    
261
  if options.fork:
262
    utils.CloseFDs()
263
    utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
264

    
265
  utils.WritePidFile(daemon_name)
266
  try:
267
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
268
                       debug=options.debug,
269
                       stderr_logging=not options.fork,
270
                       multithreaded=multithread)
271
    logging.info("%s daemon startup" % daemon_name)
272
    exec_fn(options, args)
273
  finally:
274
    utils.RemovePidFile(daemon_name)
275