You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by cs...@apache.org on 2018/02/19 15:48:12 UTC

[incubator-openwhisk-package-alarms] branch master updated: Allow delete of trigger and rules after firing alarms once action (#128)

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 13ccc29  Allow delete of trigger and rules after firing alarms once action (#128)
13ccc29 is described below

commit 13ccc29a119f80dfea8ae824b793fceb295d02c3
Author: Jason Peterson <ja...@us.ibm.com>
AuthorDate: Mon Feb 19 10:48:10 2018 -0500

    Allow delete of trigger and rules after firing alarms once action (#128)
---
 README.md                                          |  36 +++---
 action/alarmWebAction.js                           |  23 +++-
 action/lib/Database.js                             |  15 ++-
 gradle/docker.gradle                               |  16 ++-
 installCatalog.sh                                  |   2 +-
 provider/lib/cronAlarm.js                          |   4 +-
 provider/lib/dateAlarm.js                          |   5 +-
 provider/lib/intervalAlarm.js                      |   4 +-
 provider/lib/sanitizer.js                          | 143 +++++++++++++++++++++
 provider/lib/utils.js                              |  84 +++++++-----
 settings.gradle                                    |   2 +-
 .../system/health/AlarmsHealthFeedTests.scala      |  79 ++++++------
 12 files changed, 307 insertions(+), 106 deletions(-)

diff --git a/README.md b/README.md
index 53c88aa..466feaf 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ The package includes the following feeds.
 | --- | --- | --- | --- |
 | `/whisk.system/alarms` | package | - | Alarms and periodic utility. |
 | `/whisk.system/alarms/interval` | feed | minutes, trigger_payload, startDate, stopDate | Fire Trigger event on an interval based schedule. |
-| `/whisk.system/alarms/once` | feed | date, trigger_payload | Fire Trigger event once on a specific date. |
+| `/whisk.system/alarms/once` | feed | date, trigger_payload, deleteAfterFire | Fire Trigger event once on a specific date. |
 | `/whisk.system/alarms/alarm` | feed | cron, trigger_payload, startDate, stopDate | Fire Trigger event on a time-based schedule using cron. |
 
 
@@ -19,17 +19,17 @@ The package includes the following feeds.
 
 The `/whisk.system/alarms/interval` feed configures the Alarm service to fire a Trigger event on an interval based schedule. The parameters are as follows:
 
-- `minutes`: An integer representing the length of the interval (in minutes) between trigger fires.
+- `minutes` (*required*): An integer representing the length of the interval (in minutes) between trigger fires.
 
-- `trigger_payload`: The value of this parameter becomes the content of the Trigger every time the Trigger is fired.
+- `trigger_payload` (*optional*): The value of this parameter becomes the content of the Trigger every time the Trigger is fired.
 
-- `startDate`: The date when the first trigger will be fired.  Subsequent fires will occur based on the interval length specified by the `minutes` parameter.   
+- `startDate` (*optional*): The date when the first trigger will be fired.  Subsequent fires will occur based on the interval length specified by the `minutes` parameter.   
 
-- `stopDate`: The date when the trigger will stop running.  Triggers will no longer be fired once this date has been reached.
+- `stopDate` (*optional*): The date when the Trigger will stop running.  Triggers will no longer be fired once this date has been reached.
 
   **Note**: The `startDate` and `stopDate` parameters support an integer or string value.  The integer value represents the number of milliseconds since 1 January 1970 00:00:00 UTC and the string value should be in the ISO 8601 format (http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15).
 
-The following example creates a trigger that is fired once every 2 minutes. The trigger fires as soon as possible, and will stop firing January 31, 2019, 23:59:00 UTC.
+The following example creates a trigger that is fired once every 2 minutes. The Trigger fires as soon as possible, and will stop firing January 31, 2019, 23:59:00 UTC.
 
   ```
   wsk trigger create interval \
@@ -45,27 +45,33 @@ Each generated event includes parameters, which are the properties that are spec
 
 The `/whisk.system/alarms/once` feed configures the Alarm service to fire a trigger event on a specified date. The parameters are as follows:
 
-- `date`: The date when the trigger will be fired.  The trigger will be fired just once at the given time. 
+- `date` (*required*): The date when the Trigger will be fired.  The Trigger will be fired just once at the given time. 
 
   **Note**: The `date` parameter supports an integer or string value.  The integer value represents the number of milliseconds 
   since 1 January 1970 00:00:00 UTC and the string value should be in the ISO 8601 format (http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15).
 
-- `trigger_payload`: The value of this parameter becomes the content of the Trigger when the Trigger is fired. 
+- `trigger_payload` (*optional*): The value of this parameter becomes the content of the Trigger when the Trigger is fired. 
 
-The following is an example of creating a trigger that will be fired once on December 25, 2017, 12:30:00 UTC.
+- `deleteAfterFire` (*optional*, default: false): The value of this parameter determines whether the Trigger and potentially all of its associated rules will be deleted after the Trigger is fired.  
+  - `false`: No action will be taken after the Trigger fires. 
+  - `true`: The Trigger will be deleted after it fires. 
+  - `rules`: The Trigger and all of its associated rules will be deleted after it fires.
+
+The following is an example of creating a trigger that will be fired once on December 25, 2019, 12:30:00 UTC.  After the Trigger fires it will be deleted as well as all of its associated rules.  
 
   ```
   wsk trigger create fireOnce \
     --feed /whisk.system/alarms/once \
     --param trigger_payload "{\"name\":\"Odin\",\"place\":\"Asgard\"}" \
-    --param date "2017-12-25T12:30:00.000Z"
+    --param date "2019-12-25T12:30:00.000Z" \
+    --param deleteAfterFire "rules"
   ``` 
 
 ## Firing a Trigger on a time-based schedule using cron
 
 The `/whisk.system/alarms/alarm` feed configures the Alarm service to fire a Trigger event at a specified frequency. The parameters are as follows:
 
-- `cron`: A string, based on the UNIX crontab syntax that indicates when to fire the Trigger in Coordinated Universal Time (UTC). The string is a sequence of five fields that are separated by spaces: `X X X X X`.
+- `cron` (*required*): A string, based on the UNIX crontab syntax that indicates when to fire the Trigger in Coordinated Universal Time (UTC). The string is a sequence of five fields that are separated by spaces: `X X X X X`.
 For more information, see: http://crontab.org. The following strings are examples that use varying duration's of frequency.
 
   - `* * * * *`: The Trigger fires at the top of every minute.
@@ -78,15 +84,15 @@ For more information, see: http://crontab.org. The following strings are example
   Here is an example using six fields notation:
     - `*/30 * * * * *`: every thirty seconds.
     
-- `trigger_payload`: The value of this parameter becomes the content of the Trigger every time the Trigger is fired.
+- `trigger_payload` (*optional*): The value of this parameter becomes the content of the Trigger every time the Trigger is fired.
 
-- `startDate`: The date when the Trigger will start running. The Trigger fires based on the schedule specified by the cron parameter.  
+- `startDate` (*optional*): The date when the Trigger will start running. The Trigger fires based on the schedule specified by the cron parameter.  
 
-- `stopDate`: The date when the Trigger will stop running. Triggers are no longer fired once this date is reached.
+- `stopDate` (*optional*): The date when the Trigger will stop running. Triggers are no longer fired once this date is reached.
 
   **Note**: The `startDate` and `stopDate` parameters support an integer or string value.  The integer value represents the number of milliseconds since 1 January 1970 00:00:00 UTC, and the string value should be in the ISO 8601 format (http://www.ecma-international.org/ecma-262/5.1/#sec-15.9.1.15).
 
-The following is an example of creating a trigger that fires once every 2 minutes with `name` and `place` values in the trigger event.  The trigger will not start firing until
+The following is an example of creating a trigger that fires once every 2 minutes with `name` and `place` values in the trigger event.  The Trigger will not start firing until
 January 1, 2019, 00:00:00 UTC and will stop firing January 31, 2019, 23:59:00 UTC.
 
   ```
diff --git a/action/alarmWebAction.js b/action/alarmWebAction.js
index b5b5bde..478af06 100644
--- a/action/alarmWebAction.js
+++ b/action/alarmWebAction.js
@@ -18,6 +18,7 @@ function main(params) {
     var triggerURL = `https://${params.apihost}/api/v1/namespaces/${triggerParts.namespace}/triggers/${triggerParts.name}`;
 
     var workers = params.workers instanceof Array ? params.workers : [];
+    var deleteAfterFireArray = ['false', 'true', 'rules'];
     var db;
 
     if (params.__ow_method === "post") {
@@ -47,6 +48,14 @@ function main(params) {
                 return common.sendError(400, date);
             }
             newTrigger.date = date;
+
+            if (params.deleteAfterFire) {
+                var deleteAfterFire = ('' + params.deleteAfterFire).trim().toLowerCase();
+                if (deleteAfterFireArray.indexOf(deleteAfterFire) === -1) {
+                    return common.sendError(400, 'deleteAfterFire parameter must be one of [false, true, rules].');
+                }
+                newTrigger.deleteAfterFire = deleteAfterFire;
+            }
         }
         else {
             var cronHandle;
@@ -213,6 +222,14 @@ function main(params) {
                         }
                         updatedParams.date = date;
                     }
+
+                    if (params.deleteAfterFire) {
+                        var deleteAfterFire = ('' + params.deleteAfterFire).trim().toLowerCase();
+                        if (deleteAfterFireArray.indexOf(deleteAfterFire) === -1) {
+                            return common.sendError(400, 'deleteAfterFire parameter must be one of [false, true, rules].');
+                        }
+                        newTrigger.deleteAfterFire = deleteAfterFire;
+                    }
                 }
                 else {
                     if (trigger.minutes) {
@@ -344,11 +361,7 @@ function hasSecondsGranularity(cron) {
 
     var fields = (cron + '').trim().split(/\s+/);
 
-    if (fields.length > 5 && fields[fields.length - 6] !== '0') {
-        return true;
-    }
-
-    return false;
+    return fields.length > 5 && fields[fields.length - 6] !== '0';
 }
 
 exports.main = main;
diff --git a/action/lib/Database.js b/action/lib/Database.js
index fcf062b..236f2f5 100644
--- a/action/lib/Database.js
+++ b/action/lib/Database.js
@@ -63,12 +63,12 @@ module.exports = function(dbURL, dbName) {
 
         return new Promise(function(resolve, reject) {
 
-            utilsDB.db.get(triggerID, function (err, existing) {
+            var qName = triggerID.split('/');
+            var id = retry ? triggerID : qName[0] + '/_/' + qName[2];
+            utilsDB.db.get(id, function (err, existing) {
                 if (err) {
                     if (retry) {
-                        var parts = triggerID.split('/');
-                        var id = parts[0] + '/_/' + parts[2];
-                        utilsDB.getTrigger(id, false)
+                        utilsDB.getTrigger(triggerID, false)
                         .then(doc => {
                             resolve(doc);
                         })
@@ -76,7 +76,8 @@ module.exports = function(dbURL, dbName) {
                             reject(err);
                         });
                     } else {
-                        reject(common.sendError(err.statusCode, 'could not find the trigger in the database'));
+                        var name = '/' + qName[1] + '/' + qName[2];
+                        reject(common.sendError(err.statusCode, 'could not find trigger ' + name + ' in the database'));
                     }
                 } else {
                     resolve(existing);
@@ -156,7 +157,9 @@ module.exports = function(dbURL, dbName) {
                     });
                 }
                 else {
-                    reject(common.sendError(err.statusCode, 'could not find the trigger in the database'));
+                    var qName = triggerID.split('/');
+                    var name = '/' + qName[1] + '/' + qName[2];
+                    reject(common.sendError(err.statusCode, 'could not find trigger ' + name + ' in the database'));
                 }
             });
         });
diff --git a/gradle/docker.gradle b/gradle/docker.gradle
index 8192d3a..c74ba30 100644
--- a/gradle/docker.gradle
+++ b/gradle/docker.gradle
@@ -37,16 +37,21 @@ if(project.hasProperty('dockerHost')) {
 }
 
 if(project.hasProperty('dockerBuildArgs')) {
-    dockerBuildArg += ['--build-arg', project.dockerBuildArgs]
+    dockerBuildArgs.each { arg  ->
+        dockerBuildArg += ['--build-arg', arg]
+    }
 }
 
-task distDocker << {
+task distDocker {
+    doLast {
     def start = new Date()
     def cmd = dockerBinary + dockerBuildArg + ['-t', dockerImageName, project.buildscript.sourceFile.getParentFile().getAbsolutePath()]
     retry(cmd, dockerRetries, dockerTimeout)
     println("Building '${dockerImageName}' took ${TimeCategory.minus(new Date(), start)}")
 }
-task tagImage << {
+}
+task tagImage {
+    doLast {
     def versionString = (dockerBinary + ['-v']).execute().text
     def matched = (versionString =~ /(\d+)\.(\d+)\.(\d+)/)
 
@@ -59,11 +64,14 @@ task tagImage << {
     }
     retry(dockerBinary + dockerCmd + [dockerImageName, dockerTaggedImageName], dockerRetries, dockerTimeout)
 }
+}
 
-task pushImage << {
+task pushImage {
+    doLast {
     def cmd = dockerBinary + ['push', dockerTaggedImageName]
     retry(cmd, dockerRetries, dockerTimeout)
 }
+}
 pushImage.dependsOn tagImage
 pushImage.onlyIf { dockerRegistry != '' }
 distDocker.finalizedBy pushImage
diff --git a/installCatalog.sh b/installCatalog.sh
index 85f6467..9261716 100755
--- a/installCatalog.sh
+++ b/installCatalog.sh
@@ -71,7 +71,7 @@ $WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" a
 
 $WSK_CLI -i --apihost "$EDGEHOST" action update --kind nodejs:6 --auth "$AUTH" alarms/once "$PACKAGE_HOME/action/alarmFeed.zip" \
      -a description 'Fire trigger once when alarm occurs' \
-     -a parameters '[ {"name":"date", "required":true} ]' \
+     -a parameters '[ {"name":"date", "required":true}, {"name":"deleteAfterFire", "required":false} ]' \
      -a feed true \
      -p fireOnce true
 
diff --git a/provider/lib/cronAlarm.js b/provider/lib/cronAlarm.js
index f60ec62..ff89d39 100644
--- a/provider/lib/cronAlarm.js
+++ b/provider/lib/cronAlarm.js
@@ -11,7 +11,9 @@ module.exports = function(logger, newTrigger) {
         name: newTrigger.name,
         namespace: newTrigger.namespace,
         payload: newTrigger.payload,
-        cron: newTrigger.cron
+        cron: newTrigger.cron,
+        triggerID: newTrigger.triggerID,
+        uri: newTrigger.uri
     };
 
     this.scheduleAlarm = function(triggerIdentifier, callback) {
diff --git a/provider/lib/dateAlarm.js b/provider/lib/dateAlarm.js
index 011261d..f505ca2 100644
--- a/provider/lib/dateAlarm.js
+++ b/provider/lib/dateAlarm.js
@@ -7,7 +7,10 @@ module.exports = function(logger, newTrigger) {
         name: newTrigger.name,
         namespace: newTrigger.namespace,
         payload: newTrigger.payload,
-        date: newTrigger.date
+        date: newTrigger.date,
+        deleteAfterFire: newTrigger.deleteAfterFire,
+        triggerID: newTrigger.triggerID,
+        uri: newTrigger.uri
     };
 
     this.scheduleAlarm = function(triggerIdentifier, callback) {
diff --git a/provider/lib/intervalAlarm.js b/provider/lib/intervalAlarm.js
index 077a68b..39d1a78 100644
--- a/provider/lib/intervalAlarm.js
+++ b/provider/lib/intervalAlarm.js
@@ -8,7 +8,9 @@ module.exports = function(logger, newTrigger) {
         name: newTrigger.name,
         namespace: newTrigger.namespace,
         payload: newTrigger.payload,
-        minutes: newTrigger.minutes
+        minutes: newTrigger.minutes,
+        triggerID: newTrigger.triggerID,
+        uri: newTrigger.uri
     };
 
     this.scheduleAlarm = function(triggerIdentifier, callback) {
diff --git a/provider/lib/sanitizer.js b/provider/lib/sanitizer.js
new file mode 100644
index 0000000..19bb747
--- /dev/null
+++ b/provider/lib/sanitizer.js
@@ -0,0 +1,143 @@
+var request = require('request');
+
+module.exports = function(logger, triggerDB, uriHost) {
+
+    var sanitizer = this;
+
+    this.deleteTriggerFromDB = function(triggerID, retryCount) {
+        var method = 'deleteTriggerFromDB';
+
+        //delete from database
+        triggerDB.get(triggerID, function (err, existing) {
+            if (!err) {
+                triggerDB.destroy(existing._id, existing._rev, function (err) {
+                    if (err) {
+                        if (err.statusCode === 409 && retryCount < 5) {
+                            setTimeout(function () {
+                                sanitizer.deleteTriggerFromDB(triggerID, (retryCount + 1));
+                            }, 1000);
+                        }
+                        else {
+                            logger.error(method, triggerID, 'there was an error deleting the trigger from the database');
+                        }
+                    }
+                });
+            }
+            else {
+                logger.error(method, triggerID, 'could not find the trigger in the database');
+            }
+        });
+    };
+
+    this.deleteTriggerAndRules = function(dataTrigger) {
+        var method = 'deleteTriggerAndRules';
+
+        var triggerIdentifier = dataTrigger.triggerID;
+        var auth = dataTrigger.apikey.split(':');
+
+        request({
+            method: 'get',
+            uri: dataTrigger.uri,
+            auth: {
+                user: auth[0],
+                pass: auth[1]
+            },
+        }, function(error, response, body) {
+            logger.info(method, triggerIdentifier, 'http get request, STATUS:', response ? response.statusCode : undefined);
+
+            if (error || response.statusCode >= 400) {
+                logger.error(method, triggerIdentifier, 'trigger get request failed');
+            }
+            else {
+                //delete the trigger
+                sanitizer.deleteTrigger(dataTrigger, auth, 0)
+                .then((info) => {
+                    logger.info(method, triggerIdentifier, info);
+                    if (body) {
+                        try {
+                            var jsonBody = JSON.parse(body);
+                            for (var rule in jsonBody.rules) {
+                                var qualifiedName = rule.split('/');
+                                var uri = uriHost + '/api/v1/namespaces/' + qualifiedName[0] + '/rules/' + qualifiedName[1];
+                                sanitizer.deleteRule(rule, uri, auth, 0);
+                            }
+                        }
+                        catch (err) {
+                            logger.error(method, triggerIdentifier, err);
+                        }
+                    }
+                })
+                .catch(err => {
+                    logger.error(method, triggerIdentifier, err);
+                });
+            }
+        });
+    };
+
+    this.deleteTrigger = function(dataTrigger, auth, retryCount) {
+        var method = 'deleteTrigger';
+
+        return new Promise(function(resolve, reject) {
+
+            var triggerIdentifier = dataTrigger.triggerID;
+            request({
+                method: 'delete',
+                uri: dataTrigger.uri,
+                auth: {
+                    user: auth[0],
+                    pass: auth[1]
+                },
+            }, function (error, response) {
+                logger.info(method, triggerIdentifier, 'http delete request, STATUS:', response ? response.statusCode : undefined);
+                if (error || response.statusCode >= 400) {
+                    if (!error && response.statusCode === 409 && retryCount < 5) {
+                        logger.info(method, 'attempting to delete trigger again', triggerIdentifier, 'Retry Count:', (retryCount + 1));
+                        setTimeout(function () {
+                            sanitizer.deleteTrigger(dataTrigger, auth, (retryCount + 1))
+                            .then(info => {
+                                resolve(info);
+                            })
+                            .catch(err => {
+                                reject(err);
+                            });
+                        }, 1000);
+                    } else {
+                        reject('trigger delete request failed');
+                    }
+                }
+                else {
+                    resolve('trigger delete request was successful');
+                }
+            });
+        });
+    };
+
+    this.deleteRule = function(rule, uri, auth, retryCount) {
+        var method = 'deleteRule';
+
+        request({
+            method: 'delete',
+            uri: uri,
+            auth: {
+                user: auth[0],
+                pass: auth[1]
+            },
+        }, function(error, response) {
+            logger.info(method, rule, 'http delete rule request, STATUS:', response ? response.statusCode : undefined);
+            if (error || response.statusCode >= 400) {
+                if (!error && response.statusCode === 409 && retryCount < 5) {
+                    logger.info(method, 'attempting to delete rule again', rule, 'Retry Count:', (retryCount + 1));
+                    setTimeout(function () {
+                        sanitizer.deleteRule(rule, uri, auth, (retryCount + 1));
+                    }, 1000);
+                } else {
+                    logger.error(method, rule, 'rule delete request failed');
+                }
+            }
+            else {
+                logger.info(method, rule, 'rule delete request was successful');
+            }
+        });
+    };
+
+};
diff --git a/provider/lib/utils.js b/provider/lib/utils.js
index a8f3153..6402b85 100644
--- a/provider/lib/utils.js
+++ b/provider/lib/utils.js
@@ -5,12 +5,10 @@ var constants = require('./constants.js');
 var DateAlarm = require('./dateAlarm.js');
 var CronAlarm = require('./cronAlarm.js');
 var IntervalAlarm = require('./intervalAlarm.js');
+var Sanitizer = require('./sanitizer');
+
+module.exports = function(logger, triggerDB, redisClient) {
 
-module.exports = function(
-  logger,
-  triggerDB,
-  redisClient
-) {
     this.module = 'utils';
     this.triggers = {};
     this.endpointAuth = process.env.ENDPOINT_AUTH;
@@ -22,6 +20,8 @@ module.exports = function(
     this.redisClient = redisClient;
     this.redisHash = triggerDB.config.db + '_' + this.worker;
     this.redisKey = constants.REDIS_KEY;
+    this.uriHost ='https://' + this.routerHost + ':443';
+    this.sanitizer = new Sanitizer(logger, triggerDB, this.uriHost);
 
     var retryDelay = constants.RETRY_DELAY;
     var retryAttempts = constants.RETRY_ATTEMPTS;
@@ -46,6 +46,9 @@ module.exports = function(
             }
         };
 
+        newTrigger.uri = utils.uriHost + '/api/v1/namespaces/' + newTrigger.namespace + '/triggers/' + newTrigger.name;
+        newTrigger.triggerID = triggerIdentifier;
+
         var alarm;
         if (newTrigger.date) {
             alarm = new DateAlarm(logger, newTrigger);
@@ -63,24 +66,22 @@ module.exports = function(
     this.fireTrigger = function(dataTrigger) {
         var method = 'fireTrigger';
 
-        var triggerIdentifier = utils.getTriggerIdentifier(dataTrigger.apikey, dataTrigger.namespace, dataTrigger.name);
-        var host = 'https://' + utils.routerHost + ':443';
+        var triggerIdentifier = dataTrigger.triggerID;
         var auth = dataTrigger.apikey.split(':');
-        var uri = host + '/api/v1/namespaces/' + dataTrigger.namespace + '/triggers/' + dataTrigger.name;
 
         logger.info(method, 'Alarm fired for', triggerIdentifier, 'attempting to fire trigger');
-        utils.postTrigger(dataTrigger, uri, auth, 0)
+        utils.postTrigger(dataTrigger, auth, 0)
         .then(triggerId => {
             logger.info(method, 'Trigger', triggerId, 'was successfully fired');
-            utils.disableExtinctTriggers(triggerIdentifier, dataTrigger);
+            utils.handleExpiredTriggers(dataTrigger);
         })
         .catch(err => {
             logger.error(method, err);
-            utils.disableExtinctTriggers(triggerIdentifier, dataTrigger);
+            utils.handleExpiredTriggers(dataTrigger);
         });
     };
 
-    this.postTrigger = function(dataTrigger, uri, auth, retryCount) {
+    this.postTrigger = function(dataTrigger, auth, retryCount) {
         var method = 'postTrigger';
 
         return new Promise(function(resolve, reject) {
@@ -92,7 +93,7 @@ module.exports = function(
 
             request({
                 method: 'post',
-                uri: uri,
+                uri: dataTrigger.uri,
                 auth: {
                     user: auth[0],
                     pass: auth[1]
@@ -100,8 +101,8 @@ module.exports = function(
                 json: dataTrigger.payload
             }, function(error, response) {
                 try {
-                    var triggerIdentifier = utils.getTriggerIdentifier(dataTrigger.apikey, dataTrigger.namespace, dataTrigger.name);
-                    logger.info(method, triggerIdentifier, 'http post request, STATUS:', response ? response.statusCode : response);
+                    var triggerIdentifier = dataTrigger.triggerID;
+                    logger.info(method, triggerIdentifier, 'http post request, STATUS:', response ? response.statusCode : undefined);
 
                     if (error || response.statusCode >= 400) {
                         // only manage trigger fires if they are not infinite
@@ -119,7 +120,7 @@ module.exports = function(
                             if (retryCount < retryAttempts) {
                                 logger.info(method, 'attempting to fire trigger again', triggerIdentifier, 'Retry Count:', (retryCount + 1));
                                 setTimeout(function () {
-                                    utils.postTrigger(dataTrigger, uri, auth, (retryCount + 1))
+                                    utils.postTrigger(dataTrigger, auth, (retryCount + 1))
                                     .then(triggerId => {
                                         resolve(triggerId);
                                     })
@@ -148,12 +149,36 @@ module.exports = function(
             [HttpStatus.REQUEST_TIMEOUT, HttpStatus.TOO_MANY_REQUESTS].indexOf(statusCode) === -1);
     };
 
-    this.disableExtinctTriggers = function(triggerIdentifier, dataTrigger) {
-        var method = 'disableExtinctTriggers';
+    this.handleExpiredTriggers = function(dataTrigger) {
+        var method = 'handleExpiredTriggers';
 
+        var triggerIdentifier = dataTrigger.triggerID;
         if (dataTrigger.date) {
-            utils.disableTrigger(triggerIdentifier, undefined, 'Automatically disabled after firing once');
-            logger.info(method, 'the fire once date has expired, disabled', triggerIdentifier);
+            if (dataTrigger.deleteAfterFire && dataTrigger.deleteAfterFire !== 'false') {
+                utils.stopTrigger(triggerIdentifier);
+
+                //delete trigger feed from database
+                utils.sanitizer.deleteTriggerFromDB(triggerIdentifier, 0);
+
+                //check if trigger and all associated rules should be deleted
+                if (dataTrigger.deleteAfterFire === 'rules') {
+                    utils.sanitizer.deleteTriggerAndRules(dataTrigger);
+                }
+                else {
+                    var auth = dataTrigger.apikey.split(':');
+                    utils.sanitizer.deleteTrigger(dataTrigger, auth, 0)
+                    .then((info) => {
+                        logger.info(method, triggerIdentifier, info);
+                    })
+                    .catch(err => {
+                        logger.error(method, triggerIdentifier, err);
+                    });
+                }
+            }
+            else {
+                utils.disableTrigger(triggerIdentifier, undefined, 'Automatically disabled after firing once');
+                logger.info(method, 'the fire once date has expired, disabled', triggerIdentifier);
+            }
         }
         else if (dataTrigger.stopDate) {
             //check if the next scheduled trigger is after the stop date
@@ -199,14 +224,14 @@ module.exports = function(
             }
             else {
                 logger.info(method, 'could not find', triggerIdentifier, 'in database');
-                //make sure it is removed from memory as well
-                utils.deleteTrigger(triggerIdentifier);
+                //make sure it is already stopped
+                utils.stopTrigger(triggerIdentifier);
             }
         });
     };
 
-    this.deleteTrigger = function(triggerIdentifier) {
-        var method = 'deleteTrigger';
+    this.stopTrigger = function (triggerIdentifier) {
+        var method = 'stopTrigger';
 
         if (utils.triggers[triggerIdentifier]) {
             if (utils.triggers[triggerIdentifier].cronHandle) {
@@ -220,10 +245,6 @@ module.exports = function(
         }
     };
 
-    this.getTriggerIdentifier = function(apikey, namespace, name) {
-        return apikey + '/' + namespace + '/' + name;
-    };
-
     this.initAllTriggers = function() {
         var method = 'initAllTriggers';
 
@@ -242,14 +263,13 @@ module.exports = function(
                         var namespace = doc.namespace;
                         var name = doc.name;
                         var apikey = doc.apikey;
-                        var host = 'https://' + utils.routerHost + ':' + 443;
-                        var triggerURL = host + '/api/v1/namespaces/' + namespace + '/triggers/' + name;
+                        var uri = utils.uriHost + '/api/v1/namespaces/' + namespace + '/triggers/' + name;
                         var auth = apikey.split(':');
 
                         logger.info(method, 'Checking if trigger', triggerIdentifier, 'still exists');
                         request({
                             method: 'get',
-                            url: triggerURL,
+                            url: uri,
                             auth: {
                                 user: auth[0],
                                 pass: auth[1]
@@ -308,7 +328,7 @@ module.exports = function(
 
                 if (utils.triggers[triggerIdentifier]) {
                     if (doc.status && doc.status.active === false) {
-                        utils.deleteTrigger(triggerIdentifier);
+                        utils.stopTrigger(triggerIdentifier);
                     }
                 }
                 else {
diff --git a/settings.gradle b/settings.gradle
index 9c7a819..33ecef8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -11,6 +11,6 @@ include 'tests'
 rootProject.name = 'openwhisk-package-alarms'
 
 gradle.ext.scala = [
-    version: '2.11.8',
+    version: '2.11.11',
     compileFlags: ['-feature', '-unchecked', '-deprecation', '-Xfatal-warnings', '-Ywarn-unused-import']
 ]
diff --git a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
index 456d9ab..e158f7a 100644
--- a/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
+++ b/tests/src/test/scala/system/health/AlarmsHealthFeedTests.scala
@@ -35,9 +35,8 @@ class AlarmsHealthFeedTests
 
     val wskprops = WskProps()
     val wsk = new Wsk
-
-
     val defaultAction = Some(TestUtils.getTestActionFilename("hello.js"))
+    val maxRetries = System.getProperty("max.retries", "100").toInt
 
     behavior of "Alarms Health tests"
 
@@ -60,8 +59,8 @@ class AlarmsHealthFeedTests
             }
 
             //create action
-            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
-                action.create(name, defaultAction)
+            assetHelper.withCleaner(wsk.action, actionName) {
+                (action, name) => action.create(name, defaultAction)
             }
 
             val futureDate = System.currentTimeMillis + (1000 * 20)
@@ -72,18 +71,30 @@ class AlarmsHealthFeedTests
                 (trigger, name) =>
                     trigger.create(name, feed = Some(s"$packageName/once"), parameters = Map(
                         "trigger_payload" -> "alarmTest".toJson,
-                        "date" -> futureDate.toJson))
+                        "date" -> futureDate.toJson,
+                        "deleteAfterFire" -> "rules".toJson))
             }
 
             // create rule
-            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = actionName)
+            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
+            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = maxRetries).length
             println(s"Found activation size (should be 1): $activations")
             activations should be(1)
+
+            // get activation list again, should be same as before waiting
+            println("confirming no new triggers")
+            val afterWait = wsk.activation.pollFor(N = activations + 1, Some(triggerName)).length
+            println(s"Found activation size after wait: $afterWait")
+            println("Activation list after wait should equal with activation list after firing once")
+            afterWait should be(activations)
+
+            //check that assets had been deleted by verifying we can recreate them
+            wsk.trigger.create(triggerName)
+            wsk.rule.create(ruleName, triggerName, actionName)
     }
 
     it should "fire cron trigger using startDate and stopDate" in withAssetCleaner(wskprops) {
@@ -105,8 +116,8 @@ class AlarmsHealthFeedTests
             }
 
             // create action
-            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
-                action.create(name, defaultAction)
+            assetHelper.withCleaner(wsk.action, actionName) {
+                (action, name) => action.create(name, defaultAction)
             }
 
             val startDate = System.currentTimeMillis + (1000 * 20)
@@ -123,12 +134,12 @@ class AlarmsHealthFeedTests
             }
 
             // create rule
-            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = actionName)
+            assetHelper.withCleaner(wsk.rule, ruleName) {
+                (rule, name) => rule.create(name, trigger = triggerName, action = actionName)
             }
 
             println("waiting for triggers")
-            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = 90).length
+            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = maxRetries).length
             println(s"Found activation size (should be 1): $activations")
             activations should be(1)
 
@@ -160,8 +171,8 @@ class AlarmsHealthFeedTests
             }
 
             // create action
-            assetHelper.withCleaner(wsk.action, actionName) { (action, name) =>
-                action.create(name, defaultAction)
+            assetHelper.withCleaner(wsk.action, actionName) {
+                (action, name) => action.create(name, defaultAction)
             }
 
             val startDate = System.currentTimeMillis + (1000 * 20)
@@ -178,17 +189,17 @@ class AlarmsHealthFeedTests
             }
 
             // create rule
-            assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
-                rule.create(name, trigger = triggerName, action = actionName)
+            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
+            val activations = wsk.activation.pollFor(N = 1, Some(triggerName), retries = maxRetries).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
+            val activationsAfterInterval = wsk.activation.pollFor(N = 2, Some(triggerName), retries = maxRetries).length
             println(s"Found activation size (should be 2): $activationsAfterInterval")
             activationsAfterInterval should be(2)
     }
@@ -233,8 +244,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, run) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
 
                     inside (activation.response.result) {
                         case Some(result) =>
@@ -294,8 +304,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, readRunResult) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
 
                     inside(activation.response.result) {
                         case Some(result) =>
@@ -321,8 +330,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, updateRunAction) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
             }
 
             val runResult = wsk.action.invoke(actionName, parameters = Map(
@@ -332,8 +340,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, runResult) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
 
                     inside(activation.response.result) {
                         case Some(result) =>
@@ -384,8 +391,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, readRunResult) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
 
                     inside(activation.response.result) {
                         case Some(result) =>
@@ -410,8 +416,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, updateRunAction) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
             }
 
             val runResult = wsk.action.invoke(actionName, parameters = Map(
@@ -421,8 +426,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, runResult) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
 
                     inside(activation.response.result) {
                         case Some(result) =>
@@ -473,8 +477,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, readRunResult) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
 
                     inside(activation.response.result) {
                         case Some(result) =>
@@ -500,8 +503,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, updateRunAction) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
             }
 
             val runResult = wsk.action.invoke(actionName, parameters = Map(
@@ -511,8 +513,7 @@ class AlarmsHealthFeedTests
             ))
 
             withActivation(wsk.activation, runResult) {
-                activation =>
-                    activation.response.success shouldBe true
+                activation => activation.response.success shouldBe true
 
                     inside(activation.response.result) {
                         case Some(result) =>

-- 
To stop receiving notification emails like this one, please contact
csantanapr@apache.org.