test_parsers.py 39 KB

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