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 <[email protected]>
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;
+ }
+
};