Statistics
| Branch: | Tag: | Revision:

root / lib / daemon.py @ 112d240d

History | View | Annotate | Download (6.3 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

    
32
from ganeti import utils
33
from ganeti import constants
34

    
35

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

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

43
    """
44
    self._signal_wait = []
45

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

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

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

    
68
      asyncore.loop(timeout=5, count=1, use_poll=True)
69

    
70
      # Check whether a signal was raised
71
      for sig in signal_handlers:
72
        handler = signal_handlers[sig]
73
        if handler.called:
74
          self._CallSignalWaiters(sig)
75
          running = (sig != signal.SIGTERM)
76
          handler.Clear()
77

    
78
  def _CallSignalWaiters(self, signum):
79
    """Calls all signal waiters for a certain signal.
80

81
    @type signum: int
82
    @param signum: Signal number
83

84
    """
85
    for owner in self._signal_wait:
86
      owner.OnSignal(signal.SIGCHLD)
87

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

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

93
    @type owner: instance
94
    @param owner: Receiver
95

96
    """
97
    self._signal_wait.append(owner)
98

    
99

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

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

119
  """
120
  optionparser.add_option("-f", "--foreground", dest="fork",
121
                          help="Don't detach from the current terminal",
122
                          default=True, action="store_false")
123
  optionparser.add_option("-d", "--debug", dest="debug",
124
                          help="Enable some debug messages",
125
                          default=False, action="store_true")
126
  if daemon_name in constants.DAEMONS_PORTS:
127
    # for networked daemons we also allow choosing the bind port and address.
128
    # by default we use the port provided by utils.GetDaemonPort, and bind to
129
    # 0.0.0.0 (which is represented by and empty bind address.
130
    port = utils.GetDaemonPort(daemon_name)
131
    optionparser.add_option("-p", "--port", dest="port",
132
                            help="Network port (%s default)." % port,
133
                            default=port, type="int")
134
    optionparser.add_option("-b", "--bind", dest="bind_address",
135
                            help="Bind address",
136
                            default="", metavar="ADDRESS")
137

    
138
  if daemon_name in constants.DAEMONS_SSL:
139
    default_cert, default_key = constants.DAEMONS_SSL[daemon_name]
140
    optionparser.add_option("--no-ssl", dest="ssl",
141
                            help="Do not secure HTTP protocol with SSL",
142
                            default=True, action="store_false")
143
    optionparser.add_option("-K", "--ssl-key", dest="ssl_key",
144
                            help="SSL key",
145
                            default=default_key, type="string")
146
    optionparser.add_option("-C", "--ssl-cert", dest="ssl_cert",
147
                            help="SSL certificate",
148
                            default=default_cert, type="string")
149

    
150
  multithread = utils.no_fork = daemon_name in constants.MULTITHREADED_DAEMONS
151

    
152
  options, args = optionparser.parse_args()
153

    
154
  if hasattr(options, 'ssl') and options.ssl:
155
    if not (options.ssl_cert and options.ssl_key):
156
      print >> sys.stderr, "Need key and certificate to use ssl"
157
      sys.exit(constants.EXIT_FAILURE)
158
    for fname in (options.ssl_cert, options.ssl_key):
159
      if not os.path.isfile(fname):
160
        print >> sys.stderr, "Need ssl file %s to run" % fname
161
        sys.exit(constants.EXIT_FAILURE)
162

    
163
  if check_fn is not None:
164
    check_fn(options, args)
165

    
166
  utils.EnsureDirs(dirs)
167

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

    
172
  utils.WritePidFile(daemon_name)
173
  try:
174
    utils.SetupLogging(logfile=constants.DAEMONS_LOGFILES[daemon_name],
175
                       debug=options.debug,
176
                       stderr_logging=not options.fork,
177
                       multithreaded=multithread)
178
    logging.info("%s daemon startup" % daemon_name)
179
    exec_fn(options, args)
180
  finally:
181
    utils.RemovePidFile(daemon_name)
182