1
0

test_eval.py 31 KB

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