Statistics
| Branch: | Tag: | Revision:

root / daemons / ganeti-confd @ 7e3c1da6

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