You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by cs...@apache.org on 2017/11/17 19:24:27 UTC

[incubator-openwhisk-package-alarms] branch master updated: add startDate and stopDate support for the alarms feed (#109)

This is an automated email from the ASF dual-hosted git repository.

csantanapr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-package-alarms.git


The following commit(s) were added to refs/heads/master by this push:
     new d31c475  add startDate and stopDate support for the alarms feed (#109)
d31c475 is described below

commit d31c47582262ad3125a28d5905715a57c9a88fe5
Author: Jason Peterson <ja...@us.ibm.com>
AuthorDate: Fri Nov 17 14:24:25 2017 -0500

    add startDate and stopDate support for the alarms feed (#109)
    
    * add startDate and stopDate support for the alarms feed
    
    * documentation for startDate, stopDate and date parameters
    
    * remove unnecessary alarms once zip
    
    * fix no eol travis error
---
 README.md                                          | 49 +++++++++++---
 action/alarm.js                                    |  2 +-
 action/alarmOnce.js                                | 31 ---------
 action/alarmOnce_package.json                      |  5 --
 action/alarmWebAction.js                           | 76 +++++++++++++++++-----
 installCatalog.sh                                  | 20 ++----
 package.json                                       |  3 +-
 provider/app.js                                    |  1 -
 provider/lib/cronAlarm.js                          | 44 +++++++++++--
 provider/lib/dateAlarm.js                          |  2 +-
 provider/lib/utils.js                              | 18 +++--
 .../system/health/AlarmsHealthFeedTests.scala      | 48 +++++++++++++-
 .../scala/system/packages/AlarmsFeedTests.scala    | 66 +++++++++++++++++++
 13 files changed, 269 insertions(+), 96 deletions(-)

diff --git a/README.md b/README.md
index 66d3dee..33f383c 100644
--- a/README.md
+++ b/README.md
@@ -5,12 +5,13 @@
 
 The `/whisk.system/alarms` package can be used to fire a trigger at a specified frequency. This is useful for setting up recurring jobs or tasks, such as invoking a system backup action every hour.
 
-The package includes the following feed.
+The package includes the following feeds.
 
 | Entity | Type | Parameters | Description |
 | --- | --- | --- | --- |
 | `/whisk.system/alarms` | package | - | Alarms and periodic utility |
-| `/whisk.system/alarms/alarm` | feed | cron, trigger_payload, maxTriggers | Fire trigger event periodically |
+| `/whisk.system/alarms/alarm` | feed | cron, trigger_payload, maxTriggers, startDate, stopDate | Fire trigger event periodically |
+| `/whisk.system/alarms/once` | feed | date, trigger_payload | Fire trigger event once on a specific date |
 
 
 ## Firing a trigger event periodically
@@ -24,24 +25,54 @@ For more details about using cron syntax, see: http://crontab.org. Following are
   - `0 * * * *`: top of every hour.
   - `0 */2 * * *`: every 2 hours (i.e. 02:00:00, 04:00:00, ...)
   - `0 9 8 * *`: at 9:00:00AM (UTC) on the eighth day of every month
+  
+  **Note**: The parameter `cron` also supports a custom syntax of six fields, where the first field represents seconds.
+  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`: The value of this parameter becomes the content of the trigger every time the trigger is fired.
 
-- `maxTriggers`: Stop firing triggers when this limit is reached. Defaults to 1,000,000. You can set it to infinite (-1).
+- `maxTriggers`: Stop firing triggers when this limit is reached. Defaults to infinite (-1).
 
-The following is an example of creating a trigger that will be fired once every 2 minutes with `name` and `place` values in the trigger event.
+- `startDate`: The date when the trigger will start running.  The trigger will fire based on the schedule specified by the `cron` parameter.   
+
+- `stopDate`: The date when the trigger will stop running.  Triggers will no longer be fired once this date has been reached.
+
+  **Note**: The `startDate` and `stopDate` parameters support an integer or string value.  The integer value represents the number of milliseconds 
+  since 1 January 1970 00:00:00 UTC and the string value should be in the ISO 8601 format (http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15).
+
+
+The following is an example of creating a trigger that will be fired once every 2 minutes with `name` and `place` values in the trigger event.  The trigger will not start firing until
+January 1, 2019, 00:00:00 UTC and will stop firing January 31, 2019, 23:59:00 UTC.
 
   ```
   wsk trigger create periodic \
     --feed /whisk.system/alarms/alarm \
     --param cron "*/2 * * * *" \
-    --param trigger_payload "{\"name\":\"Odin\",\"place\":\"Asgard\"}"
+    --param trigger_payload "{\"name\":\"Odin\",\"place\":\"Asgard\"}" \
+    --param startDate "2019-01-01T00:00:00.000Z" \
+    --param stopDate "2019-01-31T23:59:00.000Z"
   ```
 
 Each generated event will include as parameters the properties specified in the `trigger_payload` value. In this case, each trigger event will have parameters `name=Odin` and `place=Asgard`.
 
-**Note**: The parameter `cron` also supports a custom syntax of six fields, where the first field represents seconds.
-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.
+## Firing a trigger event once  
+
+The `/whisk.system/alarms/once` feed configures the Alarm service to fire a trigger event on a specified date. The parameters are as follows:
 
+- `date`: The date when the trigger will be fired.  The trigger will be fired just once at the given time. 
+
+  **Note**: The `date` parameter supports an integer or string value.  The integer value represents the number of milliseconds 
+  since 1 January 1970 00:00:00 UTC and the string value should be in the ISO 8601 format (http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15).
+
+- `trigger_payload`: The value of this parameter becomes the content of the trigger when the trigger is fired. 
+
+The following is an example of creating a trigger that will be fired once on December 25, 2017, 12:30:00 UTC.
+
+  ```
+  wsk trigger create fireOnce \
+    --feed /whisk.system/alarms/once \
+    --param trigger_payload "{\"name\":\"Odin\",\"place\":\"Asgard\"}" \
+    --param date "2017-12-25T12:30:00.000Z"
+  ``` 
diff --git a/action/alarm.js b/action/alarm.js
index 6e72985..ef624b3 100644
--- a/action/alarm.js
+++ b/action/alarm.js
@@ -3,7 +3,7 @@ const common = require('./lib/common');
 function main(msg) {
 
     let eventMap = {
-        CREATE: 'put',
+        CREATE: 'post',
         READ: 'get',
         // UPDATE: 'put',
         DELETE: 'delete'
diff --git a/action/alarmOnce.js b/action/alarmOnce.js
deleted file mode 100644
index 38a7ebc..0000000
--- a/action/alarmOnce.js
+++ /dev/null
@@ -1,31 +0,0 @@
-const common = require('./lib/common');
-
-function main(msg) {
-
-    let eventMap = {
-        CREATE: 'put',
-        READ: 'get',
-        // UPDATE: 'put',
-        DELETE: 'delete'
-    };
-    // for creation -> CREATE
-    // for reading -> READ
-    // for deletion -> DELETE
-    var lifecycleEvent = msg.lifecycleEvent;
-
-    var endpoint = msg.apihost;
-    var webparams = common.createWebParams(msg);
-    webparams.fireOnce = true;
-
-    var url = `https://${endpoint}/api/v1/web/whisk.system/alarmsWeb/alarmWebAction.http`;
-
-    if (lifecycleEvent in eventMap) {
-        var method = eventMap[lifecycleEvent];
-        return common.requestHelper(url, webparams, method);
-    } else {
-        return Promise.reject('unsupported lifecycleEvent');
-    }
-}
-
-
-exports.main = main;
diff --git a/action/alarmOnce_package.json b/action/alarmOnce_package.json
deleted file mode 100644
index e48cd14..0000000
--- a/action/alarmOnce_package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "alarmOnce",
-  "version": "1.0.0",
-  "main": "alarmOnce.js"
-}
diff --git a/action/alarmWebAction.js b/action/alarmWebAction.js
index 4ba3a73..9f8789c 100644
--- a/action/alarmWebAction.js
+++ b/action/alarmWebAction.js
@@ -20,7 +20,7 @@ function main(params) {
     var workers = params.workers instanceof Array ? params.workers : [];
     var db;
 
-    if (params.__ow_method === "put") {
+    if (params.__ow_method === "post") {
 
         if (typeof params.trigger_payload === 'string') {
             params.trigger_payload = {payload: params.trigger_payload};
@@ -42,30 +42,49 @@ function main(params) {
             if (!params.date) {
                 return common.sendError(400, 'alarms once trigger feed is missing the date parameter');
             }
-            else {
-                var date = new Date(params.date);
-                if (isNaN(date.getTime())) {
-                    return common.sendError(400, `date parameter '${params.date}' is not a valid Date`);
-                }
-                else if (Date.now() >= date.getTime()) {
-                    return common.sendError(400, `date parameter '${params.date}' must be in the future`);
-                }
-                else {
-                    newTrigger.date = params.date;
-                }
+            var date = validateDate(params.date, 'date');
+            if (date !== params.date) {
+                return common.sendError(400, date);
             }
+            newTrigger.date = date;
         }
         else {
             if (!params.cron) {
                 return common.sendError(400, 'alarms trigger feed is missing the cron parameter');
             }
-            else {
-                try {
-                    new CronJob(params.cron, function() {});
-                    newTrigger.cron = params.cron;
-                } catch(ex) {
-                    return common.sendError(400, `cron pattern '${params.cron}' is not valid`);
+
+            var cronHandle;
+            try {
+                cronHandle = new CronJob(params.cron, function() {});
+                newTrigger.cron = params.cron;
+            } catch(ex) {
+                return common.sendError(400, `cron pattern '${params.cron}' is not valid`);
+            }
+
+            if (params.startDate) {
+                var startDate = validateDate(params.startDate, 'startDate');
+                if (startDate !== params.startDate) {
+                    return common.sendError(400, startDate);
+                }
+                newTrigger.startDate = startDate;
+            }
+
+            if (params.stopDate) {
+                if (params.maxTriggers) {
+                    return common.sendError(400, 'maxTriggers is not allowed when the stopDate parameter is specified');
+                }
+
+                var stopDate = validateDate(params.stopDate, 'stopDate', params.startDate);
+                if (stopDate !== params.stopDate) {
+                    return common.sendError(400, stopDate);
                 }
+                //verify that the next scheduled trigger fire will occur before the stop date
+                var triggerDate = cronHandle.nextDate();
+                if (triggerDate.isAfter(new Date(params.stopDate))) {
+                    return common.sendError(400, 'the next scheduled trigger fire is not until after the stop date');
+                }
+
+                newTrigger.stopDate = stopDate;
             }
         }
 
@@ -119,6 +138,8 @@ function main(params) {
                 }
                 else {
                     body.config.cron = doc.cron;
+                    body.config.startDate = doc.startDate;
+                    body.config.stopDate = doc.stopDate;
                 }
                 resolve({
                     statusCode: 200,
@@ -159,6 +180,25 @@ function main(params) {
     }
 }
 
+function validateDate(date, paramName, startDate) {
+
+    var dateObject = new Date(date);
+
+    if (isNaN(dateObject.getTime())) {
+        return `${paramName} parameter '${date}' is not a valid Date`;
+    }
+    else if (Date.now() >= dateObject.getTime()) {
+        return `${paramName} parameter '${date}' must be in the future`;
+    }
+    else if (startDate && dateObject <= new Date(startDate).getTime()) {
+        return `${paramName} parameter '${date}' must be greater than the startDate parameter ${startDate}`;
+    }
+    else {
+        return date;
+    }
+
+}
+
 exports.main = main;
 
 
diff --git a/installCatalog.sh b/installCatalog.sh
index 63fde64..3b34e49 100755
--- a/installCatalog.sh
+++ b/installCatalog.sh
@@ -50,9 +50,8 @@ echo Installing Alarms package.
 
 $WSK_CLI -i --apihost "$EDGEHOST" package update --auth "$AUTH" --shared yes alarms \
      -a description 'Alarms and periodic utility' \
-     -a parameters '[ {"name":"cron", "required":true}, {"name":"trigger_payload", "required":false} ]' \
+     -a parameters '[ {"name":"trigger_payload", "required":false} ]' \
      -p apihost "$APIHOST" \
-     -p cron '' \
      -p trigger_payload ''
 
 # make alarmFeed.zip
@@ -68,21 +67,14 @@ zip -r alarmFeed.zip lib package.json alarm.js
 
 $WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" alarms/alarm "$PACKAGE_HOME/action/alarmFeed.zip" \
      -a description 'Fire trigger when alarm occurs' \
+     -a parameters '[ {"name":"cron", "required":true}, {"name":"startDate", "required":false}, {"name":"stopDate", "required":false} ]' \
      -a feed true
 
-
-# make alarmOnce.zip
-if [ -e alarmOnce.zip ]
-then
-    rm -rf alarmOnce.zip
-fi
-
-cp -f alarmOnce_package.json package.json
-zip -r alarmOnce.zip lib package.json alarmOnce.js
-
-$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" alarms/once "$PACKAGE_HOME/action/alarmOnce.zip" \
+$WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" alarms/once "$PACKAGE_HOME/action/alarmFeed.zip" \
      -a description 'Fire trigger once when alarm occurs' \
-     -a feed true
+     -a parameters '[ {"name":"date", "required":true} ]' \
+     -a feed true \
+     -p fireOnce true
 
 if [ -n "$WORKERS" ];
 then
diff --git a/package.json b/package.json
index 6331109..7b89d74 100755
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
     "request-promise": "^1.0.2",
     "redis":"^2.7.1",
     "bluebird": "^3.5.0",
-    "systeminformation": "^3.19.0"
+    "systeminformation": "^3.19.0",
+    "long-timeout": "^0.1.1"
   }
 }
\ No newline at end of file
diff --git a/provider/app.js b/provider/app.js
index 3d90fd3..9ba5f98 100755
--- a/provider/app.js
+++ b/provider/app.js
@@ -5,7 +5,6 @@
  */
 var http = require('http');
 var express = require('express');
-var request = require('request');
 var bodyParser = require('body-parser');
 var bluebird = require('bluebird');
 var logger = require('./Logger');
diff --git a/provider/lib/cronAlarm.js b/provider/lib/cronAlarm.js
index efc68c9..98b691b 100644
--- a/provider/lib/cronAlarm.js
+++ b/provider/lib/cronAlarm.js
@@ -1,4 +1,5 @@
 var CronJob = require('cron').CronJob;
+var lt =  require('long-timeout');
 var constants = require('./constants.js');
 
 module.exports = function(logger, newTrigger) {
@@ -9,9 +10,7 @@ module.exports = function(logger, newTrigger) {
         apikey: newTrigger.apikey,
         name: newTrigger.name,
         namespace: newTrigger.namespace,
-        cron: newTrigger.cron,
-        triggersLeft: maxTriggers,
-        maxTriggers: maxTriggers
+        cron: newTrigger.cron
     };
 
     this.scheduleAlarm = function(triggerIdentifier, callback) {
@@ -21,11 +20,42 @@ module.exports = function(logger, newTrigger) {
             return new Promise(function(resolve, reject) {
 
                 var cronHandle = new CronJob(newTrigger.cron, callback);
-                logger.info(method, triggerIdentifier, 'starting cron job');
-                cronHandle.start();
 
-                cachedTrigger.cronHandle = cronHandle;
-                resolve(cachedTrigger);
+                if (newTrigger.stopDate) {
+                    cachedTrigger.stopDate = newTrigger.stopDate;
+                    //do not create trigger if the stopDate is in the past
+                    //or if it will never fire before the stopDate occurs
+                    if (new Date(newTrigger.stopDate).getTime() <= Date.now()) {
+                        return reject('the stop date has expired');
+                    }
+                    else if (cronHandle.nextDate().isAfter(new Date(newTrigger.stopDate))) {
+                        return reject('the next scheduled trigger fire is after the stop date');
+                    }
+                }
+                else {
+                    cachedTrigger.triggersLeft = maxTriggers;
+                    cachedTrigger.maxTriggers = maxTriggers;
+                }
+
+                if (newTrigger.startDate && new Date(newTrigger.startDate).getTime() > Date.now()) {
+                    var startDate = new Date(newTrigger.startDate).getTime();
+                    logger.info(method, triggerIdentifier, 'waiting for start date', startDate);
+                    lt.setTimeout(function() {
+                        logger.info(method, triggerIdentifier, 'starting cron job upon reaching start date', startDate);
+                        cronHandle.start();
+
+                        cachedTrigger.cronHandle = cronHandle;
+                        resolve(cachedTrigger);
+                    }, startDate - Date.now());
+                }
+                else {
+                    logger.info(method, triggerIdentifier, 'starting cron job');
+                    cronHandle.start();
+
+                    cachedTrigger.cronHandle = cronHandle;
+                    resolve(cachedTrigger);
+                }
+
             });
         } catch (err) {
             return Promise.reject(err);
diff --git a/provider/lib/dateAlarm.js b/provider/lib/dateAlarm.js
index 656ac40..04d2ec0 100644
--- a/provider/lib/dateAlarm.js
+++ b/provider/lib/dateAlarm.js
@@ -26,7 +26,7 @@ module.exports = function(logger, newTrigger) {
                     resolve(cachedTrigger);
                 }
                 else {
-                    return reject("the fire once date has expired");
+                    return reject('the fire once date has expired');
                 }
             });
         } catch (err) {
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index f5ee08f..8c7040b 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -1,4 +1,3 @@
-var _ = require('lodash');
 var request = require('request');
 var HttpStatus = require('http-status-codes');
 var constants = require('./constants.js');
@@ -152,6 +151,13 @@ module.exports = function(
             utils.disableTrigger(triggerIdentifier, undefined, 'Automatically disabled after firing once');
             logger.info(method, 'the fire once date has expired, disabled', triggerIdentifier);
         }
+        else if (dataTrigger.stopDate) {
+            //check if the next scheduled trigger is after the stop date
+            if (dataTrigger.cronHandle && dataTrigger.cronHandle.nextDate().isAfter(new Date(dataTrigger.stopDate))) {
+                utils.disableTrigger(triggerIdentifier, undefined, 'Automatically disabled after firing last scheduled trigger');
+                logger.info(method, 'last scheduled trigger before stop date, disabled', triggerIdentifier);
+            }
+        }
         else if (dataTrigger.maxTriggers && dataTrigger.triggersLeft === 0) {
             utils.disableTrigger(triggerIdentifier, undefined, 'Automatically disabled after reaching max triggers');
             logger.warn(method, 'no more triggers left, disabled', triggerIdentifier);
@@ -240,7 +246,7 @@ module.exports = function(
                         }, function (error, response) {
                             //disable trigger in database if trigger is dead
                             if (!error && utils.shouldDisableTrigger(response.statusCode)) {
-                                var message = 'Automatically disabled after receiving a ' + response.statusCode + ' status code on init trigger';
+                                var message = 'Automatically disabled after receiving a ' + response.statusCode + ' status code on trigger initialization';
                                 utils.disableTrigger(triggerIdentifier, response.statusCode, message);
                                 logger.error(method, 'trigger', triggerIdentifier, 'has been disabled due to status code:', response.statusCode);
                             }
@@ -251,9 +257,9 @@ module.exports = function(
                                     logger.info(method, triggerIdentifier, 'created successfully');
                                 })
                                 .catch(err => {
-                                    var message = 'Automatically disabled after receiving exception on init trigger: ' + err;
+                                    var message = 'Automatically disabled after receiving error on trigger initialization: ' + err;
                                     utils.disableTrigger(triggerIdentifier, undefined, message);
-                                    logger.error(method, 'Disabled trigger', triggerIdentifier, 'due to exception:', err);
+                                    logger.error(method, 'Disabled trigger', triggerIdentifier, err);
                                 });
                             }
                         });
@@ -296,9 +302,9 @@ module.exports = function(
                             logger.info(method, triggerIdentifier, 'created successfully');
                         })
                         .catch(err => {
-                            var message = 'Automatically disabled after receiving exception on create trigger: ' + err;
+                            var message = 'Automatically disabled after receiving error on trigger creation: ' + err;
                             utils.disableTrigger(triggerIdentifier, undefined, message);
-                            logger.error(method, 'Disabled trigger', triggerIdentifier, 'due to exception:', err);
+                            logger.error(method, 'Disabled trigger', triggerIdentifier, err);
                         });
                     }
                 }
diff --git a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
index fe70822..fb7c6d8 100644
--- a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
+++ b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
@@ -106,7 +106,7 @@ class AlarmsHealthFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            val futureDate = System.currentTimeMillis + (1000 * 30)
+            val futureDate = System.currentTimeMillis + (1000 * 20)
 
             // create whisk stuff
             println(s"Creating trigger: $triggerName")
@@ -119,7 +119,7 @@ class AlarmsHealthFeedTests
             feedCreationResult.stdout should include("ok")
 
             println("waiting for trigger")
-            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 60).length
+            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 30).length
             println(s"Found activation size (should be 1): $activations")
             activations should be(1)
     }
@@ -186,4 +186,48 @@ class AlarmsHealthFeedTests
             }
 
     }
+
+    it should "fire cron trigger using startDate and stopDate" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            implicit val wskprops = wp // shadow global props and make implicit
+            val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
+            val packageName = "dummyAlarmsPackage"
+
+            // the package alarms should be there
+            val packageGetResult = wsk.pkg.get("/whisk.system/alarms")
+            println("fetched package alarms")
+            packageGetResult.stdout should include("ok")
+
+            // create package binding
+            assetHelper.withCleaner(wsk.pkg, packageName) {
+                (pkg, name) => pkg.bind("/whisk.system/alarms", name)
+            }
+
+            val startDate = System.currentTimeMillis + (1000 * 20)
+            val stopDate = startDate + (1000 * 10)
+
+            println(s"Creating trigger: $triggerName")
+            // create whisk stuff
+            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+                (trigger, name) =>
+                    trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
+                        "cron" -> "* * * * * *".toJson,
+                        "startDate" -> startDate.toJson,
+                        "stopDate" -> stopDate.toJson))
+            }
+            feedCreationResult.stdout should include("ok")
+
+            println("waiting for triggers")
+            val activations = wsk.activation.pollFor(N = 20, Some(triggerName), retries = 45).length
+            println(s"Found activation size (should be at least 5): $activations")
+            activations should be >= 5
+
+
+            // get activation list again, should be same as before sleeping
+            println("confirming no new triggers")
+            val activationsAfterWait = wsk.activation.pollFor(N = activations + 1, Some(triggerName)).length
+            println(s"Found activation size after wait: $activationsAfterWait")
+            println("Activation list after wait should equal with activation list after stopDate")
+            activationsAfterWait should be(activations)
+    }
 }
diff --git a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
index 45ec618..2ad09c7 100644
--- a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
+++ b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
@@ -224,4 +224,70 @@ class AlarmsFeedTests
             feedCreationResult.stderr should include(s"date parameter '${pastDate}' must be in the future")
 
     }
+
+    it should "return error message when alarms startDate parameter is not a future date" in withAssetCleaner(wskprops) {
+
+        (wp, assetHelper) =>
+            implicit val wskprops = wp // shadow global props and make implicit
+            val triggerName = s"dummyCloudantTrigger-${System.currentTimeMillis}"
+            val packageName = "dummyCloudantPackage"
+            val feed = "alarm"
+
+            // the package alarms should be there
+            val packageGetResult = wsk.pkg.get("/whisk.system/alarms")
+            println("fetched package alarms")
+            packageGetResult.stdout should include("ok")
+
+            // create package binding
+            assetHelper.withCleaner(wsk.pkg, packageName) {
+                (pkg, name) => pkg.bind("/whisk.system/alarms", name)
+            }
+
+            val pastDate = System.currentTimeMillis - 5000
+
+            // create whisk stuff
+            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
+                (trigger, name) =>
+                    trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
+                        "startDate" -> pastDate.toJson,
+                        "cron" -> "* * * * *".toJson),
+                        expectedExitCode = 246)
+            }
+            feedCreationResult.stderr should include(s"startDate parameter '${pastDate}' must be in the future")
+
+    }
+
+    it should "return error message when alarms stopDate parameter is not greater than startDate" in withAssetCleaner(wskprops) {
+
+        (wp, assetHelper) =>
+            implicit val wskprops = wp // shadow global props and make implicit
+            val triggerName = s"dummyCloudantTrigger-${System.currentTimeMillis}"
+            val packageName = "dummyCloudantPackage"
+            val feed = "alarm"
+
+            // the package alarms should be there
+            val packageGetResult = wsk.pkg.get("/whisk.system/alarms")
+            println("fetched package alarms")
+            packageGetResult.stdout should include("ok")
+
+            // create package binding
+            assetHelper.withCleaner(wsk.pkg, packageName) {
+                (pkg, name) => pkg.bind("/whisk.system/alarms", name)
+            }
+
+            val stopDate = System.currentTimeMillis + 5000
+            val startDate = stopDate
+
+            // create whisk stuff
+            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
+                (trigger, name) =>
+                    trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
+                        "startDate" -> startDate.toJson,
+                        "stopDate" -> stopDate.toJson,
+                        "cron" -> "* * * * *".toJson),
+                        expectedExitCode = 246)
+            }
+            feedCreationResult.stderr should include(s"stopDate parameter '${stopDate}' must be greater than the startDate")
+
+    }
 }

-- 
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].