test_inotify.py 5.5 KB

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