test_schedule.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. # -*- coding: utf-8 -*-
  2. '''
  3. :codeauthor: Nicole Thomas <nicole@saltstack.com>
  4. '''
  5. # Import python libs
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. import copy
  8. import datetime
  9. import logging
  10. import os
  11. # Import Salt Testing Libs
  12. from tests.support.unit import skipIf, TestCase
  13. from tests.support.mock import MagicMock, patch, NO_MOCK, NO_MOCK_REASON
  14. import tests.integration as integration
  15. # Import Salt Libs
  16. import salt.config
  17. from salt.utils.schedule import Schedule
  18. # pylint: disable=import-error,unused-import
  19. try:
  20. import croniter
  21. _CRON_SUPPORTED = True
  22. except ImportError:
  23. _CRON_SUPPORTED = False
  24. # pylint: enable=import-error
  25. log = logging.getLogger(__name__)
  26. ROOT_DIR = os.path.join(integration.TMP, 'schedule-unit-tests')
  27. SOCK_DIR = os.path.join(ROOT_DIR, 'test-socks')
  28. DEFAULT_CONFIG = salt.config.minion_config(None)
  29. DEFAULT_CONFIG['conf_dir'] = ROOT_DIR
  30. DEFAULT_CONFIG['root_dir'] = ROOT_DIR
  31. DEFAULT_CONFIG['sock_dir'] = SOCK_DIR
  32. DEFAULT_CONFIG['pki_dir'] = os.path.join(ROOT_DIR, 'pki')
  33. DEFAULT_CONFIG['cachedir'] = os.path.join(ROOT_DIR, 'cache')
  34. # pylint: disable=too-many-public-methods,invalid-name
  35. @skipIf(NO_MOCK, NO_MOCK_REASON)
  36. class ScheduleTestCase(TestCase):
  37. '''
  38. Unit tests for salt.utils.schedule module
  39. '''
  40. def setUp(self):
  41. with patch('salt.utils.schedule.clean_proc_dir', MagicMock(return_value=None)):
  42. self.schedule = Schedule(copy.deepcopy(DEFAULT_CONFIG), {}, returners={})
  43. # delete_job tests
  44. def test_delete_job_exists(self):
  45. '''
  46. Tests ensuring the job exists and deleting it
  47. '''
  48. self.schedule.opts.update({'schedule': {'foo': 'bar'}, 'pillar': {}})
  49. self.assertIn('foo', self.schedule.opts['schedule'])
  50. self.schedule.delete_job('foo')
  51. self.assertNotIn('foo', self.schedule.opts['schedule'])
  52. def test_delete_job_in_pillar(self):
  53. '''
  54. Tests ignoring deletion job from pillar
  55. '''
  56. self.schedule.opts.update({'pillar': {'schedule': {'foo': 'bar'}}, 'schedule': {}})
  57. self.assertIn('foo', self.schedule.opts['pillar']['schedule'])
  58. self.schedule.delete_job('foo')
  59. self.assertIn('foo', self.schedule.opts['pillar']['schedule'])
  60. def test_delete_job_intervals(self):
  61. '''
  62. Tests removing job from intervals
  63. '''
  64. self.schedule.opts.update({'pillar': {}, 'schedule': {}})
  65. self.schedule.intervals = {'foo': 'bar'}
  66. self.schedule.delete_job('foo')
  67. self.assertNotIn('foo', self.schedule.intervals)
  68. def test_delete_job_prefix(self):
  69. '''
  70. Tests ensuring jobs exists and deleting them by prefix
  71. '''
  72. self.schedule.opts.update({'schedule': {'foobar': 'bar', 'foobaz': 'baz', 'fooboo': 'boo'},
  73. 'pillar': {}})
  74. ret = copy.deepcopy(self.schedule.opts)
  75. del ret['schedule']['foobar']
  76. del ret['schedule']['foobaz']
  77. self.schedule.delete_job_prefix('fooba')
  78. self.assertEqual(self.schedule.opts, ret)
  79. def test_delete_job_prefix_in_pillar(self):
  80. '''
  81. Tests ignoring deletion jobs by prefix from pillar
  82. '''
  83. self.schedule.opts.update({'pillar': {'schedule': {'foobar': 'bar', 'foobaz': 'baz', 'fooboo': 'boo'}},
  84. 'schedule': {}})
  85. ret = copy.deepcopy(self.schedule.opts)
  86. self.schedule.delete_job_prefix('fooba')
  87. self.assertEqual(self.schedule.opts, ret)
  88. # add_job tests
  89. def test_add_job_data_not_dict(self):
  90. '''
  91. Tests if data is a dictionary
  92. '''
  93. data = 'foo'
  94. self.assertRaises(ValueError, Schedule.add_job, self.schedule, data)
  95. def test_add_job_multiple_jobs(self):
  96. '''
  97. Tests if more than one job is scheduled at a time
  98. '''
  99. data = {'key1': 'value1', 'key2': 'value2'}
  100. self.assertRaises(ValueError, Schedule.add_job, self.schedule, data)
  101. def test_add_job(self):
  102. '''
  103. Tests adding a job to the schedule
  104. '''
  105. data = {'foo': {'bar': 'baz'}}
  106. ret = copy.deepcopy(self.schedule.opts)
  107. ret.update({'schedule': {'foo': {'bar': 'baz', 'enabled': True},
  108. 'hello': {'world': 'peace', 'enabled': True}},
  109. 'pillar': {}})
  110. self.schedule.opts.update({'schedule': {'hello': {'world': 'peace', 'enabled': True}},
  111. 'pillar': {}})
  112. Schedule.add_job(self.schedule, data)
  113. self.assertEqual(self.schedule.opts, ret)
  114. # enable_job tests
  115. def test_enable_job(self):
  116. '''
  117. Tests enabling a job
  118. '''
  119. self.schedule.opts.update({'schedule': {'name': {'enabled': 'foo'}}})
  120. Schedule.enable_job(self.schedule, 'name')
  121. self.assertTrue(self.schedule.opts['schedule']['name']['enabled'])
  122. def test_enable_job_pillar(self):
  123. '''
  124. Tests ignoring enable a job from pillar
  125. '''
  126. self.schedule.opts.update({'pillar': {'schedule': {'name': {'enabled': False}}}})
  127. Schedule.enable_job(self.schedule, 'name', persist=False)
  128. self.assertFalse(self.schedule.opts['pillar']['schedule']['name']['enabled'])
  129. # disable_job tests
  130. def test_disable_job(self):
  131. '''
  132. Tests disabling a job
  133. '''
  134. self.schedule.opts.update({'schedule': {'name': {'enabled': 'foo'}}, 'pillar': {}})
  135. Schedule.disable_job(self.schedule, 'name')
  136. self.assertFalse(self.schedule.opts['schedule']['name']['enabled'])
  137. def test_disable_job_pillar(self):
  138. '''
  139. Tests ignoring disable a job in pillar
  140. '''
  141. self.schedule.opts.update({'pillar': {'schedule': {'name': {'enabled': True}}}, 'schedule': {}})
  142. Schedule.disable_job(self.schedule, 'name', persist=False)
  143. self.assertTrue(self.schedule.opts['pillar']['schedule']['name']['enabled'])
  144. # modify_job tests
  145. def test_modify_job(self):
  146. '''
  147. Tests modifying a job in the scheduler
  148. '''
  149. schedule = {'foo': 'bar'}
  150. self.schedule.opts.update({'schedule': {'name': 'baz'}, 'pillar': {}})
  151. ret = copy.deepcopy(self.schedule.opts)
  152. ret.update({'schedule': {'name': {'foo': 'bar'}}})
  153. Schedule.modify_job(self.schedule, 'name', schedule)
  154. self.assertEqual(self.schedule.opts, ret)
  155. def test_modify_job_not_exists(self):
  156. '''
  157. Tests modifying a job in the scheduler if jobs not exists
  158. '''
  159. schedule = {'foo': 'bar'}
  160. self.schedule.opts.update({'schedule': {}, 'pillar': {}})
  161. ret = copy.deepcopy(self.schedule.opts)
  162. ret.update({'schedule': {'name': {'foo': 'bar'}}})
  163. Schedule.modify_job(self.schedule, 'name', schedule)
  164. self.assertEqual(self.schedule.opts, ret)
  165. def test_modify_job_pillar(self):
  166. '''
  167. Tests ignoring modification of job from pillar
  168. '''
  169. schedule = {'foo': 'bar'}
  170. self.schedule.opts.update({'schedule': {}, 'pillar': {'schedule': {'name': 'baz'}}})
  171. ret = copy.deepcopy(self.schedule.opts)
  172. Schedule.modify_job(self.schedule, 'name', schedule, persist=False)
  173. self.assertEqual(self.schedule.opts, ret)
  174. maxDiff = None
  175. # enable_schedule tests
  176. def test_enable_schedule(self):
  177. '''
  178. Tests enabling the scheduler
  179. '''
  180. self.schedule.opts.update({'schedule': {'enabled': 'foo'}, 'pillar': {}})
  181. Schedule.enable_schedule(self.schedule)
  182. self.assertTrue(self.schedule.opts['schedule']['enabled'])
  183. # disable_schedule tests
  184. def test_disable_schedule(self):
  185. '''
  186. Tests disabling the scheduler
  187. '''
  188. self.schedule.opts.update({'schedule': {'enabled': 'foo'}, 'pillar': {}})
  189. Schedule.disable_schedule(self.schedule)
  190. self.assertFalse(self.schedule.opts['schedule']['enabled'])
  191. # reload tests
  192. def test_reload_update_schedule_key(self):
  193. '''
  194. Tests reloading the schedule from saved schedule where both the
  195. saved schedule and self.schedule.opts contain a schedule key
  196. '''
  197. saved = {'schedule': {'foo': 'bar'}}
  198. ret = copy.deepcopy(self.schedule.opts)
  199. ret.update({'schedule': {'foo': 'bar', 'hello': 'world'}})
  200. self.schedule.opts.update({'schedule': {'hello': 'world'}})
  201. Schedule.reload(self.schedule, saved)
  202. self.assertEqual(self.schedule.opts, ret)
  203. def test_reload_update_schedule_no_key(self):
  204. '''
  205. Tests reloading the schedule from saved schedule that does not
  206. contain a schedule key but self.schedule.opts does
  207. '''
  208. saved = {'foo': 'bar'}
  209. ret = copy.deepcopy(self.schedule.opts)
  210. ret.update({'schedule': {'foo': 'bar', 'hello': 'world'}})
  211. self.schedule.opts.update({'schedule': {'hello': 'world'}})
  212. Schedule.reload(self.schedule, saved)
  213. self.assertEqual(self.schedule.opts, ret)
  214. def test_reload_no_schedule_in_opts(self):
  215. '''
  216. Tests reloading the schedule from saved schedule that does not
  217. contain a schedule key and neither does self.schedule.opts
  218. '''
  219. saved = {'foo': 'bar'}
  220. ret = copy.deepcopy(self.schedule.opts)
  221. ret['schedule'] = {'foo': 'bar'}
  222. self.schedule.opts.pop('schedule', None)
  223. Schedule.reload(self.schedule, saved)
  224. self.assertEqual(self.schedule.opts, ret)
  225. def test_reload_schedule_in_saved_but_not_opts(self):
  226. '''
  227. Tests reloading the schedule from saved schedule that contains
  228. a schedule key, but self.schedule.opts does not
  229. '''
  230. saved = {'schedule': {'foo': 'bar'}}
  231. ret = copy.deepcopy(self.schedule.opts)
  232. ret['schedule'] = {'foo': 'bar'}
  233. self.schedule.opts.pop('schedule', None)
  234. Schedule.reload(self.schedule, saved)
  235. self.assertEqual(self.schedule.opts, ret)
  236. # eval tests
  237. def test_eval_schedule_is_not_dict(self):
  238. '''
  239. Tests eval if the schedule is not a dictionary
  240. '''
  241. self.schedule.opts.update({'schedule': '', 'pillar': {'schedule': {}}})
  242. self.assertRaises(ValueError, Schedule.eval, self.schedule)
  243. def test_eval_schedule_is_not_dict_in_pillar(self):
  244. '''
  245. Tests eval if the schedule from pillar is not a dictionary
  246. '''
  247. self.schedule.opts.update({'schedule': {}, 'pillar': {'schedule': ''}})
  248. self.assertRaises(ValueError, Schedule.eval, self.schedule)
  249. def test_eval_schedule_time(self):
  250. '''
  251. Tests eval if the schedule setting time is in the future
  252. '''
  253. self.schedule.opts.update({'pillar': {'schedule': {}}})
  254. self.schedule.opts.update({'schedule': {'testjob': {'function': 'test.true', 'seconds': 60}}})
  255. now = datetime.datetime.now()
  256. self.schedule.eval()
  257. self.assertTrue(self.schedule.opts['schedule']['testjob']['_next_fire_time'] > now)
  258. def test_eval_schedule_time_eval(self):
  259. '''
  260. Tests eval if the schedule setting time is in the future plus splay
  261. '''
  262. self.schedule.opts.update({'pillar': {'schedule': {}}})
  263. self.schedule.opts.update(
  264. {'schedule': {'testjob': {'function': 'test.true', 'seconds': 60, 'splay': 5}}})
  265. now = datetime.datetime.now()
  266. self.schedule.eval()
  267. self.assertTrue(self.schedule.opts['schedule']['testjob']['_splay'] - now > datetime.timedelta(seconds=60))
  268. @skipIf(not _CRON_SUPPORTED, 'croniter module not installed')
  269. def test_eval_schedule_cron(self):
  270. '''
  271. Tests eval if the schedule is defined with cron expression
  272. '''
  273. self.schedule.opts.update({'pillar': {'schedule': {}}})
  274. self.schedule.opts.update({'schedule': {'testjob': {'function': 'test.true', 'cron': '* * * * *'}}})
  275. now = datetime.datetime.now()
  276. self.schedule.eval()
  277. self.assertTrue(self.schedule.opts['schedule']['testjob']['_next_fire_time'] > now)
  278. @skipIf(not _CRON_SUPPORTED, 'croniter module not installed')
  279. def test_eval_schedule_cron_splay(self):
  280. '''
  281. Tests eval if the schedule is defined with cron expression plus splay
  282. '''
  283. self.schedule.opts.update({'pillar': {'schedule': {}}})
  284. self.schedule.opts.update(
  285. {'schedule': {'testjob': {'function': 'test.true', 'cron': '* * * * *', 'splay': 5}}})
  286. self.schedule.eval()
  287. self.assertTrue(self.schedule.opts['schedule']['testjob']['_splay'] >
  288. self.schedule.opts['schedule']['testjob']['_next_fire_time'])
  289. def test_handle_func_schedule_minion_blackout(self):
  290. '''
  291. Tests eval if the schedule from pillar is not a dictionary
  292. '''
  293. self.schedule.opts.update({'pillar': {'schedule': {}}})
  294. self.schedule.opts.update({'grains': {'minion_blackout': True}})
  295. self.schedule.opts.update(
  296. {'schedule': {'testjob': {'function': 'test.true',
  297. 'seconds': 60}}})
  298. data = {'function': 'test.true',
  299. '_next_scheduled_fire_time': datetime.datetime(2018,
  300. 11,
  301. 21,
  302. 14,
  303. 9,
  304. 53,
  305. 903438),
  306. 'run': True,
  307. 'name': 'testjob',
  308. 'seconds': 60,
  309. '_splay': None,
  310. '_seconds': 60,
  311. 'jid_include': True,
  312. 'maxrunning': 1,
  313. '_next_fire_time': datetime.datetime(2018,
  314. 11,
  315. 21,
  316. 14,
  317. 8,
  318. 53,
  319. 903438)}
  320. with patch.object(salt.utils.schedule, 'log') as log_mock:
  321. with patch('salt.utils.process.daemonize'), \
  322. patch('sys.platform', 'linux2'):
  323. self.schedule.handle_func(False, 'test.ping', data)
  324. self.assertTrue(log_mock.exception.called)