Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ 9b739173

History | View | Annotate | Download (7.4 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._signal_wait = []
45
    self._poller = select.poll()
46

    
47
  @utils.SignalHandled([signal.SIGCHLD])
48
  @utils.SignalHandled([signal.SIGTERM])
49
  def Run(self, stop_on_empty=False, signal_handlers=None):
50
    """Runs the mainloop.
51

52
    @type stop_on_empty: bool
53
    @param stop_on_empty: Whether to stop mainloop once all I/O waiters
54
                          unregistered
55
    @type signal_handlers: dict
56
    @param signal_handlers: signal->L{utils.SignalHandler} passed by decorator
57

58
    """
59
    assert isinstance(signal_handlers, dict) and \
60
           len(signal_handlers) > 0, \
61
           "Broken SignalHandled decorator"
62
    running = True
63
    # Start actual main loop
64
    while running:
65
      # Stop if nothing is listening anymore
66
      if stop_on_empty and not (self._io_wait):
67
        break
68

    
69
      # Wait for I/O events
70
      try:
71
        io_events = self._poller.poll(None)
72
      except select.error, err:
73
        # EINTR can happen when signals are sent
74
        if err.args and err.args[0] in (errno.EINTR,):
75
          io_events = None
76
        else:
77
          raise
78

    
79
      if io_events:
80
        # Check for I/O events
81
        for (evfd, evcond) in io_events:
82
          owner = self._io_wait.get(evfd, None)
83
          if owner:
84
            owner.OnIO(evfd, evcond)
85

    
86
      # Check whether a signal was raised
87
      for sig in signal_handlers:
88
        handler = signal_handlers[sig]
89
        if handler.called:
90
          self._CallSignalWaiters(sig)
91
          running = (sig != signal.SIGTERM)
92
          handler.Clear()
93

    
94
  def _CallSignalWaiters(self, signum):
95
    """Calls all signal waiters for a certain signal.
96

97
    @type signum: int
98
    @param signum: Signal number
99

100
    """
101
    for owner in self._signal_wait:
102
      owner.OnSignal(signal.SIGCHLD)
103

    
104
  def RegisterIO(self, owner, fd, condition):
105
    """Registers a receiver for I/O notifications
106

107
    The receiver must support a "OnIO(self, fd, conditions)" function.
108

109
    @type owner: instance
110
    @param owner: Receiver
111
    @type fd: int
112
    @param fd: File descriptor
113
    @type condition: int
114
    @param condition: ORed field of conditions to be notified
115
                      (see select module)
116

117
    """
118
    # select.Poller also supports file() like objects, but we don't.
119
    assert isinstance(fd, (int, long)), \
120
      "Only integers are supported for file descriptors"
121

    
122
    self._io_wait[fd] = owner
123
    self._poller.register(fd, condition)
124

    
125
  def RegisterSignal(self, owner):
126
    """Registers a receiver for signal notifications
127

128
    The receiver must support a "OnSignal(self, signum)" function.
129

130
    @type owner: instance
131
    @param owner: Receiver
132

133
    """
134
    self._signal_wait.append(owner)
135

    
136

    
137
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn):
138
  """Shared main function for daemons.
139

140
  @type daemon_name: string
141
  @param daemon_name: daemon name
142
  @type optionparser: L{optparse.OptionParser}
143
  @param optionparser: initialized optionparser with daemon-specific options
144
                       (common -f -d options will be handled by this module)
145
  @type options: object @param options: OptionParser result, should contain at
146
                 least the fork and the debug options
147
  @type dirs: list of strings
148
  @param dirs: list of directories that must exist for this daemon to work
149
  @type check_fn: function which accepts (options, args)
150
  @param check_fn: function that checks start conditions and exits if they're
151
                   not met
152
  @type exec_fn: function which accepts (options, args)
153
  @param exec_fn: function that's executed with the daemon's pid file held, and
154
                  runs the daemon itself.
155

156
  """
157
  optionparser.add_option("-f", "--foreground", dest="fork",
158
                          help="Don't detach from the current terminal",
159
                          default=True, action="store_false")
160
  optionparser.add_option("-d", "--debug", dest="debug",
161
                          help="Enable some debug messages",
162
                          default=False, action="store_true")
163
  if daemon_name in constants.DAEMONS_PORTS:
164
    # for networked daemons we also allow choosing the bind port and address.
165
    # by default we use the port provided by utils.GetDaemonPort, and bind to
166
    # 0.0.0.0 (which is represented by and empty bind address.
167
    port = utils.GetDaemonPort(daemon_name)
168
    optionparser.add_option("-p", "--port", dest="port",
169
                            help="Network port (%s default)." % port,
170
                            default=port, type="int")
171
    optionparser.add_option("-b", "--bind", dest="bind_address",
172
                            help="Bind address",
173
                            default="", metavar="ADDRESS")
174

    
175
  if daemon_name in constants.DAEMONS_SSL:
176
    default_cert, default_key = constants.DAEMONS_SSL[daemon_name]
177
    optionparser.add_option("--no-ssl", dest="ssl",
178
                            help="Do not secure HTTP protocol with SSL",
179
                            default=True, action="store_false")
180
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
181
                            help="SSL key",
182
                            default=default_key, type="string")
183
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
184
                            help="SSL certificate",
185
                            default=default_cert, type="string")
186

    
187
  multithread = utils.no_fork = daemon_name in constants.MULTITHREADED_DAEMONS
188

    
189
  options, args = optionparser.parse_args()
190

    
191
  if hasattr(options, 'ssl') and options.ssl:
192
    if not (options.ssl_cert and options.ssl_key):
193
      print >> sys.stderr, "Need key and certificate to use ssl"
194
      sys.exit(constants.EXIT_FAILURE)
195
    for fname in (options.ssl_cert, options.ssl_key):
196
      if not os.path.isfile(fname):
197
        print >> sys.stderr, "Need ssl file %s to run" % fname
198
        sys.exit(constants.EXIT_FAILURE)
199

    
200
  if check_fn is not None:
201
    check_fn(options, args)
202

    
203
  utils.EnsureDirs(dirs)
204

    
205
  if options.fork:
206
    utils.CloseFDs()
207
    utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
208

    
209
  utils.WritePidFile(daemon_name)
210
  try:
211
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
212
                       debug=options.debug,
213
                       stderr_logging=not options.fork,
214
                       multithreaded=multithread)
215
    logging.info("%s daemon startup" % daemon_name)
216
    exec_fn(options, args)
217
  finally:
218
    utils.RemovePidFile(daemon_name)
219