Statistics
| Branch: | Tag: | Revision:

root / lib / asyncnotifier.py @ d021e478

History | View | Annotate | Download (5.4 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-msg=E0611
30
  from pyinotify import pyinotify
31
except ImportError:
32
  import pyinotify
33

    
34
from ganeti import errors
35

    
36
# We contributed the AsyncNotifier class back to python-pyinotify, and it's
37
# part of their codebase since version 0.8.7. This code can be removed once
38
# we'll be ready to depend on python-pyinotify >= 0.8.7
39
class AsyncNotifier(asyncore.file_dispatcher):
40
  """An asyncore dispatcher for inotify events.
41

42
  """
43
  # pylint: disable-msg=W0622,W0212
44
  def __init__(self, watch_manager, default_proc_fun=None, map=None):
45
    """Initializes this class.
46

47
    This is a a special asyncore file_dispatcher that actually wraps a
48
    pyinotify Notifier, making it asyncronous.
49

50
    """
51
    if default_proc_fun is None:
52
      default_proc_fun = pyinotify.ProcessEvent()
53

    
54
    self.notifier = pyinotify.Notifier(watch_manager, default_proc_fun)
55

    
56
    # here we need to steal the file descriptor from the notifier, so we can
57
    # use it in the global asyncore select, and avoid calling the
58
    # check_events() function of the notifier (which doesn't allow us to select
59
    # together with other file descriptors)
60
    self.fd = self.notifier._fd
61
    asyncore.file_dispatcher.__init__(self, self.fd, map)
62

    
63
  def handle_read(self):
64
    self.notifier.read_events()
65
    self.notifier.process_events()
66

    
67

    
68
class SingleFileEventHandler(pyinotify.ProcessEvent):
69
  """Handle modify events for a single file.
70

71
  """
72

    
73
  def __init__(self, watch_manager, callback, filename):
74
    """Constructor for SingleFileEventHandler
75

76
    @type watch_manager: pyinotify.WatchManager
77
    @param watch_manager: inotify watch manager
78
    @type callback: function accepting a boolean
79
    @param callback: function to call when an inotify event happens
80
    @type filename: string
81
    @param filename: config file to watch
82

83
    """
84
    # pylint: disable-msg=W0231
85
    # no need to call the parent's constructor
86
    self.watch_manager = watch_manager
87
    self.callback = callback
88
    self.mask = pyinotify.EventsCodes.ALL_FLAGS["IN_IGNORED"] | \
89
                pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"]
90
    self.file = filename
91
    self.watch_handle = None
92

    
93
  def enable(self):
94
    """Watch the given file
95

96
    """
97
    if self.watch_handle is None:
98
      result = self.watch_manager.add_watch(self.file, self.mask)
99
      if not self.file in result or result[self.file] <= 0:
100
        raise errors.InotifyError("Could not add inotify watcher")
101
      else:
102
        self.watch_handle = result[self.file]
103

    
104
  def disable(self):
105
    """Stop watching the given file
106

107
    """
108
    if self.watch_handle is not None:
109
      result = self.watch_manager.rm_watch(self.watch_handle)
110
      if result[self.watch_handle]:
111
        self.watch_handle = None
112

    
113
  # pylint: disable-msg=C0103
114
  # this overrides a method in pyinotify.ProcessEvent
115
  def process_IN_IGNORED(self, event):
116
    # Since we monitor a single file rather than the directory it resides in,
117
    # when that file is replaced with another one (which is what happens when
118
    # utils.WriteFile, the most normal way of updating files in ganeti, is
119
    # called) we're going to receive an IN_IGNORED event from inotify, because
120
    # of the file removal (which is contextual with the replacement). In such a
121
    # case we'll need to create a watcher for the "new" file. This can be done
122
    # by the callback by calling "enable" again on us.
123
    logging.debug("Received 'ignored' inotify event for %s", event.path)
124
    self.watch_handle = None
125

    
126
    try:
127
      self.callback(False)
128
    except: # pylint: disable-msg=W0702
129
      # we need to catch any exception here, log it, but proceed, because even
130
      # if we failed handling a single request, we still want our daemon to
131
      # proceed.
132
      logging.error("Unexpected exception", exc_info=True)
133

    
134
  # pylint: disable-msg=C0103
135
  # this overrides a method in pyinotify.ProcessEvent
136
  def process_IN_MODIFY(self, event):
137
    # This gets called when the monitored file is modified. Note that this
138
    # doesn't usually happen in Ganeti, as most of the time we're just
139
    # replacing any file with a new one, at filesystem level, rather than
140
    # actually changing it. (see utils.WriteFile)
141
    logging.debug("Received 'modify' inotify event for %s", event.path)
142

    
143
    try:
144
      self.callback(True)
145
    except: # pylint: disable-msg=W0702
146
      # we need to catch any exception here, log it, but proceed, because even
147
      # if we failed handling a single request, we still want our daemon to
148
      # proceed.
149
      logging.error("Unexpected exception", exc_info=True)
150

    
151
  def process_default(self, event):
152
    logging.error("Received unhandled inotify event: %s", event)