test_inotify.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import logging
  2. import shutil
  3. import time
  4. import pytest
  5. import salt.config
  6. import salt.version
  7. from tests.support.helpers import slowTest
  8. try:
  9. import pyinotify # pylint: disable=unused-import
  10. HAS_PYINOTIFY = True
  11. except ImportError:
  12. HAS_PYINOTIFY = False
  13. log = logging.getLogger(__name__)
  14. pytestmark = [
  15. pytest.mark.skipif(HAS_PYINOTIFY is False, reason="pyinotify is not available"),
  16. pytest.mark.skipif(
  17. salt.utils.platform.is_freebsd(),
  18. reason="Skip on FreeBSD, IN_CREATE event is not supported",
  19. ),
  20. ]
  21. @pytest.fixture(scope="module")
  22. def inotify_test_path(tmp_path_factory):
  23. test_path = tmp_path_factory.mktemp("inotify-tests")
  24. try:
  25. yield test_path
  26. finally:
  27. shutil.rmtree(str(test_path), ignore_errors=True)
  28. @pytest.fixture(scope="module")
  29. def event_listener(salt_factories):
  30. return salt_factories.event_listener
  31. @pytest.fixture(scope="module")
  32. def setup_beacons(mm_master_1_salt_cli, salt_mm_minion_1, inotify_test_path):
  33. start_time = time.time()
  34. try:
  35. # Add a status beacon to use for interval checks
  36. ret = mm_master_1_salt_cli.run(
  37. "beacons.add",
  38. "inotify",
  39. beacon_data=[{"files": {str(inotify_test_path): {"mask": ["create"]}}}],
  40. minion_tgt=salt_mm_minion_1.id,
  41. )
  42. assert ret.exitcode == 0
  43. log.debug("Inotify beacon add returned: %s", ret.json or ret.stdout)
  44. assert ret.json
  45. assert ret.json["result"] is True
  46. ret = mm_master_1_salt_cli.run(
  47. "beacons.add",
  48. "status",
  49. beacon_data=[{"time": ["all"]}],
  50. minion_tgt=salt_mm_minion_1.id,
  51. )
  52. assert ret.exitcode == 0
  53. log.debug("Status beacon add returned: %s", ret.json or ret.stdout)
  54. assert ret.json
  55. assert ret.json["result"] is True
  56. ret = mm_master_1_salt_cli.run(
  57. "beacons.list", return_yaml=False, minion_tgt=salt_mm_minion_1.id
  58. )
  59. assert ret.exitcode == 0
  60. log.debug("Beacons list: %s", ret.json or ret.stdout)
  61. assert ret.json
  62. assert "inotify" in ret.json
  63. assert ret.json["inotify"] == [
  64. {"files": {str(inotify_test_path): {"mask": ["create"]}}}
  65. ]
  66. assert "status" in ret.json
  67. assert ret.json["status"] == [{"time": ["all"]}]
  68. yield start_time
  69. finally:
  70. # Remove the added beacons
  71. for beacon in ("inotify", "status"):
  72. mm_master_1_salt_cli.run(
  73. "beacons.delete", beacon, minion_tgt=salt_mm_minion_1.id
  74. )
  75. @slowTest
  76. def test_beacons_duplicate_53344(
  77. event_listener,
  78. inotify_test_path,
  79. salt_mm_minion_1,
  80. salt_mm_master_1,
  81. salt_mm_master_2,
  82. setup_beacons,
  83. ):
  84. # We have to wait beacon first execution that would configure the inotify watch.
  85. # Since beacons will be executed both together, we wait for the status beacon event
  86. # which means that, the inotify becacon was executed too
  87. start_time = setup_beacons
  88. stop_time = start_time + salt_mm_minion_1.config["loop_interval"] * 2 + 60
  89. mm_master_1_event = mm_master_2_event = None
  90. expected_tag = "salt/beacon/{}/status/*".format(salt_mm_minion_1.id)
  91. mm_master_1_event_pattern = (salt_mm_master_1.id, expected_tag)
  92. mm_master_2_event_pattern = (salt_mm_master_2.id, expected_tag)
  93. while True:
  94. if time.time() > stop_time:
  95. pytest.fail(
  96. "Failed to receive at least one of the status events. "
  97. "Master 1 Event: {}; Master 2 Event: {}".format(
  98. mm_master_1_event, mm_master_2_event
  99. )
  100. )
  101. if not mm_master_1_event:
  102. events = event_listener.get_events(
  103. [mm_master_1_event_pattern], after_time=start_time
  104. )
  105. for event in events:
  106. mm_master_1_event = event
  107. break
  108. if not mm_master_2_event:
  109. events = event_listener.get_events(
  110. [mm_master_2_event_pattern], after_time=start_time
  111. )
  112. for event in events:
  113. mm_master_2_event = event
  114. break
  115. if mm_master_1_event and mm_master_2_event:
  116. # We got all events back
  117. break
  118. time.sleep(0.5)
  119. log.debug("Status events received: %s, %s", mm_master_1_event, mm_master_2_event)
  120. # Let's trigger an inotify event
  121. start_time = time.time()
  122. file_path = inotify_test_path / "tmpfile"
  123. file_path.write_text("")
  124. log.warning(
  125. "Test file to trigger the inotify event has been written to: %s", file_path
  126. )
  127. stop_time = start_time + salt_mm_minion_1.config["loop_interval"] * 3 + 60
  128. # Now in successful case this test will get results at most in 3 loop intervals.
  129. # Waiting for 3 loops intervals + some seconds to the hardware stupidity.
  130. mm_master_1_event = mm_master_2_event = None
  131. expected_tag = "salt/beacon/{}/inotify/{}".format(
  132. salt_mm_minion_1.id, inotify_test_path
  133. )
  134. mm_master_1_event_pattern = (salt_mm_master_1.id, expected_tag)
  135. mm_master_2_event_pattern = (salt_mm_master_2.id, expected_tag)
  136. while True:
  137. if time.time() > stop_time:
  138. pytest.fail(
  139. "Failed to receive at least one of the inotify events. "
  140. "Master 1 Event: {}; Master 2 Event: {}".format(
  141. mm_master_1_event, mm_master_2_event
  142. )
  143. )
  144. if not mm_master_1_event:
  145. events = event_listener.get_events(
  146. [mm_master_1_event_pattern], after_time=start_time
  147. )
  148. for event in events:
  149. mm_master_1_event = event
  150. break
  151. if not mm_master_2_event:
  152. events = event_listener.get_events(
  153. [mm_master_2_event_pattern], after_time=start_time
  154. )
  155. for event in events:
  156. mm_master_2_event = event
  157. break
  158. if mm_master_1_event and mm_master_2_event:
  159. # We got all events back
  160. break
  161. time.sleep(0.5)
  162. log.debug("Inotify events received: %s, %s", mm_master_1_event, mm_master_2_event)
  163. # We can't determine the timestamp so remove it from results
  164. for event in (mm_master_1_event, mm_master_2_event):
  165. del event.data["_stamp"]
  166. expected_data = {
  167. "path": str(file_path),
  168. "change": "IN_CREATE",
  169. "id": salt_mm_minion_1.id,
  170. }
  171. # It's better to compare both at once to see both responses in the error log.
  172. assert ((expected_tag, expected_data), (expected_tag, expected_data)) == (
  173. (mm_master_1_event.tag, mm_master_1_event.data),
  174. (mm_master_2_event.tag, mm_master_2_event.data),
  175. )