Package ganeti :: Module asyncnotifier
[hide private]
[frames] | no frames]

Source Code for Module ganeti.asyncnotifier

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2009 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  """Asynchronous pyinotify implementation""" 
 23   
 24   
 25  import asyncore 
 26  import logging 
 27   
 28  try: 
 29    # pylint: disable=E0611 
 30    from pyinotify import pyinotify 
 31  except ImportError: 
 32    import pyinotify 
 33   
 34  from ganeti import daemon 
 35  from ganeti import errors 
 36   
 37   
 38  # We contributed the AsyncNotifier class back to python-pyinotify, and it's 
 39  # part of their codebase since version 0.8.7. This code can be removed once 
 40  # we'll be ready to depend on python-pyinotify >= 0.8.7 
41 -class AsyncNotifier(asyncore.file_dispatcher):
42 """An asyncore dispatcher for inotify events. 43 44 """ 45 # pylint: disable=W0622,W0212
46 - def __init__(self, watch_manager, default_proc_fun=None, map=None):
47 """Initializes this class. 48 49 This is a a special asyncore file_dispatcher that actually wraps a 50 pyinotify Notifier, making it asyncronous. 51 52 """ 53 if default_proc_fun is None: 54 default_proc_fun = pyinotify.ProcessEvent() 55 56 self.notifier = pyinotify.Notifier(watch_manager, default_proc_fun) 57 58 # here we need to steal the file descriptor from the notifier, so we can 59 # use it in the global asyncore select, and avoid calling the 60 # check_events() function of the notifier (which doesn't allow us to select 61 # together with other file descriptors) 62 self.fd = self.notifier._fd 63 asyncore.file_dispatcher.__init__(self, self.fd, map)
64
65 - def handle_read(self):
66 self.notifier.read_events() 67 self.notifier.process_events()
68 69
70 -class ErrorLoggingAsyncNotifier(AsyncNotifier, 71 daemon.GanetiBaseAsyncoreDispatcher):
72 """An asyncnotifier that can survive errors in the callbacks. 73 74 We define this as a separate class, since we don't want to make AsyncNotifier 75 diverge from what we contributed upstream. 76 77 """
78 79
80 -class FileEventHandlerBase(pyinotify.ProcessEvent):
81 """Base class for file event handlers. 82 83 @ivar watch_manager: Inotify watch manager 84 85 """
86 - def __init__(self, watch_manager):
87 """Initializes this class. 88 89 @type watch_manager: pyinotify.WatchManager 90 @param watch_manager: inotify watch manager 91 92 """ 93 # pylint: disable=W0231 94 # no need to call the parent's constructor 95 self.watch_manager = watch_manager
96
97 - def process_default(self, event):
98 logging.error("Received unhandled inotify event: %s", event)
99
100 - def AddWatch(self, filename, mask):
101 """Adds a file watch. 102 103 @param filename: Path to file 104 @param mask: Inotify event mask 105 @return: Result 106 107 """ 108 result = self.watch_manager.add_watch(filename, mask) 109 110 ret = result.get(filename, -1) 111 if ret <= 0: 112 raise errors.InotifyError("Could not add inotify watcher (error code %s);" 113 " increasing fs.inotify.max_user_watches sysctl" 114 " might be necessary" % ret) 115 116 return result[filename]
117
118 - def RemoveWatch(self, handle):
119 """Removes a handle from the watcher. 120 121 @param handle: Inotify handle 122 @return: Whether removal was successful 123 124 """ 125 result = self.watch_manager.rm_watch(handle) 126 127 return result[handle]
128 129
130 -class SingleFileEventHandler(FileEventHandlerBase):
131 """Handle modify events for a single file. 132 133 """
134 - def __init__(self, watch_manager, callback, filename):
135 """Constructor for SingleFileEventHandler 136 137 @type watch_manager: pyinotify.WatchManager 138 @param watch_manager: inotify watch manager 139 @type callback: function accepting a boolean 140 @param callback: function to call when an inotify event happens 141 @type filename: string 142 @param filename: config file to watch 143 144 """ 145 FileEventHandlerBase.__init__(self, watch_manager) 146 147 self._callback = callback 148 self._filename = filename 149 150 self._watch_handle = None
151
152 - def enable(self):
153 """Watch the given file. 154 155 """ 156 if self._watch_handle is not None: 157 return 158 159 # Different Pyinotify versions have the flag constants at different places, 160 # hence not accessing them directly 161 mask = (pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"] | 162 pyinotify.EventsCodes.ALL_FLAGS["IN_IGNORED"]) 163 164 self._watch_handle = self.AddWatch(self._filename, mask)
165
166 - def disable(self):
167 """Stop watching the given file. 168 169 """ 170 if self._watch_handle is not None and self.RemoveWatch(self._watch_handle): 171 self._watch_handle = None
172 173 # pylint: disable=C0103 174 # this overrides a method in pyinotify.ProcessEvent
175 - def process_IN_IGNORED(self, event):
176 # Since we monitor a single file rather than the directory it resides in, 177 # when that file is replaced with another one (which is what happens when 178 # utils.WriteFile, the most normal way of updating files in ganeti, is 179 # called) we're going to receive an IN_IGNORED event from inotify, because 180 # of the file removal (which is contextual with the replacement). In such a 181 # case we'll need to create a watcher for the "new" file. This can be done 182 # by the callback by calling "enable" again on us. 183 logging.debug("Received 'ignored' inotify event for %s", event.path) 184 self._watch_handle = None 185 self._callback(False)
186 187 # pylint: disable=C0103 188 # this overrides a method in pyinotify.ProcessEvent
189 - def process_IN_MODIFY(self, event):
190 # This gets called when the monitored file is modified. Note that this 191 # doesn't usually happen in Ganeti, as most of the time we're just 192 # replacing any file with a new one, at filesystem level, rather than 193 # actually changing it. (see utils.WriteFile) 194 logging.debug("Received 'modify' inotify event for %s", event.path) 195 self._callback(True)
196