test_watchdog.py 6.5 KB

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