Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-confd @ 562bee4d

History | View | Annotate | Download (9 kB)

1 b84cb9a0 Guido Trotter
#!/usr/bin/python
2 b84cb9a0 Guido Trotter
#
3 b84cb9a0 Guido Trotter
4 b84cb9a0 Guido Trotter
# Copyright (C) 2009, Google Inc.
5 b84cb9a0 Guido Trotter
#
6 b84cb9a0 Guido Trotter
# This program is free software; you can redistribute it and/or modify
7 b84cb9a0 Guido Trotter
# it under the terms of the GNU General Public License as published by
8 b84cb9a0 Guido Trotter
# the Free Software Foundation; either version 2 of the License, or
9 b84cb9a0 Guido Trotter
# (at your option) any later version.
10 b84cb9a0 Guido Trotter
#
11 b84cb9a0 Guido Trotter
# This program is distributed in the hope that it will be useful, but
12 b84cb9a0 Guido Trotter
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 b84cb9a0 Guido Trotter
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 b84cb9a0 Guido Trotter
# General Public License for more details.
15 b84cb9a0 Guido Trotter
#
16 b84cb9a0 Guido Trotter
# You should have received a copy of the GNU General Public License
17 b84cb9a0 Guido Trotter
# along with this program; if not, write to the Free Software
18 b84cb9a0 Guido Trotter
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 b84cb9a0 Guido Trotter
# 02110-1301, USA.
20 b84cb9a0 Guido Trotter
21 b84cb9a0 Guido Trotter
22 b84cb9a0 Guido Trotter
"""Ganeti configuration daemon
23 b84cb9a0 Guido Trotter
24 b84cb9a0 Guido Trotter
Ganeti-confd is a daemon to query master candidates for configuration values.
25 b84cb9a0 Guido Trotter
It uses UDP+HMAC for authentication with a global cluster key.
26 b84cb9a0 Guido Trotter
27 b84cb9a0 Guido Trotter
"""
28 b84cb9a0 Guido Trotter
29 b84cb9a0 Guido Trotter
import os
30 b84cb9a0 Guido Trotter
import sys
31 b84cb9a0 Guido Trotter
import logging
32 b84cb9a0 Guido Trotter
import asyncore
33 b84cb9a0 Guido Trotter
import socket
34 b84cb9a0 Guido Trotter
import pyinotify
35 b84cb9a0 Guido Trotter
36 b84cb9a0 Guido Trotter
from optparse import OptionParser
37 b84cb9a0 Guido Trotter
38 b84cb9a0 Guido Trotter
from ganeti import constants
39 b84cb9a0 Guido Trotter
from ganeti import errors
40 b84cb9a0 Guido Trotter
from ganeti import daemon
41 b84cb9a0 Guido Trotter
from ganeti import ssconf
42 b84cb9a0 Guido Trotter
from ganeti.asyncnotifier import AsyncNotifier
43 b84cb9a0 Guido Trotter
from ganeti.confd.server import ConfdProcessor
44 b84cb9a0 Guido Trotter
45 b84cb9a0 Guido Trotter
46 b84cb9a0 Guido Trotter
class ConfdAsyncUDPServer(asyncore.dispatcher):
47 b84cb9a0 Guido Trotter
  """The confd udp server, suitable for use with asyncore.
48 b84cb9a0 Guido Trotter
49 b84cb9a0 Guido Trotter
  """
50 b84cb9a0 Guido Trotter
  def __init__(self, bind_address, port, processor):
51 b84cb9a0 Guido Trotter
    """Constructor for ConfdAsyncUDPServer
52 b84cb9a0 Guido Trotter
53 b84cb9a0 Guido Trotter
    @type bind_address: string
54 b84cb9a0 Guido Trotter
    @param bind_address: socket bind address ('' for all)
55 b84cb9a0 Guido Trotter
    @type port: int
56 b84cb9a0 Guido Trotter
    @param port: udp port
57 b84cb9a0 Guido Trotter
    @type processor: L{confd.server.ConfdProcessor}
58 b84cb9a0 Guido Trotter
    @param reader: ConfigReader to use to access the config
59 b84cb9a0 Guido Trotter
60 b84cb9a0 Guido Trotter
    """
61 b84cb9a0 Guido Trotter
    asyncore.dispatcher.__init__(self)
62 b84cb9a0 Guido Trotter
    self.bind_address = bind_address
63 b84cb9a0 Guido Trotter
    self.port = port
64 b84cb9a0 Guido Trotter
    self.processor = processor
65 b84cb9a0 Guido Trotter
    self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
66 b84cb9a0 Guido Trotter
    self.bind((bind_address, port))
67 b84cb9a0 Guido Trotter
    logging.debug("listening on ('%s':%d)" % (bind_address, port))
68 b84cb9a0 Guido Trotter
69 b84cb9a0 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
70 b84cb9a0 Guido Trotter
  def handle_connect(self):
71 b84cb9a0 Guido Trotter
    # Python thinks that the first udp message from a source qualifies as a
72 b84cb9a0 Guido Trotter
    # "connect" and further ones are part of the same connection. We beg to
73 b84cb9a0 Guido Trotter
    # differ and treat all messages equally.
74 b84cb9a0 Guido Trotter
    pass
75 b84cb9a0 Guido Trotter
76 b84cb9a0 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
77 b84cb9a0 Guido Trotter
  def handle_read(self):
78 b84cb9a0 Guido Trotter
    try:
79 b84cb9a0 Guido Trotter
      payload_in, address = self.recvfrom(4096)
80 b84cb9a0 Guido Trotter
      ip, port = address
81 b84cb9a0 Guido Trotter
      payload_out =  self.processor.ExecQuery(payload_in, ip, port)
82 b84cb9a0 Guido Trotter
      if payload_out is not None:
83 b84cb9a0 Guido Trotter
        self.sendto(payload_out, 0, (ip, port))
84 b84cb9a0 Guido Trotter
    except:
85 b84cb9a0 Guido Trotter
      # we need to catch any exception here, log it, but proceed, because even
86 b84cb9a0 Guido Trotter
      # if we failed handling a single request, we still want the confd to
87 b84cb9a0 Guido Trotter
      # continue working.
88 b84cb9a0 Guido Trotter
      logging.error("Unexpected exception", exc_info=True)
89 b84cb9a0 Guido Trotter
90 b84cb9a0 Guido Trotter
  # this method is overriding an asyncore.dispatcher method
91 b84cb9a0 Guido Trotter
  def writable(self):
92 b84cb9a0 Guido Trotter
    # No need to check if we can write to the UDP socket
93 b84cb9a0 Guido Trotter
    return False
94 b84cb9a0 Guido Trotter
95 b84cb9a0 Guido Trotter
96 b84cb9a0 Guido Trotter
class ConfdInotifyEventHandler(pyinotify.ProcessEvent):
97 b84cb9a0 Guido Trotter
98 b84cb9a0 Guido Trotter
  def __init__(self, watch_manager, reader,
99 b84cb9a0 Guido Trotter
               file=constants.CLUSTER_CONF_FILE):
100 b84cb9a0 Guido Trotter
    """Constructor for ConfdInotifyEventHandler
101 b84cb9a0 Guido Trotter
102 b84cb9a0 Guido Trotter
    @type watch_manager: L{pyinotify.WatchManager}
103 b84cb9a0 Guido Trotter
    @param watch_manager: ganeti-confd inotify watch manager
104 b84cb9a0 Guido Trotter
    @type reader: L{ssconf.SimpleConfigReader}
105 b84cb9a0 Guido Trotter
    @param reader: ganeti-confd SimpleConfigReader
106 b84cb9a0 Guido Trotter
    @type file: string
107 b84cb9a0 Guido Trotter
    @param file: config file to watch
108 b84cb9a0 Guido Trotter
109 b84cb9a0 Guido Trotter
    """
110 b84cb9a0 Guido Trotter
    # no need to call the parent's constructor
111 b84cb9a0 Guido Trotter
    self.watch_manager = watch_manager
112 b84cb9a0 Guido Trotter
    self.reader = reader
113 b84cb9a0 Guido Trotter
    self.mask = pyinotify.EventsCodes.IN_IGNORED | \
114 b84cb9a0 Guido Trotter
                pyinotify.EventsCodes.IN_MODIFY
115 b84cb9a0 Guido Trotter
    self.file = file
116 46c9b31d Guido Trotter
    self.watch_handle = None
117 46c9b31d Guido Trotter
    self.enable()
118 b84cb9a0 Guido Trotter
119 46c9b31d Guido Trotter
  def enable(self):
120 46c9b31d Guido Trotter
    """Watch the given file
121 b84cb9a0 Guido Trotter
122 b84cb9a0 Guido Trotter
    """
123 46c9b31d Guido Trotter
    if self.watch_handle is None:
124 46c9b31d Guido Trotter
      result = self.watch_manager.add_watch(self.file, self.mask)
125 46c9b31d Guido Trotter
      if not self.file in result or result[self.file] <= 0:
126 46c9b31d Guido Trotter
        raise errors.ConfdFatalError("Could not add inotify watcher")
127 46c9b31d Guido Trotter
      else:
128 46c9b31d Guido Trotter
        self.watch_handle = result[self.file]
129 46c9b31d Guido Trotter
130 46c9b31d Guido Trotter
  def disable(self):
131 46c9b31d Guido Trotter
    """Stop watching the given file
132 46c9b31d Guido Trotter
133 46c9b31d Guido Trotter
    """
134 46c9b31d Guido Trotter
    if self.watch_handle is not None:
135 46c9b31d Guido Trotter
      result = self.watch_manager.rm_watch(self.watch_handle)
136 46c9b31d Guido Trotter
      if result[self.watch_handle]:
137 46c9b31d Guido Trotter
        self.watch_handle = None
138 b84cb9a0 Guido Trotter
139 b84cb9a0 Guido Trotter
  def reload_config(self):
140 b84cb9a0 Guido Trotter
    try:
141 b84cb9a0 Guido Trotter
      reloaded = self.reader.Reload()
142 b84cb9a0 Guido Trotter
      if reloaded:
143 b84cb9a0 Guido Trotter
        logging.info("Reloaded ganeti config")
144 b84cb9a0 Guido Trotter
      else:
145 b84cb9a0 Guido Trotter
        logging.debug("Skipped double config reload")
146 b84cb9a0 Guido Trotter
    except errors.ConfigurationError:
147 b84cb9a0 Guido Trotter
      # transform a ConfigurationError in a fatal error, that will cause confd
148 b84cb9a0 Guido Trotter
      # to quit.
149 b84cb9a0 Guido Trotter
      raise errors.ConfdFatalError(err)
150 b84cb9a0 Guido Trotter
151 b84cb9a0 Guido Trotter
  def process_IN_IGNORED(self, event):
152 b84cb9a0 Guido Trotter
    # Due to the fact that we monitor just for the cluster config file (rather
153 b84cb9a0 Guido Trotter
    # than for the whole data dir) when the file is replaced with another one
154 b84cb9a0 Guido Trotter
    # (which is what happens normally in ganeti) we're going to receive an
155 b84cb9a0 Guido Trotter
    # IN_IGNORED event from inotify, because of the file removal (which is
156 b84cb9a0 Guido Trotter
    # contextual with the replacement). In such a case we need to create
157 b84cb9a0 Guido Trotter
    # another watcher for the "new" file.
158 b84cb9a0 Guido Trotter
    logging.debug("Received 'ignored' inotify event for %s" % event.path)
159 46c9b31d Guido Trotter
    self.watch_handle = None
160 b84cb9a0 Guido Trotter
161 b84cb9a0 Guido Trotter
    try:
162 b84cb9a0 Guido Trotter
      # Since the kernel believes the file we were interested in is gone, it's
163 b84cb9a0 Guido Trotter
      # not going to notify us of any other events, until we set up, here, the
164 b84cb9a0 Guido Trotter
      # new watch. This is not a race condition, though, since we're anyway
165 b84cb9a0 Guido Trotter
      # going to realod the file after setting up the new watch.
166 46c9b31d Guido Trotter
      self.enable()
167 b84cb9a0 Guido Trotter
      self.reload_config()
168 b84cb9a0 Guido Trotter
    except errors.ConfdFatalError, err:
169 b84cb9a0 Guido Trotter
      logging.critical("Critical error, shutting down: %s" % err)
170 b84cb9a0 Guido Trotter
      sys.exit(constants.EXIT_FAILURE)
171 b84cb9a0 Guido Trotter
    except:
172 b84cb9a0 Guido Trotter
      # we need to catch any exception here, log it, but proceed, because even
173 b84cb9a0 Guido Trotter
      # if we failed handling a single request, we still want the confd to
174 b84cb9a0 Guido Trotter
      # continue working.
175 b84cb9a0 Guido Trotter
      logging.error("Unexpected exception", exc_info=True)
176 b84cb9a0 Guido Trotter
177 b84cb9a0 Guido Trotter
  def process_IN_MODIFY(self, event):
178 b84cb9a0 Guido Trotter
    # This gets called when the config file is modified. Note that this doesn't
179 b84cb9a0 Guido Trotter
    # usually happen in Ganeti, as the config file is normally replaced by a
180 b84cb9a0 Guido Trotter
    # new one, at filesystem level, rather than actually modified (see
181 b84cb9a0 Guido Trotter
    # utils.WriteFile)
182 b84cb9a0 Guido Trotter
    logging.debug("Received 'modify' inotify event for %s" % event.path)
183 b84cb9a0 Guido Trotter
184 b84cb9a0 Guido Trotter
    try:
185 b84cb9a0 Guido Trotter
      self.reload_config()
186 b84cb9a0 Guido Trotter
    except errors.ConfdFatalError, err:
187 b84cb9a0 Guido Trotter
      logging.critical("Critical error, shutting down: %s" % err)
188 b84cb9a0 Guido Trotter
      sys.exit(constants.EXIT_FAILURE)
189 b84cb9a0 Guido Trotter
    except:
190 b84cb9a0 Guido Trotter
      # we need to catch any exception here, log it, but proceed, because even
191 b84cb9a0 Guido Trotter
      # if we failed handling a single request, we still want the confd to
192 b84cb9a0 Guido Trotter
      # continue working.
193 b84cb9a0 Guido Trotter
      logging.error("Unexpected exception", exc_info=True)
194 b84cb9a0 Guido Trotter
195 b84cb9a0 Guido Trotter
  def process_default(self, event):
196 b84cb9a0 Guido Trotter
    logging.error("Received unhandled inotify event: %s" % event)
197 b84cb9a0 Guido Trotter
198 b84cb9a0 Guido Trotter
199 562bee4d Guido Trotter
class ConfdConfigurationReloader(object):
200 562bee4d Guido Trotter
  """Logic to control when to reload the ganeti configuration
201 562bee4d Guido Trotter
202 562bee4d Guido Trotter
  This class is able to alter between inotify and polling, to rate-limit the
203 562bee4d Guido Trotter
  number of reloads. When using inotify it also supports a fallback timed
204 562bee4d Guido Trotter
  check, to verify that the reload hasn't failed.
205 562bee4d Guido Trotter
206 562bee4d Guido Trotter
  """
207 562bee4d Guido Trotter
  def __init__(self, reader):
208 562bee4d Guido Trotter
    """Constructor for ConfdConfigurationReloader
209 562bee4d Guido Trotter
210 562bee4d Guido Trotter
    @type reader: L{ssconf.SimpleConfigReader}
211 562bee4d Guido Trotter
    @param reader: ganeti-confd SimpleConfigReader
212 562bee4d Guido Trotter
213 562bee4d Guido Trotter
    """
214 562bee4d Guido Trotter
    self.reader = reader
215 562bee4d Guido Trotter
216 562bee4d Guido Trotter
    # Asyncronous inotify handler for config changes
217 562bee4d Guido Trotter
    self.wm = pyinotify.WatchManager()
218 562bee4d Guido Trotter
    self.inotify_handler = ConfdInotifyEventHandler(self.wm, reader)
219 562bee4d Guido Trotter
    self.notifier = AsyncNotifier(self.wm, self.confd_event_handler)
220 562bee4d Guido Trotter
221 562bee4d Guido Trotter
222 6c948699 Michael Hanselmann
def CheckConfd(options, args):
223 6c948699 Michael Hanselmann
  """Initial checks whether to run exit with a failure.
224 b84cb9a0 Guido Trotter
225 b84cb9a0 Guido Trotter
  """
226 b84cb9a0 Guido Trotter
  # TODO: collapse HMAC daemons handling in daemons GenericMain, when we'll
227 b84cb9a0 Guido Trotter
  # have more than one.
228 b84cb9a0 Guido Trotter
  if not os.path.isfile(constants.HMAC_CLUSTER_KEY):
229 b84cb9a0 Guido Trotter
    print >> sys.stderr, "Need HMAC key %s to run" % constants.HMAC_CLUSTER_KEY
230 b84cb9a0 Guido Trotter
    sys.exit(constants.EXIT_FAILURE)
231 b84cb9a0 Guido Trotter
232 b84cb9a0 Guido Trotter
  ssconf.CheckMasterCandidate(options.debug)
233 b84cb9a0 Guido Trotter
234 b84cb9a0 Guido Trotter
235 6c948699 Michael Hanselmann
def ExecConfd(options, args):
236 6c948699 Michael Hanselmann
  """Main confd function, executed with PID file held
237 b84cb9a0 Guido Trotter
238 b84cb9a0 Guido Trotter
  """
239 f91c7223 Guido Trotter
  mainloop = daemon.Mainloop()
240 f91c7223 Guido Trotter
241 b84cb9a0 Guido Trotter
  # confd-level SimpleConfigReader
242 b84cb9a0 Guido Trotter
  reader = ssconf.SimpleConfigReader()
243 b84cb9a0 Guido Trotter
244 b84cb9a0 Guido Trotter
  # Asyncronous confd UDP server
245 b84cb9a0 Guido Trotter
  processor = ConfdProcessor(reader)
246 b84cb9a0 Guido Trotter
  server = ConfdAsyncUDPServer(options.bind_address, options.port, processor)
247 b84cb9a0 Guido Trotter
248 562bee4d Guido Trotter
  # Configuration reloader
249 562bee4d Guido Trotter
  reloader = ConfdConfigurationReloader(reader)
250 f91c7223 Guido Trotter
251 f91c7223 Guido Trotter
  mainloop.Run()
252 b84cb9a0 Guido Trotter
253 b84cb9a0 Guido Trotter
254 b84cb9a0 Guido Trotter
def main():
255 b84cb9a0 Guido Trotter
  """Main function for the confd daemon.
256 b84cb9a0 Guido Trotter
257 b84cb9a0 Guido Trotter
  """
258 b84cb9a0 Guido Trotter
  parser = OptionParser(description="Ganeti configuration daemon",
259 b84cb9a0 Guido Trotter
                        usage="%prog [-f] [-d] [-b ADDRESS]",
260 b84cb9a0 Guido Trotter
                        version="%%prog (ganeti) %s" %
261 b84cb9a0 Guido Trotter
                        constants.RELEASE_VERSION)
262 b84cb9a0 Guido Trotter
263 b84cb9a0 Guido Trotter
  dirs = [(val, constants.RUN_DIRS_MODE) for val in constants.SUB_RUN_DIRS]
264 b84cb9a0 Guido Trotter
  dirs.append((constants.LOG_OS_DIR, 0750))
265 b84cb9a0 Guido Trotter
  dirs.append((constants.LOCK_DIR, 1777))
266 6c948699 Michael Hanselmann
  daemon.GenericMain(constants.CONFD, parser, dirs, CheckConfd, ExecConfd)
267 b84cb9a0 Guido Trotter
268 b84cb9a0 Guido Trotter
269 6c948699 Michael Hanselmann
if __name__ == "__main__":
270 b84cb9a0 Guido Trotter
  main()