Apache OpenWhisk supports a cron-based alarm package for invoking serverless functions on a fixed schedule, e.g. every 5 minutes, every day at 5PM, once a week.
Scheduled events allow functions to be invoked for background processes or batch operations, like processing logs generated in the past 24 hours.
Using a cron-based schedule pattern, running functions once a minute, every two hours or 5pm on Mondays is simple, but what about more complex schedule patterns? π€
What if we need toβ¦
- β° Fire a single one-off event at a specific time in the future?
- β° Fire events a fixed period of time from an action finishing?
- β° Fire events on an irregular schedule?
It is possible to implement all these examples with a few tricksβ¦ π€ΉββοΈπ€ΉββοΈπ€ΉββοΈ.
Before we dive into the details, let’s review how the alarm feed provider worksβ¦
Alarm Trigger Feeds
OpenWhisk triggers are connected to external event sources using feed providers.
Feed providers listen to event sources, like message queues, firing triggers with event parameters as external events occur.
There are a number of pre-installed feed providers in the whisk.system
namespace. This includes the alarms package which includes a feed provider (/whisk.system/alarms/alarm
).
$ wsk package get /whisk.system/alarms --summary
package /whisk.system/alarms: Alarms and periodic utility
(parameters: *apihost, *cron, *trigger_payload)
feed /whisk.system/alarms/alarm: Fire trigger when alarm occurs
(parameters: none defined)
feed parameters
The following parameters are used to configure the feed provider.
cron
- crontab syntax used to configure timer schedule.trigger_payload
- event parameters to fire trigger with.maxTriggers
- maximum number of triggers to fire (-1 for no limit).
cron
is the parameter which controls when triggers will be fired. It uses the cron syntax to specify the schedule expression.
cron schedule format
Cron schedule values are a string containing sections for the following time fields. Field values can be integers or patterns including wild cards.
# βββββββββββββββ second (0 - 59, optional & defaults to 0)
# β βββββββββββββββ minute (0 - 59)
# β β βββββββββββββββ hour (0 - 23)
# β β β βββββββββββββββ day of month (1 - 31)
# β β β β βββββββββββββββ month (0 - 11)
# β β β β β βββββββββββββββ day of week (0 - 6) (Sunday to Saturday)
# β β β β β β
# β β β β β β
# * * * * * *
NOTE: Month field starts from 0 not 1, 0 is January with December being 11. Day of week also starts from 0. Sunday is first day of the week.
The second field is a non-standard cron field and does not need to be used. The Node.js module used to parse the cron schedules supports a value with five or six fields.
crontab examples
Here are some example patternsβ¦
*/10 * * * * *
- run every 10 seconds* * * * *
- run every minute0 * * * *
- run every hour0 */2 * * *
- run every two hours30 11 * * 1-5
- run Monday to Friday at 11:30AM0 0 1 * *
- run at midnight the first day of the month
https://crontab.guru/ is an online editor for generating cron schedule expressions.
Creating Alarm Triggers
Using the wsk
cli triggers can be created using the alarm
feed. Schedule and event parameters are passed in using command-line arguments (-p name value
).
$ wsk trigger create periodic --feed /whisk.system/alarms/alarm -p cron '* * * * * *' -p trigger_payload '{"hello":"world"}'
ok: invoked /whisk.system/alarms/alarm with id 42ca80fbe7cf47318a80fbe7cff73177
...
ok: created trigger feed periodic
Trigger invocations are recorded in the activation records.
$ wsk activation list periodic
activations
d55d15297781474b9d15297781974b92 periodic
...
$ wsk activation get d55d15297781474b9d15297781974b92
ok: got activation d55d15297781474b9d15297781974b92
{
"namespace": "user@host.com_dev",
"name": "periodic",
...
}
Deleting the trigger will automatically remove the trigger from the alarm scheduler.
$ wsk delete periodic
ok: invoked /whisk.system/alarms/alarm with id 44e8fc5e76c64175a8fc5e76c6c175dd
...
ok: deleted trigger periodic
Programmatic Creation
The OpenWhisk JavaScript library can also register and remove triggers with feed providers.
const params = {cron: '* * * * * *', trigger_payload: {"hello":"world"}}
const name = '/whisk.system/alarms/alarm'
const trigger = 'periodic'
ow.feeds.create({name, trigger, params}).then(package => {
console.log('alarm trigger feed created', package)
}).catch(err => {
console.error('failed to create alarm trigger', err)
})
Triggers must already exist before registering with the feed provider using the client library.
Using the client library provides a mechanism for actions to dynamically set up scheduled events.
Advanced Examples
Having reviewed how the alarm feed works, let’s look at some more advanced use-cases for the schedulerβ¦
Schedule one-off event at a specific time in the future
Creating one-off events, that fire at a specific date and time, is possible using the cron
and maxTriggers
parameters together.
Using the minute, hour, day of the month and month fields in the cron parameter, the schedule can be configured to run once a year. The day of the week field will use the wildcard value.
Setting the maxTriggers
parameter to 1, the trigger is removed from the scheduler after firing.
happy new year example
What if we want to fire an event when the New Year starts?
Here’s the cron schedule for 00:00 on January 1st.
# ββββββββββββββ minute (0 - 59)
# β ββββββββββββββ hour (0 - 23)
# β β ββββββββββββββ day of month (1 - 31)
# β β β ββββββββββββββ month (0 - 11)
# β β β β ββββββββββββββ day of week (0 - 6) (Sunday to Saturday)
# β β β β β
# β β β β β
# 0 0 1 0 *
Here are the cli commands to set up a trigger to run at 01/01/2018 @ 00:00 to celebrate the new year.
$ wsk trigger create new_year --feed /whisk.system/alarms/alarm -p cron '0 0 1 0 *' -p maxTriggers 1 -p trigger_payload '{"message":"Happy New Year!"}'
ok: invoked /whisk.system/alarms/alarm with id 754bec0a58b944a68bec0a58b9f4a6c1
...
ok: created trigger new_year
Firing events a fixed period of time from an action finishing
Imagine you want to run an action on a loop, with a 60 second delay between invocations. Start times for future invocations are dependent on the finishing time of previous invocations. This means we can’t use the alarm feed with a fixed schedule like ‘* * * * *
’.
Instead we’ll schedule the first invocation as a one-off event and then have the action re-schedule itself using the JavaScript client library!
action code
Here’s the sample JavaScript code for an action which does thatβ¦.
const openwhisk = require('openwhisk');
function calculateSchedule() {
const now = new Date()
const seconds = now.getSeconds()
const nextMinute = (now.getMinutes() + 1) % 60
return `${seconds} ${nextMinute} * * * *`
}
function main(params) {
const ow = openwhisk();
const params = {cron: calculateSchedule(), maxTriggers: 1}
console.log(params)
return ow.feeds.delete({name: '/whisk.system/alarms/alarm', trigger: 'delay'}).then(() => {
console.log('delay trigger feed deleted.')
return ow.feeds.create({name: '/whisk.system/alarms/alarm', trigger: 'delay', params: params})
}).then(result => {
console.log('delay trigger feed created.')
})
.catch(err => {
console.error('failed to create/delete delay trigger', err)
console.log("ERROR", err.error.response.result)
})
}
setting up
- Create an action called
reschedule
with code from above.
$ wsk action create reschedule reschedule.js
ok: created action reschedule
- Create a trigger (
delay
) using the alarm feed, set to run in the next 60 seconds.
$ wsk trigger create delay --feed /whisk.system/alarms/alarm -p cron '* * * * * *'
ok: invoked /whisk.system/alarms/alarm with id b3da4de5726b41679a4de5726b0167c8
...
ok: created trigger delay
- Connect the action (
reschedule
) to the trigger (delay
) with a rule (reschedule_delay
).
$ wsk rule create reschedule_delay delay reschedule
ok: created rule reschedule_delay
This action will continue to re-schedule itself indefinitely.
Stop this infinite loop by disabling or removing the rule connecting the action to the trigger.
$ wsk rule disable reschedule_delay
ok: disabled rule reschedule_delay
Firing events on an irregular schedule
How can you schedule events to occur from a predictable but irregular pattern, e.g. sending a daily message to users at sunrise?
Sunrise happens at a different time each morning. This schedule cannot be defined using a static cron-based pattern.
Using the same approach as above, where actions re-schedule triggers at runtime, events can created to follow an irregular schedule.
sunrise times
This external API provides the sunrise times for a location. Retrieving the sunrise times for tomorrow during execution will provide the date time used to re-schedule the action.
{
"results": {
"astronomical_twilight_begin": "5:13:40 AM",
"astronomical_twilight_end": "6:48:52 PM",
"civil_twilight_begin": "6:14:23 AM",
"civil_twilight_end": "5:48:09 PM",
"day_length": "10:40:26",
"nautical_twilight_begin": "5:43:50 AM",
"nautical_twilight_end": "6:18:42 PM",
"solar_noon": "12:01:16 PM",
"sunrise": "6:41:03 AM",
"sunset": "5:21:29 PM"
},
"status": "OK"
}
action code
Here’s the sample JavaScript action that will re-schedule itself at sunrise.
const openwhisk = require('openwhisk');
const request = require('request-promise');
function getNextSunrise(lat, lng, when) {
const options = {
uri: 'https://api.sunrise-sunset.org/json',
qs: { lat: lat, lng: lng, when: when },
json: true
}
return request(options)
.then(result => result.results.sunrise)
}
function calculateSchedule(sunrise) {
console.log('Next sunrise:', sunrise)
const sections = sunrise.split(':')
const hour = sections[0], minute = sections[1]
return `${minute} ${hour} * * *`
}
function scheduleSunriseEvent (sunrise) {
const ow = openwhisk();
const params = {cron: sunrise, maxTriggers: 1}
return ow.feeds.delete({name: '/whisk.system/alarms/alarm', trigger: 'sunrise'}).then(() => {
console.log('trigger feed deleted.')
return ow.feeds.create({name: '/whisk.system/alarms/alarm', trigger: 'sunrise', params: params})
}).then(result => {
console.log('trigger feed created.')
})
.catch(err => {
console.error('failed to create/delete trigger', err)
console.log("ERROR", err.error.response.result)
})
}
function main(params) {
console.log('GOOD MORNING!')
return getNextSunrise(params.lat, params.lng, 'tomorrow')
.then(calculateSchedule)
.then(scheduleSunriseEvent)
}
setting up
- Create an action called
wake_up
with code from above.lat
andlng
parameters define location for sunrise.
$ wsk action create wake_up wake_up.js -p lat 51.50 -p lng -0.076
ok: created action wake_up
- Create a trigger (
sunrise
) with the alarm feed, scheduled for the next sunrise.
$ wsk trigger create sunrise --feed /whisk.system/alarms/alarm -p cron '03 41 06 * * *'
ok: invoked /whisk.system/alarms/alarm with id 606dafe276f24400adafe276f2240082
...
ok: created trigger sunrise
- Connect the action (
wake_up
) to the trigger (sunrise
) with a rule (wake_up_at_sunrise
).
$ wsk rule create wake_up_at_sunrise sunrise wake_up
ok: created rule wake_up_at_sunrise
Checking the activation logs the following morning will show the trigger being fired, which invokes the action, which re-schedules the one-off event! π π π
Caveats
Here’s a few issues you might encounter using the alarm feed that I ran intoβ¦.
- Month field in cron schedule starts from zero not one. January is 0, December is 11.
- Day of the week field starts from zero. First day of the week is Sunday, not Monday.
- Feeds cannot be updated with a new schedule once created. Feeds must be deleted before being re-created to use a different schedule.
Future Plans
Extending the alarm feed to support even more features and improve the developer experience is in-progress. There are a number of Github issues in the official OpenWhisk repository around this work.
- Enhancements for startDate, stopDate, fire once, interval (#102)
- Add fire once triggers (#89)
- Support read and updating trigger details (#101)
If you have feature requests, discover bugs with the feed or have other suggestions, please comment on the existing issues or open new ones.
Conclusion
Scheduled events are a necessary feature of serverless cloud platforms. Due to the ephemeral nature of runtime environments, scheduling background tasks must be managed by the platform.
In Apache OpenWhisk, the alarm feed allows static events to be generated on a customisable schedule. Using a cron-based schedule pattern, running functions once a minute, every two hours or 5pm on Mondays, is simple but what about more complex schedule patterns?
Using the cron
and maxTriggers
parameters with the OpenWhisk client library, much more advanced event schedules can be utilised within the platform. In the examples above, we looked at how to schedule one-off events, events using a predictable but irregular schedule and how actions can re-schedule events at runtime. π―π―π―