1
0

test_parsers.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226
  1. # -*- coding: utf-8 -*-
  2. """
  3. :codeauthor: Denys Havrysh <denys.gavrysh@gmail.com>
  4. """
  5. # Import python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. import os
  8. import shutil
  9. import tempfile
  10. import salt.config
  11. # Import Salt Libs
  12. import salt.log.setup as log
  13. import salt.syspaths
  14. import salt.utils.parsers
  15. import salt.utils.platform
  16. from tests.support.mock import MagicMock, patch
  17. from tests.support.runtests import RUNTIME_VARS
  18. # Import Salt Testing Libs
  19. from tests.support.unit import TestCase, skipIf
  20. class ErrorMock(object): # pylint: disable=too-few-public-methods
  21. """
  22. Error handling
  23. """
  24. def __init__(self):
  25. """
  26. init
  27. """
  28. self.msg = None
  29. def error(self, msg):
  30. """
  31. Capture error message
  32. """
  33. self.msg = msg
  34. class LogSetupMock(object):
  35. """
  36. Logger setup
  37. """
  38. def __init__(self):
  39. """
  40. init
  41. """
  42. self.log_level = None
  43. self.log_file = None
  44. self.log_level_logfile = None
  45. self.config = {}
  46. self.temp_log_level = None
  47. def setup_console_logger(
  48. self, log_level="error", **kwargs
  49. ): # pylint: disable=unused-argument
  50. """
  51. Set console loglevel
  52. """
  53. self.log_level = log_level
  54. def setup_extended_logging(self, opts):
  55. """
  56. Set opts
  57. """
  58. self.config = opts
  59. def setup_logfile_logger(
  60. self, logfile, loglevel, **kwargs
  61. ): # pylint: disable=unused-argument
  62. """
  63. Set logfile and loglevel
  64. """
  65. self.log_file = logfile
  66. self.log_level_logfile = loglevel
  67. @staticmethod
  68. def get_multiprocessing_logging_queue(): # pylint: disable=invalid-name
  69. """
  70. Mock
  71. """
  72. import multiprocessing
  73. return multiprocessing.Queue()
  74. def setup_multiprocessing_logging_listener(
  75. self, opts, *args
  76. ): # pylint: disable=invalid-name,unused-argument
  77. """
  78. Set opts
  79. """
  80. self.config = opts
  81. def setup_temp_logger(self, log_level="error"):
  82. """
  83. Set temp loglevel
  84. """
  85. self.temp_log_level = log_level
  86. class ObjectView(object): # pylint: disable=too-few-public-methods
  87. """
  88. Dict object view
  89. """
  90. def __init__(self, d):
  91. self.__dict__ = d
  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. log,
  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. def test_get_log_level_config(self):
  709. """
  710. Tests that log level set in config is ignored
  711. """
  712. log_level = "info"
  713. args = self.args
  714. # Set log level in config and set additional mocked opts keys
  715. opts = {
  716. self.loglevel_config_setting_name: log_level,
  717. self.logfile_config_setting_name: "key_logfile",
  718. "log_fmt_logfile": None,
  719. "log_datefmt_logfile": None,
  720. "log_rotate_max_bytes": None,
  721. "log_rotate_backup_count": None,
  722. }
  723. parser = self.parser()
  724. with patch(self.config_func, MagicMock(return_value=opts)):
  725. parser.parse_args(args)
  726. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  727. parser.setup_logfile_logger()
  728. # Check config name absence in options
  729. self.assertNotIn(self.loglevel_config_setting_name, parser.options.__dict__)
  730. # Check console loggger has not been set
  731. self.assertEqual(self.log_setup.log_level, None)
  732. self.assertNotIn(self.loglevel_config_setting_name, self.log_setup.config)
  733. # Check temp logger
  734. self.assertEqual(self.log_setup.temp_log_level, "error")
  735. # Check log file logger log level
  736. self.assertEqual(self.log_setup.log_level_logfile, log_level)
  737. def test_get_log_level_default(self):
  738. """
  739. Tests that log level default value is ignored
  740. """
  741. # Set defaults
  742. default_log_level = self.testing_config[self.loglevel_config_setting_name]
  743. log_level = None
  744. args = self.args
  745. parser = self.parser()
  746. parser.parse_args(args)
  747. with patch("salt.utils.parsers.is_writeable", MagicMock(return_value=True)):
  748. parser.setup_logfile_logger()
  749. # Check config name absence in options
  750. self.assertNotIn(self.loglevel_config_setting_name, parser.options.__dict__)
  751. # Check console loggger has not been set
  752. self.assertEqual(self.log_setup.log_level, log_level)
  753. self.assertNotIn(self.loglevel_config_setting_name, self.log_setup.config)
  754. # Check temp logger
  755. self.assertEqual(self.log_setup.temp_log_level, "error")
  756. # Check log file logger log level
  757. self.assertEqual(self.log_setup.log_level_logfile, default_log_level)
  758. def tearDown(self):
  759. if os.path.exists(self.log_file):
  760. os.unlink(self.log_file)
  761. if os.path.exists(self.key_logfile):
  762. os.unlink(self.key_logfile)
  763. class SaltCallOptionParserTestCase(ParserBase, TestCase):
  764. """
  765. Tests parsing Salt Minion options
  766. """
  767. def setUp(self):
  768. """
  769. Setting up
  770. """
  771. # Set mandatory CLI options
  772. self.args = ["foo.bar"]
  773. # Set defaults
  774. self.default_config = salt.config.DEFAULT_MINION_OPTS.copy()
  775. self.addCleanup(delattr, self, "default_config")
  776. # Log file
  777. self.log_file = "/tmp/salt_call_parser_test"
  778. # Function to patch
  779. self.config_func = "salt.config.minion_config"
  780. # Mock log setup
  781. self.setup_log()
  782. # Assign parser
  783. self.parser = salt.utils.parsers.SaltCallOptionParser
  784. self.addCleanup(delattr, self, "parser")
  785. def tearDown(self):
  786. if os.path.exists(self.log_file):
  787. os.unlink(self.log_file)
  788. class SaltRunOptionParserTestCase(ParserBase, TestCase):
  789. """
  790. Tests parsing Salt Master options
  791. """
  792. def setUp(self):
  793. """
  794. Setting up
  795. """
  796. # Set mandatory CLI options
  797. self.args = ["foo.bar"]
  798. # Set defaults
  799. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  800. self.addCleanup(delattr, self, "default_config")
  801. # Log file
  802. self.log_file = "/tmp/salt_run_parser_test"
  803. # Function to patch
  804. self.config_func = "salt.config.master_config"
  805. # Mock log setup
  806. self.setup_log()
  807. # Assign parser
  808. self.parser = salt.utils.parsers.SaltRunOptionParser
  809. self.addCleanup(delattr, self, "parser")
  810. def tearDown(self):
  811. if os.path.exists(self.log_file):
  812. os.unlink(self.log_file)
  813. class SaltSSHOptionParserTestCase(ParserBase, TestCase):
  814. """
  815. Tests parsing Salt Master options
  816. """
  817. def setUp(self):
  818. """
  819. Setting up
  820. """
  821. # Set mandatory CLI options
  822. self.args = ["foo", "bar.baz"]
  823. # Set config option names
  824. self.logfile_config_setting_name = "ssh_log_file"
  825. # Set defaults
  826. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  827. self.addCleanup(delattr, self, "default_config")
  828. # Log file
  829. self.log_file = "/tmp/salt_ssh_parser_test"
  830. self.ssh_log_file = "/tmp/ssh_logfile"
  831. # Function to patch
  832. self.config_func = "salt.config.master_config"
  833. # Mock log setup
  834. self.setup_log()
  835. # Assign parser
  836. self.parser = salt.utils.parsers.SaltSSHOptionParser
  837. self.addCleanup(delattr, self, "parser")
  838. def tearDown(self):
  839. if os.path.exists(self.log_file):
  840. os.unlink(self.log_file)
  841. if os.path.exists(self.ssh_log_file):
  842. os.unlink(self.ssh_log_file)
  843. class SaltCloudParserTestCase(ParserBase, TestCase):
  844. """
  845. Tests parsing Salt Cloud options
  846. """
  847. def setUp(self):
  848. """
  849. Setting up
  850. """
  851. # Set mandatory CLI options
  852. self.args = ["-p", "foo", "bar"]
  853. # Set default configs
  854. # Cloud configs are merged with master configs in
  855. # config/__init__.py, so we'll do that here as well
  856. # As we need the 'user' key later on.
  857. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  858. self.default_config.update(salt.config.DEFAULT_CLOUD_OPTS)
  859. self.addCleanup(delattr, self, "default_config")
  860. # Log file
  861. self.log_file = "/tmp/salt_cloud_parser_test"
  862. # Function to patch
  863. self.config_func = "salt.config.cloud_config"
  864. # Mock log setup
  865. self.setup_log()
  866. # Assign parser
  867. self.parser = salt.utils.parsers.SaltCloudParser
  868. self.addCleanup(delattr, self, "parser")
  869. def tearDown(self):
  870. if os.path.exists(self.log_file):
  871. os.unlink(self.log_file)
  872. class SPMParserTestCase(ParserBase, TestCase):
  873. """
  874. Tests parsing Salt Cloud options
  875. """
  876. def setUp(self):
  877. """
  878. Setting up
  879. """
  880. # Set mandatory CLI options
  881. self.args = ["foo", "bar"]
  882. # Set config option names
  883. self.logfile_config_setting_name = "spm_logfile"
  884. # Set defaults
  885. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  886. self.default_config.update(salt.config.DEFAULT_SPM_OPTS)
  887. self.addCleanup(delattr, self, "default_config")
  888. # Log file
  889. self.log_file = "/tmp/spm_parser_test"
  890. self.spm_logfile = "/tmp/spm_logfile"
  891. # Function to patch
  892. self.config_func = "salt.config.spm_config"
  893. # Mock log setup
  894. self.setup_log()
  895. # Assign parser
  896. self.parser = salt.utils.parsers.SPMParser
  897. self.addCleanup(delattr, self, "parser")
  898. def tearDown(self):
  899. if os.path.exists(self.log_file):
  900. os.unlink(self.log_file)
  901. if os.path.exists(self.spm_logfile):
  902. os.unlink(self.spm_logfile)
  903. class SaltAPIParserTestCase(ParserBase, TestCase):
  904. """
  905. Tests parsing Salt Cloud options
  906. """
  907. def setUp(self):
  908. """
  909. Setting up
  910. """
  911. # Set mandatory CLI options
  912. self.args = []
  913. # Set config option names
  914. self.logfile_config_setting_name = "api_logfile"
  915. # Set defaults
  916. self.default_config = salt.config.DEFAULT_MASTER_OPTS.copy()
  917. self.default_config.update(salt.config.DEFAULT_API_OPTS)
  918. self.addCleanup(delattr, self, "default_config")
  919. # Log file
  920. self.log_file = "/tmp/salt_api_parser_test"
  921. self.api_logfile = "/tmp/api_logfile"
  922. # Function to patch
  923. self.config_func = "salt.config.api_config"
  924. # Mock log setup
  925. self.setup_log()
  926. # Assign parser
  927. self.parser = salt.utils.parsers.SaltAPIParser
  928. self.addCleanup(delattr, self, "parser")
  929. def tearDown(self):
  930. if os.path.exists(self.log_file):
  931. os.unlink(self.log_file)
  932. if os.path.exists(self.api_logfile):
  933. os.unlink(self.api_logfile)
  934. class DaemonMixInTestCase(TestCase):
  935. """
  936. Tests the PIDfile deletion in the DaemonMixIn.
  937. """
  938. def setUp(self):
  939. """
  940. Setting up
  941. """
  942. # Setup mixin
  943. self.daemon_mixin = salt.utils.parsers.DaemonMixIn()
  944. self.daemon_mixin.config = {}
  945. self.daemon_mixin.config["pidfile"] = "/some/fake.pid"
  946. def tearDown(self):
  947. """
  948. Tear down test
  949. :return:
  950. """
  951. del self.daemon_mixin
  952. @patch("os.unlink", MagicMock())
  953. @patch("os.path.isfile", MagicMock(return_value=True))
  954. @patch("salt.utils.parsers.logger", MagicMock())
  955. def test_pid_file_deletion(self):
  956. """
  957. PIDfile deletion without exception.
  958. """
  959. self.daemon_mixin._mixin_before_exit()
  960. assert salt.utils.parsers.os.unlink.call_count == 1
  961. salt.utils.parsers.logger.info.assert_not_called()
  962. salt.utils.parsers.logger.debug.assert_not_called()
  963. @patch("os.unlink", MagicMock(side_effect=OSError()))
  964. @patch("os.path.isfile", MagicMock(return_value=True))
  965. @patch("salt.utils.parsers.logger", MagicMock())
  966. def test_pid_deleted_oserror_as_root(self):
  967. """
  968. PIDfile deletion with exception, running as root.
  969. """
  970. if salt.utils.platform.is_windows():
  971. patch_args = (
  972. "salt.utils.win_functions.is_admin",
  973. MagicMock(return_value=True),
  974. )
  975. else:
  976. patch_args = ("os.getuid", MagicMock(return_value=0))
  977. with patch(*patch_args):
  978. self.daemon_mixin._mixin_before_exit()
  979. assert salt.utils.parsers.os.unlink.call_count == 1
  980. salt.utils.parsers.logger.info.assert_called_with(
  981. "PIDfile could not be deleted: %s",
  982. format(self.daemon_mixin.config["pidfile"]),
  983. )
  984. salt.utils.parsers.logger.debug.assert_called()
  985. @patch("os.unlink", MagicMock(side_effect=OSError()))
  986. @patch("os.path.isfile", MagicMock(return_value=True))
  987. @patch("salt.utils.parsers.logger", MagicMock())
  988. def test_pid_deleted_oserror_as_non_root(self):
  989. """
  990. PIDfile deletion with exception, running as non-root.
  991. """
  992. if salt.utils.platform.is_windows():
  993. patch_args = (
  994. "salt.utils.win_functions.is_admin",
  995. MagicMock(return_value=False),
  996. )
  997. else:
  998. patch_args = ("os.getuid", MagicMock(return_value=1000))
  999. with patch(*patch_args):
  1000. self.daemon_mixin._mixin_before_exit()
  1001. assert salt.utils.parsers.os.unlink.call_count == 1
  1002. salt.utils.parsers.logger.info.assert_not_called()
  1003. salt.utils.parsers.logger.debug.assert_not_called()