You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by ke...@apache.org on 2014/05/15 04:34:41 UTC
[1/5] CronScheduler based on Quartz
Repository: incubator-aurora
Updated Branches:
refs/heads/master 2759696ca -> 3361dbedf
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/resources/org/apache/aurora/scheduler/cron/expected-predictions.json
----------------------------------------------------------------------
diff --git a/src/test/resources/org/apache/aurora/scheduler/cron/expected-predictions.json b/src/test/resources/org/apache/aurora/scheduler/cron/expected-predictions.json
new file mode 100644
index 0000000..dced8b4
--- /dev/null
+++ b/src/test/resources/org/apache/aurora/scheduler/cron/expected-predictions.json
@@ -0,0 +1,3332 @@
+[
+ {
+ "schedule": "0 20 * * 1",
+ "triggerTimes": [
+ 417600000,
+ 1022400000,
+ 1627200000,
+ 2232000000,
+ 2836800000,
+ 3441600000,
+ 4046400000,
+ 4651200000,
+ 5256000000,
+ 5860800000
+ ]
+ },
+ {
+ "schedule": "11 * * * *",
+ "triggerTimes": [
+ 660000,
+ 4260000,
+ 7860000,
+ 11460000,
+ 15060000,
+ 18660000,
+ 22260000,
+ 25860000,
+ 29460000,
+ 33060000
+ ]
+ },
+ {
+ "schedule": "04 02 * * *",
+ "triggerTimes": [
+ 7440000,
+ 93840000,
+ 180240000,
+ 266640000,
+ 353040000,
+ 439440000,
+ 525840000,
+ 612240000,
+ 698640000,
+ 785040000
+ ]
+ },
+ {
+ "schedule": "09 22 * * *",
+ "triggerTimes": [
+ 79740000,
+ 166140000,
+ 252540000,
+ 338940000,
+ 425340000,
+ 511740000,
+ 598140000,
+ 684540000,
+ 770940000,
+ 857340000
+ ]
+ },
+ {
+ "schedule": "1-56/5 * * * *",
+ "triggerTimes": [
+ 60000,
+ 360000,
+ 660000,
+ 960000,
+ 1260000,
+ 1560000,
+ 1860000,
+ 2160000,
+ 2460000,
+ 2760000
+ ]
+ },
+ {
+ "schedule": "05 02,08,12 * * *",
+ "triggerTimes": [
+ 7500000,
+ 29100000,
+ 43500000,
+ 93900000,
+ 115500000,
+ 129900000,
+ 180300000,
+ 201900000,
+ 216300000,
+ 266700000
+ ]
+ },
+ {
+ "schedule": "26 * * * *",
+ "triggerTimes": [
+ 1560000,
+ 5160000,
+ 8760000,
+ 12360000,
+ 15960000,
+ 19560000,
+ 23160000,
+ 26760000,
+ 30360000,
+ 33960000
+ ]
+ },
+ {
+ "schedule": "3,43 * * * *",
+ "triggerTimes": [
+ 180000,
+ 2580000,
+ 3780000,
+ 6180000,
+ 7380000,
+ 9780000,
+ 10980000,
+ 13380000,
+ 14580000,
+ 16980000
+ ]
+ },
+ {
+ "schedule": "0 17-23 * * 1-5",
+ "triggerTimes": [
+ 61200000,
+ 64800000,
+ 68400000,
+ 72000000,
+ 75600000,
+ 79200000,
+ 82800000,
+ 147600000,
+ 151200000,
+ 154800000
+ ]
+ },
+ {
+ "schedule": "0 0,12 * * *",
+ "triggerTimes": [
+ 43200000,
+ 86400000,
+ 129600000,
+ 172800000,
+ 216000000,
+ 259200000,
+ 302400000,
+ 345600000,
+ 388800000,
+ 432000000
+ ]
+ },
+ {
+ "schedule": "10 02,08,12 * * *",
+ "triggerTimes": [
+ 7800000,
+ 29400000,
+ 43800000,
+ 94200000,
+ 115800000,
+ 130200000,
+ 180600000,
+ 202200000,
+ 216600000,
+ 267000000
+ ]
+ },
+ {
+ "schedule": "50 */4 * * *",
+ "triggerTimes": [
+ 3000000,
+ 17400000,
+ 31800000,
+ 46200000,
+ 60600000,
+ 75000000,
+ 89400000,
+ 103800000,
+ 118200000,
+ 132600000
+ ]
+ },
+ {
+ "schedule": "10 02,08,14,20 * * *",
+ "triggerTimes": [
+ 7800000,
+ 29400000,
+ 51000000,
+ 72600000,
+ 94200000,
+ 115800000,
+ 137400000,
+ 159000000,
+ 180600000,
+ 202200000
+ ]
+ },
+ {
+ "schedule": "0 */6 * * *",
+ "triggerTimes": [
+ 21600000,
+ 43200000,
+ 64800000,
+ 86400000,
+ 108000000,
+ 129600000,
+ 151200000,
+ 172800000,
+ 194400000,
+ 216000000
+ ]
+ },
+ {
+ "schedule": "* * * * *",
+ "triggerTimes": [
+ 60000,
+ 120000,
+ 180000,
+ 240000,
+ 300000,
+ 360000,
+ 420000,
+ 480000,
+ 540000,
+ 600000
+ ]
+ },
+ {
+ "schedule": "30 15 * * *,",
+ "triggerTimes": [
+ 55800000,
+ 142200000,
+ 228600000,
+ 315000000,
+ 401400000,
+ 487800000,
+ 574200000,
+ 660600000,
+ 747000000,
+ 833400000
+ ]
+ },
+ {
+ "schedule": "00 11 * * *",
+ "triggerTimes": [
+ 39600000,
+ 126000000,
+ 212400000,
+ 298800000,
+ 385200000,
+ 471600000,
+ 558000000,
+ 644400000,
+ 730800000,
+ 817200000
+ ]
+ },
+ {
+ "schedule": "55 06 * * *",
+ "triggerTimes": [
+ 24900000,
+ 111300000,
+ 197700000,
+ 284100000,
+ 370500000,
+ 456900000,
+ 543300000,
+ 629700000,
+ 716100000,
+ 802500000
+ ]
+ },
+ {
+ "schedule": "0 4 * * *",
+ "triggerTimes": [
+ 14400000,
+ 100800000,
+ 187200000,
+ 273600000,
+ 360000000,
+ 446400000,
+ 532800000,
+ 619200000,
+ 705600000,
+ 792000000
+ ]
+ },
+ {
+ "schedule": "55 */1 * * *",
+ "triggerTimes": [
+ 3300000,
+ 6900000,
+ 10500000,
+ 14100000,
+ 17700000,
+ 21300000,
+ 24900000,
+ 28500000,
+ 32100000,
+ 35700000
+ ]
+ },
+ {
+ "schedule": "15 */3 * * *",
+ "triggerTimes": [
+ 900000,
+ 11700000,
+ 22500000,
+ 33300000,
+ 44100000,
+ 54900000,
+ 65700000,
+ 76500000,
+ 87300000,
+ 98100000
+ ]
+ },
+ {
+ "schedule": "42 8,12,16 * * *",
+ "triggerTimes": [
+ 31320000,
+ 45720000,
+ 60120000,
+ 117720000,
+ 132120000,
+ 146520000,
+ 204120000,
+ 218520000,
+ 232920000,
+ 290520000
+ ]
+ },
+ {
+ "schedule": "23 * * * *",
+ "triggerTimes": [
+ 1380000,
+ 4980000,
+ 8580000,
+ 12180000,
+ 15780000,
+ 19380000,
+ 22980000,
+ 26580000,
+ 30180000,
+ 33780000
+ ]
+ },
+ {
+ "schedule": "10 16 * * *",
+ "triggerTimes": [
+ 58200000,
+ 144600000,
+ 231000000,
+ 317400000,
+ 403800000,
+ 490200000,
+ 576600000,
+ 663000000,
+ 749400000,
+ 835800000
+ ]
+ },
+ {
+ "schedule": "*/30 * * * *",
+ "triggerTimes": [
+ 1800000,
+ 3600000,
+ 5400000,
+ 7200000,
+ 9000000,
+ 10800000,
+ 12600000,
+ 14400000,
+ 16200000,
+ 18000000
+ ]
+ },
+ {
+ "schedule": "20 */3 * * *",
+ "triggerTimes": [
+ 1200000,
+ 12000000,
+ 22800000,
+ 33600000,
+ 44400000,
+ 55200000,
+ 66000000,
+ 76800000,
+ 87600000,
+ 98400000
+ ]
+ },
+ {
+ "schedule": "8 6,12,18 * * *",
+ "triggerTimes": [
+ 22080000,
+ 43680000,
+ 65280000,
+ 108480000,
+ 130080000,
+ 151680000,
+ 194880000,
+ 216480000,
+ 238080000,
+ 281280000
+ ]
+ },
+ {
+ "schedule": "30 7,12,22 * * *",
+ "triggerTimes": [
+ 27000000,
+ 45000000,
+ 81000000,
+ 113400000,
+ 131400000,
+ 167400000,
+ 199800000,
+ 217800000,
+ 253800000,
+ 286200000
+ ]
+ },
+ {
+ "schedule": "0 0 12 * *",
+ "triggerTimes": [
+ 950400000,
+ 3628800000,
+ 6048000000,
+ 8726400000,
+ 11318400000,
+ 13996800000,
+ 16588800000,
+ 19267200000,
+ 21945600000,
+ 24537600000
+ ]
+ },
+ {
+ "schedule": "17 5,8,13,16,19 * * *",
+ "triggerTimes": [
+ 19020000,
+ 29820000,
+ 47820000,
+ 58620000,
+ 69420000,
+ 105420000,
+ 116220000,
+ 134220000,
+ 145020000,
+ 155820000
+ ]
+ },
+ {
+ "schedule": "27 8,20 * * *",
+ "triggerTimes": [
+ 30420000,
+ 73620000,
+ 116820000,
+ 160020000,
+ 203220000,
+ 246420000,
+ 289620000,
+ 332820000,
+ 376020000,
+ 419220000
+ ]
+ },
+ {
+ "schedule": "15 */6 * * *",
+ "triggerTimes": [
+ 900000,
+ 22500000,
+ 44100000,
+ 65700000,
+ 87300000,
+ 108900000,
+ 130500000,
+ 152100000,
+ 173700000,
+ 195300000
+ ]
+ },
+ {
+ "schedule": "01 15 * * *",
+ "triggerTimes": [
+ 54060000,
+ 140460000,
+ 226860000,
+ 313260000,
+ 399660000,
+ 486060000,
+ 572460000,
+ 658860000,
+ 745260000,
+ 831660000
+ ]
+ },
+ {
+ "schedule": "0 18 * * *",
+ "triggerTimes": [
+ 64800000,
+ 151200000,
+ 237600000,
+ 324000000,
+ 410400000,
+ 496800000,
+ 583200000,
+ 669600000,
+ 756000000,
+ 842400000
+ ]
+ },
+ {
+ "schedule": "24 * * * *",
+ "triggerTimes": [
+ 1440000,
+ 5040000,
+ 8640000,
+ 12240000,
+ 15840000,
+ 19440000,
+ 23040000,
+ 26640000,
+ 30240000,
+ 33840000
+ ]
+ },
+ {
+ "schedule": "18 00 * * *",
+ "triggerTimes": [
+ 1080000,
+ 87480000,
+ 173880000,
+ 260280000,
+ 346680000,
+ 433080000,
+ 519480000,
+ 605880000,
+ 692280000,
+ 778680000
+ ]
+ },
+ {
+ "schedule": "0 16 * * *",
+ "triggerTimes": [
+ 57600000,
+ 144000000,
+ 230400000,
+ 316800000,
+ 403200000,
+ 489600000,
+ 576000000,
+ 662400000,
+ 748800000,
+ 835200000
+ ]
+ },
+ {
+ "schedule": "45 5 * * *",
+ "triggerTimes": [
+ 20700000,
+ 107100000,
+ 193500000,
+ 279900000,
+ 366300000,
+ 452700000,
+ 539100000,
+ 625500000,
+ 711900000,
+ 798300000
+ ]
+ },
+ {
+ "schedule": "0 18 * * 4",
+ "triggerTimes": [
+ 64800000,
+ 669600000,
+ 1274400000,
+ 1879200000,
+ 2484000000,
+ 3088800000,
+ 3693600000,
+ 4298400000,
+ 4903200000,
+ 5508000000
+ ]
+ },
+ {
+ "schedule": "30 19 * * *",
+ "triggerTimes": [
+ 70200000,
+ 156600000,
+ 243000000,
+ 329400000,
+ 415800000,
+ 502200000,
+ 588600000,
+ 675000000,
+ 761400000,
+ 847800000
+ ]
+ },
+ {
+ "schedule": "0 13 * * 2",
+ "triggerTimes": [
+ 478800000,
+ 1083600000,
+ 1688400000,
+ 2293200000,
+ 2898000000,
+ 3502800000,
+ 4107600000,
+ 4712400000,
+ 5317200000,
+ 5922000000
+ ]
+ },
+ {
+ "schedule": "25 17,20,21,23 * * *",
+ "triggerTimes": [
+ 62700000,
+ 73500000,
+ 77100000,
+ 84300000,
+ 149100000,
+ 159900000,
+ 163500000,
+ 170700000,
+ 235500000,
+ 246300000
+ ]
+ },
+ {
+ "schedule": "0 13 * * 3",
+ "triggerTimes": [
+ 565200000,
+ 1170000000,
+ 1774800000,
+ 2379600000,
+ 2984400000,
+ 3589200000,
+ 4194000000,
+ 4798800000,
+ 5403600000,
+ 6008400000
+ ]
+ },
+ {
+ "schedule": "58 */2 * * *",
+ "triggerTimes": [
+ 3480000,
+ 10680000,
+ 17880000,
+ 25080000,
+ 32280000,
+ 39480000,
+ 46680000,
+ 53880000,
+ 61080000,
+ 68280000
+ ]
+ },
+ {
+ "schedule": "0 9 4,18 * *",
+ "triggerTimes": [
+ 291600000,
+ 1501200000,
+ 2970000000,
+ 4179600000,
+ 5389200000,
+ 6598800000,
+ 8067600000,
+ 9277200000,
+ 10659600000,
+ 11869200000
+ ]
+ },
+ {
+ "schedule": "37 */6 * * *",
+ "triggerTimes": [
+ 2220000,
+ 23820000,
+ 45420000,
+ 67020000,
+ 88620000,
+ 110220000,
+ 131820000,
+ 153420000,
+ 175020000,
+ 196620000
+ ]
+ },
+ {
+ "schedule": "00 14 * * *",
+ "triggerTimes": [
+ 50400000,
+ 136800000,
+ 223200000,
+ 309600000,
+ 396000000,
+ 482400000,
+ 568800000,
+ 655200000,
+ 741600000,
+ 828000000
+ ]
+ },
+ {
+ "schedule": "0 * * * *",
+ "triggerTimes": [
+ 3600000,
+ 7200000,
+ 10800000,
+ 14400000,
+ 18000000,
+ 21600000,
+ 25200000,
+ 28800000,
+ 32400000,
+ 36000000
+ ]
+ },
+ {
+ "schedule": "29 9,16,22 * * *",
+ "triggerTimes": [
+ 34140000,
+ 59340000,
+ 80940000,
+ 120540000,
+ 145740000,
+ 167340000,
+ 206940000,
+ 232140000,
+ 253740000,
+ 293340000
+ ]
+ },
+ {
+ "schedule": "37 3 * * *",
+ "triggerTimes": [
+ 13020000,
+ 99420000,
+ 185820000,
+ 272220000,
+ 358620000,
+ 445020000,
+ 531420000,
+ 617820000,
+ 704220000,
+ 790620000
+ ]
+ },
+ {
+ "schedule": "*/5 * * * *",
+ "triggerTimes": [
+ 300000,
+ 600000,
+ 900000,
+ 1200000,
+ 1500000,
+ 1800000,
+ 2100000,
+ 2400000,
+ 2700000,
+ 3000000
+ ]
+ },
+ {
+ "schedule": "7 */2 * * *",
+ "triggerTimes": [
+ 420000,
+ 7620000,
+ 14820000,
+ 22020000,
+ 29220000,
+ 36420000,
+ 43620000,
+ 50820000,
+ 58020000,
+ 65220000
+ ]
+ },
+ {
+ "schedule": "55 07 * * *",
+ "triggerTimes": [
+ 28500000,
+ 114900000,
+ 201300000,
+ 287700000,
+ 374100000,
+ 460500000,
+ 546900000,
+ 633300000,
+ 719700000,
+ 806100000
+ ]
+ },
+ {
+ "schedule": "0 19 * * *",
+ "triggerTimes": [
+ 68400000,
+ 154800000,
+ 241200000,
+ 327600000,
+ 414000000,
+ 500400000,
+ 586800000,
+ 673200000,
+ 759600000,
+ 846000000
+ ]
+ },
+ {
+ "schedule": "15 */2 * * *",
+ "triggerTimes": [
+ 900000,
+ 8100000,
+ 15300000,
+ 22500000,
+ 29700000,
+ 36900000,
+ 44100000,
+ 51300000,
+ 58500000,
+ 65700000
+ ]
+ },
+ {
+ "schedule": "17 00 * * *",
+ "triggerTimes": [
+ 1020000,
+ 87420000,
+ 173820000,
+ 260220000,
+ 346620000,
+ 433020000,
+ 519420000,
+ 605820000,
+ 692220000,
+ 778620000
+ ]
+ },
+ {
+ "schedule": "0 0 * * 1",
+ "triggerTimes": [
+ 345600000,
+ 950400000,
+ 1555200000,
+ 2160000000,
+ 2764800000,
+ 3369600000,
+ 3974400000,
+ 4579200000,
+ 5184000000,
+ 5788800000
+ ]
+ },
+ {
+ "schedule": "29 */4 * * *",
+ "triggerTimes": [
+ 1740000,
+ 16140000,
+ 30540000,
+ 44940000,
+ 59340000,
+ 73740000,
+ 88140000,
+ 102540000,
+ 116940000,
+ 131340000
+ ]
+ },
+ {
+ "schedule": "0 23 * * *",
+ "triggerTimes": [
+ 82800000,
+ 169200000,
+ 255600000,
+ 342000000,
+ 428400000,
+ 514800000,
+ 601200000,
+ 687600000,
+ 774000000,
+ 860400000
+ ]
+ },
+ {
+ "schedule": "0 7 * * *",
+ "triggerTimes": [
+ 25200000,
+ 111600000,
+ 198000000,
+ 284400000,
+ 370800000,
+ 457200000,
+ 543600000,
+ 630000000,
+ 716400000,
+ 802800000
+ ]
+ },
+ {
+ "schedule": "12 * * * *",
+ "triggerTimes": [
+ 720000,
+ 4320000,
+ 7920000,
+ 11520000,
+ 15120000,
+ 18720000,
+ 22320000,
+ 25920000,
+ 29520000,
+ 33120000
+ ]
+ },
+ {
+ "schedule": "0 23 * * 3",
+ "triggerTimes": [
+ 601200000,
+ 1206000000,
+ 1810800000,
+ 2415600000,
+ 3020400000,
+ 3625200000,
+ 4230000000,
+ 4834800000,
+ 5439600000,
+ 6044400000
+ ]
+ },
+ {
+ "schedule": "23 */4 * * *",
+ "triggerTimes": [
+ 1380000,
+ 15780000,
+ 30180000,
+ 44580000,
+ 58980000,
+ 73380000,
+ 87780000,
+ 102180000,
+ 116580000,
+ 130980000
+ ]
+ },
+ {
+ "schedule": "30 1-23/2 * * *",
+ "triggerTimes": [
+ 5400000,
+ 12600000,
+ 19800000,
+ 27000000,
+ 34200000,
+ 41400000,
+ 48600000,
+ 55800000,
+ 63000000,
+ 70200000
+ ]
+ },
+ {
+ "schedule": "5,15,25,35,45,55 * * * *",
+ "triggerTimes": [
+ 300000,
+ 900000,
+ 1500000,
+ 2100000,
+ 2700000,
+ 3300000,
+ 3900000,
+ 4500000,
+ 5100000,
+ 5700000
+ ]
+ },
+ {
+ "schedule": "23 1,11,21 * * *",
+ "triggerTimes": [
+ 4980000,
+ 40980000,
+ 76980000,
+ 91380000,
+ 127380000,
+ 163380000,
+ 177780000,
+ 213780000,
+ 249780000,
+ 264180000
+ ]
+ },
+ {
+ "schedule": "15 04,10,16,22 * * *",
+ "triggerTimes": [
+ 15300000,
+ 36900000,
+ 58500000,
+ 80100000,
+ 101700000,
+ 123300000,
+ 144900000,
+ 166500000,
+ 188100000,
+ 209700000
+ ]
+ },
+ {
+ "schedule": "*/20 * * * *",
+ "triggerTimes": [
+ 1200000,
+ 2400000,
+ 3600000,
+ 4800000,
+ 6000000,
+ 7200000,
+ 8400000,
+ 9600000,
+ 10800000,
+ 12000000
+ ]
+ },
+ {
+ "schedule": "12,42 * * * *",
+ "triggerTimes": [
+ 720000,
+ 2520000,
+ 4320000,
+ 6120000,
+ 7920000,
+ 9720000,
+ 11520000,
+ 13320000,
+ 15120000,
+ 16920000
+ ]
+ },
+ {
+ "schedule": "26 2,6,10,14,18,22 * * *",
+ "triggerTimes": [
+ 8760000,
+ 23160000,
+ 37560000,
+ 51960000,
+ 66360000,
+ 80760000,
+ 95160000,
+ 109560000,
+ 123960000,
+ 138360000
+ ]
+ },
+ {
+ "schedule": "0 3,6,9,12,15,18,21 * * *",
+ "triggerTimes": [
+ 10800000,
+ 21600000,
+ 32400000,
+ 43200000,
+ 54000000,
+ 64800000,
+ 75600000,
+ 97200000,
+ 108000000,
+ 118800000
+ ]
+ },
+ {
+ "schedule": "25 14 * * *",
+ "triggerTimes": [
+ 51900000,
+ 138300000,
+ 224700000,
+ 311100000,
+ 397500000,
+ 483900000,
+ 570300000,
+ 656700000,
+ 743100000,
+ 829500000
+ ]
+ },
+ {
+ "schedule": "0 5 * * *,",
+ "triggerTimes": [
+ 18000000,
+ 104400000,
+ 190800000,
+ 277200000,
+ 363600000,
+ 450000000,
+ 536400000,
+ 622800000,
+ 709200000,
+ 795600000
+ ]
+ },
+ {
+ "schedule": "43 * * * *",
+ "triggerTimes": [
+ 2580000,
+ 6180000,
+ 9780000,
+ 13380000,
+ 16980000,
+ 20580000,
+ 24180000,
+ 27780000,
+ 31380000,
+ 34980000
+ ]
+ },
+ {
+ "schedule": "39 6,12,16 * * *",
+ "triggerTimes": [
+ 23940000,
+ 45540000,
+ 59940000,
+ 110340000,
+ 131940000,
+ 146340000,
+ 196740000,
+ 218340000,
+ 232740000,
+ 283140000
+ ]
+ },
+ {
+ "schedule": "0 9 1 * *",
+ "triggerTimes": [
+ 32400000,
+ 2710800000,
+ 5130000000,
+ 7808400000,
+ 10400400000,
+ 13078800000,
+ 15670800000,
+ 18349200000,
+ 21027600000,
+ 23619600000
+ ]
+ },
+ {
+ "schedule": "14-59/30 * * * *",
+ "triggerTimes": [
+ 840000,
+ 2640000,
+ 4440000,
+ 6240000,
+ 8040000,
+ 9840000,
+ 11640000,
+ 13440000,
+ 15240000,
+ 17040000
+ ]
+ },
+ {
+ "schedule": "0 0 * * *",
+ "triggerTimes": [
+ 86400000,
+ 172800000,
+ 259200000,
+ 345600000,
+ 432000000,
+ 518400000,
+ 604800000,
+ 691200000,
+ 777600000,
+ 864000000
+ ]
+ },
+ {
+ "schedule": "0 */3 * * *",
+ "triggerTimes": [
+ 10800000,
+ 21600000,
+ 32400000,
+ 43200000,
+ 54000000,
+ 64800000,
+ 75600000,
+ 86400000,
+ 97200000,
+ 108000000
+ ]
+ },
+ {
+ "schedule": "16 5,13,21 * * *",
+ "triggerTimes": [
+ 18960000,
+ 47760000,
+ 76560000,
+ 105360000,
+ 134160000,
+ 162960000,
+ 191760000,
+ 220560000,
+ 249360000,
+ 278160000
+ ]
+ },
+ {
+ "schedule": "30 18,23 * * MON-FRI",
+ "triggerTimes": [
+ 66600000,
+ 84600000,
+ 153000000,
+ 171000000,
+ 412200000,
+ 430200000,
+ 498600000,
+ 516600000,
+ 585000000,
+ 603000000
+ ]
+ },
+ {
+ "schedule": "0,15,30,45 * * * *",
+ "triggerTimes": [
+ 900000,
+ 1800000,
+ 2700000,
+ 3600000,
+ 4500000,
+ 5400000,
+ 6300000,
+ 7200000,
+ 8100000,
+ 9000000
+ ]
+ },
+ {
+ "schedule": "42 8,20 * * *",
+ "triggerTimes": [
+ 31320000,
+ 74520000,
+ 117720000,
+ 160920000,
+ 204120000,
+ 247320000,
+ 290520000,
+ 333720000,
+ 376920000,
+ 420120000
+ ]
+ },
+ {
+ "schedule": "46 */6 * * *",
+ "triggerTimes": [
+ 2760000,
+ 24360000,
+ 45960000,
+ 67560000,
+ 89160000,
+ 110760000,
+ 132360000,
+ 153960000,
+ 175560000,
+ 197160000
+ ]
+ },
+ {
+ "schedule": "0 3 * * *",
+ "triggerTimes": [
+ 10800000,
+ 97200000,
+ 183600000,
+ 270000000,
+ 356400000,
+ 442800000,
+ 529200000,
+ 615600000,
+ 702000000,
+ 788400000
+ ]
+ },
+ {
+ "schedule": "16 9,16 * * *",
+ "triggerTimes": [
+ 33360000,
+ 58560000,
+ 119760000,
+ 144960000,
+ 206160000,
+ 231360000,
+ 292560000,
+ 317760000,
+ 378960000,
+ 404160000
+ ]
+ },
+ {
+ "schedule": "15 0 * * *",
+ "triggerTimes": [
+ 900000,
+ 87300000,
+ 173700000,
+ 260100000,
+ 346500000,
+ 432900000,
+ 519300000,
+ 605700000,
+ 692100000,
+ 778500000
+ ]
+ },
+ {
+ "schedule": "05 * * * *",
+ "triggerTimes": [
+ 300000,
+ 3900000,
+ 7500000,
+ 11100000,
+ 14700000,
+ 18300000,
+ 21900000,
+ 25500000,
+ 29100000,
+ 32700000
+ ]
+ },
+ {
+ "schedule": "30 * * * *",
+ "triggerTimes": [
+ 1800000,
+ 5400000,
+ 9000000,
+ 12600000,
+ 16200000,
+ 19800000,
+ 23400000,
+ 27000000,
+ 30600000,
+ 34200000
+ ]
+ },
+ {
+ "schedule": "0 2,14 * * *",
+ "triggerTimes": [
+ 7200000,
+ 50400000,
+ 93600000,
+ 136800000,
+ 180000000,
+ 223200000,
+ 266400000,
+ 309600000,
+ 352800000,
+ 396000000
+ ]
+ },
+ {
+ "schedule": "28 23 * * 3",
+ "triggerTimes": [
+ 602880000,
+ 1207680000,
+ 1812480000,
+ 2417280000,
+ 3022080000,
+ 3626880000,
+ 4231680000,
+ 4836480000,
+ 5441280000,
+ 6046080000
+ ]
+ },
+ {
+ "schedule": "5 */4 * * *",
+ "triggerTimes": [
+ 300000,
+ 14700000,
+ 29100000,
+ 43500000,
+ 57900000,
+ 72300000,
+ 86700000,
+ 101100000,
+ 115500000,
+ 129900000
+ ]
+ },
+ {
+ "schedule": "0 18,22 * * MON-FRI",
+ "triggerTimes": [
+ 64800000,
+ 79200000,
+ 151200000,
+ 165600000,
+ 410400000,
+ 424800000,
+ 496800000,
+ 511200000,
+ 583200000,
+ 597600000
+ ]
+ },
+ {
+ "schedule": "01 21 * * *",
+ "triggerTimes": [
+ 75660000,
+ 162060000,
+ 248460000,
+ 334860000,
+ 421260000,
+ 507660000,
+ 594060000,
+ 680460000,
+ 766860000,
+ 853260000
+ ]
+ },
+ {
+ "schedule": "1 */6 * * *",
+ "triggerTimes": [
+ 60000,
+ 21660000,
+ 43260000,
+ 64860000,
+ 86460000,
+ 108060000,
+ 129660000,
+ 151260000,
+ 172860000,
+ 194460000
+ ]
+ },
+ {
+ "schedule": "*/10 * * * *",
+ "triggerTimes": [
+ 600000,
+ 1200000,
+ 1800000,
+ 2400000,
+ 3000000,
+ 3600000,
+ 4200000,
+ 4800000,
+ 5400000,
+ 6000000
+ ]
+ },
+ {
+ "schedule": "44 */2 * * *",
+ "triggerTimes": [
+ 2640000,
+ 9840000,
+ 17040000,
+ 24240000,
+ 31440000,
+ 38640000,
+ 45840000,
+ 53040000,
+ 60240000,
+ 67440000
+ ]
+ },
+ {
+ "schedule": "30 2 * * *",
+ "triggerTimes": [
+ 9000000,
+ 95400000,
+ 181800000,
+ 268200000,
+ 354600000,
+ 441000000,
+ 527400000,
+ 613800000,
+ 700200000,
+ 786600000
+ ]
+ },
+ {
+ "schedule": "58 * * * *",
+ "triggerTimes": [
+ 3480000,
+ 7080000,
+ 10680000,
+ 14280000,
+ 17880000,
+ 21480000,
+ 25080000,
+ 28680000,
+ 32280000,
+ 35880000
+ ]
+ },
+ {
+ "schedule": "30 23 * * 6",
+ "triggerTimes": [
+ 257400000,
+ 862200000,
+ 1467000000,
+ 2071800000,
+ 2676600000,
+ 3281400000,
+ 3886200000,
+ 4491000000,
+ 5095800000,
+ 5700600000
+ ]
+ },
+ {
+ "schedule": "40 23 * * *",
+ "triggerTimes": [
+ 85200000,
+ 171600000,
+ 258000000,
+ 344400000,
+ 430800000,
+ 517200000,
+ 603600000,
+ 690000000,
+ 776400000,
+ 862800000
+ ]
+ },
+ {
+ "schedule": "0 5,10,15,20,1 * * *",
+ "triggerTimes": [
+ 3600000,
+ 18000000,
+ 36000000,
+ 54000000,
+ 72000000,
+ 90000000,
+ 104400000,
+ 122400000,
+ 140400000,
+ 158400000
+ ]
+ },
+ {
+ "schedule": "22 * * * *",
+ "triggerTimes": [
+ 1320000,
+ 4920000,
+ 8520000,
+ 12120000,
+ 15720000,
+ 19320000,
+ 22920000,
+ 26520000,
+ 30120000,
+ 33720000
+ ]
+ },
+ {
+ "schedule": "00 17 1-3,5-31 * *",
+ "triggerTimes": [
+ 61200000,
+ 147600000,
+ 234000000,
+ 406800000,
+ 493200000,
+ 579600000,
+ 666000000,
+ 752400000,
+ 838800000,
+ 925200000
+ ]
+ },
+ {
+ "schedule": "0 2 1 * *",
+ "triggerTimes": [
+ 7200000,
+ 2685600000,
+ 5104800000,
+ 7783200000,
+ 10375200000,
+ 13053600000,
+ 15645600000,
+ 18324000000,
+ 21002400000,
+ 23594400000
+ ]
+ },
+ {
+ "schedule": "20 20 * * *",
+ "triggerTimes": [
+ 73200000,
+ 159600000,
+ 246000000,
+ 332400000,
+ 418800000,
+ 505200000,
+ 591600000,
+ 678000000,
+ 764400000,
+ 850800000
+ ]
+ },
+ {
+ "schedule": "45 1 * * *",
+ "triggerTimes": [
+ 6300000,
+ 92700000,
+ 179100000,
+ 265500000,
+ 351900000,
+ 438300000,
+ 524700000,
+ 611100000,
+ 697500000,
+ 783900000
+ ]
+ },
+ {
+ "schedule": "3-59/5 * * * *",
+ "triggerTimes": [
+ 180000,
+ 480000,
+ 780000,
+ 1080000,
+ 1380000,
+ 1680000,
+ 1980000,
+ 2280000,
+ 2580000,
+ 2880000
+ ]
+ },
+ {
+ "schedule": "21 * * * *",
+ "triggerTimes": [
+ 1260000,
+ 4860000,
+ 8460000,
+ 12060000,
+ 15660000,
+ 19260000,
+ 22860000,
+ 26460000,
+ 30060000,
+ 33660000
+ ]
+ },
+ {
+ "schedule": "37 */1 * * *",
+ "triggerTimes": [
+ 2220000,
+ 5820000,
+ 9420000,
+ 13020000,
+ 16620000,
+ 20220000,
+ 23820000,
+ 27420000,
+ 31020000,
+ 34620000
+ ]
+ },
+ {
+ "schedule": "12 3 * * 1,3,5",
+ "triggerTimes": [
+ 97920000,
+ 357120000,
+ 529920000,
+ 702720000,
+ 961920000,
+ 1134720000,
+ 1307520000,
+ 1566720000,
+ 1739520000,
+ 1912320000
+ ]
+ },
+ {
+ "schedule": "10 * * * *",
+ "triggerTimes": [
+ 600000,
+ 4200000,
+ 7800000,
+ 11400000,
+ 15000000,
+ 18600000,
+ 22200000,
+ 25800000,
+ 29400000,
+ 33000000
+ ]
+ },
+ {
+ "schedule": "*/4 * * * *",
+ "triggerTimes": [
+ 240000,
+ 480000,
+ 720000,
+ 960000,
+ 1200000,
+ 1440000,
+ 1680000,
+ 1920000,
+ 2160000,
+ 2400000
+ ]
+ },
+ {
+ "schedule": "36 * * * *",
+ "triggerTimes": [
+ 2160000,
+ 5760000,
+ 9360000,
+ 12960000,
+ 16560000,
+ 20160000,
+ 23760000,
+ 27360000,
+ 30960000,
+ 34560000
+ ]
+ },
+ {
+ "schedule": "10 7 * * *",
+ "triggerTimes": [
+ 25800000,
+ 112200000,
+ 198600000,
+ 285000000,
+ 371400000,
+ 457800000,
+ 544200000,
+ 630600000,
+ 717000000,
+ 803400000
+ ]
+ },
+ {
+ "schedule": "55 6 * * *",
+ "triggerTimes": [
+ 24900000,
+ 111300000,
+ 197700000,
+ 284100000,
+ 370500000,
+ 456900000,
+ 543300000,
+ 629700000,
+ 716100000,
+ 802500000
+ ]
+ },
+ {
+ "schedule": "0 */2 * * *",
+ "triggerTimes": [
+ 7200000,
+ 14400000,
+ 21600000,
+ 28800000,
+ 36000000,
+ 43200000,
+ 50400000,
+ 57600000,
+ 64800000,
+ 72000000
+ ]
+ },
+ {
+ "schedule": "0 5 * * *",
+ "triggerTimes": [
+ 18000000,
+ 104400000,
+ 190800000,
+ 277200000,
+ 363600000,
+ 450000000,
+ 536400000,
+ 622800000,
+ 709200000,
+ 795600000
+ ]
+ },
+ {
+ "schedule": "22 */4 * * *",
+ "triggerTimes": [
+ 1320000,
+ 15720000,
+ 30120000,
+ 44520000,
+ 58920000,
+ 73320000,
+ 87720000,
+ 102120000,
+ 116520000,
+ 130920000
+ ]
+ },
+ {
+ "schedule": "17 */2 * * *",
+ "triggerTimes": [
+ 1020000,
+ 8220000,
+ 15420000,
+ 22620000,
+ 29820000,
+ 37020000,
+ 44220000,
+ 51420000,
+ 58620000,
+ 65820000
+ ]
+ },
+ {
+ "schedule": "25 * * * *",
+ "triggerTimes": [
+ 1500000,
+ 5100000,
+ 8700000,
+ 12300000,
+ 15900000,
+ 19500000,
+ 23100000,
+ 26700000,
+ 30300000,
+ 33900000
+ ]
+ },
+ {
+ "schedule": "*/6 * * * *",
+ "triggerTimes": [
+ 360000,
+ 720000,
+ 1080000,
+ 1440000,
+ 1800000,
+ 2160000,
+ 2520000,
+ 2880000,
+ 3240000,
+ 3600000
+ ]
+ },
+ {
+ "schedule": "5 * * * *",
+ "triggerTimes": [
+ 300000,
+ 3900000,
+ 7500000,
+ 11100000,
+ 14700000,
+ 18300000,
+ 21900000,
+ 25500000,
+ 29100000,
+ 32700000
+ ]
+ },
+ {
+ "schedule": "0 2 * * *",
+ "triggerTimes": [
+ 7200000,
+ 93600000,
+ 180000000,
+ 266400000,
+ 352800000,
+ 439200000,
+ 525600000,
+ 612000000,
+ 698400000,
+ 784800000
+ ]
+ },
+ {
+ "schedule": "0 * * * *",
+ "triggerTimes": [
+ 3600000,
+ 7200000,
+ 10800000,
+ 14400000,
+ 18000000,
+ 21600000,
+ 25200000,
+ 28800000,
+ 32400000,
+ 36000000
+ ]
+ },
+ {
+ "schedule": "0 14 * * *,,",
+ "triggerTimes": [
+ 50400000,
+ 136800000,
+ 223200000,
+ 309600000,
+ 396000000,
+ 482400000,
+ 568800000,
+ 655200000,
+ 741600000,
+ 828000000
+ ]
+ },
+ {
+ "schedule": "30 02,08,12 * * *",
+ "triggerTimes": [
+ 9000000,
+ 30600000,
+ 45000000,
+ 95400000,
+ 117000000,
+ 131400000,
+ 181800000,
+ 203400000,
+ 217800000,
+ 268200000
+ ]
+ },
+ {
+ "schedule": "44 23 * * *",
+ "triggerTimes": [
+ 85440000,
+ 171840000,
+ 258240000,
+ 344640000,
+ 431040000,
+ 517440000,
+ 603840000,
+ 690240000,
+ 776640000,
+ 863040000
+ ]
+ },
+ {
+ "schedule": "0 */4 * * *",
+ "triggerTimes": [
+ 14400000,
+ 28800000,
+ 43200000,
+ 57600000,
+ 72000000,
+ 86400000,
+ 100800000,
+ 115200000,
+ 129600000,
+ 144000000
+ ]
+ },
+ {
+ "schedule": "0 12 * * *",
+ "triggerTimes": [
+ 43200000,
+ 129600000,
+ 216000000,
+ 302400000,
+ 388800000,
+ 475200000,
+ 561600000,
+ 648000000,
+ 734400000,
+ 820800000
+ ]
+ },
+ {
+ "schedule": "*/2 * * * *",
+ "triggerTimes": [
+ 120000,
+ 240000,
+ 360000,
+ 480000,
+ 600000,
+ 720000,
+ 840000,
+ 960000,
+ 1080000,
+ 1200000
+ ]
+ },
+ {
+ "schedule": "22 1 * * *",
+ "triggerTimes": [
+ 4920000,
+ 91320000,
+ 177720000,
+ 264120000,
+ 350520000,
+ 436920000,
+ 523320000,
+ 609720000,
+ 696120000,
+ 782520000
+ ]
+ },
+ {
+ "schedule": "45 * * * *",
+ "triggerTimes": [
+ 2700000,
+ 6300000,
+ 9900000,
+ 13500000,
+ 17100000,
+ 20700000,
+ 24300000,
+ 27900000,
+ 31500000,
+ 35100000
+ ]
+ },
+ {
+ "schedule": "00 23 * * *",
+ "triggerTimes": [
+ 82800000,
+ 169200000,
+ 255600000,
+ 342000000,
+ 428400000,
+ 514800000,
+ 601200000,
+ 687600000,
+ 774000000,
+ 860400000
+ ]
+ },
+ {
+ "schedule": "3,6,9,12,18,21,24,27,33,36,39,42,48,51,54,57 * * * *",
+ "triggerTimes": [
+ 180000,
+ 360000,
+ 540000,
+ 720000,
+ 1080000,
+ 1260000,
+ 1440000,
+ 1620000,
+ 1980000,
+ 2160000
+ ]
+ },
+ {
+ "schedule": "32 1 * * *",
+ "triggerTimes": [
+ 5520000,
+ 91920000,
+ 178320000,
+ 264720000,
+ 351120000,
+ 437520000,
+ 523920000,
+ 610320000,
+ 696720000,
+ 783120000
+ ]
+ },
+ {
+ "schedule": "35 */2 * * *",
+ "triggerTimes": [
+ 2100000,
+ 9300000,
+ 16500000,
+ 23700000,
+ 30900000,
+ 38100000,
+ 45300000,
+ 52500000,
+ 59700000,
+ 66900000
+ ]
+ },
+ {
+ "schedule": "27 1 * * *",
+ "triggerTimes": [
+ 5220000,
+ 91620000,
+ 178020000,
+ 264420000,
+ 350820000,
+ 437220000,
+ 523620000,
+ 610020000,
+ 696420000,
+ 782820000
+ ]
+ },
+ {
+ "schedule": "0 21 * * 3",
+ "triggerTimes": [
+ 594000000,
+ 1198800000,
+ 1803600000,
+ 2408400000,
+ 3013200000,
+ 3618000000,
+ 4222800000,
+ 4827600000,
+ 5432400000,
+ 6037200000
+ ]
+ },
+ {
+ "schedule": "55 03 * * *",
+ "triggerTimes": [
+ 14100000,
+ 100500000,
+ 186900000,
+ 273300000,
+ 359700000,
+ 446100000,
+ 532500000,
+ 618900000,
+ 705300000,
+ 791700000
+ ]
+ },
+ {
+ "schedule": "0 23 2-31 * *",
+ "triggerTimes": [
+ 169200000,
+ 255600000,
+ 342000000,
+ 428400000,
+ 514800000,
+ 601200000,
+ 687600000,
+ 774000000,
+ 860400000,
+ 946800000
+ ]
+ },
+ {
+ "schedule": "09 11 * * *",
+ "triggerTimes": [
+ 40140000,
+ 126540000,
+ 212940000,
+ 299340000,
+ 385740000,
+ 472140000,
+ 558540000,
+ 644940000,
+ 731340000,
+ 817740000
+ ]
+ },
+ {
+ "schedule": "0 14 * * *",
+ "triggerTimes": [
+ 50400000,
+ 136800000,
+ 223200000,
+ 309600000,
+ 396000000,
+ 482400000,
+ 568800000,
+ 655200000,
+ 741600000,
+ 828000000
+ ]
+ },
+ {
+ "schedule": "20 2,12,22 * * *",
+ "triggerTimes": [
+ 8400000,
+ 44400000,
+ 80400000,
+ 94800000,
+ 130800000,
+ 166800000,
+ 181200000,
+ 217200000,
+ 253200000,
+ 267600000
+ ]
+ },
+ {
+ "schedule": "2,6,10,14,18,22,26,30,34,38,42,46,50,54,58 * * * *",
+ "triggerTimes": [
+ 120000,
+ 360000,
+ 600000,
+ 840000,
+ 1080000,
+ 1320000,
+ 1560000,
+ 1800000,
+ 2040000,
+ 2280000
+ ]
+ },
+ {
+ "schedule": "1 16,18,20 * * *",
+ "triggerTimes": [
+ 57660000,
+ 64860000,
+ 72060000,
+ 144060000,
+ 151260000,
+ 158460000,
+ 230460000,
+ 237660000,
+ 244860000,
+ 316860000
+ ]
+ },
+ {
+ "schedule": "30 */6 * * *",
+ "triggerTimes": [
+ 1800000,
+ 23400000,
+ 45000000,
+ 66600000,
+ 88200000,
+ 109800000,
+ 131400000,
+ 153000000,
+ 174600000,
+ 196200000
+ ]
+ },
+ {
+ "schedule": "00 06,15 * * *",
+ "triggerTimes": [
+ 21600000,
+ 54000000,
+ 108000000,
+ 140400000,
+ 194400000,
+ 226800000,
+ 280800000,
+ 313200000,
+ 367200000,
+ 399600000
+ ]
+ },
+ {
+ "schedule": "52 4,10,16,22 * * *",
+ "triggerTimes": [
+ 17520000,
+ 39120000,
+ 60720000,
+ 82320000,
+ 103920000,
+ 125520000,
+ 147120000,
+ 168720000,
+ 190320000,
+ 211920000
+ ]
+ },
+ {
+ "schedule": "37 1 * * *",
+ "triggerTimes": [
+ 5820000,
+ 92220000,
+ 178620000,
+ 265020000,
+ 351420000,
+ 437820000,
+ 524220000,
+ 610620000,
+ 697020000,
+ 783420000
+ ]
+ },
+ {
+ "schedule": "10 10,14 * * *",
+ "triggerTimes": [
+ 36600000,
+ 51000000,
+ 123000000,
+ 137400000,
+ 209400000,
+ 223800000,
+ 295800000,
+ 310200000,
+ 382200000,
+ 396600000
+ ]
+ },
+ {
+ "schedule": "2,7,12,17,22,27,32,37,42,47,52,57 * * * *",
+ "triggerTimes": [
+ 120000,
+ 420000,
+ 720000,
+ 1020000,
+ 1320000,
+ 1620000,
+ 1920000,
+ 2220000,
+ 2520000,
+ 2820000
+ ]
+ },
+ {
+ "schedule": "0 21 * * *",
+ "triggerTimes": [
+ 75600000,
+ 162000000,
+ 248400000,
+ 334800000,
+ 421200000,
+ 507600000,
+ 594000000,
+ 680400000,
+ 766800000,
+ 853200000
+ ]
+ },
+ {
+ "schedule": "25 * * * *",
+ "triggerTimes": [
+ 1500000,
+ 5100000,
+ 8700000,
+ 12300000,
+ 15900000,
+ 19500000,
+ 23100000,
+ 26700000,
+ 30300000,
+ 33900000
+ ]
+ },
+ {
+ "schedule": "0 15 * * *,,",
+ "triggerTimes": [
+ 54000000,
+ 140400000,
+ 226800000,
+ 313200000,
+ 399600000,
+ 486000000,
+ 572400000,
+ 658800000,
+ 745200000,
+ 831600000
+ ]
+ },
+ {
+ "schedule": "13 9,21 * * *",
+ "triggerTimes": [
+ 33180000,
+ 76380000,
+ 119580000,
+ 162780000,
+ 205980000,
+ 249180000,
+ 292380000,
+ 335580000,
+ 378780000,
+ 421980000
+ ]
+ },
+ {
+ "schedule": "10 * * * *",
+ "triggerTimes": [
+ 600000,
+ 4200000,
+ 7800000,
+ 11400000,
+ 15000000,
+ 18600000,
+ 22200000,
+ 25800000,
+ 29400000,
+ 33000000
+ ]
+ },
+ {
+ "schedule": "12 18 * * 1,3,5",
+ "triggerTimes": [
+ 151920000,
+ 411120000,
+ 583920000,
+ 756720000,
+ 1015920000,
+ 1188720000,
+ 1361520000,
+ 1620720000,
+ 1793520000,
+ 1966320000
+ ]
+ },
+ {
+ "schedule": "0 17-19 * * 1",
+ "triggerTimes": [
+ 406800000,
+ 410400000,
+ 414000000,
+ 1011600000,
+ 1015200000,
+ 1018800000,
+ 1616400000,
+ 1620000000,
+ 1623600000,
+ 2221200000
+ ]
+ },
+ {
+ "schedule": "0 10 * * *",
+ "triggerTimes": [
+ 36000000,
+ 122400000,
+ 208800000,
+ 295200000,
+ 381600000,
+ 468000000,
+ 554400000,
+ 640800000,
+ 727200000,
+ 813600000
+ ]
+ },
+ {
+ "schedule": "00 00 * * *",
+ "triggerTimes": [
+ 86400000,
+ 172800000,
+ 259200000,
+ 345600000,
+ 432000000,
+ 518400000,
+ 604800000,
+ 691200000,
+ 777600000,
+ 864000000
+ ]
+ },
+ {
+ "schedule": "25 16,17,18,22 * * *",
+ "triggerTimes": [
+ 59100000,
+ 62700000,
+ 66300000,
+ 80700000,
+ 145500000,
+ 149100000,
+ 152700000,
+ 167100000,
+ 231900000,
+ 235500000
+ ]
+ },
+ {
+ "schedule": "23 6,18 * * *",
+ "triggerTimes": [
+ 22980000,
+ 66180000,
+ 109380000,
+ 152580000,
+ 195780000,
+ 238980000,
+ 282180000,
+ 325380000,
+ 368580000,
+ 411780000
+ ]
+ },
+ {
+ "schedule": "17 1,9,17 * * 0",
+ "triggerTimes": [
+ 263820000,
+ 292620000,
+ 321420000,
+ 868620000,
+ 897420000,
+ 926220000,
+ 1473420000,
+ 1502220000,
+ 1531020000,
+ 2078220000
+ ]
+ },
+ {
+ "schedule": "00 16 * * *",
+ "triggerTimes": [
+ 57600000,
+ 144000000,
+ 230400000,
+ 316800000,
+ 403200000,
+ 489600000,
+ 576000000,
+ 662400000,
+ 748800000,
+ 835200000
+ ]
+ },
+ {
+ "schedule": "*/3 * * * *",
+ "triggerTimes": [
+ 180000,
+ 360000,
+ 540000,
+ 720000,
+ 900000,
+ 1080000,
+ 1260000,
+ 1440000,
+ 1620000,
+ 1800000
+ ]
+ },
+ {
+ "schedule": "19 * * * *",
+ "triggerTimes": [
+ 1140000,
+ 4740000,
+ 8340000,
+ 11940000,
+ 15540000,
+ 19140000,
+ 22740000,
+ 26340000,
+ 29940000,
+ 33540000
+ ]
+ },
+ {
+ "schedule": "15 * * * *",
+ "triggerTimes": [
+ 900000,
+ 4500000,
+ 8100000,
+ 11700000,
+ 15300000,
+ 18900000,
+ 22500000,
+ 26100000,
+ 29700000,
+ 33300000
+ ]
+ },
+ {
+ "schedule": "*/15 * * * *",
+ "triggerTimes": [
+ 900000,
+ 1800000,
+ 2700000,
+ 3600000,
+ 4500000,
+ 5400000,
+ 6300000,
+ 7200000,
+ 8100000,
+ 9000000
+ ]
+ },
+ {
+ "schedule": "0 22 * * 1",
+ "triggerTimes": [
+ 424800000,
+ 1029600000,
+ 1634400000,
+ 2239200000,
+ 2844000000,
+ 3448800000,
+ 4053600000,
+ 4658400000,
+ 5263200000,
+ 5868000000
+ ]
+ },
+ {
+ "schedule": "15 * * * *",
+ "triggerTimes": [
+ 900000,
+ 4500000,
+ 8100000,
+ 11700000,
+ 15300000,
+ 18900000,
+ 22500000,
+ 26100000,
+ 29700000,
+ 33300000
+ ]
+ },
+ {
+ "schedule": "20 04 * * *",
+ "triggerTimes": [
+ 15600000,
+ 102000000,
+ 188400000,
+ 274800000,
+ 361200000,
+ 447600000,
+ 534000000,
+ 620400000,
+ 706800000,
+ 793200000
+ ]
+ },
+ {
+ "schedule": "30 0,12 * * *",
+ "triggerTimes": [
+ 1800000,
+ 45000000,
+ 88200000,
+ 131400000,
+ 174600000,
+ 217800000,
+ 261000000,
+ 304200000,
+ 347400000,
+ 390600000
+ ]
+ },
+ {
+ "schedule": "15 */4 * * *",
+ "triggerTimes": [
+ 900000,
+ 15300000,
+ 29700000,
+ 44100000,
+ 58500000,
+ 72900000,
+ 87300000,
+ 101700000,
+ 116100000,
+ 130500000
+ ]
+ },
+ {
+ "schedule": "29 16,17,18,22 * * *",
+ "triggerTimes": [
+ 59340000,
+ 62940000,
+ 66540000,
+ 80940000,
+ 145740000,
+ 149340000,
+ 152940000,
+ 167340000,
+ 232140000,
+ 235740000
+ ]
+ },
+ {
+ "schedule": "37 */3 * * *",
+ "triggerTimes": [
+ 2220000,
+ 13020000,
+ 23820000,
+ 34620000,
+ 45420000,
+ 56220000,
+ 67020000,
+ 77820000,
+ 88620000,
+ 99420000
+ ]
+ },
+ {
+ "schedule": "*/15 * * * *",
+ "triggerTimes": [
+ 900000,
+ 1800000,
+ 2700000,
+ 3600000,
+ 4500000,
+ 5400000,
+ 6300000,
+ 7200000,
+ 8100000,
+ 9000000
+ ]
+ },
+ {
+ "schedule": "35 23 * * *",
+ "triggerTimes": [
+ 84900000,
+ 171300000,
+ 257700000,
+ 344100000,
+ 430500000,
+ 516900000,
+ 603300000,
+ 689700000,
+ 776100000,
+ 862500000
+ ]
+ },
+ {
+ "schedule": "0 17 * * *",
+ "triggerTimes": [
+ 61200000,
+ 147600000,
+ 234000000,
+ 320400000,
+ 406800000,
+ 493200000,
+ 579600000,
+ 666000000,
+ 752400000,
+ 838800000
+ ]
+ },
+ {
+ "schedule": "0 22 * * *",
+ "triggerTimes": [
+ 79200000,
+ 165600000,
+ 252000000,
+ 338400000,
+ 424800000,
+ 511200000,
+ 597600000,
+ 684000000,
+ 770400000,
+ 856800000
+ ]
+ },
+ {
+ "schedule": "0 11 * * *",
+ "triggerTimes": [
+ 39600000,
+ 126000000,
+ 212400000,
+ 298800000,
+ 385200000,
+ 471600000,
+ 558000000,
+ 644400000,
+ 730800000,
+ 817200000
+ ]
+ },
+ {
+ "schedule": "30 * * * *",
+ "triggerTimes": [
+ 1800000,
+ 5400000,
+ 9000000,
+ 12600000,
+ 16200000,
+ 19800000,
+ 23400000,
+ 27000000,
+ 30600000,
+ 34200000
+ ]
+ },
+ {
+ "schedule": "41 * * * *",
+ "triggerTimes": [
+ 2460000,
+ 6060000,
+ 9660000,
+ 13260000,
+ 16860000,
+ 20460000,
+ 24060000,
+ 27660000,
+ 31260000,
+ 34860000
+ ]
+ },
+ {
+ "schedule": "45 23 * * *",
+ "triggerTimes": [
+ 85500000,
+ 171900000,
+ 258300000,
+ 344700000,
+ 431100000,
+ 517500000,
+ 603900000,
+ 690300000,
+ 776700000,
+ 863100000
+ ]
+ },
+ {
+ "schedule": "*/2 * * * *",
+ "triggerTimes": [
+ 120000,
+ 240000,
+ 360000,
+ 480000,
+ 600000,
+ 720000,
+ 840000,
+ 960000,
+ 1080000,
+ 1200000
+ ]
+ },
+ {
+ "schedule": "0 0,3,6,9,12,15,18,21 * * *",
+ "triggerTimes": [
+ 10800000,
+ 21600000,
+ 32400000,
+ 43200000,
+ 54000000,
+ 64800000,
+ 75600000,
+ 86400000,
+ 97200000,
+ 108000000
+ ]
+ },
+ {
+ "schedule": "0,30 * * * *",
+ "triggerTimes": [
+ 1800000,
+ 3600000,
+ 5400000,
+ 7200000,
+ 9000000,
+ 10800000,
+ 12600000,
+ 14400000,
+ 16200000,
+ 18000000
+ ]
+ },
+ {
+ "schedule": "17 * * * *",
+ "triggerTimes": [
+ 1020000,
+ 4620000,
+ 8220000,
+ 11820000,
+ 15420000,
+ 19020000,
+ 22620000,
+ 26220000,
+ 29820000,
+ 33420000
+ ]
+ },
+ {
+ "schedule": "30,45 18 * * 1",
+ "triggerTimes": [
+ 412200000,
+ 413100000,
+ 1017000000,
+ 1017900000,
+ 1621800000,
+ 1622700000,
+ 2226600000,
+ 2227500000,
+ 2831400000,
+ 2832300000
+ ]
+ },
+ {
+ "schedule": "13,43 * * * *",
+ "triggerTimes": [
+ 780000,
+ 2580000,
+ 4380000,
+ 6180000,
+ 7980000,
+ 9780000,
+ 11580000,
+ 13380000,
+ 15180000,
+ 16980000
+ ]
+ },
+ {
+ "schedule": "0 0 10 * *",
+ "triggerTimes": [
+ 777600000,
+ 3456000000,
+ 5875200000,
+ 8553600000,
+ 11145600000,
+ 13824000000,
+ 16416000000,
+ 19094400000,
+ 21772800000,
+ 24364800000
+ ]
+ },
+ {
+ "schedule": "13,28,43,58 * * * *",
+ "triggerTimes": [
+ 780000,
+ 1680000,
+ 2580000,
+ 3480000,
+ 4380000,
+ 5280000,
+ 6180000,
+ 7080000,
+ 7980000,
+ 8880000
+ ]
+ },
+ {
+ "schedule": "17 9,13,22 * * *",
+ "triggerTimes": [
+ 33420000,
+ 47820000,
+ 80220000,
+ 119820000,
+ 134220000,
+ 166620000,
+ 206220000,
+ 220620000,
+ 253020000,
+ 292620000
+ ]
+ },
+ {
+ "schedule": "10 8,12 * * *",
+ "triggerTimes": [
+ 29400000,
+ 43800000,
+ 115800000,
+ 130200000,
+ 202200000,
+ 216600000,
+ 288600000,
+ 303000000,
+ 375000000,
+ 389400000
+ ]
+ },
+ {
+ "schedule": "*/5 * * * *",
+ "triggerTimes": [
+ 300000,
+ 600000,
+ 900000,
+ 1200000,
+ 1500000,
+ 1800000,
+ 2100000,
+ 2400000,
+ 2700000,
+ 3000000
+ ]
+ },
+ {
+ "schedule": "5,20,35,50 * * * *",
+ "triggerTimes": [
+ 300000,
+ 1200000,
+ 2100000,
+ 3000000,
+ 3900000,
+ 4800000,
+ 5700000,
+ 6600000,
+ 7500000,
+ 8400000
+ ]
+ },
+ {
+ "schedule": "00 */2 * * *",
+ "triggerTimes": [
+ 7200000,
+ 14400000,
+ 21600000,
+ 28800000,
+ 36000000,
+ 43200000,
+ 50400000,
+ 57600000,
+ 64800000,
+ 72000000
+ ]
+ },
+ {
+ "schedule": "23 * * * *",
+ "triggerTimes": [
+ 1380000,
+ 4980000,
+ 8580000,
+ 12180000,
+ 15780000,
+ 19380000,
+ 22980000,
+ 26580000,
+ 30180000,
+ 33780000
+ ]
+ },
+ {
+ "schedule": "7 12 * * *",
+ "triggerTimes": [
+ 43620000,
+ 130020000,
+ 216420000,
+ 302820000,
+ 389220000,
+ 475620000,
+ 562020000,
+ 648420000,
+ 734820000,
+ 821220000
+ ]
+ },
+ {
+ "schedule": "*/1 * * * *",
+ "triggerTimes": [
+ 60000,
+ 120000,
+ 180000,
+ 240000,
+ 300000,
+ 360000,
+ 420000,
+ 480000,
+ 540000,
+ 600000
+ ]
+ },
+ {
+ "schedule": "0,10,20,30,40,50 * * * *",
+ "triggerTimes": [
+ 600000,
+ 1200000,
+ 1800000,
+ 2400000,
+ 3000000,
+ 3600000,
+ 4200000,
+ 4800000,
+ 5400000,
+ 6000000
+ ]
+ },
+ {
+ "schedule": "45 02,06,10,14,18,22 * * *",
+ "triggerTimes": [
+ 9900000,
+ 24300000,
+ 38700000,
+ 53100000,
+ 67500000,
+ 81900000,
+ 96300000,
+ 110700000,
+ 125100000,
+ 139500000
+ ]
+ },
+ {
+ "schedule": "39 1 * * *",
+ "triggerTimes": [
+ 5940000,
+ 92340000,
+ 178740000,
+ 265140000,
+ 351540000,
+ 437940000,
+ 524340000,
+ 610740000,
+ 697140000,
+ 783540000
+ ]
+ },
+ {
+ "schedule": "0 0-2 * * 2-6",
+ "triggerTimes": [
+ 3600000,
+ 7200000,
+ 86400000,
+ 90000000,
+ 93600000,
+ 172800000,
+ 176400000,
+ 180000000,
+ 432000000,
+ 435600000
+ ]
+ },
+ {
+ "schedule": "35,50 * * * *",
+ "triggerTimes": [
+ 2100000,
+ 3000000,
+ 5700000,
+ 6600000,
+ 9300000,
+ 10200000,
+ 12900000,
+ 13800000,
+ 16500000,
+ 17400000
+ ]
+ },
+ {
+ "schedule": "0 3 1 * *",
+ "triggerTimes": [
+ 10800000,
+ 2689200000,
+ 5108400000,
+ 7786800000,
+ 10378800000,
+ 13057200000,
+ 15649200000,
+ 18327600000,
+ 21006000000,
+ 23598000000
+ ]
+ },
+ {
+ "schedule": "5 5 * * *",
+ "triggerTimes": [
+ 18300000,
+ 104700000,
+ 191100000,
+ 277500000,
+ 363900000,
+ 450300000,
+ 536700000,
+ 623100000,
+ 709500000,
+ 795900000
+ ]
+ },
+ {
+ "schedule": "18 8 * * *",
+ "triggerTimes": [
+ 29880000,
+ 116280000,
+ 202680000,
+ 289080000,
+ 375480000,
+ 461880000,
+ 548280000,
+ 634680000,
+ 721080000,
+ 807480000
+ ]
+ },
+ {
+ "schedule": "0 9 * * *",
+ "triggerTimes": [
+ 32400000,
+ 118800000,
+ 205200000,
+ 291600000,
+ 378000000,
+ 464400000,
+ 550800000,
+ 637200000,
+ 723600000,
+ 810000000
+ ]
+ },
+ {
+ "schedule": "*/1 * * * *",
+ "triggerTimes": [
+ 60000,
+ 120000,
+ 180000,
+ 240000,
+ 300000,
+ 360000,
+ 420000,
+ 480000,
+ 540000,
+ 600000
+ ]
+ },
+ {
+ "schedule": "50 8,12,21 * * *",
+ "triggerTimes": [
+ 31800000,
+ 46200000,
+ 78600000,
+ 118200000,
+ 132600000,
+ 165000000,
+ 204600000,
+ 219000000,
+ 251400000,
+ 291000000
+ ]
+ },
+ {
+ "schedule": "29 9,21 * * *",
+ "triggerTimes": [
+ 34140000,
+ 77340000,
+ 120540000,
+ 163740000,
+ 206940000,
+ 250140000,
+ 293340000,
+ 336540000,
+ 379740000,
+ 422940000
+ ]
+ },
+ {
+ "schedule": "40 * * * *",
+ "triggerTimes": [
+ 2400000,
+ 6000000,
+ 9600000,
+ 13200000,
+ 16800000,
+ 20400000,
+ 24000000,
+ 27600000,
+ 31200000,
+ 34800000
+ ]
+ },
+ {
+ "schedule": "8 21 * * *",
+ "triggerTimes": [
+ 76080000,
+ 162480000,
+ 248880000,
+ 335280000,
+ 421680000,
+ 508080000,
+ 594480000,
+ 680880000,
+ 767280000,
+ 853680000
+ ]
+ },
+ {
+ "schedule": "0 6 * * *",
+ "triggerTimes": [
+ 21600000,
+ 108000000,
+ 194400000,
+ 280800000,
+ 367200000,
+ 453600000,
+ 540000000,
+ 626400000,
+ 712800000,
+ 799200000
+ ]
+ },
+ {
+ "schedule": "30 0-23/2 * * *",
+ "triggerTimes": [
+ 1800000,
+ 9000000,
+ 16200000,
+ 23400000,
+ 30600000,
+ 37800000,
+ 45000000,
+ 52200000,
+ 59400000,
+ 66600000
+ ]
+ },
+ {
+ "schedule": "0 14,22 * * *",
+ "triggerTimes": [
+ 50400000,
+ 79200000,
+ 136800000,
+ 165600000,
+ 223200000,
+ 252000000,
+ 309600000,
+ 338400000,
+ 396000000,
+ 424800000
+ ]
+ },
+ {
+ "schedule": "0 */1 * * *",
+ "triggerTimes": [
+ 3600000,
+ 7200000,
+ 10800000,
+ 14400000,
+ 18000000,
+ 21600000,
+ 25200000,
+ 28800000,
+ 32400000,
+ 36000000
+ ]
+ },
+ {
+ "schedule": "0 1 * * 1",
+ "triggerTimes": [
+ 349200000,
+ 954000000,
+ 1558800000,
+ 2163600000,
+ 2768400000,
+ 3373200000,
+ 3978000000,
+ 4582800000,
+ 5187600000,
+ 5792400000
+ ]
+ },
+ {
+ "schedule": "0 8 * * *",
+ "triggerTimes": [
+ 28800000,
+ 115200000,
+ 201600000,
+ 288000000,
+ 374400000,
+ 460800000,
+ 547200000,
+ 633600000,
+ 720000000,
+ 806400000
+ ]
+ },
+ {
+ "schedule": "01 17 * * *",
+ "triggerTimes": [
+ 61260000,
+ 147660000,
+ 234060000,
+ 320460000,
+ 406860000,
+ 493260000,
+ 579660000,
+ 666060000,
+ 752460000,
+ 838860000
+ ]
+ },
+ {
+ "schedule": "13 * * * *",
+ "triggerTimes": [
+ 780000,
+ 4380000,
+ 7980000,
+ 11580000,
+ 15180000,
+ 18780000,
+ 22380000,
+ 25980000,
+ 29580000,
+ 33180000
+ ]
+ }
+]
[4/5] CronScheduler based on Quartz
Posted by ke...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java b/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java
index 9831012..6d76f60 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java
@@ -39,8 +39,8 @@ import com.twitter.common.net.pool.DynamicHostSet;
import com.twitter.common.net.pool.DynamicHostSet.MonitorException;
import com.twitter.thrift.ServiceInstance;
+import org.apache.aurora.scheduler.cron.CronJobManager;
import org.apache.aurora.scheduler.quota.QuotaManager;
-import org.apache.aurora.scheduler.state.CronJobManager;
import org.apache.aurora.scheduler.state.SchedulerCore;
import org.apache.aurora.scheduler.storage.entities.IServerInfo;
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/http/StructDump.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/StructDump.java b/src/main/java/org/apache/aurora/scheduler/http/StructDump.java
index efea75f..823668f 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/StructDump.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/StructDump.java
@@ -25,7 +25,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.twitter.common.base.Closure;
import com.twitter.common.thrift.Util;
@@ -34,7 +33,7 @@ import org.antlr.stringtemplate.StringTemplate;
import org.apache.aurora.gen.JobConfiguration;
import org.apache.aurora.scheduler.base.JobKeys;
import org.apache.aurora.scheduler.base.Query;
-import org.apache.aurora.scheduler.state.CronJobManager;
+import org.apache.aurora.scheduler.cron.CronJobManager;
import org.apache.aurora.scheduler.storage.Storage;
import org.apache.aurora.scheduler.storage.Storage.StoreProvider;
import org.apache.aurora.scheduler.storage.Storage.Work;
@@ -43,6 +42,8 @@ import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
import org.apache.aurora.scheduler.storage.entities.IJobKey;
import org.apache.thrift.TBase;
+import static com.google.common.base.Preconditions.checkNotNull;
+
/**
* Servlet that prints out the raw configuration for a specified struct.
*/
@@ -50,11 +51,13 @@ import org.apache.thrift.TBase;
public class StructDump extends JerseyTemplateServlet {
private final Storage storage;
+ private final CronJobManager cronJobManager;
@Inject
- public StructDump(Storage storage) {
+ public StructDump(Storage storage, CronJobManager cronJobManager) {
super("structdump");
- this.storage = Preconditions.checkNotNull(storage);
+ this.storage = checkNotNull(storage);
+ this.cronJobManager = checkNotNull(cronJobManager);
}
private static final String USAGE =
@@ -106,11 +109,11 @@ public class StructDump extends JerseyTemplateServlet {
@PathParam("job") final String job) {
final IJobKey jobKey = JobKeys.from(role, environment, job);
- return dumpEntity("Cron job " + JobKeys.toPath(jobKey),
+ return dumpEntity("Cron job " + JobKeys.canonicalString(jobKey),
new Work.Quiet<Optional<? extends TBase<?, ?>>>() {
@Override
public Optional<JobConfiguration> apply(StoreProvider storeProvider) {
- return storeProvider.getJobStore().fetchJob(CronJobManager.MANAGER_KEY, jobKey)
+ return storeProvider.getJobStore().fetchJob(cronJobManager.getManagerKey(), jobKey)
.transform(IJobConfiguration.TO_BUILDER);
}
});
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/sla/SlaGroup.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/sla/SlaGroup.java b/src/main/java/org/apache/aurora/scheduler/sla/SlaGroup.java
index ff7e3cb..46be612 100644
--- a/src/main/java/org/apache/aurora/scheduler/sla/SlaGroup.java
+++ b/src/main/java/org/apache/aurora/scheduler/sla/SlaGroup.java
@@ -119,7 +119,7 @@ interface SlaGroup {
return Multimaps.index(tasks, Functions.compose(new Function<IJobKey, String>() {
@Override
public String apply(IJobKey jobKey) {
- return "sla_" + JobKeys.toPath(jobKey) + "_";
+ return "sla_" + JobKeys.canonicalString(jobKey) + "_";
}
}, Tasks.SCHEDULED_TO_JOB_KEY));
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/state/CronJobManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/state/CronJobManager.java b/src/main/java/org/apache/aurora/scheduler/state/CronJobManager.java
deleted file mode 100644
index 4bd190c..0000000
--- a/src/main/java/org/apache/aurora/scheduler/state/CronJobManager.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/**
- * Copyright 2013 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.state;
-
-import java.util.Collections;
-import java.util.Date;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
-import com.google.common.eventbus.Subscribe;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import com.twitter.common.application.ShutdownRegistry;
-import com.twitter.common.args.Arg;
-import com.twitter.common.args.CmdLine;
-import com.twitter.common.base.Command;
-import com.twitter.common.base.Supplier;
-import com.twitter.common.quantity.Amount;
-import com.twitter.common.quantity.Time;
-import com.twitter.common.stats.Stats;
-import com.twitter.common.util.BackoffHelper;
-
-import org.apache.aurora.gen.CronCollisionPolicy;
-import org.apache.aurora.gen.ScheduleStatus;
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.base.Query;
-import org.apache.aurora.scheduler.base.ScheduleException;
-import org.apache.aurora.scheduler.base.Tasks;
-import org.apache.aurora.scheduler.configuration.ConfigurationManager.TaskDescriptionException;
-import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
-import org.apache.aurora.scheduler.cron.CronException;
-import org.apache.aurora.scheduler.cron.CronScheduler;
-import org.apache.aurora.scheduler.events.PubsubEvent.EventSubscriber;
-import org.apache.aurora.scheduler.events.PubsubEvent.SchedulerActive;
-import org.apache.aurora.scheduler.storage.Storage;
-import org.apache.aurora.scheduler.storage.Storage.MutateWork;
-import org.apache.aurora.scheduler.storage.Storage.Work;
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
-import org.apache.commons.lang.StringUtils;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import static org.apache.aurora.gen.ScheduleStatus.KILLING;
-
-/**
- * A job scheduler that receives jobs that should be run periodically on a cron schedule.
- */
-public class CronJobManager implements EventSubscriber {
-
- public static final String MANAGER_KEY = "CRON";
-
- @VisibleForTesting
- static final Optional<String> KILL_AUDIT_MESSAGE = Optional.of("Killed by cron");
-
- private static final Logger LOG = Logger.getLogger(CronJobManager.class.getName());
-
- @CmdLine(name = "cron_start_initial_backoff", help =
- "Initial backoff delay while waiting for a previous cron run to start.")
- private static final Arg<Amount<Long, Time>> CRON_START_INITIAL_BACKOFF =
- Arg.create(Amount.of(1L, Time.SECONDS));
-
- @CmdLine(name = "cron_start_max_backoff", help =
- "Max backoff delay while waiting for a previous cron run to start.")
- private static final Arg<Amount<Long, Time>> CRON_START_MAX_BACKOFF =
- Arg.create(Amount.of(1L, Time.MINUTES));
-
- private final AtomicLong cronJobsTriggered = Stats.exportLong("cron_jobs_triggered");
- private final AtomicLong cronJobLaunchFailures = Stats.exportLong("cron_job_launch_failures");
-
- // Maps from the unique job identifier to the unique identifier used internally by the cron
- // scheduler.
- private final Map<IJobKey, String> scheduledJobs =
- Collections.synchronizedMap(Maps.<IJobKey, String>newHashMap());
-
- // Prevents runs from dogpiling while waiting for a run to transition out of the KILLING state.
- // This is necessary because killing a job (if dictated by cron collision policy) is an
- // asynchronous operation.
- private final Map<IJobKey, SanitizedConfiguration> pendingRuns =
- Collections.synchronizedMap(Maps.<IJobKey, SanitizedConfiguration>newHashMap());
-
- private final StateManager stateManager;
- private final Storage storage;
- private final CronScheduler cron;
- private final ShutdownRegistry shutdownRegistry;
- private final BackoffHelper delayedStartBackoff;
- private final Executor delayedRunExecutor;
-
- @Inject
- CronJobManager(
- StateManager stateManager,
- Storage storage,
- CronScheduler cron,
- ShutdownRegistry shutdownRegistry) {
-
- this(
- stateManager,
- storage,
- cron,
- shutdownRegistry,
- Executors.newCachedThreadPool(
- new ThreadFactoryBuilder().setDaemon(true).setNameFormat("CronDelay-%d").build()));
- }
-
- @VisibleForTesting
- CronJobManager(
- StateManager stateManager,
- Storage storage,
- CronScheduler cron,
- ShutdownRegistry shutdownRegistry,
- Executor delayedRunExecutor) {
-
- this.stateManager = checkNotNull(stateManager);
- this.storage = checkNotNull(storage);
- this.cron = checkNotNull(cron);
- this.shutdownRegistry = checkNotNull(shutdownRegistry);
- this.delayedStartBackoff =
- new BackoffHelper(CRON_START_INITIAL_BACKOFF.get(), CRON_START_MAX_BACKOFF.get());
- this.delayedRunExecutor = checkNotNull(delayedRunExecutor);
-
- Stats.exportSize("cron_num_pending_runs", pendingRuns);
- }
-
- private void mapScheduledJob(SanitizedCronJob cronJob) throws ScheduleException {
- IJobKey jobKey = cronJob.config.getJobConfig().getKey();
- synchronized (scheduledJobs) {
- Preconditions.checkState(
- !scheduledJobs.containsKey(jobKey),
- "Illegal state - cron schedule already exists for " + JobKeys.toPath(jobKey));
- scheduledJobs.put(jobKey, scheduleJob(cronJob));
- }
- }
-
- /**
- * Notifies the cron job manager that the scheduler is active, and job configurations are ready to
- * load.
- *
- * @param schedulerActive Event.
- */
- @Subscribe
- public void schedulerActive(SchedulerActive schedulerActive) {
- cron.startAsync().awaitRunning();
- shutdownRegistry.addAction(new Command() {
- @Override
- public void execute() {
- // NOTE: We don't know ahead-of-time which thread will execute the shutdown command,
- // so we shouldn't block here.
- cron.stopAsync();
- }
- });
-
- Iterable<IJobConfiguration> crons =
- storage.consistentRead(new Work.Quiet<Iterable<IJobConfiguration>>() {
- @Override
- public Iterable<IJobConfiguration> apply(Storage.StoreProvider storeProvider) {
- return storeProvider.getJobStore().fetchJobs(MANAGER_KEY);
- }
- });
-
- for (IJobConfiguration job : crons) {
- try {
- mapScheduledJob(new SanitizedCronJob(job, cron));
- } catch (ScheduleException | TaskDescriptionException e) {
- logLaunchFailure(job, e);
- }
- }
- }
-
- private void logLaunchFailure(IJobConfiguration job, Exception e) {
- cronJobLaunchFailures.incrementAndGet();
- LOG.log(Level.SEVERE, "Scheduling failed for recovered job " + job, e);
- }
-
- /**
- * Triggers execution of a job.
- *
- * @param jobKey Key of the job to start.
- * @throws ScheduleException If the job could not be started with the cron system.
- * @throws TaskDescriptionException If the stored job associated with {@code jobKey} has field
- * validation problems.
- */
- public void startJobNow(IJobKey jobKey) throws TaskDescriptionException, ScheduleException {
- Optional<IJobConfiguration> jobConfig = fetchJob(jobKey);
- if (!jobConfig.isPresent()) {
- throw new ScheduleException("Cron job does not exist for " + JobKeys.toPath(jobKey));
- }
-
- cronTriggered(new SanitizedCronJob(jobConfig.get(), cron));
- }
-
- private void delayedRun(final Query.Builder query, final SanitizedConfiguration config) {
- IJobConfiguration job = config.getJobConfig();
- final String jobPath = JobKeys.toPath(job);
- final IJobKey jobKey = job.getKey();
- LOG.info("Waiting for job to terminate before launching cron job " + jobPath);
- if (pendingRuns.put(jobKey, config) == null) {
- LOG.info("Launching a task to wait for job to finish: " + jobPath);
- // There was no run already pending for this job, launch a task to delay launch until the
- // existing run has terminated.
- delayedRunExecutor.execute(new Runnable() {
- @Override
- public void run() {
- runWhenTerminated(query, jobKey);
- }
- });
- }
- }
-
- private void runWhenTerminated(final Query.Builder query, final IJobKey jobKey) {
- try {
- delayedStartBackoff.doUntilSuccess(new Supplier<Boolean>() {
- @Override
- public Boolean get() {
- if (Storage.Util.consistentFetchTasks(storage, query).isEmpty()) {
- LOG.info("Initiating delayed launch of cron " + jobKey);
- SanitizedConfiguration config = pendingRuns.remove(jobKey);
- checkNotNull(config, "Failed to fetch job for delayed run of " + jobKey);
- LOG.info("Launching " + config.getTaskConfigs().size() + " tasks.");
- stateManager.insertPendingTasks(config.getTaskConfigs());
- return true;
- } else {
- LOG.info("Not yet safe to run cron " + jobKey);
- return false;
- }
- }
- });
- } catch (InterruptedException e) {
- LOG.log(Level.WARNING, "Interrupted while trying to launch cron " + jobKey, e);
- Thread.currentThread().interrupt();
- }
- }
-
- private void killActiveTasks(Set<String> taskIds) {
- if (taskIds.isEmpty()) {
- return;
- }
-
- for (String taskId : taskIds) {
- stateManager.changeState(
- taskId,
- Optional.<ScheduleStatus>absent(),
- KILLING,
- KILL_AUDIT_MESSAGE);
- }
- }
-
- public static CronCollisionPolicy orDefault(@Nullable CronCollisionPolicy policy) {
- return Optional.fromNullable(policy).or(CronCollisionPolicy.KILL_EXISTING);
- }
-
- /**
- * Triggers execution of a cron job, depending on the cron collision policy for the job.
- *
- * @param cronJob The job to be triggered.
- */
- private void cronTriggered(SanitizedCronJob cronJob) {
- final SanitizedConfiguration config = cronJob.config;
- final IJobConfiguration job = config.getJobConfig();
- LOG.info(String.format("Cron triggered for %s at %s with policy %s",
- JobKeys.toPath(job), new Date(), job.getCronCollisionPolicy()));
- cronJobsTriggered.incrementAndGet();
-
- storage.write(new MutateWork.NoResult.Quiet() {
- @Override protected void execute(Storage.MutableStoreProvider storeProvider) {
- ImmutableMap.Builder<Integer, ITaskConfig> builder = ImmutableMap.builder();
- Query.Builder activeQuery = Query.jobScoped(job.getKey()).active();
- Set<String> activeTasks = Tasks.ids(storeProvider.getTaskStore().fetchTasks(activeQuery));
-
- if (activeTasks.isEmpty()) {
- builder.putAll(config.getTaskConfigs());
- } else {
- switch (orDefault(job.getCronCollisionPolicy())) {
- case KILL_EXISTING:
- killActiveTasks(activeTasks);
- delayedRun(activeQuery, config);
- break;
-
- case CANCEL_NEW:
- break;
-
- case RUN_OVERLAP:
- LOG.severe(String.format(
- "Ignoring trigger for job %s with deprecated collision"
- + "policy RUN_OVERLAP due to unterminated active tasks.",
- JobKeys.toPath(job)));
- break;
-
- default:
- LOG.severe("Unrecognized cron collision policy: " + job.getCronCollisionPolicy());
- }
- }
-
- Map<Integer, ITaskConfig> newTasks = builder.build();
- if (!newTasks.isEmpty()) {
- stateManager.insertPendingTasks(newTasks);
- }
- }
- });
- }
-
- /**
- * Updates (re-schedules) the existing cron job.
- *
- * @param config New job configuration to update to.
- * @throws ScheduleException If non-cron job confuration provided.
- */
- public void updateJob(SanitizedConfiguration config) throws ScheduleException {
- IJobConfiguration job = config.getJobConfig();
- if (!hasCronSchedule(job)) {
- throw new ScheduleException("A cron job may not be updated to a non-cron job.");
- }
- String key = scheduledJobs.remove(job.getKey());
- if (key == null) {
- throw new ScheduleException(
- "No cron template found for the given key: " + JobKeys.toPath(job));
- }
- cron.deschedule(key);
- checkArgument(receiveJob(config));
- }
-
- private static boolean hasCronSchedule(IJobConfiguration job) {
- checkNotNull(job);
- return !StringUtils.isEmpty(job.getCronSchedule());
- }
-
- public boolean receiveJob(SanitizedConfiguration config) throws ScheduleException {
- final IJobConfiguration job = config.getJobConfig();
- if (!hasCronSchedule(job)) {
- return false;
- }
-
- if (CronCollisionPolicy.RUN_OVERLAP.equals(job.getCronCollisionPolicy())) {
- throw new ScheduleException(
- "The RUN_OVERLAP collision policy has been removed (AURORA-38).");
- }
-
- SanitizedCronJob cronJob = new SanitizedCronJob(config, cron);
- storage.write(new MutateWork.NoResult.Quiet() {
- @Override
- protected void execute(Storage.MutableStoreProvider storeProvider) {
- storeProvider.getJobStore().saveAcceptedJob(MANAGER_KEY, job);
- }
- });
- mapScheduledJob(cronJob);
-
- return true;
- }
-
- private String scheduleJob(final SanitizedCronJob cronJob) throws ScheduleException {
- IJobConfiguration job = cronJob.config.getJobConfig();
- final String jobPath = JobKeys.toPath(job);
- LOG.info(String.format("Scheduling cron job %s: %s", jobPath, job.getCronSchedule()));
- try {
- return cron.schedule(job.getCronSchedule(), new Runnable() {
- @Override
- public void run() {
- // TODO(William Farner): May want to record information about job runs.
- LOG.info("Running cron job: " + jobPath);
- cronTriggered(cronJob);
- }
- });
- } catch (CronException e) {
- throw new ScheduleException("Failed to schedule cron job: " + e.getMessage(), e);
- }
- }
-
- public Iterable<IJobConfiguration> getJobs() {
- return storage.consistentRead(new Work.Quiet<Iterable<IJobConfiguration>>() {
- @Override
- public Iterable<IJobConfiguration> apply(Storage.StoreProvider storeProvider) {
- return storeProvider.getJobStore().fetchJobs(MANAGER_KEY);
- }
- });
- }
-
- public boolean hasJob(IJobKey jobKey) {
- return fetchJob(jobKey).isPresent();
- }
-
- private Optional<IJobConfiguration> fetchJob(final IJobKey jobKey) {
- checkNotNull(jobKey);
- return storage.consistentRead(new Work.Quiet<Optional<IJobConfiguration>>() {
- @Override
- public Optional<IJobConfiguration> apply(Storage.StoreProvider storeProvider) {
- return storeProvider.getJobStore().fetchJob(MANAGER_KEY, jobKey);
- }
- });
- }
-
- public boolean deleteJob(final IJobKey jobKey) {
- Optional<IJobConfiguration> job = fetchJob(jobKey);
- if (!job.isPresent()) {
- return false;
- }
-
- String scheduledJobKey = scheduledJobs.remove(jobKey);
- if (scheduledJobKey != null) {
- cron.deschedule(scheduledJobKey);
- storage.write(new MutateWork.NoResult.Quiet() {
- @Override
- protected void execute(Storage.MutableStoreProvider storeProvider) {
- storeProvider.getJobStore().removeJob(jobKey);
- }
- });
- LOG.info("Successfully deleted cron job " + jobKey);
- }
- return true;
- }
-
- private final Function<String, String> keyToSchedule = new Function<String, String>() {
- @Override
- public String apply(String key) {
- return cron.getSchedule(key).or("Not found.");
- }
- };
-
- public Map<IJobKey, String> getScheduledJobs() {
- synchronized (scheduledJobs) {
- return ImmutableMap.copyOf(Maps.transformValues(scheduledJobs, keyToSchedule));
- }
- }
-
- public Set<IJobKey> getPendingRuns() {
- synchronized (pendingRuns) {
- return ImmutableSet.copyOf(pendingRuns.keySet());
- }
- }
-
- /**
- * Used by functions that expect field validation before being called.
- */
- private static class SanitizedCronJob {
- private final SanitizedConfiguration config;
-
- SanitizedCronJob(IJobConfiguration unsanitized, CronScheduler cron)
- throws ScheduleException, TaskDescriptionException {
-
- this(SanitizedConfiguration.fromUnsanitized(unsanitized), cron);
- }
-
- SanitizedCronJob(SanitizedConfiguration config, CronScheduler cron) throws ScheduleException {
- final IJobConfiguration job = config.getJobConfig();
- if (!hasCronSchedule(job)) {
- throw new ScheduleException(
- String.format("Not a valid cronjob, %s has no cron schedule", JobKeys.toPath(job)));
- }
-
- if (!cron.isValidSchedule(job.getCronSchedule())) {
- throw new ScheduleException("Invalid cron schedule: " + job.getCronSchedule());
- }
-
- this.config = config;
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/state/LockManagerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/state/LockManagerImpl.java b/src/main/java/org/apache/aurora/scheduler/state/LockManagerImpl.java
index 5696485..e3b5b04 100644
--- a/src/main/java/org/apache/aurora/scheduler/state/LockManagerImpl.java
+++ b/src/main/java/org/apache/aurora/scheduler/state/LockManagerImpl.java
@@ -125,7 +125,7 @@ class LockManagerImpl implements LockManager {
private static String formatLockKey(ILockKey lockKey) {
switch (lockKey.getSetField()) {
case JOB:
- return JobKeys.toPath(lockKey.getJob());
+ return JobKeys.canonicalString(lockKey.getJob());
default:
return "Unknown lock key type: " + lockKey.getSetField();
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/state/SchedulerCoreImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/state/SchedulerCoreImpl.java b/src/main/java/org/apache/aurora/scheduler/state/SchedulerCoreImpl.java
index f330599..d377974 100644
--- a/src/main/java/org/apache/aurora/scheduler/state/SchedulerCoreImpl.java
+++ b/src/main/java/org/apache/aurora/scheduler/state/SchedulerCoreImpl.java
@@ -40,6 +40,9 @@ import org.apache.aurora.scheduler.base.Query;
import org.apache.aurora.scheduler.base.ScheduleException;
import org.apache.aurora.scheduler.base.Tasks;
import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
import org.apache.aurora.scheduler.quota.QuotaCheckResult;
import org.apache.aurora.scheduler.quota.QuotaManager;
import org.apache.aurora.scheduler.storage.Storage;
@@ -49,6 +52,7 @@ import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
import org.apache.aurora.scheduler.storage.entities.IJobKey;
import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
+import org.apache.commons.lang.StringUtils;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -71,7 +75,7 @@ class SchedulerCoreImpl implements SchedulerCore {
// TODO(wfarner): Consider changing this class to not be concerned with cron jobs, requiring the
// caller to deal with the fork.
- private final CronJobManager cronScheduler;
+ private final CronJobManager cronJobManager;
// State manager handles persistence of task modifications and state transitions.
private final StateManager stateManager;
@@ -83,7 +87,7 @@ class SchedulerCoreImpl implements SchedulerCore {
* Creates a new core scheduler.
*
* @param storage Backing store implementation.
- * @param cronScheduler Cron scheduler.
+ * @param cronJobManager Cron scheduler.
* @param stateManager Persistent state manager.
* @param taskIdGenerator Task ID generator.
* @param quotaManager Quota manager.
@@ -91,13 +95,13 @@ class SchedulerCoreImpl implements SchedulerCore {
@Inject
public SchedulerCoreImpl(
Storage storage,
- CronJobManager cronScheduler,
+ CronJobManager cronJobManager,
StateManager stateManager,
TaskIdGenerator taskIdGenerator,
QuotaManager quotaManager) {
this.storage = checkNotNull(storage);
- this.cronScheduler = cronScheduler;
+ this.cronJobManager = cronJobManager;
this.stateManager = checkNotNull(stateManager);
this.taskIdGenerator = checkNotNull(taskIdGenerator);
this.quotaManager = checkNotNull(quotaManager);
@@ -108,7 +112,20 @@ class SchedulerCoreImpl implements SchedulerCore {
storage,
Query.jobScoped(job.getKey()).active()).isEmpty();
- return hasActiveTasks || cronScheduler.hasJob(job.getKey());
+ return hasActiveTasks || cronJobManager.hasJob(job.getKey());
+ }
+
+ private static boolean isCron(SanitizedConfiguration config) {
+ if (!config.getJobConfig().isSetCronSchedule()) {
+ return false;
+ } else if (StringUtils.isEmpty(config.getJobConfig().getCronSchedule())) {
+ // TODO(ksweeney): Remove this in 0.7.0 (AURORA-423).
+ LOG.warning("Got service config with empty string cron schedule. aurora-0.7.x "
+ + "will interpret this as cron job and cause an error.");
+ return false;
+ } else {
+ return true;
+ }
}
@Override
@@ -120,12 +137,19 @@ class SchedulerCoreImpl implements SchedulerCore {
protected void execute(MutableStoreProvider storeProvider) throws ScheduleException {
final IJobConfiguration job = sanitizedConfiguration.getJobConfig();
if (hasActiveJob(job)) {
- throw new ScheduleException("Job already exists: " + JobKeys.toPath(job));
+ throw new ScheduleException(
+ "Job already exists: " + JobKeys.canonicalString(job.getKey()));
}
validateTaskLimits(job.getTaskConfig(), job.getInstanceCount());
- if (!cronScheduler.receiveJob(sanitizedConfiguration)) {
+ if (isCron(sanitizedConfiguration)) {
+ try {
+ cronJobManager.createJob(SanitizedCronJob.from(sanitizedConfiguration));
+ } catch (CronException e) {
+ throw new ScheduleException(e);
+ }
+ } else {
LOG.info("Launching " + sanitizedConfiguration.getTaskConfigs().size() + " tasks.");
stateManager.insertPendingTasks(sanitizedConfiguration.getTaskConfigs());
}
@@ -216,7 +240,7 @@ class SchedulerCoreImpl implements SchedulerCore {
// it.
// TODO(maxim): Should be trivial to support killing multiple jobs instead.
IJobKey jobKey = Iterables.getOnlyElement(JobKeys.from(query).get());
- cronScheduler.deleteJob(jobKey);
+ cronJobManager.deleteJob(jobKey);
}
// Unless statuses were specifically supplied, only attempt to kill active tasks.
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/state/StateModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/state/StateModule.java b/src/main/java/org/apache/aurora/scheduler/state/StateModule.java
index 7d26082..9db2a1a 100644
--- a/src/main/java/org/apache/aurora/scheduler/state/StateModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/state/StateModule.java
@@ -49,17 +49,10 @@ public class StateModule extends AbstractModule {
bind(LockManager.class).to(LockManagerImpl.class);
bind(LockManagerImpl.class).in(Singleton.class);
- bindCronJobManager(binder());
bindMaintenanceController(binder());
}
@VisibleForTesting
- static void bindCronJobManager(Binder binder) {
- binder.bind(CronJobManager.class).in(Singleton.class);
- PubsubEventModule.bindSubscriber(binder, CronJobManager.class);
- }
-
- @VisibleForTesting
static void bindMaintenanceController(Binder binder) {
binder.bind(MaintenanceController.class).to(MaintenanceControllerImpl.class);
binder.bind(MaintenanceControllerImpl.class).in(Singleton.class);
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java b/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
index 9bb5c25..f101143 100644
--- a/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
+++ b/src/main/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterface.java
@@ -94,11 +94,14 @@ import org.apache.aurora.scheduler.base.Tasks;
import org.apache.aurora.scheduler.configuration.ConfigurationManager;
import org.apache.aurora.scheduler.configuration.ConfigurationManager.TaskDescriptionException;
import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.CronJobManager;
import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
import org.apache.aurora.scheduler.quota.QuotaInfo;
import org.apache.aurora.scheduler.quota.QuotaManager;
import org.apache.aurora.scheduler.quota.QuotaManager.QuotaException;
-import org.apache.aurora.scheduler.state.CronJobManager;
import org.apache.aurora.scheduler.state.LockManager;
import org.apache.aurora.scheduler.state.LockManager.LockException;
import org.apache.aurora.scheduler.state.MaintenanceController;
@@ -242,7 +245,7 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
schedulerCore.createJob(sanitized);
response.setResponseCode(OK)
.setMessage(String.format("%d new tasks pending for job %s",
- sanitized.getJobConfig().getInstanceCount(), JobKeys.toPath(job)));
+ sanitized.getJobConfig().getInstanceCount(), JobKeys.canonicalString(job.getKey())));
} catch (LockException e) {
response.setResponseCode(LOCK_ERROR).setMessage(e.getMessage());
} catch (TaskDescriptionException | ScheduleException e) {
@@ -275,11 +278,11 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
ILockKey.build(LockKey.job(jobKey.newBuilder())),
Optional.fromNullable(mutableLock).transform(ILock.FROM_BUILDER));
- cronJobManager.updateJob(SanitizedConfiguration.fromUnsanitized(job));
+ cronJobManager.updateJob(SanitizedCronJob.fromUnsanitized(job));
return response.setResponseCode(OK).setMessage("Replaced template for: " + jobKey);
} catch (LockException e) {
return response.setResponseCode(LOCK_ERROR).setMessage(e.getMessage());
- } catch (TaskDescriptionException | ScheduleException e) {
+ } catch (CronException | TaskDescriptionException e) {
return response.setResponseCode(INVALID_REQUEST).setMessage(e.getMessage());
}
}
@@ -314,21 +317,16 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
try {
sessionValidator.checkAuthenticated(session, ImmutableSet.of(jobKey.getRole()));
} catch (AuthFailedException e) {
- response.setResponseCode(AUTH_FAILED).setMessage(e.getMessage());
- return response;
+ return response.setResponseCode(AUTH_FAILED).setMessage(e.getMessage());
}
try {
cronJobManager.startJobNow(jobKey);
- response.setResponseCode(OK).setMessage("Cron run started.");
- } catch (ScheduleException e) {
- response.setResponseCode(INVALID_REQUEST)
+ return response.setResponseCode(OK).setMessage("Cron run started.");
+ } catch (CronException e) {
+ return response.setResponseCode(INVALID_REQUEST)
.setMessage("Failed to start cron job - " + e.getMessage());
- } catch (TaskDescriptionException e) {
- response.setResponseCode(ERROR).setMessage("Invalid task description: " + e.getMessage());
}
-
- return response;
}
// TODO(William Farner): Provide status information about cron jobs here.
@@ -386,13 +384,14 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
@Override
public JobSummary apply(IJobKey jobKey) {
IJobConfiguration job = jobs.get(jobKey);
- JobSummary smry = new JobSummary()
+ JobSummary summary = new JobSummary()
.setJob(job.newBuilder())
.setStats(Jobs.getJobStats(tasks.get(jobKey)).newBuilder());
return Strings.isNullOrEmpty(job.getCronSchedule())
- ? smry
- : smry.setNextCronRunMs(cronPredictor.predictNextRun(job.getCronSchedule()).getTime());
+ ? summary
+ : summary.setNextCronRunMs(
+ cronPredictor.predictNextRun(CrontabEntry.parse(job.getCronSchedule())).getTime());
}
};
@@ -826,7 +825,8 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
jobsByKey(jobStore, existingJob.getKey());
switch (matches.size()) {
case 0:
- error = Optional.of("No jobs found for key " + JobKeys.toPath(existingJob));
+ error = Optional.of(
+ "No jobs found for key " + JobKeys.canonicalString(existingJob.getKey()));
break;
case 1:
@@ -834,14 +834,16 @@ class SchedulerThriftInterface implements AuroraAdmin.Iface {
Iterables.getOnlyElement(matches.entries());
IJobConfiguration storedJob = match.getValue();
if (!storedJob.equals(existingJob)) {
- error = Optional.of("CAS compare failed for " + JobKeys.toPath(storedJob));
+ error = Optional.of(
+ "CAS compare failed for " + JobKeys.canonicalString(storedJob.getKey()));
} else {
jobStore.saveAcceptedJob(match.getKey(), rewrittenJob);
}
break;
default:
- error = Optional.of("Multiple jobs found for key " + JobKeys.toPath(existingJob));
+ error = Optional.of("Multiple jobs found for key "
+ + JobKeys.canonicalString(existingJob.getKey()));
}
}
break;
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/python/apache/aurora/config/thrift.py
----------------------------------------------------------------------
diff --git a/src/main/python/apache/aurora/config/thrift.py b/src/main/python/apache/aurora/config/thrift.py
index 1798c40..0cd9246 100644
--- a/src/main/python/apache/aurora/config/thrift.py
+++ b/src/main/python/apache/aurora/config/thrift.py
@@ -250,9 +250,6 @@ def convert(job, metadata=frozenset(), ports=frozenset()):
if unbound:
raise InvalidConfig('Config contains unbound variables: %s' % ' '.join(map(str, unbound)))
- cron_schedule = not_empty_or(job.cron_schedule(), '')
- cron_policy = select_cron_policy(job.cron_policy(), job.cron_collision_policy())
-
task.executorConfig = ExecutorConfig(
name=AURORA_EXECUTOR_NAME,
data=filter_aliased_fields(underlying).json_dumps())
@@ -260,7 +257,7 @@ def convert(job, metadata=frozenset(), ports=frozenset()):
return JobConfiguration(
key=key,
owner=owner,
- cronSchedule=cron_schedule,
- cronCollisionPolicy=cron_policy,
+ cronSchedule=not_empty_or(job.cron_schedule(), None),
+ cronCollisionPolicy=select_cron_policy(job.cron_policy(), job.cron_collision_policy()),
taskConfig=task,
instanceCount=fully_interpolated(job.instances()))
[5/5] git commit: CronScheduler based on Quartz
Posted by ke...@apache.org.
CronScheduler based on Quartz
This introduces a new CronScheduler based on Quartz and removes the
NoopCronScheduler.
Testing Done:
./gradlew build
Bugs closed: AURORA-132, AURORA-349
Reviewed at https://reviews.apache.org/r/19767/
Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/3361dbed
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/3361dbed
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/3361dbed
Branch: refs/heads/master
Commit: 3361dbedfad8f2b1fcf05488a259f9c35546dc7c
Parents: 2759696
Author: Kevin Sweeney <ke...@apache.org>
Authored: Wed May 14 19:34:33 2014 -0700
Committer: Kevin Sweeney <ke...@apache.org>
Committed: Wed May 14 19:34:33 2014 -0700
----------------------------------------------------------------------
build.gradle | 1 +
.../aurora/scheduler/MesosTaskFactory.java | 2 +-
.../aurora/scheduler/app/SchedulerMain.java | 20 +-
.../aurora/scheduler/async/TaskGroups.java | 2 +-
.../apache/aurora/scheduler/base/JobKeys.java | 40 +-
.../configuration/ConfigurationManager.java | 12 +-
.../aurora/scheduler/cron/CronJobManager.java | 97 +
.../aurora/scheduler/cron/CronPredictor.java | 2 +-
.../aurora/scheduler/cron/CronScheduler.java | 40 +-
.../aurora/scheduler/cron/CrontabEntryTest.java | 163 -
.../aurora/scheduler/cron/SanitizedCronJob.java | 131 +
.../scheduler/cron/noop/NoopCronModule.java | 40 -
.../scheduler/cron/noop/NoopCronPredictor.java | 33 -
.../scheduler/cron/noop/NoopCronScheduler.java | 83 -
.../scheduler/cron/quartz/AuroraCronJob.java | 231 ++
.../cron/quartz/AuroraCronJobFactory.java | 49 +
.../cron/quartz/CronJobManagerImpl.java | 256 ++
.../scheduler/cron/quartz/CronLifecycle.java | 114 +
.../scheduler/cron/quartz/CronModule.java | 130 +
.../cron/quartz/CronPredictorImpl.java | 46 +
.../cron/quartz/CronSchedulerImpl.java | 71 +
.../aurora/scheduler/cron/quartz/Quartz.java | 124 +
.../scheduler/cron/testing/AbstractCronIT.java | 135 -
.../org/apache/aurora/scheduler/http/Cron.java | 3 +-
.../aurora/scheduler/http/ServletModule.java | 2 +-
.../aurora/scheduler/http/StructDump.java | 15 +-
.../apache/aurora/scheduler/sla/SlaGroup.java | 2 +-
.../aurora/scheduler/state/CronJobManager.java | 484 ---
.../aurora/scheduler/state/LockManagerImpl.java | 2 +-
.../scheduler/state/SchedulerCoreImpl.java | 40 +-
.../aurora/scheduler/state/StateModule.java | 7 -
.../thrift/SchedulerThriftInterface.java | 40 +-
src/main/python/apache/aurora/config/thrift.py | 7 +-
.../cron/testing/cron-schedule-predictions.json | 3332 ------------------
.../aurora/scheduler/cron/CrontabEntryTest.java | 168 +
.../scheduler/cron/ExpectedPrediction.java | 57 +
.../aurora/scheduler/cron/noop/NoopCronIT.java | 60 -
.../cron/quartz/AuroraCronJobTest.java | 174 +
.../aurora/scheduler/cron/quartz/CronIT.java | 257 ++
.../cron/quartz/CronJobManagerImplTest.java | 221 ++
.../cron/quartz/CronPredictorImplTest.java | 89 +
.../scheduler/cron/quartz/QuartzTestUtil.java | 78 +
.../state/BaseSchedulerCoreImplTest.java | 366 +-
.../scheduler/state/CronJobManagerTest.java | 490 ---
.../scheduler/state/LockManagerImplTest.java | 2 +-
.../thrift/SchedulerThriftInterfaceTest.java | 17 +-
.../aurora/scheduler/thrift/ThriftIT.java | 4 +-
.../scheduler/cron/expected-predictions.json | 3332 ++++++++++++++++++
48 files changed, 5831 insertions(+), 5240 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/build.gradle
----------------------------------------------------------------------
diff --git a/build.gradle b/build.gradle
index f2c729e..6f43fea 100644
--- a/build.gradle
+++ b/build.gradle
@@ -164,6 +164,7 @@ dependencies {
compile 'org.apache.mesos:mesos:0.18.0'
compile thriftLib
compile 'org.apache.zookeeper:zookeeper:3.3.4'
+ compile 'org.quartz-scheduler:quartz:2.2.1'
compile "org.slf4j:slf4j-api:${slf4jRev}"
compile "org.slf4j:slf4j-jdk14:${slf4jRev}"
compile 'com.twitter.common.logging:log4j:0.0.7'
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java b/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
index 86bbc29..bdd8c19 100644
--- a/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
+++ b/src/main/java/org/apache/aurora/scheduler/MesosTaskFactory.java
@@ -135,7 +135,7 @@ public interface MesosTaskFactory {
}
TaskInfo.Builder taskBuilder =
TaskInfo.newBuilder()
- .setName(JobKeys.toPath(Tasks.ASSIGNED_TO_JOB_KEY.apply(task)))
+ .setName(JobKeys.canonicalString(Tasks.ASSIGNED_TO_JOB_KEY.apply(task)))
.setTaskId(TaskID.newBuilder().setValue(task.getTaskId()))
.setSlaveId(slaveId)
.addAllResources(resources)
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java b/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
index bf3d7a3..da6f5e5 100644
--- a/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
+++ b/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
@@ -59,9 +59,7 @@ import org.apache.aurora.scheduler.DriverFactory;
import org.apache.aurora.scheduler.DriverFactory.DriverFactoryImpl;
import org.apache.aurora.scheduler.MesosTaskFactory.ExecutorConfig;
import org.apache.aurora.scheduler.SchedulerLifecycle;
-import org.apache.aurora.scheduler.cron.CronPredictor;
-import org.apache.aurora.scheduler.cron.CronScheduler;
-import org.apache.aurora.scheduler.cron.noop.NoopCronModule;
+import org.apache.aurora.scheduler.cron.quartz.CronModule;
import org.apache.aurora.scheduler.local.IsolatedSchedulerModule;
import org.apache.aurora.scheduler.log.mesos.MesosLogStreamModule;
import org.apache.aurora.scheduler.storage.backup.BackupModule;
@@ -115,17 +113,7 @@ public class SchedulerMain extends AbstractApplication {
.add(CapabilityValidator.class)
.build();
- @CmdLine(name = "cron_module",
- help = "A Guice module to provide cron bindings. NOTE: The default is a no-op.")
- private static final Arg<? extends Class<? extends Module>> CRON_MODULE =
- Arg.create(NoopCronModule.class);
-
- private static final Iterable<Class<?>> CRON_MODULE_CLASSES = ImmutableList.<Class<?>>builder()
- .add(CronPredictor.class)
- .add(CronScheduler.class)
- .build();
-
- // TODO(Suman Karumuri): Pass in AUTH and CRON modules as extra modules
+ // TODO(Suman Karumuri): Pass in AUTH as extra module
@CmdLine(name = "extra_modules",
help = "A list of modules that provide additional functionality.")
private static final Arg<List<Class<? extends Module>>> EXTRA_MODULES =
@@ -151,8 +139,7 @@ public class SchedulerMain extends AbstractApplication {
private static Iterable<? extends Module> getExtraModules() {
Builder<Module> modules = ImmutableList.builder();
- modules.add(Modules.wrapInPrivateModule(AUTH_MODULE.get(), AUTH_MODULE_CLASSES))
- .add(Modules.wrapInPrivateModule(CRON_MODULE.get(), CRON_MODULE_CLASSES));
+ modules.add(Modules.wrapInPrivateModule(AUTH_MODULE.get(), AUTH_MODULE_CLASSES));
for (Class<? extends Module> moduleClass : EXTRA_MODULES.get()) {
modules.add(Modules.getModule(moduleClass));
@@ -173,6 +160,7 @@ public class SchedulerMain extends AbstractApplication {
.addAll(getExtraModules())
.add(new LogStorageModule())
.add(new MemStorageModule(Bindings.annotatedKeyFactory(LogStorage.WriteBehind.class)))
+ .add(new CronModule())
.add(new ThriftModule())
.add(new ThriftAuthModule())
.build();
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java b/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
index 38e19aa..54748b2 100644
--- a/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
+++ b/src/main/java/org/apache/aurora/scheduler/async/TaskGroups.java
@@ -244,7 +244,7 @@ public class TaskGroups implements EventSubscriber {
@Override
public String toString() {
- return JobKeys.toPath(Tasks.INFO_TO_JOB_KEY.apply(canonicalTask));
+ return JobKeys.canonicalString(Tasks.INFO_TO_JOB_KEY.apply(canonicalTask));
}
}
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java b/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
index db1bec4..c81ac62 100644
--- a/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
+++ b/src/main/java/org/apache/aurora/scheduler/base/JobKeys.java
@@ -15,17 +15,20 @@
*/
package org.apache.aurora.scheduler.base;
+import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.base.Functions;
+import com.google.common.base.Joiner;
import com.google.common.base.Optional;
-import com.google.common.base.Strings;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import org.apache.aurora.gen.JobKey;
import org.apache.aurora.gen.TaskQuery;
+import org.apache.aurora.scheduler.configuration.ConfigurationManager;
import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
import org.apache.aurora.scheduler.storage.entities.IJobKey;
import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
@@ -83,9 +86,9 @@ public final class JobKeys {
*/
public static boolean isValid(@Nullable IJobKey jobKey) {
return jobKey != null
- && !Strings.isNullOrEmpty(jobKey.getRole())
- && !Strings.isNullOrEmpty(jobKey.getEnvironment())
- && !Strings.isNullOrEmpty(jobKey.getName());
+ && ConfigurationManager.isGoodIdentifier(jobKey.getRole())
+ && ConfigurationManager.isGoodIdentifier(jobKey.getEnvironment())
+ && ConfigurationManager.isGoodIdentifier(jobKey.getName());
}
/**
@@ -132,25 +135,32 @@ public final class JobKeys {
}
/**
- * Create a "/"-delimited String representation of a job key, suitable for logging but not
- * necessarily suitable for use as a unique identifier.
+ * Create a "/"-delimited representation of job key usable as a unique identifier in this cluster.
*
+ * It is guaranteed that {@code k.equals(JobKeys.parse(JobKeys.canonicalString(k))}.
+ *
+ * @see #parse(String)
* @param jobKey Key to represent.
- * @return "/"-delimited representation of the key.
+ * @return Canonical "/"-delimited representation of the key.
*/
- public static String toPath(IJobKey jobKey) {
- return jobKey.getRole() + "/" + jobKey.getEnvironment() + "/" + jobKey.getName();
+ public static String canonicalString(IJobKey jobKey) {
+ return Joiner.on("/").join(jobKey.getRole(), jobKey.getEnvironment(), jobKey.getName());
}
/**
- * Create a "/"-delimited String representation of job key, suitable for logging but not
- * necessarily suitable for use as a unique identifier.
+ * Create a job key from a "role/environment/name" representation.
+ *
+ * It is guaranteed that {@code k.equals(JobKeys.parse(JobKeys.canonicalString(k))}.
*
- * @param job Job to represent.
- * @return "/"-delimited representation of the job's key.
+ * @see #canonicalString(IJobKey)
+ * @param string Input to parse.
+ * @return Parsed representation.
+ * @throws IllegalArgumentException when the string fails to parse.
*/
- public static String toPath(IJobConfiguration job) {
- return toPath(job.getKey());
+ public static IJobKey parse(String string) throws IllegalArgumentException {
+ List<String> components = Splitter.on("/").splitToList(string);
+ checkArgument(components.size() == 3);
+ return from(components.get(0), components.get(1), components.get(2));
}
/**
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
index 82034e0..e5ad461 100644
--- a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
+++ b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
@@ -164,9 +164,15 @@ public final class ConfigurationManager {
// Utility class.
}
- @VisibleForTesting
- static boolean isGoodIdentifier(String identifier) {
- return GOOD_IDENTIFIER.matcher(identifier).matches()
+ /**
+ * Verifies that an identifier is an acceptable name component.
+ *
+ * @param identifier Identifier to check.
+ * @return false if the identifier is null or invalid.
+ */
+ public static boolean isGoodIdentifier(@Nullable String identifier) {
+ return identifier != null
+ && GOOD_IDENTIFIER.matcher(identifier).matches()
&& (identifier.length() <= MAX_IDENTIFIER_LENGTH);
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java b/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java
new file mode 100644
index 0000000..7c8d5ec
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/CronJobManager.java
@@ -0,0 +1,97 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron;
+
+import java.util.Map;
+
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+
+/**
+ * Manages the persistence and scheduling of jobs that should be run periodically on a cron
+ * schedule.
+ */
+public interface CronJobManager {
+ /**
+ * Triggers execution of a job.
+ *
+ * @param jobKey Key of the job to start.
+ * @throws CronException If the job could not be started with the cron system.
+ */
+ void startJobNow(IJobKey jobKey) throws CronException;
+
+ /**
+ * Persist a new cron job to storage and schedule it for future execution.
+ *
+ * @param config Cron job configuration to update to.
+ * @throws CronException If a job with the same key does not exist or the job could not be
+ * scheduled.
+ */
+ void updateJob(SanitizedCronJob config) throws CronException;
+
+ /**
+ * Persist a cron job to storage and schedule it for future execution.
+ *
+ * @param config New cron job configuration.
+ * @throws CronException If a job with the same key exists or the job could not be scheduled.
+ */
+ void createJob(SanitizedCronJob config) throws CronException;
+
+ /**
+ * Get all cron jobs.
+ *
+ * TODO(ksweeney): Consider deprecating this and letting caller query storage directly.
+ *
+ * @return An immutable snapshot of cron jobs at some instant.
+ */
+ Iterable<IJobConfiguration> getJobs();
+
+ /**
+ * Test whether a job exists.
+ *
+ * TODO(ksweeney): Consider deprecating this and letting caller query storage directly.
+ *
+ * @param jobKey Key of the job to check.
+ * @return false when a job does not exist in storage.
+ */
+ boolean hasJob(IJobKey jobKey);
+
+ /**
+ * Remove a job and deschedule it.
+ *
+ * @param jobKey Key of the job to delete.
+ * @return true if a job was removed.
+ */
+ boolean deleteJob(IJobKey jobKey);
+
+ /**
+ * A list of the currently scheduled jobs and when they will run according to the underlying
+ * execution engine.
+ *
+ * @return A map from job to the cron schedule in use for that job.
+ */
+ Map<IJobKey, CrontabEntry> getScheduledJobs();
+
+ /**
+ * The unique ID of this cron job manager, used as a prefix in the JobStore.
+ *
+ * TODO(ksweeney): Consider removing this from storage entirely since the JobManager abstraction
+ * is gone.
+ *
+ * @return The unique ID of the manager.
+ */
+ String getManagerKey();
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java b/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
index df0c378..0ce60f8 100644
--- a/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
+++ b/src/main/java/org/apache/aurora/scheduler/cron/CronPredictor.java
@@ -27,5 +27,5 @@ public interface CronPredictor {
* @param schedule Cron schedule to predict the next time for.
* @return A prediction for the next time a cron will run.
*/
- Date predictNextRun(String schedule);
+ Date predictNextRun(CrontabEntry schedule);
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java b/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
index 56e9950..f38dea5 100644
--- a/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
+++ b/src/main/java/org/apache/aurora/scheduler/cron/CronScheduler.java
@@ -15,49 +15,19 @@
*/
package org.apache.aurora.scheduler.cron;
-import javax.annotation.Nullable;
-
import com.google.common.base.Optional;
-import com.google.common.util.concurrent.Service;
+
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
/**
* An execution manager that executes work on a cron schedule.
*/
-public interface CronScheduler extends Service {
- /**
- * Schedules a task on a cron schedule.
- *
- * @param schedule Cron-style schedule.
- * @param task Work to run when on the cron schedule.
- * @return A unique ID to identify the scheduled cron task.
- * @throws CronException when there was a failure to schedule, for example if {@code schedule}
- * is not a valid input.
- * @throws IllegalStateException If the cron scheduler is not currently running.
- */
- String schedule(String schedule, Runnable task) throws CronException, IllegalStateException;
-
- /**
- * Removes a scheduled cron item.
- *
- * @param key Key previously returned from {@link #schedule(String, Runnable)}.
- * @throws IllegalStateException If the cron scheduler is not currently running.
- */
- void deschedule(String key) throws IllegalStateException;
-
+public interface CronScheduler {
/**
* Gets the cron schedule associated with a scheduling key.
*
- * @param key Key previously returned from {@link #schedule(String, Runnable)}.
+ * @param key Key previously returned from {@link #schedule(CrontabEntry, Runnable)}.
* @return The task's cron schedule, if a matching task was found.
- * @throws IllegalStateException If the cron scheduler is not currently running.
- */
- Optional<String> getSchedule(String key) throws IllegalStateException;
-
- /**
- * Checks to see if the scheduler would be accepted by the underlying scheduler.
- *
- * @param schedule Cron scheduler to validate.
- * @return {@code true} if the schedule is valid.
*/
- boolean isValidSchedule(@Nullable String schedule);
+ Optional<CrontabEntry> getSchedule(IJobKey key);
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java b/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
deleted file mode 100644
index 2bb848a..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/**
- * Copyright 2014 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.cron;
-
-import java.util.List;
-import java.util.Set;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Sets;
-
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-public class CrontabEntryTest {
- @Test
- public void testHashCodeAndEquals() {
-
- List<CrontabEntry> entries = ImmutableList.of(
- CrontabEntry.parse("* * * * *"),
- CrontabEntry.parse("0-59 * * * *"),
- CrontabEntry.parse("0-57,58,59 * * * *"),
- CrontabEntry.parse("* 23,1,2,4,0-22 * * *"),
- CrontabEntry.parse("1-50,0,51-59 * * * sun-sat"));
-
- for (CrontabEntry lhs : entries) {
- for (CrontabEntry rhs : entries) {
- assertEquals(lhs, rhs);
- }
- }
-
- Set<CrontabEntry> equivalentEntries = Sets.newHashSet(entries);
- assertTrue(equivalentEntries.size() == 1);
- }
-
- @Test
- public void testEqualsCoverage() {
- assertNotEquals(CrontabEntry.parse("* * * * *"), new Object());
-
- assertNotEquals(CrontabEntry.parse("* * * * *"), CrontabEntry.parse("1 * * * *"));
- assertEquals(CrontabEntry.parse("1,2,3 * * * *"), CrontabEntry.parse("1-3 * * * *"));
-
- assertNotEquals(CrontabEntry.parse("* 0-22 * * *"), CrontabEntry.parse("* * * * *"));
- assertEquals(CrontabEntry.parse("* 0-23 * * *"), CrontabEntry.parse("* * * * *"));
-
- assertNotEquals(CrontabEntry.parse("1 1 1-30 * *"), CrontabEntry.parse("1 1 * * *"));
- assertEquals(CrontabEntry.parse("1 1 1-31 * *"), CrontabEntry.parse("1 1 * * *"));
-
- assertNotEquals(CrontabEntry.parse("1 1 * JAN,FEB-NOV *"), CrontabEntry.parse("1 1 * * *"));
- assertEquals(CrontabEntry.parse("1 1 * JAN,FEB-DEC *"), CrontabEntry.parse("1 1 * * *"));
-
- assertNotEquals(CrontabEntry.parse("* * * * SUN"), CrontabEntry.parse("* * * * SAT"));
- assertEquals(CrontabEntry.parse("* * * * 0"), CrontabEntry.parse("* * * * SUN"));
- }
-
- @Test
- public void testSkip() {
- assertEquals(CrontabEntry.parse("*/15 * * * *"), CrontabEntry.parse("0,15,30,45 * * * *"));
- assertEquals(
- CrontabEntry.parse("* */2 * * *"),
- CrontabEntry.parse("0-59 0,2,4,6,8,10,12-23/2 * * *"));
- }
-
- @Test
- public void testToString() {
- assertEquals("0-58 * * * *", CrontabEntry.parse("0,1-57,58 * * * *").toString());
- assertEquals("* * * * *", CrontabEntry.parse("* * * * *").toString());
- }
-
- @Test
- public void testWildcards() {
- CrontabEntry wildcardMinuteEntry = CrontabEntry.parse("* 1 1 1 *");
- assertEquals("*", wildcardMinuteEntry.getMinuteAsString());
- assertTrue(wildcardMinuteEntry.hasWildcardMinute());
- assertFalse(wildcardMinuteEntry.hasWildcardHour());
- assertFalse(wildcardMinuteEntry.hasWildcardDayOfMonth());
- assertFalse(wildcardMinuteEntry.hasWildcardMonth());
- assertTrue(wildcardMinuteEntry.hasWildcardDayOfWeek());
-
- CrontabEntry wildcardHourEntry = CrontabEntry.parse("1 * 1 1 *");
- assertEquals("*", wildcardHourEntry.getHourAsString());
- assertFalse(wildcardHourEntry.hasWildcardMinute());
- assertTrue(wildcardHourEntry.hasWildcardHour());
- assertFalse(wildcardHourEntry.hasWildcardDayOfMonth());
- assertFalse(wildcardHourEntry.hasWildcardMonth());
- assertTrue(wildcardHourEntry.hasWildcardDayOfWeek());
-
- CrontabEntry wildcardDayOfMonth = CrontabEntry.parse("1 1 * 1 *");
- assertEquals("*", wildcardDayOfMonth.getDayOfMonthAsString());
- assertFalse(wildcardDayOfMonth.hasWildcardMinute());
- assertFalse(wildcardDayOfMonth.hasWildcardHour());
- assertTrue(wildcardDayOfMonth.hasWildcardDayOfMonth());
- assertFalse(wildcardDayOfMonth.hasWildcardMonth());
- assertTrue(wildcardDayOfMonth.hasWildcardDayOfWeek());
-
- CrontabEntry wildcardMonth = CrontabEntry.parse("1 1 1 * *");
- assertEquals("*", wildcardMonth.getMonthAsString());
- assertFalse(wildcardMonth.hasWildcardMinute());
- assertFalse(wildcardMonth.hasWildcardHour());
- assertFalse(wildcardMonth.hasWildcardDayOfMonth());
- assertTrue(wildcardMonth.hasWildcardMonth());
- assertTrue(wildcardMonth.hasWildcardDayOfWeek());
-
- CrontabEntry wildcardDayOfWeek = CrontabEntry.parse("1 1 1 1 *");
- assertEquals("*", wildcardDayOfWeek.getDayOfWeekAsString());
- assertFalse(wildcardDayOfWeek.hasWildcardMinute());
- assertFalse(wildcardDayOfWeek.hasWildcardHour());
- assertFalse(wildcardDayOfWeek.hasWildcardDayOfMonth());
- assertFalse(wildcardDayOfWeek.hasWildcardMonth());
- assertTrue(wildcardDayOfWeek.hasWildcardDayOfWeek());
- }
-
- @Test
- public void testEqualsIsCanonical() {
- String rawEntry = "* * */3 * *";
- CrontabEntry input = CrontabEntry.parse(rawEntry);
- assertNotEquals(
- rawEntry + " is not the canonical form of " + input,
- rawEntry,
- input.toString());
- assertEquals(
- "The form returned by toString is canonical",
- input.toString(),
- CrontabEntry.parse(input.toString()).toString());
- }
-
- @Test
- public void testBadEntries() {
- List<String> badPatterns = ImmutableList.of(
- "* * * * MON-SUN",
- "* * **",
- "0-59 0-59 * * *",
- "1/1 * * * *",
- "5 5 * MAR-JAN *",
- "*/0 * * * *",
- "0-59/0 * * * *",
- "0-59/60 * * * *",
- "* * * *, *",
- "* * 1 * 1"
- );
-
- for (String pattern : badPatterns) {
- assertNull(CrontabEntry.tryParse(pattern).orNull());
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java b/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java
new file mode 100644
index 0000000..0082932
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/SanitizedCronJob.java
@@ -0,0 +1,131 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron;
+
+import javax.annotation.Nullable;
+
+import com.google.common.base.Optional;
+
+import org.apache.aurora.gen.CronCollisionPolicy;
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.configuration.ConfigurationManager;
+import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.commons.lang.StringUtils;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Used by functions that expect field validation before being called.
+ */
+public final class SanitizedCronJob {
+ private final SanitizedConfiguration config;
+ private final CrontabEntry crontabEntry;
+
+ private SanitizedCronJob(IJobConfiguration unsanitized)
+ throws CronException, ConfigurationManager.TaskDescriptionException {
+
+ this(SanitizedConfiguration.fromUnsanitized(unsanitized));
+ }
+
+ private SanitizedCronJob(SanitizedConfiguration config) throws CronException {
+ final IJobConfiguration job = config.getJobConfig();
+ if (!hasCronSchedule(job)) {
+ throw new CronException(String.format(
+ "Not a valid cron job, %s has no cron schedule", JobKeys.canonicalString(job.getKey())));
+ }
+
+ Optional<CrontabEntry> entry = CrontabEntry.tryParse(job.getCronSchedule());
+ if (!entry.isPresent()) {
+ throw new CronException("Invalid cron schedule: " + job.getCronSchedule());
+ }
+
+ this.config = config;
+ this.crontabEntry = entry.get();
+ }
+
+ /**
+ * Get the default cron collision policy.
+ *
+ * @param policy A (possibly null) policy.
+ * @return The given policy or a default if the policy was null.
+ */
+ public static CronCollisionPolicy orDefault(@Nullable CronCollisionPolicy policy) {
+ return Optional.fromNullable(policy).or(CronCollisionPolicy.KILL_EXISTING);
+ }
+
+ /**
+ * Create a SanitizedCronJob from a SanitizedConfiguration. SanitizedCronJob performs additional
+ * validation to ensure that the provided job contains all properties needed to run it on a
+ * cron schedule.
+ *
+ * @param config Config to validate.
+ * @return Config wrapped in defaults.
+ * @throws CronException If a cron-specific validation error occured.
+ */
+ public static SanitizedCronJob from(SanitizedConfiguration config)
+ throws CronException {
+
+ return new SanitizedCronJob(config);
+ }
+
+ /**
+ * Create a cron job from an unsanitized input job. Suitable for RPC input validation.
+ *
+ * @param unsanitized Unsanitized input job.
+ * @return A sanitized job if all validation succeeds.
+ * @throws CronException If validation fails with a cron-specific error.
+ * @throws ConfigurationManager.TaskDescriptionException If validation fails with a non
+ * cron-specific error.
+ */
+ public static SanitizedCronJob fromUnsanitized(IJobConfiguration unsanitized)
+ throws CronException, ConfigurationManager.TaskDescriptionException {
+
+ return new SanitizedCronJob(unsanitized);
+ }
+
+ /**
+ * Get this job's cron collision policy.
+ *
+ * @return This job's cron collision policy.
+ */
+ public CronCollisionPolicy getCronCollisionPolicy() {
+ return orDefault(config.getJobConfig().getCronCollisionPolicy());
+ }
+
+ private static boolean hasCronSchedule(IJobConfiguration job) {
+ checkNotNull(job);
+ return !StringUtils.isEmpty(job.getCronSchedule());
+ }
+
+ /**
+ * Returns the cron schedule associated with this job.
+ *
+ * @return The cron schedule associated with this job.
+ */
+ public CrontabEntry getCrontabEntry() {
+ return crontabEntry;
+ }
+
+ /**
+ * Returns the sanitized job configuration associated with the cron job.
+ *
+ * @return This cron job's sanitized job configuration.
+ */
+ public SanitizedConfiguration getSanitizedConfig() {
+ return config;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java
deleted file mode 100644
index e0935f5..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronModule.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright 2013 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.cron.noop;
-
-import javax.inject.Singleton;
-
-import com.google.inject.AbstractModule;
-
-import org.apache.aurora.scheduler.cron.CronPredictor;
-import org.apache.aurora.scheduler.cron.CronScheduler;
-
-/**
- * A Module to wire up a cron scheduler that does not actually schedule cron jobs.
- *
- * This class exists as a short term hack to get around a license compatibility issue - Real
- * Implementation (TM) coming soon.
- */
-public class NoopCronModule extends AbstractModule {
- @Override
- protected void configure() {
- bind(CronScheduler.class).to(NoopCronScheduler.class);
- bind(NoopCronScheduler.class).in(Singleton.class);
-
- bind(CronPredictor.class).to(NoopCronPredictor.class);
- bind(NoopCronPredictor.class).in(Singleton.class);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java
deleted file mode 100644
index 7b25152..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronPredictor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Copyright 2013 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.cron.noop;
-
-import java.util.Date;
-
-import org.apache.aurora.scheduler.cron.CronPredictor;
-
-/**
- * A cron predictor that always suggests that the next run is Unix epoch time.
- *
- * This class exists as a short term hack to get around a license compatibility issue - Real
- * Implementation (TM) coming soon.
- */
-class NoopCronPredictor implements CronPredictor {
- @Override
- public Date predictNextRun(String schedule) {
- return new Date(0);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java b/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java
deleted file mode 100644
index a31551c..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/noop/NoopCronScheduler.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * Copyright 2013 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.cron.noop;
-
-import java.util.Collections;
-import java.util.Set;
-import java.util.logging.Logger;
-
-import javax.annotation.Nullable;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.AbstractIdleService;
-
-import org.apache.aurora.scheduler.cron.CronScheduler;
-
-/**
- * A cron scheduler that accepts cron jobs but never runs them. Useful if you want to hook up an
- * external triggering mechanism (e.g. a system cron job that calls the startCronJob RPC manually
- * on an interval).
- *
- * This class exists as a short term hack to get around a license compatibility issue - Real
- * Implementation (TM) coming soon.
- */
-class NoopCronScheduler extends AbstractIdleService implements CronScheduler {
- private static final Logger LOG = Logger.getLogger(NoopCronScheduler.class.getName());
-
- // Keep a list of schedules we've seen.
- private final Set<String> schedules = Collections.synchronizedSet(Sets.<String>newHashSet());
-
- @Override
- public void startUp() throws Exception {
- LOG.warning("NO-OP cron scheduler is in use. Cron jobs submitted will not be triggered!");
- }
-
- @Override
- public void shutDown() {
- // No-op.
- }
-
- @Override
- public String schedule(String schedule, Runnable task) {
- schedules.add(schedule);
-
- LOG.warning(String.format(
- "NO-OP cron scheduler is in use! %s with schedule %s WILL NOT be automatically triggered!",
- task,
- schedule));
-
- return schedule;
- }
-
- @Override
- public void deschedule(String key) throws IllegalStateException {
- schedules.remove(key);
- }
-
- @Override
- public Optional<String> getSchedule(String key) throws IllegalStateException {
- return schedules.contains(key)
- ? Optional.of(key)
- : Optional.<String>absent();
- }
-
- @Override
- public boolean isValidSchedule(@Nullable String schedule) {
- // Accept everything.
- return schedule != null;
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java
new file mode 100644
index 0000000..fc02264
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJob.java
@@ -0,0 +1,231 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.inject.Inject;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+
+import com.twitter.common.base.Supplier;
+import com.twitter.common.stats.Stats;
+import com.twitter.common.util.BackoffHelper;
+
+import org.apache.aurora.gen.CronCollisionPolicy;
+import org.apache.aurora.gen.ScheduleStatus;
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.base.Query;
+import org.apache.aurora.scheduler.base.Tasks;
+import org.apache.aurora.scheduler.configuration.ConfigurationManager;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
+import org.apache.aurora.scheduler.state.StateManager;
+import org.apache.aurora.scheduler.storage.Storage;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
+
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import static org.apache.aurora.gen.ScheduleStatus.KILLING;
+
+/**
+ * Encapsulates the logic behind a single trigger of a single job key. Multiple executions may run
+ * concurrently but only a single instance will be active at a time per job key.
+ *
+ * <p>
+ * Executions may block for long periods of time when waiting for a kill to complete. The Quartz
+ * scheduler should therefore be configured with a large number of threads.
+ */
+@DisallowConcurrentExecution
+class AuroraCronJob implements Job {
+ private static final Logger LOG = Logger.getLogger(AuroraCronJob.class.getName());
+
+ private static final AtomicLong CRON_JOB_TRIGGERS = Stats.exportLong("cron_job_triggers");
+ private static final AtomicLong CRON_JOB_MISFIRES = Stats.exportLong("cron_job_misfires");
+ private static final AtomicLong CRON_JOB_PARSE_FAILURES =
+ Stats.exportLong("cron_job_parse_failures");
+ private static final AtomicLong CRON_JOB_COLLISIONS = Stats.exportLong("cron_job_collisions");
+
+ @VisibleForTesting
+ static final Optional<String> KILL_AUDIT_MESSAGE = Optional.of("Killed by cronScheduler");
+
+ private final Storage storage;
+ private final StateManager stateManager;
+ private final CronJobManager cronJobManager;
+ private final BackoffHelper delayedStartBackoff;
+
+ @Inject
+ AuroraCronJob(
+ Config config,
+ Storage storage,
+ StateManager stateManager,
+ CronJobManager cronJobManager) {
+
+ this.storage = checkNotNull(storage);
+ this.stateManager = checkNotNull(stateManager);
+ this.cronJobManager = checkNotNull(cronJobManager);
+ this.delayedStartBackoff = checkNotNull(config.getDelayedStartBackoff());
+ }
+
+ private static final class DeferredLaunch {
+ private final Map<Integer, ITaskConfig> pendingTasks;
+ private final Set<String> activeTaskIds;
+
+ private DeferredLaunch(Map<Integer, ITaskConfig> pendingTasks, Set<String> activeTaskIds) {
+ this.pendingTasks = pendingTasks;
+ this.activeTaskIds = activeTaskIds;
+ }
+ }
+
+ @Override
+ public void execute(JobExecutionContext context) throws JobExecutionException {
+ // We assume quartz prevents concurrent runs of this job for a given job key. This allows us
+ // to avoid races where we might kill another run's tasks.
+ checkState(context.getJobDetail().isConcurrentExectionDisallowed());
+
+ doExecute(Quartz.auroraJobKey(context.getJobDetail().getKey()));
+ }
+
+ @VisibleForTesting
+ void doExecute(final IJobKey key) throws JobExecutionException {
+ final String path = JobKeys.canonicalString(key);
+
+ final Optional<DeferredLaunch> deferredLaunch = storage.write(
+ new Storage.MutateWork.Quiet<Optional<DeferredLaunch>>() {
+ @Override
+ public Optional<DeferredLaunch> apply(Storage.MutableStoreProvider storeProvider) {
+ Optional<IJobConfiguration> config =
+ storeProvider.getJobStore().fetchJob(cronJobManager.getManagerKey(), key);
+ if (!config.isPresent()) {
+ LOG.warning(String.format(
+ "Cron was triggered for %s but no job with that key was found in storage.",
+ path));
+ CRON_JOB_MISFIRES.incrementAndGet();
+ return Optional.absent();
+ }
+
+ SanitizedCronJob cronJob;
+ try {
+ cronJob = SanitizedCronJob.fromUnsanitized(config.get());
+ } catch (ConfigurationManager.TaskDescriptionException | CronException e) {
+ LOG.warning(String.format(
+ "Invalid cron job for %s in storage - failed to parse with %s", key, e));
+ CRON_JOB_PARSE_FAILURES.incrementAndGet();
+ return Optional.absent();
+ }
+
+ CronCollisionPolicy collisionPolicy = cronJob.getCronCollisionPolicy();
+ LOG.info(String.format(
+ "Cron triggered for %s at %s with policy %s", path, new Date(), collisionPolicy));
+ CRON_JOB_TRIGGERS.incrementAndGet();
+
+ ImmutableMap<Integer, ITaskConfig> pendingTasks =
+ ImmutableMap.copyOf(cronJob.getSanitizedConfig().getTaskConfigs());
+
+ final Query.Builder activeQuery = Query.jobScoped(key).active();
+ Set<String> activeTasks =
+ Tasks.ids(storeProvider.getTaskStore().fetchTasks(activeQuery));
+
+ if (activeTasks.isEmpty()) {
+ stateManager.insertPendingTasks(pendingTasks);
+ return Optional.absent();
+ }
+
+ CRON_JOB_COLLISIONS.incrementAndGet();
+ switch (collisionPolicy) {
+ case KILL_EXISTING:
+ return Optional.of(new DeferredLaunch(pendingTasks, activeTasks));
+
+ case RUN_OVERLAP:
+ LOG.severe(String.format("Ignoring trigger for job %s with deprecated collision"
+ + "policy RUN_OVERLAP due to unterminated active tasks.", path));
+ return Optional.absent();
+
+ case CANCEL_NEW:
+ return Optional.absent();
+
+ default:
+ LOG.severe("Unrecognized cron collision policy: " + collisionPolicy);
+ return Optional.absent();
+ }
+ }
+ }
+ );
+
+ if (!deferredLaunch.isPresent()) {
+ return;
+ }
+
+ for (String taskId : deferredLaunch.get().activeTaskIds) {
+ stateManager.changeState(
+ taskId,
+ Optional.<ScheduleStatus>absent(),
+ KILLING,
+ KILL_AUDIT_MESSAGE);
+ }
+ LOG.info(String.format("Waiting for job to terminate before launching cron job %s.", path));
+
+ final Query.Builder query = Query.taskScoped(deferredLaunch.get().activeTaskIds).active();
+ try {
+ // NOTE: We block the quartz execution thread here until we've successfully killed our
+ // ancestor. We mitigate this by using a cached thread pool for quartz.
+ delayedStartBackoff.doUntilSuccess(new Supplier<Boolean>() {
+ @Override
+ public Boolean get() {
+ if (Storage.Util.consistentFetchTasks(storage, query).isEmpty()) {
+ LOG.info("Initiating delayed launch of cron " + path);
+ stateManager.insertPendingTasks(deferredLaunch.get().pendingTasks);
+ return true;
+ } else {
+ LOG.info("Not yet safe to run cron " + path);
+ return false;
+ }
+ }
+ });
+ } catch (InterruptedException e) {
+ LOG.log(Level.WARNING, "Interrupted while trying to launch cron " + path, e);
+ Thread.currentThread().interrupt();
+ throw new JobExecutionException(e);
+ }
+ }
+
+ static class Config {
+ private final BackoffHelper delayedStartBackoff;
+
+ Config(BackoffHelper delayedStartBackoff) {
+ this.delayedStartBackoff = checkNotNull(delayedStartBackoff);
+ }
+
+ public BackoffHelper getDelayedStartBackoff() {
+ return delayedStartBackoff;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java
new file mode 100644
index 0000000..c5268cb
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobFactory.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import javax.inject.Inject;
+import javax.inject.Provider;
+
+import org.quartz.Job;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.spi.JobFactory;
+import org.quartz.spi.TriggerFiredBundle;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+* Adapter that allows AuroraCronJobs to be constructed by Guice instead of directly by quartz.
+*/
+class AuroraCronJobFactory implements JobFactory {
+ private final Provider<AuroraCronJob> auroraCronJobProvider;
+
+ @Inject
+ AuroraCronJobFactory(Provider<AuroraCronJob> auroraCronJobProvider) {
+ this.auroraCronJobProvider = checkNotNull(auroraCronJobProvider);
+ }
+
+ @Override
+ public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
+ checkState(AuroraCronJob.class.equals(bundle.getJobDetail().getJobClass()),
+ "Quartz tried to run a type of job we don't know about: "
+ + bundle.getJobDetail().getJobClass());
+
+ return auroraCronJobProvider.get();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java
new file mode 100644
index 0000000..8a5f569
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImpl.java
@@ -0,0 +1,256 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.aurora.gen.CronCollisionPolicy;
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
+import org.apache.aurora.scheduler.storage.JobStore;
+import org.apache.aurora.scheduler.storage.Storage;
+import org.apache.aurora.scheduler.storage.Storage.MutateWork;
+import org.apache.aurora.scheduler.storage.Storage.Work;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.impl.matchers.GroupMatcher;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * NOTE: The source of truth for whether a cron job exists or not is always the JobStore. If state
+ * somehow becomes inconsistent (i.e. a job key is scheduled for execution but its underlying
+ * JobConfiguration does not exist in storage the execution of the job will log a warning and
+ * exit).
+ */
+class CronJobManagerImpl implements CronJobManager {
+ private static final Logger LOG = Logger.getLogger(CronJobManagerImpl.class.getName());
+
+ private final Storage storage;
+ private final Scheduler scheduler;
+ private final TimeZone timeZone;
+
+ @Inject
+ CronJobManagerImpl(Storage storage, Scheduler scheduler, TimeZone timeZone) {
+ this.storage = checkNotNull(storage);
+ this.scheduler = checkNotNull(scheduler);
+ this.timeZone = checkNotNull(timeZone);
+ }
+
+ @Override
+ public String getManagerKey() {
+ return "CRON";
+ }
+
+ @Override
+ public void startJobNow(final IJobKey jobKey) throws CronException {
+ checkNotNull(jobKey);
+
+ storage.weaklyConsistentRead(new Work<Void, CronException>() {
+ @Override
+ public Void apply(Storage.StoreProvider storeProvider) throws CronException {
+ checkCronExists(jobKey, storeProvider.getJobStore());
+ triggerJob(jobKey);
+ return null;
+ }
+ });
+ }
+
+ private void triggerJob(IJobKey jobKey) throws CronException {
+ try {
+ scheduler.triggerJob(Quartz.jobKey(jobKey));
+ } catch (SchedulerException e) {
+ throw new CronException(e);
+ }
+ LOG.info(String.format("Triggered cron job for %s.", JobKeys.canonicalString(jobKey)));
+ }
+
+ private static void checkNoRunOverlap(SanitizedCronJob cronJob) throws CronException {
+ // NOTE: We check at create and update instead of in SanitizedCronJob to allow existing jobs
+ // but reject new ones.
+ if (CronCollisionPolicy.RUN_OVERLAP.equals(cronJob.getCronCollisionPolicy())) {
+ throw new CronException(
+ "The RUN_OVERLAP collision policy has been removed (AURORA-38).");
+ }
+ }
+
+ @Override
+ public void updateJob(final SanitizedCronJob config) throws CronException {
+ checkNotNull(config);
+ checkNoRunOverlap(config);
+
+ final IJobKey jobKey = config.getSanitizedConfig().getJobConfig().getKey();
+ storage.write(new MutateWork.NoResult<CronException>() {
+ @Override
+ public void execute(Storage.MutableStoreProvider storeProvider) throws CronException {
+ checkCronExists(jobKey, storeProvider.getJobStore());
+
+ removeJob(jobKey, storeProvider.getJobStore());
+ descheduleJob(jobKey);
+ saveJob(config, storeProvider.getJobStore());
+ scheduleJob(config.getCrontabEntry(), jobKey);
+ }
+ });
+ }
+
+ @Override
+ public void createJob(final SanitizedCronJob cronJob) throws CronException {
+ checkNotNull(cronJob);
+ checkNoRunOverlap(cronJob);
+
+ final IJobKey jobKey = cronJob.getSanitizedConfig().getJobConfig().getKey();
+ storage.write(new MutateWork.NoResult<CronException>() {
+ @Override
+ protected void execute(Storage.MutableStoreProvider storeProvider) throws CronException {
+ checkNotExists(jobKey, storeProvider.getJobStore());
+
+ saveJob(cronJob, storeProvider.getJobStore());
+ scheduleJob(cronJob.getCrontabEntry(), jobKey);
+ }
+ });
+ }
+
+ private void checkNotExists(IJobKey jobKey, JobStore jobStore) throws CronException {
+ if (jobStore.fetchJob(getManagerKey(), jobKey).isPresent()) {
+ throw new CronException(
+ String.format("Job already exists for %s.", JobKeys.canonicalString(jobKey)));
+ }
+ }
+
+ private void checkCronExists(IJobKey jobKey, JobStore jobStore) throws CronException {
+ if (!jobStore.fetchJob(getManagerKey(), jobKey).isPresent()) {
+ throw new CronException(
+ String.format("No cron template found for %s.", JobKeys.canonicalString(jobKey)));
+ }
+ }
+
+ private void removeJob(IJobKey jobKey, JobStore.Mutable jobStore) {
+ jobStore.removeJob(jobKey);
+ LOG.info(
+ String.format("Deleted cron job %s from storage.", JobKeys.canonicalString(jobKey)));
+ }
+
+ private void saveJob(SanitizedCronJob cronJob, JobStore.Mutable jobStore) {
+ IJobConfiguration config = cronJob.getSanitizedConfig().getJobConfig();
+
+ jobStore.saveAcceptedJob(getManagerKey(), config);
+ LOG.info(String.format(
+ "Saved new cron job %s to storage.", JobKeys.canonicalString(config.getKey())));
+ }
+
+ // TODO(ksweeney): Consider exposing this in the interface and making caller responsible.
+ void scheduleJob(CrontabEntry crontabEntry, IJobKey jobKey) throws CronException {
+ try {
+ scheduler.scheduleJob(
+ Quartz.jobDetail(jobKey, AuroraCronJob.class),
+ Quartz.cronTrigger(crontabEntry, timeZone));
+ } catch (SchedulerException e) {
+ throw new CronException(e);
+ }
+ LOG.info(String.format(
+ "Scheduled job %s with schedule %s.", JobKeys.canonicalString(jobKey), crontabEntry));
+ }
+
+ @Override
+ public Iterable<IJobConfiguration> getJobs() {
+ // NOTE: no synchronization is needed here since we don't touch internal quartz state.
+ return storage.consistentRead(new Work.Quiet<Iterable<IJobConfiguration>>() {
+ @Override
+ public Iterable<IJobConfiguration> apply(Storage.StoreProvider storeProvider) {
+ return storeProvider.getJobStore().fetchJobs(getManagerKey());
+ }
+ });
+ }
+
+ @Override
+ public boolean hasJob(final IJobKey jobKey) {
+ checkNotNull(jobKey);
+
+ return storage.consistentRead(new Work.Quiet<Boolean>() {
+ @Override
+ public Boolean apply(Storage.StoreProvider storeProvider) {
+ return storeProvider.getJobStore().fetchJob(getManagerKey(), jobKey).isPresent();
+ }
+ });
+ }
+
+ @Override
+ public boolean deleteJob(final IJobKey jobKey) {
+ checkNotNull(jobKey);
+
+ return storage.write(new MutateWork.Quiet<Boolean>() {
+ @Override
+ public Boolean apply(Storage.MutableStoreProvider storeProvider) {
+ if (!hasJob(jobKey)) {
+ return false;
+ }
+
+ removeJob(jobKey, storeProvider.getJobStore());
+ descheduleJob(jobKey);
+ return true;
+ }
+ });
+ }
+
+ private void descheduleJob(IJobKey jobKey) {
+ String path = JobKeys.canonicalString(jobKey);
+ try {
+ // TODO(ksweeney): Consider interrupting the running job here.
+ // There's a race here where an old running job could fail to find the old config. That's
+ // fine given that the behavior of AuroraCronJob is to log an error and exit if it's unable
+ // to find a job for its key.
+ scheduler.deleteJob(Quartz.jobKey(jobKey));
+ LOG.info("Successfully descheduled " + path + ".");
+ } catch (SchedulerException e) {
+ LOG.log(Level.WARNING, "Error when attempting to deschedule " + path + ": " + e, e);
+ }
+ }
+
+ @Override
+ public Map<IJobKey, CrontabEntry> getScheduledJobs() {
+ // NOTE: no synchronization is needed here since this is just a dump of internal quartz state
+ // for debugging.
+ ImmutableMap.Builder<IJobKey, CrontabEntry> scheduledJobs = ImmutableMap.builder();
+ try {
+ for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.<JobKey>anyGroup())) {
+ Optional<JobDetail> jobDetail = Optional.fromNullable(scheduler.getJobDetail(jobKey));
+ if (jobDetail.isPresent()) {
+ scheduledJobs.put(
+ Quartz.auroraJobKey(jobKey), CrontabEntry.parse(jobDetail.get().getDescription()));
+ }
+ }
+ } catch (SchedulerException e) {
+ throw Throwables.propagate(e);
+ }
+ return scheduledJobs.build();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java
new file mode 100644
index 0000000..64fa068
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronLifecycle.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import com.google.common.eventbus.Subscribe;
+import com.google.common.util.concurrent.AbstractIdleService;
+import com.twitter.common.application.ShutdownRegistry;
+import com.twitter.common.base.Command;
+import com.twitter.common.stats.Stats;
+
+import org.apache.aurora.scheduler.configuration.ConfigurationManager;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
+import org.apache.aurora.scheduler.events.PubsubEvent;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.quartz.Scheduler;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Manager for startup and teardown of Quartz scheduler.
+ */
+class CronLifecycle extends AbstractIdleService implements PubsubEvent.EventSubscriber {
+ private static final Logger LOG = Logger.getLogger(CronLifecycle.class.getName());
+
+ private static final AtomicInteger RUNNING_FLAG = Stats.exportInt("quartz_scheduler_running");
+ private static final AtomicInteger LOADED_FLAG = Stats.exportInt("cron_jobs_loaded");
+ private static final AtomicLong LAUNCH_FAILURES = Stats.exportLong("cron_job_launch_failures");
+
+ private final Scheduler scheduler;
+ private final ShutdownRegistry shutdownRegistry;
+ private final CronJobManagerImpl cronJobManager;
+
+ @Inject
+ CronLifecycle(
+ Scheduler scheduler,
+ ShutdownRegistry shutdownRegistry,
+ CronJobManagerImpl cronJobManager) {
+
+ this.scheduler = checkNotNull(scheduler);
+ this.shutdownRegistry = checkNotNull(shutdownRegistry);
+ this.cronJobManager = checkNotNull(cronJobManager);
+ }
+
+ /**
+ * Notifies the cronScheduler job manager that the scheduler is active, and job configurations
+ * are ready to load.
+ *
+ * @param schedulerActive Event.
+ */
+ @Subscribe
+ public void schedulerActive(PubsubEvent.SchedulerActive schedulerActive) {
+ startAsync();
+ shutdownRegistry.addAction(new Command() {
+ @Override
+ public void execute() {
+ CronLifecycle.this.stopAsync().awaitTerminated();
+ }
+ });
+ awaitRunning();
+ }
+
+ @Override
+ protected void startUp() throws Exception {
+ LOG.info("Starting Quartz cron scheduler" + scheduler.getSchedulerName() + ".");
+ scheduler.start();
+ RUNNING_FLAG.set(1);
+
+ // TODO(ksweeney): Refactor the interface - we really only need the job keys here.
+ for (IJobConfiguration job : cronJobManager.getJobs()) {
+ try {
+ SanitizedCronJob cronJob = SanitizedCronJob.fromUnsanitized(job);
+ cronJobManager.scheduleJob(
+ cronJob.getCrontabEntry(),
+ cronJob.getSanitizedConfig().getJobConfig().getKey());
+ } catch (CronException | ConfigurationManager.TaskDescriptionException e) {
+ logLaunchFailure(job, e);
+ }
+ }
+ LOADED_FLAG.set(1);
+ }
+
+ private void logLaunchFailure(IJobConfiguration job, Exception e) {
+ LAUNCH_FAILURES.incrementAndGet();
+ LOG.log(Level.SEVERE, "Scheduling failed for recovered job " + job, e);
+ }
+
+ @Override
+ protected void shutDown() throws Exception {
+ LOG.info("Shutting down Quartz cron scheduler.");
+ scheduler.shutdown();
+ RUNNING_FLAG.set(0);
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
new file mode 100644
index 0000000..6934828
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+
+import javax.inject.Singleton;
+
+import com.google.common.base.Throwables;
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.BackoffHelper;
+
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CronScheduler;
+import org.apache.aurora.scheduler.events.PubsubEventModule;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+import org.quartz.impl.DirectSchedulerFactory;
+import org.quartz.simpl.RAMJobStore;
+import org.quartz.simpl.SimpleThreadPool;
+
+/**
+ * Provides a {@link CronJobManager} with a Quartz backend. While Quartz itself supports
+ * persistence, the scheduler exposed by this module does not persist any state - it simply
+ * creates tasks from a {@link org.apache.aurora.gen.JobConfiguration} template on a cron-style
+ * schedule.
+ */
+public class CronModule extends AbstractModule {
+ private static final Logger LOG = Logger.getLogger(CronModule.class.getName());
+
+ @CmdLine(name = "cron_scheduler_num_threads",
+ help = "Number of threads to use for the cron scheduler thread pool.")
+ private static final Arg<Integer> NUM_THREADS = Arg.create(100);
+
+ @CmdLine(name = "cron_timezone", help = "TimeZone to use for cron predictions.")
+ private static final Arg<String> CRON_TIMEZONE = Arg.create("GMT");
+
+ @CmdLine(name = "cron_start_initial_backoff", help =
+ "Initial backoff delay while waiting for a previous cron run to be killed.")
+ public static final Arg<Amount<Long, Time>> CRON_START_INITIAL_BACKOFF =
+ Arg.create(Amount.of(1L, Time.SECONDS));
+
+ @CmdLine(name = "cron_start_max_backoff", help =
+ "Max backoff delay while waiting for a previous cron run to be killed.")
+ public static final Arg<Amount<Long, Time>> CRON_START_MAX_BACKOFF =
+ Arg.create(Amount.of(1L, Time.MINUTES));
+
+ // Global per-JVM ID number generator for the provided Quartz Scheduler.
+ private static final AtomicLong ID_GENERATOR = new AtomicLong();
+
+ @Override
+ protected void configure() {
+ bind(CronPredictor.class).to(CronPredictorImpl.class);
+ bind(CronPredictorImpl.class).in(Singleton.class);
+
+ bind(CronJobManager.class).to(CronJobManagerImpl.class);
+ bind(CronJobManagerImpl.class).in(Singleton.class);
+
+ bind(CronScheduler.class).to(CronSchedulerImpl.class);
+ bind(CronSchedulerImpl.class).in(Singleton.class);
+
+ bind(AuroraCronJobFactory.class).in(Singleton.class);
+
+ bind(AuroraCronJob.class).in(Singleton.class);
+ bind(AuroraCronJob.Config.class).toInstance(new AuroraCronJob.Config(
+ new BackoffHelper(CRON_START_INITIAL_BACKOFF.get(), CRON_START_MAX_BACKOFF.get())));
+
+ bind(CronLifecycle.class).in(Singleton.class);
+ PubsubEventModule.bindSubscriber(binder(), CronLifecycle.class);
+ }
+
+ @Provides
+ private TimeZone provideTimeZone() {
+ TimeZone timeZone = TimeZone.getTimeZone(CRON_TIMEZONE.get());
+ TimeZone systemTimeZone = TimeZone.getDefault();
+ if (!timeZone.equals(systemTimeZone)) {
+ LOG.warning("Cron schedules are configured to fire according to timezone "
+ + timeZone.getDisplayName()
+ + " but system timezone is set to "
+ + systemTimeZone.getDisplayName());
+ }
+ return timeZone;
+ }
+
+ /*
+ * NOTE: Quartz implements DirectSchedulerFactory as a mutable global singleton in a static
+ * variable. While the Scheduler instances it produces are independent we synchronize here to
+ * avoid an initialization race across injectors. In practice this only shows up during testing;
+ * production Aurora instances will only have one object graph at a time.
+ */
+ @Provides
+ @Singleton
+ private static synchronized Scheduler provideScheduler(AuroraCronJobFactory jobFactory) {
+ SimpleThreadPool threadPool = new SimpleThreadPool(NUM_THREADS.get(), Thread.NORM_PRIORITY);
+ threadPool.setMakeThreadsDaemons(true);
+
+ DirectSchedulerFactory schedulerFactory = DirectSchedulerFactory.getInstance();
+ String schedulerName = "aurora-cron-" + ID_GENERATOR.incrementAndGet();
+ try {
+ schedulerFactory.createScheduler(schedulerName, schedulerName, threadPool, new RAMJobStore());
+ Scheduler scheduler = schedulerFactory.getScheduler(schedulerName);
+ scheduler.setJobFactory(jobFactory);
+ return scheduler;
+ } catch (SchedulerException e) {
+ LOG.severe("Error initializing Quartz cron scheduler: " + e);
+ throw Throwables.propagate(e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java
new file mode 100644
index 0000000..fb24c28
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImpl.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+import javax.inject.Inject;
+
+import com.twitter.common.util.Clock;
+
+import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.quartz.CronExpression;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+class CronPredictorImpl implements CronPredictor {
+ private final Clock clock;
+ private final TimeZone timeZone;
+
+ @Inject
+ CronPredictorImpl(Clock clock, TimeZone timeZone) {
+ this.clock = checkNotNull(clock);
+ this.timeZone = checkNotNull(timeZone);
+ }
+
+ @Override
+ public Date predictNextRun(CrontabEntry schedule) {
+ CronExpression cronExpression = Quartz.cronExpression(schedule, timeZone);
+ return cronExpression.getNextValidTimeAfter(new Date(clock.nowMillis()));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java
new file mode 100644
index 0000000..3bef22d
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronSchedulerImpl.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.inject.Inject;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.Iterables;
+import com.twitter.common.base.Function;
+
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.cron.CronScheduler;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.quartz.CronTrigger;
+import org.quartz.Scheduler;
+import org.quartz.SchedulerException;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import static org.apache.aurora.scheduler.cron.quartz.Quartz.jobKey;
+
+class CronSchedulerImpl implements CronScheduler {
+ private static final Logger LOG = Logger.getLogger(CronSchedulerImpl.class.getName());
+
+ private final Scheduler scheduler;
+
+ @Inject
+ CronSchedulerImpl(Scheduler scheduler) {
+ this.scheduler = checkNotNull(scheduler);
+ }
+
+ @Override
+ public Optional<CrontabEntry> getSchedule(IJobKey jobKey) throws IllegalStateException {
+ checkNotNull(jobKey);
+
+ try {
+ return Optional.of(Iterables.getOnlyElement(
+ FluentIterable.from(scheduler.getTriggersOfJob(jobKey(jobKey)))
+ .filter(CronTrigger.class)
+ .transform(new Function<CronTrigger, CrontabEntry>() {
+ @Override
+ public CrontabEntry apply(CronTrigger trigger) {
+ return Quartz.crontabEntry(trigger);
+ }
+ })));
+ } catch (SchedulerException e) {
+ LOG.log(Level.SEVERE,
+ "Error reading job " + JobKeys.canonicalString(jobKey) + " cronExpression Quartz: " + e,
+ e);
+ return Optional.absent();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java
new file mode 100644
index 0000000..63aaade
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/Quartz.java
@@ -0,0 +1,124 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.TimeZone;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ContiguousSet;
+import com.google.common.collect.DiscreteDomain;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Range;
+
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.quartz.CronExpression;
+import org.quartz.CronScheduleBuilder;
+import org.quartz.CronTrigger;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.TriggerBuilder;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Utilities for converting Aurora datatypes to Quartz datatypes.
+ */
+final class Quartz {
+ private Quartz() {
+ // Utility class.
+ }
+
+ /**
+ * Convert an Aurora CrontabEntry to a Quartz CronExpression.
+ */
+ static CronExpression cronExpression(CrontabEntry entry, TimeZone timeZone) {
+ String dayOfMonth;
+ if (entry.hasWildcardDayOfMonth()) {
+ dayOfMonth = "?"; // special quartz token meaning "don't care"
+ } else {
+ dayOfMonth = entry.getDayOfMonthAsString();
+ }
+ String dayOfWeek;
+ if (entry.hasWildcardDayOfWeek() && !entry.hasWildcardDayOfMonth()) {
+ dayOfWeek = "?";
+ } else {
+ List<Integer> daysOfWeek = Lists.newArrayList();
+ for (Range<Integer> range : entry.getDayOfWeek().asRanges()) {
+ for (int i : ContiguousSet.create(range, DiscreteDomain.integers())) {
+ daysOfWeek.add(i + 1); // Quartz has an off-by-one with what the "standard" defines.
+ }
+ }
+ dayOfWeek = Joiner.on(",").join(daysOfWeek);
+ }
+
+ String rawCronExpresion = Joiner.on(" ").join(
+ "0",
+ entry.getMinuteAsString(),
+ entry.getHourAsString(),
+ dayOfMonth,
+ entry.getMonthAsString(),
+ dayOfWeek);
+ CronExpression cronExpression;
+ try {
+ cronExpression = new CronExpression(rawCronExpresion);
+ } catch (ParseException e) {
+ throw Throwables.propagate(e);
+ }
+ cronExpression.setTimeZone(timeZone);
+ return cronExpression;
+ }
+
+ /**
+ * Convert a Quartz JobKey to an Aurora IJobKey.
+ */
+ static IJobKey auroraJobKey(org.quartz.JobKey jobKey) {
+ return JobKeys.parse(jobKey.getName());
+ }
+
+ /**
+ * Convert an Aurora IJobKey to a Quartz JobKey.
+ */
+ static JobKey jobKey(IJobKey jobKey) {
+ return JobKey.jobKey(JobKeys.canonicalString(jobKey));
+ }
+
+ static CronTrigger cronTrigger(CrontabEntry schedule, TimeZone timeZone) {
+ return TriggerBuilder.newTrigger()
+ .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression(schedule, timeZone)))
+ .withDescription(schedule.toString())
+ .build();
+ }
+
+ static JobDetail jobDetail(IJobKey jobKey, Class<? extends Job> jobClass) {
+ checkNotNull(jobKey);
+ checkNotNull(jobClass);
+
+ return JobBuilder.newJob(jobClass)
+ .withIdentity(jobKey(jobKey))
+ .build();
+ }
+
+ static CrontabEntry crontabEntry(CronTrigger cronTrigger) {
+ return CrontabEntry.parse(cronTrigger.getDescription());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java b/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java
deleted file mode 100644
index 61b01d2..0000000
--- a/src/main/java/org/apache/aurora/scheduler/cron/testing/AbstractCronIT.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/**
- * Copyright 2013 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.cron.testing;
-
-import java.io.InputStreamReader;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
-import com.twitter.common.util.Clock;
-import com.twitter.common.util.testing.FakeClock;
-
-import org.apache.aurora.scheduler.cron.CronPredictor;
-import org.apache.aurora.scheduler.cron.CronScheduler;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Abstract test to verify conformance with the {@link CronScheduler} interface.
- */
-public abstract class AbstractCronIT {
- private static final String WILDCARD_SCHEDULE = "* * * * *";
-
- /**
- * Child should return an instance of the {@link CronScheduler} test under test here.
- */
- protected abstract CronScheduler makeCronScheduler() throws Exception;
-
- /**
- * Child should return an instance of the {@link CronPredictor} under test here.
- *
- * @param clock The clock the predictor should use.
- */
- protected abstract CronPredictor makeCronPredictor(Clock clock) throws Exception;
-
- @Test
- public void testCronSchedulerLifecycle() throws Exception {
- CronScheduler scheduler = makeCronScheduler();
-
- scheduler.startAsync().awaitRunning();
- final CountDownLatch cronRan = new CountDownLatch(1);
- scheduler.schedule(WILDCARD_SCHEDULE, new Runnable() {
- @Override public void run() {
- cronRan.countDown();
- }
- });
- cronRan.await();
- scheduler.stopAsync().awaitTerminated();
- }
-
- @Test
- public void testCronPredictorConforms() throws Exception {
- FakeClock clock = new FakeClock();
- CronPredictor cronPredictor = makeCronPredictor(clock);
-
- for (TriggerPrediction triggerPrediction : getExpectedTriggerPredictions()) {
- List<Long> results = Lists.newArrayList();
- clock.setNowMillis(0);
- for (int i = 0; i < triggerPrediction.getTriggerTimes().size(); i++) {
- Date nextTriggerTime = cronPredictor.predictNextRun(triggerPrediction.getSchedule());
- results.add(nextTriggerTime.getTime());
- clock.setNowMillis(nextTriggerTime.getTime());
- }
- assertEquals("Cron schedule "
- + triggerPrediction.getSchedule()
- + " should have have predicted trigger times "
- + triggerPrediction.getTriggerTimes()
- + " but predicted "
- + results
- + " instead.", triggerPrediction.getTriggerTimes(), results);
- }
- }
-
- @Test
- public void testCronScheduleValidatorAcceptsValidSchedules() throws Exception {
- CronScheduler cron = makeCronScheduler();
-
- for (TriggerPrediction triggerPrediction : getExpectedTriggerPredictions()) {
- assertTrue("Cron schedule " + triggerPrediction.getSchedule() + " should pass validation.",
- cron.isValidSchedule(triggerPrediction.getSchedule()));
- }
- }
-
- private static List<TriggerPrediction> getExpectedTriggerPredictions() {
- return new Gson()
- .fromJson(
- new InputStreamReader(
- AbstractCronIT.class.getResourceAsStream("cron-schedule-predictions.json")),
- new TypeToken<List<TriggerPrediction>>() { }.getType());
- }
-
- /**
- * A schedule and the expected iteratively-applied prediction results.
- */
- public static class TriggerPrediction {
- private String schedule;
- private List<Long> triggerTimes;
-
- private TriggerPrediction() {
- // GSON constructor.
- }
-
- public TriggerPrediction(String schedule, List<Long> triggerTimes) {
- this.schedule = schedule;
- this.triggerTimes = triggerTimes;
- }
-
- public String getSchedule() {
- return schedule;
- }
-
- public List<Long> getTriggerTimes() {
- return ImmutableList.copyOf(triggerTimes);
- }
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/java/org/apache/aurora/scheduler/http/Cron.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/Cron.java b/src/main/java/org/apache/aurora/scheduler/http/Cron.java
index 80a398a..d8c44f8 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/Cron.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/Cron.java
@@ -26,7 +26,7 @@ import javax.ws.rs.core.Response;
import com.google.common.collect.ImmutableMap;
-import org.apache.aurora.scheduler.state.CronJobManager;
+import org.apache.aurora.scheduler.cron.CronJobManager;
/**
* HTTP interface to dump state of the internal cron scheduler.
@@ -50,7 +50,6 @@ public class Cron {
public Response dumpContents() {
Map<String, Object> response = ImmutableMap.<String, Object>builder()
.put("scheduled", cronManager.getScheduledJobs())
- .put("pending", cronManager.getPendingRuns())
.build();
return Response.ok(response).build();
[2/5] CronScheduler based on Quartz
Posted by ke...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobTest.java b/src/test/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobTest.java
new file mode 100644
index 0000000..e7d1c14
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/cron/quartz/AuroraCronJobTest.java
@@ -0,0 +1,174 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.Map;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.twitter.common.base.Supplier;
+import com.twitter.common.testing.easymock.EasyMockTest;
+import com.twitter.common.util.BackoffHelper;
+
+import org.apache.aurora.gen.AssignedTask;
+import org.apache.aurora.gen.CronCollisionPolicy;
+import org.apache.aurora.gen.ScheduleStatus;
+import org.apache.aurora.gen.ScheduledTask;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.state.StateManager;
+import org.apache.aurora.scheduler.storage.Storage;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
+import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
+import org.apache.aurora.scheduler.storage.mem.MemStorage;
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.quartz.JobExecutionException;
+
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class AuroraCronJobTest extends EasyMockTest {
+ public static final String TASK_ID = "A";
+ private Storage storage;
+ private StateManager stateManager;
+ private CronJobManager cronJobManager;
+ private BackoffHelper backoffHelper;
+
+ private AuroraCronJob auroraCronJob;
+
+ private static final String MANAGER_ID = "MANAGER_ID";
+
+ @Before
+ public void setUp() {
+ storage = MemStorage.newEmptyStorage();
+ stateManager = createMock(StateManager.class);
+ cronJobManager = createMock(CronJobManager.class);
+ backoffHelper = createMock(BackoffHelper.class);
+
+ auroraCronJob = new AuroraCronJob(
+ new AuroraCronJob.Config(backoffHelper), storage, stateManager, cronJobManager);
+ expect(cronJobManager.getManagerKey()).andStubReturn(MANAGER_ID);
+ }
+
+ @Test
+ public void testExecuteNonexistentIsNoop() throws JobExecutionException {
+ control.replay();
+
+ auroraCronJob.doExecute(QuartzTestUtil.AURORA_JOB_KEY);
+ }
+
+ @Test
+ public void testInvalidConfigIsNoop() throws JobExecutionException {
+ control.replay();
+ storage.write(new Storage.MutateWork.NoResult.Quiet() {
+ @Override
+ protected void execute(Storage.MutableStoreProvider storeProvider) {
+ storeProvider.getJobStore().saveAcceptedJob(
+ MANAGER_ID,
+ IJobConfiguration.build(QuartzTestUtil.JOB.newBuilder().setCronSchedule(null)));
+ }
+ });
+
+ auroraCronJob.doExecute(QuartzTestUtil.AURORA_JOB_KEY);
+ }
+
+ @Test
+ public void testEmptyStorage() throws JobExecutionException {
+ stateManager.insertPendingTasks(EasyMock.<Map<Integer, ITaskConfig>>anyObject());
+ expectLastCall().times(3);
+
+ control.replay();
+ populateStorage(CronCollisionPolicy.CANCEL_NEW);
+ auroraCronJob.doExecute(QuartzTestUtil.AURORA_JOB_KEY);
+ storage = MemStorage.newEmptyStorage();
+
+ populateStorage(CronCollisionPolicy.KILL_EXISTING);
+ auroraCronJob.doExecute(QuartzTestUtil.AURORA_JOB_KEY);
+ storage = MemStorage.newEmptyStorage();
+
+ populateStorage(CronCollisionPolicy.RUN_OVERLAP);
+ auroraCronJob.doExecute(QuartzTestUtil.AURORA_JOB_KEY);
+ }
+
+ @Test
+ public void testCancelNew() throws JobExecutionException {
+ control.replay();
+
+ populateTaskStore();
+ populateStorage(CronCollisionPolicy.CANCEL_NEW);
+ auroraCronJob.doExecute(QuartzTestUtil.AURORA_JOB_KEY);
+ }
+
+ @Test
+ public void testKillExisting() throws Exception {
+ Capture<Supplier<Boolean>> capture = createCapture();
+
+ expect(stateManager.changeState(
+ TASK_ID,
+ Optional.<ScheduleStatus>absent(),
+ ScheduleStatus.KILLING,
+ AuroraCronJob.KILL_AUDIT_MESSAGE))
+ .andReturn(true);
+ backoffHelper.doUntilSuccess(EasyMock.capture(capture));
+ stateManager.insertPendingTasks(EasyMock.<Map<Integer, ITaskConfig>>anyObject());
+
+ control.replay();
+
+ populateStorage(CronCollisionPolicy.KILL_EXISTING);
+ populateTaskStore();
+ auroraCronJob.doExecute(QuartzTestUtil.AURORA_JOB_KEY);
+ assertFalse(capture.getValue().get());
+ storage.write(
+ new Storage.MutateWork.NoResult.Quiet() {
+ @Override
+ protected void execute(Storage.MutableStoreProvider storeProvider) {
+ storeProvider.getUnsafeTaskStore().deleteAllTasks();
+ }
+ });
+ assertTrue(capture.getValue().get());
+ }
+
+ private void populateTaskStore() {
+ storage.write(new Storage.MutateWork.NoResult.Quiet() {
+ @Override
+ protected void execute(Storage.MutableStoreProvider storeProvider) {
+ storeProvider.getUnsafeTaskStore().saveTasks(ImmutableSet.of(
+ IScheduledTask.build(new ScheduledTask()
+ .setStatus(ScheduleStatus.RUNNING)
+ .setAssignedTask(new AssignedTask()
+ .setTaskId(TASK_ID)
+ .setTask(QuartzTestUtil.JOB.getTaskConfig().newBuilder())))
+ ));
+ }
+ });
+ }
+
+ private void populateStorage(final CronCollisionPolicy policy) {
+ storage.write(new Storage.MutateWork.NoResult.Quiet() {
+ @Override
+ public void execute(Storage.MutableStoreProvider storeProvider) {
+ storeProvider.getJobStore().saveAcceptedJob(
+ MANAGER_ID,
+ QuartzTestUtil.makeSanitizedCronJob(policy).getSanitizedConfig().getJobConfig());
+ }
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronIT.java b/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronIT.java
new file mode 100644
index 0000000..21e8278
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronIT.java
@@ -0,0 +1,257 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.concurrent.CountDownLatch;
+
+import com.google.common.util.concurrent.Service;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+import com.google.inject.util.Modules;
+import com.twitter.common.application.ShutdownRegistry;
+import com.twitter.common.base.ExceptionalCommand;
+import com.twitter.common.testing.easymock.EasyMockTest;
+import com.twitter.common.util.Clock;
+
+import org.apache.aurora.gen.ExecutorConfig;
+import org.apache.aurora.gen.Identity;
+import org.apache.aurora.gen.JobConfiguration;
+import org.apache.aurora.gen.TaskConfig;
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
+import org.apache.aurora.scheduler.events.EventSink;
+import org.apache.aurora.scheduler.events.PubsubEvent;
+import org.apache.aurora.scheduler.state.PubsubTestUtil;
+import org.apache.aurora.scheduler.state.StateManager;
+import org.apache.aurora.scheduler.storage.Storage;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.apache.aurora.scheduler.storage.mem.MemStorage;
+import org.easymock.Capture;
+import org.easymock.IAnswer;
+import org.junit.Before;
+import org.junit.Test;
+import org.quartz.JobExecutionContext;
+import org.quartz.Scheduler;
+import org.quartz.Trigger;
+import org.quartz.TriggerListener;
+
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.isA;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class CronIT extends EasyMockTest {
+ public static final CrontabEntry CRONTAB_ENTRY = CrontabEntry.parse("* * * * *");
+
+ private static final IJobKey JOB_KEY = JobKeys.from("roll", "b", "c");
+ private static final Identity IDENTITY = new Identity()
+ .setRole(JOB_KEY.getRole())
+ .setUser("user");
+
+ private static final IJobConfiguration CRON_JOB = IJobConfiguration.build(
+ new JobConfiguration()
+ .setCronSchedule(CRONTAB_ENTRY.toString())
+ .setKey(JOB_KEY.newBuilder())
+ .setInstanceCount(2)
+ .setOwner(IDENTITY)
+ .setTaskConfig(new TaskConfig()
+ .setJobName(JOB_KEY.getName())
+ .setEnvironment(JOB_KEY.getEnvironment())
+ .setOwner(IDENTITY)
+ .setExecutorConfig(new ExecutorConfig()
+ .setName("cmd.exe")
+ .setData("echo hello world"))
+ .setNumCpus(7)
+ .setRamMb(8)
+ .setDiskMb(9))
+ );
+
+ private ShutdownRegistry shutdownRegistry;
+ private EventSink eventSink;
+ private Injector injector;
+ private StateManager stateManager;
+ private Storage storage;
+ private AuroraCronJob auroraCronJob;
+
+ private Capture<ExceptionalCommand<?>> shutdown;
+
+ @Before
+ public void setUp() throws Exception {
+ shutdownRegistry = createMock(ShutdownRegistry.class);
+ stateManager = createMock(StateManager.class);
+ storage = MemStorage.newEmptyStorage();
+ auroraCronJob = createMock(AuroraCronJob.class);
+
+ injector = Guice.createInjector(
+ // Override to verify that Guice is actually used for construction of the AuroraCronJob.
+ // TODO(ksweeney): Use the production class here.
+ Modules.override(new CronModule()).with(new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(AuroraCronJob.class).toInstance(auroraCronJob);
+ }
+ }), new AbstractModule() {
+ @Override
+ protected void configure() {
+ bind(Clock.class).toInstance(Clock.SYSTEM_CLOCK);
+ bind(ShutdownRegistry.class).toInstance(shutdownRegistry);
+ bind(StateManager.class).toInstance(stateManager);
+ bind(Storage.class).toInstance(storage);
+
+ PubsubTestUtil.installPubsub(binder());
+ }
+ });
+ eventSink = PubsubTestUtil.startPubsub(injector);
+
+ shutdown = createCapture();
+ shutdownRegistry.addAction(capture(shutdown));
+ }
+
+ private void boot() {
+ eventSink.post(new PubsubEvent.SchedulerActive());
+ }
+
+ @Test
+ public void testCronSchedulerLifecycle() throws Exception {
+ control.replay();
+
+ Scheduler scheduler = injector.getInstance(Scheduler.class);
+ assertTrue(!scheduler.isStarted());
+
+ boot();
+ Service cronLifecycle = injector.getInstance(CronLifecycle.class);
+
+ assertTrue(cronLifecycle.isRunning());
+ assertTrue(scheduler.isStarted());
+
+ shutdown.getValue().execute();
+
+ assertTrue(!cronLifecycle.isRunning());
+ assertTrue(scheduler.isShutdown());
+ }
+
+ @Test
+ public void testJobsAreScheduled() throws Exception {
+ auroraCronJob.execute(isA(JobExecutionContext.class));
+
+ control.replay();
+ final CronJobManager cronJobManager = injector.getInstance(CronJobManager.class);
+ final Scheduler scheduler = injector.getInstance(Scheduler.class);
+
+ storage.write(new Storage.MutateWork.NoResult.Quiet() {
+ @Override
+ public void execute(Storage.MutableStoreProvider storeProvider) {
+ storeProvider.getJobStore().saveAcceptedJob(
+ cronJobManager.getManagerKey(),
+ CRON_JOB);
+ }
+ });
+
+ final CountDownLatch cronRan = new CountDownLatch(1);
+ scheduler.getListenerManager().addTriggerListener(new CountDownWhenComplete(cronRan));
+ boot();
+
+ cronRan.await();
+
+ shutdown.getValue().execute();
+ }
+
+ @Test
+ public void testKillExistingDogpiles() throws Exception {
+ // Test that a trigger for a job that hasn't finished running is ignored.
+ final CronJobManager cronJobManager = injector.getInstance(CronJobManager.class);
+
+ final CountDownLatch firstExecutionTriggered = new CountDownLatch(1);
+ final CountDownLatch firstExecutionCompleted = new CountDownLatch(1);
+ final CountDownLatch secondExecutionTriggered = new CountDownLatch(1);
+ final CountDownLatch secondExecutionCompleted = new CountDownLatch(1);
+
+ auroraCronJob.execute(isA(JobExecutionContext.class));
+ expectLastCall().andAnswer(new IAnswer<Void>() {
+ @Override
+ public Void answer() throws Throwable {
+ firstExecutionTriggered.countDown();
+ firstExecutionCompleted.await();
+ return null;
+ }
+ });
+ auroraCronJob.execute(isA(JobExecutionContext.class));
+ expectLastCall().andAnswer(new IAnswer<Object>() {
+ @Override
+ public Void answer() throws Throwable {
+ secondExecutionTriggered.countDown();
+ secondExecutionCompleted.await();
+ return null;
+ }
+ });
+
+ control.replay();
+
+ boot();
+
+ cronJobManager.createJob(SanitizedCronJob.fromUnsanitized(CRON_JOB));
+ cronJobManager.startJobNow(JOB_KEY);
+ firstExecutionTriggered.await();
+ cronJobManager.startJobNow(JOB_KEY);
+ assertEquals(1, secondExecutionTriggered.getCount());
+ firstExecutionCompleted.countDown();
+ secondExecutionTriggered.await();
+ secondExecutionTriggered.countDown();
+ }
+
+ private static class CountDownWhenComplete implements TriggerListener {
+ private final CountDownLatch countDownLatch;
+
+ CountDownWhenComplete(CountDownLatch countDownLatch) {
+ this.countDownLatch = countDownLatch;
+ }
+
+ @Override
+ public String getName() {
+ return CountDownWhenComplete.class.getName();
+ }
+
+ @Override
+ public void triggerFired(Trigger trigger, JobExecutionContext context) {
+ }
+
+ @Override
+ public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
+ return false;
+ }
+
+ @Override
+ public void triggerMisfired(Trigger trigger) {
+ // No-op.
+ }
+
+ @Override
+ public void triggerComplete(
+ Trigger trigger,
+ JobExecutionContext context,
+ Trigger.CompletedExecutionInstruction triggerInstructionCode) {
+
+ countDownLatch.countDown();
+ // No-op.
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImplTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImplTest.java b/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImplTest.java
new file mode 100644
index 0000000..b9116a4
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronJobManagerImplTest.java
@@ -0,0 +1,221 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TimeZone;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.twitter.common.testing.easymock.EasyMockTest;
+
+import org.apache.aurora.gen.CronCollisionPolicy;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
+import org.apache.aurora.scheduler.storage.Storage;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.apache.aurora.scheduler.storage.mem.MemStorage;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.quartz.JobDetail;
+import org.quartz.JobKey;
+import org.quartz.Scheduler;
+import org.quartz.Trigger;
+import org.quartz.impl.matchers.GroupMatcher;
+
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class CronJobManagerImplTest extends EasyMockTest {
+ private Storage storage;
+ private Scheduler scheduler;
+
+ private CronJobManager cronJobManager;
+
+ @Before
+ public void setUp() {
+ storage = MemStorage.newEmptyStorage();
+ scheduler = createMock(Scheduler.class);
+
+ cronJobManager = new CronJobManagerImpl(storage, scheduler, TimeZone.getTimeZone("GMT"));
+ }
+
+ @Test
+ public void testStartJobNowExistent() throws Exception {
+ populateStorage();
+ scheduler.triggerJob(QuartzTestUtil.QUARTZ_JOB_KEY);
+
+ control.replay();
+
+ cronJobManager.startJobNow(QuartzTestUtil.AURORA_JOB_KEY);
+ }
+
+ @Test(expected = CronException.class)
+ public void testStartJobNowNonexistent() throws Exception {
+ control.replay();
+
+ cronJobManager.startJobNow(QuartzTestUtil.AURORA_JOB_KEY);
+ }
+
+ @Test
+ public void testUpdateExistingJob() throws Exception {
+ SanitizedCronJob sanitizedCronJob = QuartzTestUtil.makeSanitizedCronJob();
+
+ expect(scheduler.deleteJob(QuartzTestUtil.QUARTZ_JOB_KEY)).andReturn(true);
+ expect(scheduler.scheduleJob(anyObject(JobDetail.class), anyObject(Trigger.class)))
+ .andReturn(null);
+
+ populateStorage();
+
+ control.replay();
+
+ cronJobManager.updateJob(sanitizedCronJob);
+ assertEquals(sanitizedCronJob.getSanitizedConfig().getJobConfig(), fetchFromStorage().orNull());
+ }
+
+ @Test
+ public void testUpdateNonexistentJob() throws Exception {
+ control.replay();
+
+ try {
+ cronJobManager.updateJob(QuartzTestUtil.makeUpdatedJob());
+ fail();
+ } catch (CronException e) {
+ // Expected.
+ }
+
+ assertEquals(Optional.<IJobConfiguration>absent(), fetchFromStorage());
+ }
+
+ @Test
+ public void testCreateNonexistentJob() throws Exception {
+ SanitizedCronJob sanitizedCronJob = QuartzTestUtil.makeSanitizedCronJob();
+
+ expect(scheduler.scheduleJob(anyObject(JobDetail.class), anyObject(Trigger.class)))
+ .andReturn(null);
+
+ control.replay();
+
+ cronJobManager.createJob(sanitizedCronJob);
+
+ assertEquals(
+ sanitizedCronJob.getSanitizedConfig().getJobConfig(),
+ fetchFromStorage().orNull());
+ }
+
+ @Test(expected = CronException.class)
+ public void testCreateExistingJobFails() throws Exception {
+ SanitizedCronJob sanitizedCronJob = QuartzTestUtil.makeSanitizedCronJob();
+ populateStorage();
+ control.replay();
+
+ cronJobManager.createJob(sanitizedCronJob);
+ }
+
+ @Test
+ public void testGetJobs() throws Exception {
+ control.replay();
+ assertEquals(Collections.emptyList(), ImmutableList.copyOf(cronJobManager.getJobs()));
+
+ populateStorage();
+ assertEquals(
+ QuartzTestUtil.makeSanitizedCronJob().getSanitizedConfig().getJobConfig(),
+ Iterables.getOnlyElement(cronJobManager.getJobs()));
+ }
+
+ @Test
+ public void testNoRunOverlap() throws Exception {
+ SanitizedCronJob runOverlapJob = SanitizedCronJob.fromUnsanitized(
+ IJobConfiguration.build(QuartzTestUtil.JOB.newBuilder()
+ .setCronCollisionPolicy(CronCollisionPolicy.RUN_OVERLAP)));
+
+ control.replay();
+
+ try {
+ cronJobManager.createJob(runOverlapJob);
+ fail();
+ } catch (CronException e) {
+ // Expected.
+ }
+
+ try {
+ cronJobManager.updateJob(runOverlapJob);
+ } catch (CronException e) {
+ // Expected.
+ }
+
+ assertEquals(Optional.<IJobConfiguration>absent(), fetchFromStorage());
+ }
+
+ @Test
+ public void testDeleteJob() throws Exception {
+ expect(scheduler.deleteJob(QuartzTestUtil.QUARTZ_JOB_KEY)).andReturn(true);
+
+ control.replay();
+
+ assertFalse(cronJobManager.deleteJob(QuartzTestUtil.AURORA_JOB_KEY));
+ populateStorage();
+ assertTrue(cronJobManager.deleteJob(QuartzTestUtil.AURORA_JOB_KEY));
+ assertEquals(Optional.<IJobConfiguration>absent(), fetchFromStorage());
+ }
+
+ @Test
+ public void testGetScheduledJobs() throws Exception {
+ JobDetail jobDetail = createMock(JobDetail.class);
+ expect(scheduler.getJobKeys(EasyMock.<GroupMatcher<JobKey>>anyObject()))
+ .andReturn(ImmutableSet.of(QuartzTestUtil.QUARTZ_JOB_KEY));
+ expect(scheduler.getJobDetail(QuartzTestUtil.QUARTZ_JOB_KEY))
+ .andReturn(jobDetail);
+ expect(jobDetail.getDescription()).andReturn("* * * * *");
+
+ control.replay();
+
+ Map<IJobKey, CrontabEntry> scheduledJobs = cronJobManager.getScheduledJobs();
+ assertEquals(CrontabEntry.parse("* * * * *"), scheduledJobs.get(QuartzTestUtil.AURORA_JOB_KEY));
+ }
+
+ private void populateStorage() throws Exception {
+ storage.write(new Storage.MutateWork.NoResult<Exception>() {
+ @Override
+ protected void execute(Storage.MutableStoreProvider storeProvider) throws Exception {
+ storeProvider.getJobStore().saveAcceptedJob(
+ cronJobManager.getManagerKey(),
+ QuartzTestUtil.makeSanitizedCronJob().getSanitizedConfig().getJobConfig());
+ }
+ });
+ }
+
+ private Optional<IJobConfiguration> fetchFromStorage() {
+ return storage.consistentRead(new Storage.Work.Quiet<Optional<IJobConfiguration>>() {
+ @Override
+ public Optional<IJobConfiguration> apply(Storage.StoreProvider storeProvider) {
+ return storeProvider.getJobStore().fetchJob(cronJobManager.getManagerKey(),
+ QuartzTestUtil.AURORA_JOB_KEY);
+ }
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImplTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImplTest.java b/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImplTest.java
new file mode 100644
index 0000000..6bc97f3
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/cron/quartz/CronPredictorImplTest.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.twitter.common.quantity.Amount;
+import com.twitter.common.quantity.Time;
+import com.twitter.common.util.testing.FakeClock;
+
+import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+
+import org.apache.aurora.scheduler.cron.ExpectedPrediction;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class CronPredictorImplTest {
+ private static final TimeZone TIME_ZONE = TimeZone.getTimeZone("GMT");
+ public static final CrontabEntry CRONTAB_ENTRY = CrontabEntry.parse("* * * * *");
+
+ private CronPredictor cronPredictor;
+
+ private FakeClock clock;
+
+ @Before
+ public void setUp() {
+ clock = new FakeClock();
+ cronPredictor = new CronPredictorImpl(clock, TIME_ZONE);
+ }
+
+ @Test
+ public void testValidSchedule() {
+ clock.advance(Amount.of(1L, Time.DAYS));
+ Date expectedPrediction = new Date(Amount.of(1L, Time.DAYS).as(Time.MILLISECONDS)
+ + Amount.of(1L, Time.MINUTES).as(Time.MILLISECONDS));
+ assertEquals(expectedPrediction, cronPredictor.predictNextRun(CrontabEntry.parse("* * * * *")));
+ }
+
+ @Test
+ public void testCronExpressions() {
+ assertEquals("0 * * ? * 1,2,3,4,5,6,7",
+ Quartz.cronExpression(CRONTAB_ENTRY, TIME_ZONE).getCronExpression());
+ }
+
+ @Test
+ public void testCronPredictorConforms() throws Exception {
+ for (ExpectedPrediction expectedPrediction : ExpectedPrediction.getAll()) {
+ List<Date> results = Lists.newArrayList();
+ clock.setNowMillis(0);
+ for (int i = 0; i < expectedPrediction.getTriggerTimes().size(); i++) {
+ Date nextTriggerTime = cronPredictor.predictNextRun(expectedPrediction.parseCrontabEntry());
+ results.add(nextTriggerTime);
+ clock.setNowMillis(nextTriggerTime.getTime());
+ }
+ assertEquals(
+ "Cron schedule " + expectedPrediction.getSchedule() + " made unexpected predictions.",
+ Lists.transform(
+ expectedPrediction.getTriggerTimes(),
+ new Function<Long, Date>() {
+ @Override
+ public Date apply(Long time) {
+ return new Date(time);
+ }
+ }
+ ),
+ results);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/quartz/QuartzTestUtil.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/quartz/QuartzTestUtil.java b/src/test/java/org/apache/aurora/scheduler/cron/quartz/QuartzTestUtil.java
new file mode 100644
index 0000000..b10f514
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/cron/quartz/QuartzTestUtil.java
@@ -0,0 +1,78 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron.quartz;
+
+import com.google.common.base.Throwables;
+
+import org.apache.aurora.gen.CronCollisionPolicy;
+import org.apache.aurora.gen.ExecutorConfig;
+import org.apache.aurora.gen.Identity;
+import org.apache.aurora.gen.JobConfiguration;
+import org.apache.aurora.gen.TaskConfig;
+import org.apache.aurora.scheduler.base.JobKeys;
+import org.apache.aurora.scheduler.configuration.ConfigurationManager;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
+import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
+import org.apache.aurora.scheduler.storage.entities.IJobKey;
+import org.quartz.JobKey;
+
+/**
+ * Fixtures used across quartz tests.
+ */
+final class QuartzTestUtil {
+ static final IJobKey AURORA_JOB_KEY = JobKeys.from("role", "env", "job");
+ static final IJobConfiguration JOB = IJobConfiguration.build(
+ new JobConfiguration()
+ .setCronSchedule("* * * * SUN")
+ .setInstanceCount(10)
+ .setOwner(new Identity("role", "user"))
+ .setKey(AURORA_JOB_KEY.newBuilder())
+ .setTaskConfig(new TaskConfig()
+ .setOwner(new Identity("role", "user"))
+ .setJobName(AURORA_JOB_KEY.getName())
+ .setEnvironment(AURORA_JOB_KEY.getEnvironment())
+ .setDiskMb(3)
+ .setRamMb(4)
+ .setNumCpus(5)
+ .setExecutorConfig(new ExecutorConfig()
+ .setName("cmd.exe")
+ .setData("echo hello world")))
+ );
+ static final JobKey QUARTZ_JOB_KEY = Quartz.jobKey(AURORA_JOB_KEY);
+
+ private QuartzTestUtil() {
+ // Utility class.
+ }
+
+ static SanitizedCronJob makeSanitizedCronJob(CronCollisionPolicy collisionPolicy) {
+ try {
+ return SanitizedCronJob.fromUnsanitized(
+ IJobConfiguration.build(JOB.newBuilder().setCronCollisionPolicy(collisionPolicy)));
+ } catch (CronException | ConfigurationManager.TaskDescriptionException e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
+ static SanitizedCronJob makeSanitizedCronJob() {
+ return makeSanitizedCronJob(CronCollisionPolicy.KILL_EXISTING);
+ }
+
+ static SanitizedCronJob makeUpdatedJob() throws Exception {
+ return SanitizedCronJob.fromUnsanitized(
+ IJobConfiguration.build(JOB.newBuilder().setCronSchedule("* * 1 * *")));
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/state/BaseSchedulerCoreImplTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/state/BaseSchedulerCoreImplTest.java b/src/test/java/org/apache/aurora/scheduler/state/BaseSchedulerCoreImplTest.java
index da6c0ff..0e17f49 100644
--- a/src/test/java/org/apache/aurora/scheduler/state/BaseSchedulerCoreImplTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/state/BaseSchedulerCoreImplTest.java
@@ -38,15 +38,12 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.twitter.common.application.ShutdownRegistry;
import com.twitter.common.collections.Pair;
import com.twitter.common.testing.easymock.EasyMockTest;
import com.twitter.common.util.testing.FakeClock;
import org.apache.aurora.gen.AssignedTask;
import org.apache.aurora.gen.Constraint;
-import org.apache.aurora.gen.CronCollisionPolicy;
import org.apache.aurora.gen.ExecutorConfig;
import org.apache.aurora.gen.Identity;
import org.apache.aurora.gen.JobConfiguration;
@@ -68,7 +65,10 @@ import org.apache.aurora.scheduler.base.Tasks;
import org.apache.aurora.scheduler.configuration.ConfigurationManager;
import org.apache.aurora.scheduler.configuration.ConfigurationManager.TaskDescriptionException;
import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
-import org.apache.aurora.scheduler.cron.CronScheduler;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
import org.apache.aurora.scheduler.events.EventSink;
import org.apache.aurora.scheduler.events.PubsubEvent;
import org.apache.aurora.scheduler.quota.QuotaCheckResult;
@@ -85,6 +85,7 @@ import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
import org.apache.aurora.scheduler.storage.entities.ITaskEvent;
import org.apache.mesos.Protos.SlaveID;
import org.easymock.EasyMock;
+import org.easymock.IArgumentMatcher;
import org.easymock.IExpectationSetters;
import org.junit.Before;
import org.junit.Test;
@@ -106,9 +107,9 @@ import static org.apache.aurora.scheduler.quota.QuotaCheckResult.Result.INSUFFIC
import static org.apache.aurora.scheduler.quota.QuotaCheckResult.Result.SUFFICIENT_QUOTA;
import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.reportMatcher;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -128,25 +129,23 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
private static final IJobKey KEY_A = JobKeys.from(ROLE_A, ENV_A, JOB_A);
private static final int ONE_GB = 1024;
- private static final String ROLE_B = "Test_Role_B";
- private static final IJobKey KEY_B = JobKeys.from(ROLE_B, ENV_A, JOB_A);
-
private static final SlaveID SLAVE_ID = SlaveID.newBuilder().setValue("SlaveId").build();
private static final String SLAVE_HOST_1 = "SlaveHost1";
private static final QuotaCheckResult ENOUGH_QUOTA = new QuotaCheckResult(SUFFICIENT_QUOTA);
private static final QuotaCheckResult NOT_ENOUGH_QUOTA = new QuotaCheckResult(INSUFFICIENT_QUOTA);
+ public static final CrontabEntry CRONTAB_ENTRY = CrontabEntry.parse("1 1 1 1 *");
+ public static final String RAW_CRONTAB_ENTRY = CRONTAB_ENTRY.toString();
+
private Driver driver;
private StateManagerImpl stateManager;
private Storage storage;
private SchedulerCoreImpl scheduler;
- private CronScheduler cronScheduler;
- private CronJobManager cron;
+ private CronJobManager cronJobManager;
private FakeClock clock;
private EventSink eventSink;
private RescheduleCalculator rescheduleCalculator;
- private ShutdownRegistry shutdownRegistry;
private QuotaManager quotaManager;
// TODO(William Farner): Set up explicit expectations for calls to generate task IDs.
@@ -164,18 +163,15 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
clock = new FakeClock();
eventSink = createMock(EventSink.class);
rescheduleCalculator = createMock(RescheduleCalculator.class);
- cronScheduler = createMock(CronScheduler.class);
- shutdownRegistry = createMock(ShutdownRegistry.class);
+ cronJobManager = createMock(CronJobManager.class);
quotaManager = createMock(QuotaManager.class);
eventSink.post(EasyMock.<PubsubEvent>anyObject());
expectLastCall().anyTimes();
- expect(cronScheduler.schedule(anyObject(String.class), anyObject(Runnable.class)))
- .andStubReturn("key");
- expect(cronScheduler.isValidSchedule(anyObject(String.class))).andStubReturn(true);
expect(quotaManager.checkQuota(anyObject(ITaskConfig.class), anyInt()))
.andStubReturn(ENOUGH_QUOTA);
+ expect(cronJobManager.hasJob(anyObject(IJobKey.class))).andStubReturn(false);
}
/**
@@ -208,15 +204,9 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
taskIdGenerator,
eventSink,
rescheduleCalculator);
- cron = new CronJobManager(
- stateManager,
- storage,
- cronScheduler,
- shutdownRegistry,
- MoreExecutors.sameThreadExecutor());
scheduler = new SchedulerCoreImpl(
storage,
- cron,
+ cronJobManager,
stateManager,
taskIdGenerator,
quotaManager);
@@ -250,6 +240,19 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
FluentIterable.from(tasks).transform(Tasks.SCHEDULED_TO_INSTANCE_ID).toSet());
}
+ @Test
+ public void testCreateJobEmptyString() throws Exception {
+ // TODO(ksweeney): Deprecate this as part of AURORA-423.
+
+ control.replay();
+ buildScheduler();
+
+ SanitizedConfiguration job = SanitizedConfiguration.fromUnsanitized(
+ IJobConfiguration.build(makeJob(KEY_A, 1).getJobConfig().newBuilder().setCronSchedule("")));
+ scheduler.createJob(job);
+ assertTaskCount(1);
+ }
+
private static Constraint dedicatedConstraint(Set<String> values) {
return new Constraint(DEDICATED_ATTRIBUTE,
TaskConstraint.value(new ValueConstraint(false, values)));
@@ -272,7 +275,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
buildScheduler();
TaskConfig newTask = nonProductionTask();
- newTask.addToConstraints(dedicatedConstraint(ImmutableSet.of(JobKeys.toPath(KEY_A))));
+ newTask.addToConstraints(dedicatedConstraint(ImmutableSet.of(JobKeys.canonicalString(KEY_A))));
scheduler.createJob(makeJob(KEY_A, newTask));
assertEquals(PENDING, getOnlyTask(Query.jobScoped(KEY_A)).getStatus());
}
@@ -445,147 +448,6 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
scheduler.createJob(makeJob(KEY_A, 1));
}
- @Test(expected = ScheduleException.class)
- public void testCreateDuplicateCronJob() throws Exception {
- SanitizedConfiguration sanitizedConfiguration = makeCronJob(KEY_A, 1, "1 1 1 1 1");
-
- control.replay();
- buildScheduler();
-
- // Cron jobs are scheduled on a delay, so this job's tasks will not be scheduled immediately,
- // but duplicate jobs should still be rejected.
- scheduler.createJob(sanitizedConfiguration);
- assertTaskCount(0);
-
- scheduler.createJob(makeJob(KEY_A, 1));
- }
-
- @Test
- public void testStartCronJob() throws Exception {
- // Create a cron job, ask the scheduler to start it, and ensure that the tasks exist
- // in the PENDING state.
-
- SanitizedConfiguration sanitizedConfiguration = makeCronJob(KEY_A, 1, "1 1 1 1 1");
- IJobKey jobKey = sanitizedConfiguration.getJobConfig().getKey();
-
- control.replay();
- buildScheduler();
-
- scheduler.createJob(sanitizedConfiguration);
- assertTaskCount(0);
-
- cron.startJobNow(jobKey);
- assertEquals(PENDING, getOnlyTask(Query.jobScoped(jobKey)).getStatus());
- }
-
- @Test(expected = ScheduleException.class)
- public void testStartNonexistentCronJob() throws Exception {
- // Try to start a cron job that doesn't exist.
- control.replay();
- buildScheduler();
-
- cron.startJobNow(KEY_A);
- }
-
- @Test
- public void testStartNonCronJob() throws Exception {
- // Create a NON cron job and try to start it as though it were a cron job, and ensure that
- // no cron tasks are created.
- control.replay();
- buildScheduler();
-
- scheduler.createJob(makeJob(KEY_A, 1));
- String taskId = Tasks.id(getOnlyTask(Query.jobScoped(KEY_A)));
-
- try {
- cron.startJobNow(KEY_A);
- fail("Start should have failed.");
- } catch (ScheduleException e) {
- // Expected.
- }
-
- assertEquals(PENDING, getTask(taskId).getStatus());
- assertFalse(cron.hasJob(KEY_A));
- }
-
- @Test(expected = ScheduleException.class)
- public void testStartNonOwnedCronJob() throws Exception {
- // Try to start a cron job that is not owned by us.
- // Should throw an exception.
-
- SanitizedConfiguration sanitizedConfiguration = makeCronJob(KEY_A, 1, "1 1 1 1 1");
- IJobConfiguration job = sanitizedConfiguration.getJobConfig();
- expect(cronScheduler.isValidSchedule(job.getCronSchedule())).andReturn(true);
- expect(cronScheduler.schedule(eq(job.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andReturn("key");
-
- control.replay();
- buildScheduler();
-
- scheduler.createJob(sanitizedConfiguration);
- assertTaskCount(0);
-
- cron.startJobNow(KEY_B);
- }
-
- @Test
- public void testStartRunningCronJob() throws Exception {
- // Start a cron job that is already started by an earlier
- // call and is PENDING. Make sure it follows the cron collision policy.
- SanitizedConfiguration sanitizedConfiguration =
- makeCronJob(KEY_A, 1, "1 1 1 1 1", CronCollisionPolicy.KILL_EXISTING);
- expect(cronScheduler.schedule(eq(sanitizedConfiguration.getJobConfig().getCronSchedule()),
- EasyMock.<Runnable>anyObject()))
- .andReturn("key");
-
- control.replay();
- buildScheduler();
-
- scheduler.createJob(sanitizedConfiguration);
- assertTaskCount(0);
- assertTrue(cron.hasJob(KEY_A));
-
- cron.startJobNow(KEY_A);
- assertTaskCount(1);
-
- String taskId = Tasks.id(getOnlyTask(Query.jobScoped(KEY_A)));
-
- // Now start the same cron job immediately.
- cron.startJobNow(KEY_A);
- assertTaskCount(1);
- assertEquals(PENDING, getOnlyTask(Query.jobScoped(KEY_A)).getStatus());
-
- // Make sure the pending job is the new one.
- String newTaskId = Tasks.id(getOnlyTask(Query.jobScoped(KEY_A)));
- assertFalse(taskId.equals(newTaskId));
- }
-
- @Test
- public void testKillCreateCronJob() throws Exception {
- SanitizedConfiguration sanitizedConfiguration = makeCronJob(KEY_A, 1, "1 1 1 1 1");
- IJobConfiguration job = sanitizedConfiguration.getJobConfig();
- expect(cronScheduler.schedule(eq(job.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andReturn("key");
- cronScheduler.deschedule("key");
-
- SanitizedConfiguration updated = makeCronJob(KEY_A, 1, "1 2 3 4 5");
- IJobConfiguration updatedJob = updated.getJobConfig();
- expect(cronScheduler.schedule(eq(updatedJob.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andReturn("key2");
-
- control.replay();
- buildScheduler();
-
- scheduler.createJob(sanitizedConfiguration);
- assertTrue(cron.hasJob(KEY_A));
-
- scheduler.killTasks(Query.jobScoped(KEY_A), OWNER_A.getUser());
- scheduler.createJob(updated);
-
- IJobConfiguration stored = Iterables.getOnlyElement(cron.getJobs());
- assertEquals(updatedJob.getCronSchedule(), stored.getCronSchedule());
- }
-
@Test
public void testKillTask() throws Exception {
driver.killTask(EasyMock.<String>anyObject());
@@ -638,17 +500,21 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
@Test
public void testServiceTasksRescheduled() throws Exception {
int numServiceTasks = 5;
+ IJobKey adhocKey = KEY_A;
+ IJobKey serviceKey = IJobKey.build(
+ adhocKey.newBuilder().setName(adhocKey.getName() + "service"));
expectTaskNotThrottled().times(numServiceTasks);
+ expectNoCronJob(adhocKey);
+ expectNoCronJob(serviceKey);
control.replay();
buildScheduler();
// Schedule 5 service and 5 non-service tasks.
- scheduler.createJob(makeJob(KEY_A, numServiceTasks));
+ scheduler.createJob(makeJob(adhocKey, numServiceTasks));
TaskConfig task = productionTask().setIsService(true);
- scheduler.createJob(
- makeJob(IJobKey.build(KEY_A.newBuilder().setName(KEY_A.getName() + "service")), task, 5));
+ scheduler.createJob(makeJob(serviceKey, task, 5));
assertEquals(10, getTasksByStatus(PENDING).size());
changeStatus(Query.roleScoped(ROLE_A), ASSIGNED, STARTING);
@@ -675,6 +541,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
int totalFailures = 10;
expectTaskNotThrottled().times(totalFailures);
+ expectNoCronJob(KEY_A);
control.replay();
buildScheduler();
@@ -733,6 +600,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
@Test
public void testNoTransitionFromTerminalState() throws Exception {
expectKillTask(1);
+ expectNoCronJob(KEY_A);
control.replay();
buildScheduler();
@@ -753,6 +621,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
public void testFailedTaskIncrementsFailureCount() throws Exception {
int maxFailures = 5;
expectTaskNotThrottled().times(maxFailures - 1);
+ expect(cronJobManager.hasJob(KEY_A)).andReturn(false);
control.replay();
buildScheduler();
@@ -785,78 +654,9 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
}
@Test
- public void testCronJobLifeCycle() throws Exception {
- SanitizedConfiguration sanitizedConfiguration = makeCronJob(KEY_A, 10, "1 1 1 1 1");
- IJobConfiguration job = sanitizedConfiguration.getJobConfig();
- expect(cronScheduler.schedule(eq(job.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andReturn("key");
-
- control.replay();
- buildScheduler();
-
- scheduler.createJob(sanitizedConfiguration);
- assertTaskCount(0);
- assertTrue(cron.hasJob(KEY_A));
-
- // Simulate a triggering of the cron job.
- cron.startJobNow(KEY_A);
- assertTaskCount(10);
- assertEquals(10,
- getTasks(Query.jobScoped(KEY_A).byStatus(PENDING)).size());
-
- assertTaskCount(10);
-
- changeStatus(Query.roleScoped(ROLE_A), ASSIGNED, STARTING);
- assertTaskCount(10);
- changeStatus(Query.roleScoped(ROLE_A), RUNNING);
- assertTaskCount(10);
- changeStatus(Query.roleScoped(ROLE_A), FINISHED);
- }
-
- @Test
- public void testCronNoSuicide() throws Exception {
- SanitizedConfiguration sanitizedConfiguration =
- makeCronJob(KEY_A, 10, "1 1 1 1 1", CronCollisionPolicy.KILL_EXISTING);
- expect(cronScheduler.schedule(eq(sanitizedConfiguration.getJobConfig().getCronSchedule()),
- EasyMock.<Runnable>anyObject()))
- .andReturn("key");
-
- control.replay();
- buildScheduler();
-
- scheduler.createJob(sanitizedConfiguration);
- assertTaskCount(0);
-
- try {
- scheduler.createJob(sanitizedConfiguration);
- fail();
- } catch (ScheduleException e) {
- // Expected.
- }
- assertTrue(cron.hasJob(KEY_A));
-
- // Simulate a triggering of the cron job.
- cron.startJobNow(KEY_A);
- assertTaskCount(10);
-
- Set<String> taskIds = Tasks.ids(getTasksOwnedBy(OWNER_A));
-
- // Simulate a triggering of the cron job.
- cron.startJobNow(KEY_A);
- assertTaskCount(10);
- assertTrue(Sets.intersection(taskIds, Tasks.ids(getTasksOwnedBy(OWNER_A))).isEmpty());
-
- try {
- scheduler.createJob(sanitizedConfiguration);
- fail();
- } catch (ScheduleException e) {
- // Expected.
- }
- assertTrue(cron.hasJob(KEY_A));
- }
-
- @Test
public void testKillPendingTask() throws Exception {
+ expectNoCronJob(KEY_A);
+
control.replay();
buildScheduler();
@@ -891,16 +691,12 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
@Test
public void testKillCronTask() throws Exception {
- SanitizedConfiguration sanitizedConfiguration =
- makeCronJob(KEY_A, 1, "1 1 1 1 1", CronCollisionPolicy.KILL_EXISTING);
- expect(cronScheduler.schedule(eq(sanitizedConfiguration.getJobConfig().getCronSchedule()),
- EasyMock.<Runnable>anyObject()))
- .andReturn("key");
- cronScheduler.deschedule("key");
-
+ expect(cronJobManager.hasJob(KEY_A)).andReturn(false);
+ cronJobManager.createJob(anyObject(SanitizedCronJob.class));
+ expect(cronJobManager.deleteJob(KEY_A)).andReturn(true);
control.replay();
buildScheduler();
- scheduler.createJob(makeCronJob(KEY_A, 1, "1 1 1 1 1"));
+ scheduler.createJob(makeCronJob(KEY_A, 1, RAW_CRONTAB_ENTRY));
// This will fail if the cron task could not be found.
scheduler.killTasks(Query.jobScoped(KEY_A), OWNER_A.getUser());
@@ -910,6 +706,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
public void testLostTaskRescheduled() throws Exception {
expectKillTask(2);
expectTaskNotThrottled().times(2);
+ expect(cronJobManager.hasJob(KEY_A)).andReturn(false);
control.replay();
buildScheduler();
@@ -939,32 +736,10 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
}
@Test
- public void testKillNotStrictlyJobScoped() throws Exception {
- // Makes sure that queries that are not strictly job scoped will not remove the job entirely.
- SanitizedConfiguration config = makeCronJob(KEY_A, 10, "1 1 1 1 1");
- IJobConfiguration job = config.getJobConfig();
- expect(cronScheduler.schedule(eq(job.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andReturn("key");
- cronScheduler.deschedule("key");
-
- control.replay();
- buildScheduler();
-
- scheduler.createJob(config);
- assertTrue(cron.hasJob(KEY_A));
- cron.startJobNow(KEY_A);
- assertTaskCount(10);
-
- scheduler.killTasks(Query.instanceScoped(KEY_A, 0), USER_A);
- assertTaskCount(9);
- assertTrue(cron.hasJob(KEY_A));
-
- scheduler.killTasks(Query.jobScoped(KEY_A), USER_A);
- assertFalse(cron.hasJob(KEY_A));
- }
-
- @Test
public void testKillJob() throws Exception {
+ expectNoCronJob(KEY_A);
+ expect(cronJobManager.deleteJob(KEY_A)).andReturn(false);
+
control.replay();
buildScheduler();
@@ -1015,6 +790,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
@Test(expected = ScheduleException.class)
public void testRestartNonexistentShard() throws Exception {
expectTaskNotThrottled();
+ expectNoCronJob(KEY_A);
control.replay();
buildScheduler();
@@ -1026,6 +802,8 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
@Test
public void testRestartPendingShard() throws Exception {
+ expect(cronJobManager.hasJob(KEY_A)).andReturn(false);
+
control.replay();
buildScheduler();
@@ -1058,6 +836,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
public void testPortResourceResetAfterReschedule() throws Exception {
expectKillTask(1);
expectTaskNotThrottled();
+ expect(cronJobManager.hasJob(KEY_A)).andReturn(false);
control.replay();
buildScheduler();
@@ -1120,6 +899,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
}
};
+ expectNoCronJob(KEY_A);
control.replay();
buildScheduler();
@@ -1174,9 +954,8 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
scheduler.addInstances(
job.getKey(),
- ImmutableSet.copyOf(
- ContiguousSet.create(Range.closed(0, SchedulerCoreImpl.MAX_TASKS_PER_JOB.get()),
- DiscreteDomain.integers())),
+ ContiguousSet.create(Range.closed(0, SchedulerCoreImpl.MAX_TASKS_PER_JOB.get()),
+ DiscreteDomain.integers()),
job.getTaskConfig());
}
@@ -1225,6 +1004,7 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
.setOwner(OWNER_A);
ImmutableSet<Integer> instances = ImmutableSet.of(0);
+ expectNoCronJob(KEY_A);
control.replay();
buildScheduler();
@@ -1235,6 +1015,10 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
scheduler.addInstances(KEY_A, instances, ITaskConfig.build(newTask));
}
+ private void expectNoCronJob(IJobKey jobKey) throws CronException {
+ expect(cronJobManager.hasJob(jobKey)).andReturn(false);
+ }
+
private static String getLocalHost() {
try {
return InetAddress.getLocalHost().getHostName();
@@ -1265,19 +1049,6 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
private static SanitizedConfiguration makeCronJob(
IJobKey jobKey,
int numDefaultTasks,
- String cronSchedule,
- CronCollisionPolicy policy) throws TaskDescriptionException {
-
- return new SanitizedConfiguration(IJobConfiguration.build(
- makeCronJob(jobKey, numDefaultTasks, cronSchedule)
- .getJobConfig()
- .newBuilder()
- .setCronCollisionPolicy(policy)));
- }
-
- private static SanitizedConfiguration makeCronJob(
- IJobKey jobKey,
- int numDefaultTasks,
String cronSchedule) throws TaskDescriptionException {
SanitizedConfiguration job = makeJob(jobKey, numDefaultTasks);
@@ -1401,4 +1172,23 @@ public abstract class BaseSchedulerCoreImplTest extends EasyMockTest {
public void changeStatus(String taskId, ScheduleStatus status, Optional<String> message) {
changeStatus(Query.taskScoped(taskId), status, message);
}
+
+ private SanitizedConfiguration hasJobKey(final IJobKey key) {
+ reportMatcher(new IArgumentMatcher() {
+ @Override
+ public boolean matches(Object item) {
+ if (!(item instanceof SanitizedConfiguration)) {
+ return false;
+ }
+ SanitizedConfiguration configuration = (SanitizedConfiguration) item;
+ return key.equals(configuration.getJobConfig().getKey());
+ }
+
+ @Override
+ public void appendTo(StringBuffer buffer) {
+ buffer.append(key.toString());
+ }
+ });
+ return null;
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/state/CronJobManagerTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/state/CronJobManagerTest.java b/src/test/java/org/apache/aurora/scheduler/state/CronJobManagerTest.java
deleted file mode 100644
index fa9cb75..0000000
--- a/src/test/java/org/apache/aurora/scheduler/state/CronJobManagerTest.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/**
- * Copyright 2013 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.state;
-
-import java.util.concurrent.Executor;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.twitter.common.application.ShutdownRegistry;
-import com.twitter.common.base.ExceptionalCommand;
-import com.twitter.common.testing.easymock.EasyMockTest;
-
-import org.apache.aurora.gen.AssignedTask;
-import org.apache.aurora.gen.CronCollisionPolicy;
-import org.apache.aurora.gen.ExecutorConfig;
-import org.apache.aurora.gen.Identity;
-import org.apache.aurora.gen.JobConfiguration;
-import org.apache.aurora.gen.ScheduleStatus;
-import org.apache.aurora.gen.ScheduledTask;
-import org.apache.aurora.gen.TaskConfig;
-import org.apache.aurora.scheduler.base.JobKeys;
-import org.apache.aurora.scheduler.base.Query;
-import org.apache.aurora.scheduler.base.ScheduleException;
-import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
-import org.apache.aurora.scheduler.cron.CronException;
-import org.apache.aurora.scheduler.cron.CronScheduler;
-import org.apache.aurora.scheduler.events.EventSink;
-import org.apache.aurora.scheduler.events.PubsubEvent.SchedulerActive;
-import org.apache.aurora.scheduler.storage.Storage;
-import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
-import org.apache.aurora.scheduler.storage.entities.IJobKey;
-import org.apache.aurora.scheduler.storage.entities.IScheduledTask;
-import org.apache.aurora.scheduler.storage.testing.StorageTestUtil;
-import org.easymock.Capture;
-import org.easymock.EasyMock;
-import org.easymock.IExpectationSetters;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.apache.aurora.gen.apiConstants.DEFAULT_ENVIRONMENT;
-import static org.apache.aurora.scheduler.state.CronJobManager.MANAGER_KEY;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.eq;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-public class CronJobManagerTest extends EasyMockTest {
-
- private static final String OWNER = "owner";
- private static final String ENVIRONMENT = "staging11";
- private static final String JOB_NAME = "jobName";
- private static final String DEFAULT_JOB_KEY = "key";
- private static final String TASK_ID = "id";
- private static final IScheduledTask TASK = IScheduledTask.build(
- new ScheduledTask().setAssignedTask(new AssignedTask().setTaskId(TASK_ID)));
-
- private StateManager stateManager;
- private Executor delayExecutor;
- private Capture<Runnable> delayLaunchCapture;
- private StorageTestUtil storageUtil;
-
- private CronScheduler cronScheduler;
- private ShutdownRegistry shutdownRegistry;
- private CronJobManager cron;
- private IJobConfiguration job;
- private SanitizedConfiguration sanitizedConfiguration;
-
- @Before
- public void setUp() throws Exception {
- stateManager = createMock(StateManager.class);
- delayExecutor = createMock(Executor.class);
- delayLaunchCapture = createCapture();
- storageUtil = new StorageTestUtil(this);
- storageUtil.expectOperations();
- cronScheduler = createMock(CronScheduler.class);
- shutdownRegistry = createMock(ShutdownRegistry.class);
-
- cron = new CronJobManager(
- stateManager,
- storageUtil.storage,
- cronScheduler,
- shutdownRegistry,
- delayExecutor);
- job = makeJob();
- sanitizedConfiguration = SanitizedConfiguration.fromUnsanitized(job);
- }
-
- private void expectJobValidated(IJobConfiguration savedJob) {
- expect(cronScheduler.isValidSchedule(savedJob.getCronSchedule())).andReturn(true);
- }
-
- private void expectJobValidated() {
- expectJobValidated(sanitizedConfiguration.getJobConfig());
- }
-
- private Capture<Runnable> expectJobAccepted(IJobConfiguration savedJob) throws Exception {
- expectJobValidated(savedJob);
- storageUtil.jobStore.saveAcceptedJob(MANAGER_KEY, savedJob);
- Capture<Runnable> jobTriggerCapture = createCapture();
- expect(cronScheduler.schedule(eq(savedJob.getCronSchedule()), capture(jobTriggerCapture)))
- .andReturn(DEFAULT_JOB_KEY);
- return jobTriggerCapture;
- }
-
- private void expectJobAccepted() throws Exception {
- expectJobAccepted(sanitizedConfiguration.getJobConfig());
- }
-
- private void expectJobFetch() {
- expectJobValidated();
- expect(storageUtil.jobStore.fetchJob(MANAGER_KEY, job.getKey()))
- .andReturn(Optional.of(sanitizedConfiguration.getJobConfig()));
- }
-
- private IExpectationSetters<?> expectActiveTaskFetch(IScheduledTask... activeTasks) {
- return storageUtil.expectTaskFetch(Query.jobScoped(job.getKey()).active(), activeTasks);
- }
-
- @Test
- public void testPubsubWiring() throws Exception {
- expect(cronScheduler.startAsync()).andReturn(cronScheduler);
- cronScheduler.awaitRunning();
- shutdownRegistry.addAction(EasyMock.<ExceptionalCommand<?>>anyObject());
- expect(storageUtil.jobStore.fetchJobs(MANAGER_KEY))
- .andReturn(ImmutableList.<IJobConfiguration>of());
-
- control.replay();
-
- Injector injector = Guice.createInjector(new AbstractModule() {
- @Override
- protected void configure() {
- bind(StateManager.class).toInstance(stateManager);
- bind(Storage.class).toInstance(storageUtil.storage);
- bind(CronScheduler.class).toInstance(cronScheduler);
- bind(ShutdownRegistry.class).toInstance(shutdownRegistry);
- PubsubTestUtil.installPubsub(binder());
- StateModule.bindCronJobManager(binder());
- }
- });
- cron = injector.getInstance(CronJobManager.class);
- EventSink eventSink = PubsubTestUtil.startPubsub(injector);
- eventSink.post(new SchedulerActive());
- }
-
- @Test
- public void testStart() throws Exception {
- expectJobAccepted();
- expectJobFetch();
- expectActiveTaskFetch();
-
- // Job is executed immediately since there are no existing tasks to kill.
- stateManager.insertPendingTasks(sanitizedConfiguration.getTaskConfigs());
- expect(cronScheduler.getSchedule(DEFAULT_JOB_KEY))
- .andReturn(Optional.of(job.getCronSchedule()))
- .times(2);
-
- control.replay();
-
- assertEquals(ImmutableMap.<IJobKey, String>of(), cron.getScheduledJobs());
- cron.receiveJob(sanitizedConfiguration);
- assertEquals(ImmutableMap.of(job.getKey(), job.getCronSchedule()), cron.getScheduledJobs());
- cron.startJobNow(job.getKey());
- assertEquals(ImmutableMap.of(job.getKey(), job.getCronSchedule()), cron.getScheduledJobs());
- }
-
- @Test
- public void testDeleteInconsistent() throws Exception {
- // Tests a case where a job exists in the storage, but is not registered with the cron system.
-
- expect(storageUtil.jobStore.fetchJob(MANAGER_KEY, job.getKey()))
- .andReturn(Optional.of(sanitizedConfiguration.getJobConfig()));
-
- control.replay();
-
- assertTrue(cron.deleteJob(job.getKey()));
- }
-
- private IExpectationSetters<?> expectTaskKilled(String id) {
- expect(stateManager.changeState(
- id,
- Optional.<ScheduleStatus>absent(),
- ScheduleStatus.KILLING,
- CronJobManager.KILL_AUDIT_MESSAGE))
- .andReturn(true);
- return expectLastCall();
- }
-
- @Test
- public void testDelayedStart() throws Exception {
- expectJobAccepted();
- expectJobFetch();
-
- // Query to test if live tasks exist for the job.
- expectActiveTaskFetch(TASK);
-
- // Live tasks exist, so the cron manager must delay the cron launch.
- delayExecutor.execute(capture(delayLaunchCapture));
-
- // The cron manager will then try to initiate the kill.
- expectTaskKilled(TASK_ID);
-
- // Immediate query and delayed query.
- expectActiveTaskFetch(TASK).times(2);
-
- // Simulate the live task disappearing.
- expectActiveTaskFetch();
-
- stateManager.insertPendingTasks(sanitizedConfiguration.getTaskConfigs());
-
- control.replay();
-
- cron.receiveJob(sanitizedConfiguration);
- cron.startJobNow(job.getKey());
- assertEquals(ImmutableSet.of(job.getKey()), cron.getPendingRuns());
- delayLaunchCapture.getValue().run();
- assertEquals(ImmutableSet.<IJobKey>of(), cron.getPendingRuns());
- }
-
- @Test
- public void testDelayedStartResets() throws Exception {
- expectJobAccepted();
- expectJobFetch();
-
- // Query to test if live tasks exist for the job.
- expectActiveTaskFetch(TASK);
-
- // Live tasks exist, so the cron manager must delay the cron launch.
- delayExecutor.execute(capture(delayLaunchCapture));
-
- // The cron manager will then try to initiate the kill.
- expectTaskKilled(TASK_ID);
-
- // Immediate query and delayed query.
- expectActiveTaskFetch(TASK).times(2);
-
- // Simulate the live task disappearing.
- expectActiveTaskFetch();
-
- // Round two.
- expectJobFetch();
- expectActiveTaskFetch(TASK);
- delayExecutor.execute(capture(delayLaunchCapture));
- expectTaskKilled(TASK_ID);
- expectActiveTaskFetch(TASK).times(2);
- expectActiveTaskFetch();
-
- stateManager.insertPendingTasks(sanitizedConfiguration.getTaskConfigs());
- expectLastCall().times(2);
-
- control.replay();
-
- cron.receiveJob(sanitizedConfiguration);
- cron.startJobNow(job.getKey());
- delayLaunchCapture.getValue().run();
-
- // Start the job again. Since the previous delayed start completed, this should repeat the
- // entire process.
- cron.startJobNow(job.getKey());
- delayLaunchCapture.getValue().run();
- }
-
- @Test
- public void testDelayedStartMultiple() throws Exception {
- expectJobAccepted();
- expectJobFetch();
-
- // Query to test if live tasks exist for the job.
- expectActiveTaskFetch(TASK).times(3);
-
- // Live tasks exist, so the cron manager must delay the cron launch.
- delayExecutor.execute(capture(delayLaunchCapture));
-
- // The cron manager will then try to initiate the kill.
- expectJobFetch();
- expectJobFetch();
- expectTaskKilled(TASK_ID).times(3);
-
- // Immediate queries and delayed query.
- expectActiveTaskFetch(TASK).times(4);
-
- // Simulate the live task disappearing.
- expectActiveTaskFetch();
-
- stateManager.insertPendingTasks(sanitizedConfiguration.getTaskConfigs());
-
- control.replay();
-
- cron.receiveJob(sanitizedConfiguration);
-
- // Attempt to trick the cron manager into launching multiple times, or launching multiple
- // pollers.
- cron.startJobNow(job.getKey());
- cron.startJobNow(job.getKey());
- cron.startJobNow(job.getKey());
- delayLaunchCapture.getValue().run();
- }
-
- @Test
- public void testUpdate() throws Exception {
- SanitizedConfiguration updated = new SanitizedConfiguration(
- IJobConfiguration.build(job.newBuilder().setCronSchedule("1 2 3 4 5")));
-
- expectJobAccepted();
- cronScheduler.deschedule(DEFAULT_JOB_KEY);
- expectJobAccepted(updated.getJobConfig());
-
- control.replay();
-
- cron.receiveJob(sanitizedConfiguration);
- cron.updateJob(updated);
- }
-
- @Test
- public void testConsistentState() throws Exception {
- IJobConfiguration updated =
- IJobConfiguration.build(makeJob().newBuilder().setCronSchedule("1 2 3 4 5"));
-
- expectJobAccepted();
- cronScheduler.deschedule(DEFAULT_JOB_KEY);
- expectJobAccepted(updated);
-
- control.replay();
-
- cron.receiveJob(sanitizedConfiguration);
-
- IJobConfiguration failedUpdate =
- IJobConfiguration.build(updated.newBuilder().setCronSchedule(null));
- try {
- cron.updateJob(new SanitizedConfiguration(failedUpdate));
- fail();
- } catch (ScheduleException e) {
- // Expected.
- }
-
- cron.updateJob(new SanitizedConfiguration(updated));
- }
-
- @Test
- public void testInvalidStoredJob() throws Exception {
- // Invalid jobs are left alone, but doesn't halt operation.
-
- expect(cronScheduler.startAsync()).andReturn(cronScheduler);
- cronScheduler.awaitRunning();
- shutdownRegistry.addAction(EasyMock.<ExceptionalCommand<?>>anyObject());
-
- IJobConfiguration jobA =
- IJobConfiguration.build(makeJob().newBuilder().setCronSchedule("1 2 3 4 5 6 7"));
- IJobConfiguration jobB =
- IJobConfiguration.build(makeJob().newBuilder().setCronSchedule(null));
-
- expect(storageUtil.jobStore.fetchJobs(MANAGER_KEY)).andReturn(ImmutableList.of(jobA, jobB));
- expect(cronScheduler.isValidSchedule(jobA.getCronSchedule())).andReturn(false);
-
- control.replay();
-
- cron.schedulerActive(new SchedulerActive());
- }
-
- @Test(expected = IllegalStateException.class)
- public void testJobStoredTwice() throws Exception {
- // Simulate an inconsistent storage that contains two cron jobs under the same key.
-
- expect(cronScheduler.startAsync()).andReturn(cronScheduler);
- cronScheduler.awaitRunning();
- shutdownRegistry.addAction(EasyMock.<ExceptionalCommand<?>>anyObject());
-
- IJobConfiguration jobA =
- IJobConfiguration.build(makeJob().newBuilder().setCronSchedule("1 2 3 4 5"));
- IJobConfiguration jobB =
- IJobConfiguration.build(makeJob().newBuilder().setCronSchedule("* * * * *"));
- expect(storageUtil.jobStore.fetchJobs(MANAGER_KEY))
- .andReturn(ImmutableList.of(jobA, jobB));
- expectJobValidated(jobA);
- expect(cronScheduler.schedule(eq(jobA.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andReturn("keyA");
- expectJobValidated(jobB);
-
- control.replay();
-
- cron.schedulerActive(new SchedulerActive());
- }
-
- @Test(expected = ScheduleException.class)
- public void testInvalidCronSchedule() throws Exception {
- expect(cronScheduler.isValidSchedule(job.getCronSchedule())).andReturn(false);
-
- control.replay();
-
- cron.receiveJob(sanitizedConfiguration);
- }
-
- @Test
- public void testCancelNewCollision() throws Exception {
- IJobConfiguration killExisting = IJobConfiguration.build(
- job.newBuilder().setCronCollisionPolicy(CronCollisionPolicy.CANCEL_NEW));
- Capture<Runnable> jobTriggerCapture = expectJobAccepted(killExisting);
- expectActiveTaskFetch(TASK);
-
- control.replay();
-
- cron.receiveJob(new SanitizedConfiguration(killExisting));
- jobTriggerCapture.getValue().run();
- }
-
- @Test(expected = ScheduleException.class)
- public void testScheduleFails() throws Exception {
- expectJobValidated(job);
- storageUtil.jobStore.saveAcceptedJob(MANAGER_KEY, sanitizedConfiguration.getJobConfig());
- expect(cronScheduler.schedule(eq(job.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andThrow(new CronException("injected"));
-
- control.replay();
-
- cron.receiveJob(sanitizedConfiguration);
- }
-
- @Test(expected = ScheduleException.class)
- public void testRunOverlapRejected() throws Exception {
- IJobConfiguration killExisting = IJobConfiguration.build(
- job.newBuilder().setCronCollisionPolicy(CronCollisionPolicy.RUN_OVERLAP));
-
- control.replay();
-
- cron.receiveJob(new SanitizedConfiguration(killExisting));
- }
-
- @Test
- public void testRunOverlapLoadedSuccessfully() throws Exception {
- // Existing RUN_OVERLAP jobs should still load and map.
-
- expect(cronScheduler.startAsync()).andReturn(cronScheduler);
- cronScheduler.awaitRunning();
- shutdownRegistry.addAction(EasyMock.<ExceptionalCommand<?>>anyObject());
-
- IJobConfiguration jobA =
- IJobConfiguration.build(makeJob().newBuilder()
- .setCronCollisionPolicy(CronCollisionPolicy.RUN_OVERLAP));
-
- expect(storageUtil.jobStore.fetchJobs(MANAGER_KEY)).andReturn(ImmutableList.of(jobA));
- expect(cronScheduler.isValidSchedule(jobA.getCronSchedule())).andReturn(true);
- expect(cronScheduler.schedule(eq(jobA.getCronSchedule()), EasyMock.<Runnable>anyObject()))
- .andReturn("keyA");
-
- control.replay();
-
- cron.schedulerActive(new SchedulerActive());
- }
-
- private IJobConfiguration makeJob() {
- return IJobConfiguration.build(new JobConfiguration()
- .setOwner(new Identity(OWNER, OWNER))
- .setKey(JobKeys.from(OWNER, ENVIRONMENT, JOB_NAME).newBuilder())
- .setCronSchedule("1 1 1 1 1")
- .setTaskConfig(defaultTask())
- .setInstanceCount(1));
- }
-
- private static TaskConfig defaultTask() {
- return new TaskConfig()
- .setContactEmail("testing@twitter.com")
- .setExecutorConfig(new ExecutorConfig("aurora", "data"))
- .setNumCpus(1)
- .setRamMb(1024)
- .setDiskMb(1024)
- .setJobName(JOB_NAME)
- .setOwner(new Identity(OWNER, OWNER))
- .setEnvironment(DEFAULT_ENVIRONMENT);
- }
-}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/state/LockManagerImplTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/state/LockManagerImplTest.java b/src/test/java/org/apache/aurora/scheduler/state/LockManagerImplTest.java
index c8ad55d..cdb1f5d 100644
--- a/src/test/java/org/apache/aurora/scheduler/state/LockManagerImplTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/state/LockManagerImplTest.java
@@ -133,6 +133,6 @@ public class LockManagerImplTest extends EasyMockTest {
private void expectLockException(IJobKey key) {
expectedException.expect(LockException.class);
- expectedException.expectMessage(JobKeys.toPath(key));
+ expectedException.expectMessage(JobKeys.canonicalString(key));
}
}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
index 405da0a..2142f11 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/SchedulerThriftInterfaceTest.java
@@ -76,10 +76,13 @@ import org.apache.aurora.scheduler.base.Query;
import org.apache.aurora.scheduler.base.ScheduleException;
import org.apache.aurora.scheduler.configuration.ConfigurationManager;
import org.apache.aurora.scheduler.configuration.SanitizedConfiguration;
+import org.apache.aurora.scheduler.cron.CronException;
+import org.apache.aurora.scheduler.cron.CronJobManager;
import org.apache.aurora.scheduler.cron.CronPredictor;
+import org.apache.aurora.scheduler.cron.CrontabEntry;
+import org.apache.aurora.scheduler.cron.SanitizedCronJob;
import org.apache.aurora.scheduler.quota.QuotaInfo;
import org.apache.aurora.scheduler.quota.QuotaManager;
-import org.apache.aurora.scheduler.state.CronJobManager;
import org.apache.aurora.scheduler.state.LockManager;
import org.apache.aurora.scheduler.state.LockManager.LockException;
import org.apache.aurora.scheduler.state.MaintenanceController;
@@ -137,7 +140,7 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
private static final IJobKey JOB_KEY = JobKeys.from(ROLE, DEFAULT_ENVIRONMENT, JOB_NAME);
private static final ILockKey LOCK_KEY = ILockKey.build(LockKey.job(JOB_KEY.newBuilder()));
private static final ILock LOCK = ILock.build(new Lock().setKey(LOCK_KEY.newBuilder()));
- private static final JobConfiguration CRON_JOB = makeJob().setCronSchedule("test");
+ private static final JobConfiguration CRON_JOB = makeJob().setCronSchedule("* * * * *");
private static final Lock DEFAULT_LOCK = null;
private static final IResourceAggregate QUOTA =
@@ -678,7 +681,7 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
public void testReplaceCronTemplate() throws Exception {
expectAuth(ROLE, true);
lockManager.validateIfLocked(LOCK_KEY, Optional.<ILock>absent());
- cronJobManager.updateJob(anyObject(SanitizedConfiguration.class));
+ cronJobManager.updateJob(anyObject(SanitizedCronJob.class));
control.replay();
assertOkResponse(thrift.replaceCronTemplate(CRON_JOB, DEFAULT_LOCK, SESSION));
@@ -707,9 +710,9 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
public void testReplaceCronTemplateDoesNotExist() throws Exception {
expectAuth(ROLE, true);
lockManager.validateIfLocked(LOCK_KEY, Optional.<ILock>absent());
- cronJobManager.updateJob(
- SanitizedConfiguration.fromUnsanitized(IJobConfiguration.build(CRON_JOB)));
- expectLastCall().andThrow(new ScheduleException("Nope"));
+ cronJobManager.updateJob(anyObject(SanitizedCronJob.class));
+ expectLastCall().andThrow(new CronException("Nope"));
+
control.replay();
assertResponse(INVALID_REQUEST, thrift.replaceCronTemplate(CRON_JOB, DEFAULT_LOCK, SESSION));
@@ -1004,7 +1007,7 @@ public class SchedulerThriftInterfaceTest extends EasyMockTest {
Set<JobSummary> ownedImmedieteJobSummaryOnly = ImmutableSet.of(
new JobSummary().setJob(ownedImmediateJob).setStats(new JobStats().setActiveTaskCount(1)));
- expect(cronPredictor.predictNextRun(CRON_SCHEDULE))
+ expect(cronPredictor.predictNextRun(CrontabEntry.parse(CRON_SCHEDULE)))
.andReturn(new Date(nextCronRunMs))
.anyTimes();
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/thrift/ThriftIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/thrift/ThriftIT.java b/src/test/java/org/apache/aurora/scheduler/thrift/ThriftIT.java
index 488a545..5f32f21 100644
--- a/src/test/java/org/apache/aurora/scheduler/thrift/ThriftIT.java
+++ b/src/test/java/org/apache/aurora/scheduler/thrift/ThriftIT.java
@@ -34,8 +34,8 @@ import org.apache.aurora.gen.AuroraAdmin;
import org.apache.aurora.gen.ResourceAggregate;
import org.apache.aurora.gen.ServerInfo;
import org.apache.aurora.gen.SessionKey;
+import org.apache.aurora.scheduler.cron.CronJobManager;
import org.apache.aurora.scheduler.cron.CronPredictor;
-import org.apache.aurora.scheduler.cron.CronScheduler;
import org.apache.aurora.scheduler.quota.QuotaManager;
import org.apache.aurora.scheduler.state.LockManager;
import org.apache.aurora.scheduler.state.MaintenanceController;
@@ -149,7 +149,7 @@ public class ThriftIT extends EasyMockTest {
@Override
protected void configure() {
- bindMock(CronScheduler.class);
+ bindMock(CronJobManager.class);
bindMock(MaintenanceController.class);
bindMock(Recovery.class);
bindMock(SchedulerCore.class);
[3/5] CronScheduler based on Quartz
Posted by ke...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/main/resources/org/apache/aurora/scheduler/cron/testing/cron-schedule-predictions.json
----------------------------------------------------------------------
diff --git a/src/main/resources/org/apache/aurora/scheduler/cron/testing/cron-schedule-predictions.json b/src/main/resources/org/apache/aurora/scheduler/cron/testing/cron-schedule-predictions.json
deleted file mode 100644
index dced8b4..0000000
--- a/src/main/resources/org/apache/aurora/scheduler/cron/testing/cron-schedule-predictions.json
+++ /dev/null
@@ -1,3332 +0,0 @@
-[
- {
- "schedule": "0 20 * * 1",
- "triggerTimes": [
- 417600000,
- 1022400000,
- 1627200000,
- 2232000000,
- 2836800000,
- 3441600000,
- 4046400000,
- 4651200000,
- 5256000000,
- 5860800000
- ]
- },
- {
- "schedule": "11 * * * *",
- "triggerTimes": [
- 660000,
- 4260000,
- 7860000,
- 11460000,
- 15060000,
- 18660000,
- 22260000,
- 25860000,
- 29460000,
- 33060000
- ]
- },
- {
- "schedule": "04 02 * * *",
- "triggerTimes": [
- 7440000,
- 93840000,
- 180240000,
- 266640000,
- 353040000,
- 439440000,
- 525840000,
- 612240000,
- 698640000,
- 785040000
- ]
- },
- {
- "schedule": "09 22 * * *",
- "triggerTimes": [
- 79740000,
- 166140000,
- 252540000,
- 338940000,
- 425340000,
- 511740000,
- 598140000,
- 684540000,
- 770940000,
- 857340000
- ]
- },
- {
- "schedule": "1-56/5 * * * *",
- "triggerTimes": [
- 60000,
- 360000,
- 660000,
- 960000,
- 1260000,
- 1560000,
- 1860000,
- 2160000,
- 2460000,
- 2760000
- ]
- },
- {
- "schedule": "05 02,08,12 * * *",
- "triggerTimes": [
- 7500000,
- 29100000,
- 43500000,
- 93900000,
- 115500000,
- 129900000,
- 180300000,
- 201900000,
- 216300000,
- 266700000
- ]
- },
- {
- "schedule": "26 * * * *",
- "triggerTimes": [
- 1560000,
- 5160000,
- 8760000,
- 12360000,
- 15960000,
- 19560000,
- 23160000,
- 26760000,
- 30360000,
- 33960000
- ]
- },
- {
- "schedule": "3,43 * * * *",
- "triggerTimes": [
- 180000,
- 2580000,
- 3780000,
- 6180000,
- 7380000,
- 9780000,
- 10980000,
- 13380000,
- 14580000,
- 16980000
- ]
- },
- {
- "schedule": "0 17-23 * * 1-5",
- "triggerTimes": [
- 61200000,
- 64800000,
- 68400000,
- 72000000,
- 75600000,
- 79200000,
- 82800000,
- 147600000,
- 151200000,
- 154800000
- ]
- },
- {
- "schedule": "0 0,12 * * *",
- "triggerTimes": [
- 43200000,
- 86400000,
- 129600000,
- 172800000,
- 216000000,
- 259200000,
- 302400000,
- 345600000,
- 388800000,
- 432000000
- ]
- },
- {
- "schedule": "10 02,08,12 * * *",
- "triggerTimes": [
- 7800000,
- 29400000,
- 43800000,
- 94200000,
- 115800000,
- 130200000,
- 180600000,
- 202200000,
- 216600000,
- 267000000
- ]
- },
- {
- "schedule": "50 */4 * * *",
- "triggerTimes": [
- 3000000,
- 17400000,
- 31800000,
- 46200000,
- 60600000,
- 75000000,
- 89400000,
- 103800000,
- 118200000,
- 132600000
- ]
- },
- {
- "schedule": "10 02,08,14,20 * * *",
- "triggerTimes": [
- 7800000,
- 29400000,
- 51000000,
- 72600000,
- 94200000,
- 115800000,
- 137400000,
- 159000000,
- 180600000,
- 202200000
- ]
- },
- {
- "schedule": "0 */6 * * *",
- "triggerTimes": [
- 21600000,
- 43200000,
- 64800000,
- 86400000,
- 108000000,
- 129600000,
- 151200000,
- 172800000,
- 194400000,
- 216000000
- ]
- },
- {
- "schedule": "* * * * *",
- "triggerTimes": [
- 60000,
- 120000,
- 180000,
- 240000,
- 300000,
- 360000,
- 420000,
- 480000,
- 540000,
- 600000
- ]
- },
- {
- "schedule": "30 15 * * *,",
- "triggerTimes": [
- 55800000,
- 142200000,
- 228600000,
- 315000000,
- 401400000,
- 487800000,
- 574200000,
- 660600000,
- 747000000,
- 833400000
- ]
- },
- {
- "schedule": "00 11 * * *",
- "triggerTimes": [
- 39600000,
- 126000000,
- 212400000,
- 298800000,
- 385200000,
- 471600000,
- 558000000,
- 644400000,
- 730800000,
- 817200000
- ]
- },
- {
- "schedule": "55 06 * * *",
- "triggerTimes": [
- 24900000,
- 111300000,
- 197700000,
- 284100000,
- 370500000,
- 456900000,
- 543300000,
- 629700000,
- 716100000,
- 802500000
- ]
- },
- {
- "schedule": "0 4 * * *",
- "triggerTimes": [
- 14400000,
- 100800000,
- 187200000,
- 273600000,
- 360000000,
- 446400000,
- 532800000,
- 619200000,
- 705600000,
- 792000000
- ]
- },
- {
- "schedule": "55 */1 * * *",
- "triggerTimes": [
- 3300000,
- 6900000,
- 10500000,
- 14100000,
- 17700000,
- 21300000,
- 24900000,
- 28500000,
- 32100000,
- 35700000
- ]
- },
- {
- "schedule": "15 */3 * * *",
- "triggerTimes": [
- 900000,
- 11700000,
- 22500000,
- 33300000,
- 44100000,
- 54900000,
- 65700000,
- 76500000,
- 87300000,
- 98100000
- ]
- },
- {
- "schedule": "42 8,12,16 * * *",
- "triggerTimes": [
- 31320000,
- 45720000,
- 60120000,
- 117720000,
- 132120000,
- 146520000,
- 204120000,
- 218520000,
- 232920000,
- 290520000
- ]
- },
- {
- "schedule": "23 * * * *",
- "triggerTimes": [
- 1380000,
- 4980000,
- 8580000,
- 12180000,
- 15780000,
- 19380000,
- 22980000,
- 26580000,
- 30180000,
- 33780000
- ]
- },
- {
- "schedule": "10 16 * * *",
- "triggerTimes": [
- 58200000,
- 144600000,
- 231000000,
- 317400000,
- 403800000,
- 490200000,
- 576600000,
- 663000000,
- 749400000,
- 835800000
- ]
- },
- {
- "schedule": "*/30 * * * *",
- "triggerTimes": [
- 1800000,
- 3600000,
- 5400000,
- 7200000,
- 9000000,
- 10800000,
- 12600000,
- 14400000,
- 16200000,
- 18000000
- ]
- },
- {
- "schedule": "20 */3 * * *",
- "triggerTimes": [
- 1200000,
- 12000000,
- 22800000,
- 33600000,
- 44400000,
- 55200000,
- 66000000,
- 76800000,
- 87600000,
- 98400000
- ]
- },
- {
- "schedule": "8 6,12,18 * * *",
- "triggerTimes": [
- 22080000,
- 43680000,
- 65280000,
- 108480000,
- 130080000,
- 151680000,
- 194880000,
- 216480000,
- 238080000,
- 281280000
- ]
- },
- {
- "schedule": "30 7,12,22 * * *",
- "triggerTimes": [
- 27000000,
- 45000000,
- 81000000,
- 113400000,
- 131400000,
- 167400000,
- 199800000,
- 217800000,
- 253800000,
- 286200000
- ]
- },
- {
- "schedule": "0 0 12 * *",
- "triggerTimes": [
- 950400000,
- 3628800000,
- 6048000000,
- 8726400000,
- 11318400000,
- 13996800000,
- 16588800000,
- 19267200000,
- 21945600000,
- 24537600000
- ]
- },
- {
- "schedule": "17 5,8,13,16,19 * * *",
- "triggerTimes": [
- 19020000,
- 29820000,
- 47820000,
- 58620000,
- 69420000,
- 105420000,
- 116220000,
- 134220000,
- 145020000,
- 155820000
- ]
- },
- {
- "schedule": "27 8,20 * * *",
- "triggerTimes": [
- 30420000,
- 73620000,
- 116820000,
- 160020000,
- 203220000,
- 246420000,
- 289620000,
- 332820000,
- 376020000,
- 419220000
- ]
- },
- {
- "schedule": "15 */6 * * *",
- "triggerTimes": [
- 900000,
- 22500000,
- 44100000,
- 65700000,
- 87300000,
- 108900000,
- 130500000,
- 152100000,
- 173700000,
- 195300000
- ]
- },
- {
- "schedule": "01 15 * * *",
- "triggerTimes": [
- 54060000,
- 140460000,
- 226860000,
- 313260000,
- 399660000,
- 486060000,
- 572460000,
- 658860000,
- 745260000,
- 831660000
- ]
- },
- {
- "schedule": "0 18 * * *",
- "triggerTimes": [
- 64800000,
- 151200000,
- 237600000,
- 324000000,
- 410400000,
- 496800000,
- 583200000,
- 669600000,
- 756000000,
- 842400000
- ]
- },
- {
- "schedule": "24 * * * *",
- "triggerTimes": [
- 1440000,
- 5040000,
- 8640000,
- 12240000,
- 15840000,
- 19440000,
- 23040000,
- 26640000,
- 30240000,
- 33840000
- ]
- },
- {
- "schedule": "18 00 * * *",
- "triggerTimes": [
- 1080000,
- 87480000,
- 173880000,
- 260280000,
- 346680000,
- 433080000,
- 519480000,
- 605880000,
- 692280000,
- 778680000
- ]
- },
- {
- "schedule": "0 16 * * *",
- "triggerTimes": [
- 57600000,
- 144000000,
- 230400000,
- 316800000,
- 403200000,
- 489600000,
- 576000000,
- 662400000,
- 748800000,
- 835200000
- ]
- },
- {
- "schedule": "45 5 * * *",
- "triggerTimes": [
- 20700000,
- 107100000,
- 193500000,
- 279900000,
- 366300000,
- 452700000,
- 539100000,
- 625500000,
- 711900000,
- 798300000
- ]
- },
- {
- "schedule": "0 18 * * 4",
- "triggerTimes": [
- 64800000,
- 669600000,
- 1274400000,
- 1879200000,
- 2484000000,
- 3088800000,
- 3693600000,
- 4298400000,
- 4903200000,
- 5508000000
- ]
- },
- {
- "schedule": "30 19 * * *",
- "triggerTimes": [
- 70200000,
- 156600000,
- 243000000,
- 329400000,
- 415800000,
- 502200000,
- 588600000,
- 675000000,
- 761400000,
- 847800000
- ]
- },
- {
- "schedule": "0 13 * * 2",
- "triggerTimes": [
- 478800000,
- 1083600000,
- 1688400000,
- 2293200000,
- 2898000000,
- 3502800000,
- 4107600000,
- 4712400000,
- 5317200000,
- 5922000000
- ]
- },
- {
- "schedule": "25 17,20,21,23 * * *",
- "triggerTimes": [
- 62700000,
- 73500000,
- 77100000,
- 84300000,
- 149100000,
- 159900000,
- 163500000,
- 170700000,
- 235500000,
- 246300000
- ]
- },
- {
- "schedule": "0 13 * * 3",
- "triggerTimes": [
- 565200000,
- 1170000000,
- 1774800000,
- 2379600000,
- 2984400000,
- 3589200000,
- 4194000000,
- 4798800000,
- 5403600000,
- 6008400000
- ]
- },
- {
- "schedule": "58 */2 * * *",
- "triggerTimes": [
- 3480000,
- 10680000,
- 17880000,
- 25080000,
- 32280000,
- 39480000,
- 46680000,
- 53880000,
- 61080000,
- 68280000
- ]
- },
- {
- "schedule": "0 9 4,18 * *",
- "triggerTimes": [
- 291600000,
- 1501200000,
- 2970000000,
- 4179600000,
- 5389200000,
- 6598800000,
- 8067600000,
- 9277200000,
- 10659600000,
- 11869200000
- ]
- },
- {
- "schedule": "37 */6 * * *",
- "triggerTimes": [
- 2220000,
- 23820000,
- 45420000,
- 67020000,
- 88620000,
- 110220000,
- 131820000,
- 153420000,
- 175020000,
- 196620000
- ]
- },
- {
- "schedule": "00 14 * * *",
- "triggerTimes": [
- 50400000,
- 136800000,
- 223200000,
- 309600000,
- 396000000,
- 482400000,
- 568800000,
- 655200000,
- 741600000,
- 828000000
- ]
- },
- {
- "schedule": "0 * * * *",
- "triggerTimes": [
- 3600000,
- 7200000,
- 10800000,
- 14400000,
- 18000000,
- 21600000,
- 25200000,
- 28800000,
- 32400000,
- 36000000
- ]
- },
- {
- "schedule": "29 9,16,22 * * *",
- "triggerTimes": [
- 34140000,
- 59340000,
- 80940000,
- 120540000,
- 145740000,
- 167340000,
- 206940000,
- 232140000,
- 253740000,
- 293340000
- ]
- },
- {
- "schedule": "37 3 * * *",
- "triggerTimes": [
- 13020000,
- 99420000,
- 185820000,
- 272220000,
- 358620000,
- 445020000,
- 531420000,
- 617820000,
- 704220000,
- 790620000
- ]
- },
- {
- "schedule": "*/5 * * * *",
- "triggerTimes": [
- 300000,
- 600000,
- 900000,
- 1200000,
- 1500000,
- 1800000,
- 2100000,
- 2400000,
- 2700000,
- 3000000
- ]
- },
- {
- "schedule": "7 */2 * * *",
- "triggerTimes": [
- 420000,
- 7620000,
- 14820000,
- 22020000,
- 29220000,
- 36420000,
- 43620000,
- 50820000,
- 58020000,
- 65220000
- ]
- },
- {
- "schedule": "55 07 * * *",
- "triggerTimes": [
- 28500000,
- 114900000,
- 201300000,
- 287700000,
- 374100000,
- 460500000,
- 546900000,
- 633300000,
- 719700000,
- 806100000
- ]
- },
- {
- "schedule": "0 19 * * *",
- "triggerTimes": [
- 68400000,
- 154800000,
- 241200000,
- 327600000,
- 414000000,
- 500400000,
- 586800000,
- 673200000,
- 759600000,
- 846000000
- ]
- },
- {
- "schedule": "15 */2 * * *",
- "triggerTimes": [
- 900000,
- 8100000,
- 15300000,
- 22500000,
- 29700000,
- 36900000,
- 44100000,
- 51300000,
- 58500000,
- 65700000
- ]
- },
- {
- "schedule": "17 00 * * *",
- "triggerTimes": [
- 1020000,
- 87420000,
- 173820000,
- 260220000,
- 346620000,
- 433020000,
- 519420000,
- 605820000,
- 692220000,
- 778620000
- ]
- },
- {
- "schedule": "0 0 * * 1",
- "triggerTimes": [
- 345600000,
- 950400000,
- 1555200000,
- 2160000000,
- 2764800000,
- 3369600000,
- 3974400000,
- 4579200000,
- 5184000000,
- 5788800000
- ]
- },
- {
- "schedule": "29 */4 * * *",
- "triggerTimes": [
- 1740000,
- 16140000,
- 30540000,
- 44940000,
- 59340000,
- 73740000,
- 88140000,
- 102540000,
- 116940000,
- 131340000
- ]
- },
- {
- "schedule": "0 23 * * *",
- "triggerTimes": [
- 82800000,
- 169200000,
- 255600000,
- 342000000,
- 428400000,
- 514800000,
- 601200000,
- 687600000,
- 774000000,
- 860400000
- ]
- },
- {
- "schedule": "0 7 * * *",
- "triggerTimes": [
- 25200000,
- 111600000,
- 198000000,
- 284400000,
- 370800000,
- 457200000,
- 543600000,
- 630000000,
- 716400000,
- 802800000
- ]
- },
- {
- "schedule": "12 * * * *",
- "triggerTimes": [
- 720000,
- 4320000,
- 7920000,
- 11520000,
- 15120000,
- 18720000,
- 22320000,
- 25920000,
- 29520000,
- 33120000
- ]
- },
- {
- "schedule": "0 23 * * 3",
- "triggerTimes": [
- 601200000,
- 1206000000,
- 1810800000,
- 2415600000,
- 3020400000,
- 3625200000,
- 4230000000,
- 4834800000,
- 5439600000,
- 6044400000
- ]
- },
- {
- "schedule": "23 */4 * * *",
- "triggerTimes": [
- 1380000,
- 15780000,
- 30180000,
- 44580000,
- 58980000,
- 73380000,
- 87780000,
- 102180000,
- 116580000,
- 130980000
- ]
- },
- {
- "schedule": "30 1-23/2 * * *",
- "triggerTimes": [
- 5400000,
- 12600000,
- 19800000,
- 27000000,
- 34200000,
- 41400000,
- 48600000,
- 55800000,
- 63000000,
- 70200000
- ]
- },
- {
- "schedule": "5,15,25,35,45,55 * * * *",
- "triggerTimes": [
- 300000,
- 900000,
- 1500000,
- 2100000,
- 2700000,
- 3300000,
- 3900000,
- 4500000,
- 5100000,
- 5700000
- ]
- },
- {
- "schedule": "23 1,11,21 * * *",
- "triggerTimes": [
- 4980000,
- 40980000,
- 76980000,
- 91380000,
- 127380000,
- 163380000,
- 177780000,
- 213780000,
- 249780000,
- 264180000
- ]
- },
- {
- "schedule": "15 04,10,16,22 * * *",
- "triggerTimes": [
- 15300000,
- 36900000,
- 58500000,
- 80100000,
- 101700000,
- 123300000,
- 144900000,
- 166500000,
- 188100000,
- 209700000
- ]
- },
- {
- "schedule": "*/20 * * * *",
- "triggerTimes": [
- 1200000,
- 2400000,
- 3600000,
- 4800000,
- 6000000,
- 7200000,
- 8400000,
- 9600000,
- 10800000,
- 12000000
- ]
- },
- {
- "schedule": "12,42 * * * *",
- "triggerTimes": [
- 720000,
- 2520000,
- 4320000,
- 6120000,
- 7920000,
- 9720000,
- 11520000,
- 13320000,
- 15120000,
- 16920000
- ]
- },
- {
- "schedule": "26 2,6,10,14,18,22 * * *",
- "triggerTimes": [
- 8760000,
- 23160000,
- 37560000,
- 51960000,
- 66360000,
- 80760000,
- 95160000,
- 109560000,
- 123960000,
- 138360000
- ]
- },
- {
- "schedule": "0 3,6,9,12,15,18,21 * * *",
- "triggerTimes": [
- 10800000,
- 21600000,
- 32400000,
- 43200000,
- 54000000,
- 64800000,
- 75600000,
- 97200000,
- 108000000,
- 118800000
- ]
- },
- {
- "schedule": "25 14 * * *",
- "triggerTimes": [
- 51900000,
- 138300000,
- 224700000,
- 311100000,
- 397500000,
- 483900000,
- 570300000,
- 656700000,
- 743100000,
- 829500000
- ]
- },
- {
- "schedule": "0 5 * * *,",
- "triggerTimes": [
- 18000000,
- 104400000,
- 190800000,
- 277200000,
- 363600000,
- 450000000,
- 536400000,
- 622800000,
- 709200000,
- 795600000
- ]
- },
- {
- "schedule": "43 * * * *",
- "triggerTimes": [
- 2580000,
- 6180000,
- 9780000,
- 13380000,
- 16980000,
- 20580000,
- 24180000,
- 27780000,
- 31380000,
- 34980000
- ]
- },
- {
- "schedule": "39 6,12,16 * * *",
- "triggerTimes": [
- 23940000,
- 45540000,
- 59940000,
- 110340000,
- 131940000,
- 146340000,
- 196740000,
- 218340000,
- 232740000,
- 283140000
- ]
- },
- {
- "schedule": "0 9 1 * *",
- "triggerTimes": [
- 32400000,
- 2710800000,
- 5130000000,
- 7808400000,
- 10400400000,
- 13078800000,
- 15670800000,
- 18349200000,
- 21027600000,
- 23619600000
- ]
- },
- {
- "schedule": "14-59/30 * * * *",
- "triggerTimes": [
- 840000,
- 2640000,
- 4440000,
- 6240000,
- 8040000,
- 9840000,
- 11640000,
- 13440000,
- 15240000,
- 17040000
- ]
- },
- {
- "schedule": "0 0 * * *",
- "triggerTimes": [
- 86400000,
- 172800000,
- 259200000,
- 345600000,
- 432000000,
- 518400000,
- 604800000,
- 691200000,
- 777600000,
- 864000000
- ]
- },
- {
- "schedule": "0 */3 * * *",
- "triggerTimes": [
- 10800000,
- 21600000,
- 32400000,
- 43200000,
- 54000000,
- 64800000,
- 75600000,
- 86400000,
- 97200000,
- 108000000
- ]
- },
- {
- "schedule": "16 5,13,21 * * *",
- "triggerTimes": [
- 18960000,
- 47760000,
- 76560000,
- 105360000,
- 134160000,
- 162960000,
- 191760000,
- 220560000,
- 249360000,
- 278160000
- ]
- },
- {
- "schedule": "30 18,23 * * MON-FRI",
- "triggerTimes": [
- 66600000,
- 84600000,
- 153000000,
- 171000000,
- 412200000,
- 430200000,
- 498600000,
- 516600000,
- 585000000,
- 603000000
- ]
- },
- {
- "schedule": "0,15,30,45 * * * *",
- "triggerTimes": [
- 900000,
- 1800000,
- 2700000,
- 3600000,
- 4500000,
- 5400000,
- 6300000,
- 7200000,
- 8100000,
- 9000000
- ]
- },
- {
- "schedule": "42 8,20 * * *",
- "triggerTimes": [
- 31320000,
- 74520000,
- 117720000,
- 160920000,
- 204120000,
- 247320000,
- 290520000,
- 333720000,
- 376920000,
- 420120000
- ]
- },
- {
- "schedule": "46 */6 * * *",
- "triggerTimes": [
- 2760000,
- 24360000,
- 45960000,
- 67560000,
- 89160000,
- 110760000,
- 132360000,
- 153960000,
- 175560000,
- 197160000
- ]
- },
- {
- "schedule": "0 3 * * *",
- "triggerTimes": [
- 10800000,
- 97200000,
- 183600000,
- 270000000,
- 356400000,
- 442800000,
- 529200000,
- 615600000,
- 702000000,
- 788400000
- ]
- },
- {
- "schedule": "16 9,16 * * *",
- "triggerTimes": [
- 33360000,
- 58560000,
- 119760000,
- 144960000,
- 206160000,
- 231360000,
- 292560000,
- 317760000,
- 378960000,
- 404160000
- ]
- },
- {
- "schedule": "15 0 * * *",
- "triggerTimes": [
- 900000,
- 87300000,
- 173700000,
- 260100000,
- 346500000,
- 432900000,
- 519300000,
- 605700000,
- 692100000,
- 778500000
- ]
- },
- {
- "schedule": "05 * * * *",
- "triggerTimes": [
- 300000,
- 3900000,
- 7500000,
- 11100000,
- 14700000,
- 18300000,
- 21900000,
- 25500000,
- 29100000,
- 32700000
- ]
- },
- {
- "schedule": "30 * * * *",
- "triggerTimes": [
- 1800000,
- 5400000,
- 9000000,
- 12600000,
- 16200000,
- 19800000,
- 23400000,
- 27000000,
- 30600000,
- 34200000
- ]
- },
- {
- "schedule": "0 2,14 * * *",
- "triggerTimes": [
- 7200000,
- 50400000,
- 93600000,
- 136800000,
- 180000000,
- 223200000,
- 266400000,
- 309600000,
- 352800000,
- 396000000
- ]
- },
- {
- "schedule": "28 23 * * 3",
- "triggerTimes": [
- 602880000,
- 1207680000,
- 1812480000,
- 2417280000,
- 3022080000,
- 3626880000,
- 4231680000,
- 4836480000,
- 5441280000,
- 6046080000
- ]
- },
- {
- "schedule": "5 */4 * * *",
- "triggerTimes": [
- 300000,
- 14700000,
- 29100000,
- 43500000,
- 57900000,
- 72300000,
- 86700000,
- 101100000,
- 115500000,
- 129900000
- ]
- },
- {
- "schedule": "0 18,22 * * MON-FRI",
- "triggerTimes": [
- 64800000,
- 79200000,
- 151200000,
- 165600000,
- 410400000,
- 424800000,
- 496800000,
- 511200000,
- 583200000,
- 597600000
- ]
- },
- {
- "schedule": "01 21 * * *",
- "triggerTimes": [
- 75660000,
- 162060000,
- 248460000,
- 334860000,
- 421260000,
- 507660000,
- 594060000,
- 680460000,
- 766860000,
- 853260000
- ]
- },
- {
- "schedule": "1 */6 * * *",
- "triggerTimes": [
- 60000,
- 21660000,
- 43260000,
- 64860000,
- 86460000,
- 108060000,
- 129660000,
- 151260000,
- 172860000,
- 194460000
- ]
- },
- {
- "schedule": "*/10 * * * *",
- "triggerTimes": [
- 600000,
- 1200000,
- 1800000,
- 2400000,
- 3000000,
- 3600000,
- 4200000,
- 4800000,
- 5400000,
- 6000000
- ]
- },
- {
- "schedule": "44 */2 * * *",
- "triggerTimes": [
- 2640000,
- 9840000,
- 17040000,
- 24240000,
- 31440000,
- 38640000,
- 45840000,
- 53040000,
- 60240000,
- 67440000
- ]
- },
- {
- "schedule": "30 2 * * *",
- "triggerTimes": [
- 9000000,
- 95400000,
- 181800000,
- 268200000,
- 354600000,
- 441000000,
- 527400000,
- 613800000,
- 700200000,
- 786600000
- ]
- },
- {
- "schedule": "58 * * * *",
- "triggerTimes": [
- 3480000,
- 7080000,
- 10680000,
- 14280000,
- 17880000,
- 21480000,
- 25080000,
- 28680000,
- 32280000,
- 35880000
- ]
- },
- {
- "schedule": "30 23 * * 6",
- "triggerTimes": [
- 257400000,
- 862200000,
- 1467000000,
- 2071800000,
- 2676600000,
- 3281400000,
- 3886200000,
- 4491000000,
- 5095800000,
- 5700600000
- ]
- },
- {
- "schedule": "40 23 * * *",
- "triggerTimes": [
- 85200000,
- 171600000,
- 258000000,
- 344400000,
- 430800000,
- 517200000,
- 603600000,
- 690000000,
- 776400000,
- 862800000
- ]
- },
- {
- "schedule": "0 5,10,15,20,1 * * *",
- "triggerTimes": [
- 3600000,
- 18000000,
- 36000000,
- 54000000,
- 72000000,
- 90000000,
- 104400000,
- 122400000,
- 140400000,
- 158400000
- ]
- },
- {
- "schedule": "22 * * * *",
- "triggerTimes": [
- 1320000,
- 4920000,
- 8520000,
- 12120000,
- 15720000,
- 19320000,
- 22920000,
- 26520000,
- 30120000,
- 33720000
- ]
- },
- {
- "schedule": "00 17 1-3,5-31 * *",
- "triggerTimes": [
- 61200000,
- 147600000,
- 234000000,
- 406800000,
- 493200000,
- 579600000,
- 666000000,
- 752400000,
- 838800000,
- 925200000
- ]
- },
- {
- "schedule": "0 2 1 * *",
- "triggerTimes": [
- 7200000,
- 2685600000,
- 5104800000,
- 7783200000,
- 10375200000,
- 13053600000,
- 15645600000,
- 18324000000,
- 21002400000,
- 23594400000
- ]
- },
- {
- "schedule": "20 20 * * *",
- "triggerTimes": [
- 73200000,
- 159600000,
- 246000000,
- 332400000,
- 418800000,
- 505200000,
- 591600000,
- 678000000,
- 764400000,
- 850800000
- ]
- },
- {
- "schedule": "45 1 * * *",
- "triggerTimes": [
- 6300000,
- 92700000,
- 179100000,
- 265500000,
- 351900000,
- 438300000,
- 524700000,
- 611100000,
- 697500000,
- 783900000
- ]
- },
- {
- "schedule": "3-59/5 * * * *",
- "triggerTimes": [
- 180000,
- 480000,
- 780000,
- 1080000,
- 1380000,
- 1680000,
- 1980000,
- 2280000,
- 2580000,
- 2880000
- ]
- },
- {
- "schedule": "21 * * * *",
- "triggerTimes": [
- 1260000,
- 4860000,
- 8460000,
- 12060000,
- 15660000,
- 19260000,
- 22860000,
- 26460000,
- 30060000,
- 33660000
- ]
- },
- {
- "schedule": "37 */1 * * *",
- "triggerTimes": [
- 2220000,
- 5820000,
- 9420000,
- 13020000,
- 16620000,
- 20220000,
- 23820000,
- 27420000,
- 31020000,
- 34620000
- ]
- },
- {
- "schedule": "12 3 * * 1,3,5",
- "triggerTimes": [
- 97920000,
- 357120000,
- 529920000,
- 702720000,
- 961920000,
- 1134720000,
- 1307520000,
- 1566720000,
- 1739520000,
- 1912320000
- ]
- },
- {
- "schedule": "10 * * * *",
- "triggerTimes": [
- 600000,
- 4200000,
- 7800000,
- 11400000,
- 15000000,
- 18600000,
- 22200000,
- 25800000,
- 29400000,
- 33000000
- ]
- },
- {
- "schedule": "*/4 * * * *",
- "triggerTimes": [
- 240000,
- 480000,
- 720000,
- 960000,
- 1200000,
- 1440000,
- 1680000,
- 1920000,
- 2160000,
- 2400000
- ]
- },
- {
- "schedule": "36 * * * *",
- "triggerTimes": [
- 2160000,
- 5760000,
- 9360000,
- 12960000,
- 16560000,
- 20160000,
- 23760000,
- 27360000,
- 30960000,
- 34560000
- ]
- },
- {
- "schedule": "10 7 * * *",
- "triggerTimes": [
- 25800000,
- 112200000,
- 198600000,
- 285000000,
- 371400000,
- 457800000,
- 544200000,
- 630600000,
- 717000000,
- 803400000
- ]
- },
- {
- "schedule": "55 6 * * *",
- "triggerTimes": [
- 24900000,
- 111300000,
- 197700000,
- 284100000,
- 370500000,
- 456900000,
- 543300000,
- 629700000,
- 716100000,
- 802500000
- ]
- },
- {
- "schedule": "0 */2 * * *",
- "triggerTimes": [
- 7200000,
- 14400000,
- 21600000,
- 28800000,
- 36000000,
- 43200000,
- 50400000,
- 57600000,
- 64800000,
- 72000000
- ]
- },
- {
- "schedule": "0 5 * * *",
- "triggerTimes": [
- 18000000,
- 104400000,
- 190800000,
- 277200000,
- 363600000,
- 450000000,
- 536400000,
- 622800000,
- 709200000,
- 795600000
- ]
- },
- {
- "schedule": "22 */4 * * *",
- "triggerTimes": [
- 1320000,
- 15720000,
- 30120000,
- 44520000,
- 58920000,
- 73320000,
- 87720000,
- 102120000,
- 116520000,
- 130920000
- ]
- },
- {
- "schedule": "17 */2 * * *",
- "triggerTimes": [
- 1020000,
- 8220000,
- 15420000,
- 22620000,
- 29820000,
- 37020000,
- 44220000,
- 51420000,
- 58620000,
- 65820000
- ]
- },
- {
- "schedule": "25 * * * *",
- "triggerTimes": [
- 1500000,
- 5100000,
- 8700000,
- 12300000,
- 15900000,
- 19500000,
- 23100000,
- 26700000,
- 30300000,
- 33900000
- ]
- },
- {
- "schedule": "*/6 * * * *",
- "triggerTimes": [
- 360000,
- 720000,
- 1080000,
- 1440000,
- 1800000,
- 2160000,
- 2520000,
- 2880000,
- 3240000,
- 3600000
- ]
- },
- {
- "schedule": "5 * * * *",
- "triggerTimes": [
- 300000,
- 3900000,
- 7500000,
- 11100000,
- 14700000,
- 18300000,
- 21900000,
- 25500000,
- 29100000,
- 32700000
- ]
- },
- {
- "schedule": "0 2 * * *",
- "triggerTimes": [
- 7200000,
- 93600000,
- 180000000,
- 266400000,
- 352800000,
- 439200000,
- 525600000,
- 612000000,
- 698400000,
- 784800000
- ]
- },
- {
- "schedule": "0 * * * *",
- "triggerTimes": [
- 3600000,
- 7200000,
- 10800000,
- 14400000,
- 18000000,
- 21600000,
- 25200000,
- 28800000,
- 32400000,
- 36000000
- ]
- },
- {
- "schedule": "0 14 * * *,,",
- "triggerTimes": [
- 50400000,
- 136800000,
- 223200000,
- 309600000,
- 396000000,
- 482400000,
- 568800000,
- 655200000,
- 741600000,
- 828000000
- ]
- },
- {
- "schedule": "30 02,08,12 * * *",
- "triggerTimes": [
- 9000000,
- 30600000,
- 45000000,
- 95400000,
- 117000000,
- 131400000,
- 181800000,
- 203400000,
- 217800000,
- 268200000
- ]
- },
- {
- "schedule": "44 23 * * *",
- "triggerTimes": [
- 85440000,
- 171840000,
- 258240000,
- 344640000,
- 431040000,
- 517440000,
- 603840000,
- 690240000,
- 776640000,
- 863040000
- ]
- },
- {
- "schedule": "0 */4 * * *",
- "triggerTimes": [
- 14400000,
- 28800000,
- 43200000,
- 57600000,
- 72000000,
- 86400000,
- 100800000,
- 115200000,
- 129600000,
- 144000000
- ]
- },
- {
- "schedule": "0 12 * * *",
- "triggerTimes": [
- 43200000,
- 129600000,
- 216000000,
- 302400000,
- 388800000,
- 475200000,
- 561600000,
- 648000000,
- 734400000,
- 820800000
- ]
- },
- {
- "schedule": "*/2 * * * *",
- "triggerTimes": [
- 120000,
- 240000,
- 360000,
- 480000,
- 600000,
- 720000,
- 840000,
- 960000,
- 1080000,
- 1200000
- ]
- },
- {
- "schedule": "22 1 * * *",
- "triggerTimes": [
- 4920000,
- 91320000,
- 177720000,
- 264120000,
- 350520000,
- 436920000,
- 523320000,
- 609720000,
- 696120000,
- 782520000
- ]
- },
- {
- "schedule": "45 * * * *",
- "triggerTimes": [
- 2700000,
- 6300000,
- 9900000,
- 13500000,
- 17100000,
- 20700000,
- 24300000,
- 27900000,
- 31500000,
- 35100000
- ]
- },
- {
- "schedule": "00 23 * * *",
- "triggerTimes": [
- 82800000,
- 169200000,
- 255600000,
- 342000000,
- 428400000,
- 514800000,
- 601200000,
- 687600000,
- 774000000,
- 860400000
- ]
- },
- {
- "schedule": "3,6,9,12,18,21,24,27,33,36,39,42,48,51,54,57 * * * *",
- "triggerTimes": [
- 180000,
- 360000,
- 540000,
- 720000,
- 1080000,
- 1260000,
- 1440000,
- 1620000,
- 1980000,
- 2160000
- ]
- },
- {
- "schedule": "32 1 * * *",
- "triggerTimes": [
- 5520000,
- 91920000,
- 178320000,
- 264720000,
- 351120000,
- 437520000,
- 523920000,
- 610320000,
- 696720000,
- 783120000
- ]
- },
- {
- "schedule": "35 */2 * * *",
- "triggerTimes": [
- 2100000,
- 9300000,
- 16500000,
- 23700000,
- 30900000,
- 38100000,
- 45300000,
- 52500000,
- 59700000,
- 66900000
- ]
- },
- {
- "schedule": "27 1 * * *",
- "triggerTimes": [
- 5220000,
- 91620000,
- 178020000,
- 264420000,
- 350820000,
- 437220000,
- 523620000,
- 610020000,
- 696420000,
- 782820000
- ]
- },
- {
- "schedule": "0 21 * * 3",
- "triggerTimes": [
- 594000000,
- 1198800000,
- 1803600000,
- 2408400000,
- 3013200000,
- 3618000000,
- 4222800000,
- 4827600000,
- 5432400000,
- 6037200000
- ]
- },
- {
- "schedule": "55 03 * * *",
- "triggerTimes": [
- 14100000,
- 100500000,
- 186900000,
- 273300000,
- 359700000,
- 446100000,
- 532500000,
- 618900000,
- 705300000,
- 791700000
- ]
- },
- {
- "schedule": "0 23 2-31 * *",
- "triggerTimes": [
- 169200000,
- 255600000,
- 342000000,
- 428400000,
- 514800000,
- 601200000,
- 687600000,
- 774000000,
- 860400000,
- 946800000
- ]
- },
- {
- "schedule": "09 11 * * *",
- "triggerTimes": [
- 40140000,
- 126540000,
- 212940000,
- 299340000,
- 385740000,
- 472140000,
- 558540000,
- 644940000,
- 731340000,
- 817740000
- ]
- },
- {
- "schedule": "0 14 * * *",
- "triggerTimes": [
- 50400000,
- 136800000,
- 223200000,
- 309600000,
- 396000000,
- 482400000,
- 568800000,
- 655200000,
- 741600000,
- 828000000
- ]
- },
- {
- "schedule": "20 2,12,22 * * *",
- "triggerTimes": [
- 8400000,
- 44400000,
- 80400000,
- 94800000,
- 130800000,
- 166800000,
- 181200000,
- 217200000,
- 253200000,
- 267600000
- ]
- },
- {
- "schedule": "2,6,10,14,18,22,26,30,34,38,42,46,50,54,58 * * * *",
- "triggerTimes": [
- 120000,
- 360000,
- 600000,
- 840000,
- 1080000,
- 1320000,
- 1560000,
- 1800000,
- 2040000,
- 2280000
- ]
- },
- {
- "schedule": "1 16,18,20 * * *",
- "triggerTimes": [
- 57660000,
- 64860000,
- 72060000,
- 144060000,
- 151260000,
- 158460000,
- 230460000,
- 237660000,
- 244860000,
- 316860000
- ]
- },
- {
- "schedule": "30 */6 * * *",
- "triggerTimes": [
- 1800000,
- 23400000,
- 45000000,
- 66600000,
- 88200000,
- 109800000,
- 131400000,
- 153000000,
- 174600000,
- 196200000
- ]
- },
- {
- "schedule": "00 06,15 * * *",
- "triggerTimes": [
- 21600000,
- 54000000,
- 108000000,
- 140400000,
- 194400000,
- 226800000,
- 280800000,
- 313200000,
- 367200000,
- 399600000
- ]
- },
- {
- "schedule": "52 4,10,16,22 * * *",
- "triggerTimes": [
- 17520000,
- 39120000,
- 60720000,
- 82320000,
- 103920000,
- 125520000,
- 147120000,
- 168720000,
- 190320000,
- 211920000
- ]
- },
- {
- "schedule": "37 1 * * *",
- "triggerTimes": [
- 5820000,
- 92220000,
- 178620000,
- 265020000,
- 351420000,
- 437820000,
- 524220000,
- 610620000,
- 697020000,
- 783420000
- ]
- },
- {
- "schedule": "10 10,14 * * *",
- "triggerTimes": [
- 36600000,
- 51000000,
- 123000000,
- 137400000,
- 209400000,
- 223800000,
- 295800000,
- 310200000,
- 382200000,
- 396600000
- ]
- },
- {
- "schedule": "2,7,12,17,22,27,32,37,42,47,52,57 * * * *",
- "triggerTimes": [
- 120000,
- 420000,
- 720000,
- 1020000,
- 1320000,
- 1620000,
- 1920000,
- 2220000,
- 2520000,
- 2820000
- ]
- },
- {
- "schedule": "0 21 * * *",
- "triggerTimes": [
- 75600000,
- 162000000,
- 248400000,
- 334800000,
- 421200000,
- 507600000,
- 594000000,
- 680400000,
- 766800000,
- 853200000
- ]
- },
- {
- "schedule": "25 * * * *",
- "triggerTimes": [
- 1500000,
- 5100000,
- 8700000,
- 12300000,
- 15900000,
- 19500000,
- 23100000,
- 26700000,
- 30300000,
- 33900000
- ]
- },
- {
- "schedule": "0 15 * * *,,",
- "triggerTimes": [
- 54000000,
- 140400000,
- 226800000,
- 313200000,
- 399600000,
- 486000000,
- 572400000,
- 658800000,
- 745200000,
- 831600000
- ]
- },
- {
- "schedule": "13 9,21 * * *",
- "triggerTimes": [
- 33180000,
- 76380000,
- 119580000,
- 162780000,
- 205980000,
- 249180000,
- 292380000,
- 335580000,
- 378780000,
- 421980000
- ]
- },
- {
- "schedule": "10 * * * *",
- "triggerTimes": [
- 600000,
- 4200000,
- 7800000,
- 11400000,
- 15000000,
- 18600000,
- 22200000,
- 25800000,
- 29400000,
- 33000000
- ]
- },
- {
- "schedule": "12 18 * * 1,3,5",
- "triggerTimes": [
- 151920000,
- 411120000,
- 583920000,
- 756720000,
- 1015920000,
- 1188720000,
- 1361520000,
- 1620720000,
- 1793520000,
- 1966320000
- ]
- },
- {
- "schedule": "0 17-19 * * 1",
- "triggerTimes": [
- 406800000,
- 410400000,
- 414000000,
- 1011600000,
- 1015200000,
- 1018800000,
- 1616400000,
- 1620000000,
- 1623600000,
- 2221200000
- ]
- },
- {
- "schedule": "0 10 * * *",
- "triggerTimes": [
- 36000000,
- 122400000,
- 208800000,
- 295200000,
- 381600000,
- 468000000,
- 554400000,
- 640800000,
- 727200000,
- 813600000
- ]
- },
- {
- "schedule": "00 00 * * *",
- "triggerTimes": [
- 86400000,
- 172800000,
- 259200000,
- 345600000,
- 432000000,
- 518400000,
- 604800000,
- 691200000,
- 777600000,
- 864000000
- ]
- },
- {
- "schedule": "25 16,17,18,22 * * *",
- "triggerTimes": [
- 59100000,
- 62700000,
- 66300000,
- 80700000,
- 145500000,
- 149100000,
- 152700000,
- 167100000,
- 231900000,
- 235500000
- ]
- },
- {
- "schedule": "23 6,18 * * *",
- "triggerTimes": [
- 22980000,
- 66180000,
- 109380000,
- 152580000,
- 195780000,
- 238980000,
- 282180000,
- 325380000,
- 368580000,
- 411780000
- ]
- },
- {
- "schedule": "17 1,9,17 * * 0",
- "triggerTimes": [
- 263820000,
- 292620000,
- 321420000,
- 868620000,
- 897420000,
- 926220000,
- 1473420000,
- 1502220000,
- 1531020000,
- 2078220000
- ]
- },
- {
- "schedule": "00 16 * * *",
- "triggerTimes": [
- 57600000,
- 144000000,
- 230400000,
- 316800000,
- 403200000,
- 489600000,
- 576000000,
- 662400000,
- 748800000,
- 835200000
- ]
- },
- {
- "schedule": "*/3 * * * *",
- "triggerTimes": [
- 180000,
- 360000,
- 540000,
- 720000,
- 900000,
- 1080000,
- 1260000,
- 1440000,
- 1620000,
- 1800000
- ]
- },
- {
- "schedule": "19 * * * *",
- "triggerTimes": [
- 1140000,
- 4740000,
- 8340000,
- 11940000,
- 15540000,
- 19140000,
- 22740000,
- 26340000,
- 29940000,
- 33540000
- ]
- },
- {
- "schedule": "15 * * * *",
- "triggerTimes": [
- 900000,
- 4500000,
- 8100000,
- 11700000,
- 15300000,
- 18900000,
- 22500000,
- 26100000,
- 29700000,
- 33300000
- ]
- },
- {
- "schedule": "*/15 * * * *",
- "triggerTimes": [
- 900000,
- 1800000,
- 2700000,
- 3600000,
- 4500000,
- 5400000,
- 6300000,
- 7200000,
- 8100000,
- 9000000
- ]
- },
- {
- "schedule": "0 22 * * 1",
- "triggerTimes": [
- 424800000,
- 1029600000,
- 1634400000,
- 2239200000,
- 2844000000,
- 3448800000,
- 4053600000,
- 4658400000,
- 5263200000,
- 5868000000
- ]
- },
- {
- "schedule": "15 * * * *",
- "triggerTimes": [
- 900000,
- 4500000,
- 8100000,
- 11700000,
- 15300000,
- 18900000,
- 22500000,
- 26100000,
- 29700000,
- 33300000
- ]
- },
- {
- "schedule": "20 04 * * *",
- "triggerTimes": [
- 15600000,
- 102000000,
- 188400000,
- 274800000,
- 361200000,
- 447600000,
- 534000000,
- 620400000,
- 706800000,
- 793200000
- ]
- },
- {
- "schedule": "30 0,12 * * *",
- "triggerTimes": [
- 1800000,
- 45000000,
- 88200000,
- 131400000,
- 174600000,
- 217800000,
- 261000000,
- 304200000,
- 347400000,
- 390600000
- ]
- },
- {
- "schedule": "15 */4 * * *",
- "triggerTimes": [
- 900000,
- 15300000,
- 29700000,
- 44100000,
- 58500000,
- 72900000,
- 87300000,
- 101700000,
- 116100000,
- 130500000
- ]
- },
- {
- "schedule": "29 16,17,18,22 * * *",
- "triggerTimes": [
- 59340000,
- 62940000,
- 66540000,
- 80940000,
- 145740000,
- 149340000,
- 152940000,
- 167340000,
- 232140000,
- 235740000
- ]
- },
- {
- "schedule": "37 */3 * * *",
- "triggerTimes": [
- 2220000,
- 13020000,
- 23820000,
- 34620000,
- 45420000,
- 56220000,
- 67020000,
- 77820000,
- 88620000,
- 99420000
- ]
- },
- {
- "schedule": "*/15 * * * *",
- "triggerTimes": [
- 900000,
- 1800000,
- 2700000,
- 3600000,
- 4500000,
- 5400000,
- 6300000,
- 7200000,
- 8100000,
- 9000000
- ]
- },
- {
- "schedule": "35 23 * * *",
- "triggerTimes": [
- 84900000,
- 171300000,
- 257700000,
- 344100000,
- 430500000,
- 516900000,
- 603300000,
- 689700000,
- 776100000,
- 862500000
- ]
- },
- {
- "schedule": "0 17 * * *",
- "triggerTimes": [
- 61200000,
- 147600000,
- 234000000,
- 320400000,
- 406800000,
- 493200000,
- 579600000,
- 666000000,
- 752400000,
- 838800000
- ]
- },
- {
- "schedule": "0 22 * * *",
- "triggerTimes": [
- 79200000,
- 165600000,
- 252000000,
- 338400000,
- 424800000,
- 511200000,
- 597600000,
- 684000000,
- 770400000,
- 856800000
- ]
- },
- {
- "schedule": "0 11 * * *",
- "triggerTimes": [
- 39600000,
- 126000000,
- 212400000,
- 298800000,
- 385200000,
- 471600000,
- 558000000,
- 644400000,
- 730800000,
- 817200000
- ]
- },
- {
- "schedule": "30 * * * *",
- "triggerTimes": [
- 1800000,
- 5400000,
- 9000000,
- 12600000,
- 16200000,
- 19800000,
- 23400000,
- 27000000,
- 30600000,
- 34200000
- ]
- },
- {
- "schedule": "41 * * * *",
- "triggerTimes": [
- 2460000,
- 6060000,
- 9660000,
- 13260000,
- 16860000,
- 20460000,
- 24060000,
- 27660000,
- 31260000,
- 34860000
- ]
- },
- {
- "schedule": "45 23 * * *",
- "triggerTimes": [
- 85500000,
- 171900000,
- 258300000,
- 344700000,
- 431100000,
- 517500000,
- 603900000,
- 690300000,
- 776700000,
- 863100000
- ]
- },
- {
- "schedule": "*/2 * * * *",
- "triggerTimes": [
- 120000,
- 240000,
- 360000,
- 480000,
- 600000,
- 720000,
- 840000,
- 960000,
- 1080000,
- 1200000
- ]
- },
- {
- "schedule": "0 0,3,6,9,12,15,18,21 * * *",
- "triggerTimes": [
- 10800000,
- 21600000,
- 32400000,
- 43200000,
- 54000000,
- 64800000,
- 75600000,
- 86400000,
- 97200000,
- 108000000
- ]
- },
- {
- "schedule": "0,30 * * * *",
- "triggerTimes": [
- 1800000,
- 3600000,
- 5400000,
- 7200000,
- 9000000,
- 10800000,
- 12600000,
- 14400000,
- 16200000,
- 18000000
- ]
- },
- {
- "schedule": "17 * * * *",
- "triggerTimes": [
- 1020000,
- 4620000,
- 8220000,
- 11820000,
- 15420000,
- 19020000,
- 22620000,
- 26220000,
- 29820000,
- 33420000
- ]
- },
- {
- "schedule": "30,45 18 * * 1",
- "triggerTimes": [
- 412200000,
- 413100000,
- 1017000000,
- 1017900000,
- 1621800000,
- 1622700000,
- 2226600000,
- 2227500000,
- 2831400000,
- 2832300000
- ]
- },
- {
- "schedule": "13,43 * * * *",
- "triggerTimes": [
- 780000,
- 2580000,
- 4380000,
- 6180000,
- 7980000,
- 9780000,
- 11580000,
- 13380000,
- 15180000,
- 16980000
- ]
- },
- {
- "schedule": "0 0 10 * *",
- "triggerTimes": [
- 777600000,
- 3456000000,
- 5875200000,
- 8553600000,
- 11145600000,
- 13824000000,
- 16416000000,
- 19094400000,
- 21772800000,
- 24364800000
- ]
- },
- {
- "schedule": "13,28,43,58 * * * *",
- "triggerTimes": [
- 780000,
- 1680000,
- 2580000,
- 3480000,
- 4380000,
- 5280000,
- 6180000,
- 7080000,
- 7980000,
- 8880000
- ]
- },
- {
- "schedule": "17 9,13,22 * * *",
- "triggerTimes": [
- 33420000,
- 47820000,
- 80220000,
- 119820000,
- 134220000,
- 166620000,
- 206220000,
- 220620000,
- 253020000,
- 292620000
- ]
- },
- {
- "schedule": "10 8,12 * * *",
- "triggerTimes": [
- 29400000,
- 43800000,
- 115800000,
- 130200000,
- 202200000,
- 216600000,
- 288600000,
- 303000000,
- 375000000,
- 389400000
- ]
- },
- {
- "schedule": "*/5 * * * *",
- "triggerTimes": [
- 300000,
- 600000,
- 900000,
- 1200000,
- 1500000,
- 1800000,
- 2100000,
- 2400000,
- 2700000,
- 3000000
- ]
- },
- {
- "schedule": "5,20,35,50 * * * *",
- "triggerTimes": [
- 300000,
- 1200000,
- 2100000,
- 3000000,
- 3900000,
- 4800000,
- 5700000,
- 6600000,
- 7500000,
- 8400000
- ]
- },
- {
- "schedule": "00 */2 * * *",
- "triggerTimes": [
- 7200000,
- 14400000,
- 21600000,
- 28800000,
- 36000000,
- 43200000,
- 50400000,
- 57600000,
- 64800000,
- 72000000
- ]
- },
- {
- "schedule": "23 * * * *",
- "triggerTimes": [
- 1380000,
- 4980000,
- 8580000,
- 12180000,
- 15780000,
- 19380000,
- 22980000,
- 26580000,
- 30180000,
- 33780000
- ]
- },
- {
- "schedule": "7 12 * * *",
- "triggerTimes": [
- 43620000,
- 130020000,
- 216420000,
- 302820000,
- 389220000,
- 475620000,
- 562020000,
- 648420000,
- 734820000,
- 821220000
- ]
- },
- {
- "schedule": "*/1 * * * *",
- "triggerTimes": [
- 60000,
- 120000,
- 180000,
- 240000,
- 300000,
- 360000,
- 420000,
- 480000,
- 540000,
- 600000
- ]
- },
- {
- "schedule": "0,10,20,30,40,50 * * * *",
- "triggerTimes": [
- 600000,
- 1200000,
- 1800000,
- 2400000,
- 3000000,
- 3600000,
- 4200000,
- 4800000,
- 5400000,
- 6000000
- ]
- },
- {
- "schedule": "45 02,06,10,14,18,22 * * *",
- "triggerTimes": [
- 9900000,
- 24300000,
- 38700000,
- 53100000,
- 67500000,
- 81900000,
- 96300000,
- 110700000,
- 125100000,
- 139500000
- ]
- },
- {
- "schedule": "39 1 * * *",
- "triggerTimes": [
- 5940000,
- 92340000,
- 178740000,
- 265140000,
- 351540000,
- 437940000,
- 524340000,
- 610740000,
- 697140000,
- 783540000
- ]
- },
- {
- "schedule": "0 0-2 * * 2-6",
- "triggerTimes": [
- 3600000,
- 7200000,
- 86400000,
- 90000000,
- 93600000,
- 172800000,
- 176400000,
- 180000000,
- 432000000,
- 435600000
- ]
- },
- {
- "schedule": "35,50 * * * *",
- "triggerTimes": [
- 2100000,
- 3000000,
- 5700000,
- 6600000,
- 9300000,
- 10200000,
- 12900000,
- 13800000,
- 16500000,
- 17400000
- ]
- },
- {
- "schedule": "0 3 1 * *",
- "triggerTimes": [
- 10800000,
- 2689200000,
- 5108400000,
- 7786800000,
- 10378800000,
- 13057200000,
- 15649200000,
- 18327600000,
- 21006000000,
- 23598000000
- ]
- },
- {
- "schedule": "5 5 * * *",
- "triggerTimes": [
- 18300000,
- 104700000,
- 191100000,
- 277500000,
- 363900000,
- 450300000,
- 536700000,
- 623100000,
- 709500000,
- 795900000
- ]
- },
- {
- "schedule": "18 8 * * *",
- "triggerTimes": [
- 29880000,
- 116280000,
- 202680000,
- 289080000,
- 375480000,
- 461880000,
- 548280000,
- 634680000,
- 721080000,
- 807480000
- ]
- },
- {
- "schedule": "0 9 * * *",
- "triggerTimes": [
- 32400000,
- 118800000,
- 205200000,
- 291600000,
- 378000000,
- 464400000,
- 550800000,
- 637200000,
- 723600000,
- 810000000
- ]
- },
- {
- "schedule": "*/1 * * * *",
- "triggerTimes": [
- 60000,
- 120000,
- 180000,
- 240000,
- 300000,
- 360000,
- 420000,
- 480000,
- 540000,
- 600000
- ]
- },
- {
- "schedule": "50 8,12,21 * * *",
- "triggerTimes": [
- 31800000,
- 46200000,
- 78600000,
- 118200000,
- 132600000,
- 165000000,
- 204600000,
- 219000000,
- 251400000,
- 291000000
- ]
- },
- {
- "schedule": "29 9,21 * * *",
- "triggerTimes": [
- 34140000,
- 77340000,
- 120540000,
- 163740000,
- 206940000,
- 250140000,
- 293340000,
- 336540000,
- 379740000,
- 422940000
- ]
- },
- {
- "schedule": "40 * * * *",
- "triggerTimes": [
- 2400000,
- 6000000,
- 9600000,
- 13200000,
- 16800000,
- 20400000,
- 24000000,
- 27600000,
- 31200000,
- 34800000
- ]
- },
- {
- "schedule": "8 21 * * *",
- "triggerTimes": [
- 76080000,
- 162480000,
- 248880000,
- 335280000,
- 421680000,
- 508080000,
- 594480000,
- 680880000,
- 767280000,
- 853680000
- ]
- },
- {
- "schedule": "0 6 * * *",
- "triggerTimes": [
- 21600000,
- 108000000,
- 194400000,
- 280800000,
- 367200000,
- 453600000,
- 540000000,
- 626400000,
- 712800000,
- 799200000
- ]
- },
- {
- "schedule": "30 0-23/2 * * *",
- "triggerTimes": [
- 1800000,
- 9000000,
- 16200000,
- 23400000,
- 30600000,
- 37800000,
- 45000000,
- 52200000,
- 59400000,
- 66600000
- ]
- },
- {
- "schedule": "0 14,22 * * *",
- "triggerTimes": [
- 50400000,
- 79200000,
- 136800000,
- 165600000,
- 223200000,
- 252000000,
- 309600000,
- 338400000,
- 396000000,
- 424800000
- ]
- },
- {
- "schedule": "0 */1 * * *",
- "triggerTimes": [
- 3600000,
- 7200000,
- 10800000,
- 14400000,
- 18000000,
- 21600000,
- 25200000,
- 28800000,
- 32400000,
- 36000000
- ]
- },
- {
- "schedule": "0 1 * * 1",
- "triggerTimes": [
- 349200000,
- 954000000,
- 1558800000,
- 2163600000,
- 2768400000,
- 3373200000,
- 3978000000,
- 4582800000,
- 5187600000,
- 5792400000
- ]
- },
- {
- "schedule": "0 8 * * *",
- "triggerTimes": [
- 28800000,
- 115200000,
- 201600000,
- 288000000,
- 374400000,
- 460800000,
- 547200000,
- 633600000,
- 720000000,
- 806400000
- ]
- },
- {
- "schedule": "01 17 * * *",
- "triggerTimes": [
- 61260000,
- 147660000,
- 234060000,
- 320460000,
- 406860000,
- 493260000,
- 579660000,
- 666060000,
- 752460000,
- 838860000
- ]
- },
- {
- "schedule": "13 * * * *",
- "triggerTimes": [
- 780000,
- 4380000,
- 7980000,
- 11580000,
- 15180000,
- 18780000,
- 22380000,
- 25980000,
- 29580000,
- 33180000
- ]
- }
-]
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java b/src/test/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
new file mode 100644
index 0000000..0ff8d12
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/cron/CrontabEntryTest.java
@@ -0,0 +1,168 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron;
+
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class CrontabEntryTest {
+ @Test
+ public void testHashCodeAndEquals() {
+ List<CrontabEntry> entries = ImmutableList.of(
+ CrontabEntry.parse("* * * * *"),
+ CrontabEntry.parse("0-59 * * * *"),
+ CrontabEntry.parse("0-57,58,59 * * * *"),
+ CrontabEntry.parse("* 23,1,2,4,0-22 * * *"),
+ CrontabEntry.parse("1-50,0,51-59 * * * sun-sat"));
+
+ for (CrontabEntry lhs : entries) {
+ for (CrontabEntry rhs : entries) {
+ assertEquals(lhs, rhs);
+ }
+ }
+
+ Set<CrontabEntry> equivalentEntries = Sets.newHashSet(entries);
+ assertTrue(equivalentEntries.size() == 1);
+ }
+
+ @Test
+ public void testEqualsCoverage() {
+ assertNotEquals(CrontabEntry.parse("* * * * *"), new Object());
+
+ assertNotEquals(CrontabEntry.parse("* * * * *"), CrontabEntry.parse("1 * * * *"));
+ assertEquals(CrontabEntry.parse("1,2,3 * * * *"), CrontabEntry.parse("1-3 * * * *"));
+
+ assertNotEquals(CrontabEntry.parse("* 0-22 * * *"), CrontabEntry.parse("* * * * *"));
+ assertEquals(CrontabEntry.parse("* 0-23 * * *"), CrontabEntry.parse("* * * * *"));
+
+ assertNotEquals(CrontabEntry.parse("1 1 1-30 * *"), CrontabEntry.parse("1 1 * * *"));
+ assertEquals(CrontabEntry.parse("1 1 1-31 * *"), CrontabEntry.parse("1 1 * * *"));
+
+ assertNotEquals(CrontabEntry.parse("1 1 * JAN,FEB-NOV *"), CrontabEntry.parse("1 1 * * *"));
+ assertEquals(CrontabEntry.parse("1 1 * JAN,FEB-DEC *"), CrontabEntry.parse("1 1 * * *"));
+
+ assertNotEquals(CrontabEntry.parse("* * * * SUN"), CrontabEntry.parse("* * * * SAT"));
+ assertEquals(CrontabEntry.parse("* * * * 0"), CrontabEntry.parse("* * * * SUN"));
+ }
+
+ @Test
+ public void testSkip() {
+ assertEquals(CrontabEntry.parse("*/15 * * * *"), CrontabEntry.parse("0,15,30,45 * * * *"));
+ assertEquals(
+ CrontabEntry.parse("* */2 * * *"),
+ CrontabEntry.parse("0-59 0,2,4,6,8,10,12-23/2 * * *"));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("0-58 * * * *", CrontabEntry.parse("0,1-57,58 * * * *").toString());
+ assertEquals("* * * * *", CrontabEntry.parse("* * * * *").toString());
+ }
+
+ @Test
+ public void testWildcards() {
+ CrontabEntry wildcardMinuteEntry = CrontabEntry.parse("* 1 1 1 *");
+ assertEquals("*", wildcardMinuteEntry.getMinuteAsString());
+ assertTrue(wildcardMinuteEntry.hasWildcardMinute());
+ assertFalse(wildcardMinuteEntry.hasWildcardHour());
+ assertFalse(wildcardMinuteEntry.hasWildcardDayOfMonth());
+ assertFalse(wildcardMinuteEntry.hasWildcardMonth());
+ assertTrue(wildcardMinuteEntry.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardHourEntry = CrontabEntry.parse("1 * 1 1 *");
+ assertEquals("*", wildcardHourEntry.getHourAsString());
+ assertFalse(wildcardHourEntry.hasWildcardMinute());
+ assertTrue(wildcardHourEntry.hasWildcardHour());
+ assertFalse(wildcardHourEntry.hasWildcardDayOfMonth());
+ assertFalse(wildcardHourEntry.hasWildcardMonth());
+ assertTrue(wildcardHourEntry.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardDayOfMonth = CrontabEntry.parse("1 1 * 1 *");
+ assertEquals("*", wildcardDayOfMonth.getDayOfMonthAsString());
+ assertFalse(wildcardDayOfMonth.hasWildcardMinute());
+ assertFalse(wildcardDayOfMonth.hasWildcardHour());
+ assertTrue(wildcardDayOfMonth.hasWildcardDayOfMonth());
+ assertFalse(wildcardDayOfMonth.hasWildcardMonth());
+ assertTrue(wildcardDayOfMonth.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardMonth = CrontabEntry.parse("1 1 1 * *");
+ assertEquals("*", wildcardMonth.getMonthAsString());
+ assertFalse(wildcardMonth.hasWildcardMinute());
+ assertFalse(wildcardMonth.hasWildcardHour());
+ assertFalse(wildcardMonth.hasWildcardDayOfMonth());
+ assertTrue(wildcardMonth.hasWildcardMonth());
+ assertTrue(wildcardMonth.hasWildcardDayOfWeek());
+
+ CrontabEntry wildcardDayOfWeek = CrontabEntry.parse("1 1 1 1 *");
+ assertEquals("*", wildcardDayOfWeek.getDayOfWeekAsString());
+ assertFalse(wildcardDayOfWeek.hasWildcardMinute());
+ assertFalse(wildcardDayOfWeek.hasWildcardHour());
+ assertFalse(wildcardDayOfWeek.hasWildcardDayOfMonth());
+ assertFalse(wildcardDayOfWeek.hasWildcardMonth());
+ assertTrue(wildcardDayOfWeek.hasWildcardDayOfWeek());
+ }
+
+ @Test
+ public void testEqualsIsCanonical() {
+ String rawEntry = "* * */3 * *";
+ CrontabEntry input = CrontabEntry.parse(rawEntry);
+ assertNotEquals(
+ rawEntry + " is not the canonical form of " + input,
+ rawEntry,
+ input.toString());
+ assertEquals(
+ "The form returned by toString is canonical",
+ input.toString(),
+ CrontabEntry.parse(input.toString()).toString());
+ }
+
+ @Test
+ public void testBadEntries() {
+ List<String> badPatterns = ImmutableList.of(
+ "* * * * MON-SUN",
+ "* * **",
+ "0-59 0-59 * * *",
+ "1/1 * * * *",
+ "5 5 * MAR-JAN *",
+ "*/0 * * * *",
+ "0-59/0 * * * *",
+ "0-59/60 * * * *",
+ "* * 1 * 1"
+ );
+
+ for (String pattern : badPatterns) {
+ assertNull(CrontabEntry.tryParse(pattern).orNull());
+ }
+ }
+
+ @Test
+ public void testExpectedTriggerPredictionsParse() {
+ for (ExpectedPrediction prediction : ExpectedPrediction.getAll()) {
+ prediction.parseCrontabEntry();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/ExpectedPrediction.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/ExpectedPrediction.java b/src/test/java/org/apache/aurora/scheduler/cron/ExpectedPrediction.java
new file mode 100644
index 0000000..d4caf4e
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/cron/ExpectedPrediction.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright 2014 Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aurora.scheduler.cron;
+
+import java.io.InputStreamReader;
+import java.util.List;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * A schedule and the expected iteratively-applied prediction results.
+ */
+public final class ExpectedPrediction {
+ private String schedule;
+ private List<Long> triggerTimes;
+
+ ExpectedPrediction() {
+ // GSON constructor.
+ }
+
+ public static List<ExpectedPrediction> getAll() {
+ return new Gson()
+ .fromJson(
+ new InputStreamReader(
+ ExpectedPrediction.class.getResourceAsStream("expected-predictions.json"),
+ Charsets.UTF_8),
+ new TypeToken<List<ExpectedPrediction>>() { }.getType());
+ }
+
+ public String getSchedule() {
+ return schedule;
+ }
+
+ public List<Long> getTriggerTimes() {
+ return ImmutableList.copyOf(triggerTimes);
+ }
+
+ public CrontabEntry parseCrontabEntry() {
+ return CrontabEntry.parse(getSchedule());
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/3361dbed/src/test/java/org/apache/aurora/scheduler/cron/noop/NoopCronIT.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/cron/noop/NoopCronIT.java b/src/test/java/org/apache/aurora/scheduler/cron/noop/NoopCronIT.java
deleted file mode 100644
index 0d2f66f..0000000
--- a/src/test/java/org/apache/aurora/scheduler/cron/noop/NoopCronIT.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * Copyright 2013 Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.aurora.scheduler.cron.noop;
-
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-
-import org.apache.aurora.scheduler.cron.CronScheduler;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-public class NoopCronIT {
- private static final String SCHEDULE = "* * * * *";
-
- private CronScheduler cronScheduler;
-
- @Before
- public void setUp() {
- Injector injector = Guice.createInjector(new NoopCronModule());
- cronScheduler = injector.getInstance(CronScheduler.class);
- }
-
- @Test
- public void testLifecycle() throws Exception {
- cronScheduler.startAsync().awaitRunning();
- cronScheduler.stopAsync().awaitTerminated();
- }
-
- @Test
- public void testSchedule() throws Exception {
- cronScheduler.schedule(SCHEDULE, new Runnable() {
- @Override
- public void run() {
- // No-op.
- }
- });
-
- assertEquals(SCHEDULE, cronScheduler.getSchedule(SCHEDULE).orNull());
-
- cronScheduler.deschedule(SCHEDULE);
-
- assertNull(cronScheduler.getSchedule(SCHEDULE).orNull());
- }
-}