Use node UUID for locking in LUInstanceMove
[ganeti-local] / lib / asyncnotifier.py
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)