test_parsers.py 39 KB

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