test_watchdog.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import os
  2. import shutil
  3. import tempfile
  4. import time
  5. import salt.utils.files
  6. import salt.utils.platform
  7. from salt.beacons import watchdog
  8. from salt.ext.six.moves import range
  9. from tests.support.helpers import slowTest
  10. from tests.support.mixins import LoaderModuleMockMixin
  11. from tests.support.unit import TestCase, skipIf
  12. def check_events(config):
  13. total_delay = 1
  14. delay_per_loop = 20e-3
  15. for _ in range(int(total_delay / delay_per_loop)):
  16. events = watchdog.beacon(config)
  17. if events:
  18. return events
  19. time.sleep(delay_per_loop)
  20. return []
  21. def create(path, content=None):
  22. with salt.utils.files.fopen(path, "w") as f:
  23. if content:
  24. f.write(content)
  25. os.fsync(f)
  26. @skipIf(not watchdog.HAS_WATCHDOG, "watchdog is not available")
  27. @skipIf(
  28. salt.utils.platform.is_darwin(),
  29. "Tests were being skipped pre macos under nox. Keep it like that for now.",
  30. )
  31. class IWatchdogBeaconTestCase(TestCase, LoaderModuleMockMixin):
  32. """
  33. Test case for salt.beacons.watchdog
  34. """
  35. def setup_loader_modules(self):
  36. return {watchdog: {}}
  37. def setUp(self):
  38. self.tmpdir = tempfile.mkdtemp()
  39. def tearDown(self):
  40. watchdog.close({})
  41. shutil.rmtree(self.tmpdir, ignore_errors=True)
  42. def assertValid(self, config):
  43. ret = watchdog.validate(config)
  44. self.assertEqual(ret, (True, "Valid beacon configuration"))
  45. def test_empty_config(self):
  46. config = [{}]
  47. ret = watchdog.beacon(config)
  48. self.assertEqual(ret, [])
  49. @skipIf(
  50. salt.utils.platform.is_freebsd(),
  51. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  52. )
  53. def test_file_create(self):
  54. path = os.path.join(self.tmpdir, "tmpfile")
  55. config = [{"directories": {self.tmpdir: {"mask": ["create"]}}}]
  56. self.assertValid(config)
  57. self.assertEqual(watchdog.beacon(config), [])
  58. create(path)
  59. ret = check_events(config)
  60. self.assertEqual(len(ret), 1)
  61. self.assertEqual(ret[0]["path"], path)
  62. self.assertEqual(ret[0]["change"], "created")
  63. def test_file_modified(self):
  64. path = os.path.join(self.tmpdir, "tmpfile")
  65. # Create triggers a modify event along with the create event in Py3
  66. # So, let's do this before configuring the beacon
  67. create(path)
  68. config = [{"directories": {self.tmpdir: {"mask": ["modify"]}}}]
  69. self.assertValid(config)
  70. self.assertEqual(watchdog.beacon(config), [])
  71. create(path, "some content")
  72. ret = check_events(config)
  73. modified = False
  74. for event in ret:
  75. # "modified" requires special handling
  76. # A modification sometimes triggers 2 modified events depending on
  77. # the OS and the python version
  78. # When the "modified" event triggers on modify, it will have the
  79. # path to the temp file (path), other modified events will contain
  80. # the path minus "tmpfile" and will not match. That's how we'll
  81. # distinguish the two
  82. if event["change"] == "modified":
  83. if event["path"] == path:
  84. modified = True
  85. # Check results of the for loop to validate modified
  86. self.assertTrue(modified)
  87. def test_file_deleted(self):
  88. path = os.path.join(self.tmpdir, "tmpfile")
  89. create(path)
  90. config = [{"directories": {self.tmpdir: {"mask": ["delete"]}}}]
  91. self.assertValid(config)
  92. self.assertEqual(watchdog.beacon(config), [])
  93. os.remove(path)
  94. ret = check_events(config)
  95. self.assertEqual(len(ret), 1)
  96. self.assertEqual(ret[0]["path"], path)
  97. self.assertEqual(ret[0]["change"], "deleted")
  98. @skipIf(
  99. salt.utils.platform.is_freebsd(),
  100. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  101. )
  102. def test_file_moved(self):
  103. path = os.path.join(self.tmpdir, "tmpfile")
  104. create(path)
  105. config = [{"directories": {self.tmpdir: {"mask": ["move"]}}}]
  106. self.assertValid(config)
  107. self.assertEqual(watchdog.beacon(config), [])
  108. os.rename(path, path + "_moved")
  109. ret = check_events(config)
  110. self.assertEqual(len(ret), 1)
  111. self.assertEqual(ret[0]["path"], path)
  112. self.assertEqual(ret[0]["change"], "moved")
  113. @skipIf(
  114. salt.utils.platform.is_freebsd(),
  115. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  116. )
  117. def test_file_create_in_directory(self):
  118. config = [{"directories": {self.tmpdir: {"mask": ["create"]}}}]
  119. self.assertValid(config)
  120. self.assertEqual(watchdog.beacon(config), [])
  121. path = os.path.join(self.tmpdir, "tmpfile")
  122. create(path)
  123. ret = check_events(config)
  124. self.assertEqual(len(ret), 1)
  125. self.assertEqual(ret[0]["path"], path)
  126. self.assertEqual(ret[0]["change"], "created")
  127. @skipIf(
  128. salt.utils.platform.is_freebsd(),
  129. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  130. )
  131. @slowTest
  132. def test_trigger_all_possible_events(self):
  133. path = os.path.join(self.tmpdir, "tmpfile")
  134. moved = path + "_moved"
  135. config = [{"directories": {self.tmpdir: {}}}]
  136. self.assertValid(config)
  137. self.assertEqual(watchdog.beacon(config), [])
  138. # create
  139. create(path)
  140. # modify
  141. create(path, "modified content")
  142. # move
  143. os.rename(path, moved)
  144. # delete
  145. os.remove(moved)
  146. # Give the events time to load into the queue
  147. time.sleep(1)
  148. ret = check_events(config)
  149. events = {"created": "", "deleted": "", "moved": ""}
  150. modified = False
  151. for event in ret:
  152. if event["change"] == "created":
  153. self.assertEqual(event["path"], path)
  154. events.pop("created", "")
  155. if event["change"] == "moved":
  156. self.assertEqual(event["path"], path)
  157. events.pop("moved", "")
  158. if event["change"] == "deleted":
  159. self.assertEqual(event["path"], moved)
  160. events.pop("deleted", "")
  161. # "modified" requires special handling
  162. # All events [created, moved, deleted] also trigger a "modified"
  163. # event on Linux
  164. # Only the "created" event triggers a modified event on Py3 Windows
  165. # When the "modified" event triggers on modify, it will have the
  166. # path to the temp file (path), other modified events will contain
  167. # the path minus "tmpfile" and will not match. That's how we'll
  168. # distinguish the two
  169. if event["change"] == "modified":
  170. if event["path"] == path:
  171. modified = True
  172. # Check results of the for loop to validate modified
  173. self.assertTrue(modified)
  174. # Make sure all events were checked
  175. self.assertDictEqual(events, {})