Package ganeti :: Package server :: Module confd
[hide private]
[frames] | no frames]

Source Code for Module ganeti.server.confd

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