# -*- coding: utf-8 -*- # Import Python libs from __future__ import absolute_import, print_function, unicode_literals import os import shutil import tempfile import time try: import pyinotify # pylint: disable=unused-import HAS_PYINOTIFY = True except ImportError: HAS_PYINOTIFY = False # Import Salt Testing libs from tests.support.case import MultimasterModuleCase from tests.support.mixins import AdaptedConfigurationTestCaseMixin from tests.support.unit import skipIf # Import salt libs import salt.version import salt.config import logging log = logging.getLogger(__name__) @skipIf(not HAS_PYINOTIFY, 'pyinotify is not available') class TestBeaconsInotify(MultimasterModuleCase, AdaptedConfigurationTestCaseMixin): ''' Validate the inotify beacon in multimaster environment ''' def setUp(self): self.tmpdir = salt.utils.stringutils.to_unicode(tempfile.mkdtemp()) def tearDown(self): shutil.rmtree(self.tmpdir, ignore_errors=True) def test_beacons_duplicate_53344(self): # Also add a status beacon to use it for interval checks res = self.run_function( 'beacons.add', ('inotify', [{'files': {self.tmpdir: {'mask': ['create']}}}]), master_tgt=0, ) log.debug('Inotify beacon add returned: %s', res) self.assertTrue(res.get('result')) res = self.run_function( 'beacons.add', ('status', [{'time': ['all']}]), master_tgt=0, ) log.debug('Status beacon add returned: %s', res) self.assertTrue(res.get('result')) # Ensure beacons are added. res = self.run_function( 'beacons.list', (), return_yaml=False, master_tgt=0, ) log.debug('Beacons list: %s', res) self.assertEqual({ 'inotify': [{ 'files': { self.tmpdir: { 'mask': ['create'] } } }], 'status': [{ 'time': ['all'] }] }, res) file_path = os.path.join(self.tmpdir, 'tmpfile') try: master_listener = salt.utils.event.get_event( 'master', sock_dir=self.mm_master_opts['sock_dir'], transport=self.mm_master_opts['transport'], opts=self.mm_master_opts) sub_master_listener = salt.utils.event.get_event( 'master', sock_dir=self.mm_sub_master_opts['sock_dir'], transport=self.mm_sub_master_opts['transport'], opts=self.mm_sub_master_opts) # We have to wait beacon first execution that would configure the inotify watch. # Since beacons will be executed both together waiting for the first status beacon event # which will mean the inotify beacon was executed too. start = time.time() stop_at = start + self.mm_minion_opts['loop_interval'] * 2 + 60 event = sub_event = None while True: if time.time() > stop_at: break if not event: event = master_listener.get_event( full=True, wait=1, tag='salt/beacon/minion/status', match_type='startswith', ) if sub_event is None: sub_event = sub_master_listener.get_event( full=True, wait=1, tag='salt/beacon/minion/status', match_type='startswith', ) if event and sub_event: break log.debug('Status events received: %s, %s', event, sub_event) with salt.utils.files.fopen(file_path, 'w') as f: pass start = time.time() # Now in successful case this test will get results at most in 2 loop intervals. # Waiting for 2 loops intervals + some seconds to the hardware stupidity. stop_at = start + self.mm_minion_opts['loop_interval'] * 2 + 60 event = sub_event = None while True: if time.time() > stop_at: break if not event: event = master_listener.get_event( full=True, wait=1, tag='salt/beacon/minion/inotify/' + self.tmpdir, match_type='startswith', ) if sub_event is None: sub_event = sub_master_listener.get_event( full=True, wait=1, tag='salt/beacon/minion/inotify/' + self.tmpdir, match_type='startswith', ) if event and sub_event: break log.debug('Inotify events received: %s, %s', event, sub_event) finally: self.assertTrue(self.run_function( 'beacons.delete', ('inotify',), master_tgt=0, )) self.assertTrue(self.run_function( 'beacons.delete', ('status',), master_tgt=0, )) master_listener.destroy() sub_master_listener.destroy() # We can't determine the timestamp so remove it from results if event: del event['data']['_stamp'] if sub_event: del sub_event['data']['_stamp'] expected = { 'data': { 'path': file_path, 'change': 'IN_CREATE', 'id': 'minion', }, 'tag': salt.utils.stringutils.to_str('salt/beacon/minion/inotify/' + self.tmpdir), } # It's better to compare both at once to see both responses in the error log. self.assertEqual((expected, expected), (event, sub_event))