test_eval.py 31 KB

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