Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-confd @ 26d3fd2f

History | View | Annotate | Download (9 kB)

1 b84cb9a0 Guido Trotter
#!/usr/bin/python
2 b84cb9a0 Guido Trotter
#
3 b84cb9a0 Guido Trotter
4 8b312c1d Manuel Franceschini
# Copyright (C) 2009, 2010 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 7260cfbe Iustin Pop
# pylint: disable-msg=C0103
30 7260cfbe Iustin Pop
# C0103: Invalid name ganeti-confd
31 7260cfbe Iustin Pop
32 b84cb9a0 Guido Trotter
import os
33 b84cb9a0 Guido Trotter
import sys
34 b84cb9a0 Guido Trotter
import logging
35 e2be81cf Guido Trotter
import time
36 b84cb9a0 Guido Trotter
37 ad54f3d2 Guido Trotter
try:
38 7260cfbe Iustin Pop
  # pylint: disable-msg=E0611
39 ad54f3d2 Guido Trotter
  from pyinotify import pyinotify
40 ad54f3d2 Guido Trotter
except ImportError:
41 ad54f3d2 Guido Trotter
  import pyinotify
42 ad54f3d2 Guido Trotter
43 b84cb9a0 Guido Trotter
from optparse import OptionParser
44 b84cb9a0 Guido Trotter
45 e1081705 Guido Trotter
from ganeti import asyncnotifier
46 e1081705 Guido Trotter
from ganeti import confd
47 e1081705 Guido Trotter
from ganeti.confd import server as confd_server
48 b84cb9a0 Guido Trotter
from ganeti import constants
49 b84cb9a0 Guido Trotter
from ganeti import errors
50 b84cb9a0 Guido Trotter
from ganeti import daemon
51 d8bcfe21 Manuel Franceschini
from ganeti import netutils
52 b84cb9a0 Guido Trotter
53 b84cb9a0 Guido Trotter
54 5f3269fc Guido Trotter
class ConfdAsyncUDPServer(daemon.AsyncUDPSocket):
55 b84cb9a0 Guido Trotter
  """The confd udp server, suitable for use with asyncore.
56 b84cb9a0 Guido Trotter
57 b84cb9a0 Guido Trotter
  """
58 b84cb9a0 Guido Trotter
  def __init__(self, bind_address, port, processor):
59 b84cb9a0 Guido Trotter
    """Constructor for ConfdAsyncUDPServer
60 b84cb9a0 Guido Trotter
61 b84cb9a0 Guido Trotter
    @type bind_address: string
62 d8bcfe21 Manuel Franceschini
    @param bind_address: socket bind address
63 b84cb9a0 Guido Trotter
    @type port: int
64 b84cb9a0 Guido Trotter
    @param port: udp port
65 b84cb9a0 Guido Trotter
    @type processor: L{confd.server.ConfdProcessor}
66 fe759e4c Guido Trotter
    @param processor: ConfdProcessor to use to handle queries
67 b84cb9a0 Guido Trotter
68 b84cb9a0 Guido Trotter
    """
69 8b312c1d Manuel Franceschini
    family = netutils.IPAddress.GetAddressFamily(bind_address)
70 8b312c1d Manuel Franceschini
    daemon.AsyncUDPSocket.__init__(self, family)
71 b84cb9a0 Guido Trotter
    self.bind_address = bind_address
72 b84cb9a0 Guido Trotter
    self.port = port
73 b84cb9a0 Guido Trotter
    self.processor = processor
74 b84cb9a0 Guido Trotter
    self.bind((bind_address, port))
75 07b8a2b5 Iustin Pop
    logging.debug("listening on ('%s':%d)", bind_address, port)
76 b84cb9a0 Guido Trotter
77 5f3269fc Guido Trotter
  # this method is overriding a daemon.AsyncUDPSocket method
78 5f3269fc Guido Trotter
  def handle_datagram(self, payload_in, ip, port):
79 9748ab35 Guido Trotter
    try:
80 e1081705 Guido Trotter
      query = confd.UnpackMagic(payload_in)
81 9748ab35 Guido Trotter
    except errors.ConfdMagicError, err:
82 9748ab35 Guido Trotter
      logging.debug(err)
83 a3758ab2 Guido Trotter
      return
84 a3758ab2 Guido Trotter
85 a3758ab2 Guido Trotter
    answer =  self.processor.ExecQuery(query, ip, port)
86 a3758ab2 Guido Trotter
    if answer is not None:
87 86488201 Guido Trotter
      try:
88 e1081705 Guido Trotter
        self.enqueue_send(ip, port, confd.PackMagic(answer))
89 86488201 Guido Trotter
      except errors.UdpDataSizeError:
90 86488201 Guido Trotter
        logging.error("Reply too big to fit in an udp packet.")
91 b84cb9a0 Guido Trotter
92 b84cb9a0 Guido Trotter
93 562bee4d Guido Trotter
class ConfdConfigurationReloader(object):
94 562bee4d Guido Trotter
  """Logic to control when to reload the ganeti configuration
95 562bee4d Guido Trotter
96 562bee4d Guido Trotter
  This class is able to alter between inotify and polling, to rate-limit the
97 562bee4d Guido Trotter
  number of reloads. When using inotify it also supports a fallback timed
98 562bee4d Guido Trotter
  check, to verify that the reload hasn't failed.
99 562bee4d Guido Trotter
100 562bee4d Guido Trotter
  """
101 05f1ebf3 Guido Trotter
  def __init__(self, processor, mainloop):
102 562bee4d Guido Trotter
    """Constructor for ConfdConfigurationReloader
103 562bee4d Guido Trotter
104 05f1ebf3 Guido Trotter
    @type processor: L{confd.server.ConfdProcessor}
105 05f1ebf3 Guido Trotter
    @param processor: ganeti-confd ConfdProcessor
106 e2be81cf Guido Trotter
    @type mainloop: L{daemon.Mainloop}
107 e2be81cf Guido Trotter
    @param mainloop: ganeti-confd mainloop
108 562bee4d Guido Trotter
109 562bee4d Guido Trotter
    """
110 05f1ebf3 Guido Trotter
    self.processor = processor
111 e2be81cf Guido Trotter
    self.mainloop = mainloop
112 e2be81cf Guido Trotter
113 c6259dbc Guido Trotter
    self.polling = True
114 e2be81cf Guido Trotter
    self.last_notification = 0
115 562bee4d Guido Trotter
116 562bee4d Guido Trotter
    # Asyncronous inotify handler for config changes
117 c666f1f4 Guido Trotter
    cfg_file = constants.CLUSTER_CONF_FILE
118 562bee4d Guido Trotter
    self.wm = pyinotify.WatchManager()
119 c666f1f4 Guido Trotter
    self.inotify_handler = asyncnotifier.SingleFileEventHandler(self.wm,
120 c666f1f4 Guido Trotter
                                                                self.OnInotify,
121 c666f1f4 Guido Trotter
                                                                cfg_file)
122 e9c8deab Guido Trotter
    notifier_class = asyncnotifier.ErrorLoggingAsyncNotifier
123 e9c8deab Guido Trotter
    self.notifier = notifier_class(self.wm, self.inotify_handler)
124 4afe249b Guido Trotter
125 e2be81cf Guido Trotter
    self.timer_handle = None
126 e2be81cf Guido Trotter
    self._EnableTimer()
127 e2be81cf Guido Trotter
128 4afe249b Guido Trotter
  def OnInotify(self, notifier_enabled):
129 4afe249b Guido Trotter
    """Receive an inotify notification.
130 4afe249b Guido Trotter
131 4afe249b Guido Trotter
    @type notifier_enabled: boolean
132 4afe249b Guido Trotter
    @param notifier_enabled: whether the notifier is still enabled
133 4afe249b Guido Trotter
134 4afe249b Guido Trotter
    """
135 e2be81cf Guido Trotter
    current_time = time.time()
136 e2be81cf Guido Trotter
    time_delta = current_time - self.last_notification
137 e2be81cf Guido Trotter
    self.last_notification = current_time
138 e2be81cf Guido Trotter
139 e2be81cf Guido Trotter
    if time_delta < constants.CONFD_CONFIG_RELOAD_RATELIMIT:
140 e2be81cf Guido Trotter
      logging.debug("Moving from inotify mode to polling mode")
141 e2be81cf Guido Trotter
      self.polling = True
142 e2be81cf Guido Trotter
      if notifier_enabled:
143 176d3122 Guido Trotter
        self.inotify_handler.disable()
144 e2be81cf Guido Trotter
145 e2be81cf Guido Trotter
    if not self.polling and not notifier_enabled:
146 ef4ca33b Guido Trotter
      try:
147 ef4ca33b Guido Trotter
        self.inotify_handler.enable()
148 ef4ca33b Guido Trotter
      except errors.InotifyError:
149 22d3e184 Guido Trotter
        self.polling = True
150 4afe249b Guido Trotter
151 4afe249b Guido Trotter
    try:
152 05f1ebf3 Guido Trotter
      reloaded = self.processor.reader.Reload()
153 4afe249b Guido Trotter
      if reloaded:
154 4afe249b Guido Trotter
        logging.info("Reloaded ganeti config")
155 4afe249b Guido Trotter
      else:
156 4afe249b Guido Trotter
        logging.debug("Skipped double config reload")
157 4afe249b Guido Trotter
    except errors.ConfigurationError:
158 22d3e184 Guido Trotter
      self.DisableConfd()
159 22d3e184 Guido Trotter
      self.inotify_handler.disable()
160 22d3e184 Guido Trotter
      return
161 4afe249b Guido Trotter
162 e2be81cf Guido Trotter
    # Reset the timer. If we're polling it will go to the polling rate, if
163 e2be81cf Guido Trotter
    # we're not it will delay it again to its base safe timeout.
164 22d3e184 Guido Trotter
    self._ResetTimer()
165 e2be81cf Guido Trotter
166 e2be81cf Guido Trotter
  def _DisableTimer(self):
167 e2be81cf Guido Trotter
    if self.timer_handle is not None:
168 e2be81cf Guido Trotter
      self.mainloop.scheduler.cancel(self.timer_handle)
169 e2be81cf Guido Trotter
      self.timer_handle = None
170 e2be81cf Guido Trotter
171 e2be81cf Guido Trotter
  def _EnableTimer(self):
172 e2be81cf Guido Trotter
    if self.polling:
173 e2be81cf Guido Trotter
      timeout = constants.CONFD_CONFIG_RELOAD_RATELIMIT
174 e2be81cf Guido Trotter
    else:
175 e2be81cf Guido Trotter
      timeout = constants.CONFD_CONFIG_RELOAD_TIMEOUT
176 e2be81cf Guido Trotter
177 e2be81cf Guido Trotter
    if self.timer_handle is None:
178 e2be81cf Guido Trotter
      self.timer_handle = self.mainloop.scheduler.enter(
179 e2be81cf Guido Trotter
        timeout, 1, self.OnTimer, [])
180 e2be81cf Guido Trotter
181 22d3e184 Guido Trotter
  def _ResetTimer(self):
182 22d3e184 Guido Trotter
    self._DisableTimer()
183 22d3e184 Guido Trotter
    self._EnableTimer()
184 22d3e184 Guido Trotter
185 e2be81cf Guido Trotter
  def OnTimer(self):
186 e2be81cf Guido Trotter
    """Function called when the timer fires
187 e2be81cf Guido Trotter
188 e2be81cf Guido Trotter
    """
189 e2be81cf Guido Trotter
    self.timer_handle = None
190 22d3e184 Guido Trotter
    reloaded = False
191 22d3e184 Guido Trotter
    was_disabled = False
192 e2be81cf Guido Trotter
    try:
193 22d3e184 Guido Trotter
      if self.processor.reader is None:
194 22d3e184 Guido Trotter
        was_disabled = True
195 22d3e184 Guido Trotter
        self.EnableConfd()
196 22d3e184 Guido Trotter
        reloaded = True
197 22d3e184 Guido Trotter
      else:
198 22d3e184 Guido Trotter
        reloaded = self.processor.reader.Reload()
199 e2be81cf Guido Trotter
    except errors.ConfigurationError:
200 a544f755 Guido Trotter
      self.DisableConfd(silent=was_disabled)
201 22d3e184 Guido Trotter
      return
202 e2be81cf Guido Trotter
203 e2be81cf Guido Trotter
    if self.polling and reloaded:
204 e2be81cf Guido Trotter
      logging.info("Reloaded ganeti config")
205 e2be81cf Guido Trotter
    elif reloaded:
206 e2be81cf Guido Trotter
      # We have reloaded the config files, but received no inotify event.  If
207 e2be81cf Guido Trotter
      # an event is pending though, we just happen to have timed out before
208 e2be81cf Guido Trotter
      # receiving it, so this is not a problem, and we shouldn't alert
209 22d3e184 Guido Trotter
      if not self.notifier.check_events() and not was_disabled:
210 e2be81cf Guido Trotter
        logging.warning("Config file reload at timeout (inotify failure)")
211 e2be81cf Guido Trotter
    elif self.polling:
212 e2be81cf Guido Trotter
      # We're polling, but we haven't reloaded the config:
213 e2be81cf Guido Trotter
      # Going back to inotify mode
214 e2be81cf Guido Trotter
      logging.debug("Moving from polling mode to inotify mode")
215 e2be81cf Guido Trotter
      self.polling = False
216 22d3e184 Guido Trotter
      try:
217 22d3e184 Guido Trotter
        self.inotify_handler.enable()
218 22d3e184 Guido Trotter
      except errors.InotifyError:
219 22d3e184 Guido Trotter
        self.polling = True
220 e2be81cf Guido Trotter
    else:
221 e2be81cf Guido Trotter
      logging.debug("Performed configuration check")
222 e2be81cf Guido Trotter
223 e2be81cf Guido Trotter
    self._EnableTimer()
224 562bee4d Guido Trotter
225 a544f755 Guido Trotter
  def DisableConfd(self, silent=False):
226 22d3e184 Guido Trotter
    """Puts confd in non-serving mode
227 22d3e184 Guido Trotter
228 22d3e184 Guido Trotter
    """
229 a544f755 Guido Trotter
    if not silent:
230 a544f755 Guido Trotter
      logging.warning("Confd is being disabled")
231 22d3e184 Guido Trotter
    self.processor.Disable()
232 22d3e184 Guido Trotter
    self.polling = False
233 22d3e184 Guido Trotter
    self._ResetTimer()
234 22d3e184 Guido Trotter
235 22d3e184 Guido Trotter
  def EnableConfd(self):
236 22d3e184 Guido Trotter
    self.processor.Enable()
237 22d3e184 Guido Trotter
    logging.warning("Confd is being enabled")
238 22d3e184 Guido Trotter
    self.polling = True
239 22d3e184 Guido Trotter
    self._ResetTimer()
240 22d3e184 Guido Trotter
241 562bee4d Guido Trotter
242 2d54e29c Iustin Pop
def CheckConfd(_, args):
243 6c948699 Michael Hanselmann
  """Initial checks whether to run exit with a failure.
244 b84cb9a0 Guido Trotter
245 b84cb9a0 Guido Trotter
  """
246 f93427cd Iustin Pop
  if args: # confd doesn't take any arguments
247 f93427cd Iustin Pop
    print >> sys.stderr, ("Usage: %s [-f] [-d] [-b ADDRESS]" % sys.argv[0])
248 f93427cd Iustin Pop
    sys.exit(constants.EXIT_FAILURE)
249 f93427cd Iustin Pop
250 b84cb9a0 Guido Trotter
  # TODO: collapse HMAC daemons handling in daemons GenericMain, when we'll
251 b84cb9a0 Guido Trotter
  # have more than one.
252 6b7d5878 Michael Hanselmann
  if not os.path.isfile(constants.CONFD_HMAC_KEY):
253 6b7d5878 Michael Hanselmann
    print >> sys.stderr, "Need HMAC key %s to run" % constants.CONFD_HMAC_KEY
254 b84cb9a0 Guido Trotter
    sys.exit(constants.EXIT_FAILURE)
255 b84cb9a0 Guido Trotter
256 d8bcfe21 Manuel Franceschini
  # TODO: once we have a cluster param specifying the address family
257 d8bcfe21 Manuel Franceschini
  # preference, we need to check if the requested options.bind_address does not
258 d8bcfe21 Manuel Franceschini
  # conflict with that. If so, we might warn or EXIT_FAILURE.
259 d8bcfe21 Manuel Franceschini
260 b84cb9a0 Guido Trotter
261 2d54e29c Iustin Pop
def ExecConfd(options, _):
262 6c948699 Michael Hanselmann
  """Main confd function, executed with PID file held
263 b84cb9a0 Guido Trotter
264 b84cb9a0 Guido Trotter
  """
265 c9ca81c9 Iustin Pop
  # TODO: clarify how the server and reloader variables work (they are
266 c9ca81c9 Iustin Pop
  # not used)
267 c9ca81c9 Iustin Pop
  # pylint: disable-msg=W0612
268 f91c7223 Guido Trotter
  mainloop = daemon.Mainloop()
269 f91c7223 Guido Trotter
270 b84cb9a0 Guido Trotter
  # Asyncronous confd UDP server
271 e1081705 Guido Trotter
  processor = confd_server.ConfdProcessor()
272 e369f21d Guido Trotter
  try:
273 e369f21d Guido Trotter
    processor.Enable()
274 e369f21d Guido Trotter
  except errors.ConfigurationError:
275 4d4a651d Michael Hanselmann
    # If enabling the processor has failed, we can still go on, but confd will
276 4d4a651d Michael Hanselmann
    # be disabled
277 a544f755 Guido Trotter
    logging.warning("Confd is starting in disabled mode")
278 2d54e29c Iustin Pop
279 b84cb9a0 Guido Trotter
  server = ConfdAsyncUDPServer(options.bind_address, options.port, processor)
280 b84cb9a0 Guido Trotter
281 562bee4d Guido Trotter
  # Configuration reloader
282 05f1ebf3 Guido Trotter
  reloader = ConfdConfigurationReloader(processor, mainloop)
283 f91c7223 Guido Trotter
284 f91c7223 Guido Trotter
  mainloop.Run()
285 b84cb9a0 Guido Trotter
286 b84cb9a0 Guido Trotter
287 b84cb9a0 Guido Trotter
def main():
288 b84cb9a0 Guido Trotter
  """Main function for the confd daemon.
289 b84cb9a0 Guido Trotter
290 b84cb9a0 Guido Trotter
  """
291 b84cb9a0 Guido Trotter
  parser = OptionParser(description="Ganeti configuration daemon",
292 b84cb9a0 Guido Trotter
                        usage="%prog [-f] [-d] [-b ADDRESS]",
293 b84cb9a0 Guido Trotter
                        version="%%prog (ganeti) %s" %
294 b84cb9a0 Guido Trotter
                        constants.RELEASE_VERSION)
295 b84cb9a0 Guido Trotter
296 fd346851 René Nussbaumer
  daemon.GenericMain(constants.CONFD, parser, CheckConfd, ExecConfd)
297 b84cb9a0 Guido Trotter
298 b84cb9a0 Guido Trotter
299 6c948699 Michael Hanselmann
if __name__ == "__main__":
300 b84cb9a0 Guido Trotter
  main()