test_inotify.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. # -*- coding: utf-8 -*-
  2. # Import Python libs
  3. from __future__ import absolute_import, print_function, unicode_literals
  4. import os
  5. import shutil
  6. import tempfile
  7. import time
  8. try:
  9. import pyinotify # pylint: disable=unused-import
  10. HAS_PYINOTIFY = True
  11. except ImportError:
  12. HAS_PYINOTIFY = False
  13. # Import Salt Testing libs
  14. from tests.support.case import MultimasterModuleCase
  15. from tests.support.mixins import AdaptedConfigurationTestCaseMixin
  16. from tests.support.unit import skipIf
  17. # Import salt libs
  18. import salt.version
  19. import salt.config
  20. import logging
  21. log = logging.getLogger(__name__)
  22. @skipIf(not HAS_PYINOTIFY, 'pyinotify is not available')
  23. class TestBeaconsInotify(MultimasterModuleCase, AdaptedConfigurationTestCaseMixin):
  24. '''
  25. Validate the inotify beacon in multimaster environment
  26. '''
  27. def setUp(self):
  28. self.tmpdir = salt.utils.stringutils.to_unicode(tempfile.mkdtemp())
  29. self.addCleanup(shutil.rmtree, self.tmpdir, ignore_errors=True)
  30. def test_beacons_duplicate_53344(self):
  31. # Also add a status beacon to use it for interval checks
  32. res = self.run_function(
  33. 'beacons.add',
  34. ('inotify', [{'files': {self.tmpdir: {'mask': ['create']}}}]),
  35. master_tgt='mm-master',
  36. )
  37. log.debug('Inotify beacon add returned: %s', res)
  38. self.assertTrue(res.get('result'))
  39. self.addCleanup(self.run_function, 'beacons.delete', ('inotify',), master_tgt='mm-master')
  40. res = self.run_function(
  41. 'beacons.add',
  42. ('status', [{'time': ['all']}]),
  43. master_tgt='mm-master',
  44. )
  45. log.debug('Status beacon add returned: %s', res)
  46. self.assertTrue(res.get('result'))
  47. self.addCleanup(self.run_function, 'beacons.delete', ('status',), master_tgt='mm-master')
  48. # Ensure beacons are added.
  49. res = self.run_function(
  50. 'beacons.list',
  51. (),
  52. return_yaml=False,
  53. master_tgt='mm-master',
  54. )
  55. log.debug('Beacons list: %s', res)
  56. self.assertEqual({
  57. 'inotify': [{
  58. 'files': {
  59. self.tmpdir: {
  60. 'mask': ['create']
  61. }
  62. }
  63. }],
  64. 'status': [{
  65. 'time': ['all']
  66. }]
  67. }, res)
  68. file_path = os.path.join(self.tmpdir, 'tmpfile')
  69. master_listener = salt.utils.event.get_master_event(
  70. self.mm_master_opts,
  71. sock_dir=self.mm_master_opts['sock_dir'])
  72. self.addCleanup(master_listener.destroy)
  73. sub_master_listener = salt.utils.event.get_master_event(
  74. self.mm_sub_master_opts,
  75. sock_dir=self.mm_sub_master_opts['sock_dir'])
  76. self.addCleanup(sub_master_listener.destroy)
  77. # We have to wait beacon first execution that would configure the inotify watch.
  78. # Since beacons will be executed both together waiting for the first status beacon event
  79. # which will mean the inotify beacon was executed too.
  80. start = time.time()
  81. stop_at = start + self.mm_minion_opts['loop_interval'] * 2 + 60
  82. event = sub_event = None
  83. while True:
  84. if time.time() > stop_at:
  85. break
  86. if not event:
  87. event = master_listener.get_event(
  88. full=True,
  89. wait=1,
  90. tag='salt/beacon/mm-minion/status',
  91. match_type='startswith'
  92. )
  93. if sub_event is None:
  94. sub_event = sub_master_listener.get_event(
  95. full=True,
  96. wait=1,
  97. tag='salt/beacon/mm-minion/status',
  98. match_type='startswith'
  99. )
  100. if event and sub_event:
  101. break
  102. log.debug('Status events received: %s, %s', event, sub_event)
  103. if not event or not sub_event:
  104. self.fail('Failed to receive at least one of the status events')
  105. with salt.utils.files.fopen(file_path, 'w') as f:
  106. pass
  107. start = time.time()
  108. # Now in successful case this test will get results at most in 2 loop intervals.
  109. # Waiting for 2 loops intervals + some seconds to the hardware stupidity.
  110. stop_at = start + self.mm_minion_opts['loop_interval'] * 3 + 60
  111. event = sub_event = None
  112. expected_tag = salt.utils.stringutils.to_str(
  113. 'salt/beacon/mm-minion/inotify/{}'.format(self.tmpdir))
  114. while True:
  115. if time.time() > stop_at:
  116. break
  117. if not event:
  118. event = master_listener.get_event(
  119. full=True,
  120. wait=1,
  121. tag=expected_tag,
  122. match_type='startswith'
  123. )
  124. if sub_event is None:
  125. sub_event = sub_master_listener.get_event(
  126. full=True,
  127. wait=1,
  128. tag=expected_tag,
  129. match_type='startswith'
  130. )
  131. if event and sub_event:
  132. break
  133. log.debug('Inotify events received: %s, %s', event, sub_event)
  134. if not event or not sub_event:
  135. self.fail('Failed to receive at least one of the inotify events')
  136. # We can't determine the timestamp so remove it from results
  137. if event:
  138. del event['data']['_stamp']
  139. if sub_event:
  140. del sub_event['data']['_stamp']
  141. expected = {
  142. 'data': {
  143. 'path': file_path,
  144. 'change': 'IN_CREATE',
  145. 'id': 'mm-minion',
  146. },
  147. 'tag': expected_tag
  148. }
  149. # It's better to compare both at once to see both responses in the error log.
  150. self.assertEqual((expected, expected), (event, sub_event))