From e543a42f56dcc20a7232eb5667200749475032f3 Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Wed, 13 Oct 2010 12:43:27 +0200
Subject: [PATCH] Extract base class from SingleFileEventHandler
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The base class can contain code useful to other inotify users.
As it is β€œSingleFileEventHandler” can not be used in ganeti-rapi,
therefore it'll use its own small inotify handler class based
on this base class.

Signed-off-by: Michael Hanselmann <hansmi@google.com>
Reviewed-by: Iustin Pop <iustin@google.com>
---
 lib/asyncnotifier.py                  | 98 +++++++++++++++++++--------
 test/ganeti.asyncnotifier_unittest.py | 20 ++++++
 2 files changed, 90 insertions(+), 28 deletions(-)

diff --git a/lib/asyncnotifier.py b/lib/asyncnotifier.py
index e60c26087..e9b68cac9 100644
--- a/lib/asyncnotifier.py
+++ b/lib/asyncnotifier.py
@@ -76,11 +76,58 @@ class ErrorLoggingAsyncNotifier(AsyncNotifier,
   """
 
 
-class SingleFileEventHandler(pyinotify.ProcessEvent):
-  """Handle modify events for a single file.
+class FileEventHandlerBase(pyinotify.ProcessEvent):
+  """Base class for file event handlers.
+
+  @ivar watch_manager: Inotify watch manager
 
   """
+  def __init__(self, watch_manager):
+    """Initializes this class.
+
+    @type watch_manager: pyinotify.WatchManager
+    @param watch_manager: inotify watch manager
+
+    """
+    # pylint: disable-msg=W0231
+    # no need to call the parent's constructor
+    self.watch_manager = watch_manager
+
+  def process_default(self, event):
+    logging.error("Received unhandled inotify event: %s", event)
+
+  def AddWatch(self, filename, mask):
+    """Adds a file watch.
+
+    @param filename: Path to file
+    @param mask: Inotify event mask
+    @return: Result
+
+    """
+    result = self.watch_manager.add_watch(filename, mask)
+
+    ret = result.get(filename, -1)
+    if ret <= 0:
+      raise errors.InotifyError("Could not add inotify watcher (%s)" % ret)
+
+    return result[filename]
 
+  def RemoveWatch(self, handle):
+    """Removes a handle from the watcher.
+
+    @param handle: Inotify handle
+    @return: Whether removal was successful
+
+    """
+    result = self.watch_manager.rm_watch(handle)
+
+    return result[handle]
+
+
+class SingleFileEventHandler(FileEventHandlerBase):
+  """Handle modify events for a single file.
+
+  """
   def __init__(self, watch_manager, callback, filename):
     """Constructor for SingleFileEventHandler
 
@@ -92,34 +139,32 @@ class SingleFileEventHandler(pyinotify.ProcessEvent):
     @param filename: config file to watch
 
     """
-    # pylint: disable-msg=W0231
-    # no need to call the parent's constructor
-    self.watch_manager = watch_manager
-    self.callback = callback
-    self.mask = pyinotify.EventsCodes.ALL_FLAGS["IN_IGNORED"] | \
-                pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"]
-    self.file = filename
-    self.watch_handle = None
+    FileEventHandlerBase.__init__(self, watch_manager)
+
+    self._callback = callback
+    self._filename = filename
+
+    self._watch_handle = None
 
   def enable(self):
-    """Watch the given file
+    """Watch the given file.
 
     """
-    if self.watch_handle is None:
-      result = self.watch_manager.add_watch(self.file, self.mask)
-      if not self.file in result or result[self.file] <= 0:
-        raise errors.InotifyError("Could not add inotify watcher")
-      else:
-        self.watch_handle = result[self.file]
+    if self._watch_handle is not None:
+      return
+
+    # Class '...' has no 'IN_...' member, pylint: disable-msg=E1103
+    mask = (pyinotify.EventsCodes.IN_MODIFY |
+            pyinotify.EventsCodes.IN_IGNORED)
+
+    self._watch_handle = self.AddWatch(self._filename, mask)
 
   def disable(self):
-    """Stop watching the given file
+    """Stop watching the given file.
 
     """
-    if self.watch_handle is not None:
-      result = self.watch_manager.rm_watch(self.watch_handle)
-      if result[self.watch_handle]:
-        self.watch_handle = None
+    if self._watch_handle is not None and self.RemoveWatch(self._watch_handle):
+      self._watch_handle = None
 
   # pylint: disable-msg=C0103
   # this overrides a method in pyinotify.ProcessEvent
@@ -132,8 +177,8 @@ class SingleFileEventHandler(pyinotify.ProcessEvent):
     # case we'll need to create a watcher for the "new" file. This can be done
     # by the callback by calling "enable" again on us.
     logging.debug("Received 'ignored' inotify event for %s", event.path)
-    self.watch_handle = None
-    self.callback(False)
+    self._watch_handle = None
+    self._callback(False)
 
   # pylint: disable-msg=C0103
   # this overrides a method in pyinotify.ProcessEvent
@@ -143,7 +188,4 @@ class SingleFileEventHandler(pyinotify.ProcessEvent):
     # replacing any file with a new one, at filesystem level, rather than
     # actually changing it. (see utils.WriteFile)
     logging.debug("Received 'modify' inotify event for %s", event.path)
-    self.callback(True)
-
-  def process_default(self, event):
-    logging.error("Received unhandled inotify event: %s", event)
+    self._callback(True)
diff --git a/test/ganeti.asyncnotifier_unittest.py b/test/ganeti.asyncnotifier_unittest.py
index 0e9c1e484..adef2dd20 100755
--- a/test/ganeti.asyncnotifier_unittest.py
+++ b/test/ganeti.asyncnotifier_unittest.py
@@ -24,6 +24,8 @@
 import unittest
 import signal
 import os
+import tempfile
+import shutil
 
 try:
   # pylint: disable-msg=E0611
@@ -149,5 +151,23 @@ class TestSingleFileEventHandler(testutils.GanetiTestCase):
     self.assertEquals(self.notifiers[self.NOTIFIER_TERM].error_count, 0)
 
 
+class TestSingleFileEventHandlerError(unittest.TestCase):
+  def setUp(self):
+    self.tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    shutil.rmtree(self.tmpdir)
+
+  def test(self):
+    wm = pyinotify.WatchManager()
+    handler = asyncnotifier.SingleFileEventHandler(wm, None,
+                                                   utils.PathJoin(self.tmpdir,
+                                                                  "nonexist"))
+    self.assertRaises(errors.InotifyError, handler.enable)
+    self.assertRaises(errors.InotifyError, handler.enable)
+    handler.disable()
+    self.assertRaises(errors.InotifyError, handler.enable)
+
+
 if __name__ == "__main__":
   testutils.GanetiTestProgram()
-- 
GitLab