blob: 97e62278fcd446c549e88f37e00cc62831979c69 [file] [log] [blame]
/*
* 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")))
}
}
}