daemon.py 7.91 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#
#

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


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


25
import os
26
27
28
import select
import signal
import errno
29
import logging
30
31

from ganeti import utils
32
from ganeti import constants
33
34
35
36
37
38
39


class Mainloop(object):
  """Generic mainloop for daemons

  """
  def __init__(self):
40
41
42
43
    """Constructs a new Mainloop instance.

    """
    self._io_wait = {}
44
    self._signal_wait = []
45
    self._poller = select.poll()
46

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
  def Run(self, handle_sigchld=True, handle_sigterm=True, stop_on_empty=False):
    """Runs the mainloop.

    @type handle_sigchld: bool
    @param handle_sigchld: Whether to install handler for SIGCHLD
    @type handle_sigterm: bool
    @param handle_sigterm: Whether to install handler for SIGTERM
    @type stop_on_empty: bool
    @param stop_on_empty: Whether to stop mainloop once all I/O waiters
                          unregistered

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

      try:
        running = True

        # Start actual main loop
        while running:
          # Stop if nothing is listening anymore
Guido Trotter's avatar
Guido Trotter committed
76
          if stop_on_empty and not (self._io_wait):
77
78
79
80
            break

          # Wait for I/O events
          try:
81
            io_events = self._poller.poll(None)
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
          except select.error, err:
            # EINTR can happen when signals are sent
            if err.args and err.args[0] in (errno.EINTR,):
              io_events = None
            else:
              raise

          if io_events:
            # Check for I/O events
            for (evfd, evcond) in io_events:
              owner = self._io_wait.get(evfd, None)
              if owner:
                owner.OnIO(evfd, evcond)

          # Check whether signal was raised
          if sigchld_handler and sigchld_handler.called:
            self._CallSignalWaiters(signal.SIGCHLD)
            sigchld_handler.Clear()

          if sigterm_handler and sigterm_handler.called:
            self._CallSignalWaiters(signal.SIGTERM)
            running = False
            sigterm_handler.Clear()
      finally:
        # Restore signal handlers
        if sigterm_handler:
          sigterm_handler.Reset()
109
    finally:
110
111
      if sigchld_handler:
        sigchld_handler.Reset()
Guido Trotter's avatar
Guido Trotter committed
112

113
114
115
116
117
118
119
120
121
  def _CallSignalWaiters(self, signum):
    """Calls all signal waiters for a certain signal.

    @type signum: int
    @param signum: Signal number

    """
    for owner in self._signal_wait:
      owner.OnSignal(signal.SIGCHLD)
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136

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

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

    @type owner: instance
    @param owner: Receiver
    @type fd: int
    @param fd: File descriptor
    @type condition: int
    @param condition: ORed field of conditions to be notified
                      (see select module)

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

141
142
    self._io_wait[fd] = owner
    self._poller.register(fd, condition)
143
144
145
146
147
148
149
150
151
152
153

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

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

    @type owner: instance
    @param owner: Receiver

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

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193

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

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

  """
  optionparser.add_option("-f", "--foreground", dest="fork",
                          help="Don't detach from the current terminal",
                          default=True, action="store_false")
  optionparser.add_option("-d", "--debug", dest="debug",
                          help="Enable some debug messages",
                          default=False, action="store_true")
  if daemon_name in constants.DAEMONS_PORTS:
    # for networked daemons we also allow choosing the bind port and address.
    # by default we use the port provided by utils.GetDaemonPort, and bind to
    # 0.0.0.0 (which is represented by and empty bind address.
    port = utils.GetDaemonPort(daemon_name)
    optionparser.add_option("-p", "--port", dest="port",
                            help="Network port (%s default)." % port,
                            default=port, type="int")
    optionparser.add_option("-b", "--bind", dest="bind_address",
                            help="Bind address",
                            default="", metavar="ADDRESS")

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

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

  options, args = optionparser.parse_args()

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

  if check_fn is not None:
    check_fn(options, args)

222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
  utils.EnsureDirs(dirs)

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

  utils.WritePidFile(daemon_name)
  try:
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
                       debug=options.debug,
                       stderr_logging=not options.fork,
                       multithreaded=multithread)
    logging.info("%s daemon startup" % daemon_name)
    exec_fn(options, args)
  finally:
    utils.RemovePidFile(daemon_name)