Merge pull request #24190 from msteed/issue-23815

Fix issue 23815
This commit is contained in:
Thomas S Hatch 2015-05-28 14:10:34 -06:00
commit 3dc4b85295
4 changed files with 222 additions and 54 deletions

View file

@ -50,18 +50,17 @@ def _enqueue(revent):
'''
Enqueue the event
'''
__context__['inotify.que'].append(revent)
__context__['inotify.queue'].append(revent)
def _get_notifier():
'''
Check the context for the notifier and construct it if not present
'''
if 'inotify.notifier' in __context__:
return __context__['inotify.notifier']
__context__['inotify.que'] = collections.deque()
wm = pyinotify.WatchManager()
__context__['inotify.notifier'] = pyinotify.Notifier(wm, _enqueue)
if 'inotify.notifier' not in __context__:
__context__['inotify.queue'] = collections.deque()
wm = pyinotify.WatchManager()
__context__['inotify.notifier'] = pyinotify.Notifier(wm, _enqueue)
return __context__['inotify.notifier']
@ -83,56 +82,55 @@ def beacon(config):
recurse: True
auto_add: True
The mask list can contain options:
* access File was accessed
* attrib Metadata changed
* close_nowrite Unwrittable file closed
* close_write Writtable file was closed
* create File created
* delete File deleted
* delete_self Named file or directory deleted
* excl_unlink
* ignored
* modify File was modified
* moved_from File being watched was moved
* moved_to File moved into watched area
* move_self Named file was moved
* oneshot
The mask list can contain the following events (the default mask is create,
delete, and modify):
* access File accessed
* attrib File metadata changed
* close_nowrite Unwritable file closed
* close_write Writable file closed
* create File created in watched directory
* delete File deleted from watched directory
* delete_self Watched file or directory deleted
* modify File modified
* moved_from File moved out of watched directory
* moved_to File moved into watched directory
* move_self Watched file moved
* open File opened
The mask can also contain the following options:
* dont_follow Don't dereference symbolic links
* excl_unlink Omit events for children after they have been unlinked
* oneshot Remove watch after one event
* onlydir Operate only if name is directory
* open File was opened
* unmount Backing fs was unmounted
recurse:
Tell the beacon to recursively watch files in the directory
Recursively watch files in the directory
auto_add:
Automatically start adding files that are created in the watched directory
Automatically start watching files that are created in the watched directory
'''
ret = []
notifier = _get_notifier()
wm = notifier._watch_manager
# Read in existing events
# remove watcher files that are not in the config
# update all existing files with watcher settings
# return original data
if notifier.check_events(1):
notifier.read_events()
notifier.process_events()
while __context__['inotify.que']:
sub = {}
event = __context__['inotify.que'].popleft()
sub['tag'] = event.path
sub['path'] = event.pathname
sub['change'] = event.maskname
queue = __context__['inotify.queue']
while queue:
event = queue.popleft()
sub = {'tag': event.path,
'path': event.pathname,
'change': event.maskname}
ret.append(sub)
# Get paths currently being watched
current = set()
for wd in wm.watches:
current.add(wm.watches[wd].path)
need = set(config)
for path in current.difference(need):
# These need to be removed
for wd in wm.watches:
if path == wm.watches[wd].path:
wm.rm_watch(wd)
# Update existing watches and add new ones
# TODO: make the config handle more options
for path in config:
if isinstance(config[path], dict):
mask = config[path].get('mask', DEFAULT_MASK)
@ -151,14 +149,8 @@ def beacon(config):
mask = DEFAULT_MASK
rec = False
auto_add = False
# TODO: make the config handle more options
if path not in current:
wm.add_watch(
path,
mask,
rec=rec,
auto_add=auto_add)
else:
if path in current:
for wd in wm.watches:
if path == wm.watches[wd].path:
update = False
@ -167,9 +159,9 @@ def beacon(config):
if wm.watches[wd].auto_add != auto_add:
update = True
if update:
wm.update_watch(
wd,
mask=mask,
rec=rec,
auto_add=auto_add)
wm.update_watch(wd, mask=mask, rec=rec, auto_add=auto_add)
else:
wm.add_watch(path, mask, rec=rec, auto_add=auto_add)
# Return event data
return ret

View file

@ -893,7 +893,7 @@ class Minion(MinionBase):
try:
beacons = self.process_beacons(self.functions)
except Exception as exc:
log.critical('Beacon processing errored: {0}. No beacons will be procssed.'.format(traceback.format_exc(exc)))
log.critical('Beacon processing failed: {0}. No beacons will be processed.'.format(traceback.format_exc(exc)))
beacons = None
if beacons:
self._fire_master(events=beacons)

View file

@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View file

@ -0,0 +1,175 @@
# coding: utf-8
# Python libs
from __future__ import absolute_import
import os
import shutil
import tempfile
# Salt libs
from salt.beacons import inotify
# Salt testing libs
from salttesting import skipIf, TestCase
from salttesting.helpers import destructiveTest, ensure_in_syspath
from salttesting.mock import NO_MOCK, NO_MOCK_REASON
# Third-party libs
try:
import pyinotify # pylint: disable=unused-import
HAS_PYINOTIFY = True
except ImportError:
HAS_PYINOTIFY = False
ensure_in_syspath('../../')
@skipIf(not HAS_PYINOTIFY, 'pyinotify is not available')
@skipIf(NO_MOCK, NO_MOCK_REASON)
class INotifyBeaconTestCase(TestCase):
'''
Test case for salt.beacons.inotify
'''
def setUp(self):
inotify.__context__ = {}
def test_empty_config(self, *args, **kwargs):
config = {}
ret = inotify.beacon(config)
self.assertEqual(ret, [])
def test_file_open(self, *args, **kwargs):
path = os.path.realpath(__file__)
config = {path: {'mask': ['open']}}
ret = inotify.beacon(config)
self.assertEqual(ret, [])
with open(path, 'r') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], path)
self.assertEqual(ret[0]['change'], 'IN_OPEN')
@destructiveTest
def test_dir_no_auto_add(self, *args, **kwargs):
tmpdir = None
try:
tmpdir = tempfile.mkdtemp()
config = {tmpdir: {'mask': ['create']}}
ret = inotify.beacon(config)
self.assertEqual(ret, [])
fp = os.path.join(tmpdir, 'tmpfile')
with open(fp, 'w') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_CREATE')
with open(fp, 'r') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(ret, [])
finally:
if tmpdir:
shutil.rmtree(tmpdir)
@destructiveTest
def test_dir_auto_add(self, *args, **kwargs):
tmpdir = None
try:
tmpdir = tempfile.mkdtemp()
config = {tmpdir: {'mask': ['create', 'open'], 'auto_add': True}}
ret = inotify.beacon(config)
self.assertEqual(ret, [])
fp = os.path.join(tmpdir, 'tmpfile')
with open(fp, 'w') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 2)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_CREATE')
self.assertEqual(ret[1]['path'], fp)
self.assertEqual(ret[1]['change'], 'IN_OPEN')
with open(fp, 'r') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_OPEN')
finally:
if tmpdir:
shutil.rmtree(tmpdir)
@destructiveTest
def test_dir_recurse(self, *args, **kwargs):
tmpdir = None
try:
tmpdir = tempfile.mkdtemp()
dp1 = os.path.join(tmpdir, 'subdir1')
os.mkdir(dp1)
dp2 = os.path.join(dp1, 'subdir2')
os.mkdir(dp2)
fp = os.path.join(dp2, 'tmpfile')
with open(fp, 'w') as f:
pass
config = {tmpdir: {'mask': ['open'], 'recurse': True}}
ret = inotify.beacon(config)
self.assertEqual(ret, [])
with open(fp) as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 3)
self.assertEqual(ret[0]['path'], dp1)
self.assertEqual(ret[0]['change'], 'IN_OPEN|IN_ISDIR')
self.assertEqual(ret[1]['path'], dp2)
self.assertEqual(ret[1]['change'], 'IN_OPEN|IN_ISDIR')
self.assertEqual(ret[2]['path'], fp)
self.assertEqual(ret[2]['change'], 'IN_OPEN')
finally:
if tmpdir:
shutil.rmtree(tmpdir)
@destructiveTest
def test_dir_recurse_auto_add(self, *args, **kwargs):
tmpdir = None
try:
tmpdir = tempfile.mkdtemp()
dp1 = os.path.join(tmpdir, 'subdir1')
os.mkdir(dp1)
config = {tmpdir: {'mask': ['create', 'delete'],
'recurse': True,
'auto_add': True}}
ret = inotify.beacon(config)
self.assertEqual(ret, [])
dp2 = os.path.join(dp1, 'subdir2')
os.mkdir(dp2)
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], dp2)
self.assertEqual(ret[0]['change'], 'IN_CREATE|IN_ISDIR')
fp = os.path.join(dp2, 'tmpfile')
with open(fp, 'w') as f:
pass
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_CREATE')
os.remove(fp)
ret = inotify.beacon(config)
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['path'], fp)
self.assertEqual(ret[0]['change'], 'IN_DELETE')
finally:
if tmpdir:
shutil.rmtree(tmpdir)
if __name__ == '__main__':
from integration import run_tests
run_tests(INotifyBeaconTestCase, needs_daemon=False)