test_eval.py 32 KB

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