Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ 565fe4c4

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

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

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

58
    """
59
    # Setup signal handlers
60
    if handle_sigchld:
61
      sigchld_handler = utils.SignalHandler([signal.SIGCHLD])
62
    else:
63
      sigchld_handler = None
64
    try:
65
      if handle_sigterm:
66
        sigterm_handler = utils.SignalHandler([signal.SIGTERM])
67
      else:
68
        sigterm_handler = None
69

    
70
      try:
71
        running = True
72

    
73
        # Start actual main loop
74
        while running:
75
          # Stop if nothing is listening anymore
76
          if stop_on_empty and not (self._io_wait):
77
            break
78

    
79
          # Wait for I/O events
80
          try:
81
            io_events = self._poller.poll(None)
82
          except select.error, err:
83
            # EINTR can happen when signals are sent
84
            if err.args and err.args[0] in (errno.EINTR,):
85
              io_events = None
86
            else:
87
              raise
88

    
89
          if io_events:
90
            # Check for I/O events
91
            for (evfd, evcond) in io_events:
92
              owner = self._io_wait.get(evfd, None)
93
              if owner:
94
                owner.OnIO(evfd, evcond)
95

    
96
          # Check whether signal was raised
97
          if sigchld_handler and sigchld_handler.called:
98
            self._CallSignalWaiters(signal.SIGCHLD)
99
            sigchld_handler.Clear()
100

    
101
          if sigterm_handler and sigterm_handler.called:
102
            self._CallSignalWaiters(signal.SIGTERM)
103
            running = False
104
            sigterm_handler.Clear()
105
      finally:
106
        # Restore signal handlers
107
        if sigterm_handler:
108
          sigterm_handler.Reset()
109
    finally:
110
      if sigchld_handler:
111
        sigchld_handler.Reset()
112

    
113
  def _CallSignalWaiters(self, signum):
114
    """Calls all signal waiters for a certain signal.
115

116
    @type signum: int
117
    @param signum: Signal number
118

119
    """
120
    for owner in self._signal_wait:
121
      owner.OnSignal(signal.SIGCHLD)
122

    
123
  def RegisterIO(self, owner, fd, condition):
124
    """Registers a receiver for I/O notifications
125

126
    The receiver must support a "OnIO(self, fd, conditions)" function.
127

128
    @type owner: instance
129
    @param owner: Receiver
130
    @type fd: int
131
    @param fd: File descriptor
132
    @type condition: int
133
    @param condition: ORed field of conditions to be notified
134
                      (see select module)
135

136
    """
137
    # select.Poller also supports file() like objects, but we don't.
138
    assert isinstance(fd, (int, long)), \
139
      "Only integers are supported for file descriptors"
140

    
141
    self._io_wait[fd] = owner
142
    self._poller.register(fd, condition)
143

    
144
  def RegisterSignal(self, owner):
145
    """Registers a receiver for signal notifications
146

147
    The receiver must support a "OnSignal(self, signum)" function.
148

149
    @type owner: instance
150
    @param owner: Receiver
151

152
    """
153
    self._signal_wait.append(owner)
154

    
155

    
156
def GenericMain(daemon_name, optionparser, dirs, check_fn, exec_fn):
157
  """Shared main function for daemons.
158

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

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

    
194
  if daemon_name in constants.DAEMONS_SSL:
195
    default_cert, default_key = constants.DAEMONS_SSL[daemon_name]
196
    optionparser.add_option("--no-ssl", dest="ssl",
197
                            help="Do not secure HTTP protocol with SSL",
198
                            default=True, action="store_false")
199
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
200
                            help="SSL key",
201
                            default=default_key, type="string")
202
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
203
                            help="SSL certificate",
204
                            default=default_cert, type="string")
205

    
206
  multithread = utils.no_fork = daemon_name in constants.MULTITHREADED_DAEMONS
207

    
208
  options, args = optionparser.parse_args()
209

    
210
  if hasattr(options, 'ssl') and options.ssl:
211
    if not (options.ssl_cert and options.ssl_key):
212
      print >> sys.stderr, "Need key and certificate to use ssl"
213
      sys.exit(constants.EXIT_FAILURE)
214
    for fname in (options.ssl_cert, options.ssl_key):
215
      if not os.path.isfile(fname):
216
        print >> sys.stderr, "Need ssl file %s to run" % fname
217
        sys.exit(constants.EXIT_FAILURE)
218

    
219
  if check_fn is not None:
220
    check_fn(options, args)
221

    
222
  utils.EnsureDirs(dirs)
223

    
224
  if options.fork:
225
    utils.CloseFDs()
226
    utils.Daemonize(logfile=constants.DAEMONS_LOGFILES[daemon_name])
227

    
228
  utils.WritePidFile(daemon_name)
229
  try:
230
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
231
                       debug=options.debug,
232
                       stderr_logging=not options.fork,
233
                       multithreaded=multithread)
234
    logging.info("%s daemon startup" % daemon_name)
235
    exec_fn(options, args)
236
  finally:
237
    utils.RemovePidFile(daemon_name)
238