test_eval.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. # -*- coding: utf-8 -*-
  2. # Import Python libs
  3. from __future__ import absolute_import
  4. import copy
  5. import datetime
  6. import logging
  7. import os
  8. import random
  9. import dateutil.parser as dateutil_parser
  10. import datetime
  11. # Import Salt Testing libs
  12. from tests.support.case import ModuleCase
  13. from tests.support.mixins import SaltReturnAssertsMixin
  14. # Import Salt Testing Libs
  15. from tests.support.mock import MagicMock, patch
  16. from tests.support.unit import skipIf
  17. import tests.integration as integration
  18. # Import Salt libs
  19. import salt.utils.schedule
  20. import salt.utils.platform
  21. from salt.modules.test import ping as ping
  22. try:
  23. import croniter # pylint: disable=W0611
  24. HAS_CRONITER = True
  25. except ImportError:
  26. HAS_CRONITER = False
  27. log = logging.getLogger(__name__)
  28. ROOT_DIR = os.path.join(integration.TMP, 'schedule-unit-tests')
  29. SOCK_DIR = os.path.join(ROOT_DIR, 'test-socks')
  30. DEFAULT_CONFIG = salt.config.minion_config(None)
  31. DEFAULT_CONFIG['conf_dir'] = ROOT_DIR
  32. DEFAULT_CONFIG['root_dir'] = ROOT_DIR
  33. DEFAULT_CONFIG['sock_dir'] = SOCK_DIR
  34. DEFAULT_CONFIG['pki_dir'] = os.path.join(ROOT_DIR, 'pki')
  35. DEFAULT_CONFIG['cachedir'] = os.path.join(ROOT_DIR, 'cache')
  36. JOB_FUNCTION = 'test.ping'
  37. class SchedulerEvalTest(ModuleCase, SaltReturnAssertsMixin):
  38. '''
  39. Validate the scheduler
  40. '''
  41. def setUp(self):
  42. with patch('salt.utils.schedule.clean_proc_dir', MagicMock(return_value=None)):
  43. functions = {'test.ping': ping}
  44. self.schedule = salt.utils.schedule.Schedule(copy.deepcopy(DEFAULT_CONFIG), functions, returners={})
  45. self.schedule.opts['loop_interval'] = 1
  46. self.schedule.opts['run_schedule_jobs_in_background'] = False
  47. self.schedule.opts['grains']['whens'] = {'tea time': '11/29/2017 12:00pm'}
  48. def tearDown(self):
  49. self.schedule.reset()
  50. salt.utils.schedule.clean_proc_dir(self.schedule.opts)
  51. def test_eval(self):
  52. '''
  53. verify that scheduled job runs
  54. '''
  55. job_name = 'test_eval'
  56. job = {
  57. 'schedule': {
  58. job_name: {
  59. 'function': JOB_FUNCTION,
  60. 'when': '11/29/2017 4:00pm',
  61. }
  62. }
  63. }
  64. run_time2 = dateutil_parser.parse('11/29/2017 4:00pm')
  65. run_time1 = run_time2 - datetime.timedelta(seconds=1)
  66. # Add the job to the scheduler
  67. self.schedule.opts.update(job)
  68. # Evaluate 1 second before the run time
  69. self.schedule.eval(now=run_time1)
  70. ret = self.schedule.job_status(job_name)
  71. self.assertNotIn('_last_run', ret)
  72. # Evaluate 1 second at the run time
  73. self.schedule.eval(now=run_time2)
  74. ret = self.schedule.job_status(job_name)
  75. self.assertEqual(ret['_last_run'], run_time2)
  76. def test_eval_multiple_whens(self):
  77. '''
  78. verify that scheduled job runs
  79. '''
  80. job_name = 'test_eval_multiple_whens'
  81. job = {
  82. 'schedule': {
  83. job_name: {
  84. 'function': JOB_FUNCTION,
  85. 'when': [
  86. '11/29/2017 4:00pm',
  87. '11/29/2017 5:00pm',
  88. ],
  89. }
  90. }
  91. }
  92. if salt.utils.platform.is_darwin():
  93. job['schedule'][job_name]['dry_run'] = True
  94. run_time1 = dateutil_parser.parse('11/29/2017 4:00pm')
  95. run_time2 = dateutil_parser.parse('11/29/2017 5:00pm')
  96. # Add the job to the scheduler
  97. self.schedule.opts.update(job)
  98. # Evaluate run time1
  99. self.schedule.eval(now=run_time1)
  100. ret = self.schedule.job_status(job_name)
  101. self.assertEqual(ret['_last_run'], run_time1)
  102. # Evaluate run time2
  103. self.schedule.eval(now=run_time2)
  104. ret = self.schedule.job_status(job_name)
  105. self.assertEqual(ret['_last_run'], run_time2)
  106. def test_eval_whens(self):
  107. '''
  108. verify that scheduled job runs
  109. '''
  110. job_name = 'test_eval_whens'
  111. job = {
  112. 'schedule': {
  113. job_name: {
  114. 'function': JOB_FUNCTION,
  115. 'when': 'tea time',
  116. }
  117. }
  118. }
  119. run_time = dateutil_parser.parse('11/29/2017 12:00pm')
  120. # Add the job to the scheduler
  121. self.schedule.opts.update(job)
  122. # Evaluate run time1
  123. self.schedule.eval(now=run_time)
  124. ret = self.schedule.job_status(job_name)
  125. self.assertEqual(ret['_last_run'], run_time)
  126. def test_eval_loop_interval(self):
  127. '''
  128. verify that scheduled job runs
  129. '''
  130. job_name = 'test_eval_loop_interval'
  131. job = {
  132. 'schedule': {
  133. job_name: {
  134. 'function': JOB_FUNCTION,
  135. 'when': '11/29/2017 4:00pm',
  136. }
  137. }
  138. }
  139. # 30 second loop interval
  140. LOOP_INTERVAL = random.randint(30, 59)
  141. self.schedule.opts['loop_interval'] = LOOP_INTERVAL
  142. run_time2 = dateutil_parser.parse('11/29/2017 4:00pm')
  143. # Add the job to the scheduler
  144. self.schedule.opts.update(job)
  145. # Evaluate 1 second at the run time
  146. self.schedule.eval(now=run_time2 + datetime.timedelta(seconds=LOOP_INTERVAL))
  147. ret = self.schedule.job_status(job_name)
  148. self.assertEqual(ret['_last_run'], run_time2 + datetime.timedelta(seconds=LOOP_INTERVAL))
  149. def test_eval_multiple_whens_loop_interval(self):
  150. '''
  151. verify that scheduled job runs
  152. '''
  153. job_name = 'test_eval_multiple_whens_loop_interval'
  154. job = {
  155. 'schedule': {
  156. job_name: {
  157. 'function': JOB_FUNCTION,
  158. 'when': [
  159. '11/29/2017 4:00pm',
  160. '11/29/2017 5:00pm',
  161. ],
  162. }
  163. }
  164. }
  165. if salt.utils.platform.is_darwin():
  166. job['schedule'][job_name]['dry_run'] = True
  167. # 30 second loop interval
  168. LOOP_INTERVAL = random.randint(30, 59)
  169. self.schedule.opts['loop_interval'] = LOOP_INTERVAL
  170. run_time1 = dateutil_parser.parse('11/29/2017 4:00pm') + datetime.timedelta(seconds=LOOP_INTERVAL)
  171. run_time2 = dateutil_parser.parse('11/29/2017 5:00pm') + datetime.timedelta(seconds=LOOP_INTERVAL)
  172. # Add the job to the scheduler
  173. self.schedule.opts.update(job)
  174. # Evaluate 1 second at the run time
  175. self.schedule.eval(now=run_time1)
  176. ret = self.schedule.job_status(job_name)
  177. self.assertEqual(ret['_last_run'], run_time1)
  178. # Evaluate 1 second at the run time
  179. self.schedule.eval(now=run_time2)
  180. ret = self.schedule.job_status(job_name)
  181. self.assertEqual(ret['_last_run'], run_time2)
  182. def test_eval_once(self):
  183. '''
  184. verify that scheduled job runs
  185. '''
  186. job_name = 'test_once'
  187. job = {
  188. 'schedule': {
  189. job_name: {
  190. 'function': JOB_FUNCTION,
  191. 'once': '2017-12-13T13:00:00',
  192. }
  193. }
  194. }
  195. run_time = dateutil_parser.parse('12/13/2017 1:00pm')
  196. # Add the job to the scheduler
  197. self.schedule.opts['schedule'] = {}
  198. self.schedule.opts.update(job)
  199. # Evaluate 1 second at the run time
  200. self.schedule.eval(now=run_time)
  201. ret = self.schedule.job_status(job_name)
  202. self.assertEqual(ret['_last_run'], run_time)
  203. def test_eval_once_loop_interval(self):
  204. '''
  205. verify that scheduled job runs
  206. '''
  207. job_name = 'test_eval_once_loop_interval'
  208. job = {
  209. 'schedule': {
  210. job_name: {
  211. 'function': JOB_FUNCTION,
  212. 'once': '2017-12-13T13:00:00',
  213. }
  214. }
  215. }
  216. # Randomn second loop interval
  217. LOOP_INTERVAL = random.randint(0, 59)
  218. self.schedule.opts['loop_interval'] = LOOP_INTERVAL
  219. # Run the job at the right plus LOOP_INTERVAL
  220. run_time = dateutil_parser.parse('12/13/2017 1:00pm') + datetime.timedelta(seconds=LOOP_INTERVAL)
  221. # Add the job to the scheduler
  222. self.schedule.opts.update(job)
  223. # Evaluate at the run time
  224. self.schedule.eval(now=run_time)
  225. ret = self.schedule.job_status(job_name)
  226. self.assertEqual(ret['_last_run'], run_time)
  227. @skipIf(not HAS_CRONITER, 'Cannot find croniter python module')
  228. def test_eval_cron(self):
  229. '''
  230. verify that scheduled job runs
  231. '''
  232. job_name = 'test_eval_cron'
  233. job = {
  234. 'schedule': {
  235. job_name: {
  236. 'function': JOB_FUNCTION,
  237. 'cron': '0 16 29 11 *',
  238. }
  239. }
  240. }
  241. # Add the job to the scheduler
  242. self.schedule.opts.update(job)
  243. run_time = dateutil_parser.parse('11/29/2017 4:00pm')
  244. with patch('croniter.croniter.get_next', MagicMock(return_value=run_time)):
  245. self.schedule.eval(now=run_time)
  246. ret = self.schedule.job_status(job_name)
  247. self.assertEqual(ret['_last_run'], run_time)
  248. @skipIf(not HAS_CRONITER, 'Cannot find croniter python module')
  249. def test_eval_cron_loop_interval(self):
  250. '''
  251. verify that scheduled job runs
  252. '''
  253. job_name = 'test_eval_cron_loop_interval'
  254. job = {
  255. 'schedule': {
  256. job_name: {
  257. 'function': JOB_FUNCTION,
  258. 'cron': '0 16 29 11 *',
  259. }
  260. }
  261. }
  262. # Randomn second loop interval
  263. LOOP_INTERVAL = random.randint(0, 59)
  264. self.schedule.opts['loop_interval'] = LOOP_INTERVAL
  265. # Add the job to the scheduler
  266. self.schedule.opts.update(job)
  267. run_time = dateutil_parser.parse('11/29/2017 4:00pm')
  268. with patch('croniter.croniter.get_next', MagicMock(return_value=run_time)):
  269. self.schedule.eval(now=run_time)
  270. ret = self.schedule.job_status(job_name)
  271. self.assertEqual(ret['_last_run'], run_time)
  272. def test_eval_until(self):
  273. '''
  274. verify that scheduled job is skipped once the current
  275. time reaches the specified until time
  276. '''
  277. job_name = 'test_eval_until'
  278. job = {
  279. 'schedule': {
  280. job_name: {
  281. 'function': JOB_FUNCTION,
  282. 'hours': '1',
  283. 'until': '11/29/2017 5:00pm',
  284. }
  285. }
  286. }
  287. if salt.utils.platform.is_darwin():
  288. job['schedule'][job_name]['dry_run'] = True
  289. # Add job to schedule
  290. self.schedule.delete_job('test_eval_until')
  291. self.schedule.opts.update(job)
  292. # eval at 2:00pm to prime, simulate minion start up.
  293. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  294. self.schedule.eval(now=run_time)
  295. ret = self.schedule.job_status(job_name)
  296. # eval at 3:00pm, will run.
  297. run_time = dateutil_parser.parse('11/29/2017 3:00pm')
  298. self.schedule.eval(now=run_time)
  299. ret = self.schedule.job_status(job_name)
  300. self.assertEqual(ret['_last_run'], run_time)
  301. # eval at 4:00pm, will run.
  302. run_time = dateutil_parser.parse('11/29/2017 4:00pm')
  303. self.schedule.eval(now=run_time)
  304. ret = self.schedule.job_status(job_name)
  305. self.assertEqual(ret['_last_run'], run_time)
  306. # eval at 5:00pm, will not run
  307. run_time = dateutil_parser.parse('11/29/2017 5:00pm')
  308. self.schedule.eval(now=run_time)
  309. ret = self.schedule.job_status(job_name)
  310. self.assertEqual(ret['_skip_reason'], 'until_passed')
  311. self.assertEqual(ret['_skipped_time'], run_time)
  312. def test_eval_after(self):
  313. '''
  314. verify that scheduled job is skipped until after the specified
  315. time has been reached.
  316. '''
  317. job_name = 'test_eval_after'
  318. job = {
  319. 'schedule': {
  320. job_name: {
  321. 'function': JOB_FUNCTION,
  322. 'hours': '1',
  323. 'after': '11/29/2017 5:00pm',
  324. }
  325. }
  326. }
  327. # Add job to schedule
  328. self.schedule.opts.update(job)
  329. # eval at 2:00pm to prime, simulate minion start up.
  330. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  331. self.schedule.eval(now=run_time)
  332. ret = self.schedule.job_status(job_name)
  333. # eval at 3:00pm, will not run.
  334. run_time = dateutil_parser.parse('11/29/2017 3:00pm')
  335. self.schedule.eval(now=run_time)
  336. ret = self.schedule.job_status(job_name)
  337. self.assertEqual(ret['_skip_reason'], 'after_not_passed')
  338. self.assertEqual(ret['_skipped_time'], run_time)
  339. # eval at 4:00pm, will not run.
  340. run_time = dateutil_parser.parse('11/29/2017 4:00pm')
  341. self.schedule.eval(now=run_time)
  342. ret = self.schedule.job_status(job_name)
  343. self.assertEqual(ret['_skip_reason'], 'after_not_passed')
  344. self.assertEqual(ret['_skipped_time'], run_time)
  345. # eval at 5:00pm, will not run
  346. run_time = dateutil_parser.parse('11/29/2017 5:00pm')
  347. self.schedule.eval(now=run_time)
  348. ret = self.schedule.job_status(job_name)
  349. self.assertEqual(ret['_skip_reason'], 'after_not_passed')
  350. self.assertEqual(ret['_skipped_time'], run_time)
  351. # eval at 6:00pm, will run
  352. run_time = dateutil_parser.parse('11/29/2017 6:00pm')
  353. self.schedule.eval(now=run_time)
  354. ret = self.schedule.job_status(job_name)
  355. self.assertEqual(ret['_last_run'], run_time)
  356. def test_eval_enabled(self):
  357. '''
  358. verify that scheduled job does not run
  359. '''
  360. job_name = 'test_eval_enabled'
  361. job = {
  362. 'schedule': {
  363. 'enabled': True,
  364. job_name: {
  365. 'function': JOB_FUNCTION,
  366. 'when': '11/29/2017 4:00pm',
  367. }
  368. }
  369. }
  370. run_time1 = dateutil_parser.parse('11/29/2017 4:00pm')
  371. # Add the job to the scheduler
  372. self.schedule.opts.update(job)
  373. # Evaluate 1 second at the run time
  374. self.schedule.eval(now=run_time1)
  375. ret = self.schedule.job_status(job_name)
  376. self.assertEqual(ret['_last_run'], run_time1)
  377. def test_eval_enabled_key(self):
  378. '''
  379. verify that scheduled job runs
  380. when the enabled key is in place
  381. https://github.com/saltstack/salt/issues/47695
  382. '''
  383. job_name = 'test_eval_enabled_key'
  384. job = {
  385. 'schedule': {
  386. 'enabled': True,
  387. job_name: {
  388. 'function': JOB_FUNCTION,
  389. 'when': '11/29/2017 4:00pm',
  390. }
  391. }
  392. }
  393. run_time2 = dateutil_parser.parse('11/29/2017 4:00pm')
  394. run_time1 = run_time2 - datetime.timedelta(seconds=1)
  395. # Add the job to the scheduler
  396. self.schedule.opts.update(job)
  397. # Evaluate 1 second before the run time
  398. self.schedule.eval(now=run_time1)
  399. ret = self.schedule.job_status('test_eval_enabled_key')
  400. self.assertNotIn('_last_run', ret)
  401. # Evaluate 1 second at the run time
  402. self.schedule.eval(now=run_time2)
  403. ret = self.schedule.job_status('test_eval_enabled_key')
  404. self.assertEqual(ret['_last_run'], run_time2)
  405. def test_eval_disabled(self):
  406. '''
  407. verify that scheduled job does not run
  408. '''
  409. job_name = 'test_eval_disabled'
  410. job = {
  411. 'schedule': {
  412. 'enabled': False,
  413. job_name: {
  414. 'function': JOB_FUNCTION,
  415. 'when': '11/29/2017 4:00pm',
  416. }
  417. }
  418. }
  419. run_time1 = dateutil_parser.parse('11/29/2017 4:00pm')
  420. # Add the job to the scheduler
  421. self.schedule.opts.update(job)
  422. # Evaluate 1 second at the run time
  423. self.schedule.eval(now=run_time1)
  424. ret = self.schedule.job_status(job_name)
  425. self.assertNotIn('_last_run', ret)
  426. self.assertEqual(ret['_skip_reason'], 'disabled')
  427. def test_eval_global_disabled_job_enabled(self):
  428. '''
  429. verify that scheduled job does not run
  430. '''
  431. job_name = 'test_eval_global_disabled'
  432. job = {
  433. 'schedule': {
  434. 'enabled': False,
  435. job_name: {
  436. 'function': JOB_FUNCTION,
  437. 'when': '11/29/2017 4:00pm',
  438. 'enabled': True,
  439. }
  440. }
  441. }
  442. run_time1 = dateutil_parser.parse('11/29/2017 4:00pm')
  443. # Add the job to the scheduler
  444. self.schedule.opts.update(job)
  445. # Evaluate 1 second at the run time
  446. self.schedule.eval(now=run_time1)
  447. ret = self.schedule.job_status(job_name)
  448. self.assertNotIn('_last_run', ret)
  449. self.assertEqual(ret['_skip_reason'], 'disabled')
  450. def test_eval_run_on_start(self):
  451. '''
  452. verify that scheduled job is run when minion starts
  453. '''
  454. job_name = 'test_eval_run_on_start'
  455. job = {
  456. 'schedule': {
  457. job_name: {
  458. 'function': JOB_FUNCTION,
  459. 'hours': '1',
  460. 'run_on_start': True,
  461. }
  462. }
  463. }
  464. # Add job to schedule
  465. self.schedule.opts.update(job)
  466. # eval at 2:00pm, will run.
  467. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  468. self.schedule.eval(now=run_time)
  469. ret = self.schedule.job_status(job_name)
  470. self.assertEqual(ret['_last_run'], run_time)
  471. # eval at 3:00pm, will run.
  472. run_time = dateutil_parser.parse('11/29/2017 3:00pm')
  473. self.schedule.eval(now=run_time)
  474. ret = self.schedule.job_status(job_name)
  475. def test_eval_splay(self):
  476. '''
  477. verify that scheduled job runs with splayed time
  478. '''
  479. job_name = 'job_eval_splay'
  480. job = {
  481. 'schedule': {
  482. job_name: {
  483. 'function': JOB_FUNCTION,
  484. 'seconds': '30',
  485. 'splay': '10',
  486. }
  487. }
  488. }
  489. # Add job to schedule
  490. self.schedule.opts.update(job)
  491. with patch('random.randint', MagicMock(return_value=10)):
  492. # eval at 2:00pm to prime, simulate minion start up.
  493. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  494. self.schedule.eval(now=run_time)
  495. ret = self.schedule.job_status(job_name)
  496. # eval at 2:00:40pm, will run.
  497. run_time = dateutil_parser.parse('11/29/2017 2:00:40pm')
  498. self.schedule.eval(now=run_time)
  499. ret = self.schedule.job_status(job_name)
  500. self.assertEqual(ret['_last_run'], run_time)
  501. def test_eval_splay_range(self):
  502. '''
  503. verify that scheduled job runs with splayed time
  504. '''
  505. job_name = 'job_eval_splay_range'
  506. job = {
  507. 'schedule': {
  508. job_name: {
  509. 'function': JOB_FUNCTION,
  510. 'seconds': '30',
  511. 'splay': {'start': 5, 'end': 10},
  512. }
  513. }
  514. }
  515. # Add job to schedule
  516. self.schedule.opts.update(job)
  517. with patch('random.randint', MagicMock(return_value=10)):
  518. # eval at 2:00pm to prime, simulate minion start up.
  519. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  520. self.schedule.eval(now=run_time)
  521. ret = self.schedule.job_status(job_name)
  522. # eval at 2:00:40pm, will run.
  523. run_time = dateutil_parser.parse('11/29/2017 2:00:40pm')
  524. self.schedule.eval(now=run_time)
  525. ret = self.schedule.job_status(job_name)
  526. self.assertEqual(ret['_last_run'], run_time)
  527. def test_eval_splay_global(self):
  528. '''
  529. verify that scheduled job runs with splayed time
  530. '''
  531. job_name = 'job_eval_splay_global'
  532. job = {
  533. 'schedule': {
  534. 'splay': {'start': 5, 'end': 10},
  535. job_name: {
  536. 'function': JOB_FUNCTION,
  537. 'seconds': '30',
  538. }
  539. }
  540. }
  541. # Add job to schedule
  542. self.schedule.opts.update(job)
  543. with patch('random.randint', MagicMock(return_value=10)):
  544. # eval at 2:00pm to prime, simulate minion start up.
  545. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  546. self.schedule.eval(now=run_time)
  547. ret = self.schedule.job_status(job_name)
  548. # eval at 2:00:40pm, will run.
  549. run_time = dateutil_parser.parse('11/29/2017 2:00:40pm')
  550. self.schedule.eval(now=run_time)
  551. ret = self.schedule.job_status(job_name)
  552. self.assertEqual(ret['_last_run'], run_time)
  553. def test_eval_seconds(self):
  554. '''
  555. verify that scheduled job run mutiple times with seconds
  556. '''
  557. job_name = 'job_eval_seconds'
  558. job = {
  559. 'schedule': {
  560. job_name: {
  561. 'function': JOB_FUNCTION,
  562. 'seconds': '30',
  563. }
  564. }
  565. }
  566. if salt.utils.platform.is_darwin():
  567. job['schedule'][job_name]['dry_run'] = True
  568. # Add job to schedule
  569. self.schedule.opts.update(job)
  570. # eval at 2:00pm to prime, simulate minion start up.
  571. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  572. next_run_time = run_time + datetime.timedelta(seconds=30)
  573. self.schedule.eval(now=run_time)
  574. ret = self.schedule.job_status(job_name)
  575. self.assertEqual(ret['_next_fire_time'], next_run_time)
  576. # eval at 2:00:01pm, will not run.
  577. run_time = dateutil_parser.parse('11/29/2017 2:00:01pm')
  578. self.schedule.eval(now=run_time)
  579. ret = self.schedule.job_status(job_name)
  580. self.assertNotIn('_last_run', ret)
  581. self.assertEqual(ret['_next_fire_time'], next_run_time)
  582. # eval at 2:00:30pm, will run.
  583. run_time = dateutil_parser.parse('11/29/2017 2:00:30pm')
  584. next_run_time = run_time + datetime.timedelta(seconds=30)
  585. self.schedule.eval(now=run_time)
  586. ret = self.schedule.job_status(job_name)
  587. self.assertEqual(ret['_last_run'], run_time)
  588. self.assertEqual(ret['_next_fire_time'], next_run_time)
  589. # eval at 2:01:00pm, will run.
  590. run_time = dateutil_parser.parse('11/29/2017 2:01:00pm')
  591. next_run_time = run_time + datetime.timedelta(seconds=30)
  592. self.schedule.eval(now=run_time)
  593. ret = self.schedule.job_status(job_name)
  594. self.assertEqual(ret['_last_run'], run_time)
  595. self.assertEqual(ret['_next_fire_time'], next_run_time)
  596. # eval at 2:01:30pm, will run.
  597. run_time = dateutil_parser.parse('11/29/2017 2:01:30pm')
  598. next_run_time = run_time + datetime.timedelta(seconds=30)
  599. self.schedule.eval(now=run_time)
  600. ret = self.schedule.job_status(job_name)
  601. self.assertEqual(ret['_last_run'], run_time)
  602. self.assertEqual(ret['_next_fire_time'], next_run_time)
  603. def test_eval_minutes(self):
  604. '''
  605. verify that scheduled job run mutiple times with minutes
  606. '''
  607. job_name = 'job_eval_minutes'
  608. job = {
  609. 'schedule': {
  610. job_name: {
  611. 'function': JOB_FUNCTION,
  612. 'minutes': '30',
  613. }
  614. }
  615. }
  616. if salt.utils.platform.is_darwin():
  617. job['schedule'][job_name]['dry_run'] = True
  618. # Add job to schedule
  619. self.schedule.opts.update(job)
  620. # eval at 2:00pm to prime, simulate minion start up.
  621. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  622. next_run_time = run_time + datetime.timedelta(minutes=30)
  623. self.schedule.eval(now=run_time)
  624. ret = self.schedule.job_status(job_name)
  625. self.assertEqual(ret['_next_fire_time'], next_run_time)
  626. # eval at 2:00:01pm, will not run.
  627. run_time = dateutil_parser.parse('11/29/2017 2:00:01pm')
  628. self.schedule.eval(now=run_time)
  629. ret = self.schedule.job_status(job_name)
  630. self.assertNotIn('_last_run', ret)
  631. self.assertEqual(ret['_next_fire_time'], next_run_time)
  632. # eval at 2:30:00pm, will run.
  633. run_time = dateutil_parser.parse('11/29/2017 2:30:00pm')
  634. self.schedule.eval(now=run_time)
  635. ret = self.schedule.job_status(job_name)
  636. self.assertEqual(ret['_last_run'], run_time)
  637. # eval at 3:00:00pm, will run.
  638. run_time = dateutil_parser.parse('11/29/2017 3:00:00pm')
  639. self.schedule.eval(now=run_time)
  640. ret = self.schedule.job_status(job_name)
  641. self.assertEqual(ret['_last_run'], run_time)
  642. # eval at 3:30:00pm, will run.
  643. run_time = dateutil_parser.parse('11/29/2017 3:30:00pm')
  644. self.schedule.eval(now=run_time)
  645. ret = self.schedule.job_status(job_name)
  646. self.assertEqual(ret['_last_run'], run_time)
  647. def test_eval_hours(self):
  648. '''
  649. verify that scheduled job run mutiple times with hours
  650. '''
  651. job_name = 'job_eval_hours'
  652. job = {
  653. 'schedule': {
  654. job_name: {
  655. 'function': JOB_FUNCTION,
  656. 'hours': '2',
  657. }
  658. }
  659. }
  660. if salt.utils.platform.is_darwin():
  661. job['schedule'][job_name]['dry_run'] = True
  662. # Add job to schedule
  663. self.schedule.opts.update(job)
  664. # eval at 2:00pm to prime, simulate minion start up.
  665. run_time = dateutil_parser.parse('11/29/2017 2:00pm')
  666. next_run_time = run_time + datetime.timedelta(hours=2)
  667. self.schedule.eval(now=run_time)
  668. ret = self.schedule.job_status(job_name)
  669. self.assertEqual(ret['_next_fire_time'], next_run_time)
  670. # eval at 2:00:01pm, will not run.
  671. run_time = dateutil_parser.parse('11/29/2017 2:00:01pm')
  672. self.schedule.eval(now=run_time)
  673. ret = self.schedule.job_status(job_name)
  674. self.assertNotIn('_last_run', ret)
  675. self.assertEqual(ret['_next_fire_time'], next_run_time)
  676. # eval at 4:00:00pm, will run.
  677. run_time = dateutil_parser.parse('11/29/2017 4:00:00pm')
  678. self.schedule.eval(now=run_time)
  679. ret = self.schedule.job_status(job_name)
  680. self.assertEqual(ret['_last_run'], run_time)
  681. # eval at 6:00:00pm, will run.
  682. run_time = dateutil_parser.parse('11/29/2017 6:00:00pm')
  683. self.schedule.eval(now=run_time)
  684. ret = self.schedule.job_status(job_name)
  685. self.assertEqual(ret['_last_run'], run_time)
  686. # eval at 8:00:00pm, will run.
  687. run_time = dateutil_parser.parse('11/29/2017 8:00:00pm')
  688. self.schedule.eval(now=run_time)
  689. ret = self.schedule.job_status(job_name)
  690. self.assertEqual(ret['_last_run'], run_time)
  691. def test_eval_days(self):
  692. '''
  693. verify that scheduled job run mutiple times with days
  694. '''
  695. job_name = 'job_eval_days'
  696. job = {
  697. 'schedule': {
  698. job_name: {
  699. 'function': JOB_FUNCTION,
  700. 'days': '2',
  701. 'dry_run': True
  702. }
  703. }
  704. }
  705. if salt.utils.platform.is_darwin():
  706. job['schedule'][job_name]['dry_run'] = True
  707. # Add job to schedule
  708. self.schedule.opts.update(job)
  709. # eval at 11/23/2017 2:00pm to prime, simulate minion start up.
  710. run_time = dateutil_parser.parse('11/23/2017 2:00pm')
  711. next_run_time = run_time + datetime.timedelta(days=2)
  712. self.schedule.eval(now=run_time)
  713. ret = self.schedule.job_status(job_name)
  714. self.assertEqual(ret['_next_fire_time'], next_run_time)
  715. # eval at 11/25/2017 2:00:00pm, will run.
  716. run_time = dateutil_parser.parse('11/25/2017 2:00:00pm')
  717. next_run_time = run_time + datetime.timedelta(days=2)
  718. self.schedule.eval(now=run_time)
  719. ret = self.schedule.job_status(job_name)
  720. self.assertEqual(ret['_last_run'], run_time)
  721. self.assertEqual(ret['_next_fire_time'], next_run_time)
  722. # eval at 11/26/2017 2:00:00pm, will not run.
  723. run_time = dateutil_parser.parse('11/26/2017 2:00:00pm')
  724. last_run_time = run_time - datetime.timedelta(days=1)
  725. self.schedule.eval(now=run_time)
  726. ret = self.schedule.job_status(job_name)
  727. self.assertEqual(ret['_last_run'], last_run_time)
  728. self.assertEqual(ret['_next_fire_time'], next_run_time)
  729. # eval at 11/27/2017 2:00:00pm, will run.
  730. run_time = dateutil_parser.parse('11/27/2017 2:00:00pm')
  731. next_run_time = run_time + datetime.timedelta(days=2)
  732. self.schedule.eval(now=run_time)
  733. ret = self.schedule.job_status(job_name)
  734. self.assertEqual(ret['_last_run'], run_time)
  735. self.assertEqual(ret['_next_fire_time'], next_run_time)
  736. # eval at 11/28/2017 2:00:00pm, will not run.
  737. run_time = dateutil_parser.parse('11/28/2017 2:00:00pm')
  738. last_run_time = run_time - datetime.timedelta(days=1)
  739. self.schedule.eval(now=run_time)
  740. ret = self.schedule.job_status(job_name)
  741. self.assertEqual(ret['_last_run'], last_run_time)
  742. self.assertEqual(ret['_next_fire_time'], next_run_time)
  743. # eval at 11/29/2017 2:00:00pm, will run.
  744. run_time = dateutil_parser.parse('11/29/2017 2:00:00pm')
  745. next_run_time = run_time + datetime.timedelta(days=2)
  746. self.schedule.eval(now=run_time)
  747. ret = self.schedule.job_status(job_name)
  748. self.assertEqual(ret['_last_run'], run_time)
  749. self.assertEqual(ret['_next_fire_time'], next_run_time)
  750. def test_eval_when_splay(self):
  751. '''
  752. verify that scheduled job runs
  753. '''
  754. job_name = 'test_eval_when_splay'
  755. splay = 300
  756. job = {
  757. 'schedule': {
  758. job_name: {
  759. 'function': JOB_FUNCTION,
  760. 'when': '11/29/2017 4:00pm',
  761. 'splay': splay
  762. }
  763. }
  764. }
  765. run_time1 = dateutil_parser.parse('11/29/2017 4:00pm')
  766. run_time2 = run_time1 + datetime.timedelta(seconds=splay)
  767. # Add the job to the scheduler
  768. self.schedule.opts.update(job)
  769. with patch('random.randint', MagicMock(return_value=splay)):
  770. # Evaluate to prime
  771. run_time = dateutil_parser.parse('11/29/2017 3:00pm')
  772. self.schedule.eval(now=run_time)
  773. ret = self.schedule.job_status(job_name)
  774. # Evaluate at expected runtime1, should not run
  775. self.schedule.eval(now=run_time1)
  776. ret = self.schedule.job_status(job_name)
  777. self.assertNotIn('_last_run', ret)
  778. # Evaluate at expected runtime2, should run
  779. self.schedule.eval(now=run_time2)
  780. ret = self.schedule.job_status(job_name)
  781. self.assertEqual(ret['_last_run'], run_time2)
  782. def test_eval_when_splay_in_past(self):
  783. '''
  784. verify that scheduled job runs
  785. '''
  786. job_name = 'test_eval_when_splay_in_past'
  787. splay = 300
  788. job = {
  789. 'schedule': {
  790. job_name: {
  791. 'function': JOB_FUNCTION,
  792. 'when': ['11/29/2017 6:00am'],
  793. 'splay': splay
  794. }
  795. }
  796. }
  797. run_time1 = dateutil_parser.parse('11/29/2017 4:00pm')
  798. # Add the job to the scheduler
  799. self.schedule.opts.update(job)
  800. # Evaluate to prime
  801. run_time = dateutil_parser.parse('11/29/2017 3:00pm')
  802. self.schedule.eval(now=run_time)
  803. ret = self.schedule.job_status(job_name)
  804. # Evaluate at expected runtime1, should not run
  805. # and _next_fire_time should be None
  806. self.schedule.eval(now=run_time1)
  807. ret = self.schedule.job_status(job_name)
  808. self.assertNotIn('_last_run', ret)
  809. self.assertEqual(ret['_next_fire_time'], None)