test_minions.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. # -*- coding: utf-8 -*-
  2. # Import python libs
  3. from __future__ import absolute_import, unicode_literals
  4. import sys
  5. # Import Salt Libs
  6. import salt.utils.minions
  7. from tests.support.mock import MagicMock, patch
  8. # Import Salt Testing Libs
  9. from tests.support.unit import TestCase, skipIf
  10. NODEGROUPS = {
  11. "group1": "L@host1,host2,host3",
  12. "group2": ["G@foo:bar", "or", "web1*"],
  13. "group3": ["N@group1", "or", "N@group2"],
  14. "group4": ["host4", "host5", "host6"],
  15. "group5": "N@group4",
  16. "group6": "N@group3",
  17. "group7": ["host1"],
  18. }
  19. EXPECTED = {
  20. "group1": ["L@host1,host2,host3"],
  21. "group2": ["G@foo:bar", "or", "web1*"],
  22. "group3": [
  23. "(",
  24. "(",
  25. "L@host1,host2,host3",
  26. ")",
  27. "or",
  28. "(",
  29. "G@foo:bar",
  30. "or",
  31. "web1*",
  32. ")",
  33. ")",
  34. ],
  35. "group4": ["L@host4,host5,host6"],
  36. "group5": ["(", "L@host4,host5,host6", ")"],
  37. "group6": [
  38. "(",
  39. "(",
  40. "(",
  41. "L@host1,host2,host3",
  42. ")",
  43. "or",
  44. "(",
  45. "G@foo:bar",
  46. "or",
  47. "web1*",
  48. ")",
  49. ")",
  50. ")",
  51. ],
  52. "group7": ["L@host1"],
  53. }
  54. class MinionsTestCase(TestCase):
  55. """
  56. TestCase for salt.utils.minions module functions
  57. """
  58. def test_nodegroup_comp(self):
  59. """
  60. Test a simple string nodegroup
  61. """
  62. for nodegroup in NODEGROUPS:
  63. expected = EXPECTED[nodegroup]
  64. ret = salt.utils.minions.nodegroup_comp(nodegroup, NODEGROUPS)
  65. self.assertEqual(ret, expected)
  66. class CkMinionsTestCase(TestCase):
  67. """
  68. TestCase for salt.utils.minions.CkMinions class
  69. """
  70. def setUp(self):
  71. self.ckminions = salt.utils.minions.CkMinions({"minion_data_cache": True})
  72. def test_spec_check(self):
  73. # Test spec-only rule
  74. auth_list = ["@runner"]
  75. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  76. self.assertTrue(ret)
  77. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "wheel")
  78. self.assertFalse(ret)
  79. ret = self.ckminions.spec_check(auth_list, "testarg", {}, "runner")
  80. mock_ret = {
  81. "error": {
  82. "name": "SaltInvocationError",
  83. "message": "A command invocation error occurred: Check syntax.",
  84. }
  85. }
  86. self.assertDictEqual(mock_ret, ret)
  87. # Test spec in plural form
  88. auth_list = ["@runners"]
  89. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  90. self.assertTrue(ret)
  91. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "wheel")
  92. self.assertFalse(ret)
  93. # Test spec with module.function restriction
  94. auth_list = [{"@runner": "test.arg"}]
  95. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  96. self.assertTrue(ret)
  97. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "wheel")
  98. self.assertFalse(ret)
  99. ret = self.ckminions.spec_check(auth_list, "tes.arg", {}, "runner")
  100. self.assertFalse(ret)
  101. ret = self.ckminions.spec_check(auth_list, "test.ar", {}, "runner")
  102. self.assertFalse(ret)
  103. # Test function name is a regex
  104. auth_list = [{"@runner": "test.arg.*some"}]
  105. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  106. self.assertFalse(ret)
  107. ret = self.ckminions.spec_check(auth_list, "test.argsome", {}, "runner")
  108. self.assertTrue(ret)
  109. ret = self.ckminions.spec_check(auth_list, "test.arg_aaa_some", {}, "runner")
  110. self.assertTrue(ret)
  111. # Test a list of funcs
  112. auth_list = [{"@runner": ["test.arg", "jobs.active"]}]
  113. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  114. self.assertTrue(ret)
  115. ret = self.ckminions.spec_check(auth_list, "jobs.active", {}, "runner")
  116. self.assertTrue(ret)
  117. ret = self.ckminions.spec_check(auth_list, "test.active", {}, "runner")
  118. self.assertFalse(ret)
  119. ret = self.ckminions.spec_check(auth_list, "jobs.arg", {}, "runner")
  120. self.assertFalse(ret)
  121. # Test args-kwargs rules
  122. auth_list = [
  123. {
  124. "@runner": {
  125. "test.arg": {
  126. "args": ["1", "2"],
  127. "kwargs": {"aaa": "bbb", "ccc": "ddd"},
  128. }
  129. }
  130. }
  131. ]
  132. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  133. self.assertFalse(ret)
  134. args = {"arg": ["1", "2"], "kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  135. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  136. self.assertTrue(ret)
  137. args = {"arg": ["1", "2", "3"], "kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  138. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  139. self.assertTrue(ret)
  140. args = {"arg": ["1", "2"], "kwarg": {"aaa": "bbb", "ccc": "ddd", "zzz": "zzz"}}
  141. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  142. self.assertTrue(ret)
  143. args = {"arg": ["1", "2"], "kwarg": {"aaa": "bbb", "ccc": "ddc"}}
  144. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  145. self.assertFalse(ret)
  146. args = {"arg": ["1", "2"], "kwarg": {"aaa": "bbb"}}
  147. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  148. self.assertFalse(ret)
  149. args = {"arg": ["1", "3"], "kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  150. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  151. self.assertFalse(ret)
  152. args = {"arg": ["1"], "kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  153. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  154. self.assertFalse(ret)
  155. args = {"kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  156. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  157. self.assertFalse(ret)
  158. args = {
  159. "arg": ["1", "2"],
  160. }
  161. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  162. self.assertFalse(ret)
  163. # Test kwargs only
  164. auth_list = [
  165. {"@runner": {"test.arg": {"kwargs": {"aaa": "bbb", "ccc": "ddd"}}}}
  166. ]
  167. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  168. self.assertFalse(ret)
  169. args = {"arg": ["1", "2"], "kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  170. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  171. self.assertTrue(ret)
  172. # Test args only
  173. auth_list = [{"@runner": {"test.arg": {"args": ["1", "2"]}}}]
  174. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  175. self.assertFalse(ret)
  176. args = {"arg": ["1", "2"], "kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  177. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  178. self.assertTrue(ret)
  179. # Test list of args
  180. auth_list = [
  181. {
  182. "@runner": [
  183. {
  184. "test.arg": [
  185. {
  186. "args": ["1", "2"],
  187. "kwargs": {"aaa": "bbb", "ccc": "ddd"},
  188. },
  189. {
  190. "args": ["2", "3"],
  191. "kwargs": {"aaa": "aaa", "ccc": "ccc"},
  192. },
  193. ]
  194. }
  195. ]
  196. }
  197. ]
  198. args = {"arg": ["1", "2"], "kwarg": {"aaa": "bbb", "ccc": "ddd"}}
  199. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  200. self.assertTrue(ret)
  201. args = {"arg": ["2", "3"], "kwarg": {"aaa": "aaa", "ccc": "ccc"}}
  202. ret = self.ckminions.spec_check(auth_list, "test.arg", args, "runner")
  203. self.assertTrue(ret)
  204. # Test @module form
  205. auth_list = ["@jobs"]
  206. ret = self.ckminions.spec_check(auth_list, "jobs.active", {}, "runner")
  207. self.assertTrue(ret)
  208. ret = self.ckminions.spec_check(auth_list, "jobs.active", {}, "wheel")
  209. self.assertTrue(ret)
  210. ret = self.ckminions.spec_check(auth_list, "test.arg", {}, "runner")
  211. self.assertFalse(ret)
  212. ret = self.ckminions.spec_check(auth_list, "job.arg", {}, "runner")
  213. self.assertFalse(ret)
  214. # Test @module: function
  215. auth_list = [{"@jobs": "active"}]
  216. ret = self.ckminions.spec_check(auth_list, "jobs.active", {}, "runner")
  217. self.assertTrue(ret)
  218. ret = self.ckminions.spec_check(auth_list, "jobs.active", {}, "wheel")
  219. self.assertTrue(ret)
  220. ret = self.ckminions.spec_check(auth_list, "jobs.active_jobs", {}, "runner")
  221. self.assertTrue(ret)
  222. ret = self.ckminions.spec_check(auth_list, "jobs.activ", {}, "runner")
  223. self.assertFalse(ret)
  224. # Test @module: [functions]
  225. auth_list = [{"@jobs": ["active", "li"]}]
  226. ret = self.ckminions.spec_check(auth_list, "jobs.active", {}, "runner")
  227. self.assertTrue(ret)
  228. ret = self.ckminions.spec_check(auth_list, "jobs.list_jobs", {}, "runner")
  229. self.assertTrue(ret)
  230. ret = self.ckminions.spec_check(auth_list, "jobs.last_run", {}, "runner")
  231. self.assertFalse(ret)
  232. # Test @module: function with args
  233. auth_list = [
  234. {"@jobs": {"active": {"args": ["1", "2"], "kwargs": {"a": "b", "c": "d"}}}}
  235. ]
  236. args = {"arg": ["1", "2"], "kwarg": {"a": "b", "c": "d"}}
  237. ret = self.ckminions.spec_check(auth_list, "jobs.active", args, "runner")
  238. self.assertTrue(ret)
  239. ret = self.ckminions.spec_check(auth_list, "jobs.active", {}, "runner")
  240. self.assertFalse(ret)
  241. @patch(
  242. "salt.utils.minions.CkMinions._pki_minions",
  243. MagicMock(return_value=["alpha", "beta", "gamma"]),
  244. )
  245. def test_auth_check(self):
  246. # Test function-only rule
  247. auth_list = ["test.ping"]
  248. ret = self.ckminions.auth_check(auth_list, "test.ping", None, "alpha")
  249. self.assertTrue(ret)
  250. ret = self.ckminions.auth_check(auth_list, "test.arg", None, "alpha")
  251. self.assertFalse(ret)
  252. # Test minion and function
  253. auth_list = [{"alpha": "test.ping"}]
  254. ret = self.ckminions.auth_check(auth_list, "test.ping", None, "alpha")
  255. self.assertTrue(ret)
  256. ret = self.ckminions.auth_check(auth_list, "test.arg", None, "alpha")
  257. self.assertFalse(ret)
  258. ret = self.ckminions.auth_check(auth_list, "test.ping", None, "beta")
  259. self.assertFalse(ret)
  260. # Test function list
  261. auth_list = [{"*": ["test.*", "saltutil.cmd"]}]
  262. ret = self.ckminions.auth_check(auth_list, "test.arg", None, "alpha")
  263. self.assertTrue(ret)
  264. ret = self.ckminions.auth_check(auth_list, "test.ping", None, "beta")
  265. self.assertTrue(ret)
  266. ret = self.ckminions.auth_check(auth_list, "saltutil.cmd", None, "gamma")
  267. self.assertTrue(ret)
  268. ret = self.ckminions.auth_check(auth_list, "saltutil.running", None, "gamma")
  269. self.assertFalse(ret)
  270. # Test an args and kwargs rule
  271. auth_list = [
  272. {
  273. "alpha": {
  274. "test.arg": {
  275. "args": ["1", "2"],
  276. "kwargs": {"aaa": "bbb", "ccc": "ddd"},
  277. }
  278. }
  279. }
  280. ]
  281. ret = self.ckminions.auth_check(auth_list, "test.arg", None, "runner")
  282. self.assertFalse(ret)
  283. ret = self.ckminions.auth_check(auth_list, "test.arg", [], "runner")
  284. self.assertFalse(ret)
  285. args = ["1", "2", {"aaa": "bbb", "ccc": "ddd", "__kwarg__": True}]
  286. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  287. self.assertTrue(ret)
  288. args = [
  289. "1",
  290. "2",
  291. "3",
  292. {"aaa": "bbb", "ccc": "ddd", "eee": "fff", "__kwarg__": True},
  293. ]
  294. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  295. self.assertTrue(ret)
  296. args = ["1", {"aaa": "bbb", "ccc": "ddd", "__kwarg__": True}]
  297. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  298. self.assertFalse(ret)
  299. args = ["1", "2", {"aaa": "bbb", "__kwarg__": True}]
  300. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  301. self.assertFalse(ret)
  302. args = ["1", "3", {"aaa": "bbb", "ccc": "ddd", "__kwarg__": True}]
  303. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  304. self.assertFalse(ret)
  305. args = ["1", "2", {"aaa": "bbb", "ccc": "fff", "__kwarg__": True}]
  306. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  307. self.assertFalse(ret)
  308. # Test kwargs only rule
  309. auth_list = [{"alpha": {"test.arg": {"kwargs": {"aaa": "bbb", "ccc": "ddd"}}}}]
  310. args = ["1", "2", {"aaa": "bbb", "ccc": "ddd", "__kwarg__": True}]
  311. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  312. self.assertTrue(ret)
  313. args = [{"aaa": "bbb", "ccc": "ddd", "eee": "fff", "__kwarg__": True}]
  314. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  315. self.assertTrue(ret)
  316. # Test args only rule
  317. auth_list = [{"alpha": {"test.arg": {"args": ["1", "2"]}}}]
  318. args = ["1", "2", {"aaa": "bbb", "ccc": "ddd", "__kwarg__": True}]
  319. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  320. self.assertTrue(ret)
  321. args = ["1", "2"]
  322. ret = self.ckminions.auth_check(auth_list, "test.arg", args, "runner")
  323. self.assertTrue(ret)
  324. @skipIf(
  325. sys.version_info < (2, 7), "Python 2.7 needed for dictionary equality assertions"
  326. )
  327. class TargetParseTestCase(TestCase):
  328. def test_parse_grains_target(self):
  329. """
  330. Ensure proper parsing for grains
  331. """
  332. g_tgt = "G@a:b"
  333. ret = salt.utils.minions.parse_target(g_tgt)
  334. self.assertDictEqual(ret, {"engine": "G", "pattern": "a:b", "delimiter": None})
  335. def test_parse_grains_pcre_target(self):
  336. """
  337. Ensure proper parsing for grains PCRE matching
  338. """
  339. p_tgt = "P@a:b"
  340. ret = salt.utils.minions.parse_target(p_tgt)
  341. self.assertDictEqual(ret, {"engine": "P", "pattern": "a:b", "delimiter": None})
  342. def test_parse_pillar_pcre_target(self):
  343. """
  344. Ensure proper parsing for pillar PCRE matching
  345. """
  346. j_tgt = "J@a:b"
  347. ret = salt.utils.minions.parse_target(j_tgt)
  348. self.assertDictEqual(ret, {"engine": "J", "pattern": "a:b", "delimiter": None})
  349. def test_parse_list_target(self):
  350. """
  351. Ensure proper parsing for list matching
  352. """
  353. l_tgt = "L@a:b"
  354. ret = salt.utils.minions.parse_target(l_tgt)
  355. self.assertDictEqual(ret, {"engine": "L", "pattern": "a:b", "delimiter": None})
  356. def test_parse_nodegroup_target(self):
  357. """
  358. Ensure proper parsing for pillar matching
  359. """
  360. n_tgt = "N@a:b"
  361. ret = salt.utils.minions.parse_target(n_tgt)
  362. self.assertDictEqual(ret, {"engine": "N", "pattern": "a:b", "delimiter": None})
  363. def test_parse_subnet_target(self):
  364. """
  365. Ensure proper parsing for subnet matching
  366. """
  367. s_tgt = "S@a:b"
  368. ret = salt.utils.minions.parse_target(s_tgt)
  369. self.assertDictEqual(ret, {"engine": "S", "pattern": "a:b", "delimiter": None})
  370. def test_parse_minion_pcre_target(self):
  371. """
  372. Ensure proper parsing for minion PCRE matching
  373. """
  374. e_tgt = "E@a:b"
  375. ret = salt.utils.minions.parse_target(e_tgt)
  376. self.assertDictEqual(ret, {"engine": "E", "pattern": "a:b", "delimiter": None})
  377. def test_parse_range_target(self):
  378. """
  379. Ensure proper parsing for range matching
  380. """
  381. r_tgt = "R@a:b"
  382. ret = salt.utils.minions.parse_target(r_tgt)
  383. self.assertDictEqual(ret, {"engine": "R", "pattern": "a:b", "delimiter": None})
  384. def test_parse_multiword_target(self):
  385. """
  386. Ensure proper parsing for multi-word targets
  387. Refs https://github.com/saltstack/salt/issues/37231
  388. """
  389. mw_tgt = "G@a:b c"
  390. ret = salt.utils.minions.parse_target(mw_tgt)
  391. self.assertEqual(ret["pattern"], "a:b c")
  392. class NodegroupCompTest(TestCase):
  393. """
  394. Test nodegroup comparisons found in
  395. salt.utils.minions.nodgroup_comp()
  396. """
  397. def test_simple_nodegroup(self):
  398. """
  399. Smoke test a very simple nodegroup. No recursion.
  400. """
  401. simple_nodegroup = {
  402. "group1": "L@foo.domain.com,bar.domain.com,baz.domain.com or bl*.domain.com"
  403. }
  404. ret = salt.utils.minions.nodegroup_comp("group1", simple_nodegroup)
  405. expected_ret = [
  406. "L@foo.domain.com,bar.domain.com,baz.domain.com",
  407. "or",
  408. "bl*.domain.com",
  409. ]
  410. self.assertListEqual(ret, expected_ret)
  411. def test_simple_expression_nodegroup(self):
  412. """
  413. Smoke test a nodegroup with a simple expression. No recursion.
  414. """
  415. simple_nodegroup = {"group1": "[foo,bar,baz].domain.com"}
  416. ret = salt.utils.minions.nodegroup_comp("group1", simple_nodegroup)
  417. expected_ret = ["E@[foo,bar,baz].domain.com"]
  418. self.assertListEqual(ret, expected_ret)
  419. def test_simple_recurse(self):
  420. """
  421. Test a case where one nodegroup contains a second nodegroup
  422. """
  423. referenced_nodegroups = {
  424. "group1": "L@foo.domain.com,bar.domain.com,baz.domain.com or bl*.domain.com",
  425. "group2": "G@os:Debian and N@group1",
  426. }
  427. ret = salt.utils.minions.nodegroup_comp("group2", referenced_nodegroups)
  428. expected_ret = [
  429. "(",
  430. "G@os:Debian",
  431. "and",
  432. "(",
  433. "L@foo.domain.com,bar.domain.com,baz.domain.com",
  434. "or",
  435. "bl*.domain.com",
  436. ")",
  437. ")",
  438. ]
  439. self.assertListEqual(ret, expected_ret)
  440. def test_circular_nodegroup_reference(self):
  441. """
  442. Test to see what happens if A refers to B
  443. and B in turn refers back to A
  444. """
  445. referenced_nodegroups = {"group1": "N@group2", "group2": "N@group1"}
  446. # If this works, it should also print an error to the console
  447. ret = salt.utils.minions.nodegroup_comp("group1", referenced_nodegroups)
  448. self.assertEqual(ret, [])