You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/01/12 14:31:51 UTC

[GitHub] csantanapr closed pull request #119: Add update lifecycle support

csantanapr closed pull request #119: Add update lifecycle support
URL: https://github.com/apache/incubator-openwhisk-package-alarms/pull/119
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/action/alarm.js b/action/alarm.js
index ef624b3..9467ece 100644
--- a/action/alarm.js
+++ b/action/alarm.js
@@ -5,11 +5,12 @@ function main(msg) {
     let eventMap = {
         CREATE: 'post',
         READ: 'get',
-        // UPDATE: 'put',
+        UPDATE: 'put',
         DELETE: 'delete'
     };
     // for creation -> CREATE
     // for reading -> READ
+    // for updating -> UPDATE
     // for deletion -> DELETE
     var lifecycleEvent = msg.lifecycleEvent;
 
diff --git a/action/alarmWebAction.js b/action/alarmWebAction.js
index 7c620c6..d56a159 100644
--- a/action/alarmWebAction.js
+++ b/action/alarmWebAction.js
@@ -182,16 +182,119 @@ function main(params) {
             });
         });
     }
+    else if (params.__ow_method === "put") {
+
+        return new Promise(function (resolve, reject) {
+            var updatedParams = {};
+
+            common.verifyTriggerAuth(triggerURL, params.authKey, false)
+            .then(() => {
+                db = new Database(params.DB_URL, params.DB_NAME);
+                return db.getTrigger(triggerID);
+            })
+            .then(trigger => {
+                if (trigger.status && trigger.status.reason && trigger.status.reason.kind === 'ADMIN') {
+                    return reject(common.sendError(400, `${params.triggerName} cannot be updated because it was disabled by an admin.  Please contact support for further assistance`));
+                }
+
+                if (params.trigger_payload) {
+                    updatedParams.payload = constructPayload(params.trigger_payload);
+                }
+
+                if (trigger.date) {
+                    if (params.date) {
+                        var date = validateDate(params.date, 'date');
+                        if (date !== params.date) {
+                            return reject(common.sendError(400, date));
+                        }
+                        updatedParams.date = date;
+                    }
+                }
+                else {
+                    if (trigger.minutes) {
+                        if (params.minutes) {
+                            if (+params.minutes !== parseInt(params.minutes)) {
+                                return reject(common.sendError(400, 'the minutes parameter must be an integer'));
+                            }
+                            var minutesParam = parseInt(params.minutes);
+
+                            if (minutesParam <= 0) {
+                                return reject(common.sendError(400, 'the minutes parameter must be an integer greater than zero'));
+                            }
+                            updatedParams.minutes = minutesParam;
+                        }
+                    }
+                    else {
+                        if (params.cron) {
+                            try {
+                                new CronJob(params.cron, function() {});
+                            } catch (ex) {
+                                return reject(common.sendError(400, `cron pattern '${params.cron}' is not valid`));
+                            }
+                            updatedParams.cron = params.cron;
+                        }
+                    }
+
+                    if (params.startDate) {
+                        var startDate = validateDate(params.startDate, 'startDate');
+                        if (startDate !== params.startDate) {
+                            return reject(common.sendError(400, startDate));
+                        }
+                        updatedParams.startDate = startDate;
+                    }
+
+                    if (params.stopDate) {
+                        var stopDate = validateDate(params.stopDate, 'stopDate', params.startDate || trigger.startDate);
+                        if (stopDate !== params.stopDate) {
+                            return reject(common.sendError(400, stopDate));
+                        }
+                        updatedParams.stopDate = stopDate;
+                    }
+                    else if (params.startDate && trigger.stopDate) {
+                        //need to verify that new start date is before existing stop date
+                        if (new Date(params.startDate).getTime() >= new Date(trigger.stopDate).getTime()) {
+                            return reject(common.sendError(400, `startDate parameter '${params.startDate}' must be less than the stopDate parameter '${trigger.stopDate}'`));
+                        }
+
+                    }
+                }
+
+                if (Object.keys(updatedParams).length === 0) {
+                    return reject(common.sendError(400, 'no updatable parameters were specified'));
+                }
+                return db.disableTrigger(trigger._id, trigger, 0, 'updating');
+            })
+            .then(triggerID => {
+                return db.getTrigger(triggerID, false);
+            })
+            .then(trigger => {
+                return db.updateTrigger(trigger._id, trigger, updatedParams, 0);
+            })
+            .then(() => {
+                resolve({
+                    statusCode: 200,
+                    headers: {'Content-Type': 'application/json'},
+                    body: new Buffer(JSON.stringify({'status': 'success'})).toString('base64')
+                });
+            })
+            .catch(err => {
+                reject(err);
+            });
+        });
+    }
     else if (params.__ow_method === "delete") {
 
         return new Promise(function (resolve, reject) {
             common.verifyTriggerAuth(triggerURL, params.authKey, true)
             .then(() => {
                 db = new Database(params.DB_URL, params.DB_NAME);
-                return db.disableTrigger(triggerID, 0);
+                return db.getTrigger(triggerID);
+            })
+            .then(trigger => {
+                return db.disableTrigger(trigger._id, trigger, 0, 'deleting');
             })
-            .then(id => {
-                return db.deleteTrigger(id, 0);
+            .then(triggerID => {
+                return db.deleteTrigger(triggerID, 0);
             })
             .then(() => {
                 resolve({
@@ -210,6 +313,20 @@ function main(params) {
     }
 }
 
+function constructPayload(payload) {
+
+    var updatedPayload;
+    if (payload) {
+        if (typeof payload === 'string') {
+            updatedPayload = {payload: payload};
+        }
+        if (typeof payload === 'object') {
+            updatedPayload = payload;
+        }
+    }
+    return updatedPayload;
+}
+
 function validateDate(date, paramName, startDate) {
 
     var dateObject = new Date(date);
@@ -221,7 +338,7 @@ function validateDate(date, paramName, startDate) {
         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}`;
+        return `${paramName} parameter '${date}' must be greater than the startDate parameter '${startDate}'`;
     }
     else {
         return date;
diff --git a/action/lib/Database.js b/action/lib/Database.js
index a878b85..fcf062b 100644
--- a/action/lib/Database.js
+++ b/action/lib/Database.js
@@ -85,56 +85,48 @@ module.exports = function(dbURL, dbName) {
         });
     };
 
-    this.disableTrigger = function(triggerID, retryCount) {
+    this.disableTrigger = function(triggerID, trigger, retryCount, crudMessage) {
 
-        return new Promise(function(resolve, reject) {
+        if (retryCount === 0) {
+            //check if it is already disabled
+            if (trigger.status && trigger.status.active === false) {
+                return Promise.resolve(triggerID);
+            }
 
-            utilsDB.db.get(triggerID, function (err, existing) {
-                if (!err) {
-                    var updatedTrigger = existing;
-                    updatedTrigger.status = {'active': false};
+            var message = `Automatically disabled trigger while ${crudMessage}`;
+            var status = {
+                'active': false,
+                'dateChanged': Date.now(),
+                'reason': {'kind': 'AUTO', 'statusCode': undefined, 'message': message}
+            };
+            trigger.status = status;
+        }
 
-                    utilsDB.db.insert(updatedTrigger, triggerID, function (err) {
-                        if (err) {
-                            if (err.statusCode === 409 && retryCount < 5) {
-                                setTimeout(function () {
-                                    utilsDB.disableTrigger(triggerID, (retryCount + 1))
-                                    .then(id => {
-                                        resolve(id);
-                                    })
-                                    .catch(err => {
-                                        reject(err);
-                                    });
-                                }, 1000);
-                            }
-                            else {
-                                reject(common.sendError(err.statusCode, 'there was an error while marking the trigger for delete in the database.', err.message));
-                            }
-                        }
-                        else {
-                            resolve(triggerID);
-                        }
-                    });
-                }
-                else {
-                    //legacy alarms triggers may have been created with _ namespace
-                    if (retryCount === 0) {
-                        var parts = triggerID.split('/');
-                        var id = parts[0] + '/_/' + parts[2];
-                        utilsDB.disableTrigger(id, (retryCount + 1))
-                        .then(id => {
-                            resolve(id);
-                        })
-                        .catch(err => {
-                            reject(err);
-                        });
+        return new Promise(function(resolve, reject) {
+
+            utilsDB.db.insert(trigger, triggerID, function (err) {
+                if (err) {
+                    if (err.statusCode === 409 && retryCount < 5) {
+                        setTimeout(function () {
+                            utilsDB.disableTrigger(triggerID, trigger, (retryCount + 1))
+                            .then(id => {
+                                resolve(id);
+                            })
+                            .catch(err => {
+                                reject(err);
+                            });
+                        }, 1000);
                     }
                     else {
-                        reject(common.sendError(err.statusCode, 'could not find the trigger in the database'));
+                        reject(common.sendError(err.statusCode, 'there was an error while disabling the trigger in the database.', err.message));
                     }
                 }
+                else {
+                    resolve(triggerID);
+                }
             });
         });
+
     };
 
     this.deleteTrigger = function(triggerID, retryCount) {
@@ -169,4 +161,43 @@ module.exports = function(dbURL, dbName) {
             });
         });
     };
+
+    this.updateTrigger = function(triggerID, trigger, params, retryCount) {
+
+        if (retryCount === 0) {
+            for (var key in params) {
+                trigger[key] = params[key];
+            }
+            var status = {
+                'active': true,
+                'dateChanged': Date.now()
+            };
+            trigger.status = status;
+        }
+
+        return new Promise(function(resolve, reject) {
+            utilsDB.db.insert(trigger, triggerID, function (err) {
+                if (err) {
+                    if (err.statusCode === 409 && retryCount < 5) {
+                        setTimeout(function () {
+                            utilsDB.updateTrigger(triggerID, trigger, params, (retryCount + 1))
+                            .then(id => {
+                                resolve(id);
+                            })
+                            .catch(err => {
+                                reject(err);
+                            });
+                        }, 1000);
+                    }
+                    else {
+                        reject(common.sendError(err.statusCode, 'there was an error while updating the trigger in the database.', err.message));
+                    }
+                }
+                else {
+                    resolve(triggerID);
+                }
+            });
+        });
+    };
+
 };
diff --git a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
index 0e72e69..cbee2c5 100644
--- a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
+++ b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
@@ -40,7 +40,6 @@ class AlarmsHealthFeedTests
 
 
     val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
-    val defaultActionName = "hello"
 
     behavior of "Alarms Health tests"
 
@@ -49,6 +48,7 @@ class AlarmsHealthFeedTests
             implicit val wskprops = wp // shadow global props and make implicit
             val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
             val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
+            val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}"
             val packageName = "dummyAlarmsPackage"
 
             // the package alarms should be there
@@ -61,21 +61,23 @@ class AlarmsHealthFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
+            // create action
+            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
+                action.create(name, defaultAction)
+            }
+
+            // create trigger feed
             println(s"Creating trigger: $triggerName")
-            // create whisk stuff
-            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
                         "trigger_payload" -> "alarmTest".toJson,
                         "cron" -> "* * * * * *".toJson))
             }
-            feedCreationResult.stdout should include("ok")
 
-            assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) =>
-                action.create(name, defaultAction)
-            }
+            // create rule
             assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+                rule.create(name, trigger = triggerName, action = actionName)
             }
 
             println("waiting for triggers")
@@ -107,6 +109,7 @@ class AlarmsHealthFeedTests
             implicit val wskprops = wp // shadow global props and make implicit
             val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
             val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
+            val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}"
             val packageName = "dummyAlarmsPackage"
 
             // the package alarms should be there
@@ -119,29 +122,138 @@ class AlarmsHealthFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
+            //create action
+            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
+                action.create(name, defaultAction)
+            }
+
             val futureDate = System.currentTimeMillis + (1000 * 20)
 
-            // create whisk stuff
+            // create trigger feed
             println(s"Creating trigger: $triggerName")
-            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/once"), parameters = Map(
                         "trigger_payload" -> "alarmTest".toJson,
                         "date" -> futureDate.toJson))
             }
-            feedCreationResult.stdout should include("ok")
 
-            assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) =>
+            // create rule
+            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
+                rule.create(name, trigger = triggerName, action = actionName)
+            }
+
+            println("waiting for trigger")
+            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 90).length
+            println(s"Found activation size (should be 1): $activations")
+            activations should be(1)
+    }
+
+    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 ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
+            val actionName = s"dummyAlarmsAction-${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)
+            }
+
+            // create action
+            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
                 action.create(name, defaultAction)
             }
+
+            val startDate = System.currentTimeMillis + (1000 * 20)
+            val stopDate = startDate + (1000 * 10)
+
+            // create trigger feed
+            println(s"Creating trigger: $triggerName")
+            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))
+            }
+
+            // create rule
             assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+                rule.create(name, trigger = triggerName, action = actionName)
             }
 
-            println("waiting for trigger")
+            println("waiting for triggers")
+            val activations = wsk.activation.pollFor(N = 20, Some(triggerName), retries = 60).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 waiting
+            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)
+    }
+
+    it should "fire interval 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 ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
+            val actionName = s"dummyAlarmsAction-${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)
+            }
+
+            // create action
+            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
+                action.create(name, defaultAction)
+            }
+
+            val startDate = System.currentTimeMillis + (1000 * 20)
+            val stopDate = startDate + (1000 * 90)
+
+            // create trigger feed
+            println(s"Creating trigger: $triggerName")
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
+                (trigger, name) =>
+                    trigger.create(name, feed = Some(s"$packageName/interval"), parameters = Map(
+                        "minutes" -> 1.toJson,
+                        "startDate" -> startDate.toJson,
+                        "stopDate" -> stopDate.toJson))
+            }
+
+            // create rule
+            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
+                rule.create(name, trigger = triggerName, action = actionName)
+            }
+
+            println("waiting for start date")
             val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 90).length
             println(s"Found activation size (should be 1): $activations")
             activations should be(1)
+
+            println("waiting for interval")
+            val activationsAfterInterval = wsk.activation.pollFor(N = 2, Some(triggerName), retries = 90).length
+            println(s"Found activation size (should be 2): $activationsAfterInterval")
+            activationsAfterInterval should be(2)
     }
 
     it should "return correct status and configuration" in withAssetCleaner(wskprops) {
@@ -168,7 +280,7 @@ class AlarmsHealthFeedTests
             val cronString = "* * * * * *"
             val maxTriggers = -1
 
-            // create whisk stuff
+            // create trigger feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
@@ -207,11 +319,10 @@ class AlarmsHealthFeedTests
 
     }
 
-    it should "fire cron trigger using startDate and stopDate" in withAssetCleaner(wskprops) {
+    it should "update cron, startDate and stopDate parameters" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
-            implicit val wskprops = wp // shadow global props and make implicit
+            implicit val wskProps = wp
             val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
-            val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
             val packageName = "dummyAlarmsPackage"
 
             // the package alarms should be there
@@ -224,46 +335,85 @@ class AlarmsHealthFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
+            val cron = "* * * * * *"
             val startDate = System.currentTimeMillis + (1000 * 20)
             val stopDate = startDate + (1000 * 10)
 
+            // create trigger feed
             println(s"Creating trigger: $triggerName")
-            // create whisk stuff
-            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
-                        "cron" -> "* * * * * *".toJson,
+                        "cron" -> cron.toJson,
                         "startDate" -> startDate.toJson,
                         "stopDate" -> stopDate.toJson))
             }
-            feedCreationResult.stdout should include("ok")
 
-            assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) =>
-                action.create(name, defaultAction)
+
+            val actionName = s"$packageName/alarm"
+            val readRunResult = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "READ".toJson,
+                "authKey" -> wskProps.authKey.toJson
+            ))
+
+            withActivation(wsk.activation, readRunResult) {
+                activation =>
+                    activation.response.success shouldBe true
+
+                    inside(activation.response.result) {
+                        case Some(result) =>
+                            val config = result.getFields("config").head.asInstanceOf[JsObject].fields
+
+                            config should contain("cron" -> cron.toJson)
+                            config should contain("startDate" -> startDate.toJson)
+                            config should contain("stopDate" -> stopDate.toJson)
+                    }
             }
-            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+
+            val updatedCron = "*/2 * * * * *"
+            val updatedStartDate = System.currentTimeMillis + (1000 * 20)
+            val updatedStopDate = updatedStartDate + (1000 * 10)
+
+            val updateRunAction = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "UPDATE".toJson,
+                "authKey" -> wskProps.authKey.toJson,
+                "cron" -> updatedCron.toJson,
+                "startDate" -> updatedStartDate.toJson,
+                "stopDate" -> updatedStopDate.toJson
+            ))
+
+            withActivation(wsk.activation, updateRunAction) {
+                activation =>
+                    activation.response.success shouldBe true
             }
 
-            println("waiting for triggers")
-            val activations = wsk.activation.pollFor(N = 20, Some(triggerName), retries = 60).length
-            println(s"Found activation size (should be at least 5): $activations")
-            activations should be >= 5
+            val runResult = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "READ".toJson,
+                "authKey" -> wskProps.authKey.toJson
+            ))
 
+            withActivation(wsk.activation, runResult) {
+                activation =>
+                    activation.response.success shouldBe true
 
-            // get activation list again, should be same as before waiting
-            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)
+                    inside(activation.response.result) {
+                        case Some(result) =>
+                            val config = result.getFields("config").head.asInstanceOf[JsObject].fields
+
+                            config should contain("cron" -> updatedCron.toJson)
+                            config should contain("startDate" -> updatedStartDate.toJson)
+                            config should contain("stopDate" -> updatedStopDate.toJson)
+                    }
+            }
     }
 
-    it should "fire interval trigger using startDate and stopDate" in withAssetCleaner(wskprops) {
+    it should "update fireOnce and payload parameters" in withAssetCleaner(wskprops) {
         (wp, assetHelper) =>
-            implicit val wskprops = wp // shadow global props and make implicit
+            implicit val wskProps = wp
             val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
-            val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
             val packageName = "dummyAlarmsPackage"
 
             // the package alarms should be there
@@ -276,35 +426,166 @@ class AlarmsHealthFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
+            val futureDate = System.currentTimeMillis + (1000 * 20)
+            val payload = JsObject(
+                "test" -> JsString("alarmsTest")
+            )
+
+            // create trigger feed
+            println(s"Creating trigger: $triggerName")
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
+                (trigger, name) =>
+                    trigger.create(name, feed = Some(s"$packageName/once"), parameters = Map(
+                        "trigger_payload" -> payload,
+                        "date" -> futureDate.toJson))
+            }
+
+            val actionName = s"$packageName/alarm"
+            val readRunResult = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "READ".toJson,
+                "authKey" -> wskProps.authKey.toJson
+            ))
+
+            withActivation(wsk.activation, readRunResult) {
+                activation =>
+                    activation.response.success shouldBe true
+
+                    inside(activation.response.result) {
+                        case Some(result) =>
+                            val config = result.getFields("config").head.asInstanceOf[JsObject].fields
+
+                            config should contain("date" -> futureDate.toJson)
+                            config should contain("payload" -> payload)
+                    }
+            }
+
+            val updatedFutureDate = System.currentTimeMillis + (1000 * 20)
+            val updatedPayload = JsObject(
+                "update_test" -> JsString("alarmsTest")
+            )
+
+            val updateRunAction = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "UPDATE".toJson,
+                "authKey" -> wskProps.authKey.toJson,
+                "trigger_payload" ->updatedPayload,
+                "date" -> updatedFutureDate.toJson
+            ))
+
+            withActivation(wsk.activation, updateRunAction) {
+                activation =>
+                    activation.response.success shouldBe true
+            }
+
+            val runResult = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "READ".toJson,
+                "authKey" -> wskProps.authKey.toJson
+            ))
+
+            withActivation(wsk.activation, runResult) {
+                activation =>
+                    activation.response.success shouldBe true
+
+                    inside(activation.response.result) {
+                        case Some(result) =>
+                            val config = result.getFields("config").head.asInstanceOf[JsObject].fields
+
+                            config should contain("date" -> updatedFutureDate.toJson)
+                            config should contain("payload" -> updatedPayload)
+                    }
+            }
+    }
+
+    it should "update minutes parameter for interval feed" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            implicit val wskProps = wp
+            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 minutes = 1
             val startDate = System.currentTimeMillis + (1000 * 20)
             val stopDate = startDate + (1000 * 90)
 
+            // create trigger feed
             println(s"Creating trigger: $triggerName")
-            // create whisk stuff
-            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/interval"), parameters = Map(
-                        "minutes" -> 1.toJson,
+                        "minutes" -> minutes.toJson,
                         "startDate" -> startDate.toJson,
                         "stopDate" -> stopDate.toJson))
             }
-            feedCreationResult.stdout should include("ok")
 
-            assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) =>
-                action.create(name, defaultAction)
+
+            val actionName = s"$packageName/alarm"
+            val readRunResult = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "READ".toJson,
+                "authKey" -> wskProps.authKey.toJson
+            ))
+
+            withActivation(wsk.activation, readRunResult) {
+                activation =>
+                    activation.response.success shouldBe true
+
+                    inside(activation.response.result) {
+                        case Some(result) =>
+                            val config = result.getFields("config").head.asInstanceOf[JsObject].fields
+
+                            config should contain("minutes" -> minutes.toJson)
+                            config should contain("startDate" -> startDate.toJson)
+                            config should contain("stopDate" -> stopDate.toJson)
+                    }
             }
-            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+
+            val updatedMinutes = 2
+            val updatedStartDate = System.currentTimeMillis + (1000 * 20)
+            val updatedStopDate = updatedStartDate + (1000 * 10)
+
+            val updateRunAction = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "UPDATE".toJson,
+                "authKey" -> wskProps.authKey.toJson,
+                "minutes" -> updatedMinutes.toJson,
+                "startDate" -> updatedStartDate.toJson,
+                "stopDate" -> updatedStopDate.toJson
+            ))
+
+            withActivation(wsk.activation, updateRunAction) {
+                activation =>
+                    activation.response.success shouldBe true
             }
 
-            println("waiting for start date")
-            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 90).length
-            println(s"Found activation size (should be 1): $activations")
-            activations should be(1)
+            val runResult = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "READ".toJson,
+                "authKey" -> wskProps.authKey.toJson
+            ))
 
-            println("waiting for interval")
-            val activationsAfterInterval = wsk.activation.pollFor(N = 2, Some(triggerName), retries = 90).length
-            println(s"Found activation size (should be 2): $activationsAfterInterval")
-            activationsAfterInterval should be(2)
+            withActivation(wsk.activation, runResult) {
+                activation =>
+                    activation.response.success shouldBe true
+
+                    inside(activation.response.result) {
+                        case Some(result) =>
+                            val config = result.getFields("config").head.asInstanceOf[JsObject].fields
+
+                            config should contain("minutes" -> updatedMinutes.toJson)
+                            config should contain("startDate" -> updatedStartDate.toJson)
+                            config should contain("stopDate" -> updatedStopDate.toJson)
+                    }
+            }
     }
 }
diff --git a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
index aa2e29d..bd0aa31 100644
--- a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
+++ b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
@@ -17,12 +17,12 @@
 package system.packages
 
 import org.junit.runner.RunWith
-import org.scalatest.FlatSpec
+import org.scalatest.{FlatSpec, Inside}
 import org.scalatest.junit.JUnitRunner
 import common._
 import spray.json.DefaultJsonProtocol.IntJsonFormat
 import spray.json.DefaultJsonProtocol.{LongJsonFormat, StringJsonFormat}
-import spray.json.pimpAny
+import spray.json.{JsObject, JsString, pimpAny}
 
 /**
  * Tests for alarms trigger service
@@ -37,7 +37,6 @@ class AlarmsFeedTests
     val wsk = new Wsk
 
     val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
-    val defaultActionName = "hello"
 
     behavior of "Alarms trigger service"
 
@@ -46,6 +45,7 @@ class AlarmsFeedTests
             implicit val wskprops = wp // shadow global props and make implicit
             val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
             val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
+            val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}"
             val packageName = "dummyAlarmsPackage"
 
             // the package alarms should be there
@@ -58,21 +58,23 @@ class AlarmsFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
-            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+            // create action
+            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
+                action.create(name, defaultAction)
+            }
+
+            // create trigger with feed
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                 trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
                     "trigger_payload" -> "alarmTest".toJson,
                     "cron" -> "* * * * * *".toJson,
                     "maxTriggers" -> 3.toJson))
             }
-            feedCreationResult.stdout should include("ok")
 
-            assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) =>
-                action.create(name, defaultAction)
-            }
+            // create rule
             assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+                rule.create(name, trigger = triggerName, action = actionName)
             }
 
             // get activation list of the trigger
@@ -99,7 +101,7 @@ class AlarmsFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -128,7 +130,7 @@ class AlarmsFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -159,7 +161,7 @@ class AlarmsFeedTests
 
             val cron = System.currentTimeMillis
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -189,7 +191,7 @@ class AlarmsFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -221,7 +223,7 @@ class AlarmsFeedTests
 
             val pastDate = System.currentTimeMillis - 5000
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -253,7 +255,7 @@ class AlarmsFeedTests
 
             val pastDate = System.currentTimeMillis - 5000
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -286,7 +288,7 @@ class AlarmsFeedTests
             val stopDate = System.currentTimeMillis + 5000
             val startDate = stopDate
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -317,7 +319,7 @@ class AlarmsFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -346,7 +348,7 @@ class AlarmsFeedTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
+            // create trigger with feed
             val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map(
@@ -357,4 +359,98 @@ class AlarmsFeedTests
             feedCreationResult.stderr should include("the minutes parameter must be an integer")
 
     }
+
+    it should "return error message when alarms trigger update contains no updatable parameters" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            implicit val wskProps = wp
+            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 futureDate = System.currentTimeMillis + (1000 * 20)
+
+            // create trigger feed
+            println(s"Creating trigger: $triggerName")
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
+                (trigger, name) =>
+                    trigger.create(name, feed = Some(s"$packageName/once"), parameters = Map(
+                        "date" -> futureDate.toJson))
+            }
+
+            val actionName = s"$packageName/alarm"
+            val updatedStartDate = System.currentTimeMillis + (1000 * 20)
+            val updatedStopDate = updatedStartDate + (1000 * 10)
+
+            val updateRunAction = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "UPDATE".toJson,
+                "authKey" -> wskProps.authKey.toJson,
+                "startDate" -> updatedStartDate.toJson,
+                "stopDate" -> updatedStopDate.toJson
+            ))
+
+            withActivation(wsk.activation, updateRunAction) {
+                activation =>
+                    activation.response.success shouldBe false
+                    val error = activation.response.result.get.fields("error").asJsObject
+                    error.fields("error") shouldBe JsString("no updatable parameters were specified")
+            }
+    }
+
+    it should "return error message when startDate is updated to be greater than the stopDate" in withAssetCleaner(wskprops) {
+        (wp, assetHelper) =>
+            implicit val wskProps = wp
+            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 minutes = 1
+            val startDate = System.currentTimeMillis + (1000 * 20)
+            val stopDate = startDate + (1000 * 10)
+
+            // create trigger feed
+            println(s"Creating trigger: $triggerName")
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
+                (trigger, name) =>
+                    trigger.create(name, feed = Some(s"$packageName/interval"), parameters = Map(
+                        "minutes" -> minutes.toJson,
+                        "startDate" -> startDate.toJson,
+                        "stopDate" -> stopDate.toJson))
+            }
+
+            val actionName = s"$packageName/alarm"
+            val updatedStartDate = System.currentTimeMillis + (1000 * 2000)
+
+            val updateRunAction = wsk.action.invoke(actionName, parameters = Map(
+                "triggerName" -> triggerName.toJson,
+                "lifecycleEvent" -> "UPDATE".toJson,
+                "authKey" -> wskProps.authKey.toJson,
+                "startDate" -> updatedStartDate.toJson
+            ))
+
+            withActivation(wsk.activation, updateRunAction) {
+                activation =>
+                    activation.response.success shouldBe false
+                    val error = activation.response.result.get.fields("error").asJsObject
+                    error.fields("error") shouldBe JsString(s"startDate parameter '${updatedStartDate}' must be less than the stopDate parameter '${stopDate}'")
+            }
+    }
 }
diff --git a/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala b/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala
index aa08880..12e626e 100644
--- a/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala
+++ b/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala
@@ -53,7 +53,6 @@ class AlarmsRedundancyTests
     var endpointPrefix = s"https://$user:$password@$edgeHost/alarmstrigger/worker0/"
 
     val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
-    val defaultActionName = "hello"
 
     behavior of "Alarms redundancy tests"
 
@@ -62,6 +61,7 @@ class AlarmsRedundancyTests
             implicit val wskprops = wp // shadow global props and make implicit
             val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
             val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
+            val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}"
             val packageName = "dummyAlarmsPackage"
 
             // the package alarms should be there
@@ -74,20 +74,22 @@ class AlarmsRedundancyTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
-            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+            // create action
+            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
+                action.create(name, defaultAction)
+            }
+
+            // create trigger feed
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                 trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
                     "trigger_payload" -> "alarmTest".toJson,
                     "cron" -> "* * * * * *".toJson))
             }
-            feedCreationResult.stdout should include("ok")
 
-            assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) =>
-                action.create(name, defaultAction)
-            }
+            // create rule
             assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+                rule.create(name, trigger = triggerName, action = actionName)
             }
 
             println("waiting for triggers")
@@ -124,6 +126,7 @@ class AlarmsRedundancyTests
             implicit val wskprops = wp // shadow global props and make implicit
             val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}"
             val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}"
+            val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}"
             val packageName = "dummyAlarmsPackage"
 
             // the package alarms should be there
@@ -136,20 +139,22 @@ class AlarmsRedundancyTests
                 (pkg, name) => pkg.bind("/whisk.system/alarms", name)
             }
 
-            // create whisk stuff
-            val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+            // create action
+            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
+                action.create(name, defaultAction)
+            }
+
+            // create trigger feed
+            assetHelper.withCleaner(wsk.trigger, triggerName) {
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
                         "trigger_payload" -> "alarmTest".toJson,
                         "cron" -> "* * * * * *".toJson))
             }
-            feedCreationResult.stdout should include("ok")
 
-            assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) =>
-                action.create(name, defaultAction)
-            }
+            // create rule
             assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+                rule.create(name, trigger = triggerName, action = actionName)
             }
 
             println("waiting for triggers")


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services