This is an automated email from the ASF dual-hosted git repository. style95 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openwhisk-package-alarms.git
The following commit(s) were added to refs/heads/master by this push: new 238d6f9 Make cron triggers fire with random delay with parameter `strict` (#198) 238d6f9 is described below commit 238d6f95e69f76b76a949e527ee86652d462df00 Author: Jeongmin Yu <machen...@gmail.com> AuthorDate: Tue Mar 3 22:58:44 2020 +0900 Make cron triggers fire with random delay with parameter `strict` (#198) * Make cron triggers fire with random delay with parameter `strict` * Add comment for behavior of hashName Its implementation referred String.hashCode in Java * Fix redundant code * Add description of parameter to README * Add limit delay configuration * Fix wrong conversion bug * Add log for cron conversion * Fix missing semicolon --- README.md | 12 +++++++++++- action/alarmWebAction.js | 1 + installCatalog.sh | 2 +- provider/lib/cronAlarm.js | 40 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5a375b1..7c3fcc6 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ The following is an example of creating a trigger that will be fired once on Dec The `/whisk.system/alarms/alarm` feed configures the Alarm service to fire a Trigger event at a specified frequency. The parameters are as follows: - `cron` (*required*): A string, based on the UNIX crontab syntax that indicates when to fire the Trigger in Coordinated Universal Time (UTC). The string is a sequence of five fields that are separated by spaces: `X X X X X`. -For more information, see: http://crontab.org. The following strings are examples that use varying duration's of frequency. + For more information, see: http://crontab.org. The following strings are examples that use varying duration's of frequency. - `* * * * *`: The Trigger fires at the top of every minute. - `0 * * * *`: The Trigger fires at the top of every hour. @@ -101,6 +101,7 @@ For more information, see: http://crontab.org. The following strings are example **Note**: The parameter `cron` supports five or six fields. Not all OpenWhisk vendors may support 6 fields so please check their documentation for support. For more details about using this custom cron syntax, see: https://github.com/ncb000gt/node-cron. Here is an example using six fields notation: + - `*/30 * * * * *`: every thirty seconds. - `trigger_payload` (*optional*): The value of this parameter becomes the content of the Trigger every time the Trigger is fired. @@ -127,6 +128,15 @@ January 1, 2019, 00:00:00 UTC and will stop firing January 31, 2019, 23:59:00 UT **Note**: The parameter `maxTriggers` is deprecated and will be removed soon. To stop the Trigger, use the `stopDate` parameter. +- `strict` (*optional*): A boolean value that decides to add a few seconds to the Trigger. This work with only five-field cron alarms. + + - If it's true, the Trigger will fire at the top of the hour/minute (\**:**:00). + - Otherwise, the Trigger will fire after the specific seconds (\**:**:00-59). + +The delay is determined by the hash value of the Trigger's name, so it keeps the same interval before and after the (re)deployment. + +**Note** This option can be helpful to avoid thundering herds when the second-unit errors are not critical. + # Building from Source To build this package from source, execute the command `./gradlew distDocker` diff --git a/action/alarmWebAction.js b/action/alarmWebAction.js index 6267549..97826e1 100644 --- a/action/alarmWebAction.js +++ b/action/alarmWebAction.js @@ -103,6 +103,7 @@ function main(params) { } newTrigger.cron = params.cron; newTrigger.timezone = params.timezone; + newTrigger.strict = params.strict === 'true'; } catch(ex) { var message = ex.message !== 'Invalid timezone.' ? `cron pattern '${params.cron}' is not valid` : ex.message; return common.sendError(400, message); diff --git a/installCatalog.sh b/installCatalog.sh index 117793e..fb51232 100755 --- a/installCatalog.sh +++ b/installCatalog.sh @@ -84,7 +84,7 @@ zip -r alarmFeed.zip lib package.json alarm.js -q $WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" alarms/alarm "$PACKAGE_HOME/action/alarmFeed.zip" \ -a description 'Fire trigger when alarm occurs' \ - -a parameters '[ {"name":"cron", "required":true}, {"name":"timezone", "required":false}, {"name":"startDate", "required":false}, {"name":"stopDate", "required":false} ]' \ + -a parameters '[ {"name":"cron", "required":true}, {"name":"timezone", "required":false}, {"name":"startDate", "required":false}, {"name":"stopDate", "required":false}, {"name":"strict", "required":false} ]' \ -a feed true $WSK_CLI -i --apihost "$EDGEHOST" action update --kind "$ACTION_RUNTIME_VERSION" --auth "$AUTH" alarms/once "$PACKAGE_HOME/action/alarmFeed.zip" \ diff --git a/provider/lib/cronAlarm.js b/provider/lib/cronAlarm.js index 1a00b0e..ef26668 100644 --- a/provider/lib/cronAlarm.js +++ b/provider/lib/cronAlarm.js @@ -22,6 +22,7 @@ var constants = require('./constants.js'); module.exports = function(logger, newTrigger) { var maxTriggers = newTrigger.maxTriggers || constants.DEFAULT_MAX_TRIGGERS; + var delayLimit = validateLimit(parseInt(process.env.ALARM_DELAY_LIMIT)) || 0; var cachedTrigger = { apikey: newTrigger.apikey, @@ -42,7 +43,7 @@ module.exports = function(logger, newTrigger) { try { return new Promise(function(resolve, reject) { - var cronHandle = new CronJob(newTrigger.cron, callback, undefined, false, newTrigger.timezone); + var cronHandle = new CronJob(distributeCron(newTrigger), callback, undefined, false, newTrigger.timezone); if (newTrigger.stopDate) { cachedTrigger.stopDate = newTrigger.stopDate; @@ -85,4 +86,41 @@ module.exports = function(logger, newTrigger) { } }; + // Convert string to integer in [0, delayLimit) + function hashName(name) { + var hash = 0; + + for (var i = 0; i < name.length; i++) { + var char = name.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + } + hash %= delayLimit + 1; + hash = Math.abs(hash); + + return hash.toString(10); + } + + function distributeCron(trigger) { + var method = "distributeCronAlarm"; + + var cronFields = (trigger.cron + '').trim().split(/\s+/); + if (trigger.strict !== 'true' && cronFields.length === 5 && delayLimit !== 0) { + var newCron = [hashName(trigger.name), ...cronFields].join(' '); + logger.info(method, trigger.triggerID, 'is converted to', '"' + newCron + '"'); + return newCron; + } + + return trigger.cron; + } + + function validateLimit(limit) { + if (isNaN(limit)) { + return 0; + } + if (limit < 0 || limit >= 60) { + return 0; + } + return limit; + } + };