diff --git a/commands/flags.go b/commands/flags.go index 25c9b968f..e51ad0084 100644 --- a/commands/flags.go +++ b/commands/flags.go @@ -67,6 +67,7 @@ type FlagsStruct struct { detail bool format string nameSort bool // sorts list alphabetically by entity name + force bool } property struct { diff --git a/commands/trigger.go b/commands/trigger.go index 86b18da46..b39d247dd 100644 --- a/commands/trigger.go +++ b/commands/trigger.go @@ -20,6 +20,7 @@ package commands import ( "errors" "fmt" + "strings" "github.com/apache/openwhisk-cli/wski18n" "github.com/apache/openwhisk-client-go/whisk" @@ -286,6 +287,30 @@ var triggerDeleteCmd = &cobra.Command{ err = configureFeed(qualifiedName.GetEntityName(), fullFeedName, getParameters(Flags.common.param, false, false)) if err != nil { whisk.Debug(whisk.DbgError, "configureFeed(%s, %s) failed: %s\n", qualifiedName.GetEntityName(), fullFeedName, err) + + // If the trigger feed does not exist, it's deleted! This error message will look like + // "could not find trigger /NAMESPACE_ID/TRIGGER_NAME in the database" + // OR if the feed action is not present, there's no way to clean up the feed + // "The requested resource does not exist" + // likewise, if the feed action has no code, there's no way to clean up the feed + // "Missing main/no code to execute" + if strings.Contains(err.Error(), "could not find trigger") { + whisk.Debug(whisk.DbgWarn, "trigger feed is already deleted for trigger %s\n", qualifiedName.GetEntityName()) + } else if strings.Contains(err.Error(), "The requested resource does not exist") { + whisk.Debug(whisk.DbgWarn, "trigger feed action '%s' does not exist\n", fullFeedName) + } else if strings.Contains(err.Error(), "no code to execute") { + whisk.Debug(whisk.DbgWarn, "trigger feed action '%s' does not contain valid code\n", fullFeedName) + } else { + errStr := wski18n.T("Unable to delete trigger '{{.name}}': {{.err}}", + map[string]interface{}{"name": qualifiedName.GetEntityName(), "err": err}) + + if !Flags.common.force { + werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) + return werr + } else { + whisk.Debug(whisk.DbgInfo, "trigger delete is forced despite feed deletion failure\n") + } + } } Flags.common.param = origParams @@ -370,7 +395,7 @@ func configureFeed(triggerName string, feedName string, parameters interface{}) if err != nil { whisk.Debug(whisk.DbgError, "Invoke of action '%s' failed: %s\n", feedName, err) errStr := wski18n.T(FEED_CONFIGURATION_FAILURE, map[string]interface{}{"feedname": feedName, "trigname": triggerName, "err": err}) - err = whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE) + err = whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) } else { whisk.Debug(whisk.DbgInfo, "Successfully configured trigger feed via feed action '%s'\n", feedName) } @@ -405,6 +430,8 @@ func init() { triggerGetCmd.Flags().BoolVarP(&Flags.trigger.summary, "summary", "s", false, wski18n.T("summarize trigger details; parameters with prefix \"*\" are bound")) + triggerDeleteCmd.Flags().BoolVarP(&Flags.common.force, "force", "f", false, wski18n.T("force trigger deletion even when feed deletion fails")) + triggerFireCmd.Flags().StringSliceVarP(&Flags.common.param, "param", "p", []string{}, wski18n.T("parameter values in `KEY VALUE` format")) triggerFireCmd.Flags().StringVarP(&Flags.common.paramFile, "param-file", "P", "", wski18n.T("`FILE` containing parameter values in JSON format")) @@ -477,6 +504,7 @@ func (t *Trigger) Create(Client *whisk.Client, args []string) error { printFailedBlockingInvocationResponse(*feedName, false, res, err) reason := wski18n.T(FEED_CONFIGURATION_FAILURE, map[string]interface{}{"feedname": feedName.GetFullQualifiedName(), "err": err}) + errStr := wski18n.T("Unable to create trigger '{{.name}}': {{.err}}", map[string]interface{}{"name": trigger.Name, "err": reason}) werr := whisk.MakeWskErrorFromWskError(errors.New(errStr), err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE) diff --git a/tests/src/dat/feed-action-fails-delete.js b/tests/src/dat/feed-action-fails-delete.js new file mode 100644 index 000000000..764cf0aac --- /dev/null +++ b/tests/src/dat/feed-action-fails-delete.js @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +function main(params) { + var bodyobj = params || {}; + var sc = params.statusCode || 412; + bodystr = JSON.stringify(bodyobj); + var resp = {} + if (sc >=200 && sc <= 299) { + return { + statusCode: sc, + headers: { 'Content-Type': 'application/json' }, + body: bodystr, + }; + } + return { + error: { + error: 'Some sort of error string', + message: 'Some sort of error message', + statusCode: sc, + headers: { 'Content-Type': 'application/json' }, + } + }; +} diff --git a/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskCliBasicUsageTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskCliBasicUsageTests.scala index f907109d2..963993309 100644 --- a/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskCliBasicUsageTests.scala +++ b/tests/src/test/scala/org/apache/openwhisk/core/cli/test/WskCliBasicUsageTests.scala @@ -1561,6 +1561,37 @@ class WskCliBasicUsageTests extends TestHelpers with WskTestHelpers { } } + it should "not delete a trigger when feed deletion fails" in withAssetCleaner(wskprops) { (wp, assetHelper) => + val actionName = withTimestamp("feed-action-fails-delete") + val triggerName = withTimestamp("feedDeleteTest") + val feedActionCreateParams = Map("statusCode" -> JsNumber(200)) + val feedActionPath = new File(".").getAbsolutePath() + "/src/dat/feed-action-fails-delete.js" + + assetHelper.withCleaner(wsk.action, actionName) { (action, _) => + action.create(actionName, Some(feedActionPath)) + } + + try { + wsk.trigger.create(triggerName, feed = Some(actionName), parameters = feedActionCreateParams) + wsk.trigger.delete(triggerName, expectedExitCode = ERROR_EXIT).stderr should include( + """Unable to delete trigger""") + } finally { + wsk.cli( + Seq( + "trigger", + "delete", + s"$triggerName", + "--auth", + wskprops.authKey, + "--apihost", + wskprops.apihost, + "--apiversion", + wskprops.apiversion, + "-i", + "-f")) + } + } + it should "invoke a feed action with the correct lifecyle event when creating, retrieving and deleting a feed trigger" in withAssetCleaner( wskprops) { (wp, assetHelper) => val actionName = withTimestamp("echo") @@ -1641,6 +1672,40 @@ class WskCliBasicUsageTests extends TestHelpers with WskTestHelpers { stdoutNoDescOrParams should include regex (s"(?i)trigger ${qualtrgNoDescOrParams}\\s*\\(parameters: none defined\\)") } + it should "not create a trigger with timeout error when feed fails to initialize" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + val samplePackage = "samplePackage" + val guestNamespace = wskprops.namespace + val defaultWskProps = WskProps() + + val original_overrides = wp.overrides + wp.overrides = wp.overrides ++ Seq("-d") + val original_defaultWskProps_overrides = defaultWskProps.overrides + defaultWskProps.overrides = defaultWskProps.overrides ++ Seq("-d") + + assetHelper.withCleaner(wsk.pkg, samplePackage) { (pkg, _) => + pkg.create(samplePackage, shared = Some(true))(wp) + } + + val sampleFeed = s"$samplePackage/sampleFeed" + assetHelper.withCleaner(wsk.action, sampleFeed) { + val file = Some(TestUtils.getTestActionFilename("empty.js")) + (action, _) => + action.create(sampleFeed, file, kind = Some("nodejs:default"))(wp) + } + + val fullyQualifiedFeedName = s"/$guestNamespace/$sampleFeed" + withAssetCleaner(defaultWskProps) { (wp, assetHelper) => + assetHelper.withCleaner(wsk.trigger, "badfeed", confirmDelete = false) { (trigger, name) => + trigger.create(name, feed = Some(fullyQualifiedFeedName), expectedExitCode = TIMEOUT)(wp) + } + // with several active controllers race condition with cache invalidation might occur, thus retry + retry(wsk.trigger.get("badfeed", expectedExitCode = NOT_FOUND)(wp)) + } + wp.overrides = original_overrides + defaultWskProps.overrides = original_defaultWskProps_overrides + } + behavior of "Wsk entity list formatting" it should "create, and list a package with a long name" in withAssetCleaner(wskprops) { (wp, assetHelper) => diff --git a/vendor/vendor.json b/vendor/vendor.json index d80a786d4..539f13ef8 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,16 +3,16 @@ "ignore": "test", "package": [ { - "checksumSHA1": "upP6n3vm+l0xwq1EswdwVQSHDOg=", + "checksumSHA1": "v0S6R6MWfslQgopOXRIhcd9OdHk=", "path": "github.com/apache/openwhisk-client-go/whisk", - "revision": "d8ccb1442651beee6a9245913e3ca0cb182888b1", - "revisionTime": "2019-08-09T21:43:19Z" + "revision": "d272b2b1129269d384c1dbba6c39156abc10ad3c", + "revisionTime": "2019-12-11T16:53:14Z" }, { - "checksumSHA1": "uN1QmjP7Z6Rlkw73uOAQrtNKQR0=", + "checksumSHA1": "wdSbAqtYatmuN3ckhnIfwLMJM0U=", "path": "github.com/apache/openwhisk-client-go/wski18n", - "revision": "d8ccb1442651beee6a9245913e3ca0cb182888b1", - "revisionTime": "2019-08-09T21:43:19Z" + "revision": "d272b2b1129269d384c1dbba6c39156abc10ad3c", + "revisionTime": "2019-12-11T16:53:14Z" }, { "checksumSHA1": "jgCQbGeq+qGCWmBQOcl0LF6Dy0E=", diff --git a/wski18n/resources/en_US.all.json b/wski18n/resources/en_US.all.json index fbcdfb87c..532c3b609 100644 --- a/wski18n/resources/en_US.all.json +++ b/wski18n/resources/en_US.all.json @@ -682,6 +682,10 @@ "id": "FEED_CONFIGURATION_FAILURE", "translation": "Unable to configure feed '{{.feedname}}': {{.err}}" }, + { + "id": "force trigger deletion even when feed deletion fails", + "translation": "force trigger deletion even when feed deletion fails" + }, { "note-to-translators": "DO NOT TRANSLATE THE 'yes' AND 'no'. THOSE ARE FIXED CLI ARGUMENT VALUES", "id": "trigger visibility `SCOPE`; yes = shared, no = private",