Statistics
| Branch: | Tag: | Revision:

root / lib / asyncnotifier.py @ 8062638d

History | View | Annotate | Download (5.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
# 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 c666f1f4 Guido Trotter
class SingleFileEventHandler(pyinotify.ProcessEvent):
80 c666f1f4 Guido Trotter
  """Handle modify events for a single file.
81 c666f1f4 Guido Trotter

82 c666f1f4 Guido Trotter
  """
83 c666f1f4 Guido Trotter
84 c666f1f4 Guido Trotter
  def __init__(self, watch_manager, callback, filename):
85 c666f1f4 Guido Trotter
    """Constructor for SingleFileEventHandler
86 c666f1f4 Guido Trotter

87 c666f1f4 Guido Trotter
    @type watch_manager: pyinotify.WatchManager
88 c666f1f4 Guido Trotter
    @param watch_manager: inotify watch manager
89 c666f1f4 Guido Trotter
    @type callback: function accepting a boolean
90 c666f1f4 Guido Trotter
    @param callback: function to call when an inotify event happens
91 c666f1f4 Guido Trotter
    @type filename: string
92 c666f1f4 Guido Trotter
    @param filename: config file to watch
93 c666f1f4 Guido Trotter

94 c666f1f4 Guido Trotter
    """
95 c666f1f4 Guido Trotter
    # pylint: disable-msg=W0231
96 c666f1f4 Guido Trotter
    # no need to call the parent's constructor
97 c666f1f4 Guido Trotter
    self.watch_manager = watch_manager
98 c666f1f4 Guido Trotter
    self.callback = callback
99 c666f1f4 Guido Trotter
    self.mask = pyinotify.EventsCodes.ALL_FLAGS["IN_IGNORED"] | \
100 c666f1f4 Guido Trotter
                pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"]
101 c666f1f4 Guido Trotter
    self.file = filename
102 c666f1f4 Guido Trotter
    self.watch_handle = None
103 c666f1f4 Guido Trotter
104 c666f1f4 Guido Trotter
  def enable(self):
105 c666f1f4 Guido Trotter
    """Watch the given file
106 c666f1f4 Guido Trotter

107 c666f1f4 Guido Trotter
    """
108 c666f1f4 Guido Trotter
    if self.watch_handle is None:
109 c666f1f4 Guido Trotter
      result = self.watch_manager.add_watch(self.file, self.mask)
110 c666f1f4 Guido Trotter
      if not self.file in result or result[self.file] <= 0:
111 c666f1f4 Guido Trotter
        raise errors.InotifyError("Could not add inotify watcher")
112 c666f1f4 Guido Trotter
      else:
113 c666f1f4 Guido Trotter
        self.watch_handle = result[self.file]
114 c666f1f4 Guido Trotter
115 c666f1f4 Guido Trotter
  def disable(self):
116 c666f1f4 Guido Trotter
    """Stop watching the given file
117 c666f1f4 Guido Trotter

118 c666f1f4 Guido Trotter
    """
119 c666f1f4 Guido Trotter
    if self.watch_handle is not None:
120 c666f1f4 Guido Trotter
      result = self.watch_manager.rm_watch(self.watch_handle)
121 c666f1f4 Guido Trotter
      if result[self.watch_handle]:
122 c666f1f4 Guido Trotter
        self.watch_handle = None
123 c666f1f4 Guido Trotter
124 c666f1f4 Guido Trotter
  # pylint: disable-msg=C0103
125 c666f1f4 Guido Trotter
  # this overrides a method in pyinotify.ProcessEvent
126 c666f1f4 Guido Trotter
  def process_IN_IGNORED(self, event):
127 d021e478 Guido Trotter
    # Since we monitor a single file rather than the directory it resides in,
128 d021e478 Guido Trotter
    # when that file is replaced with another one (which is what happens when
129 d021e478 Guido Trotter
    # utils.WriteFile, the most normal way of updating files in ganeti, is
130 d021e478 Guido Trotter
    # called) we're going to receive an IN_IGNORED event from inotify, because
131 d021e478 Guido Trotter
    # of the file removal (which is contextual with the replacement). In such a
132 d021e478 Guido Trotter
    # case we'll need to create a watcher for the "new" file. This can be done
133 d021e478 Guido Trotter
    # by the callback by calling "enable" again on us.
134 c666f1f4 Guido Trotter
    logging.debug("Received 'ignored' inotify event for %s", event.path)
135 c666f1f4 Guido Trotter
    self.watch_handle = None
136 32b1efa1 Guido Trotter
    self.callback(False)
137 c666f1f4 Guido Trotter
138 c666f1f4 Guido Trotter
  # pylint: disable-msg=C0103
139 c666f1f4 Guido Trotter
  # this overrides a method in pyinotify.ProcessEvent
140 c666f1f4 Guido Trotter
  def process_IN_MODIFY(self, event):
141 d021e478 Guido Trotter
    # This gets called when the monitored file is modified. Note that this
142 d021e478 Guido Trotter
    # doesn't usually happen in Ganeti, as most of the time we're just
143 d021e478 Guido Trotter
    # replacing any file with a new one, at filesystem level, rather than
144 d021e478 Guido Trotter
    # actually changing it. (see utils.WriteFile)
145 c666f1f4 Guido Trotter
    logging.debug("Received 'modify' inotify event for %s", event.path)
146 32b1efa1 Guido Trotter
    self.callback(True)
147 c666f1f4 Guido Trotter
148 c666f1f4 Guido Trotter
  def process_default(self, event):
149 c666f1f4 Guido Trotter
    logging.error("Received unhandled inotify event: %s", event)