Add ParseCpuMask() utility function
[ganeti-local] / lib / asyncnotifier.py
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 daemon
35 from ganeti import errors
36
37 # We contributed the AsyncNotifier class back to python-pyinotify, and it's
38 # part of their codebase since version 0.8.7. This code can be removed once
39 # we'll be ready to depend on python-pyinotify >= 0.8.7
40 class AsyncNotifier(asyncore.file_dispatcher):
41   """An asyncore dispatcher for inotify events.
42
43   """
44   # pylint: disable-msg=W0622,W0212
45   def __init__(self, watch_manager, default_proc_fun=None, map=None):
46     """Initializes this class.
47
48     This is a a special asyncore file_dispatcher that actually wraps a
49     pyinotify Notifier, making it asyncronous.
50
51     """
52     if default_proc_fun is None:
53       default_proc_fun = pyinotify.ProcessEvent()
54
55     self.notifier = pyinotify.Notifier(watch_manager, default_proc_fun)
56
57     # here we need to steal the file descriptor from the notifier, so we can
58     # use it in the global asyncore select, and avoid calling the
59     # check_events() function of the notifier (which doesn't allow us to select
60     # together with other file descriptors)
61     self.fd = self.notifier._fd
62     asyncore.file_dispatcher.__init__(self, self.fd, map)
63
64   def handle_read(self):
65     self.notifier.read_events()
66     self.notifier.process_events()
67
68
69 class ErrorLoggingAsyncNotifier(AsyncNotifier,
70                                 daemon.GanetiBaseAsyncoreDispatcher):
71   """An asyncnotifier that can survive errors in the callbacks.
72
73   We define this as a separate class, since we don't want to make AsyncNotifier
74   diverge from what we contributed upstream.
75
76   """
77
78
79 class SingleFileEventHandler(pyinotify.ProcessEvent):
80   """Handle modify events for a single file.
81
82   """
83
84   def __init__(self, watch_manager, callback, filename):
85     """Constructor for SingleFileEventHandler
86
87     @type watch_manager: pyinotify.WatchManager
88     @param watch_manager: inotify watch manager
89     @type callback: function accepting a boolean
90     @param callback: function to call when an inotify event happens
91     @type filename: string
92     @param filename: config file to watch
93
94     """
95     # pylint: disable-msg=W0231
96     # no need to call the parent's constructor
97     self.watch_manager = watch_manager
98     self.callback = callback
99     self.mask = pyinotify.EventsCodes.ALL_FLAGS["IN_IGNORED"] | \
100                 pyinotify.EventsCodes.ALL_FLAGS["IN_MODIFY"]
101     self.file = filename
102     self.watch_handle = None
103
104   def enable(self):
105     """Watch the given file
106
107     """
108     if self.watch_handle is None:
109       result = self.watch_manager.add_watch(self.file, self.mask)
110       if not self.file in result or result[self.file] <= 0:
111         raise errors.InotifyError("Could not add inotify watcher")
112       else:
113         self.watch_handle = result[self.file]
114
115   def disable(self):
116     """Stop watching the given file
117
118     """
119     if self.watch_handle is not None:
120       result = self.watch_manager.rm_watch(self.watch_handle)
121       if result[self.watch_handle]:
122         self.watch_handle = None
123
124   # pylint: disable-msg=C0103
125   # this overrides a method in pyinotify.ProcessEvent
126   def process_IN_IGNORED(self, event):
127     # Since we monitor a single file rather than the directory it resides in,
128     # when that file is replaced with another one (which is what happens when
129     # utils.WriteFile, the most normal way of updating files in ganeti, is
130     # called) we're going to receive an IN_IGNORED event from inotify, because
131     # of the file removal (which is contextual with the replacement). In such a
132     # case we'll need to create a watcher for the "new" file. This can be done
133     # by the callback by calling "enable" again on us.
134     logging.debug("Received 'ignored' inotify event for %s", event.path)
135     self.watch_handle = None
136     self.callback(False)
137
138   # pylint: disable-msg=C0103
139   # this overrides a method in pyinotify.ProcessEvent
140   def process_IN_MODIFY(self, event):
141     # This gets called when the monitored file is modified. Note that this
142     # doesn't usually happen in Ganeti, as most of the time we're just
143     # replacing any file with a new one, at filesystem level, rather than
144     # actually changing it. (see utils.WriteFile)
145     logging.debug("Received 'modify' inotify event for %s", event.path)
146     self.callback(True)
147
148   def process_default(self, event):
149     logging.error("Received unhandled inotify event: %s", event)