Statistics
| Branch: | Tag: | Revision:

root / lib / asyncnotifier.py @ 952d7515

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)