4 # Copyright (C) 2009 Google Inc.
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.
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.
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
22 """Asynchronous pyinotify implementation"""
29 # pylint: disable=E0611
30 from pyinotify import pyinotify
34 from ganeti import daemon
35 from ganeti import errors
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.
45 # pylint: disable=W0622,W0212
46 def __init__(self, watch_manager, default_proc_fun=None, map=None):
47 """Initializes this class.
49 This is a a special asyncore file_dispatcher that actually wraps a
50 pyinotify Notifier, making it asyncronous.
53 if default_proc_fun is None:
54 default_proc_fun = pyinotify.ProcessEvent()
56 self.notifier = pyinotify.Notifier(watch_manager, default_proc_fun)
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)
65 def handle_read(self):
66 self.notifier.read_events()
67 self.notifier.process_events()
70 class ErrorLoggingAsyncNotifier(AsyncNotifier,
71 daemon.GanetiBaseAsyncoreDispatcher):
72 """An asyncnotifier that can survive errors in the callbacks.
74 We define this as a separate class, since we don't want to make AsyncNotifier
75 diverge from what we contributed upstream.
80 class FileEventHandlerBase(pyinotify.ProcessEvent):
81 """Base class for file event handlers.
83 @ivar watch_manager: Inotify watch manager
86 def __init__(self, watch_manager):
87 """Initializes this class.
89 @type watch_manager: pyinotify.WatchManager
90 @param watch_manager: inotify watch manager
93 # pylint: disable=W0231
94 # no need to call the parent's constructor
95 self.watch_manager = watch_manager
97 def process_default(self, event):
98 logging.error("Received unhandled inotify event: %s", event)
100 def AddWatch(self, filename, mask):
101 """Adds a file watch.
103 @param filename: Path to file
104 @param mask: Inotify event mask
108 result = self.watch_manager.add_watch(filename, mask)
110 ret = result.get(filename, -1)
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)
116 return result[filename]
118 def RemoveWatch(self, handle):
119 """Removes a handle from the watcher.
121 @param handle: Inotify handle
122 @return: Whether removal was successful
125 result = self.watch_manager.rm_watch(handle)
127 return result[handle]
130 class SingleFileEventHandler(FileEventHandlerBase):
131 """Handle modify events for a single file.
134 def __init__(self, watch_manager, callback, filename):
135 """Constructor for SingleFileEventHandler
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
145 FileEventHandlerBase.__init__(self, watch_manager)
147 self._callback = callback
148 self._filename = filename
150 self._watch_handle = None
153 """Watch the given file.
156 if self._watch_handle is not None:
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"])
164 self._watch_handle = self.AddWatch(self._filename, mask)
167 """Stop watching the given file.
170 if self._watch_handle is not None and self.RemoveWatch(self._watch_handle):
171 self._watch_handle = None
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)
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)