Statistics
| Branch: | Tag: | Revision:

root / lib / asyncnotifier.py @ 5349519d

History | View | Annotate | Download (6.1 kB)

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)