test_inotify.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. # Python libs
  2. import logging
  3. import os
  4. import shutil
  5. import tempfile
  6. # Salt libs
  7. import salt.utils.files
  8. from salt.beacons import inotify
  9. from tests.support.mixins import LoaderModuleMockMixin
  10. # Salt testing libs
  11. from tests.support.unit import TestCase, skipIf
  12. # Third-party libs
  13. try:
  14. import pyinotify # pylint: disable=unused-import
  15. HAS_PYINOTIFY = True
  16. except ImportError:
  17. HAS_PYINOTIFY = False
  18. log = logging.getLogger(__name__)
  19. @skipIf(not HAS_PYINOTIFY, "pyinotify is not available")
  20. class INotifyBeaconTestCase(TestCase, LoaderModuleMockMixin):
  21. """
  22. Test case for salt.beacons.inotify
  23. """
  24. def setup_loader_modules(self):
  25. return {inotify: {}}
  26. def setUp(self):
  27. self.tmpdir = tempfile.mkdtemp()
  28. def tearDown(self):
  29. shutil.rmtree(self.tmpdir, ignore_errors=True)
  30. def test_non_list_config(self):
  31. config = {}
  32. ret = inotify.validate(config)
  33. self.assertEqual(
  34. ret, (False, "Configuration for inotify beacon must be a list.")
  35. )
  36. def test_empty_config(self):
  37. config = [{}]
  38. ret = inotify.validate(config)
  39. _expected = (False, "Configuration for inotify beacon must include files.")
  40. self.assertEqual(ret, _expected)
  41. def test_files_none_config(self):
  42. config = [{"files": None}]
  43. ret = inotify.validate(config)
  44. _expected = (
  45. False,
  46. "Configuration for inotify beacon invalid, files must be a dict.",
  47. )
  48. self.assertEqual(ret, _expected)
  49. def test_files_list_config(self):
  50. config = [{"files": [{"/importantfile": {"mask": ["modify"]}}]}]
  51. ret = inotify.validate(config)
  52. _expected = (
  53. False,
  54. "Configuration for inotify beacon invalid, files must be a dict.",
  55. )
  56. self.assertEqual(ret, _expected)
  57. @skipIf(
  58. salt.utils.platform.is_freebsd(),
  59. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  60. )
  61. def test_file_open(self):
  62. path = os.path.realpath(__file__)
  63. config = [{"files": {path: {"mask": ["open"]}}}]
  64. ret = inotify.validate(config)
  65. self.assertEqual(ret, (True, "Valid beacon configuration"))
  66. ret = inotify.beacon(config)
  67. self.assertEqual(ret, [])
  68. with salt.utils.files.fopen(path, "r") as f:
  69. pass
  70. ret = inotify.beacon(config)
  71. self.assertEqual(len(ret), 1)
  72. self.assertEqual(ret[0]["path"], path)
  73. self.assertEqual(ret[0]["change"], "IN_OPEN")
  74. @skipIf(
  75. salt.utils.platform.is_freebsd(),
  76. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  77. )
  78. def test_dir_no_auto_add(self):
  79. config = [{"files": {self.tmpdir: {"mask": ["create"]}}}]
  80. ret = inotify.validate(config)
  81. self.assertEqual(ret, (True, "Valid beacon configuration"))
  82. ret = inotify.beacon(config)
  83. self.assertEqual(ret, [])
  84. fp = os.path.join(self.tmpdir, "tmpfile")
  85. with salt.utils.files.fopen(fp, "w") as f:
  86. pass
  87. ret = inotify.beacon(config)
  88. self.assertEqual(len(ret), 1)
  89. self.assertEqual(ret[0]["path"], fp)
  90. self.assertEqual(ret[0]["change"], "IN_CREATE")
  91. with salt.utils.files.fopen(fp, "r") as f:
  92. pass
  93. ret = inotify.beacon(config)
  94. self.assertEqual(ret, [])
  95. @skipIf(
  96. salt.utils.platform.is_freebsd(),
  97. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  98. )
  99. def test_dir_auto_add(self):
  100. config = [
  101. {"files": {self.tmpdir: {"mask": ["create", "open"], "auto_add": True}}}
  102. ]
  103. ret = inotify.validate(config)
  104. self.assertEqual(ret, (True, "Valid beacon configuration"))
  105. ret = inotify.beacon(config)
  106. self.assertEqual(ret, [])
  107. fp = os.path.join(self.tmpdir, "tmpfile")
  108. with salt.utils.files.fopen(fp, "w") as f:
  109. pass
  110. ret = inotify.beacon(config)
  111. self.assertEqual(len(ret), 2)
  112. self.assertEqual(ret[0]["path"], fp)
  113. self.assertEqual(ret[0]["change"], "IN_CREATE")
  114. self.assertEqual(ret[1]["path"], fp)
  115. self.assertEqual(ret[1]["change"], "IN_OPEN")
  116. with salt.utils.files.fopen(fp, "r") as f:
  117. pass
  118. ret = inotify.beacon(config)
  119. self.assertEqual(len(ret), 1)
  120. self.assertEqual(ret[0]["path"], fp)
  121. self.assertEqual(ret[0]["change"], "IN_OPEN")
  122. @skipIf(
  123. salt.utils.platform.is_freebsd(),
  124. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  125. )
  126. def test_dir_recurse(self):
  127. dp1 = os.path.join(self.tmpdir, "subdir1")
  128. os.mkdir(dp1)
  129. dp2 = os.path.join(dp1, "subdir2")
  130. os.mkdir(dp2)
  131. fp = os.path.join(dp2, "tmpfile")
  132. with salt.utils.files.fopen(fp, "w") as f:
  133. pass
  134. config = [{"files": {self.tmpdir: {"mask": ["open"], "recurse": True}}}]
  135. ret = inotify.validate(config)
  136. self.assertEqual(ret, (True, "Valid beacon configuration"))
  137. ret = inotify.beacon(config)
  138. self.assertEqual(ret, [])
  139. with salt.utils.files.fopen(fp) as f:
  140. pass
  141. ret = inotify.beacon(config)
  142. self.assertEqual(len(ret), 3)
  143. self.assertEqual(ret[0]["path"], dp1)
  144. self.assertEqual(ret[0]["change"], "IN_OPEN|IN_ISDIR")
  145. self.assertEqual(ret[1]["path"], dp2)
  146. self.assertEqual(ret[1]["change"], "IN_OPEN|IN_ISDIR")
  147. self.assertEqual(ret[2]["path"], fp)
  148. self.assertEqual(ret[2]["change"], "IN_OPEN")
  149. @skipIf(
  150. salt.utils.platform.is_freebsd(),
  151. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  152. )
  153. def test_dir_recurse_auto_add(self):
  154. dp1 = os.path.join(self.tmpdir, "subdir1")
  155. os.mkdir(dp1)
  156. config = [
  157. {
  158. "files": {
  159. self.tmpdir: {
  160. "mask": ["create", "delete"],
  161. "recurse": True,
  162. "auto_add": True,
  163. }
  164. }
  165. }
  166. ]
  167. ret = inotify.validate(config)
  168. self.assertEqual(ret, (True, "Valid beacon configuration"))
  169. ret = inotify.beacon(config)
  170. self.assertEqual(ret, [])
  171. dp2 = os.path.join(dp1, "subdir2")
  172. os.mkdir(dp2)
  173. ret = inotify.beacon(config)
  174. self.assertEqual(len(ret), 1)
  175. self.assertEqual(ret[0]["path"], dp2)
  176. self.assertEqual(ret[0]["change"], "IN_CREATE|IN_ISDIR")
  177. fp = os.path.join(dp2, "tmpfile")
  178. with salt.utils.files.fopen(fp, "w") as f:
  179. pass
  180. ret = inotify.beacon(config)
  181. self.assertEqual(len(ret), 1)
  182. self.assertEqual(ret[0]["path"], fp)
  183. self.assertEqual(ret[0]["change"], "IN_CREATE")
  184. os.remove(fp)
  185. ret = inotify.beacon(config)
  186. self.assertEqual(len(ret), 1)
  187. self.assertEqual(ret[0]["path"], fp)
  188. self.assertEqual(ret[0]["change"], "IN_DELETE")
  189. @skipIf(
  190. salt.utils.platform.is_freebsd(),
  191. "Skip on FreeBSD - does not yet have full inotify/watchdog support",
  192. )
  193. def test_multi_files_exclude(self):
  194. dp1 = os.path.join(self.tmpdir, "subdir1")
  195. dp2 = os.path.join(self.tmpdir, "subdir2")
  196. os.mkdir(dp1)
  197. os.mkdir(dp2)
  198. _exclude1 = "{}/subdir1/*tmpfile*$".format(self.tmpdir)
  199. _exclude2 = "{}/subdir2/*filetmp*$".format(self.tmpdir)
  200. config = [
  201. {
  202. "files": {
  203. dp1: {
  204. "mask": ["create", "delete"],
  205. "recurse": True,
  206. "exclude": [{_exclude1: {"regex": True}}],
  207. "auto_add": True,
  208. }
  209. }
  210. },
  211. {
  212. "files": {
  213. dp2: {
  214. "mask": ["create", "delete"],
  215. "recurse": True,
  216. "exclude": [{_exclude2: {"regex": True}}],
  217. "auto_add": True,
  218. }
  219. }
  220. },
  221. ]
  222. ret = inotify.validate(config)
  223. self.assertEqual(ret, (True, "Valid beacon configuration"))
  224. fp = os.path.join(dp1, "tmpfile")
  225. with salt.utils.files.fopen(fp, "w") as f:
  226. pass
  227. ret = inotify.beacon(config)
  228. self.assertEqual(len(ret), 0)
  229. os.remove(fp)
  230. ret = inotify.beacon(config)
  231. self.assertEqual(len(ret), 0)
  232. fp = os.path.join(dp2, "tmpfile")
  233. with salt.utils.files.fopen(fp, "w") as f:
  234. pass
  235. ret = inotify.beacon(config)
  236. self.assertEqual(len(ret), 1)
  237. self.assertEqual(ret[0]["path"], fp)
  238. self.assertEqual(ret[0]["change"], "IN_CREATE")
  239. os.remove(fp)
  240. ret = inotify.beacon(config)
  241. self.assertEqual(len(ret), 1)
  242. self.assertEqual(ret[0]["path"], fp)
  243. self.assertEqual(ret[0]["change"], "IN_DELETE")
  244. # Check __get_notifier and ensure that the right bits are in __context__
  245. # including a beacon_name specific notifier is found.
  246. def test__get_notifier(self):
  247. config = {
  248. "files": {
  249. "/tmp/httpd/vhost.d": {
  250. "mask": ["delete", "modify"],
  251. "recurse": True,
  252. "auto_add": True,
  253. "exclude": [
  254. {"/tmp/httpd/vhost.d/.+?\\.sw[px]*$|4913|~$": {"regex": True}}
  255. ],
  256. },
  257. "/tmp/httpd/conf.d": {
  258. "mask": ["delete", "modify"],
  259. "recurse": True,
  260. "auto_add": True,
  261. "exclude": [
  262. {"/tmp/httpd/vhost.d/.+?\\.sw[px]*$|4913|~$": {"regex": True}}
  263. ],
  264. },
  265. "/tmp/httpd/conf": {
  266. "mask": ["delete", "modify"],
  267. "recurse": True,
  268. "auto_add": True,
  269. "exclude": [
  270. {"/tmp/httpd/vhost.d/.+?\\.sw[px]*$|4913|~$": {"regex": True}}
  271. ],
  272. },
  273. },
  274. "coalesce": True,
  275. "beacon_module": "inotify",
  276. "_beacon_name": "httpd.inotify",
  277. }
  278. ret = inotify._get_notifier(config)
  279. self.assertIn("inotify.queue", inotify.__context__)
  280. self.assertIn("httpd.inotify.notifier", inotify.__context__)