RAPI server: Move user file watching out, update documentation
authorMichael Hanselmann <hansmi@google.com>
Mon, 13 Sep 2010 15:16:06 +0000 (17:16 +0200)
committerMichael Hanselmann <hansmi@google.com>
Mon, 13 Sep 2010 15:16:35 +0000 (17:16 +0200)
This patch moves the code watching the users file into a
a separate class to not mix it with HTTP serving. The users
file is now driven from outside the HTTP server class.

Also the documentation is updated to mention the automatic
reloading.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: RenĂ© Nussbaumer <rn@google.com>

daemons/ganeti-rapi
doc/rapi.rst

index c022c8b..09ad68a 100755 (executable)
@@ -44,6 +44,7 @@ from ganeti import daemon
 from ganeti import ssconf
 from ganeti import luxi
 from ganeti import serializer
+from ganeti import compat
 from ganeti.rapi import connector
 
 import ganeti.http.auth   # pylint: disable-msg=W0611
@@ -88,43 +89,32 @@ class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
 
   def __init__(self, *args, **kwargs):
     # pylint: disable-msg=W0233
-  # it seems pylint doesn't see the second parent class there
+    # it seems pylint doesn't see the second parent class there
     http.server.HttpServer.__init__(self, *args, **kwargs)
     http.auth.HttpServerRequestAuthentication.__init__(self)
     self._resmap = connector.Mapper()
+    self._users = None
 
-    # Load password file
-    if os.path.isfile(constants.RAPI_USERS_FILE):
-      wm = pyinotify.WatchManager()
-      hdl = asyncnotifier.SingleFileEventHandler(wm, self._OnUsersFileUpdate,
-                                                 constants.RAPI_USERS_FILE)
-      self._users_inotify_handler = hdl
-      asyncnotifier.AsyncNotifier(wm, default_proc_fun=hdl)
-      self._users = None
-      self._OnUsersFileUpdate(False)
-    else:
-      self._users = None
-
-  def _OnUsersFileUpdate(self, notifier_enabled):
-    """Called upon update of the RAPI users file by pyinotify.
+  def LoadUsers(self, filename):
+    """Loads a file containing users and passwords.
 
-    @type notifier_enabled: boolean
-    @param notifier_enabled: whether the notifier is still enabled
+    @type filename: string
+    @param filename: Path to file
 
     """
-    logging.info("Reloading modified %s", constants.RAPI_USERS_FILE)
+    if not os.path.isfile(constants.RAPI_USERS_FILE):
+      logging.warning("Users file %s not found", filename)
+      return False
 
     try:
-      users = http.auth.ReadPasswordFile(constants.RAPI_USERS_FILE)
-      self._users = users
+      users = http.auth.ReadPasswordFile(filename)
     except Exception, err: # pylint: disable-msg=W0703
       # We don't care about the type of exception
-      logging.error("Error while reading %s: %s", constants.RAPI_USERS_FILE,
-                    err)
+      logging.error("Error while reading %s: %s", filename, err)
+      return False
 
-    # Renable the watch again if we'd an atomic update of the file (e.g. mv)
-    if not notifier_enabled:
-      self._users_inotify_handler.enable()
+    self._users = users
+    return True
 
   def _GetRequestContext(self, req):
     """Returns the context for a request.
@@ -236,6 +226,41 @@ class RemoteApiHttpServer(http.auth.HttpServerRequestAuthentication,
     return serializer.DumpJson(result)
 
 
+class FileWatcher:
+  def __init__(self, filename, cb):
+    """Initializes this class.
+
+    @type filename: string
+    @param filename: File to watch
+    @type cb: callable
+    @param cb: Function called on file change
+
+    """
+    self._filename = filename
+    self._cb = cb
+
+    wm = pyinotify.WatchManager()
+    self._handler = asyncnotifier.SingleFileEventHandler(wm, self._OnInotify,
+                                                         filename)
+    asyncnotifier.AsyncNotifier(wm, default_proc_fun=self._handler)
+    self._handler.enable()
+
+  def _OnInotify(self, notifier_enabled):
+    """Called upon update of the RAPI users file by pyinotify.
+
+    @type notifier_enabled: boolean
+    @param notifier_enabled: whether the notifier is still enabled
+
+    """
+    logging.info("Reloading modified %s", self._filename)
+
+    self._cb()
+
+    # Renable the watch again if we'd an atomic update of the file (e.g. mv)
+    if not notifier_enabled:
+      self._handler.enable()
+
+
 def CheckRapi(options, args):
   """Initial checks whether to run or exit with a failure.
 
@@ -265,6 +290,14 @@ def ExecRapi(options, _):
                                ssl_params=options.ssl_params,
                                ssl_verify_peer=False,
                                request_executor_class=JsonErrorRequestExecutor)
+
+  if os.path.exists(constants.RAPI_USERS_FILE):
+    # Setup file watcher (it'll be driven by asyncore)
+    FileWatcher(constants.RAPI_USERS_FILE,
+                compat.partial(server.LoadUsers, constants.RAPI_USERS_FILE))
+
+  server.LoadUsers(constants.RAPI_USERS_FILE)
+
   # pylint: disable-msg=E1101
   # it seems pylint doesn't see the second parent class there
   server.Start()
index b01d217..a873274 100644 (file)
@@ -21,8 +21,10 @@ Users and passwords
 -------------------
 
 ``ganeti-rapi`` reads users and passwords from a file (usually
-``/var/lib/ganeti/rapi_users``) on startup. After modifying the password
-file, ``ganeti-rapi`` must be restarted.
+``/var/lib/ganeti/rapi_users``) on startup. If the file existed when
+``ganeti-rapi`` was started, it'll automatically reload the file upon
+changes. If the users file is newly created, ``ganeti-rapi`` must be
+restarted.
 
 Each line consists of two or three fields separated by whitespace. The
 first two fields are for username and password. The third field is