From b0d311850438e550c0d8c98b68531d10f4d562b9 Mon Sep 17 00:00:00 2001 From: Rodric Rabbah <rabbah@us.ibm.com> Date: Sat, 19 Nov 2016 14:07:17 -0500 Subject: [PATCH] Unify action container environments Add npm openwhisk package to NodeJS images, and deprecation warning for uses of the 'whisk' context object. Make OpenWhisk related environment variables available in all container runtimes. --- core/actionProxy/actionproxy.py | 7 +- core/nodejsAction/Dockerfile | 1 + .../ActionProxyContainerTests.scala | 58 +++++++++----- .../JavaActionContainerTests.scala | 50 +++++++++++- .../NodeJsActionContainerTests.scala | 80 +++++++++++++++++-- .../PythonActionContainerTests.scala | 9 ++- .../Swift3ActionContainerTests.scala | 16 +--- .../SwiftActionContainerTests.scala | 37 ++++++--- tests/src/system/basic/CLIPythonTests.scala | 2 +- tests/src/system/basic/WskActionTests.scala | 18 ++++- .../src/system/basic/WskBasicNodeTests.scala | 4 + tests/src/system/basic/WskBasicTests.scala | 16 ++-- .../core/cli/test/WskBasicUsageTests.scala | 59 ++++++++++++++ 13 files changed, 288 insertions(+), 69 deletions(-) diff --git a/core/actionProxy/actionproxy.py b/core/actionProxy/actionproxy.py index 4266f5c7..3c3c8400 100644 --- a/core/actionProxy/actionproxy.py +++ b/core/actionProxy/actionproxy.py @@ -91,13 +91,14 @@ class ActionRunner: return (os.path.isfile(self.binary) and os.access(self.binary, os.X_OK)) # constructs an environment for the action to run in - # @param message is a JSON object received from invoker (should contain 'value' and 'authKey') + # @param message is a JSON object received from invoker (should contain 'value' and 'api_key' and other metadata) # @return an environment dictionary for the action process def env(self, message): # make sure to include all the env vars passed in by the invoker env = os.environ - if 'authKey' in message: - env['AUTH_KEY'] = message['authKey'] + for p in [ 'api_key', 'namespace', 'action_name', 'activation_id', 'deadline' ]: + if p in message: + env['__OW_%s' % p.upper()] = message[p] return env # runs the action, called iff self.verify() is True. diff --git a/core/nodejsAction/Dockerfile b/core/nodejsAction/Dockerfile index 6af9760b..1abd89e9 100644 --- a/core/nodejsAction/Dockerfile +++ b/core/nodejsAction/Dockerfile @@ -34,6 +34,7 @@ mustache@2.1.3 \ nano@5.10.0 \ node-uuid@1.4.2 \ oauth2-server@2.4.0 \ +openwhisk@2.6.0 \ process@0.11.0 \ request@2.79.0 \ rimraf@2.5.1 \ diff --git a/tests/src/actionContainers/ActionProxyContainerTests.scala b/tests/src/actionContainers/ActionProxyContainerTests.scala index 110b77b3..8584b85e 100644 --- a/tests/src/actionContainers/ActionProxyContainerTests.scala +++ b/tests/src/actionContainers/ActionProxyContainerTests.scala @@ -26,12 +26,8 @@ import org.scalatest.junit.JUnitRunner import ActionContainer.withContainer import common.TestUtils import common.WskActorSystem -import spray.json.JsArray -import spray.json.JsNull -import spray.json.JsNumber -import spray.json.JsObject -import spray.json.JsString -import spray.json.JsBoolean +import spray.json.DefaultJsonProtocol._ +import spray.json._ @RunWith(classOf[JUnitRunner]) class ActionProxyContainerTests extends BasicActionRunnerTests with WskActorSystem { @@ -81,20 +77,31 @@ class ActionProxyContainerTests extends BasicActionRunnerTests with WskActorSyst val stdEnvSamples = { val bash = """ |#!/bin/bash - |echo "{ \"auth\": \"$AUTH_KEY\", \"edge\": \"$EDGE_HOST\" }" + |echo "{ \ + |\"api_host\": \"$__OW_API_HOST\", \"api_key\": \"$__OW_API_KEY\", \ + |\"namespace\": \"$__OW_NAMESPACE\", \"action_name\": \"$__OW_ACTION_NAME\", \ + |\"activation_id\": \"$__OW_ACTIVATION_ID\", \"deadline\": \"$__OW_DEADLINE\" }" """.stripMargin.trim val python = """ |#!/usr/bin/env python |import os - |print '{ "auth": "%s", "edge": "%s" }' % (os.environ['AUTH_KEY'], os.environ['EDGE_HOST']) + | + |print '{ "api_host": "%s", "api_key": "%s", "namespace": "%s", "action_name" : "%s", "activation_id": "%s", "deadline": "%s" }' % ( + | os.environ['__OW_API_HOST'], os.environ['__OW_API_KEY'], + | os.environ['__OW_NAMESPACE'], os.environ['__OW_ACTION_NAME'], + | os.environ['__OW_ACTIVATION_ID'], os.environ['__OW_DEADLINE']) """.stripMargin.trim val perl = """ |#!/usr/bin/env perl - |$a = $ENV{'AUTH_KEY'}; - |$e = $ENV{'EDGE_HOST'}; - |print "{ \"auth\": \"$a\", \"edge\": \"$e\" }"; + |$a = $ENV{'__OW_API_HOST'}; + |$b = $ENV{'__OW_API_KEY'}; + |$c = $ENV{'__OW_NAMESPACE'}; + |$d = $ENV{'__OW_ACTION_NAME'}; + |$e = $ENV{'__OW_ACTIVATION_ID'}; + |$f = $ENV{'__OW_DEADLINE'}; + |print "{ \"api_host\": \"$a\", \"api_key\": \"$b\", \"namespace\": \"$c\", \"action_name\": \"$d\", \"activation_id\": \"$e\", \"deadline\": \"$f\" }"; """.stripMargin.trim // excluding perl as it not installed in alpine based image @@ -244,28 +251,37 @@ trait BasicActionRunnerTests extends ActionProxyContainerTestUtils { } /** Runs tests for code samples which are expected to return the expected standard environment {auth, edge}. */ - def testEnv(stdEnvSamples: Seq[(String, String)], enforceEmptyOutputStream: Boolean = true) = { + def testEnv(stdEnvSamples: Seq[(String, String)], enforceEmptyOutputStream: Boolean = true, enforceEmptyErrorStream: Boolean = true) = { for (s <- stdEnvSamples) { it should s"run a ${s._1} script and confirm expected environment variables" in { - val auth = JsString("abc") - val edge = "xyz" - val env = Map("EDGE_HOST" -> edge) - - val (out, err) = withActionContainer(env) { c => + val props = Seq( + "api_host" -> "xyz", + "api_key" -> "abc", + "namespace" -> "zzz", + "action_name" -> "xxx", + "activation_id" -> "iii", + "deadline" -> "123") + val env = props.map { case (k, v) => s"__OW_${k.toUpperCase()}" -> v } + + val (out, err) = withActionContainer(env.take(1).toMap) { c => val (initCode, _) = c.init(initPayload(s._2)) initCode should be(200) - val (runCode, out) = c.run(runPayload(JsObject(), Some(JsObject("authKey" -> auth)))) + val (runCode, out) = c.run(runPayload(JsObject(), Some(props.toMap.toJson.asJsObject))) runCode should be(200) out shouldBe defined - out.get.fields("auth") shouldBe auth - out.get.fields("edge") shouldBe JsString(edge) + props.map { + case (k, v) => withClue(k) { + out.get.fields(k) shouldBe JsString(v) + } + + } } checkStreams(out, err, { case (o, e) => if (enforceEmptyOutputStream) o shouldBe empty - e shouldBe empty + if (enforceEmptyErrorStream) e shouldBe empty }) } } diff --git a/tests/src/actionContainers/JavaActionContainerTests.scala b/tests/src/actionContainers/JavaActionContainerTests.scala index 77bfff7a..876f0b4b 100644 --- a/tests/src/actionContainers/JavaActionContainerTests.scala +++ b/tests/src/actionContainers/JavaActionContainerTests.scala @@ -20,6 +20,7 @@ import org.junit.runner.RunWith import org.scalatest.FlatSpec import org.scalatest.Matchers import org.scalatest.junit.JUnitRunner +import spray.json.DefaultJsonProtocol._ import spray.json._ import ActionContainer.withContainer @@ -28,19 +29,62 @@ import ResourceHelpers.JarBuilder import common.WskActorSystem @RunWith(classOf[JUnitRunner]) -class JavaActionContainerTests extends FlatSpec with Matchers with WskActorSystem { +class JavaActionContainerTests extends FlatSpec with Matchers with WskActorSystem with ActionProxyContainerTestUtils { // Helpers specific to javaaction - def withJavaContainer(code: ActionContainer => Unit) = withContainer("javaaction")(code) + def withJavaContainer(code: ActionContainer => Unit, env: Map[String, String] = Map.empty) = withContainer("javaaction", env)(code) def initPayload(mainClass: String, jar64: String) = JsObject( "value" -> JsObject( "name" -> JsString("dummyAction"), "main" -> JsString(mainClass), "jar" -> JsString(jar64))) - def runPayload(args: JsValue) = JsObject("value" -> args) behavior of "Java action" + it should s"run a java snippet and confirm expected environment variables" in { + val props = Seq("api_host" -> "xyz", + "api_key" -> "abc", + "namespace" -> "zzz", + "action_name" -> "xxx", + "activation_id" -> "iii", + "deadline" -> "123") + val env = props.map { case (k, v) => s"__OW_${k.toUpperCase}" -> v } + val (out, err) = withJavaContainer({ c => + val jar = JarBuilder.mkBase64Jar( + Seq("example", "HelloWhisk.java") -> """ + | package example; + | + | import com.google.gson.JsonObject; + | + | public class HelloWhisk { + | public static JsonObject main(JsonObject args) { + | JsonObject response = new JsonObject(); + | response.addProperty("api_host", System.getenv("__OW_API_HOST")); + | response.addProperty("api_key", System.getenv("__OW_API_KEY")); + | response.addProperty("namespace", System.getenv("__OW_NAMESPACE")); + | response.addProperty("action_name", System.getenv("__OW_ACTION_NAME")); + | response.addProperty("activation_id", System.getenv("__OW_ACTIVATION_ID")); + | response.addProperty("deadline", System.getenv("__OW_DEADLINE")); + | return response; + | } + | } + """.stripMargin.trim) + + val (initCode, _) = c.init(initPayload("example.HelloWhisk", jar)) + initCode should be(200) + + val (runCode, out) = c.run(runPayload(JsObject(), Some(props.toMap.toJson.asJsObject))) + runCode should be(200) + props.map { + case (k, v) => out.get.fields(k) shouldBe JsString(v) + + } + }, env.take(1).toMap) + + out.trim shouldBe empty + err.trim shouldBe empty + } + it should "support valid flows" in { val (out, err) = withJavaContainer { c => val jar = JarBuilder.mkBase64Jar( diff --git a/tests/src/actionContainers/NodeJsActionContainerTests.scala b/tests/src/actionContainers/NodeJsActionContainerTests.scala index 395449da..f33e3e26 100644 --- a/tests/src/actionContainers/NodeJsActionContainerTests.scala +++ b/tests/src/actionContainers/NodeJsActionContainerTests.scala @@ -31,6 +31,8 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys lazy val nodejsContainerImageName = "nodejsaction" + val hasDeprecationWarnings = true + override def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit) = { withContainer(nodejsContainerImageName, env)(code) } @@ -68,6 +70,21 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys """.stripMargin) }) + testEnv(Seq { + ("node", """ + |function main(args) { + | return { + | "api_host": process.env['__OW_API_HOST'], + | "api_key": process.env['__OW_API_KEY'], + | "namespace": process.env['__OW_NAMESPACE'], + | "action_name": process.env['__OW_ACTION_NAME'], + | "activation_id": process.env['__OW_ACTIVATION_ID'], + | "deadline": process.env['__OW_DEADLINE'] + | } + |} + """.stripMargin.trim) + }) + it should "fail to initialize with bad code" in { val (out, err) = withNodeJsContainer { c => val code = """ @@ -158,7 +175,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys checkStreams(out, err, { case (o, e) => o shouldBe empty - e shouldBe empty + if (!hasDeprecationWarnings) e shouldBe empty }) } @@ -180,7 +197,58 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys checkStreams(out, err, { case (o, e) => o shouldBe empty - e shouldBe empty + if (!hasDeprecationWarnings) e shouldBe empty + }) + } + + it should "warn when using deprecated whisk object methods" in { + val (out, err) = withNodeJsContainer { c => + val code = """ + | function main(args) { + | whisk.getAuthKey(whisk.setAuthKey('xxx')); + | try { whisk.invoke(); } catch (e) {} + | try { whisk.trigger(); } catch (e) {} + | setTimeout(function () { whisk.done(); }, 1000); + | return whisk.async(); + | } + """.stripMargin + + c.init(initPayload(code))._1 should be(200) + + val (runCode, runRes) = c.run(runPayload(JsObject())) + runCode should be(200) + } + + checkStreams(out, err, { + case (o, e) => + o shouldBe empty + e should not be empty + val lines = e.split("\n") + lines.filter { l => l.startsWith("[WARN] \"whisk.") && l.contains("deprecated") }.length shouldBe 8 + }) + } + + it should "warn when using deprecated whisk.error" in { + val (out, err) = withNodeJsContainer { c => + val code = """ + | function main(args) { + | whisk.error("{warnme: true}"); + | } + """.stripMargin + + c.init(initPayload(code))._1 should be(200) + + val (runCode, runRes) = c.run(runPayload(JsObject())) + runCode should be(200) + } + + checkStreams(out, err, { + case (o, e) => + o shouldBe empty + e should not be empty + val lines = e.split("\n") + lines.length shouldBe 1 + lines.forall { l => l.startsWith("[WARN] \"whisk.") && l.contains("deprecated") } }) } @@ -205,7 +273,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys checkStreams(out, err, { case (o, e) => o should include("more than once") - e shouldBe empty + if (!hasDeprecationWarnings) e shouldBe empty }) } @@ -243,7 +311,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys checkStreams(out, err, { case (o, e) => o shouldBe empty - e shouldBe empty + if (!hasDeprecationWarnings) e shouldBe empty }, 3) } @@ -268,7 +336,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys checkStreams(out, err, { case (o, e) => o shouldBe empty - e shouldBe empty + if (!hasDeprecationWarnings) e shouldBe empty }) } @@ -302,7 +370,7 @@ class NodeJsActionContainerTests extends BasicActionRunnerTests with WskActorSys checkStreams(out, err, { case (o, e) => o shouldBe empty - e shouldBe empty + if (!hasDeprecationWarnings) e shouldBe empty }, 2) } diff --git a/tests/src/actionContainers/PythonActionContainerTests.scala b/tests/src/actionContainers/PythonActionContainerTests.scala index fe17476c..41968bf2 100644 --- a/tests/src/actionContainers/PythonActionContainerTests.scala +++ b/tests/src/actionContainers/PythonActionContainerTests.scala @@ -53,7 +53,14 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys ("python", """ |import os |def main(dict): - | return { "auth": os.environ['AUTH_KEY'], "edge": os.environ['EDGE_HOST'] } + | return { + | "api_host": os.environ['__OW_API_HOST'], + | "api_key": os.environ['__OW_API_KEY'], + | "namespace": os.environ['__OW_NAMESPACE'], + | "action_name": os.environ['__OW_ACTION_NAME'], + | "activation_id": os.environ['__OW_ACTIVATION_ID'], + | "deadline": os.environ['__OW_DEADLINE'] + | } """.stripMargin.trim) }) diff --git a/tests/src/actionContainers/Swift3ActionContainerTests.scala b/tests/src/actionContainers/Swift3ActionContainerTests.scala index f1f5bbde..a294bea6 100644 --- a/tests/src/actionContainers/Swift3ActionContainerTests.scala +++ b/tests/src/actionContainers/Swift3ActionContainerTests.scala @@ -27,20 +27,8 @@ class Swift3ActionContainerTests extends SwiftActionContainerTests { override val enforceEmptyOutputStream = false override lazy val swiftContainerImageName = "swift3action" - override lazy val envCode = """ - |func main(args: [String: Any]) -> [String: Any] { - | let env = ProcessInfo.processInfo.environment - | var auth = "???" - | var edge = "???" - | if let authKey : String = env["AUTH_KEY"] { - | auth = "\(authKey)" - | } - | if let edgeHost : String = env["EDGE_HOST"] { - | edge = "\(edgeHost)" - | } - | return ["auth": auth, "edge": edge] - |} - """.stripMargin + override lazy val envCode = makeEnvCode("ProcessInfo.processInfo") + override lazy val errorCode = """ | // You need an indirection, or swiftc detects the div/0 | // at compile-time. Smart. diff --git a/tests/src/actionContainers/SwiftActionContainerTests.scala b/tests/src/actionContainers/SwiftActionContainerTests.scala index 44d965e2..cfe3ce2a 100644 --- a/tests/src/actionContainers/SwiftActionContainerTests.scala +++ b/tests/src/actionContainers/SwiftActionContainerTests.scala @@ -32,21 +32,38 @@ class SwiftActionContainerTests extends BasicActionRunnerTests with WskActorSyst // prints status messages and there doesn't seem to be a way to quiet them val enforceEmptyOutputStream = true lazy val swiftContainerImageName = "swiftaction" + lazy val envCode = makeEnvCode("NSProcessInfo.processInfo()") - lazy val envCode = """ + def makeEnvCode(processInfo: String) = (""" |func main(args: [String: Any]) -> [String: Any] { - | let env = NSProcessInfo.processInfo().environment - | var auth = "???" - | var edge = "???" - | if let authKey : String = env["AUTH_KEY"] { - | auth = "\(authKey)" + | let env = """+processInfo+""".environment + | var a = "???" + | var b = "???" + | var c = "???" + | var d = "???" + | var e = "???" + | var f = "???" + | if let v : String = env["__OW_API_HOST"] { + | a = "\(v)" | } - | if let edgeHost : String = env["EDGE_HOST"] { - | edge = "\(edgeHost)" + | if let v : String = env["__OW_API_KEY"] { + | b = "\(v)" | } - | return ["auth": auth, "edge": edge] + | if let v : String = env["__OW_NAMESPACE"] { + | c = "\(v)" + | } + | if let v : String = env["__OW_ACTION_NAME"] { + | d = "\(v)" + | } + | if let v : String = env["__OW_ACTIVATION_ID"] { + | e = "\(v)" + | } + | if let v : String = env["__OW_DEADLINE"] { + | f = "\(v)" + | } + | return ["api_host": a, "api_key": b, "namespace": c, "action_name": d, "activation_id": e, "deadline": f] |} - """.stripMargin + """).stripMargin lazy val errorCode = """ | // You need an indirection, or swiftc detects the div/0 diff --git a/tests/src/system/basic/CLIPythonTests.scala b/tests/src/system/basic/CLIPythonTests.scala index 2f00ca64..891745bc 100644 --- a/tests/src/system/basic/CLIPythonTests.scala +++ b/tests/src/system/basic/CLIPythonTests.scala @@ -72,7 +72,7 @@ class CLIPythonTests it should "invoke an invalid action and get error back" in withAssetCleaner(wskprops) { (wp, assetHelper) => - val name = "basicInvoke" + val name = "bad code" assetHelper.withCleaner(wsk.action, name) { (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("malformed.py"))) } diff --git a/tests/src/system/basic/WskActionTests.scala b/tests/src/system/basic/WskActionTests.scala index 1cc8e8e0..524eb206 100644 --- a/tests/src/system/basic/WskActionTests.scala +++ b/tests/src/system/basic/WskActionTests.scala @@ -45,6 +45,22 @@ class WskActionTests behavior of "Whisk actions" + it should "invoke an action returning a promise" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + val name = "hello promise" + assetHelper.withCleaner(wsk.action, name) { + (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloPromise.js"))) + } + + val run = wsk.action.invoke(name) + withActivation(wsk.activation, run) { + activation => + activation.response.status shouldBe "success" + activation.response.result shouldBe Some(JsObject("done" -> true.toJson)) + activation.logs.get.mkString(" ") shouldBe empty + } + } + it should "invoke an action with a space in the name" in withAssetCleaner(wskprops) { (wp, assetHelper) => val name = "hello Async" @@ -169,7 +185,7 @@ class WskActionTests wsk.parseJsonString(rr.stdout).getFieldPath("exec", "code") shouldBe Some(JsString("")) } - it should "blocking invoke nested blocking actions" in withAssetCleaner(wskprops) { + it should "blocking invoke of nested blocking actions" in withAssetCleaner(wskprops) { (wp, assetHelper) => val name = "nestedBlockingAction" val child = "wc" diff --git a/tests/src/system/basic/WskBasicNodeTests.scala b/tests/src/system/basic/WskBasicNodeTests.scala index 4e5fdca1..d78c7d62 100644 --- a/tests/src/system/basic/WskBasicNodeTests.scala +++ b/tests/src/system/basic/WskBasicNodeTests.scala @@ -113,6 +113,7 @@ class WskBasicNodeTests } } + // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary it should "Ensure that whisk.invoke() returns a promise" in withAssetCleaner(wskprops) { val expectedDuration = 3.seconds @@ -165,6 +166,7 @@ class WskBasicNodeTests } } + // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary it should "Ensure that whisk.invoke() still uses a callback when provided one" in withAssetCleaner(wskprops) { val expectedDuration = 3.seconds @@ -216,6 +218,7 @@ class WskBasicNodeTests } } + // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary it should "Ensure that whisk.trigger() still uses a callback when provided one" in withAssetCleaner(wskprops) { (wp, assetHelper) => // this action supplies a 'next' callback to whisk.trigger() @@ -251,6 +254,7 @@ class WskBasicNodeTests } } + // TODO: remove this tests and its assets when "whisk.js" is removed entirely as it is no longer necessary it should "Ensure that whisk.trigger() returns a promise" in withAssetCleaner(wskprops) { (wp, assetHelper) => // this action supplies a 'next' callback to whisk.trigger() diff --git a/tests/src/system/basic/WskBasicTests.scala b/tests/src/system/basic/WskBasicTests.scala index 36c661dc..1acfa408 100644 --- a/tests/src/system/basic/WskBasicTests.scala +++ b/tests/src/system/basic/WskBasicTests.scala @@ -16,19 +16,15 @@ package system.basic -import java.time.Instant import java.io.File +import java.time.Instant import org.junit.runner.RunWith import org.scalatest.junit.JUnitRunner import common.TestHelpers import common.TestUtils -import common.TestUtils.CONFLICT -import common.TestUtils.SUCCESS_EXIT -import common.TestUtils.UNAUTHORIZED -import common.TestUtils.FORBIDDEN -import common.TestUtils.ERROR_EXIT +import common.TestUtils._ import common.Wsk import common.WskProps import common.WskTestHelpers @@ -361,15 +357,17 @@ class WskBasicTests } } - it should "create and invoke a blocking action resulting in an error response object" in withAssetCleaner(wskprops) { + it should "create and invoke a blocking action resulting in an failed promise" in withAssetCleaner(wskprops) { (wp, assetHelper) => val name = "errorResponseObject" assetHelper.withCleaner(wsk.action, name) { (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("asyncError.js"))) } - wsk.action.invoke(name, blocking = true, expectedExitCode = 246) - .stderr should include regex (""""error": "name '!' contains illegal characters \(code \d+\)"""") + val stderr = wsk.action.invoke(name, blocking = true, expectedExitCode = 246).stderr + CliActivation.serdes.read(stderr.parseJson).response.result shouldBe Some { + JsObject("error" -> JsObject("msg" -> "failed activation on purpose".toJson)) + } } it should "invoke a blocking action and get only the result" in withAssetCleaner(wskprops) { diff --git a/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala b/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala index e832c112..0bf4198b 100644 --- a/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala +++ b/tests/src/whisk/core/cli/test/WskBasicUsageTests.scala @@ -45,6 +45,8 @@ import whisk.core.entity.size.SizeInt import whisk.utils.retry import JsonArgsForTests._ import whisk.http.Messages +import common.WskAdmin +import java.time.Clock /** * Tests for basic CLI usage. Some of these tests require a deployed backend. @@ -452,6 +454,63 @@ class WskBasicUsageTests } } + it should "invoke an action using npm openwhisk" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + val name = "hello npm openwhisk" + assetHelper.withCleaner(wsk.action, name, confirmDelete = false) { + (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloOpenwhiskPackage.js"))) + } + + val run = wsk.action.invoke(name, Map("ignore_certs" -> true.toJson, "name" -> name.toJson)) + withActivation(wsk.activation, run) { + activation => + activation.response.status shouldBe "success" + activation.response.result shouldBe Some(JsObject("delete" -> true.toJson)) + activation.logs.get.mkString(" ") should include("action list has this many actions") + } + + wsk.action.delete(name, expectedExitCode = TestUtils.NOT_FOUND) + } + + it should "invoke an action receiving context properties" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + val user = WskAdmin.getUser(wskprops.authKey) + val name = "context" + assetHelper.withCleaner(wsk.action, name) { + (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloContext.js"))) + } + + val start = Instant.now(Clock.systemUTC()).toEpochMilli + val run = wsk.action.invoke(name) + withActivation(wsk.activation, run) { + activation => + activation.response.status shouldBe "success" + val fields = activation.response.result.get.convertTo[Map[String, String]] + fields("api_host") shouldBe WhiskProperties.getEdgeHost + ":" + WhiskProperties.getEdgeHostApiPort + fields("api_key") shouldBe wskprops.authKey + fields("namespace") shouldBe user + fields("action_name") shouldBe s"/$user/$name" + fields("activation_id") shouldBe activation.activationId + fields("deadline").toLong should be >= start + } + } + + it should s"invoke an action that returns a result by the deadline" in withAssetCleaner(wskprops) { + (wp, assetHelper) => + val name = "deadline" + assetHelper.withCleaner(wsk.action, name) { + (action, _) => action.create(name, Some(TestUtils.getTestActionFilename("helloDeadline.js")), timeout = Some(3 seconds)) + } + + val start = Instant.now(Clock.systemUTC()).toEpochMilli + val run = wsk.action.invoke(name) + withActivation(wsk.activation, run) { + activation => + activation.response.status shouldBe "success" + activation.response.result shouldBe Some(JsObject("timedout" -> true.toJson)) + } + } + behavior of "Wsk packages" it should "create, and get a package to verify parameter and annotation parsing" in withAssetCleaner(wskprops) { -- GitLab