summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/FileMonitor/__init__.py
blob: 1b12ab703330ef0178de8bde543d5e379bd0be1b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""Bcfg2.Server.FileMonitor provides the support for monitoring files."""

import os
import sys
import fnmatch
import logging
from time import sleep, time

LOGGER = logging.getLogger(__name__)


class Event(object):
    """ Base class for all FAM events """
    def __init__(self, request_id, filename, code):
        self.requestID = request_id
        self.filename = filename
        self.action = code

    def code2str(self):
        """return static code for event"""
        return self.action

    def __str__(self):
        return "%s: %s %s" % (self.__class__.__name__,
                              self.filename, self.action)

    def __repr__(self):
        return "%s (request ID %s)" % (str(self), self.requestID)


class FileMonitor(object):
    """File Monitor baseclass."""
    def __init__(self, ignore=None, debug=False):
        object.__init__(self)
        self.debug = debug
        self.handles = dict()
        self.events = []
        if ignore is None:
            ignore = []
        self.ignore = ignore
        self.started = False

    def __str__(self):
        return "%s: %s" % (__name__, self.__class__.__name__)

    def __repr__(self):
        return "%s (%s events, fd %s)" % (self.__class__.__name__,
                                          len(self.events),
                                          self.fileno())

    def start(self):
        """ start threads or anything else that needs to be done after
        the server forks and daemonizes """
        self.started = True

    def debug_log(self, msg):
        """ log a debug message """
        if self.debug:
            LOGGER.info(msg)

    def should_ignore(self, event):
        """ returns true if an event should be ignored """
        for pattern in self.ignore:
            if (fnmatch.fnmatch(event.filename, pattern) or
                fnmatch.fnmatch(os.path.split(event.filename)[-1], pattern)):
                self.debug_log("Ignoring %s" % event)
                return True
        return False

    def pending(self):
        """ returns True if there are pending events """
        return bool(self.events)

    def get_event(self):
        """ get the oldest pending event """
        return self.events.pop(0)

    def fileno(self):
        """ get the file descriptor of the file monitor thread """
        return 0

    def handle_one_event(self, event):
        """ handle the given event by dispatching it to the object
        that handles events for the path """
        if not self.started:
            self.start()
        if self.should_ignore(event):
            return
        if event.requestID not in self.handles:
            LOGGER.info("Got event for unexpected id %s, file %s" %
                        (event.requestID, event.filename))
            return
        self.debug_log("Dispatching event %s %s to obj %s" %
                       (event.code2str(), event.filename,
                        self.handles[event.requestID]))
        try:
            self.handles[event.requestID].HandleEvent(event)
        except:  # pylint: disable=W0702
            err = sys.exc_info()[1]
            LOGGER.error("Error in handling of event %s for %s: %s" %
                         (event.code2str(), event.filename, err))

    def handle_event_set(self, lock=None):
        """ Handle all pending events """
        if not self.started:
            self.start()
        count = 1
        event = self.get_event()
        start = time()
        if lock:
            lock.acquire()
        self.handle_one_event(event)
        while self.pending():
            self.handle_one_event(self.get_event())
            count += 1
        if lock:
            lock.release()
        end = time()
        LOGGER.info("Handled %d events in %.03fs" % (count, (end - start)))

    def handle_events_in_interval(self, interval):
        """ handle events for the specified period of time (in
        seconds) """
        if not self.started:
            self.start()
        end = time() + interval
        while time() < end:
            if self.pending():
                self.handle_event_set()
                end = time() + interval
            else:
                sleep(0.5)

    def shutdown(self):
        """ shutdown the monitor """
        self.started = False

    def AddMonitor(self, path, obj, handleID=None):
        """ watch the specified path, alerting obj to events """
        raise NotImplementedError


available = dict()  # pylint: disable=C0103

# todo: loading the monitor drivers should be automatic
from Bcfg2.Server.FileMonitor.Pseudo import Pseudo
available['pseudo'] = Pseudo

try:
    from Bcfg2.Server.FileMonitor.Fam import Fam
    available['fam'] = Fam
except ImportError:
    pass

try:
    from Bcfg2.Server.FileMonitor.Gamin import Gamin
    available['gamin'] = Gamin
except ImportError:
    pass

try:
    from Bcfg2.Server.FileMonitor.Inotify import Inotify
    available['inotify'] = Inotify
except ImportError:
    pass    

for fdrv in sorted(available.keys(), key=lambda k: available[k].__priority__):
    if fdrv in available:
        available['default'] = available[fdrv]
        break