Trigger management (#101)
* initial implementation of trigger management get status and configuration
* add test for get status and configuration
* formatting
* formatting
* remove trailing whitespace
* fix scanCode complaint
* handle namespace of legacy alarms when fetching trigger document
* update dateChanged field to millis since epoch to provide consistency
* remove maxTriggers from response, add dateChangedISO to response
diff --git a/action/alarm.js b/action/alarm.js
index cda9aee..a5c217f 100644
--- a/action/alarm.js
+++ b/action/alarm.js
@@ -2,7 +2,14 @@
function main(msg) {
+ let eventMap = {
+ CREATE: 'put',
+ READ: 'get',
+ // UPDATE: 'put',
+ DELETE: 'delete'
+ };
// for creation -> CREATE
+ // for reading -> READ
// for deletion -> DELETE
var lifecycleEvent = msg.lifecycleEvent;
@@ -11,12 +18,11 @@
var url = `https://${endpoint}/api/v1/web/whisk.system/alarmsWeb/alarmWebAction.http`;
- if (lifecycleEvent !== 'CREATE' && lifecycleEvent !== 'DELETE') {
- return Promise.reject('lifecycleEvent must be CREATE or DELETE');
- }
- else {
- var method = lifecycleEvent === 'CREATE' ? 'put' : 'delete';
+ if (lifecycleEvent in eventMap) {
+ var method = eventMap[lifecycleEvent];
return requestHelper(url, webparams, method);
+ } else {
+ return Promise.reject('unsupported lifecycleEvent');
}
}
@@ -32,7 +38,7 @@
}, function(error, response, body) {
if (!error && response.statusCode === 200) {
- resolve();
+ resolve(body);
}
else {
if (response) {
diff --git a/action/alarmWebAction.js b/action/alarmWebAction.js
index 232d82d..31ed6fd 100644
--- a/action/alarmWebAction.js
+++ b/action/alarmWebAction.js
@@ -1,5 +1,6 @@
var request = require('request');
var CronJob = require('cron').CronJob;
+var moment = require('moment');
function main(params) {
@@ -45,7 +46,7 @@
maxTriggers: params.maxTriggers || -1,
status: {
'active': true,
- 'dateChanged': new Date().toISOString()
+ 'dateChanged': Date.now()
}
};
@@ -72,6 +73,38 @@
});
}
+ else if (params.__ow_method === "get") {
+ return new Promise(function (resolve, reject) {
+ verifyTriggerAuth(triggerURL, params.authKey, false)
+ .then(() => {
+ return getTrigger(db, triggerID);
+ })
+ .then(doc => {
+ var body = {
+ config: {
+ name: doc.name,
+ namespace: doc.namespace,
+ cron: doc.cron,
+ payload: doc.payload
+ },
+ status: {
+ active: doc.status.active,
+ dateChanged: moment(doc.status.dateChanged).utc().valueOf(),
+ dateChangedISO: moment(doc.status.dateChanged).utc().format(),
+ reason: doc.status.reason
+ }
+ };
+ resolve({
+ statusCode: 200,
+ headers: {'Content-Type': 'application/json'},
+ body: new Buffer(JSON.stringify(body)).toString('base64')
+ });
+ })
+ .catch(err => {
+ reject(err);
+ });
+ });
+ }
else if (params.__ow_method === "delete") {
return new Promise(function (resolve, reject) {
@@ -95,7 +128,7 @@
});
}
else {
- return sendError(400, 'lifecycleEvent must be CREATE or DELETE');
+ return sendError(400, 'unsupported lifecycleEvent');
}
}
@@ -152,6 +185,32 @@
});
}
+function getTrigger(triggerDB, triggerID, retry = true) {
+
+ return new Promise(function(resolve, reject) {
+
+ triggerDB.get(triggerID, function (err, existing) {
+ if (err) {
+ if (retry) {
+ var parts = triggerID.split('/');
+ var id = parts[0] + '/_/' + parts[2];
+ getTrigger(triggerDB, id, false)
+ .then(doc => {
+ resolve(doc);
+ })
+ .catch(err => {
+ reject(err);
+ });
+ } else {
+ reject(sendError(err.statusCode, 'could not find the trigger in the database'));
+ }
+ } else {
+ resolve(existing);
+ }
+ });
+ });
+}
+
function updateTrigger(triggerDB, triggerID, retryCount) {
return new Promise(function(resolve, reject) {
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index 5103356..c804cb8 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -170,7 +170,7 @@
var updatedTrigger = existing;
var status = {
'active': false,
- 'dateChanged': new Date().toISOString(),
+ 'dateChanged': Date.now(),
'reason': {'kind': 'AUTO', 'statusCode': statusCode, 'message': message}
};
updatedTrigger.status = status;
diff --git a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
index c175722..5377287 100644
--- a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
+++ b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala
@@ -17,16 +17,16 @@
package system.packages
import org.junit.runner.RunWith
-import org.scalatest.FlatSpec
+import org.scalatest.{FlatSpec, Inside}
import org.scalatest.junit.JUnitRunner
-
import common.TestHelpers
import common.Wsk
import common.WskProps
import common.WskTestHelpers
import spray.json.DefaultJsonProtocol.IntJsonFormat
import spray.json.DefaultJsonProtocol.StringJsonFormat
-import spray.json.pimpAny
+import spray.json.DefaultJsonProtocol.BooleanJsonFormat
+import spray.json.{JsObject, JsString, pimpAny}
/**
* Tests for alarms trigger service
@@ -34,6 +34,7 @@
@RunWith(classOf[JUnitRunner])
class AlarmsFeedTests
extends FlatSpec
+ with Inside
with TestHelpers
with WskTestHelpers {
@@ -100,4 +101,66 @@
activations should be(3)
}
+ it should "return correct status and configuration" in withAssetCleaner(wskprops) {
+ val currentTime = s"${System.currentTimeMillis}"
+
+ (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 triggerPayload = JsObject(
+ "test" -> JsString("alarmsTest")
+ )
+ val cronString = "* * * * * *"
+ val maxTriggers = -1
+
+ // create whisk stuff
+ val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) {
+ (trigger, name) =>
+ trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map(
+ "trigger_payload" -> triggerPayload,
+ "cron" -> cronString.toJson))
+ }
+ feedCreationResult.stdout should include("ok")
+
+ val actionName = s"$packageName/alarm"
+ val run = wsk.action.invoke(actionName, parameters = Map(
+ "triggerName" -> triggerName.toJson,
+ "lifecycleEvent" -> "READ".toJson,
+ "authKey" -> wskProps.authKey.toJson
+ ))
+
+ withActivation(wsk.activation, run) {
+ activation =>
+ activation.response.success shouldBe true
+
+ inside (activation.response.result) {
+ case Some(result) =>
+ val config = result.getFields("config").head.asInstanceOf[JsObject].fields
+ val status = result.getFields("status").head.asInstanceOf[JsObject].fields
+
+ config should contain("name" -> triggerName.toJson)
+ config should contain("cron" -> cronString.toJson)
+ config should contain("payload" -> triggerPayload)
+ config should contain key "namespace"
+
+ status should contain("active" -> true.toJson)
+ status should contain key "dateChanged"
+ status should contain key "dateChangedISO"
+ status should not(contain key "reason")
+ }
+ }
+
+ }
}