| Branch: | Tag: | Revision:

root / lib / @ 18397489

History | View | Annotate | Download (6.1 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
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
29 b459a848 Andrea Spadaccini
  # pylint: disable=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 e687ec01 Michael Hanselmann
38 a2c965ea Guido Trotter
# We contributed the AsyncNotifier class back to python-pyinotify, and it's
39 a2c965ea Guido Trotter
# part of their codebase since version 0.8.7. This code can be removed once
40 a2c965ea Guido Trotter
# we'll be ready to depend on python-pyinotify >= 0.8.7
41 a8a76bc2 Guido Trotter
class AsyncNotifier(asyncore.file_dispatcher):
42 a8a76bc2 Guido Trotter
  """An asyncore dispatcher for inotify events.
43 a8a76bc2 Guido Trotter

44 a8a76bc2 Guido Trotter
45 b459a848 Andrea Spadaccini
  # pylint: disable=W0622,W0212
46 69b99987 Michael Hanselmann
  def __init__(self, watch_manager, default_proc_fun=None, map=None):
47 69b99987 Michael Hanselmann
    """Initializes this class.
48 69b99987 Michael Hanselmann

49 69b99987 Michael Hanselmann
    This is a a special asyncore file_dispatcher that actually wraps a
50 69b99987 Michael Hanselmann
    pyinotify Notifier, making it asyncronous.
51 a8a76bc2 Guido Trotter

52 a8a76bc2 Guido Trotter
53 a8a76bc2 Guido Trotter
    if default_proc_fun is None:
54 69b99987 Michael Hanselmann
      default_proc_fun = pyinotify.ProcessEvent()
55 69b99987 Michael Hanselmann
56 a8a76bc2 Guido Trotter
    self.notifier = pyinotify.Notifier(watch_manager, default_proc_fun)
57 69b99987 Michael Hanselmann
58 a8a76bc2 Guido Trotter
    # here we need to steal the file descriptor from the notifier, so we can
59 a8a76bc2 Guido Trotter
    # use it in the global asyncore select, and avoid calling the
60 a8a76bc2 Guido Trotter
    # check_events() function of the notifier (which doesn't allow us to select
61 a8a76bc2 Guido Trotter
    # together with other file descriptors)
62 a8a76bc2 Guido Trotter
    self.fd = self.notifier._fd
63 a8a76bc2 Guido Trotter
    asyncore.file_dispatcher.__init__(self, self.fd, map)
64 a8a76bc2 Guido Trotter
65 a8a76bc2 Guido Trotter
  def handle_read(self):
66 a8a76bc2 Guido Trotter
67 a8a76bc2 Guido Trotter
68 c666f1f4 Guido Trotter
69 c666f1f4 Guido Trotter
70 e9c8deab Guido Trotter
class ErrorLoggingAsyncNotifier(AsyncNotifier,
71 e9c8deab Guido Trotter
72 e9c8deab Guido Trotter
  """An asyncnotifier that can survive errors in the callbacks.
73 e9c8deab Guido Trotter

74 e9c8deab Guido Trotter
  We define this as a separate class, since we don't want to make AsyncNotifier
75 e9c8deab Guido Trotter
  diverge from what we contributed upstream.
76 e9c8deab Guido Trotter

77 e9c8deab Guido Trotter
78 e9c8deab Guido Trotter
79 e9c8deab Guido Trotter
80 e543a42f Michael Hanselmann
class FileEventHandlerBase(pyinotify.ProcessEvent):
81 e543a42f Michael Hanselmann
  """Base class for file event handlers.
82 e543a42f Michael Hanselmann

83 e543a42f Michael Hanselmann
  @ivar watch_manager: Inotify watch manager
84 c666f1f4 Guido Trotter

85 c666f1f4 Guido Trotter
86 e543a42f Michael Hanselmann
  def __init__(self, watch_manager):
87 e543a42f Michael Hanselmann
    """Initializes this class.
88 e543a42f Michael Hanselmann

89 e543a42f Michael Hanselmann
    @type watch_manager: pyinotify.WatchManager
90 e543a42f Michael Hanselmann
    @param watch_manager: inotify watch manager
91 e543a42f Michael Hanselmann

92 e543a42f Michael Hanselmann
93 b459a848 Andrea Spadaccini
    # pylint: disable=W0231
94 e543a42f Michael Hanselmann
    # no need to call the parent's constructor
95 e543a42f Michael Hanselmann
    self.watch_manager = watch_manager
96 e543a42f Michael Hanselmann
97 e543a42f Michael Hanselmann
  def process_default(self, event):
98 e543a42f Michael Hanselmann
    logging.error("Received unhandled inotify event: %s", event)
99 e543a42f Michael Hanselmann
100 e543a42f Michael Hanselmann
  def AddWatch(self, filename, mask):
101 e543a42f Michael Hanselmann
    """Adds a file watch.
102 e543a42f Michael Hanselmann

103 e543a42f Michael Hanselmann
    @param filename: Path to file
104 e543a42f Michael Hanselmann
    @param mask: Inotify event mask
105 e543a42f Michael Hanselmann
    @return: Result
106 e543a42f Michael Hanselmann

107 e543a42f Michael Hanselmann
108 e543a42f Michael Hanselmann
    result = self.watch_manager.add_watch(filename, mask)
109 e543a42f Michael Hanselmann
110 e543a42f Michael Hanselmann
    ret = result.get(filename, -1)
111 e543a42f Michael Hanselmann
    if ret <= 0:
112 7fb5ac7c Michael Hanselmann
      raise errors.InotifyError("Could not add inotify watcher (error code %s);"
113 7fb5ac7c Michael Hanselmann
                                " increasing fs.inotify.max_user_watches sysctl"
114 7fb5ac7c Michael Hanselmann
                                " might be necessary" % ret)
115 e543a42f Michael Hanselmann
116 e543a42f Michael Hanselmann
    return result[filename]
117 c666f1f4 Guido Trotter
118 e543a42f Michael Hanselmann
  def RemoveWatch(self, handle):
119 e543a42f Michael Hanselmann
    """Removes a handle from the watcher.
120 e543a42f Michael Hanselmann

121 e543a42f Michael Hanselmann
    @param handle: Inotify handle
122 e543a42f Michael Hanselmann
    @return: Whether removal was successful
123 e543a42f Michael Hanselmann

124 e543a42f Michael Hanselmann
125 e543a42f Michael Hanselmann
    result = self.watch_manager.rm_watch(handle)
126 e543a42f Michael Hanselmann
127 e543a42f Michael Hanselmann
    return result[handle]
128 e543a42f Michael Hanselmann
129 e543a42f Michael Hanselmann
130 e543a42f Michael Hanselmann
class SingleFileEventHandler(FileEventHandlerBase):
131 e543a42f Michael Hanselmann
  """Handle modify events for a single file.
132 e543a42f Michael Hanselmann

133 e543a42f Michael Hanselmann
134 c666f1f4 Guido Trotter
  def __init__(self, watch_manager, callback, filename):
135 c666f1f4 Guido Trotter
    """Constructor for SingleFileEventHandler
136 c666f1f4 Guido Trotter

137 c666f1f4 Guido Trotter
    @type watch_manager: pyinotify.WatchManager
138 c666f1f4 Guido Trotter
    @param watch_manager: inotify watch manager
139 c666f1f4 Guido Trotter
    @type callback: function accepting a boolean
140 c666f1f4 Guido Trotter
    @param callback: function to call when an inotify event happens
141 c666f1f4 Guido Trotter
    @type filename: string
142 c666f1f4 Guido Trotter
    @param filename: config file to watch
143 c666f1f4 Guido Trotter

144 c666f1f4 Guido Trotter
145 e543a42f Michael Hanselmann
    FileEventHandlerBase.__init__(self, watch_manager)
146 e543a42f Michael Hanselmann
147 e543a42f Michael Hanselmann
    self._callback = callback
148 e543a42f Michael Hanselmann
    self._filename = filename
149 e543a42f Michael Hanselmann
150 e543a42f Michael Hanselmann
    self._watch_handle = None
151 c666f1f4 Guido Trotter
152 c666f1f4 Guido Trotter
  def enable(self):
153 e543a42f Michael Hanselmann
    """Watch the given file.
154 c666f1f4 Guido Trotter

155 c666f1f4 Guido Trotter
156 e543a42f Michael Hanselmann
    if self._watch_handle is not None:
157 e543a42f Michael Hanselmann
158 e543a42f Michael Hanselmann
159 ac96953d Michael Hanselmann
    # Different Pyinotify versions have the flag constants at different places,
160 ac96953d Michael Hanselmann
    # hence not accessing them directly
161 ac96953d Michael Hanselmann
    mask = (pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"] |
162 ac96953d Michael Hanselmann
163 e543a42f Michael Hanselmann
164 e543a42f Michael Hanselmann
    self._watch_handle = self.AddWatch(self._filename, mask)
165 c666f1f4 Guido Trotter
166 c666f1f4 Guido Trotter
  def disable(self):
167 e543a42f Michael Hanselmann
    """Stop watching the given file.
168 c666f1f4 Guido Trotter

169 c666f1f4 Guido Trotter
170 e543a42f Michael Hanselmann
    if self._watch_handle is not None and self.RemoveWatch(self._watch_handle):
171 e543a42f Michael Hanselmann
      self._watch_handle = None
172 c666f1f4 Guido Trotter
173 b459a848 Andrea Spadaccini
  # pylint: disable=C0103
174 c666f1f4 Guido Trotter
  # this overrides a method in pyinotify.ProcessEvent
175 c666f1f4 Guido Trotter
  def process_IN_IGNORED(self, event):
176 d021e478 Guido Trotter
    # Since we monitor a single file rather than the directory it resides in,
177 d021e478 Guido Trotter
    # when that file is replaced with another one (which is what happens when
178 d021e478 Guido Trotter
    # utils.WriteFile, the most normal way of updating files in ganeti, is
179 d021e478 Guido Trotter
    # called) we're going to receive an IN_IGNORED event from inotify, because
180 d021e478 Guido Trotter
    # of the file removal (which is contextual with the replacement). In such a
181 d021e478 Guido Trotter
    # case we'll need to create a watcher for the "new" file. This can be done
182 d021e478 Guido Trotter
    # by the callback by calling "enable" again on us.
183 c666f1f4 Guido Trotter
    logging.debug("Received 'ignored' inotify event for %s", event.path)
184 e543a42f Michael Hanselmann
    self._watch_handle = None
185 e543a42f Michael Hanselmann
186 c666f1f4 Guido Trotter
187 b459a848 Andrea Spadaccini
  # pylint: disable=C0103
188 c666f1f4 Guido Trotter
  # this overrides a method in pyinotify.ProcessEvent
189 c666f1f4 Guido Trotter
  def process_IN_MODIFY(self, event):
190 d021e478 Guido Trotter
    # This gets called when the monitored file is modified. Note that this
191 d021e478 Guido Trotter
    # doesn't usually happen in Ganeti, as most of the time we're just
192 d021e478 Guido Trotter
    # replacing any file with a new one, at filesystem level, rather than
193 d021e478 Guido Trotter
    # actually changing it. (see utils.WriteFile)
194 c666f1f4 Guido Trotter
    logging.debug("Received 'modify' inotify event for %s", event.path)
195 e543a42f Michael Hanselmann