mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
commit
3dc4b85295
4 changed files with 222 additions and 54 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
1
tests/unit/beacons/__init__.py
Normal file
1
tests/unit/beacons/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
175
tests/unit/beacons/inotify_beacon_test.py
Normal file
175
tests/unit/beacons/inotify_beacon_test.py
Normal 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)
|
Loading…
Add table
Reference in a new issue