summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/FileMonitor
diff options
context:
space:
mode:
authorChris St. Pierre <chris.a.st.pierre@gmail.com>2012-10-11 13:01:21 -0400
committerChris St. Pierre <chris.a.st.pierre@gmail.com>2012-10-11 13:01:31 -0400
commitbc5f0007512fe07ed2b09f9ff3427a7366126f8c (patch)
tree6498e8d100363abe35a5a7737a7257b0c48f305c /src/lib/Bcfg2/Server/FileMonitor
parent24235bfffbe5640476741533d58fccab08ca197b (diff)
downloadbcfg2-bc5f0007512fe07ed2b09f9ff3427a7366126f8c.tar.gz
bcfg2-bc5f0007512fe07ed2b09f9ff3427a7366126f8c.tar.bz2
bcfg2-bc5f0007512fe07ed2b09f9ff3427a7366126f8c.zip
wrote FAM docs
Diffstat (limited to 'src/lib/Bcfg2/Server/FileMonitor')
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Fam.py32
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Gamin.py49
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Inotify.py66
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Pseudo.py13
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/__init__.py232
5 files changed, 340 insertions, 52 deletions
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Fam.py b/src/lib/Bcfg2/Server/FileMonitor/Fam.py
index d1420c105..a392af185 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/Fam.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/Fam.py
@@ -1,7 +1,8 @@
-""" Fam provides FAM support for file alteration events """
+""" File monitor backend with support for the `File Alteration Monitor
+<http://oss.sgi.com/projects/fam/>`_."""
import os
-import _fam
+#import _fam
import stat
import logging
from time import time
@@ -11,30 +12,37 @@ LOGGER = logging.getLogger(__name__)
class Fam(FileMonitor):
- """ file monitor with support for FAM """
+ """ File monitor backend with support for the `File Alteration
+ Monitor <http://oss.sgi.com/projects/fam/>`_ (also abbreviated
+ "FAM")."""
- __priority__ = 90
+ #: FAM is the worst actual monitor backend, so give it a low
+ #: priority.
+ __priority__ = 10
def __init__(self, ignore=None, debug=False):
FileMonitor.__init__(self, ignore=ignore, debug=debug)
self.filemonitor = _fam.open()
self.users = {}
+ __init__.__doc__ = FileMonitor.__init__.__doc__
def fileno(self):
- """Return fam file handle number."""
return self.filemonitor.fileno()
+ fileno.__doc__ = FileMonitor.fileno.__doc__
def handle_event_set(self, _=None):
self.Service()
+ handle_event_set.__doc__ = FileMonitor.handle_event_set.__doc__
def handle_events_in_interval(self, interval):
now = time()
while (time() - now) < interval:
if self.Service():
now = time()
+ handle_events_in_interval.__doc__ = \
+ FileMonitor.handle_events_in_interval.__doc__
def AddMonitor(self, path, obj, _=None):
- """Add a monitor to path, installing a callback to obj.HandleEvent."""
mode = os.stat(path)[stat.ST_MODE]
if stat.S_ISDIR(mode):
handle = self.filemonitor.monitorDirectory(path, None)
@@ -44,9 +52,19 @@ class Fam(FileMonitor):
if obj != None:
self.users[handle.requestID()] = obj
return handle.requestID()
+ AddMonitor.__doc__ = FileMonitor.AddMonitor.__doc__
def Service(self, interval=0.50):
- """Handle all fam work."""
+ """ Handle events for the specified period of time (in
+ seconds). This call will block for ``interval`` seconds.
+
+ :param interval: The interval, in seconds, during which events
+ should be handled. Any events that are
+ already pending when :func:`Service` is
+ called will also be handled.
+ :type interval: int
+ :returns: None
+ """
count = 0
collapsed = 0
rawevents = []
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Gamin.py b/src/lib/Bcfg2/Server/FileMonitor/Gamin.py
index 23f5424d0..9134758b8 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/Gamin.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/Gamin.py
@@ -1,4 +1,5 @@
-""" Gamin driver for file alteration events """
+""" File monitor backend with `Gamin
+<http://people.gnome.org/~veillard/gamin/>`_ support. """
import os
import stat
@@ -7,12 +8,12 @@ from gamin import WatchMonitor, GAMCreated, GAMExists, GAMEndExist, \
from Bcfg2.Server.FileMonitor import Event, FileMonitor
-
class GaminEvent(Event):
- """
- This class provides an event analogous to
- python-fam events based on gamin sources.
- """
+ """ This class maps Gamin event constants to FAM :ref:`event codes
+ <development-fam-event-codes>`. """
+
+ #: The map of gamin event constants (which mirror FAM event names
+ #: closely) to :ref:`event codes <development-fam-event-codes>`
action_map = {GAMCreated: 'created', GAMExists: 'exists',
GAMChanged: 'changed', GAMDeleted: 'deleted',
GAMEndExist: 'endExist'}
@@ -21,19 +22,38 @@ class GaminEvent(Event):
Event.__init__(self, request_id, filename, code)
if code in self.action_map:
self.action = self.action_map[code]
+ __init__.__doc__ = Event.__init__.__doc__
class Gamin(FileMonitor):
- """ file monitor with gamin support """
- __priority__ = 10
+ """ File monitor backend with `Gamin
+ <http://people.gnome.org/~veillard/gamin/>`_ support. """
+
+ #: The Gamin backend is fairly decent, particularly newer
+ #: releases, so it has a fairly high priority.
+ __priority__ = 90
def __init__(self, ignore=None, debug=False):
FileMonitor.__init__(self, ignore=ignore, debug=debug)
+
+ #: The :class:`Gamin.WatchMonitor` object for this monitor.
self.mon = None
+
+ #: The counter used to produce monotonically increasing
+ #: monitor handle IDs
self.counter = 0
+
+ #: The queue used to record monitors that are added before
+ #: :func:`start` has been called and :attr:`mon` is created.
self.add_q = []
+ __init__.__doc__ = FileMonitor.__init__.__doc__
def start(self):
+ """ The Gamin watch monitor in :attr:`mon` must be created by
+ the daemonized process, so is created in ``start()``. Before
+ the :class:`Gamin.WatchMonitor` object is created, monitors
+ are added to :attr:`add_q`, and are created once the watch
+ monitor is created."""
FileMonitor.start(self)
self.mon = WatchMonitor()
for monitor in self.add_q:
@@ -41,14 +61,18 @@ class Gamin(FileMonitor):
self.add_q = []
def fileno(self):
- return self.mon.get_fd()
+ if self.started:
+ return self.mon.get_fd()
+ else:
+ return None
+ fileno.__doc__ = FileMonitor.fileno.__doc__
def queue(self, path, action, request_id):
- """queue up the event for later handling"""
+ """ Create a new :class:`GaminEvent` and add it to the
+ :attr:`events` queue for later handling. """
self.events.append(GaminEvent(request_id, path, action))
def AddMonitor(self, path, obj, handle=None):
- """Add a monitor to path, installing a callback to obj."""
if handle is None:
handle = self.counter
self.counter += 1
@@ -69,11 +93,14 @@ class Gamin(FileMonitor):
self.mon.watch_file(path, self.queue, handle)
self.handles[handle] = obj
return handle
+ AddMonitor.__doc__ = FileMonitor.AddMonitor.__doc__
def pending(self):
return FileMonitor.pending(self) or self.mon.event_pending()
+ pending.__doc__ = FileMonitor.pending.__doc__
def get_event(self):
if self.mon.event_pending():
self.mon.handle_one_event()
return FileMonitor.get_event(self)
+ get_event.__doc__ = FileMonitor.get_event.__doc__
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
index 175df65c8..d5aa8e4ad 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
@@ -1,4 +1,5 @@
-""" Inotify driver for file alteration events """
+"""File monitor backend with `inotify <http://inotify.aiken.cz/>`_
+support. """
import os
import logging
@@ -11,29 +12,75 @@ LOGGER = logging.getLogger(__name__)
class Inotify(Pseudo, pyinotify.ProcessEvent):
- """ file monitor with inotify support """
+ """ File monitor backend with `inotify
+ <http://inotify.aiken.cz/>`_ support. """
+
+ #: Inotify is the best FAM backend, so it gets a very high
+ #: priority
+ __priority__ = 99
- __priority__ = 1
# pylint: disable=E1101
+ #: Map pyinotify event constants to FAM :ref:`event codes
+ #: <development-fam-event-codes>`. The mapping is not
+ #: terrifically exact.
action_map = {pyinotify.IN_CREATE: 'created',
pyinotify.IN_DELETE: 'deleted',
pyinotify.IN_MODIFY: 'changed',
pyinotify.IN_MOVED_FROM: 'deleted',
pyinotify.IN_MOVED_TO: 'created'}
# pylint: enable=E1101
+
+ #: The pyinotify event mask. We only ask for events that are
+ #: listed in :attr:`action_map`
mask = reduce(lambda x, y: x | y, action_map.keys())
def __init__(self, ignore=None, debug=False):
Pseudo.__init__(self, ignore=ignore, debug=debug)
pyinotify.ProcessEvent.__init__(self)
+
+ #: inotify can't set useful monitors directly on files, only
+ #: on directories, so when a monitor is added on a file we add
+ #: its parent directory to ``event_filter`` and then only
+ #: produce events on a file in that directory if the file is
+ #: listed in ``event_filter``. Keys are directories -- the
+ #: parent directories of individual files that are monitored
+ #: -- and values are lists of full paths to files in each
+ #: directory that events *should* be produced for. An event
+ #: on a file whose parent directory is in ``event_filter`` but
+ #: which is not itself listed will be silently suppressed.
self.event_filter = dict()
+
+ #: inotify doesn't like monitoring a path twice, so we keep a
+ #: dict of :class:`pyinotify.Watch` objects, keyed by monitor
+ #: path, to avoid trying to create duplicate monitors.
+ #: (Duplicates can happen if an object accidentally requests
+ #: duplicate monitors, or if two files in a single directory
+ #: are both individually monitored, since inotify can't set
+ #: monitors on the files but only on the parent directories.)
self.watches_by_path = dict()
- # these are created in start() after the server is done forking
+
+ #: The :class:`pyinotify.ThreadedNotifier` object. This is
+ #: created in :func:`start` after the server is done
+ #: daemonizing.
self.notifier = None
+
+ #: The :class:`pyinotify.WatchManager` object. This is created
+ #: in :func:`start` after the server is done daemonizing.
self.watchmgr = None
+
+ #: The queue used to record monitors that are added before
+ #: :func:`start` has been called and :attr:`notifier` and
+ #: :attr:`watchmgr` are created.
self.add_q = []
def start(self):
+ """ The inotify notifier and manager objects in
+ :attr:`notifier` and :attr:`watchmgr` must be created by the
+ daemonized process, so they are created in ``start()``. Before
+ those objects are created, monitors are added to
+ :attr:`add_q`, and are created once the
+ :class:`pyinotify.ThreadedNotifier` and
+ :class:`pyinotify.WatchManager` objects are created."""
Pseudo.start(self)
self.watchmgr = pyinotify.WatchManager()
self.notifier = pyinotify.ThreadedNotifier(self.watchmgr, self)
@@ -47,8 +94,17 @@ class Inotify(Pseudo, pyinotify.ProcessEvent):
return self.watchmgr.get_fd()
else:
return None
+ fileno.__doc__ = Pseudo.fileno.__doc__
def process_default(self, ievent):
+ """ Process all inotify events received. This process a
+ :class:`pyinotify._Event` object, creates a
+ :class:`Bcfg2.Server.FileMonitor.Event` object from it, and
+ adds that event to :attr:`events`.
+
+ :param ievent: Event to be processed
+ :type ievent: pyinotify._Event
+ """
action = ievent.maskname
for amask, aname in self.action_map.items():
if ievent.mask & amask:
@@ -142,7 +198,9 @@ class Inotify(Pseudo, pyinotify.ProcessEvent):
else:
self.handles[path] = obj
return path
+ AddMonitor.__doc__ = Pseudo.AddMonitor.__doc__
def shutdown(self):
if self.notifier:
self.notifier.stop()
+ shutdown.__doc__ = Pseudo.shutdown.__doc__
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Pseudo.py b/src/lib/Bcfg2/Server/FileMonitor/Pseudo.py
index 9062cbfd8..24cd099d0 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/Pseudo.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/Pseudo.py
@@ -1,17 +1,20 @@
-""" Pseudo provides static monitor support for file alteration events """
+""" Pseudo provides static monitor support for file alteration events.
+That is, it only produces "exists" and "endExist" events and does not
+monitor for ongoing changes. """
import os
from Bcfg2.Server.FileMonitor import FileMonitor, Event
class Pseudo(FileMonitor):
- """ file monitor that only produces events on server startup and
- doesn't actually monitor at all """
+ """ File monitor that only produces events on server startup and
+ doesn't actually monitor for ongoing changes at all. """
- __priority__ = 99
+ #: The ``Pseudo`` monitor should only be used if no other FAM
+ #: backends are available.
+ __priority__ = 1
def AddMonitor(self, path, obj, handleID=None):
- """add a monitor to path, installing a callback to obj.HandleEvent"""
if handleID is None:
handleID = len(list(self.handles.keys()))
self.events.append(Event(handleID, path, 'exists'))
diff --git a/src/lib/Bcfg2/Server/FileMonitor/__init__.py b/src/lib/Bcfg2/Server/FileMonitor/__init__.py
index 1b12ab703..ec21a744c 100644
--- a/src/lib/Bcfg2/Server/FileMonitor/__init__.py
+++ b/src/lib/Bcfg2/Server/FileMonitor/__init__.py
@@ -1,4 +1,49 @@
-"""Bcfg2.Server.FileMonitor provides the support for monitoring files."""
+""" Bcfg2.Server.FileMonitor provides the support for monitoring
+files. The FAM acts as a dispatcher for events: An event is detected
+on a file (e.g., the file content is changed), and then that event is
+dispatched to the ``HandleEvent`` method of an object that knows how
+to handle the event. Consequently,
+:func:`Bcfg2.Server.FileMonitor.FileMonitor.AddMonitor` takes two
+arguments: the path to monitor, and the object that handles events
+detected on that event.
+
+``HandleEvent`` is called with a single argument, the
+:class:`Bcfg2.Server.FileMonitor.Event` object to be handled.
+
+Assumptions
+-----------
+
+The FAM API Bcfg2 uses is based on the API of SGI's `File Alteration
+Monitor <http://oss.sgi.com/projects/fam/>`_ (also called "FAM").
+Consequently, a few assumptions apply:
+
+* When a file or directory is monitored for changes, we call that a
+ "monitor"; other backends my use the term "watch," but for
+ consistency we will use "monitor."
+* Monitors can be set on files or directories.
+* A monitor set on a directory monitors all files within that
+ directory, non-recursively. If the object that requested the
+ monitor wishes to monitor recursively, it must implement that
+ itself.
+* Setting a monitor immediately produces "exists" and "endExist"
+ events for the monitored file or directory and all files or
+ directories contained within it (non-recursively).
+* An event on a file or directory that is monitored directly yields
+ the full path to the file or directory.
+* An event on a file or directory that is *only* contained within a
+ monitored directory yields the relative path to the file or
+ directory within the monitored parent. It is the responsibility of
+ the handler to reconstruct full paths as necessary.
+* Each monitor that is set must have a unique ID that identifies it,
+ in order to make it possible to reconstruct full paths as
+ necessary. This ID will be stored in
+ :attr:`Bcfg2.Server.FileMonitor.FileMonitor.handles`. It may be any
+ hashable value; some FAM backends use monotonically increasing
+ integers, while others use the path to the monitor.
+
+Base Classes
+------------
+"""
import os
import sys
@@ -10,14 +55,45 @@ LOGGER = logging.getLogger(__name__)
class Event(object):
- """ Base class for all FAM events """
+ """ Base class for all FAM events. """
+
def __init__(self, request_id, filename, code):
+ """
+ :param request_id: The handler ID of the monitor that produced
+ this event
+ :type request_id: Varies
+ :param filename: The file or directory on which the event was
+ detected. An event on a file or directory
+ that is monitored directly yields the full
+ path to the file or directory; an event on a
+ file or directory that is *only* contained
+ within a monitored directory yields the
+ relative path to the file or directory within
+ the monitored parent.
+ :type filename: string
+ :param code: The :ref:`event code
+ <development-fam-event-codes>` produced. I.e.,
+ the type of event.
+ :type code: string
+ """
+ #: The handler ID of the monitor that produced this event
self.requestID = request_id
+
+ #: The file or directory on which the event was detected. An
+ #: event on a file or directory that is monitored directly
+ #: yields the full path to the file or directory; an event on
+ #: a file or directory that is *only* contained within a
+ #: monitored directory yields the relative path to the file or
+ #: directory within the monitored parent.
self.filename = filename
+
+ #: The :ref:`event code <development-fam-event-codes>`
+ #: produced. I.e., the type of event.
self.action = code
def code2str(self):
- """return static code for event"""
+ """ Return the :ref:`event code <development-fam-event-codes>`
+ for this event. This is just an alias for :attr:`action`. """
return self.action
def __str__(self):
@@ -29,15 +105,48 @@ class Event(object):
class FileMonitor(object):
- """File Monitor baseclass."""
+ """ The base class that all FAM implementions must inherit.
+
+ The simplest instance of a FileMonitor subclass needs only to add
+ monitor objects to :attr:`handles` and received events to
+ :attr:`events`; the basic interface will handle the rest. """
+
+ #: The relative priority of this FAM backend. Better backends
+ #: should have higher priorities.
+ __priority__ = -1
+
def __init__(self, ignore=None, debug=False):
- object.__init__(self)
+ """
+ :param ignore: A list of filename globs describing events that
+ should be ignored (i.e., not processed by any
+ object)
+ :type ignore: list of strings (filename globs)
+ :param debug: Produce debugging information about the events
+ received and handled.
+ :type debug: bool
+
+ .. -----
+ .. autoattribute:: __priority__
+ """
+ #: Whether or not to produce debug logging
self.debug = debug
+
+ #: A dict that records which objects handle which events.
+ #: Keys are monitor handle IDs and values are objects whose
+ #: ``HandleEvent`` method will be called to handle an event
self.handles = dict()
+
+ #: Queue of events to handle
self.events = []
+
if ignore is None:
ignore = []
+ #: List of filename globs to ignore events for. For events
+ #: that include the full path, both the full path and the bare
+ #: filename will be checked against ``ignore``.
self.ignore = ignore
+
+ #: Whether or not the FAM has been started. See :func:`start`.
self.started = False
def __str__(self):
@@ -49,17 +158,34 @@ class FileMonitor(object):
self.fileno())
def start(self):
- """ start threads or anything else that needs to be done after
- the server forks and daemonizes """
+ """ Start threads or anything else that needs to be done after
+ the server forks and daemonizes. Note that monitors may (and
+ almost certainly will) be added before ``start()`` is called,
+ so if a backend depends on being started to add monitors,
+ those requests will need to be enqueued and added after
+ ``start()``. See
+ :class:`Bcfg2.Server.FileMonitor.Inotify.Inotify` for an
+ example of this. """
self.started = True
def debug_log(self, msg):
- """ log a debug message """
+ """ Log a debug message.
+
+ :param msg: The message to log iff :attr:`debug` is set."""
if self.debug:
LOGGER.info(msg)
def should_ignore(self, event):
- """ returns true if an event should be ignored """
+ """ Returns True if an event should be ignored, False
+ otherwise. For events that include the full path, both the
+ full path and the bare filename will be checked against
+ :attr:`ignore`. If the event is ignored, a debug message will
+ be logged with :func:`debug_log`.
+
+ :param event: Check if this event matches :attr:`ignore`
+ :type event: Bcfg2.Server.FileMonitor.Event
+ :returns: bool - Whether not to ignore the event
+ """
for pattern in self.ignore:
if (fnmatch.fnmatch(event.filename, pattern) or
fnmatch.fnmatch(os.path.split(event.filename)[-1], pattern)):
@@ -68,20 +194,36 @@ class FileMonitor(object):
return False
def pending(self):
- """ returns True if there are pending events """
+ """ Returns True if there are pending events (i.e., events in
+ :attr:`events` that have not been processed), False
+ otherwise. """
return bool(self.events)
def get_event(self):
- """ get the oldest pending event """
+ """ Get the oldest pending event in :attr:`events`.
+
+ :returns: :class:`Bcfg2.Server.FileMonitor.Event`
+ """
return self.events.pop(0)
def fileno(self):
- """ get the file descriptor of the file monitor thread """
+ """ Get the file descriptor of the file monitor thread.
+
+ :returns: int - The FD number
+ """
return 0
def handle_one_event(self, event):
- """ handle the given event by dispatching it to the object
- that handles events for the path """
+ """ Handle the given event by dispatching it to the object
+ that handles it. This is only called by
+ :func:`handle_event_set`, so if a backend overrides that
+ method it does not necessarily need to implement this
+ function.
+
+ :param event: The event to handle.
+ :type event: Bcfg2.Server.FileMonitor.Event
+ :returns: None
+ """
if not self.started:
self.start()
if self.should_ignore(event):
@@ -101,15 +243,22 @@ class FileMonitor(object):
(event.code2str(), event.filename, err))
def handle_event_set(self, lock=None):
- """ Handle all pending events """
+ """ Handle all pending events.
+
+ :param lock: A thread lock to use while handling events. If
+ None, then no thread locking will be performed.
+ This can possibly lead to race conditions in
+ event handling, although it's unlikely to cause
+ any real problems.
+ :type lock: threading.Lock
+ :returns: None
+ """
if not self.started:
self.start()
- count = 1
- event = self.get_event()
+ count = 0
start = time()
if lock:
lock.acquire()
- self.handle_one_event(event)
while self.pending():
self.handle_one_event(self.get_event())
count += 1
@@ -119,8 +268,19 @@ class FileMonitor(object):
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) """
+ """ Handle events for the specified period of time (in
+ seconds). This call will block for ``interval`` seconds and
+ handle all events received during that period by calling
+ :func:`handle_event_set`.
+
+ :param interval: The interval, in seconds, during which events
+ should be handled. Any events that are
+ already pending when
+ :func:`handle_events_in_interval` is called
+ will also be handled.
+ :type interval: int
+ :returns: None
+ """
if not self.started:
self.start()
end = time() + interval
@@ -132,17 +292,38 @@ class FileMonitor(object):
sleep(0.5)
def shutdown(self):
- """ shutdown the monitor """
+ """ Handle any tasks required to shut down the monitor. """
self.started = False
def AddMonitor(self, path, obj, handleID=None):
- """ watch the specified path, alerting obj to events """
+ """ Monitor the specified path, alerting obj to events. This
+ method must be overridden by a subclass of
+ :class:`Bcfg2.Server.FileMonitor.FileMonitor`.
+
+ :param path: The path to monitor
+ :type path: string
+ :param obj: The object whose ``HandleEvent`` method will be
+ called when an event is produced.
+ :type obj: Varies
+ :param handleID: The handle ID to use for the monitor. This
+ is useful when requests to add a monitor must
+ be enqueued and the actual monitors added
+ after :func:`start` is called.
+ :type handleID: Varies
+ :returns: Varies - The handler ID for the newly created
+ monitor
+ """
raise NotImplementedError
+#: A dict of all available FAM backends. Keys are the human-readable
+#: names of the backends, which are used in bcfg2.conf to select a
+#: backend; values are the backend classes. In addition, the
+#: ``default`` key will be set to the best FAM backend as determined
+#: by :attr:`Bcfg2.Server.FileMonitor.FileMonitor.__priority__`
available = dict() # pylint: disable=C0103
-# todo: loading the monitor drivers should be automatic
+# TODO: loading the monitor drivers should be automatic
from Bcfg2.Server.FileMonitor.Pseudo import Pseudo
available['pseudo'] = Pseudo
@@ -162,9 +343,10 @@ try:
from Bcfg2.Server.FileMonitor.Inotify import Inotify
available['inotify'] = Inotify
except ImportError:
- pass
+ pass
-for fdrv in sorted(available.keys(), key=lambda k: available[k].__priority__):
+for fdrv in reversed(sorted(available.keys(),
+ key=lambda k: available[k].__priority__)):
if fdrv in available:
available['default'] = available[fdrv]
break