From commits-return-3152-archive-asf-public=cust-asf.ponee.io@openwhisk.apache.org Fri Jan 12 15:31:50 2018 Return-Path: X-Original-To: archive-asf-public@eu.ponee.io Delivered-To: archive-asf-public@eu.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by mx-eu-01.ponee.io (Postfix) with ESMTP id A9118180621 for ; Fri, 12 Jan 2018 15:31:50 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 98E05160C42; Fri, 12 Jan 2018 14:31:50 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 14015160C33 for ; Fri, 12 Jan 2018 15:31:48 +0100 (CET) Received: (qmail 68396 invoked by uid 500); 12 Jan 2018 14:31:48 -0000 Mailing-List: contact commits-help@openwhisk.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@openwhisk.apache.org Delivered-To: mailing list commits@openwhisk.apache.org Received: (qmail 68378 invoked by uid 99); 12 Jan 2018 14:31:48 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 12 Jan 2018 14:31:48 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 9184782193; Fri, 12 Jan 2018 14:31:47 +0000 (UTC) Date: Fri, 12 Jan 2018 14:31:47 +0000 To: "commits@openwhisk.apache.org" Subject: [incubator-openwhisk-package-alarms] branch master updated: add update lifecycle support (#119) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Message-ID: <151576750754.12347.4000545275677964318@gitbox.apache.org> From: csantanapr@apache.org X-Git-Host: gitbox.apache.org X-Git-Repo: incubator-openwhisk-package-alarms X-Git-Refname: refs/heads/master X-Git-Reftype: branch X-Git-Oldrev: 11ff4d09852e287f3a3aad26290e32c6ff75ee7a X-Git-Newrev: 5727fbc578bd2c90cd937600568d5b5e9c3bff27 X-Git-Rev: 5727fbc578bd2c90cd937600568d5b5e9c3bff27 X-Git-NotificationType: ref_changed_plus_diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated This is an automated email from the ASF dual-hosted git repository. csantanapr pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-package-alarms.git The following commit(s) were added to refs/heads/master by this push: new 5727fbc add update lifecycle support (#119) 5727fbc is described below commit 5727fbc578bd2c90cd937600568d5b5e9c3bff27 Author: Jason Peterson AuthorDate: Fri Jan 12 09:31:45 2018 -0500 add update lifecycle support (#119) --- action/alarm.js | 3 +- action/alarmWebAction.js | 125 ++++++- action/lib/Database.js | 113 +++--- .../system/health/AlarmsHealthFeedTests.scala | 391 ++++++++++++++++++--- .../scala/system/packages/AlarmsFeedTests.scala | 134 ++++++- .../system/redundancy/AlarmsRedundancyTests.scala | 35 +- 6 files changed, 666 insertions(+), 135 deletions(-) diff --git a/action/alarm.js b/action/alarm.js index ef624b3..9467ece 100644 --- a/action/alarm.js +++ b/action/alarm.js @@ -5,11 +5,12 @@ function main(msg) { let eventMap = { CREATE: 'post', READ: 'get', - // UPDATE: 'put', + UPDATE: 'put', DELETE: 'delete' }; // for creation -> CREATE // for reading -> READ + // for updating -> UPDATE // for deletion -> DELETE var lifecycleEvent = msg.lifecycleEvent; diff --git a/action/alarmWebAction.js b/action/alarmWebAction.js index 7c620c6..d56a159 100644 --- a/action/alarmWebAction.js +++ b/action/alarmWebAction.js @@ -182,16 +182,119 @@ function main(params) { }); }); } + else if (params.__ow_method === "put") { + + return new Promise(function (resolve, reject) { + var updatedParams = {}; + + common.verifyTriggerAuth(triggerURL, params.authKey, false) + .then(() => { + db = new Database(params.DB_URL, params.DB_NAME); + return db.getTrigger(triggerID); + }) + .then(trigger => { + if (trigger.status && trigger.status.reason && trigger.status.reason.kind === 'ADMIN') { + return reject(common.sendError(400, `${params.triggerName} cannot be updated because it was disabled by an admin. Please contact support for further assistance`)); + } + + if (params.trigger_payload) { + updatedParams.payload = constructPayload(params.trigger_payload); + } + + if (trigger.date) { + if (params.date) { + var date = validateDate(params.date, 'date'); + if (date !== params.date) { + return reject(common.sendError(400, date)); + } + updatedParams.date = date; + } + } + else { + if (trigger.minutes) { + if (params.minutes) { + if (+params.minutes !== parseInt(params.minutes)) { + return reject(common.sendError(400, 'the minutes parameter must be an integer')); + } + var minutesParam = parseInt(params.minutes); + + if (minutesParam <= 0) { + return reject(common.sendError(400, 'the minutes parameter must be an integer greater than zero')); + } + updatedParams.minutes = minutesParam; + } + } + else { + if (params.cron) { + try { + new CronJob(params.cron, function() {}); + } catch (ex) { + return reject(common.sendError(400, `cron pattern '${params.cron}' is not valid`)); + } + updatedParams.cron = params.cron; + } + } + + if (params.startDate) { + var startDate = validateDate(params.startDate, 'startDate'); + if (startDate !== params.startDate) { + return reject(common.sendError(400, startDate)); + } + updatedParams.startDate = startDate; + } + + if (params.stopDate) { + var stopDate = validateDate(params.stopDate, 'stopDate', params.startDate || trigger.startDate); + if (stopDate !== params.stopDate) { + return reject(common.sendError(400, stopDate)); + } + updatedParams.stopDate = stopDate; + } + else if (params.startDate && trigger.stopDate) { + //need to verify that new start date is before existing stop date + if (new Date(params.startDate).getTime() >= new Date(trigger.stopDate).getTime()) { + return reject(common.sendError(400, `startDate parameter '${params.startDate}' must be less than the stopDate parameter '${trigger.stopDate}'`)); + } + + } + } + + if (Object.keys(updatedParams).length === 0) { + return reject(common.sendError(400, 'no updatable parameters were specified')); + } + return db.disableTrigger(trigger._id, trigger, 0, 'updating'); + }) + .then(triggerID => { + return db.getTrigger(triggerID, false); + }) + .then(trigger => { + return db.updateTrigger(trigger._id, trigger, updatedParams, 0); + }) + .then(() => { + resolve({ + statusCode: 200, + headers: {'Content-Type': 'application/json'}, + body: new Buffer(JSON.stringify({'status': 'success'})).toString('base64') + }); + }) + .catch(err => { + reject(err); + }); + }); + } else if (params.__ow_method === "delete") { return new Promise(function (resolve, reject) { common.verifyTriggerAuth(triggerURL, params.authKey, true) .then(() => { db = new Database(params.DB_URL, params.DB_NAME); - return db.disableTrigger(triggerID, 0); + return db.getTrigger(triggerID); + }) + .then(trigger => { + return db.disableTrigger(trigger._id, trigger, 0, 'deleting'); }) - .then(id => { - return db.deleteTrigger(id, 0); + .then(triggerID => { + return db.deleteTrigger(triggerID, 0); }) .then(() => { resolve({ @@ -210,6 +313,20 @@ function main(params) { } } +function constructPayload(payload) { + + var updatedPayload; + if (payload) { + if (typeof payload === 'string') { + updatedPayload = {payload: payload}; + } + if (typeof payload === 'object') { + updatedPayload = payload; + } + } + return updatedPayload; +} + function validateDate(date, paramName, startDate) { var dateObject = new Date(date); @@ -221,7 +338,7 @@ function validateDate(date, paramName, startDate) { return `${paramName} parameter '${date}' must be in the future`; } else if (startDate && dateObject <= new Date(startDate).getTime()) { - return `${paramName} parameter '${date}' must be greater than the startDate parameter ${startDate}`; + return `${paramName} parameter '${date}' must be greater than the startDate parameter '${startDate}'`; } else { return date; diff --git a/action/lib/Database.js b/action/lib/Database.js index a878b85..fcf062b 100644 --- a/action/lib/Database.js +++ b/action/lib/Database.js @@ -85,56 +85,48 @@ module.exports = function(dbURL, dbName) { }); }; - this.disableTrigger = function(triggerID, retryCount) { + this.disableTrigger = function(triggerID, trigger, retryCount, crudMessage) { - return new Promise(function(resolve, reject) { + if (retryCount === 0) { + //check if it is already disabled + if (trigger.status && trigger.status.active === false) { + return Promise.resolve(triggerID); + } - utilsDB.db.get(triggerID, function (err, existing) { - if (!err) { - var updatedTrigger = existing; - updatedTrigger.status = {'active': false}; + var message = `Automatically disabled trigger while ${crudMessage}`; + var status = { + 'active': false, + 'dateChanged': Date.now(), + 'reason': {'kind': 'AUTO', 'statusCode': undefined, 'message': message} + }; + trigger.status = status; + } - utilsDB.db.insert(updatedTrigger, triggerID, function (err) { - if (err) { - if (err.statusCode === 409 && retryCount < 5) { - setTimeout(function () { - utilsDB.disableTrigger(triggerID, (retryCount + 1)) - .then(id => { - resolve(id); - }) - .catch(err => { - reject(err); - }); - }, 1000); - } - else { - reject(common.sendError(err.statusCode, 'there was an error while marking the trigger for delete in the database.', err.message)); - } - } - else { - resolve(triggerID); - } - }); - } - else { - //legacy alarms triggers may have been created with _ namespace - if (retryCount === 0) { - var parts = triggerID.split('/'); - var id = parts[0] + '/_/' + parts[2]; - utilsDB.disableTrigger(id, (retryCount + 1)) - .then(id => { - resolve(id); - }) - .catch(err => { - reject(err); - }); + return new Promise(function(resolve, reject) { + + utilsDB.db.insert(trigger, triggerID, function (err) { + if (err) { + if (err.statusCode === 409 && retryCount < 5) { + setTimeout(function () { + utilsDB.disableTrigger(triggerID, trigger, (retryCount + 1)) + .then(id => { + resolve(id); + }) + .catch(err => { + reject(err); + }); + }, 1000); } else { - reject(common.sendError(err.statusCode, 'could not find the trigger in the database')); + reject(common.sendError(err.statusCode, 'there was an error while disabling the trigger in the database.', err.message)); } } + else { + resolve(triggerID); + } }); }); + }; this.deleteTrigger = function(triggerID, retryCount) { @@ -169,4 +161,43 @@ module.exports = function(dbURL, dbName) { }); }); }; + + this.updateTrigger = function(triggerID, trigger, params, retryCount) { + + if (retryCount === 0) { + for (var key in params) { + trigger[key] = params[key]; + } + var status = { + 'active': true, + 'dateChanged': Date.now() + }; + trigger.status = status; + } + + return new Promise(function(resolve, reject) { + utilsDB.db.insert(trigger, triggerID, function (err) { + if (err) { + if (err.statusCode === 409 && retryCount < 5) { + setTimeout(function () { + utilsDB.updateTrigger(triggerID, trigger, params, (retryCount + 1)) + .then(id => { + resolve(id); + }) + .catch(err => { + reject(err); + }); + }, 1000); + } + else { + reject(common.sendError(err.statusCode, 'there was an error while updating the trigger in the database.', err.message)); + } + } + else { + resolve(triggerID); + } + }); + }); + }; + }; diff --git a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala index 0e72e69..cbee2c5 100644 --- a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala +++ b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala @@ -40,7 +40,6 @@ class AlarmsHealthFeedTests val defaultAction = Some(TestUtils.getTestActionFilename("hello.js")) - val defaultActionName = "hello" behavior of "Alarms Health tests" @@ -49,6 +48,7 @@ class AlarmsHealthFeedTests implicit val wskprops = wp // shadow global props and make implicit val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" + val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}" val packageName = "dummyAlarmsPackage" // the package alarms should be there @@ -61,21 +61,23 @@ class AlarmsHealthFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } + // create action + assetHelper.withCleaner(wsk.action, actionName) { (action, name) => + action.create(name, defaultAction) + } + + // create trigger feed println(s"Creating trigger: $triggerName") - // create whisk stuff - val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { + assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map( "trigger_payload" -> "alarmTest".toJson, "cron" -> "* * * * * *".toJson)) } - feedCreationResult.stdout should include("ok") - assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) => - action.create(name, defaultAction) - } + // create rule assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => - rule.create(name, trigger = triggerName, action = defaultActionName) + rule.create(name, trigger = triggerName, action = actionName) } println("waiting for triggers") @@ -107,6 +109,7 @@ class AlarmsHealthFeedTests implicit val wskprops = wp // shadow global props and make implicit val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" + val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}" val packageName = "dummyAlarmsPackage" // the package alarms should be there @@ -119,29 +122,138 @@ class AlarmsHealthFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } + //create action + assetHelper.withCleaner(wsk.action, actionName) { (action, name) => + action.create(name, defaultAction) + } + val futureDate = System.currentTimeMillis + (1000 * 20) - // create whisk stuff + // create trigger feed println(s"Creating trigger: $triggerName") - val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { + assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/once"), parameters = Map( "trigger_payload" -> "alarmTest".toJson, "date" -> futureDate.toJson)) } - feedCreationResult.stdout should include("ok") - assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) => + // create rule + assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => + rule.create(name, trigger = triggerName, action = actionName) + } + + println("waiting for trigger") + val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 90).length + println(s"Found activation size (should be 1): $activations") + activations should be(1) + } + + it should "fire cron trigger using startDate and stopDate" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + implicit val wskprops = wp // shadow global props and make implicit + val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" + val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" + val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}" + val packageName = "dummyAlarmsPackage" + + // the package alarms should be there + val packageGetResult = wsk.pkg.get("/whisk.system/alarms") + println("fetched package alarms") + packageGetResult.stdout should include("ok") + + // create package binding + assetHelper.withCleaner(wsk.pkg, packageName) { + (pkg, name) => pkg.bind("/whisk.system/alarms", name) + } + + // create action + assetHelper.withCleaner(wsk.action, actionName) { (action, name) => action.create(name, defaultAction) } + + val startDate = System.currentTimeMillis + (1000 * 20) + val stopDate = startDate + (1000 * 10) + + // create trigger feed + println(s"Creating trigger: $triggerName") + assetHelper.withCleaner(wsk.trigger, triggerName) { + (trigger, name) => + trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map( + "cron" -> "* * * * * *".toJson, + "startDate" -> startDate.toJson, + "stopDate" -> stopDate.toJson)) + } + + // create rule assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => - rule.create(name, trigger = triggerName, action = defaultActionName) + rule.create(name, trigger = triggerName, action = actionName) } - println("waiting for trigger") + println("waiting for triggers") + val activations = wsk.activation.pollFor(N = 20, Some(triggerName), retries = 60).length + println(s"Found activation size (should be at least 5): $activations") + activations should be >= 5 + + + // get activation list again, should be same as before waiting + println("confirming no new triggers") + val activationsAfterWait = wsk.activation.pollFor(N = activations + 1, Some(triggerName)).length + println(s"Found activation size after wait: $activationsAfterWait") + println("Activation list after wait should equal with activation list after stopDate") + activationsAfterWait should be(activations) + } + + it should "fire interval trigger using startDate and stopDate" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + implicit val wskprops = wp // shadow global props and make implicit + val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" + val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" + val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}" + val packageName = "dummyAlarmsPackage" + + // the package alarms should be there + val packageGetResult = wsk.pkg.get("/whisk.system/alarms") + println("fetched package alarms") + packageGetResult.stdout should include("ok") + + // create package binding + assetHelper.withCleaner(wsk.pkg, packageName) { + (pkg, name) => pkg.bind("/whisk.system/alarms", name) + } + + // create action + assetHelper.withCleaner(wsk.action, actionName) { (action, name) => + action.create(name, defaultAction) + } + + val startDate = System.currentTimeMillis + (1000 * 20) + val stopDate = startDate + (1000 * 90) + + // create trigger feed + println(s"Creating trigger: $triggerName") + assetHelper.withCleaner(wsk.trigger, triggerName) { + (trigger, name) => + trigger.create(name, feed = Some(s"$packageName/interval"), parameters = Map( + "minutes" -> 1.toJson, + "startDate" -> startDate.toJson, + "stopDate" -> stopDate.toJson)) + } + + // create rule + assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => + rule.create(name, trigger = triggerName, action = actionName) + } + + println("waiting for start date") val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 90).length println(s"Found activation size (should be 1): $activations") activations should be(1) + + println("waiting for interval") + val activationsAfterInterval = wsk.activation.pollFor(N = 2, Some(triggerName), retries = 90).length + println(s"Found activation size (should be 2): $activationsAfterInterval") + activationsAfterInterval should be(2) } it should "return correct status and configuration" in withAssetCleaner(wskprops) { @@ -168,7 +280,7 @@ class AlarmsHealthFeedTests val cronString = "* * * * * *" val maxTriggers = -1 - // create whisk stuff + // create trigger feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map( @@ -207,11 +319,10 @@ class AlarmsHealthFeedTests } - it should "fire cron trigger using startDate and stopDate" in withAssetCleaner(wskprops) { + it should "update cron, startDate and stopDate parameters" in withAssetCleaner(wskprops) { (wp, assetHelper) => - implicit val wskprops = wp // shadow global props and make implicit + implicit val wskProps = wp val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" - val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" val packageName = "dummyAlarmsPackage" // the package alarms should be there @@ -224,46 +335,85 @@ class AlarmsHealthFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } + val cron = "* * * * * *" val startDate = System.currentTimeMillis + (1000 * 20) val stopDate = startDate + (1000 * 10) + // create trigger feed println(s"Creating trigger: $triggerName") - // create whisk stuff - val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { + assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map( - "cron" -> "* * * * * *".toJson, + "cron" -> cron.toJson, "startDate" -> startDate.toJson, "stopDate" -> stopDate.toJson)) } - feedCreationResult.stdout should include("ok") - assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) => - action.create(name, defaultAction) + + val actionName = s"$packageName/alarm" + val readRunResult = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "READ".toJson, + "authKey" -> wskProps.authKey.toJson + )) + + withActivation(wsk.activation, readRunResult) { + activation => + activation.response.success shouldBe true + + inside(activation.response.result) { + case Some(result) => + val config = result.getFields("config").head.asInstanceOf[JsObject].fields + + config should contain("cron" -> cron.toJson) + config should contain("startDate" -> startDate.toJson) + config should contain("stopDate" -> stopDate.toJson) + } } - assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => - rule.create(name, trigger = triggerName, action = defaultActionName) + + val updatedCron = "*/2 * * * * *" + val updatedStartDate = System.currentTimeMillis + (1000 * 20) + val updatedStopDate = updatedStartDate + (1000 * 10) + + val updateRunAction = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "UPDATE".toJson, + "authKey" -> wskProps.authKey.toJson, + "cron" -> updatedCron.toJson, + "startDate" -> updatedStartDate.toJson, + "stopDate" -> updatedStopDate.toJson + )) + + withActivation(wsk.activation, updateRunAction) { + activation => + activation.response.success shouldBe true } - println("waiting for triggers") - val activations = wsk.activation.pollFor(N = 20, Some(triggerName), retries = 60).length - println(s"Found activation size (should be at least 5): $activations") - activations should be >= 5 + val runResult = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "READ".toJson, + "authKey" -> wskProps.authKey.toJson + )) + withActivation(wsk.activation, runResult) { + activation => + activation.response.success shouldBe true - // get activation list again, should be same as before waiting - println("confirming no new triggers") - val activationsAfterWait = wsk.activation.pollFor(N = activations + 1, Some(triggerName)).length - println(s"Found activation size after wait: $activationsAfterWait") - println("Activation list after wait should equal with activation list after stopDate") - activationsAfterWait should be(activations) + inside(activation.response.result) { + case Some(result) => + val config = result.getFields("config").head.asInstanceOf[JsObject].fields + + config should contain("cron" -> updatedCron.toJson) + config should contain("startDate" -> updatedStartDate.toJson) + config should contain("stopDate" -> updatedStopDate.toJson) + } + } } - it should "fire interval trigger using startDate and stopDate" in withAssetCleaner(wskprops) { + it should "update fireOnce and payload parameters" in withAssetCleaner(wskprops) { (wp, assetHelper) => - implicit val wskprops = wp // shadow global props and make implicit + implicit val wskProps = wp val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" - val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" val packageName = "dummyAlarmsPackage" // the package alarms should be there @@ -276,35 +426,166 @@ class AlarmsHealthFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } + val futureDate = System.currentTimeMillis + (1000 * 20) + val payload = JsObject( + "test" -> JsString("alarmsTest") + ) + + // create trigger feed + println(s"Creating trigger: $triggerName") + assetHelper.withCleaner(wsk.trigger, triggerName) { + (trigger, name) => + trigger.create(name, feed = Some(s"$packageName/once"), parameters = Map( + "trigger_payload" -> payload, + "date" -> futureDate.toJson)) + } + + val actionName = s"$packageName/alarm" + val readRunResult = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "READ".toJson, + "authKey" -> wskProps.authKey.toJson + )) + + withActivation(wsk.activation, readRunResult) { + activation => + activation.response.success shouldBe true + + inside(activation.response.result) { + case Some(result) => + val config = result.getFields("config").head.asInstanceOf[JsObject].fields + + config should contain("date" -> futureDate.toJson) + config should contain("payload" -> payload) + } + } + + val updatedFutureDate = System.currentTimeMillis + (1000 * 20) + val updatedPayload = JsObject( + "update_test" -> JsString("alarmsTest") + ) + + val updateRunAction = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "UPDATE".toJson, + "authKey" -> wskProps.authKey.toJson, + "trigger_payload" ->updatedPayload, + "date" -> updatedFutureDate.toJson + )) + + withActivation(wsk.activation, updateRunAction) { + activation => + activation.response.success shouldBe true + } + + val runResult = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "READ".toJson, + "authKey" -> wskProps.authKey.toJson + )) + + withActivation(wsk.activation, runResult) { + activation => + activation.response.success shouldBe true + + inside(activation.response.result) { + case Some(result) => + val config = result.getFields("config").head.asInstanceOf[JsObject].fields + + config should contain("date" -> updatedFutureDate.toJson) + config should contain("payload" -> updatedPayload) + } + } + } + + it should "update minutes parameter for interval feed" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + implicit val wskProps = wp + val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" + val packageName = "dummyAlarmsPackage" + + // the package alarms should be there + val packageGetResult = wsk.pkg.get("/whisk.system/alarms") + println("fetched package alarms") + packageGetResult.stdout should include("ok") + + // create package binding + assetHelper.withCleaner(wsk.pkg, packageName) { + (pkg, name) => pkg.bind("/whisk.system/alarms", name) + } + + val minutes = 1 val startDate = System.currentTimeMillis + (1000 * 20) val stopDate = startDate + (1000 * 90) + // create trigger feed println(s"Creating trigger: $triggerName") - // create whisk stuff - val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { + assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/interval"), parameters = Map( - "minutes" -> 1.toJson, + "minutes" -> minutes.toJson, "startDate" -> startDate.toJson, "stopDate" -> stopDate.toJson)) } - feedCreationResult.stdout should include("ok") - assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) => - action.create(name, defaultAction) + + val actionName = s"$packageName/alarm" + val readRunResult = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "READ".toJson, + "authKey" -> wskProps.authKey.toJson + )) + + withActivation(wsk.activation, readRunResult) { + activation => + activation.response.success shouldBe true + + inside(activation.response.result) { + case Some(result) => + val config = result.getFields("config").head.asInstanceOf[JsObject].fields + + config should contain("minutes" -> minutes.toJson) + config should contain("startDate" -> startDate.toJson) + config should contain("stopDate" -> stopDate.toJson) + } } - assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => - rule.create(name, trigger = triggerName, action = defaultActionName) + + val updatedMinutes = 2 + val updatedStartDate = System.currentTimeMillis + (1000 * 20) + val updatedStopDate = updatedStartDate + (1000 * 10) + + val updateRunAction = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "UPDATE".toJson, + "authKey" -> wskProps.authKey.toJson, + "minutes" -> updatedMinutes.toJson, + "startDate" -> updatedStartDate.toJson, + "stopDate" -> updatedStopDate.toJson + )) + + withActivation(wsk.activation, updateRunAction) { + activation => + activation.response.success shouldBe true } - println("waiting for start date") - val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 90).length - println(s"Found activation size (should be 1): $activations") - activations should be(1) + val runResult = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "READ".toJson, + "authKey" -> wskProps.authKey.toJson + )) - println("waiting for interval") - val activationsAfterInterval = wsk.activation.pollFor(N = 2, Some(triggerName), retries = 90).length - println(s"Found activation size (should be 2): $activationsAfterInterval") - activationsAfterInterval should be(2) + withActivation(wsk.activation, runResult) { + activation => + activation.response.success shouldBe true + + inside(activation.response.result) { + case Some(result) => + val config = result.getFields("config").head.asInstanceOf[JsObject].fields + + config should contain("minutes" -> updatedMinutes.toJson) + config should contain("startDate" -> updatedStartDate.toJson) + config should contain("stopDate" -> updatedStopDate.toJson) + } + } } } diff --git a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala index aa2e29d..bd0aa31 100644 --- a/tests/src/test/scala/system/packages/AlarmsFeedTests.scala +++ b/tests/src/test/scala/system/packages/AlarmsFeedTests.scala @@ -17,12 +17,12 @@ package system.packages import org.junit.runner.RunWith -import org.scalatest.FlatSpec +import org.scalatest.{FlatSpec, Inside} import org.scalatest.junit.JUnitRunner import common._ import spray.json.DefaultJsonProtocol.IntJsonFormat import spray.json.DefaultJsonProtocol.{LongJsonFormat, StringJsonFormat} -import spray.json.pimpAny +import spray.json.{JsObject, JsString, pimpAny} /** * Tests for alarms trigger service @@ -37,7 +37,6 @@ class AlarmsFeedTests val wsk = new Wsk val defaultAction = Some(TestUtils.getTestActionFilename("hello.js")) - val defaultActionName = "hello" behavior of "Alarms trigger service" @@ -46,6 +45,7 @@ class AlarmsFeedTests implicit val wskprops = wp // shadow global props and make implicit val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" + val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}" val packageName = "dummyAlarmsPackage" // the package alarms should be there @@ -58,21 +58,23 @@ class AlarmsFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff - val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { + // create action + assetHelper.withCleaner(wsk.action, actionName) { (action, name) => + action.create(name, defaultAction) + } + + // create trigger with feed + assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map( "trigger_payload" -> "alarmTest".toJson, "cron" -> "* * * * * *".toJson, "maxTriggers" -> 3.toJson)) } - feedCreationResult.stdout should include("ok") - assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) => - action.create(name, defaultAction) - } + // create rule assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => - rule.create(name, trigger = triggerName, action = defaultActionName) + rule.create(name, trigger = triggerName, action = actionName) } // get activation list of the trigger @@ -99,7 +101,7 @@ class AlarmsFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -128,7 +130,7 @@ class AlarmsFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -159,7 +161,7 @@ class AlarmsFeedTests val cron = System.currentTimeMillis - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -189,7 +191,7 @@ class AlarmsFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -221,7 +223,7 @@ class AlarmsFeedTests val pastDate = System.currentTimeMillis - 5000 - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -253,7 +255,7 @@ class AlarmsFeedTests val pastDate = System.currentTimeMillis - 5000 - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -286,7 +288,7 @@ class AlarmsFeedTests val stopDate = System.currentTimeMillis + 5000 val startDate = stopDate - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -317,7 +319,7 @@ class AlarmsFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -346,7 +348,7 @@ class AlarmsFeedTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff + // create trigger with feed val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName, confirmDelete = false) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/$feed"), parameters = Map( @@ -357,4 +359,98 @@ class AlarmsFeedTests feedCreationResult.stderr should include("the minutes parameter must be an integer") } + + it should "return error message when alarms trigger update contains no updatable parameters" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + implicit val wskProps = wp + val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" + val packageName = "dummyAlarmsPackage" + + // the package alarms should be there + val packageGetResult = wsk.pkg.get("/whisk.system/alarms") + println("fetched package alarms") + packageGetResult.stdout should include("ok") + + // create package binding + assetHelper.withCleaner(wsk.pkg, packageName) { + (pkg, name) => pkg.bind("/whisk.system/alarms", name) + } + + val futureDate = System.currentTimeMillis + (1000 * 20) + + // create trigger feed + println(s"Creating trigger: $triggerName") + assetHelper.withCleaner(wsk.trigger, triggerName) { + (trigger, name) => + trigger.create(name, feed = Some(s"$packageName/once"), parameters = Map( + "date" -> futureDate.toJson)) + } + + val actionName = s"$packageName/alarm" + val updatedStartDate = System.currentTimeMillis + (1000 * 20) + val updatedStopDate = updatedStartDate + (1000 * 10) + + val updateRunAction = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "UPDATE".toJson, + "authKey" -> wskProps.authKey.toJson, + "startDate" -> updatedStartDate.toJson, + "stopDate" -> updatedStopDate.toJson + )) + + withActivation(wsk.activation, updateRunAction) { + activation => + activation.response.success shouldBe false + val error = activation.response.result.get.fields("error").asJsObject + error.fields("error") shouldBe JsString("no updatable parameters were specified") + } + } + + it should "return error message when startDate is updated to be greater than the stopDate" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + implicit val wskProps = wp + val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" + val packageName = "dummyAlarmsPackage" + + // the package alarms should be there + val packageGetResult = wsk.pkg.get("/whisk.system/alarms") + println("fetched package alarms") + packageGetResult.stdout should include("ok") + + // create package binding + assetHelper.withCleaner(wsk.pkg, packageName) { + (pkg, name) => pkg.bind("/whisk.system/alarms", name) + } + + val minutes = 1 + val startDate = System.currentTimeMillis + (1000 * 20) + val stopDate = startDate + (1000 * 10) + + // create trigger feed + println(s"Creating trigger: $triggerName") + assetHelper.withCleaner(wsk.trigger, triggerName) { + (trigger, name) => + trigger.create(name, feed = Some(s"$packageName/interval"), parameters = Map( + "minutes" -> minutes.toJson, + "startDate" -> startDate.toJson, + "stopDate" -> stopDate.toJson)) + } + + val actionName = s"$packageName/alarm" + val updatedStartDate = System.currentTimeMillis + (1000 * 2000) + + val updateRunAction = wsk.action.invoke(actionName, parameters = Map( + "triggerName" -> triggerName.toJson, + "lifecycleEvent" -> "UPDATE".toJson, + "authKey" -> wskProps.authKey.toJson, + "startDate" -> updatedStartDate.toJson + )) + + withActivation(wsk.activation, updateRunAction) { + activation => + activation.response.success shouldBe false + val error = activation.response.result.get.fields("error").asJsObject + error.fields("error") shouldBe JsString(s"startDate parameter '${updatedStartDate}' must be less than the stopDate parameter '${stopDate}'") + } + } } diff --git a/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala b/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala index aa08880..12e626e 100644 --- a/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala +++ b/tests/src/test/scala/system/redundancy/AlarmsRedundancyTests.scala @@ -53,7 +53,6 @@ class AlarmsRedundancyTests var endpointPrefix = s"https://$user:$password@$edgeHost/alarmstrigger/worker0/" val defaultAction = Some(TestUtils.getTestActionFilename("hello.js")) - val defaultActionName = "hello" behavior of "Alarms redundancy tests" @@ -62,6 +61,7 @@ class AlarmsRedundancyTests implicit val wskprops = wp // shadow global props and make implicit val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" + val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}" val packageName = "dummyAlarmsPackage" // the package alarms should be there @@ -74,20 +74,22 @@ class AlarmsRedundancyTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff - val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { + // create action + assetHelper.withCleaner(wsk.action, actionName) { (action, name) => + action.create(name, defaultAction) + } + + // create trigger feed + assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map( "trigger_payload" -> "alarmTest".toJson, "cron" -> "* * * * * *".toJson)) } - feedCreationResult.stdout should include("ok") - assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) => - action.create(name, defaultAction) - } + // create rule assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => - rule.create(name, trigger = triggerName, action = defaultActionName) + rule.create(name, trigger = triggerName, action = actionName) } println("waiting for triggers") @@ -124,6 +126,7 @@ class AlarmsRedundancyTests implicit val wskprops = wp // shadow global props and make implicit val triggerName = s"dummyAlarmsTrigger-${System.currentTimeMillis}" val ruleName = s"dummyAlarmsRule-${System.currentTimeMillis}" + val actionName = s"dummyAlarmsAction-${System.currentTimeMillis}" val packageName = "dummyAlarmsPackage" // the package alarms should be there @@ -136,20 +139,22 @@ class AlarmsRedundancyTests (pkg, name) => pkg.bind("/whisk.system/alarms", name) } - // create whisk stuff - val feedCreationResult = assetHelper.withCleaner(wsk.trigger, triggerName) { + // create action + assetHelper.withCleaner(wsk.action, actionName) { (action, name) => + action.create(name, defaultAction) + } + + // create trigger feed + assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, name) => trigger.create(name, feed = Some(s"$packageName/alarm"), parameters = Map( "trigger_payload" -> "alarmTest".toJson, "cron" -> "* * * * * *".toJson)) } - feedCreationResult.stdout should include("ok") - assetHelper.withCleaner(wsk.action, defaultActionName) { (action, name) => - action.create(name, defaultAction) - } + // create rule assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) => - rule.create(name, trigger = triggerName, action = defaultActionName) + rule.create(name, trigger = triggerName, action = actionName) } println("waiting for triggers") -- To stop receiving notification emails like this one, please contact ['"commits@openwhisk.apache.org" '].