test_parsers.py 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Denys Havrysh <denys.gavrysh@gmail.com>
  4. """
  5. from __future__ import absolute_import, print_function, unicode_literals
  6. import os
  7. import shutil
  8. import tempfile
  9. import pytest
  10. import salt.config
  11. import salt.log.setup
  12. import salt.syspaths
  13. import salt.utils.parsers
  14. import salt.utils.platform
  15. from tests.support.mock import MagicMock, patch
  16. from tests.support.runtests import RUNTIME_VARS
  17. from tests.support.unit import TestCase, skipIf
  18. class ErrorMock(object): # pylint: disable=too-few-public-methods
  19. """
  20. Error handling
  21. """
  22. def __init__(self):
  23. """
  24. init
  25. """
  26. self.msg = None
  27. def error(self, msg):
  28. """
  29. Capture error message
  30. """
  31. self.msg = msg
  32. class LogSetupMock(object):
  33. """
  34. Logger setup
  35. """
  36. def __init__(self):
  37. """
  38. init
  39. """
  40. self.log_level = None
  41. self.log_file = None
  42. self.log_level_logfile = None
  43. self.config = {}
  44. self.temp_log_level = None
  45. def setup_console_logger(
  46. self, log_level="error", **kwargs
  47. ): # pylint: disable=unused-argument
  48. """
  49. Set console loglevel
  50. """
  51. self.log_level = log_level
  52. def setup_extended_logging(self, opts):
  53. """
  54. Set opts
  55. """
  56. self.config = opts
  57. def setup_logfile_logger(
  58. self, logfile, loglevel, **kwargs
  59. ): # pylint: disable=unused-argument
  60. """
  61. Set logfile and loglevel
  62. """
  63. self.log_file = logfile
  64. self.log_level_logfile = loglevel
  65. @staticmethod
  66. def get_multiprocessing_logging_queue(): # pylint: disable=invalid-name
  67. """
  68. Mock
  69. """
  70. import multiprocessing
  71. return multiprocessing.Queue()
  72. def setup_multiprocessing_logging_listener(
  73. self, opts, *args
  74. ): # pylint: disable=invalid-name,unused-argument
  75. """
  76. Set opts
  77. """
  78. self.config = opts
  79. def setup_temp_logger(self, log_level="error"):
  80. """
  81. Set temp loglevel
  82. """
  83. self.temp_log_level = log_level
  84. class ObjectView(object): # pylint: disable=too-few-public-methods
  85. """
  86. Dict object view
  87. """
  88. def __init__(self, d):
  89. self.__dict__ = d
  90. @pytest.mark.destructive_test
  91. @pytest.mark.skip_if_not_root
  92. class ParserBase(object):
  93. """
  94. Unit Tests for Log Level Mixin with Salt parsers
  95. """
  96. args = []
  97. skip_console_logging_config = False
  98. log_setup = None
  99. # Set config option names
  100. loglevel_config_setting_name = "log_level"
  101. logfile_config_setting_name = "log_file"
  102. logfile_loglevel_config_setting_name = (
  103. "log_level_logfile" # pylint: disable=invalid-name
  104. )
  105. @classmethod
  106. def setUpClass(cls):
  107. cls.root_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  108. @classmethod
  109. def tearDownClass(cls):
  110. shutil.rmtree(cls.root_dir, ignore_errors=True)
  111. def setup_log(self):
  112. """
  113. Mock logger functions
  114. """
  115. testing_config = self.default_config.copy()
  116. testing_config["root_dir"] = self.root_dir
  117. for name in ("pki_dir", "cachedir"):
  118. testing_config[name] = name
  119. testing_config[self.logfile_config_setting_name] = getattr(
  120. self, self.logfile_config_setting_name, self.log_file
  121. )
  122. self.testing_config = testing_config
  123. self.addCleanup(setattr, self, "testing_config", None)
  124. self.log_setup = LogSetupMock()
  125. patcher = patch.multiple(
  126. salt.log.setup,
  127. setup_console_logger=self.log_setup.setup_console_logger,
  128. setup_extended_logging=self.log_setup.setup_extended_logging,
  129. setup_logfile_logger=self.log_setup.setup_logfile_logger,
  130. get_multiprocessing_logging_queue=self.log_setup.get_multiprocessing_logging_queue,
  131. setup_multiprocessing_logging_listener=self.log_setup.setup_multiprocessing_logging_listener,
  132. setup_temp_logger=self.log_setup.setup_temp_logger,
  133. )
  134. patcher.start()
  135. self.addCleanup(patcher.stop)
  136. self.addCleanup(setattr, self, "log_setup", None)
  137. # log level configuration tests
  138. def test_get_log_level_cli(self):
  139. """
  140. Tests that log level match command-line specified value
  141. """
  142. # Set defaults
  143. default_log_level = self.testing_config[self.loglevel_config_setting_name]
  144. # Set log level in CLI
  145. log_level = "critical"
  146. args = ["--log-level", log_level] + self.args
  147. parser = self.parser()
  148. with patch(self.config_func, MagicMock(return_value=self.testing_config)):
  149. parser.parse_args(args)
  150. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  151. parser.setup_logfile_logger()
  152. console_log_level = getattr(parser.options, self.loglevel_config_setting_name)
  153. # Check console log level setting
  154. self.assertEqual(console_log_level, log_level)
  155. # Check console loggger log level
  156. self.assertEqual(self.log_setup.log_level, log_level)
  157. self.assertEqual(
  158. self.log_setup.config[self.loglevel_config_setting_name], log_level
  159. )
  160. self.assertEqual(self.log_setup.temp_log_level, log_level)
  161. # Check log file logger log level
  162. self.assertEqual(self.log_setup.log_level_logfile, default_log_level)
  163. def test_get_log_level_config(self):
  164. """
  165. Tests that log level match the configured value
  166. """
  167. args = self.args
  168. # Set log level in config
  169. log_level = "info"
  170. opts = self.testing_config.copy()
  171. opts.update({self.loglevel_config_setting_name: log_level})
  172. parser = self.parser()
  173. with patch(self.config_func, MagicMock(return_value=opts)):
  174. parser.parse_args(args)
  175. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  176. parser.setup_logfile_logger()
  177. console_log_level = getattr(parser.options, self.loglevel_config_setting_name)
  178. # Check console log level setting
  179. self.assertEqual(console_log_level, log_level)
  180. # Check console loggger log level
  181. self.assertEqual(self.log_setup.log_level, log_level)
  182. self.assertEqual(
  183. self.log_setup.config[self.loglevel_config_setting_name], log_level
  184. )
  185. self.assertEqual(self.log_setup.temp_log_level, "error")
  186. # Check log file logger log level
  187. self.assertEqual(self.log_setup.log_level_logfile, log_level)
  188. def test_get_log_level_default(self):
  189. """
  190. Tests that log level match the default value
  191. """
  192. # Set defaults
  193. log_level = default_log_level = self.testing_config[
  194. self.loglevel_config_setting_name
  195. ]
  196. args = self.args
  197. parser = self.parser()
  198. with patch(self.config_func, MagicMock(return_value=self.testing_config)):
  199. parser.parse_args(args)
  200. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  201. parser.setup_logfile_logger()
  202. console_log_level = getattr(parser.options, self.loglevel_config_setting_name)
  203. # Check log level setting
  204. self.assertEqual(console_log_level, log_level)
  205. # Check console loggger log level
  206. self.assertEqual(self.log_setup.log_level, log_level)
  207. # Check extended logger
  208. self.assertEqual(
  209. self.log_setup.config[self.loglevel_config_setting_name], log_level
  210. )
  211. self.assertEqual(self.log_setup.temp_log_level, "error")
  212. # Check log file logger
  213. self.assertEqual(self.log_setup.log_level_logfile, default_log_level)
  214. # Check help message
  215. self.assertIn(
  216. "Default: '{0}'.".format(default_log_level),
  217. parser.get_option("--log-level").help,
  218. )
  219. # log file configuration tests
  220. def test_get_log_file_cli(self):
  221. """
  222. Tests that log file match command-line specified value
  223. """
  224. # Set defaults
  225. log_level = self.testing_config[self.loglevel_config_setting_name]
  226. # Set log file in CLI
  227. log_file = "{0}_cli.log".format(self.log_file)
  228. args = ["--log-file", log_file] + self.args
  229. parser = self.parser()
  230. with patch(self.config_func, MagicMock(return_value=self.testing_config)):
  231. parser.parse_args(args)
  232. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  233. parser.setup_logfile_logger()
  234. log_file_option = getattr(parser.options, self.logfile_config_setting_name)
  235. if not self.skip_console_logging_config:
  236. # Check console loggger
  237. self.assertEqual(self.log_setup.log_level, log_level)
  238. # Check extended logger
  239. self.assertEqual(
  240. self.log_setup.config[self.loglevel_config_setting_name], log_level
  241. )
  242. self.assertEqual(
  243. self.log_setup.config[self.logfile_config_setting_name], log_file
  244. )
  245. # Check temp logger
  246. self.assertEqual(self.log_setup.temp_log_level, "error")
  247. # Check log file setting
  248. self.assertEqual(log_file_option, log_file)
  249. # Check log file logger
  250. self.assertEqual(self.log_setup.log_file, log_file)
  251. def test_get_log_file_config(self):
  252. """
  253. Tests that log file match the configured value
  254. """
  255. # Set defaults
  256. log_level = self.testing_config[self.loglevel_config_setting_name]
  257. args = self.args
  258. # Set log file in config
  259. log_file = "{0}_config.log".format(self.log_file)
  260. opts = self.testing_config.copy()
  261. opts.update({self.logfile_config_setting_name: log_file})
  262. parser = self.parser()
  263. with patch(self.config_func, MagicMock(return_value=opts)):
  264. parser.parse_args(args)
  265. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  266. parser.setup_logfile_logger()
  267. log_file_option = getattr(parser.options, self.logfile_config_setting_name)
  268. if not self.skip_console_logging_config:
  269. # Check console loggger
  270. self.assertEqual(self.log_setup.log_level, log_level)
  271. # Check extended logger
  272. self.assertEqual(
  273. self.log_setup.config[self.loglevel_config_setting_name], log_level
  274. )
  275. self.assertEqual(
  276. self.log_setup.config[self.logfile_config_setting_name], log_file
  277. )
  278. # Check temp logger
  279. self.assertEqual(self.log_setup.temp_log_level, "error")
  280. # Check log file setting
  281. self.assertEqual(log_file_option, log_file)
  282. # Check log file logger
  283. self.assertEqual(self.log_setup.log_file, log_file)
  284. def test_get_log_file_default(self):
  285. """
  286. Tests that log file match the default value
  287. """
  288. # Set defaults
  289. log_level = self.testing_config[self.loglevel_config_setting_name]
  290. log_file = self.testing_config[self.logfile_config_setting_name]
  291. default_log_file = self.default_config[self.logfile_config_setting_name]
  292. args = self.args
  293. parser = self.parser()
  294. with patch(self.config_func, MagicMock(return_value=self.testing_config)):
  295. parser.parse_args(args)
  296. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  297. parser.setup_logfile_logger()
  298. log_file_option = getattr(parser.options, self.logfile_config_setting_name)
  299. if not self.skip_console_logging_config:
  300. # Check console loggger
  301. self.assertEqual(self.log_setup.log_level, log_level)
  302. # Check extended logger
  303. self.assertEqual(
  304. self.log_setup.config[self.loglevel_config_setting_name], log_level
  305. )
  306. self.assertEqual(
  307. self.log_setup.config[self.logfile_config_setting_name], log_file
  308. )
  309. # Check temp logger
  310. self.assertEqual(self.log_setup.temp_log_level, "error")
  311. # Check log file setting
  312. self.assertEqual(log_file_option, log_file)
  313. # Check log file logger
  314. self.assertEqual(self.log_setup.log_file, log_file)
  315. # Check help message
  316. self.assertIn(
  317. "Default: '{0}'.".format(default_log_file),
  318. parser.get_option("--log-file").help,
  319. )
  320. # log file log level configuration tests
  321. def test_get_log_file_level_cli(self):
  322. """
  323. Tests that file log level match command-line specified value
  324. """
  325. # Set defaults
  326. default_log_level = self.testing_config[self.loglevel_config_setting_name]
  327. # Set log file level in CLI
  328. log_level_logfile = "error"
  329. args = ["--log-file-level", log_level_logfile] + self.args
  330. parser = self.parser()
  331. with patch(self.config_func, MagicMock(return_value=self.testing_config)):
  332. parser.parse_args(args)
  333. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  334. parser.setup_logfile_logger()
  335. log_level_logfile_option = getattr(
  336. parser.options, self.logfile_loglevel_config_setting_name
  337. )
  338. if not self.skip_console_logging_config:
  339. # Check console loggger
  340. self.assertEqual(self.log_setup.log_level, default_log_level)
  341. # Check extended logger
  342. self.assertEqual(
  343. self.log_setup.config[self.loglevel_config_setting_name],
  344. default_log_level,
  345. )
  346. self.assertEqual(
  347. self.log_setup.config[self.logfile_loglevel_config_setting_name],
  348. log_level_logfile,
  349. )
  350. # Check temp logger
  351. self.assertEqual(self.log_setup.temp_log_level, "error")
  352. # Check log file level setting
  353. self.assertEqual(log_level_logfile_option, log_level_logfile)
  354. # Check log file logger
  355. self.assertEqual(self.log_setup.log_level_logfile, log_level_logfile)
  356. def test_get_log_file_level_config(self):
  357. """
  358. Tests that log file level match the configured value
  359. """
  360. # Set defaults
  361. log_level = self.testing_config[self.loglevel_config_setting_name]
  362. args = self.args
  363. # Set log file level in config
  364. log_level_logfile = "info"
  365. opts = self.testing_config.copy()
  366. opts.update({self.logfile_loglevel_config_setting_name: log_level_logfile})
  367. parser = self.parser()
  368. with patch(self.config_func, MagicMock(return_value=opts)):
  369. parser.parse_args(args)
  370. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  371. parser.setup_logfile_logger()
  372. log_level_logfile_option = getattr(
  373. parser.options, self.logfile_loglevel_config_setting_name
  374. )
  375. if not self.skip_console_logging_config:
  376. # Check console loggger
  377. self.assertEqual(self.log_setup.log_level, log_level)
  378. # Check extended logger
  379. self.assertEqual(
  380. self.log_setup.config[self.loglevel_config_setting_name], log_level
  381. )
  382. self.assertEqual(
  383. self.log_setup.config[self.logfile_loglevel_config_setting_name],
  384. log_level_logfile,
  385. )
  386. # Check temp logger
  387. self.assertEqual(self.log_setup.temp_log_level, "error")
  388. # Check log file level setting
  389. self.assertEqual(log_level_logfile_option, log_level_logfile)
  390. # Check log file logger
  391. self.assertEqual(self.log_setup.log_level_logfile, log_level_logfile)
  392. def test_get_log_file_level_default(self):
  393. """
  394. Tests that log file level match the default value
  395. """
  396. # Set defaults
  397. default_log_level = self.testing_config[self.loglevel_config_setting_name]
  398. log_level = default_log_level
  399. log_level_logfile = default_log_level
  400. args = self.args
  401. parser = self.parser()
  402. with patch(self.config_func, MagicMock(return_value=self.testing_config)):
  403. parser.parse_args(args)
  404. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  405. parser.setup_logfile_logger()
  406. log_level_logfile_option = getattr(
  407. parser.options, self.logfile_loglevel_config_setting_name
  408. )
  409. if not self.skip_console_logging_config:
  410. # Check console loggger
  411. self.assertEqual(self.log_setup.log_level, log_level)
  412. # Check extended logger
  413. self.assertEqual(
  414. self.log_setup.config[self.loglevel_config_setting_name], log_level
  415. )
  416. self.assertEqual(
  417. self.log_setup.config[self.logfile_loglevel_config_setting_name],
  418. log_level_logfile,
  419. )
  420. # Check temp logger
  421. self.assertEqual(self.log_setup.temp_log_level, "error")
  422. # Check log file level setting
  423. self.assertEqual(log_level_logfile_option, log_level_logfile)
  424. # Check log file logger
  425. self.assertEqual(self.log_setup.log_level_logfile, log_level_logfile)
  426. # Check help message
  427. self.assertIn(
  428. "Default: '{0}'.".format(default_log_level),
  429. parser.get_option("--log-file-level").help,
  430. )
  431. def test_get_console_log_level_with_file_log_level(
  432. self,
  433. ): # pylint: disable=invalid-name
  434. """
  435. Tests that both console log level and log file level setting are working together
  436. """
  437. log_level = "critical"
  438. log_level_logfile = "debug"
  439. args = ["--log-file-level", log_level_logfile] + self.args
  440. opts = self.testing_config.copy()
  441. opts.update({self.loglevel_config_setting_name: log_level})
  442. parser = self.parser()
  443. with patch(self.config_func, MagicMock(return_value=opts)):
  444. parser.parse_args(args)
  445. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  446. parser.setup_logfile_logger()
  447. log_level_logfile_option = getattr(
  448. parser.options, self.logfile_loglevel_config_setting_name
  449. )
  450. if not self.skip_console_logging_config:
  451. # Check console loggger
  452. self.assertEqual(self.log_setup.log_level, log_level)
  453. # Check extended logger
  454. self.assertEqual(
  455. self.log_setup.config[self.loglevel_config_setting_name], log_level
  456. )
  457. self.assertEqual(
  458. self.log_setup.config[self.logfile_loglevel_config_setting_name],
  459. log_level_logfile,
  460. )
  461. # Check temp logger
  462. self.assertEqual(self.log_setup.temp_log_level, "error")
  463. # Check log file level setting
  464. self.assertEqual(log_level_logfile_option, log_level_logfile)
  465. # Check log file logger
  466. self.assertEqual(self.log_setup.log_level_logfile, log_level_logfile)
  467. @skipIf(salt.utils.platform.is_windows(), "Windows uses a logging listener")
  468. def test_log_created(self):
  469. """
  470. Tests that log file is created
  471. """
  472. args = self.args
  473. log_file = self.log_file
  474. log_file_name = self.logfile_config_setting_name
  475. opts = self.testing_config.copy()
  476. opts.update({"log_file": log_file})
  477. if log_file_name != "log_file":
  478. opts.update({log_file_name: getattr(self, log_file_name)})
  479. if log_file_name == "key_logfile":
  480. self.skipTest("salt-key creates log file outside of parse_args.")
  481. parser = self.parser()
  482. with patch(self.config_func, MagicMock(return_value=opts)):
  483. parser.parse_args(args)
  484. if log_file_name == "log_file":
  485. self.assertEqual(os.path.getsize(log_file), 0)
  486. else:
  487. self.assertEqual(os.path.getsize(getattr(self, log_file_name)), 0)
  488. def test_callbacks_uniqueness(self):
  489. """
  490. Test that the callbacks are only added once, no matter
  491. how many instances of the parser we create
  492. """
  493. mixin_container_names = (
  494. "_mixin_setup_funcs",
  495. "_mixin_process_funcs",
  496. "_mixin_after_parsed_funcs",
  497. "_mixin_before_exit_funcs",
  498. )
  499. parser = self.parser()
  500. nums_1 = {}
  501. for cb_container in mixin_container_names:
  502. obj = getattr(parser, cb_container)
  503. nums_1[cb_container] = len(obj)
  504. # The next time we instantiate the parser, the counts should be equal
  505. parser = self.parser()
  506. nums_2 = {}
  507. for cb_container in mixin_container_names:
  508. obj = getattr(parser, cb_container)
  509. nums_2[cb_container] = len(obj)
  510. self.assertDictEqual(nums_1, nums_2)
  511. @skipIf(salt.utils.platform.is_windows(), "Windows uses a logging listener")
  512. class MasterOptionParserTestCase(ParserBase, TestCase):
  513. """
  514. Tests parsing Salt Master options
  515. """
  516. def setUp(self):
  517. """
  518. Setting up
  519. """
  520. # Set defaults
  521. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  522. self.addCleanup(delattr, self, "default_config")
  523. # Log file
  524. self.log_file = "/tmp/salt_master_parser_test"
  525. # Function to patch
  526. self.config_func = "salt.config.master_config"
  527. # Mock log setup
  528. self.setup_log()
  529. # Assign parser
  530. self.parser = salt.utils.parsers.MasterOptionParser
  531. self.addCleanup(delattr, self, "parser")
  532. def tearDown(self):
  533. if os.path.exists(self.log_file):
  534. os.unlink(self.log_file)
  535. @skipIf(salt.utils.platform.is_windows(), "Windows uses a logging listener")
  536. class MinionOptionParserTestCase(ParserBase, TestCase):
  537. """
  538. Tests parsing Salt Minion options
  539. """
  540. def setUp(self):
  541. """
  542. Setting up
  543. """
  544. # Set defaults
  545. self.default_config = salt.config.DEFAULT_MINION_OPTS.copy()
  546. self.addCleanup(delattr, self, "default_config")
  547. # Log file
  548. self.log_file = "/tmp/salt_minion_parser_test"
  549. # Function to patch
  550. self.config_func = "salt.config.minion_config"
  551. # Mock log setup
  552. self.setup_log()
  553. # Assign parser
  554. self.parser = salt.utils.parsers.MinionOptionParser
  555. self.addCleanup(delattr, self, "parser")
  556. def tearDown(self):
  557. if os.path.exists(self.log_file):
  558. os.unlink(self.log_file)
  559. class ProxyMinionOptionParserTestCase(ParserBase, TestCase):
  560. """
  561. Tests parsing Salt Proxy Minion options
  562. """
  563. def setUp(self):
  564. """
  565. Setting up
  566. """
  567. # Set defaults
  568. self.default_config = salt.config.DEFAULT_MINION_OPTS.copy()
  569. self.default_config.update(salt.config.DEFAULT_PROXY_MINION_OPTS)
  570. self.addCleanup(delattr, self, "default_config")
  571. # Log file
  572. self.log_file = "/tmp/salt_proxy_minion_parser_test"
  573. # Function to patch
  574. self.config_func = "salt.config.proxy_config"
  575. # Mock log setup
  576. self.setup_log()
  577. # Assign parser
  578. self.parser = salt.utils.parsers.ProxyMinionOptionParser
  579. self.addCleanup(delattr, self, "parser")
  580. def tearDown(self):
  581. if os.path.exists(self.log_file):
  582. os.unlink(self.log_file)
  583. @skipIf(salt.utils.platform.is_windows(), "Windows uses a logging listener")
  584. class SyndicOptionParserTestCase(ParserBase, TestCase):
  585. """
  586. Tests parsing Salt Syndic options
  587. """
  588. def setUp(self):
  589. """
  590. Setting up
  591. """
  592. # Set config option names
  593. self.logfile_config_setting_name = "syndic_log_file"
  594. # Set defaults
  595. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  596. self.addCleanup(delattr, self, "default_config")
  597. # Log file
  598. self.log_file = "/tmp/salt_syndic_parser_test"
  599. self.syndic_log_file = "/tmp/salt_syndic_log"
  600. # Function to patch
  601. self.config_func = "salt.config.syndic_config"
  602. # Mock log setup
  603. self.setup_log()
  604. # Assign parser
  605. self.parser = salt.utils.parsers.SyndicOptionParser
  606. self.addCleanup(delattr, self, "parser")
  607. def tearDown(self):
  608. if os.path.exists(self.log_file):
  609. os.unlink(self.log_file)
  610. if os.path.exists(self.syndic_log_file):
  611. os.unlink(self.syndic_log_file)
  612. class SaltCMDOptionParserTestCase(ParserBase, TestCase):
  613. """
  614. Tests parsing Salt CLI options
  615. """
  616. def setUp(self):
  617. """
  618. Setting up
  619. """
  620. # Set mandatory CLI options
  621. self.args = ["foo", "bar.baz"]
  622. # Set defaults
  623. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  624. self.addCleanup(delattr, self, "default_config")
  625. # Log file
  626. self.log_file = "/tmp/salt_cmd_parser_test"
  627. # Function to patch
  628. self.config_func = "salt.config.client_config"
  629. # Mock log setup
  630. self.setup_log()
  631. # Assign parser
  632. self.parser = salt.utils.parsers.SaltCMDOptionParser
  633. self.addCleanup(delattr, self, "parser")
  634. def tearDown(self):
  635. if os.path.exists(self.log_file):
  636. os.unlink(self.log_file)
  637. class SaltCPOptionParserTestCase(ParserBase, TestCase):
  638. """
  639. Tests parsing salt-cp options
  640. """
  641. def setUp(self):
  642. """
  643. Setting up
  644. """
  645. # Set mandatory CLI options
  646. self.args = ["foo", "bar", "baz"]
  647. # Set defaults
  648. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  649. self.addCleanup(delattr, self, "default_config")
  650. # Log file
  651. self.log_file = "/tmp/salt_cp_parser_test"
  652. # Function to patch
  653. self.config_func = "salt.config.master_config"
  654. # Mock log setup
  655. self.setup_log()
  656. # Assign parser
  657. self.parser = salt.utils.parsers.SaltCPOptionParser
  658. self.addCleanup(delattr, self, "parser")
  659. def tearDown(self):
  660. if os.path.exists(self.log_file):
  661. os.unlink(self.log_file)
  662. class SaltKeyOptionParserTestCase(ParserBase, TestCase):
  663. """
  664. Tests parsing salt-key options
  665. """
  666. def setUp(self):
  667. """
  668. Setting up
  669. """
  670. self.skip_console_logging_config = True
  671. # Set config option names
  672. self.logfile_config_setting_name = "key_logfile"
  673. # Set defaults
  674. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  675. self.addCleanup(delattr, self, "default_config")
  676. # Log file
  677. self.log_file = "/tmp/salt_key_parser_test"
  678. self.key_logfile = "/tmp/key_logfile"
  679. # Function to patch
  680. self.config_func = "salt.config.client_config"
  681. # Mock log setup
  682. self.setup_log()
  683. # Assign parser
  684. self.parser = salt.utils.parsers.SaltKeyOptionParser
  685. self.addCleanup(delattr, self, "parser")
  686. # log level configuration tests
  687. def test_get_log_level_cli(self):
  688. """
  689. Tests that console log level option is not recognized
  690. """
  691. # No console log level will be actually set
  692. log_level = default_log_level = None
  693. option = "--log-level"
  694. args = self.args + [option, "error"]
  695. parser = self.parser()
  696. mock_err = ErrorMock()
  697. with patch("salt.utils.parsers.OptionParser.error", mock_err.error):
  698. parser.parse_args(args)
  699. # Check error msg
  700. self.assertEqual(mock_err.msg, "no such option: {0}".format(option))
  701. # Check console loggger has not been set
  702. self.assertEqual(self.log_setup.log_level, log_level)
  703. self.assertNotIn(self.loglevel_config_setting_name, self.log_setup.config)
  704. # Check temp logger
  705. self.assertEqual(self.log_setup.temp_log_level, "error")
  706. # Check log file logger log level
  707. self.assertEqual(self.log_setup.log_level_logfile, default_log_level)
  708. @pytest.mark.slow_test(seconds=1) # Test takes >0.1 and <=1 seconds
  709. def test_get_log_level_config(self):
  710. """
  711. Tests that log level set in config is ignored
  712. """
  713. log_level = "info"
  714. args = self.args
  715. # Set log level in config and set additional mocked opts keys
  716. opts = {
  717. self.loglevel_config_setting_name: log_level,
  718. self.logfile_config_setting_name: "key_logfile",
  719. "log_fmt_logfile": None,
  720. "log_datefmt_logfile": None,
  721. "log_rotate_max_bytes": None,
  722. "log_rotate_backup_count": None,
  723. }
  724. parser = self.parser()
  725. with patch(self.config_func, MagicMock(return_value=opts)):
  726. parser.parse_args(args)
  727. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  728. parser.setup_logfile_logger()
  729. # Check config name absence in options
  730. self.assertNotIn(self.loglevel_config_setting_name, parser.options.__dict__)
  731. # Check console loggger has not been set
  732. self.assertEqual(self.log_setup.log_level, None)
  733. self.assertNotIn(self.loglevel_config_setting_name, self.log_setup.config)
  734. # Check temp logger
  735. self.assertEqual(self.log_setup.temp_log_level, "error")
  736. # Check log file logger log level
  737. self.assertEqual(self.log_setup.log_level_logfile, log_level)
  738. def test_get_log_level_default(self):
  739. """
  740. Tests that log level default value is ignored
  741. """
  742. # Set defaults
  743. default_log_level = self.testing_config[self.loglevel_config_setting_name]
  744. log_level = None
  745. args = self.args
  746. parser = self.parser()
  747. parser.parse_args(args)
  748. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  749. parser.setup_logfile_logger()
  750. # Check config name absence in options
  751. self.assertNotIn(self.loglevel_config_setting_name, parser.options.__dict__)
  752. # Check console loggger has not been set
  753. self.assertEqual(self.log_setup.log_level, log_level)
  754. self.assertNotIn(self.loglevel_config_setting_name, self.log_setup.config)
  755. # Check temp logger
  756. self.assertEqual(self.log_setup.temp_log_level, "error")
  757. # Check log file logger log level
  758. self.assertEqual(self.log_setup.log_level_logfile, default_log_level)
  759. def tearDown(self):
  760. if os.path.exists(self.log_file):
  761. os.unlink(self.log_file)
  762. if os.path.exists(self.key_logfile):
  763. os.unlink(self.key_logfile)
  764. class SaltCallOptionParserTestCase(ParserBase, TestCase):
  765. """
  766. Tests parsing Salt Minion options
  767. """
  768. def setUp(self):
  769. """
  770. Setting up
  771. """
  772. # Set mandatory CLI options
  773. self.args = ["foo.bar"]
  774. # Set defaults
  775. self.default_config = salt.config.DEFAULT_MINION_OPTS.copy()
  776. self.addCleanup(delattr, self, "default_config")
  777. # Log file
  778. self.log_file = "/tmp/salt_call_parser_test"
  779. # Function to patch
  780. self.config_func = "salt.config.minion_config"
  781. # Mock log setup
  782. self.setup_log()
  783. # Assign parser
  784. self.parser = salt.utils.parsers.SaltCallOptionParser
  785. self.addCleanup(delattr, self, "parser")
  786. def tearDown(self):
  787. if os.path.exists(self.log_file):
  788. os.unlink(self.log_file)
  789. class SaltRunOptionParserTestCase(ParserBase, TestCase):
  790. """
  791. Tests parsing Salt Master options
  792. """
  793. def setUp(self):
  794. """
  795. Setting up
  796. """
  797. # Set mandatory CLI options
  798. self.args = ["foo.bar"]
  799. # Set defaults
  800. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  801. self.addCleanup(delattr, self, "default_config")
  802. # Log file
  803. self.log_file = "/tmp/salt_run_parser_test"
  804. # Function to patch
  805. self.config_func = "salt.config.master_config"
  806. # Mock log setup
  807. self.setup_log()
  808. # Assign parser
  809. self.parser = salt.utils.parsers.SaltRunOptionParser
  810. self.addCleanup(delattr, self, "parser")
  811. def tearDown(self):
  812. if os.path.exists(self.log_file):
  813. os.unlink(self.log_file)
  814. class SaltSSHOptionParserTestCase(ParserBase, TestCase):
  815. """
  816. Tests parsing Salt Master options
  817. """
  818. def setUp(self):
  819. """
  820. Setting up
  821. """
  822. # Set mandatory CLI options
  823. self.args = ["foo", "bar.baz"]
  824. # Set config option names
  825. self.logfile_config_setting_name = "ssh_log_file"
  826. # Set defaults
  827. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  828. self.addCleanup(delattr, self, "default_config")
  829. # Log file
  830. self.log_file = "/tmp/salt_ssh_parser_test"
  831. self.ssh_log_file = "/tmp/ssh_logfile"
  832. # Function to patch
  833. self.config_func = "salt.config.master_config"
  834. # Mock log setup
  835. self.setup_log()
  836. # Assign parser
  837. self.parser = salt.utils.parsers.SaltSSHOptionParser
  838. self.addCleanup(delattr, self, "parser")
  839. def tearDown(self):
  840. if os.path.exists(self.log_file):
  841. os.unlink(self.log_file)
  842. if os.path.exists(self.ssh_log_file):
  843. os.unlink(self.ssh_log_file)
  844. class SaltCloudParserTestCase(ParserBase, TestCase):
  845. """
  846. Tests parsing Salt Cloud options
  847. """
  848. def setUp(self):
  849. """
  850. Setting up
  851. """
  852. # Set mandatory CLI options
  853. self.args = ["-p", "foo", "bar"]
  854. # Set default configs
  855. # Cloud configs are merged with master configs in
  856. # config/__init__.py, so we'll do that here as well
  857. # As we need the 'user' key later on.
  858. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  859. self.default_config.update(salt.config.DEFAULT_CLOUD_OPTS)
  860. self.addCleanup(delattr, self, "default_config")
  861. # Log file
  862. self.log_file = "/tmp/salt_cloud_parser_test"
  863. # Function to patch
  864. self.config_func = "salt.config.cloud_config"
  865. # Mock log setup
  866. self.setup_log()
  867. # Assign parser
  868. self.parser = salt.utils.parsers.SaltCloudParser
  869. self.addCleanup(delattr, self, "parser")
  870. def tearDown(self):
  871. if os.path.exists(self.log_file):
  872. os.unlink(self.log_file)
  873. class SPMParserTestCase(ParserBase, TestCase):
  874. """
  875. Tests parsing Salt Cloud options
  876. """
  877. def setUp(self):
  878. """
  879. Setting up
  880. """
  881. # Set mandatory CLI options
  882. self.args = ["foo", "bar"]
  883. # Set config option names
  884. self.logfile_config_setting_name = "spm_logfile"
  885. # Set defaults
  886. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  887. self.default_config.update(salt.config.DEFAULT_SPM_OPTS)
  888. self.addCleanup(delattr, self, "default_config")
  889. # Log file
  890. self.log_file = "/tmp/spm_parser_test"
  891. self.spm_logfile = "/tmp/spm_logfile"
  892. # Function to patch
  893. self.config_func = "salt.config.spm_config"
  894. # Mock log setup
  895. self.setup_log()
  896. # Assign parser
  897. self.parser = salt.utils.parsers.SPMParser
  898. self.addCleanup(delattr, self, "parser")
  899. def tearDown(self):
  900. if os.path.exists(self.log_file):
  901. os.unlink(self.log_file)
  902. if os.path.exists(self.spm_logfile):
  903. os.unlink(self.spm_logfile)
  904. class SaltAPIParserTestCase(ParserBase, TestCase):
  905. """
  906. Tests parsing Salt Cloud options
  907. """
  908. def setUp(self):
  909. """
  910. Setting up
  911. """
  912. # Set mandatory CLI options
  913. self.args = []
  914. # Set config option names
  915. self.logfile_config_setting_name = "api_logfile"
  916. # Set defaults
  917. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  918. self.default_config.update(salt.config.DEFAULT_API_OPTS)
  919. self.addCleanup(delattr, self, "default_config")
  920. # Log file
  921. self.log_file = "/tmp/salt_api_parser_test"
  922. self.api_logfile = "/tmp/api_logfile"
  923. # Function to patch
  924. self.config_func = "salt.config.api_config"
  925. # Mock log setup
  926. self.setup_log()
  927. # Assign parser
  928. self.parser = salt.utils.parsers.SaltAPIParser
  929. self.addCleanup(delattr, self, "parser")
  930. def tearDown(self):
  931. if os.path.exists(self.log_file):
  932. os.unlink(self.log_file)
  933. if os.path.exists(self.api_logfile):
  934. os.unlink(self.api_logfile)
  935. class DaemonMixInTestCase(TestCase):
  936. """
  937. Tests the PIDfile deletion in the DaemonMixIn.
  938. """
  939. def setUp(self):
  940. """
  941. Setting up
  942. """
  943. # Setup mixin
  944. self.daemon_mixin = salt.utils.parsers.DaemonMixIn()
  945. self.daemon_mixin.config = {}
  946. self.daemon_mixin.config["pidfile"] = "/some/fake.pid"
  947. def tearDown(self):
  948. """
  949. Tear down test
  950. :return:
  951. """
  952. del self.daemon_mixin
  953. @patch("os.unlink", MagicMock())
  954. @patch("os.path.isfile", MagicMock(return_value=True))
  955. @patch("salt.utils.parsers.logger", MagicMock())
  956. def test_pid_file_deletion(self):
  957. """
  958. PIDfile deletion without exception.
  959. """
  960. self.daemon_mixin._mixin_before_exit()
  961. assert salt.utils.parsers.os.unlink.call_count == 1
  962. salt.utils.parsers.logger.info.assert_not_called()
  963. salt.utils.parsers.logger.debug.assert_not_called()
  964. @patch("os.unlink", MagicMock(side_effect=OSError()))
  965. @patch("os.path.isfile", MagicMock(return_value=True))
  966. @patch("salt.utils.parsers.logger", MagicMock())
  967. def test_pid_deleted_oserror_as_root(self):
  968. """
  969. PIDfile deletion with exception, running as root.
  970. """
  971. if salt.utils.platform.is_windows():
  972. patch_args = (
  973. "salt.utils.win_functions.is_admin",
  974. MagicMock(return_value=True),
  975. )
  976. else:
  977. patch_args = ("os.getuid", MagicMock(return_value=0))
  978. with patch(*patch_args):
  979. self.daemon_mixin._mixin_before_exit()
  980. assert salt.utils.parsers.os.unlink.call_count == 1
  981. salt.utils.parsers.logger.info.assert_called_with(
  982. "PIDfile could not be deleted: %s",
  983. format(self.daemon_mixin.config["pidfile"]),
  984. )
  985. salt.utils.parsers.logger.debug.assert_called()
  986. @patch("os.unlink", MagicMock(side_effect=OSError()))
  987. @patch("os.path.isfile", MagicMock(return_value=True))
  988. @patch("salt.utils.parsers.logger", MagicMock())
  989. def test_pid_deleted_oserror_as_non_root(self):
  990. """
  991. PIDfile deletion with exception, running as non-root.
  992. """
  993. if salt.utils.platform.is_windows():
  994. patch_args = (
  995. "salt.utils.win_functions.is_admin",
  996. MagicMock(return_value=False),
  997. )
  998. else:
  999. patch_args = ("os.getuid", MagicMock(return_value=1000))
  1000. with patch(*patch_args):
  1001. self.daemon_mixin._mixin_before_exit()
  1002. assert salt.utils.parsers.os.unlink.call_count == 1
  1003. salt.utils.parsers.logger.info.assert_not_called()
  1004. salt.utils.parsers.logger.debug.assert_not_called()