add update lifecycle support (#119)

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 @@
     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 @@
             });
         });
     }
+    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(id => {
-                return db.deleteTrigger(id, 0);
+            .then(trigger => {
+                return db.disableTrigger(trigger._id, trigger, 0, 'deleting');
+            })
+            .then(triggerID => {
+                return db.deleteTrigger(triggerID, 0);
             })
             .then(() => {
                 resolve({
@@ -210,6 +313,20 @@
     }
 }
 
+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 @@
         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 @@
         });
     };
 
-    this.disableTrigger = function(triggerID, retryCount) {
+    this.disableTrigger = function(triggerID, trigger, retryCount, crudMessage) {
+
+        if (retryCount === 0) {
+            //check if it is already disabled
+            if (trigger.status && trigger.status.active === false) {
+                return Promise.resolve(triggerID);
+            }
+
+            var message = `Automatically disabled trigger while ${crudMessage}`;
+            var status = {
+                'active': false,
+                'dateChanged': Date.now(),
+                'reason': {'kind': 'AUTO', 'statusCode': undefined, 'message': message}
+            };
+            trigger.status = status;
+        }
 
         return new Promise(function(resolve, reject) {
 
-            utilsDB.db.get(triggerID, function (err, existing) {
-                if (!err) {
-                    var updatedTrigger = existing;
-                    updatedTrigger.status = {'active': false};
-
-                    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);
-                        });
+            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 @@
             });
         });
     };
+
+    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 @@
 
 
     val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
-    val defaultActionName = "hello"
 
     behavior of "Alarms Health tests"
 
@@ -49,6 +48,7 @@
             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 @@
                 (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 @@
             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,23 +122,25 @@
                 (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) =>
-                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 trigger")
@@ -144,6 +149,113 @@
             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 = actionName)
+            }
+
+            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) {
         val currentTime = s"${System.currentTimeMillis}"
 
@@ -168,7 +280,7 @@
             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 @@
 
     }
 
-    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 @@
                 (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)
-            }
-            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+
+            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)
+                    }
             }
 
-            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 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
+            ))
 
-            // 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)
+            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("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 @@
                 (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)
-            }
-            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = defaultActionName)
+
+            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)
+                    }
             }
 
-            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 updatedMinutes = 2
+            val updatedStartDate = System.currentTimeMillis + (1000 * 20)
+            val updatedStopDate = updatedStartDate + (1000 * 10)
 
-            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)
+            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
+            }
+
+            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("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 @@
     val wsk = new Wsk
 
     val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
-    val defaultActionName = "hello"
 
     behavior of "Alarms trigger service"
 
@@ -46,6 +45,7 @@
             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 @@
                 (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 @@
                 (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 @@
                 (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 @@
 
             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 @@
                 (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 @@
 
             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 @@
 
             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 @@
             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 @@
                 (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 @@
                 (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 @@
             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 @@
     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 @@
             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 @@
                 (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 @@
             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 @@
                 (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")