Revision 073c31a5

b/daemons/ganeti-rapi
31 31
import sys
32 32
import os
33 33
import os.path
34
import errno
34 35

  
35 36
try:
36 37
  from pyinotify import pyinotify # pylint: disable-msg=E0611
......
103 104
    @param filename: Path to file
104 105

  
105 106
    """
107
    logging.info("Reading users file at %s", filename)
106 108
    try:
107
      contents = utils.ReadFile(filename)
108
    except EnvironmentError, err:
109
      logging.warning("Error while reading %s: %s", filename, err)
110
      return False
109
      try:
110
        contents = utils.ReadFile(filename)
111
      except EnvironmentError, err:
112
        self._users = None
113
        if err.errno == errno.ENOENT:
114
          logging.warning("No users file at %s", filename)
115
        else:
116
          logging.warning("Error while reading %s: %s", filename, err)
117
        return False
111 118

  
112
    try:
113 119
      users = http.auth.ParsePasswordFile(contents)
120

  
114 121
    except Exception, err: # pylint: disable-msg=W0703
115 122
      # We don't care about the type of exception
116 123
      logging.error("Error while parsing %s: %s", filename, err)
117 124
      return False
118 125

  
119 126
    self._users = users
127

  
120 128
    return True
121 129

  
122 130
  def _GetRequestContext(self, req):
......
229 237
    return serializer.DumpJson(result)
230 238

  
231 239

  
232
class FileWatcher:
233
  def __init__(self, filename, cb):
240
class FileEventHandler(asyncnotifier.FileEventHandlerBase):
241
  def __init__(self, wm, path, cb):
234 242
    """Initializes this class.
235 243

  
236
    @type filename: string
237
    @param filename: File to watch
244
    @param wm: Inotify watch manager
245
    @type path: string
246
    @param path: File path
238 247
    @type cb: callable
239 248
    @param cb: Function called on file change
240 249

  
241 250
    """
242
    self._filename = filename
251
    asyncnotifier.FileEventHandlerBase.__init__(self, wm)
252

  
243 253
    self._cb = cb
254
    self._filename = os.path.basename(path)
244 255

  
245
    wm = pyinotify.WatchManager()
246
    self._handler = asyncnotifier.SingleFileEventHandler(wm, self._OnInotify,
247
                                                         filename)
248
    asyncnotifier.AsyncNotifier(wm, default_proc_fun=self._handler)
249
    self._handler.enable()
256
    # Class '...' has no 'IN_...' member, pylint: disable-msg=E1103
257
    mask = (pyinotify.EventsCodes.IN_CLOSE_WRITE |
258
            pyinotify.EventsCodes.IN_DELETE |
259
            pyinotify.EventsCodes.IN_MOVED_FROM |
260
            pyinotify.EventsCodes.IN_MOVED_TO)
250 261

  
251
  def _OnInotify(self, notifier_enabled):
252
    """Called upon update of the RAPI users file by pyinotify.
262
    self._handle = self.AddWatch(os.path.dirname(path), mask)
253 263

  
254
    @type notifier_enabled: boolean
255
    @param notifier_enabled: whether the notifier is still enabled
264
  def process_default(self, event):
265
    """Called upon inotify event.
256 266

  
257 267
    """
258
    logging.info("Reloading modified %s", self._filename)
268
    if event.name == self._filename:
269
      logging.debug("Received inotify event %s", event)
270
      self._cb()
271

  
272

  
273
def SetupFileWatcher(filename, cb):
274
  """Configures an inotify watcher for a file.
259 275

  
260
    self._cb()
276
  @type filename: string
277
  @param filename: File to watch
278
  @type cb: callable
279
  @param cb: Function called on file change
261 280

  
262
    # Renable the watch again if we'd an atomic update of the file (e.g. mv)
263
    if not notifier_enabled:
264
      self._handler.enable()
281
  """
282
  wm = pyinotify.WatchManager()
283
  handler = FileEventHandler(wm, filename, cb)
284
  asyncnotifier.AsyncNotifier(wm, default_proc_fun=handler)
265 285

  
266 286

  
267 287
def CheckRapi(options, args):
......
294 314
                               ssl_verify_peer=False,
295 315
                               request_executor_class=JsonErrorRequestExecutor)
296 316

  
297
  if os.path.exists(constants.RAPI_USERS_FILE):
298
    # Setup file watcher (it'll be driven by asyncore)
299
    FileWatcher(constants.RAPI_USERS_FILE,
300
                compat.partial(server.LoadUsers, constants.RAPI_USERS_FILE))
317
  # Setup file watcher (it'll be driven by asyncore)
318
  SetupFileWatcher(constants.RAPI_USERS_FILE,
319
                   compat.partial(server.LoadUsers, constants.RAPI_USERS_FILE))
301 320

  
302 321
  server.LoadUsers(constants.RAPI_USERS_FILE)
303 322

  
304 323
  # pylint: disable-msg=E1101
305 324
  # it seems pylint doesn't see the second parent class there
306 325
  server.Start()
326

  
307 327
  return (mainloop, server)
308 328

  
329

  
309 330
def ExecRapi(options, args, prep_data): # pylint: disable-msg=W0613
310 331
  """Main remote API function, executed with the PID file held.
311 332

  
b/doc/rapi.rst
21 21
-------------------
22 22

  
23 23
``ganeti-rapi`` reads users and passwords from a file (usually
24
``/var/lib/ganeti/rapi_users``) on startup. If the file existed when
25
``ganeti-rapi`` was started, it'll automatically reload the file upon
26
changes. If the users file is newly created, ``ganeti-rapi`` must be
27
restarted.
24
``/var/lib/ganeti/rapi_users``) on startup. Changes to the file will be
25
read automatically.
28 26

  
29 27
Each line consists of two or three fields separated by whitespace. The
30 28
first two fields are for username and password. The third field is

Also available in: Unified diff