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  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions are 
  9  # met: 
 10  # 
 11  # 1. Redistributions of source code must retain the above copyright notice, 
 12  # this list of conditions and the following disclaimer. 
 13  # 
 14  # 2. Redistributions in binary form must reproduce the above copyright 
 15  # notice, this list of conditions and the following disclaimer in the 
 16  # documentation and/or other materials provided with the distribution. 
 17  # 
 18  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 19  # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
 20  # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 21  # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 22  # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 23  # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 24  # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 25  # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 26  # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 27  # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 28  # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 29   
 30   
 31  """Asynchronous pyinotify implementation""" 
 32   
 33   
 34  import asyncore 
 35  import logging 
 36   
 37  try: 
 38    # pylint: disable=E0611 
 39    from pyinotify import pyinotify 
 40  except ImportError: 
 41    import pyinotify 
 42   
 43  from ganeti import daemon 
 44  from ganeti import errors 
 45   
 46   
 47  # We contributed the AsyncNotifier class back to python-pyinotify, and it's 
 48  # part of their codebase since version 0.8.7. This code can be removed once 
 49  # we'll be ready to depend on python-pyinotify >= 0.8.7 
50 -class AsyncNotifier(asyncore.file_dispatcher):
51 """An asyncore dispatcher for inotify events. 52 53 """ 54 # pylint: disable=W0622,W0212
55 - def __init__(self, watch_manager, default_proc_fun=None, map=None):
56 """Initializes this class. 57 58 This is a a special asyncore file_dispatcher that actually wraps a 59 pyinotify Notifier, making it asyncronous. 60 61 """ 62 if default_proc_fun is None: 63 default_proc_fun = pyinotify.ProcessEvent() 64 65 self.notifier = pyinotify.Notifier(watch_manager, default_proc_fun) 66 67 # here we need to steal the file descriptor from the notifier, so we can 68 # use it in the global asyncore select, and avoid calling the 69 # check_events() function of the notifier (which doesn't allow us to select 70 # together with other file descriptors) 71 self.fd = self.notifier._fd 72 asyncore.file_dispatcher.__init__(self, self.fd, map)
73
74 - def handle_read(self):
75 self.notifier.read_events() 76 self.notifier.process_events()
77 78
79 -class ErrorLoggingAsyncNotifier(AsyncNotifier, 80 daemon.GanetiBaseAsyncoreDispatcher):
81 """An asyncnotifier that can survive errors in the callbacks. 82 83 We define this as a separate class, since we don't want to make AsyncNotifier 84 diverge from what we contributed upstream. 85 86 """
87 88
89 -class FileEventHandlerBase(pyinotify.ProcessEvent):
90 """Base class for file event handlers. 91 92 @ivar watch_manager: Inotify watch manager 93 94 """
95 - def __init__(self, watch_manager):
96 """Initializes this class. 97 98 @type watch_manager: pyinotify.WatchManager 99 @param watch_manager: inotify watch manager 100 101 """ 102 # pylint: disable=W0231 103 # no need to call the parent's constructor 104 self.watch_manager = watch_manager
105
106 - def process_default(self, event):
107 logging.error("Received unhandled inotify event: %s", event)
108
109 - def AddWatch(self, filename, mask):
110 """Adds a file watch. 111 112 @param filename: Path to file 113 @param mask: Inotify event mask 114 @return: Result 115 116 """ 117 result = self.watch_manager.add_watch(filename, mask) 118 119 ret = result.get(filename, -1) 120 if ret <= 0: 121 raise errors.InotifyError("Could not add inotify watcher (error code %s);" 122 " increasing fs.inotify.max_user_watches sysctl" 123 " might be necessary" % ret) 124 125 return result[filename]
126
127 - def RemoveWatch(self, handle):
128 """Removes a handle from the watcher. 129 130 @param handle: Inotify handle 131 @return: Whether removal was successful 132 133 """ 134 result = self.watch_manager.rm_watch(handle) 135 136 return result[handle]
137 138
139 -class SingleFileEventHandler(FileEventHandlerBase):
140 """Handle modify events for a single file. 141 142 """
143 - def __init__(self, watch_manager, callback, filename):
144 """Constructor for SingleFileEventHandler 145 146 @type watch_manager: pyinotify.WatchManager 147 @param watch_manager: inotify watch manager 148 @type callback: function accepting a boolean 149 @param callback: function to call when an inotify event happens 150 @type filename: string 151 @param filename: config file to watch 152 153 """ 154 FileEventHandlerBase.__init__(self, watch_manager) 155 156 self._callback = callback 157 self._filename = filename 158 159 self._watch_handle = None
160
161 - def enable(self):
162 """Watch the given file. 163 164 """ 165 if self._watch_handle is not None: 166 return 167 168 # Different Pyinotify versions have the flag constants at different places, 169 # hence not accessing them directly 170 mask = (pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"] | 171 pyinotify.EventsCodes.ALL_FLAGS["IN_IGNORED"]) 172 173 self._watch_handle = self.AddWatch(self._filename, mask)
174
175 - def disable(self):
176 """Stop watching the given file. 177 178 """ 179 if self._watch_handle is not None and self.RemoveWatch(self._watch_handle): 180 self._watch_handle = None
181 182 # pylint: disable=C0103 183 # this overrides a method in pyinotify.ProcessEvent
184 - def process_IN_IGNORED(self, event):
185 # Since we monitor a single file rather than the directory it resides in, 186 # when that file is replaced with another one (which is what happens when 187 # utils.WriteFile, the most normal way of updating files in ganeti, is 188 # called) we're going to receive an IN_IGNORED event from inotify, because 189 # of the file removal (which is contextual with the replacement). In such a 190 # case we'll need to create a watcher for the "new" file. This can be done 191 # by the callback by calling "enable" again on us. 192 logging.debug("Received 'ignored' inotify event for %s", event.path) 193 self._watch_handle = None 194 self._callback(False)
195 196 # pylint: disable=C0103 197 # this overrides a method in pyinotify.ProcessEvent
198 - def process_IN_MODIFY(self, event):
199 # This gets called when the monitored file is modified. Note that this 200 # doesn't usually happen in Ganeti, as most of the time we're just 201 # replacing any file with a new one, at filesystem level, rather than 202 # actually changing it. (see utils.WriteFile) 203 logging.debug("Received 'modify' inotify event for %s", event.path) 204 self._callback(True)
205