root / lib / asyncnotifier.py @ e83d962b
History | View | Annotate | Download (6 kB)
1 | a8a76bc2 | Guido Trotter | #
|
---|---|---|---|
2 | a8a76bc2 | Guido Trotter | #
|
3 | a8a76bc2 | Guido Trotter | |
4 | a8a76bc2 | Guido Trotter | # Copyright (C) 2009 Google Inc.
|
5 | a8a76bc2 | Guido Trotter | #
|
6 | a8a76bc2 | Guido Trotter | # This program is free software; you can redistribute it and/or modify
|
7 | a8a76bc2 | Guido Trotter | # it under the terms of the GNU General Public License as published by
|
8 | a8a76bc2 | Guido Trotter | # the Free Software Foundation; either version 2 of the License, or
|
9 | a8a76bc2 | Guido Trotter | # (at your option) any later version.
|
10 | a8a76bc2 | Guido Trotter | #
|
11 | a8a76bc2 | Guido Trotter | # This program is distributed in the hope that it will be useful, but
|
12 | a8a76bc2 | Guido Trotter | # WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 | a8a76bc2 | Guido Trotter | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 | a8a76bc2 | Guido Trotter | # General Public License for more details.
|
15 | a8a76bc2 | Guido Trotter | #
|
16 | a8a76bc2 | Guido Trotter | # You should have received a copy of the GNU General Public License
|
17 | a8a76bc2 | Guido Trotter | # along with this program; if not, write to the Free Software
|
18 | a8a76bc2 | Guido Trotter | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 | a8a76bc2 | Guido Trotter | # 02110-1301, USA.
|
20 | a8a76bc2 | Guido Trotter | |
21 | a8a76bc2 | Guido Trotter | |
22 | a8a76bc2 | Guido Trotter | """Asynchronous pyinotify implementation"""
|
23 | a8a76bc2 | Guido Trotter | |
24 | a8a76bc2 | Guido Trotter | |
25 | a8a76bc2 | Guido Trotter | import asyncore |
26 | c666f1f4 | Guido Trotter | import logging |
27 | a8a76bc2 | Guido Trotter | |
28 | ad54f3d2 | Guido Trotter | try:
|
29 | 7260cfbe | Iustin Pop | # pylint: disable-msg=E0611
|
30 | 6af8a903 | Iustin Pop | from pyinotify import pyinotify |
31 | ad54f3d2 | Guido Trotter | except ImportError: |
32 | 6af8a903 | Iustin Pop | import pyinotify |
33 | ad54f3d2 | Guido Trotter | |
34 | e9c8deab | Guido Trotter | from ganeti import daemon |
35 | c666f1f4 | Guido Trotter | from ganeti import errors |
36 | a8a76bc2 | Guido Trotter | |
37 | a2c965ea | Guido Trotter | # We contributed the AsyncNotifier class back to python-pyinotify, and it's
|
38 | a2c965ea | Guido Trotter | # part of their codebase since version 0.8.7. This code can be removed once
|
39 | a2c965ea | Guido Trotter | # we'll be ready to depend on python-pyinotify >= 0.8.7
|
40 | a8a76bc2 | Guido Trotter | class AsyncNotifier(asyncore.file_dispatcher): |
41 | a8a76bc2 | Guido Trotter | """An asyncore dispatcher for inotify events.
|
42 | a8a76bc2 | Guido Trotter |
|
43 | a8a76bc2 | Guido Trotter | """
|
44 | 7260cfbe | Iustin Pop | # pylint: disable-msg=W0622,W0212
|
45 | 69b99987 | Michael Hanselmann | def __init__(self, watch_manager, default_proc_fun=None, map=None): |
46 | 69b99987 | Michael Hanselmann | """Initializes this class.
|
47 | 69b99987 | Michael Hanselmann |
|
48 | 69b99987 | Michael Hanselmann | This is a a special asyncore file_dispatcher that actually wraps a
|
49 | 69b99987 | Michael Hanselmann | pyinotify Notifier, making it asyncronous.
|
50 | a8a76bc2 | Guido Trotter |
|
51 | a8a76bc2 | Guido Trotter | """
|
52 | a8a76bc2 | Guido Trotter | if default_proc_fun is None: |
53 | 69b99987 | Michael Hanselmann | default_proc_fun = pyinotify.ProcessEvent() |
54 | 69b99987 | Michael Hanselmann | |
55 | a8a76bc2 | Guido Trotter | self.notifier = pyinotify.Notifier(watch_manager, default_proc_fun)
|
56 | 69b99987 | Michael Hanselmann | |
57 | a8a76bc2 | Guido Trotter | # here we need to steal the file descriptor from the notifier, so we can
|
58 | a8a76bc2 | Guido Trotter | # use it in the global asyncore select, and avoid calling the
|
59 | a8a76bc2 | Guido Trotter | # check_events() function of the notifier (which doesn't allow us to select
|
60 | a8a76bc2 | Guido Trotter | # together with other file descriptors)
|
61 | a8a76bc2 | Guido Trotter | self.fd = self.notifier._fd |
62 | a8a76bc2 | Guido Trotter | asyncore.file_dispatcher.__init__(self, self.fd, map) |
63 | a8a76bc2 | Guido Trotter | |
64 | a8a76bc2 | Guido Trotter | def handle_read(self): |
65 | a8a76bc2 | Guido Trotter | self.notifier.read_events()
|
66 | a8a76bc2 | Guido Trotter | self.notifier.process_events()
|
67 | c666f1f4 | Guido Trotter | |
68 | c666f1f4 | Guido Trotter | |
69 | e9c8deab | Guido Trotter | class ErrorLoggingAsyncNotifier(AsyncNotifier, |
70 | e9c8deab | Guido Trotter | daemon.GanetiBaseAsyncoreDispatcher): |
71 | e9c8deab | Guido Trotter | """An asyncnotifier that can survive errors in the callbacks.
|
72 | e9c8deab | Guido Trotter |
|
73 | e9c8deab | Guido Trotter | We define this as a separate class, since we don't want to make AsyncNotifier
|
74 | e9c8deab | Guido Trotter | diverge from what we contributed upstream.
|
75 | e9c8deab | Guido Trotter |
|
76 | e9c8deab | Guido Trotter | """
|
77 | e9c8deab | Guido Trotter | |
78 | e9c8deab | Guido Trotter | |
79 | e543a42f | Michael Hanselmann | class FileEventHandlerBase(pyinotify.ProcessEvent): |
80 | e543a42f | Michael Hanselmann | """Base class for file event handlers.
|
81 | e543a42f | Michael Hanselmann |
|
82 | e543a42f | Michael Hanselmann | @ivar watch_manager: Inotify watch manager
|
83 | c666f1f4 | Guido Trotter |
|
84 | c666f1f4 | Guido Trotter | """
|
85 | e543a42f | Michael Hanselmann | def __init__(self, watch_manager): |
86 | e543a42f | Michael Hanselmann | """Initializes this class.
|
87 | e543a42f | Michael Hanselmann |
|
88 | e543a42f | Michael Hanselmann | @type watch_manager: pyinotify.WatchManager
|
89 | e543a42f | Michael Hanselmann | @param watch_manager: inotify watch manager
|
90 | e543a42f | Michael Hanselmann |
|
91 | e543a42f | Michael Hanselmann | """
|
92 | e543a42f | Michael Hanselmann | # pylint: disable-msg=W0231
|
93 | e543a42f | Michael Hanselmann | # no need to call the parent's constructor
|
94 | e543a42f | Michael Hanselmann | self.watch_manager = watch_manager
|
95 | e543a42f | Michael Hanselmann | |
96 | e543a42f | Michael Hanselmann | def process_default(self, event): |
97 | e543a42f | Michael Hanselmann | logging.error("Received unhandled inotify event: %s", event)
|
98 | e543a42f | Michael Hanselmann | |
99 | e543a42f | Michael Hanselmann | def AddWatch(self, filename, mask): |
100 | e543a42f | Michael Hanselmann | """Adds a file watch.
|
101 | e543a42f | Michael Hanselmann |
|
102 | e543a42f | Michael Hanselmann | @param filename: Path to file
|
103 | e543a42f | Michael Hanselmann | @param mask: Inotify event mask
|
104 | e543a42f | Michael Hanselmann | @return: Result
|
105 | e543a42f | Michael Hanselmann |
|
106 | e543a42f | Michael Hanselmann | """
|
107 | e543a42f | Michael Hanselmann | result = self.watch_manager.add_watch(filename, mask)
|
108 | e543a42f | Michael Hanselmann | |
109 | e543a42f | Michael Hanselmann | ret = result.get(filename, -1)
|
110 | e543a42f | Michael Hanselmann | if ret <= 0: |
111 | e543a42f | Michael Hanselmann | raise errors.InotifyError("Could not add inotify watcher (%s)" % ret) |
112 | e543a42f | Michael Hanselmann | |
113 | e543a42f | Michael Hanselmann | return result[filename]
|
114 | c666f1f4 | Guido Trotter | |
115 | e543a42f | Michael Hanselmann | def RemoveWatch(self, handle): |
116 | e543a42f | Michael Hanselmann | """Removes a handle from the watcher.
|
117 | e543a42f | Michael Hanselmann |
|
118 | e543a42f | Michael Hanselmann | @param handle: Inotify handle
|
119 | e543a42f | Michael Hanselmann | @return: Whether removal was successful
|
120 | e543a42f | Michael Hanselmann |
|
121 | e543a42f | Michael Hanselmann | """
|
122 | e543a42f | Michael Hanselmann | result = self.watch_manager.rm_watch(handle)
|
123 | e543a42f | Michael Hanselmann | |
124 | e543a42f | Michael Hanselmann | return result[handle]
|
125 | e543a42f | Michael Hanselmann | |
126 | e543a42f | Michael Hanselmann | |
127 | e543a42f | Michael Hanselmann | class SingleFileEventHandler(FileEventHandlerBase): |
128 | e543a42f | Michael Hanselmann | """Handle modify events for a single file.
|
129 | e543a42f | Michael Hanselmann |
|
130 | e543a42f | Michael Hanselmann | """
|
131 | c666f1f4 | Guido Trotter | def __init__(self, watch_manager, callback, filename): |
132 | c666f1f4 | Guido Trotter | """Constructor for SingleFileEventHandler
|
133 | c666f1f4 | Guido Trotter |
|
134 | c666f1f4 | Guido Trotter | @type watch_manager: pyinotify.WatchManager
|
135 | c666f1f4 | Guido Trotter | @param watch_manager: inotify watch manager
|
136 | c666f1f4 | Guido Trotter | @type callback: function accepting a boolean
|
137 | c666f1f4 | Guido Trotter | @param callback: function to call when an inotify event happens
|
138 | c666f1f4 | Guido Trotter | @type filename: string
|
139 | c666f1f4 | Guido Trotter | @param filename: config file to watch
|
140 | c666f1f4 | Guido Trotter |
|
141 | c666f1f4 | Guido Trotter | """
|
142 | e543a42f | Michael Hanselmann | FileEventHandlerBase.__init__(self, watch_manager)
|
143 | e543a42f | Michael Hanselmann | |
144 | e543a42f | Michael Hanselmann | self._callback = callback
|
145 | e543a42f | Michael Hanselmann | self._filename = filename
|
146 | e543a42f | Michael Hanselmann | |
147 | e543a42f | Michael Hanselmann | self._watch_handle = None |
148 | c666f1f4 | Guido Trotter | |
149 | c666f1f4 | Guido Trotter | def enable(self): |
150 | e543a42f | Michael Hanselmann | """Watch the given file.
|
151 | c666f1f4 | Guido Trotter |
|
152 | c666f1f4 | Guido Trotter | """
|
153 | e543a42f | Michael Hanselmann | if self._watch_handle is not None: |
154 | e543a42f | Michael Hanselmann | return
|
155 | e543a42f | Michael Hanselmann | |
156 | ac96953d | Michael Hanselmann | # Different Pyinotify versions have the flag constants at different places,
|
157 | ac96953d | Michael Hanselmann | # hence not accessing them directly
|
158 | ac96953d | Michael Hanselmann | mask = (pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"] |
|
159 | ac96953d | Michael Hanselmann | pyinotify.EventsCodes.ALL_FLAGS["IN_IGNORED"])
|
160 | e543a42f | Michael Hanselmann | |
161 | e543a42f | Michael Hanselmann | self._watch_handle = self.AddWatch(self._filename, mask) |
162 | c666f1f4 | Guido Trotter | |
163 | c666f1f4 | Guido Trotter | def disable(self): |
164 | e543a42f | Michael Hanselmann | """Stop watching the given file.
|
165 | c666f1f4 | Guido Trotter |
|
166 | c666f1f4 | Guido Trotter | """
|
167 | e543a42f | Michael Hanselmann | if self._watch_handle is not None and self.RemoveWatch(self._watch_handle): |
168 | e543a42f | Michael Hanselmann | self._watch_handle = None |
169 | c666f1f4 | Guido Trotter | |
170 | c666f1f4 | Guido Trotter | # pylint: disable-msg=C0103
|
171 | c666f1f4 | Guido Trotter | # this overrides a method in pyinotify.ProcessEvent
|
172 | c666f1f4 | Guido Trotter | def process_IN_IGNORED(self, event): |
173 | d021e478 | Guido Trotter | # Since we monitor a single file rather than the directory it resides in,
|
174 | d021e478 | Guido Trotter | # when that file is replaced with another one (which is what happens when
|
175 | d021e478 | Guido Trotter | # utils.WriteFile, the most normal way of updating files in ganeti, is
|
176 | d021e478 | Guido Trotter | # called) we're going to receive an IN_IGNORED event from inotify, because
|
177 | d021e478 | Guido Trotter | # of the file removal (which is contextual with the replacement). In such a
|
178 | d021e478 | Guido Trotter | # case we'll need to create a watcher for the "new" file. This can be done
|
179 | d021e478 | Guido Trotter | # by the callback by calling "enable" again on us.
|
180 | c666f1f4 | Guido Trotter | logging.debug("Received 'ignored' inotify event for %s", event.path)
|
181 | e543a42f | Michael Hanselmann | self._watch_handle = None |
182 | e543a42f | Michael Hanselmann | self._callback(False) |
183 | c666f1f4 | Guido Trotter | |
184 | c666f1f4 | Guido Trotter | # pylint: disable-msg=C0103
|
185 | c666f1f4 | Guido Trotter | # this overrides a method in pyinotify.ProcessEvent
|
186 | c666f1f4 | Guido Trotter | def process_IN_MODIFY(self, event): |
187 | d021e478 | Guido Trotter | # This gets called when the monitored file is modified. Note that this
|
188 | d021e478 | Guido Trotter | # doesn't usually happen in Ganeti, as most of the time we're just
|
189 | d021e478 | Guido Trotter | # replacing any file with a new one, at filesystem level, rather than
|
190 | d021e478 | Guido Trotter | # actually changing it. (see utils.WriteFile)
|
191 | c666f1f4 | Guido Trotter | logging.debug("Received 'modify' inotify event for %s", event.path)
|
192 | e543a42f | Michael Hanselmann | self._callback(True) |