test_minion.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. """
  2. :codeauthor: Mike Place <mp@saltstack.com>
  3. """
  4. import copy
  5. import logging
  6. import os
  7. import salt.ext.tornado
  8. import salt.ext.tornado.testing
  9. import salt.minion
  10. import salt.syspaths
  11. import salt.utils.crypt
  12. import salt.utils.event as event
  13. import salt.utils.platform
  14. import salt.utils.process
  15. from salt._compat import ipaddress
  16. from salt.exceptions import SaltClientError, SaltMasterUnresolvableError, SaltSystemExit
  17. from salt.ext.six.moves import range
  18. from tests.support.helpers import skip_if_not_root, slowTest
  19. from tests.support.mixins import AdaptedConfigurationTestCaseMixin
  20. from tests.support.mock import MagicMock, patch
  21. from tests.support.unit import TestCase, skipIf
  22. log = logging.getLogger(__name__)
  23. class MinionTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
  24. def setUp(self):
  25. self.opts = {}
  26. self.addCleanup(delattr, self, "opts")
  27. def test_invalid_master_address(self):
  28. with patch.dict(
  29. self.opts,
  30. {
  31. "ipv6": False,
  32. "master": float("127.0"),
  33. "master_port": "4555",
  34. "retry_dns": False,
  35. },
  36. ):
  37. self.assertRaises(SaltSystemExit, salt.minion.resolve_dns, self.opts)
  38. def test_source_int_name_local(self):
  39. """
  40. test when file_client local and
  41. source_interface_name is set
  42. """
  43. interfaces = {
  44. "bond0.1234": {
  45. "hwaddr": "01:01:01:d0:d0:d0",
  46. "up": True,
  47. "inet": [
  48. {
  49. "broadcast": "111.1.111.255",
  50. "netmask": "111.1.0.0",
  51. "label": "bond0",
  52. "address": "111.1.0.1",
  53. }
  54. ],
  55. }
  56. }
  57. with patch.dict(
  58. self.opts,
  59. {
  60. "ipv6": False,
  61. "master": "127.0.0.1",
  62. "master_port": "4555",
  63. "file_client": "local",
  64. "source_interface_name": "bond0.1234",
  65. "source_ret_port": 49017,
  66. "source_publish_port": 49018,
  67. },
  68. ), patch("salt.utils.network.interfaces", MagicMock(return_value=interfaces)):
  69. assert salt.minion.resolve_dns(self.opts) == {
  70. "master_ip": "127.0.0.1",
  71. "source_ip": "111.1.0.1",
  72. "source_ret_port": 49017,
  73. "source_publish_port": 49018,
  74. "master_uri": "tcp://127.0.0.1:4555",
  75. }
  76. @slowTest
  77. def test_source_int_name_remote(self):
  78. """
  79. test when file_client remote and
  80. source_interface_name is set and
  81. interface is down
  82. """
  83. interfaces = {
  84. "bond0.1234": {
  85. "hwaddr": "01:01:01:d0:d0:d0",
  86. "up": False,
  87. "inet": [
  88. {
  89. "broadcast": "111.1.111.255",
  90. "netmask": "111.1.0.0",
  91. "label": "bond0",
  92. "address": "111.1.0.1",
  93. }
  94. ],
  95. }
  96. }
  97. with patch.dict(
  98. self.opts,
  99. {
  100. "ipv6": False,
  101. "master": "127.0.0.1",
  102. "master_port": "4555",
  103. "file_client": "remote",
  104. "source_interface_name": "bond0.1234",
  105. "source_ret_port": 49017,
  106. "source_publish_port": 49018,
  107. },
  108. ), patch("salt.utils.network.interfaces", MagicMock(return_value=interfaces)):
  109. assert salt.minion.resolve_dns(self.opts) == {
  110. "master_ip": "127.0.0.1",
  111. "source_ret_port": 49017,
  112. "source_publish_port": 49018,
  113. "master_uri": "tcp://127.0.0.1:4555",
  114. }
  115. @slowTest
  116. def test_source_address(self):
  117. """
  118. test when source_address is set
  119. """
  120. interfaces = {
  121. "bond0.1234": {
  122. "hwaddr": "01:01:01:d0:d0:d0",
  123. "up": False,
  124. "inet": [
  125. {
  126. "broadcast": "111.1.111.255",
  127. "netmask": "111.1.0.0",
  128. "label": "bond0",
  129. "address": "111.1.0.1",
  130. }
  131. ],
  132. }
  133. }
  134. with patch.dict(
  135. self.opts,
  136. {
  137. "ipv6": False,
  138. "master": "127.0.0.1",
  139. "master_port": "4555",
  140. "file_client": "local",
  141. "source_interface_name": "",
  142. "source_address": "111.1.0.1",
  143. "source_ret_port": 49017,
  144. "source_publish_port": 49018,
  145. },
  146. ), patch("salt.utils.network.interfaces", MagicMock(return_value=interfaces)):
  147. assert salt.minion.resolve_dns(self.opts) == {
  148. "source_publish_port": 49018,
  149. "source_ret_port": 49017,
  150. "master_uri": "tcp://127.0.0.1:4555",
  151. "source_ip": "111.1.0.1",
  152. "master_ip": "127.0.0.1",
  153. }
  154. # Tests for _handle_decoded_payload in the salt.minion.Minion() class: 3
  155. @slowTest
  156. def test_handle_decoded_payload_jid_match_in_jid_queue(self):
  157. """
  158. Tests that the _handle_decoded_payload function returns when a jid is given that is already present
  159. in the jid_queue.
  160. Note: This test doesn't contain all of the patch decorators above the function like the other tests
  161. for _handle_decoded_payload below. This is essential to this test as the call to the function must
  162. return None BEFORE any of the processes are spun up because we should be avoiding firing duplicate
  163. jobs.
  164. """
  165. mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
  166. mock_data = {"fun": "foo.bar", "jid": 123}
  167. mock_jid_queue = [123]
  168. minion = salt.minion.Minion(
  169. mock_opts,
  170. jid_queue=copy.copy(mock_jid_queue),
  171. io_loop=salt.ext.tornado.ioloop.IOLoop(),
  172. )
  173. try:
  174. ret = minion._handle_decoded_payload(mock_data).result()
  175. self.assertEqual(minion.jid_queue, mock_jid_queue)
  176. self.assertIsNone(ret)
  177. finally:
  178. minion.destroy()
  179. @slowTest
  180. def test_handle_decoded_payload_jid_queue_addition(self):
  181. """
  182. Tests that the _handle_decoded_payload function adds a jid to the minion's jid_queue when the new
  183. jid isn't already present in the jid_queue.
  184. """
  185. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  186. "salt.utils.process.SignalHandlingProcess.start",
  187. MagicMock(return_value=True),
  188. ), patch(
  189. "salt.utils.process.SignalHandlingProcess.join",
  190. MagicMock(return_value=True),
  191. ):
  192. mock_jid = 11111
  193. mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
  194. mock_data = {"fun": "foo.bar", "jid": mock_jid}
  195. mock_jid_queue = [123, 456]
  196. minion = salt.minion.Minion(
  197. mock_opts,
  198. jid_queue=copy.copy(mock_jid_queue),
  199. io_loop=salt.ext.tornado.ioloop.IOLoop(),
  200. )
  201. try:
  202. # Assert that the minion's jid_queue attribute matches the mock_jid_queue as a baseline
  203. # This can help debug any test failures if the _handle_decoded_payload call fails.
  204. self.assertEqual(minion.jid_queue, mock_jid_queue)
  205. # Call the _handle_decoded_payload function and update the mock_jid_queue to include the new
  206. # mock_jid. The mock_jid should have been added to the jid_queue since the mock_jid wasn't
  207. # previously included. The minion's jid_queue attribute and the mock_jid_queue should be equal.
  208. minion._handle_decoded_payload(mock_data).result()
  209. mock_jid_queue.append(mock_jid)
  210. self.assertEqual(minion.jid_queue, mock_jid_queue)
  211. finally:
  212. minion.destroy()
  213. @slowTest
  214. def test_handle_decoded_payload_jid_queue_reduced_minion_jid_queue_hwm(self):
  215. """
  216. Tests that the _handle_decoded_payload function removes a jid from the minion's jid_queue when the
  217. minion's jid_queue high water mark (minion_jid_queue_hwm) is hit.
  218. """
  219. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  220. "salt.utils.process.SignalHandlingProcess.start",
  221. MagicMock(return_value=True),
  222. ), patch(
  223. "salt.utils.process.SignalHandlingProcess.join",
  224. MagicMock(return_value=True),
  225. ):
  226. mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
  227. mock_opts["minion_jid_queue_hwm"] = 2
  228. mock_data = {"fun": "foo.bar", "jid": 789}
  229. mock_jid_queue = [123, 456]
  230. minion = salt.minion.Minion(
  231. mock_opts,
  232. jid_queue=copy.copy(mock_jid_queue),
  233. io_loop=salt.ext.tornado.ioloop.IOLoop(),
  234. )
  235. try:
  236. # Assert that the minion's jid_queue attribute matches the mock_jid_queue as a baseline
  237. # This can help debug any test failures if the _handle_decoded_payload call fails.
  238. self.assertEqual(minion.jid_queue, mock_jid_queue)
  239. # Call the _handle_decoded_payload function and check that the queue is smaller by one item
  240. # and contains the new jid
  241. minion._handle_decoded_payload(mock_data).result()
  242. self.assertEqual(len(minion.jid_queue), 2)
  243. self.assertEqual(minion.jid_queue, [456, 789])
  244. finally:
  245. minion.destroy()
  246. @slowTest
  247. def test_process_count_max(self):
  248. """
  249. Tests that the _handle_decoded_payload function does not spawn more than the configured amount of processes,
  250. as per process_count_max.
  251. """
  252. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  253. "salt.utils.process.SignalHandlingProcess.start",
  254. MagicMock(return_value=True),
  255. ), patch(
  256. "salt.utils.process.SignalHandlingProcess.join",
  257. MagicMock(return_value=True),
  258. ), patch(
  259. "salt.utils.minion.running", MagicMock(return_value=[])
  260. ), patch(
  261. "salt.ext.tornado.gen.sleep",
  262. MagicMock(return_value=salt.ext.tornado.concurrent.Future()),
  263. ):
  264. process_count_max = 10
  265. mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
  266. mock_opts["__role"] = "minion"
  267. mock_opts["minion_jid_queue_hwm"] = 100
  268. mock_opts["process_count_max"] = process_count_max
  269. io_loop = salt.ext.tornado.ioloop.IOLoop()
  270. minion = salt.minion.Minion(mock_opts, jid_queue=[], io_loop=io_loop)
  271. try:
  272. # mock gen.sleep to throw a special Exception when called, so that we detect it
  273. class SleepCalledException(Exception):
  274. """Thrown when sleep is called"""
  275. salt.ext.tornado.gen.sleep.return_value.set_exception(
  276. SleepCalledException()
  277. )
  278. # up until process_count_max: gen.sleep does not get called, processes are started normally
  279. for i in range(process_count_max):
  280. mock_data = {"fun": "foo.bar", "jid": i}
  281. io_loop.run_sync(
  282. lambda data=mock_data: minion._handle_decoded_payload(data)
  283. )
  284. self.assertEqual(
  285. salt.utils.process.SignalHandlingProcess.start.call_count, i + 1
  286. )
  287. self.assertEqual(len(minion.jid_queue), i + 1)
  288. salt.utils.minion.running.return_value += [i]
  289. # above process_count_max: gen.sleep does get called, JIDs are created but no new processes are started
  290. mock_data = {"fun": "foo.bar", "jid": process_count_max + 1}
  291. self.assertRaises(
  292. SleepCalledException,
  293. lambda: io_loop.run_sync(
  294. lambda: minion._handle_decoded_payload(mock_data)
  295. ),
  296. )
  297. self.assertEqual(
  298. salt.utils.process.SignalHandlingProcess.start.call_count,
  299. process_count_max,
  300. )
  301. self.assertEqual(len(minion.jid_queue), process_count_max + 1)
  302. finally:
  303. minion.destroy()
  304. @slowTest
  305. def test_beacons_before_connect(self):
  306. """
  307. Tests that the 'beacons_before_connect' option causes the beacons to be initialized before connect.
  308. """
  309. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  310. "salt.minion.Minion.sync_connect_master",
  311. MagicMock(side_effect=RuntimeError("stop execution")),
  312. ), patch(
  313. "salt.utils.process.SignalHandlingProcess.start",
  314. MagicMock(return_value=True),
  315. ), patch(
  316. "salt.utils.process.SignalHandlingProcess.join",
  317. MagicMock(return_value=True),
  318. ):
  319. mock_opts = self.get_config("minion", from_scratch=True)
  320. mock_opts["beacons_before_connect"] = True
  321. io_loop = salt.ext.tornado.ioloop.IOLoop()
  322. io_loop.make_current()
  323. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  324. try:
  325. try:
  326. minion.tune_in(start=True)
  327. except RuntimeError:
  328. pass
  329. # Make sure beacons are initialized but the sheduler is not
  330. self.assertTrue("beacons" in minion.periodic_callbacks)
  331. self.assertTrue("schedule" not in minion.periodic_callbacks)
  332. finally:
  333. minion.destroy()
  334. @slowTest
  335. def test_scheduler_before_connect(self):
  336. """
  337. Tests that the 'scheduler_before_connect' option causes the scheduler to be initialized before connect.
  338. """
  339. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  340. "salt.minion.Minion.sync_connect_master",
  341. MagicMock(side_effect=RuntimeError("stop execution")),
  342. ), patch(
  343. "salt.utils.process.SignalHandlingProcess.start",
  344. MagicMock(return_value=True),
  345. ), patch(
  346. "salt.utils.process.SignalHandlingProcess.join",
  347. MagicMock(return_value=True),
  348. ):
  349. mock_opts = self.get_config("minion", from_scratch=True)
  350. mock_opts["scheduler_before_connect"] = True
  351. io_loop = salt.ext.tornado.ioloop.IOLoop()
  352. io_loop.make_current()
  353. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  354. try:
  355. try:
  356. minion.tune_in(start=True)
  357. except RuntimeError:
  358. pass
  359. # Make sure the scheduler is initialized but the beacons are not
  360. self.assertTrue("schedule" in minion.periodic_callbacks)
  361. self.assertTrue("beacons" not in minion.periodic_callbacks)
  362. finally:
  363. minion.destroy()
  364. @slowTest
  365. def test_when_ping_interval_is_set_the_callback_should_be_added_to_periodic_callbacks(
  366. self,
  367. ):
  368. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  369. "salt.minion.Minion.sync_connect_master",
  370. MagicMock(side_effect=RuntimeError("stop execution")),
  371. ), patch(
  372. "salt.utils.process.SignalHandlingProcess.start",
  373. MagicMock(return_value=True),
  374. ), patch(
  375. "salt.utils.process.SignalHandlingProcess.join",
  376. MagicMock(return_value=True),
  377. ):
  378. mock_opts = self.get_config("minion", from_scratch=True)
  379. mock_opts["ping_interval"] = 10
  380. io_loop = salt.ext.tornado.ioloop.IOLoop()
  381. io_loop.make_current()
  382. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  383. try:
  384. try:
  385. minion.connected = MagicMock(side_effect=(False, True))
  386. minion._fire_master_minion_start = MagicMock()
  387. minion.tune_in(start=False)
  388. except RuntimeError:
  389. pass
  390. # Make sure the scheduler is initialized but the beacons are not
  391. self.assertTrue("ping" in minion.periodic_callbacks)
  392. finally:
  393. minion.destroy()
  394. @slowTest
  395. def test_when_passed_start_event_grains(self):
  396. mock_opts = self.get_config("minion", from_scratch=True)
  397. mock_opts["start_event_grains"] = ["os"]
  398. io_loop = salt.ext.tornado.ioloop.IOLoop()
  399. io_loop.make_current()
  400. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  401. try:
  402. minion.tok = MagicMock()
  403. minion._send_req_sync = MagicMock()
  404. minion._fire_master(
  405. "Minion has started", "minion_start", include_startup_grains=True
  406. )
  407. load = minion._send_req_sync.call_args[0][0]
  408. self.assertTrue("grains" in load)
  409. self.assertTrue("os" in load["grains"])
  410. finally:
  411. minion.destroy()
  412. @slowTest
  413. def test_when_not_passed_start_event_grains(self):
  414. mock_opts = self.get_config("minion", from_scratch=True)
  415. io_loop = salt.ext.tornado.ioloop.IOLoop()
  416. io_loop.make_current()
  417. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  418. try:
  419. minion.tok = MagicMock()
  420. minion._send_req_sync = MagicMock()
  421. minion._fire_master("Minion has started", "minion_start")
  422. load = minion._send_req_sync.call_args[0][0]
  423. self.assertTrue("grains" not in load)
  424. finally:
  425. minion.destroy()
  426. @slowTest
  427. def test_when_other_events_fired_and_start_event_grains_are_set(self):
  428. mock_opts = self.get_config("minion", from_scratch=True)
  429. mock_opts["start_event_grains"] = ["os"]
  430. io_loop = salt.ext.tornado.ioloop.IOLoop()
  431. io_loop.make_current()
  432. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  433. try:
  434. minion.tok = MagicMock()
  435. minion._send_req_sync = MagicMock()
  436. minion._fire_master("Custm_event_fired", "custom_event")
  437. load = minion._send_req_sync.call_args[0][0]
  438. self.assertTrue("grains" not in load)
  439. finally:
  440. minion.destroy()
  441. @slowTest
  442. def test_minion_retry_dns_count(self):
  443. """
  444. Tests that the resolve_dns will retry dns look ups for a maximum of
  445. 3 times before raising a SaltMasterUnresolvableError exception.
  446. """
  447. with patch.dict(
  448. self.opts,
  449. {
  450. "ipv6": False,
  451. "master": "dummy",
  452. "master_port": "4555",
  453. "retry_dns": 1,
  454. "retry_dns_count": 3,
  455. },
  456. ):
  457. self.assertRaises(
  458. SaltMasterUnresolvableError, salt.minion.resolve_dns, self.opts
  459. )
  460. @slowTest
  461. def test_gen_modules_executors(self):
  462. """
  463. Ensure gen_modules is called with the correct arguments #54429
  464. """
  465. mock_opts = self.get_config("minion", from_scratch=True)
  466. io_loop = salt.ext.tornado.ioloop.IOLoop()
  467. io_loop.make_current()
  468. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  469. class MockPillarCompiler:
  470. def compile_pillar(self):
  471. return {}
  472. try:
  473. with patch("salt.pillar.get_pillar", return_value=MockPillarCompiler()):
  474. with patch("salt.loader.executors") as execmock:
  475. minion.gen_modules()
  476. assert execmock.called_with(minion.opts, minion.functions)
  477. finally:
  478. minion.destroy()
  479. @patch("salt.utils.process.default_signals")
  480. @slowTest
  481. def test_reinit_crypto_on_fork(self, def_mock):
  482. """
  483. Ensure salt.utils.crypt.reinit_crypto() is executed when forking for new job
  484. """
  485. mock_opts = self.get_config("minion", from_scratch=True)
  486. mock_opts["multiprocessing"] = True
  487. io_loop = salt.ext.tornado.ioloop.IOLoop()
  488. io_loop.make_current()
  489. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  490. job_data = {"jid": "test-jid", "fun": "test.ping"}
  491. def mock_start(self):
  492. # pylint: disable=comparison-with-callable
  493. assert (
  494. len(
  495. [
  496. x
  497. for x in self._after_fork_methods
  498. if x[0] == salt.utils.crypt.reinit_crypto
  499. ]
  500. )
  501. == 1
  502. )
  503. # pylint: enable=comparison-with-callable
  504. with patch.object(
  505. salt.utils.process.SignalHandlingProcess, "start", mock_start
  506. ):
  507. io_loop.run_sync(lambda: minion._handle_decoded_payload(job_data))
  508. def test_minion_manage_schedule(self):
  509. """
  510. Tests that the manage_schedule will call the add function, adding
  511. schedule data into opts.
  512. """
  513. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  514. "salt.minion.Minion.sync_connect_master",
  515. MagicMock(side_effect=RuntimeError("stop execution")),
  516. ), patch(
  517. "salt.utils.process.SignalHandlingMultiprocessingProcess.start",
  518. MagicMock(return_value=True),
  519. ), patch(
  520. "salt.utils.process.SignalHandlingMultiprocessingProcess.join",
  521. MagicMock(return_value=True),
  522. ):
  523. mock_opts = self.get_config("minion", from_scratch=True)
  524. io_loop = salt.ext.tornado.ioloop.IOLoop()
  525. io_loop.make_current()
  526. with patch(
  527. "salt.utils.schedule.clean_proc_dir", MagicMock(return_value=None)
  528. ):
  529. mock_functions = {"test.ping": None}
  530. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  531. minion.schedule = salt.utils.schedule.Schedule(
  532. mock_opts, mock_functions, returners={}
  533. )
  534. schedule_data = {
  535. "test_job": {
  536. "function": "test.ping",
  537. "return_job": False,
  538. "jid_include": True,
  539. "maxrunning": 2,
  540. "seconds": 10,
  541. }
  542. }
  543. data = {"name": "test-item", "schedule": schedule_data, "func": "add"}
  544. tag = "manage_schedule"
  545. minion.manage_schedule(tag, data)
  546. self.assertIn("test_job", minion.opts["schedule"])
  547. def test_minion_manage_beacons(self):
  548. """
  549. Tests that the manage_beacons will call the add function, adding
  550. beacon data into opts.
  551. """
  552. with patch("salt.minion.Minion.ctx", MagicMock(return_value={})), patch(
  553. "salt.minion.Minion.sync_connect_master",
  554. MagicMock(side_effect=RuntimeError("stop execution")),
  555. ), patch(
  556. "salt.utils.process.SignalHandlingMultiprocessingProcess.start",
  557. MagicMock(return_value=True),
  558. ), patch(
  559. "salt.utils.process.SignalHandlingMultiprocessingProcess.join",
  560. MagicMock(return_value=True),
  561. ):
  562. mock_opts = self.get_config("minion", from_scratch=True)
  563. io_loop = salt.ext.tornado.ioloop.IOLoop()
  564. io_loop.make_current()
  565. mock_functions = {"test.ping": None}
  566. minion = salt.minion.Minion(mock_opts, io_loop=io_loop)
  567. minion.beacons = salt.beacons.Beacon(mock_opts, mock_functions)
  568. bdata = [{"salt-master": "stopped"}, {"apache2": "stopped"}]
  569. data = {"name": "ps", "beacon_data": bdata, "func": "add"}
  570. tag = "manage_beacons"
  571. minion.manage_beacons(tag, data)
  572. self.assertIn("ps", minion.opts["beacons"])
  573. self.assertEqual(minion.opts["beacons"]["ps"], bdata)
  574. def test_prep_ip_port(self):
  575. _ip = ipaddress.ip_address
  576. opts = {"master": "10.10.0.3", "master_uri_format": "ip_only"}
  577. ret = salt.minion.prep_ip_port(opts)
  578. self.assertEqual(ret, {"master": _ip("10.10.0.3")})
  579. opts = {
  580. "master": "10.10.0.3",
  581. "master_port": 1234,
  582. "master_uri_format": "default",
  583. }
  584. ret = salt.minion.prep_ip_port(opts)
  585. self.assertEqual(ret, {"master": "10.10.0.3"})
  586. opts = {"master": "10.10.0.3:1234", "master_uri_format": "default"}
  587. ret = salt.minion.prep_ip_port(opts)
  588. self.assertEqual(ret, {"master": "10.10.0.3", "master_port": 1234})
  589. opts = {"master": "host name", "master_uri_format": "default"}
  590. self.assertRaises(SaltClientError, salt.minion.prep_ip_port, opts)
  591. opts = {"master": "10.10.0.3:abcd", "master_uri_format": "default"}
  592. self.assertRaises(SaltClientError, salt.minion.prep_ip_port, opts)
  593. opts = {"master": "10.10.0.3::1234", "master_uri_format": "default"}
  594. self.assertRaises(SaltClientError, salt.minion.prep_ip_port, opts)
  595. class MinionAsyncTestCase(
  596. TestCase, AdaptedConfigurationTestCaseMixin, salt.ext.tornado.testing.AsyncTestCase
  597. ):
  598. def setUp(self):
  599. super().setUp()
  600. self.opts = {}
  601. self.addCleanup(delattr, self, "opts")
  602. @skip_if_not_root
  603. def test_sock_path_len(self):
  604. """
  605. This tests whether or not a larger hash causes the sock path to exceed
  606. the system's max sock path length. See the below link for more
  607. information.
  608. https://github.com/saltstack/salt/issues/12172#issuecomment-43903643
  609. """
  610. opts = {
  611. "id": "salt-testing",
  612. "hash_type": "sha512",
  613. "sock_dir": os.path.join(salt.syspaths.SOCK_DIR, "minion"),
  614. "extension_modules": "",
  615. }
  616. with patch.dict(self.opts, opts):
  617. try:
  618. event_publisher = event.AsyncEventPublisher(self.opts)
  619. result = True
  620. except ValueError:
  621. # There are rare cases where we operate a closed socket, especially in containers.
  622. # In this case, don't fail the test because we'll catch it down the road.
  623. result = True
  624. except SaltSystemExit:
  625. result = False
  626. self.assertTrue(result)
  627. @salt.ext.tornado.testing.gen_test
  628. @skipIf(
  629. salt.utils.platform.is_windows(), "Skipping, no Salt master running on Windows."
  630. )
  631. def test_master_type_failover(self):
  632. """
  633. Tests master_type "failover" to not fall back to 127.0.0.1 address when master does not resolve in DNS
  634. """
  635. mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
  636. mock_opts.update(
  637. {
  638. "master_type": "failover",
  639. "master": ["master1", "master2"],
  640. "__role": "",
  641. "retry_dns": 0,
  642. }
  643. )
  644. class MockPubChannel:
  645. def connect(self):
  646. raise SaltClientError("MockedChannel")
  647. def close(self):
  648. return
  649. def mock_resolve_dns(opts, fallback=False):
  650. self.assertFalse(fallback)
  651. if opts["master"] == "master1":
  652. raise SaltClientError("Cannot resolve {}".format(opts["master"]))
  653. return {
  654. "master_ip": "192.168.2.1",
  655. "master_uri": "tcp://192.168.2.1:4505",
  656. }
  657. def mock_transport_factory(opts, **kwargs):
  658. self.assertEqual(opts["master"], "master2")
  659. return MockPubChannel()
  660. with patch("salt.minion.resolve_dns", mock_resolve_dns), patch(
  661. "salt.transport.client.AsyncPubChannel.factory", mock_transport_factory
  662. ), patch("salt.loader.grains", MagicMock(return_value=[])):
  663. with self.assertRaises(SaltClientError, msg="MockedChannel"):
  664. minion = salt.minion.Minion(mock_opts)
  665. yield minion.connect_master()
  666. @salt.ext.tornado.testing.gen_test
  667. def test_master_type_failover_no_masters(self):
  668. """
  669. Tests master_type "failover" to not fall back to 127.0.0.1 address when no master can be resolved
  670. """
  671. mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
  672. mock_opts.update(
  673. {
  674. "master_type": "failover",
  675. "master": ["master1", "master2"],
  676. "__role": "",
  677. "retry_dns": 0,
  678. }
  679. )
  680. def mock_resolve_dns(opts, fallback=False):
  681. self.assertFalse(fallback)
  682. raise SaltClientError("Cannot resolve {}".format(opts["master"]))
  683. with patch("salt.minion.resolve_dns", mock_resolve_dns), patch(
  684. "salt.loader.grains", MagicMock(return_value=[])
  685. ):
  686. with self.assertRaises(SaltClientError, msg="No master could be resolved"):
  687. minion = salt.minion.Minion(mock_opts)
  688. yield minion.connect_master()