| /* |
| * 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. |
| */ |
| |
| package runtime.actionContainers |
| |
| import java.io.File |
| import java.util.Base64 |
| |
| import org.apache.commons.io.FileUtils |
| import org.junit.runner.RunWith |
| import org.scalatest.junit.JUnitRunner |
| |
| import actionContainers.{ActionContainer, BasicActionRunnerTests} |
| import actionContainers.ActionContainer.withContainer |
| import common.TestUtils |
| import common.WskActorSystem |
| import spray.json._ |
| |
| object CodeSamples { |
| val codeNotReturningJson = """ |
| |#!/bin/sh |
| |echo not a json object |
| """.stripMargin.trim |
| |
| /** Standard code samples, should print 'hello' to stdout and echo the input args. */ |
| val stdCodeSamples = { |
| val bash = """ |
| |#!/bin/bash |
| |echo 'hello stdout' |
| |echo 'hello stderr' 1>&2 |
| |if [[ -z $1 || $1 == '{}' ]]; then |
| | echo '{ "msg": "Hello from bash script!" }' |
| |else |
| | echo $1 # echo the arguments back as the result |
| |fi |
| """.stripMargin.trim |
| |
| val python = """ |
| |#!/usr/bin/env python |
| |from __future__ import print_function |
| |import sys |
| |print('hello stdout') |
| |print('hello stderr', file=sys.stderr) |
| |print(sys.argv[1]) |
| """.stripMargin.trim |
| |
| val perl = """ |
| |#!/usr/bin/env perl |
| |print STDOUT "hello stdout\n"; |
| |print STDERR "hello stderr\n"; |
| |print $ARGV[0]; |
| """.stripMargin.trim |
| |
| Seq(("bash", bash), ("python", python), ("perl", perl)) |
| } |
| |
| val stdUnicodeSamples = { |
| // python 3 in base image |
| val python = """ |
| |#!/usr/bin/env python |
| |import json, sys |
| |j = json.loads(sys.argv[1]) |
| |sep = j["delimiter"] |
| |s = sep + " ☃ " + sep |
| |print(s) |
| |print(json.dumps({"winter": s})) |
| """.stripMargin.trim |
| |
| Seq(("python", python)) |
| } |
| |
| /** Standard code samples, should print 'hello' to stdout and echo the input args. */ |
| val stdEnvSamples = { |
| val bash = |
| """ |
| |#!/bin/bash |
| |echo "{ \ |
| |\"api_host\": \"$__OW_API_HOST\", \"api_key\": \"$__OW_API_KEY\", \ |
| |\"namespace\": \"$__OW_NAMESPACE\", \"action_name\": \"$__OW_ACTION_NAME\", \"action_version\": \"$__OW_ACTION_VERSION\", \ |
| |\"activation_id\": \"$__OW_ACTIVATION_ID\", \"deadline\": \"$__OW_DEADLINE\" }" |
| """.stripMargin.trim |
| |
| val python = |
| """ |
| |#!/usr/bin/env python |
| |import os |
| | |
| |print('{ "api_host": "%s", "api_key": "%s", "namespace": "%s", "action_name" : "%s", action_version" : "%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_ACTION_VERSION'], |
| | os.environ['__OW_ACTIVATION_ID'], os.environ['__OW_DEADLINE'])) |
| """.stripMargin.trim |
| |
| val perl = |
| """ |
| |#!/usr/bin/env perl |
| |$a = $ENV{'__OW_API_HOST'}; |
| |$b = $ENV{'__OW_API_KEY'}; |
| |$c = $ENV{'__OW_NAMESPACE'}; |
| |$d = $ENV{'__OW_ACTION_NAME'}; |
| |$r = $ENV{'__OW_ACTION_VERSION'}; |
| |$e = $ENV{'__OW_ACTIVATION_ID'}; |
| |$f = $ENV{'__OW_DEADLINE'}; |
| |print "{ \"api_host\": \"$a\", \"api_key\": \"$b\", \"namespace\": \"$c\", \"action_name\": \"$d\", \"action_version\": \"$r\", \"activation_id\": \"$e\", \"deadline\": \"$f\" }"; |
| """.stripMargin.trim |
| |
| Seq(("bash", bash), ("python", python), ("perl", perl)) |
| } |
| |
| /** Large param samples, echo the input args with input larger than 128K and using STDIN */ |
| val stdLargeInputSamples = { |
| val bash = """ |
| |#!/bin/bash |
| | read inputstring |
| | echo $inputstring |
| """.stripMargin.trim |
| |
| val python = """ |
| |#!/usr/bin/env python |
| |import sys, json |
| |params = sys.stdin.readline() |
| |j = json.loads(params) |
| |print(json.dumps(j)) |
| """.stripMargin.trim |
| |
| val perl = """ |
| |#!/usr/bin/env perl |
| |$params=<STDIN>; |
| |print $params; |
| """.stripMargin.trim |
| |
| Seq(("bash", bash), ("python", python), ("perl", perl)) |
| } |
| } |
| |
| @RunWith(classOf[JUnitRunner]) |
| class ActionProxyContainerTests extends BasicActionRunnerTests with WskActorSystem { |
| |
| override def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit) = { |
| withContainer("dockerskeleton", env)(code) |
| } |
| |
| override val testNoSourceOrExec = TestConfig("", hasCodeStub = true) |
| override val testNotReturningJson = TestConfig(CodeSamples.codeNotReturningJson, enforceEmptyOutputStream = false) |
| override val testInitCannotBeCalledMoreThanOnce = TestConfig(CodeSamples.codeNotReturningJson) |
| // the skeleton requires the executable to be called /action/exec, this test will pass with any "main" |
| override val testEntryPointOtherThanMain = |
| TestConfig(CodeSamples.stdLargeInputSamples(0)._2, main = "exec", false, true) |
| override val testEcho = TestConfig(CodeSamples.stdCodeSamples(0)._2) |
| override val testUnicode = TestConfig(CodeSamples.stdUnicodeSamples(0)._2) |
| override val testEnv = TestConfig(CodeSamples.stdEnvSamples(0)._2) |
| override val testLargeInput = TestConfig(CodeSamples.stdLargeInputSamples(0)._2) |
| |
| behavior of "openwhisk/dockerskeleton" |
| |
| it should "run sample without init" in { |
| val (out, err) = withActionContainer() { c => |
| val (runCode, out) = c.run(JsObject()) |
| runCode should be(200) |
| out should be(Some(JsObject("error" -> JsString("This is a stub action. Replace it with custom logic.")))) |
| } |
| |
| checkStreams(out, err, { |
| case (o, _) => o should include("This is a stub action") |
| }) |
| } |
| |
| it should "run sample with 'null' init" in { |
| val (out, err) = withActionContainer() { c => |
| val (initCode, _) = c.init(initPayload(null)) |
| initCode should be(200) |
| |
| val (runCode, out) = c.run(JsObject()) |
| runCode should be(200) |
| out should be(Some(JsObject("error" -> JsString("This is a stub action. Replace it with custom logic.")))) |
| } |
| |
| checkStreams(out, err, { |
| case (o, _) => o should include("This is a stub action") |
| }) |
| } |
| |
| it should "run sample with init that does nothing" in { |
| val (out, err) = withActionContainer() { c => |
| val (initCode, _) = c.init(JsObject()) |
| initCode should be(200) |
| val (runCode, out) = c.run(JsObject()) |
| runCode should be(200) |
| out should be(Some(JsObject("error" -> JsString("This is a stub action. Replace it with custom logic.")))) |
| } |
| |
| checkStreams(out, err, { |
| case (o, _) => o should include("This is a stub action") |
| }) |
| } |
| |
| it should "respond with 404 for bad run argument" in { |
| val (out, err) = withActionContainer() { c => |
| val (runCode, out) = c.run(runPayload(JsString("A"))) |
| runCode should be(404) |
| } |
| |
| checkStreams(out, err, { |
| case (o, e) => |
| o shouldBe empty |
| e shouldBe empty |
| }) |
| } |
| |
| it should "fail to run a bad script" in { |
| val (out, err) = withActionContainer() { c => |
| val (initCode, _) = c.init(initPayload("")) |
| initCode should be(200) |
| val (runCode, out) = c.run(JsNull) |
| runCode should be(502) |
| out should be(Some(JsObject("error" -> JsString("The action did not return a dictionary or array.")))) |
| } |
| |
| checkStreams(out, err, { |
| case (o, _) => o should include("error") |
| }) |
| } |
| |
| it should "extract and run a compatible zip exec" in { |
| val zip = FileUtils.readFileToByteArray(new File(TestUtils.getTestActionFilename("blackbox.zip"))) |
| val contents = Base64.getEncoder.encodeToString(zip) |
| |
| val (out, err) = withActionContainer() { c => |
| val (initCode, err) = |
| c.init(JsObject("value" -> JsObject("code" -> JsString(contents), "binary" -> JsBoolean(true)))) |
| initCode should be(200) |
| val (runCode, out) = c.run(JsObject()) |
| runCode should be(200) |
| out.get should be(JsObject("msg" -> JsString("hello zip"))) |
| } |
| |
| checkStreams(out, err, { |
| case (o, e) => |
| o shouldBe "This is an example zip used with the docker skeleton action." |
| e shouldBe empty |
| }) |
| } |
| |
| it should "support current directory be action location" in { |
| withActionContainer() { c => |
| val code = """ |
| |#!/bin/bash |
| |echo "{\"pwd_env\":\"$PWD\",\"pwd_cmd\":\"$(pwd)\"}" |
| """.stripMargin.trim |
| |
| val (initCode, initRes) = c.init(initPayload(code)) |
| initCode should be(200) |
| |
| val (_, runRes) = c.run(runPayload(JsObject())) |
| runRes.get.fields.get("pwd_env") shouldBe Some(JsString("/action")) |
| runRes.get.fields.get("pwd_cmd") shouldBe Some(JsString("/action")) |
| } |
| } |
| |
| it should "support return array result" in { |
| withActionContainer() { c => |
| val code = """ |
| |#!/bin/bash |
| |echo '["a", "b"]' |
| """.stripMargin.trim |
| |
| val (initCode, initRes) = c.init(initPayload(code)) |
| initCode should be(200) |
| |
| val (runCode, runRes) = c.runForJsArray(runPayload(JsObject())) |
| runCode should be(200) |
| runRes shouldBe Some(JsArray(JsString("a"), JsString("b"))) |
| } |
| } |
| |
| it should "support array as input param" in { |
| withActionContainer() { c => |
| val code = """ |
| |#!/bin/bash |
| |arr=$1 |
| |echo $arr |
| """.stripMargin.trim |
| |
| val (initCode, initRes) = c.init(initPayload(code)) |
| initCode should be(200) |
| |
| val (runCode, runRes) = c.runForJsArray(runPayload(JsArray(JsString("a"), JsString("b")))) |
| runCode should be(200) |
| runRes shouldBe Some(JsArray(JsString("a"), JsString("b"))) |
| } |
| } |
| } |