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")