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 2019/01/14 03:00:20 UTC

[incubator-openwhisk-runtime-python] branch master updated (6558390 -> d6a8920)

This is an automated email from the ASF dual-hosted git repository.

csantanapr pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-runtime-python.git.


    from 6558390  Adds a new AI Action kind
     new 8ee2d2d  python actionloop v3.7
     new d6a8920  enable actionllop tests

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .travis.yml                                        |   8 +-
 core/{pythonAction => pythonActionLoop}/Dockerfile |  29 +--
 .../build.gradle                                   |   2 +-
 core/pythonActionLoop/pythonbuild.py               | 110 ++++++++
 core/pythonActionLoop/pythonbuild.py.launcher.py   |  72 ++++++
 settings.gradle                                    |   2 +
 .../PythonActionContainerTests.scala               | 285 +++++++++++++--------
 ....scala => PythonActionLoopContainerTests.scala} |  13 +-
 tools/travis/publish.sh                            |   2 +
 9 files changed, 385 insertions(+), 138 deletions(-)
 copy core/{pythonAction => pythonActionLoop}/Dockerfile (68%)
 copy core/{pythonAction => pythonActionLoop}/build.gradle (94%)
 create mode 100755 core/pythonActionLoop/pythonbuild.py
 create mode 100755 core/pythonActionLoop/pythonbuild.py.launcher.py
 copy tests/src/test/scala/runtime/actionContainers/{Python2ActionContainerTests.scala => PythonActionLoopContainerTests.scala} (72%)


[incubator-openwhisk-runtime-python] 01/02: python actionloop v3.7

Posted by cs...@apache.org.
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-runtime-python.git

commit 8ee2d2dac536141bfcd8989d9fdab9495bed7b4f
Author: Michele Sciabarra <sc...@sciabarra.com>
AuthorDate: Sun Dec 23 16:29:47 2018 +0100

    python actionloop v3.7
---
 .travis.yml                                        |   6 +
 core/pythonActionLoop/Dockerfile                   |  42 +++
 .../pythonActionLoop/build.gradle                  |  23 +-
 core/pythonActionLoop/pythonbuild.py               | 110 +++++++
 core/pythonActionLoop/pythonbuild.py.launcher.py   |  72 +++++
 settings.gradle                                    |   2 +
 .../PythonActionContainerTests.scala               | 345 ++++++++++++---------
 .../PythonActionLoopContainerTests.scala           |  30 +-
 8 files changed, 450 insertions(+), 180 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 52d92d7..968b66d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,6 +8,11 @@ scala:
 - 2.12.7
 services:
   - docker
+# required to support multi-stage build
+addons:
+   apt:
+     packages:
+       - docker-ce
 before_install:
   - "./tools/travis/setup.sh"
 install: true
@@ -39,3 +44,4 @@ notifications:
     urls:
       # travis2slack webhook to enable DMs on openwhisk-team.slack.com to PR authors with TravisCI results
       secure: "jhiMGpQ6kJFWjjsO68RmgD2Lga7jgNE+EKwND0dMOvzf5llMLFDKcY5J3tgtrqYaslQdXeuYeru/9qJrTTjFEu+vz3iCwoJ/eme+D0TtTIFGlPr7oa9tZlWrkPM/0zFLq7KjJauIIX2+6qrGVrNJJ6ENfr4U8Ir8q51oLIk44bsCeB8EmkahPOlNG6kcNqgpxHWKYUdUIg3B0GxqCKida/76dXDTRHCV2dZuT2bXz2oSJYog/lybomsjQIUZj0+HqxecgWTzag3Y6rTpK+m+vywazHP91hE+oU4e7YrxCH6v9+ukoWaljFqO5ZEKXcpx6tzx8Q0FvoTP8vGOO9b/t1loVcA8OxSJDrtOAztfoz/u0HJN6vnVt+maqnrYAD1F4pxA63JA6/+a7firmtADP7A/WQMZg6RgEkGUr+amFn303dTvgjDDkZ4oH8MAr0EPsneGUA2MZgB3i1MEcnCrYzT7KpYmD [...]
+
diff --git a/core/pythonActionLoop/Dockerfile b/core/pythonActionLoop/Dockerfile
new file mode 100644
index 0000000..b2daade
--- /dev/null
+++ b/core/pythonActionLoop/Dockerfile
@@ -0,0 +1,42 @@
+#
+# 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.
+#
+FROM openwhisk/actionloop:latest as builder
+
+FROM python:3.7-stretch
+
+# Install common modules for python
+RUN pip install \
+    beautifulsoup4==4.6.3 \
+    httplib2==0.11.3 \
+    kafka_python==1.4.3 \
+    lxml==4.2.5 \
+    python-dateutil==2.7.3 \
+    requests==2.19.1 \
+    scrapy==1.5.1 \
+    simplejson==3.16.0 \
+    virtualenv==16.0.0 \
+    twisted==18.7.0
+
+RUN mkdir -p /action
+WORKDIR /
+COPY --from=builder /bin/proxy /bin/proxy
+ADD pythonbuild.py /bin/compile
+ADD pythonbuild.py.launcher.py /bin/compile.launcher.py
+ENV OW_COMPILER=/bin/compile
+ENTRYPOINT []
+CMD ["/bin/proxy"]
+
diff --git a/settings.gradle b/core/pythonActionLoop/build.gradle
similarity index 63%
copy from settings.gradle
copy to core/pythonActionLoop/build.gradle
index cec472b..2e4226b 100644
--- a/settings.gradle
+++ b/core/pythonActionLoop/build.gradle
@@ -15,24 +15,5 @@
  * limitations under the License.
  */
 
-include 'tests'
-
-include 'core:pythonAction'
-include 'core:python2Action'
-include 'core:python3AiAction'
-
-rootProject.name = 'runtime-python'
-
-gradle.ext.openwhisk = [
-        version: '1.0.0-SNAPSHOT'
-]
-
-gradle.ext.scala = [
-    version: '2.12.7',
-    compileFlags: ['-feature', '-unchecked', '-deprecation', '-Xfatal-warnings', '-Ywarn-unused-import']
-]
-
-gradle.ext.scalafmt = [
-    version: '1.5.0',
-    config: new File(rootProject.projectDir, '.scalafmt.conf')
-]
+ext.dockerImageName = 'actionloop-python-v3.7'
+apply from: '../../gradle/docker.gradle'
diff --git a/core/pythonActionLoop/pythonbuild.py b/core/pythonActionLoop/pythonbuild.py
new file mode 100755
index 0000000..30802c2
--- /dev/null
+++ b/core/pythonActionLoop/pythonbuild.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+"""Python Action Compiler
+#
+# 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.
+#
+"""
+
+from __future__ import print_function
+import os
+import sys
+import codecs
+import subprocess
+
+
+def copy(src, dst):
+    with codecs.open(src, 'r', 'utf-8') as s:
+        body = s.read()
+        with codecs.open(dst, 'w', 'utf-8') as d:
+            d.write(body)
+
+# if there is an exec copy to main__.py
+# else if there is a __main__.py copy to main__.py
+# (exec prevails over __main__.py)
+# then copy the launcher in exec__.py replacing the main function
+def sources(launcher, source_dir, main):
+    # source and dest
+    src = "%s/exec" % source_dir
+    dst = "%s/main__.py" % source_dir
+    # copy exec to main__.py
+    if os.path.isfile(src):
+        copy(src,dst)
+    else:
+        # renaming __main__ to main__
+        src = "%s/__main__.py" % source_dir
+        if os.path.isfile(src):
+            copy(src, dst)
+
+    # copy a launcher
+    starter = "%s/exec__.py" % source_dir
+    with codecs.open(launcher, 'r', 'utf-8') as s:
+        with codecs.open(starter, 'w', 'utf-8') as d:
+            body = s.read()
+            body = body.replace("from main__ import main as main",
+                                "from main__ import %s as main" % main)
+            d.write(body)
+    return starter
+
+# build the launcher but only if there is the main
+def build(source_dir, target_file, launcher):
+    main = "%s/main__.py" % source_dir
+    cmd = "#!/bin/bash"
+    if os.path.isfile(main):
+        cmd += """
+cd %s
+exec python %s "$@"
+""" % (source_dir, launcher)
+    else:
+        cmd += """
+echo "Zip file does not include mandatory files."
+"""
+    with codecs.open(target_file, 'w', 'utf-8') as d:
+        d.write(cmd)
+    os.chmod(target_file, 0o755)
+
+def compile(argv):
+    if len(argv) < 4:
+        sys.stdout.write("usage: <main-function> <source-dir> <target-dir>\n")
+        sys.exit(1)
+
+    main = argv[1]
+    source_dir = os.path.abspath(argv[2])
+    target_file = os.path.abspath("%s/exec" % argv[3])
+    launcher = os.path.abspath(argv[0]+".launcher.py")
+    starter = sources(launcher, source_dir, main)
+    build(source_dir, target_file, starter)
+    sys.stdout.flush()
+    sys.stderr.flush()
+    return target_file
+
+
+if __name__ == '__main__':
+    p = subprocess.Popen([compile(sys.argv), "exit"],
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE)
+    (o, e) = p.communicate()
+    if isinstance(o, bytes) and not isinstance(o, str):
+        o = o.decode('utf-8')
+    if isinstance(e, bytes) and not isinstance(e, str):
+        e = e.decode('utf-8')
+    if o:
+        sys.stdout.write(o)
+        sys.stdout.flush()
+
+    if e:
+        sys.stderr.write(e)
+        sys.stderr.flush()
+
diff --git a/core/pythonActionLoop/pythonbuild.py.launcher.py b/core/pythonActionLoop/pythonbuild.py.launcher.py
new file mode 100755
index 0000000..b7007c9
--- /dev/null
+++ b/core/pythonActionLoop/pythonbuild.py.launcher.py
@@ -0,0 +1,72 @@
+#
+# 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.
+#
+from __future__ import print_function
+from sys import stdin
+from sys import stdout
+from sys import stderr
+from os import fdopen
+import sys, os, json, traceback
+
+try:
+  # if the directory 'virtualenv' is extracted out of a zip file
+  path_to_virtualenv = os.path.abspath('./virtualenv')
+  if os.path.isdir(path_to_virtualenv):
+    # activate the virtualenv using activate_this.py contained in the virtualenv
+    activate_this_file = path_to_virtualenv + '/bin/activate_this.py'
+    if os.path.exists(activate_this_file):
+      with open(activate_this_file) as f:
+        code = compile(f.read(), activate_this_file, 'exec')
+        exec(code, dict(__file__=activate_this_file))
+    else:
+      sys.stderr.write('Invalid virtualenv. Zip file does not include /virtualenv/bin/' + os.path.basename(activate_this_file) + '\n')
+      sys.exit(1)
+except Exception:
+  traceback.print_exc(file=sys.stderr, limit=0)
+  sys.exit(1)
+
+# now import the action as process input/output
+from main__ import main as main
+
+# if there are some arguments exit immediately
+if len(sys.argv) >1:
+  sys.stderr.flush()
+  sys.stdout.flush()
+  sys.exit(0)
+
+env = os.environ
+out = fdopen(3, "wb")
+while True:
+  line = stdin.readline()
+  if not line: break
+  args = json.loads(line)
+  payload = {}
+  for key in args:
+    if key == "value":
+      payload = args["value"]
+    else:
+      env["__OW_%s" % key.upper()]= args[key]
+  res = {}
+  try:
+    res = main(payload)
+  except Exception as ex:
+    print(traceback.format_exc(), file=stderr)
+    res = {"error": str(ex)}
+  out.write(json.dumps(res, ensure_ascii=False).encode('utf-8'))
+  out.write(b'\n')
+  stdout.flush()
+  stderr.flush()
+  out.flush()
diff --git a/settings.gradle b/settings.gradle
index cec472b..7bec58b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,6 +20,8 @@ include 'tests'
 include 'core:pythonAction'
 include 'core:python2Action'
 include 'core:python3AiAction'
+include 'core:pythonActionLoop'
+
 
 rootProject.name = 'runtime-python'
 
diff --git a/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala b/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
index a706938..29333bc 100644
--- a/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
+++ b/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
@@ -36,6 +36,9 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
   /** indicates if strings in python are unicode by default (i.e., python3 -> true, python2.7 -> false) */
   lazy val pythonStringAsUnicode = true
 
+  /** indicates if errors are logged or returned in the answer */
+  lazy val initErrorsAreLogged = true
+
   override def withActionContainer(env: Map[String, String] = Map.empty)(code: ActionContainer => Unit) = {
     withContainer(imageName, env)(code)
   }
@@ -47,15 +50,15 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
   override val testNotReturningJson =
     TestConfig("""
-                 |def main(args):
-                 |    return "not a json object"
-               """.stripMargin)
+        |def main(args):
+        |    return "not a json object"
+      """.stripMargin)
 
   override val testInitCannotBeCalledMoreThanOnce =
     TestConfig("""
-                 |def main(args):
-                 |    return args
-               """.stripMargin)
+        |def main(args):
+        |    return args
+      """.stripMargin)
 
   override val testEntryPointOtherThanMain =
     TestConfig(
@@ -67,13 +70,13 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
   override val testEcho =
     TestConfig("""
-      |from __future__ import print_function
-      |import sys
-      |def main(args):
-      |    print('hello stdout')
-      |    print('hello stderr', file=sys.stderr)
-      |    return args
-    """.stripMargin)
+        |from __future__ import print_function
+        |import sys
+        |def main(args):
+        |    print('hello stdout')
+        |    print('hello stderr', file=sys.stderr)
+        |    return args
+      """.stripMargin)
 
   override val testUnicode =
     TestConfig(if (pythonStringAsUnicode) {
@@ -96,17 +99,17 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
   override val testEnv =
     TestConfig("""
-      |import os
-      |def main(dict):
-      |    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)
+        |import os
+        |def main(dict):
+        |    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)
 
   override val testLargeInput =
     TestConfig("""
@@ -116,17 +119,20 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
   it should "support zip-encoded action using non-default entry points" in {
     val srcs = Seq(
-      Seq("__main__.py") -> """
-                |from echo import echo
-                |def niam(args):
-                |    return echo(args)
-            """.stripMargin,
-      Seq("echo.py") -> """
-                |def echo(args):
-                |  return { "echo": args }
-            """.stripMargin)
+      Seq("__main__.py") ->
+        """
+          |from echo import echo
+          |def niam(args):
+          |    return echo(args)
+        """.stripMargin,
+      Seq("echo.py") ->
+        """
+          |def echo(args):
+          |  return { "echo": args }
+        """.stripMargin)
 
     val code = ZipBuilder.mkBase64Zip(srcs)
+    println(code)
 
     val (out, err) = withActionContainer() { c =>
       val (initCode, initRes) = c.init(initPayload(code, main = "niam"))
@@ -148,11 +154,12 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
   it should "support zip-encoded action which can read from relative paths" in {
     val srcs = Seq(
-      Seq("__main__.py") -> """
-                |def main(args):
-                |    f = open('workfile', 'r')
-                |    return {'file': f.read()}
-            """.stripMargin,
+      Seq("__main__.py") ->
+        """
+          |def main(args):
+          |    f = open('workfile', 'r')
+          |    return {'file': f.read()}
+        """.stripMargin,
       Seq("workfile") -> "this is a test string")
 
     val code = ZipBuilder.mkBase64Zip(srcs)
@@ -176,85 +183,115 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
   }
 
   it should "report error if zip-encoded action does not include required file" in {
-    val srcs = Seq(Seq("echo.py") -> """
-                |def echo(args):
-                |  return { "echo": args }
-            """.stripMargin)
+    val srcs = Seq(
+      Seq("echo.py") ->
+        """
+        |def echo(args):
+        |  return { "echo": args }
+      """.stripMargin)
 
     val code = ZipBuilder.mkBase64Zip(srcs)
 
     val (out, err) = withActionContainer() { c =>
       val (initCode, initRes) = c.init(initPayload(code, main = "echo"))
       initCode should be(502)
+      if (!initErrorsAreLogged)
+        initRes.get.fields.get("error").get.toString() should include("Zip file does not include")
     }
 
-    checkStreams(out, err, {
-      case (o, e) =>
-        o shouldBe empty
-        e should include("Zip file does not include")
-    })
+    if (initErrorsAreLogged)
+      checkStreams(out, err, {
+        case (o, e) =>
+          o shouldBe empty
+          e should include("Zip file does not include")
+      })
   }
-
-  it should "run zipped Python action containing a virtual environment" in {
-    val zippedPythonAction = if (imageName == "python2action") "python2_virtualenv.zip" else "python3_virtualenv.zip"
-    val zippedPythonActionName = TestUtils.getTestActionFilename(zippedPythonAction)
-    val code = readAsBase64(Paths.get(zippedPythonActionName))
-
-    val (out, err) = withActionContainer() { c =>
-      val (initCode, initRes) = c.init(initPayload(code, main = "main"))
-      initCode should be(200)
-      val args = JsObject("msg" -> JsString("any"))
-      val (runCode, runRes) = c.run(runPayload(args))
-      runCode should be(200)
-      runRes.get.toString() should include("netmask")
+  /*
+    it should "run zipped Python action containing a virtual environment" in {
+      val zippedPythonAction = if (imageName == "python2action") "python2_virtualenv.zip" else "python3_virtualenv.zip"
+      val zippedPythonActionName = TestUtils.getTestActionFilename(zippedPythonAction)
+      val code = readAsBase64(Paths.get(zippedPythonActionName))
+
+      val (out, err) = withActionContainer() { c =>
+        val (initCode, initRes) = c.init(initPayload(code, main = "main"))
+        initCode should be(200)
+        val args = JsObject("msg" -> JsString("any"))
+        val (runCode, runRes) = c.run(runPayload(args))
+        runCode should be(200)
+        runRes.get.toString() should include("netmask")
+      }
+      checkStreams(out, err, {
+        case (o, e) =>
+          o should include("netmask")
+          e shouldBe empty
+      })
     }
-    checkStreams(out, err, {
-      case (o, e) =>
-        o should include("netmask")
-        e shouldBe empty
-    })
-  }
+   */
 
   it should "run zipped Python action containing a virtual environment with non-standard entry point" in {
-    val zippedPythonAction = if (imageName == "python2action") "python2_virtualenv.zip" else "python3_virtualenv.zip"
+    val zippedPythonAction =
+      if (imageName == "python2action") "python2_virtualenv.zip"
+      else if (imageName == "actionloop-python-v3.7") "python37_virtualenv.zip"
+      else "python3_virtualenv.zip"
     val zippedPythonActionName = TestUtils.getTestActionFilename(zippedPythonAction)
-    val code = readAsBase64(Paths.get(zippedPythonActionName))
 
-    val (out, err) = withActionContainer() { c =>
-      val (initCode, initRes) = c.init(initPayload(code, main = "naim"))
-      initCode should be(200)
-      val args = JsObject("msg" -> JsString("any"))
-      val (runCode, runRes) = c.run(runPayload(args))
-      runCode should be(200)
-      runRes.get.toString() should include("netmask")
+    // temporary guard to comment out this test
+    // until python37_virtualenv.zip is available in main repo
+    if (initErrorsAreLogged) {
+      val code = readAsBase64(Paths.get(zippedPythonActionName))
+      val (out, err) = withActionContainer() { c =>
+        val (initCode, initRes) = c.init(initPayload(code, main = "naim"))
+        initCode should be(200)
+        val args = JsObject("msg" -> JsString("any"))
+        val (runCode, runRes) = c.run(runPayload(args))
+        runCode should be(200)
+        runRes.get.toString() should include("netmask")
+      }
+      checkStreams(out, err, {
+        case (o, e) =>
+          o should include("netmask")
+          e shouldBe empty
+      })
     }
-    checkStreams(out, err, {
-      case (o, e) =>
-        o should include("netmask")
-        e shouldBe empty
-    })
   }
 
   it should "report error if zipped Python action containing a virtual environment for wrong python version" in {
-    val zippedPythonAction = if (imageName.contains("python3")) "python2_virtualenv.zip" else "python3_virtualenv.zip"
+    val zippedPythonAction = if (imageName == "python2action") "python3_virtualenv.zip" else "python2_virtualenv.zip"
     val zippedPythonActionName = TestUtils.getTestActionFilename(zippedPythonAction)
+
     val code = readAsBase64(Paths.get(zippedPythonActionName))
 
-    val (out, err) = withActionContainer() { c =>
-      val (initCode, initRes) = c.init(initPayload(code, main = "main"))
-      initCode should be(200)
-      val args = JsObject("msg" -> JsString("any"))
-      val (runCode, runRes) = c.run(runPayload(args))
-      runCode should be {
-        if (imageName == "python3aiaction") 200 else 502
+    // temporary guard to comment out this test for python3aiaction
+    // until it is fixed (it does not detect the wrong virtual env)
+    if (imageName != "python3aiaction") {
+      val (out, err) = withActionContainer() { c =>
+        val (initCode, initRes) = c.init(initPayload(code, main = "main"))
+        if (initErrorsAreLogged) {
+          initCode should be(200)
+          val args = JsObject("msg" -> JsString("any"))
+          val (runCode, runRes) = c.run(runPayload(args))
+          runCode should be(502)
+        } else {
+          // it actually means it is actionloop
+          // it checks the error at init time
+          initCode should be(502)
+          initRes.get.fields.get("error").get.toString() should include("No module")
+        }
       }
+      if (initErrorsAreLogged)
+        checkStreams(
+          out,
+          err, {
+            case (o, e) =>
+              o shouldBe empty
+              if (imageName == "python2action") {
+                e should include("ImportError")
+              }
+              if (imageName == "python3action") {
+                e should include("ModuleNotFoundError")
+              }
+          })
     }
-    checkStreams(out, err, {
-      case (o, e) =>
-        if (imageName != "python3aiaction") { o shouldBe empty }
-        if (imageName == "python2action") { e should include("ImportError") }
-        if (imageName == "python3action") { e should include("ModuleNotFoundError") }
-    })
   }
 
   it should "report error if zipped Python action has wrong main module name" in {
@@ -265,12 +302,15 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
     val (out, err) = withActionContainer() { c =>
       val (initCode, initRes) = c.init(initPayload(code, main = "main"))
       initCode should be(502)
+      if (!initErrorsAreLogged)
+        initRes.get.fields.get("error").get.toString() should include("Zip file does not include mandatory files")
     }
-    checkStreams(out, err, {
-      case (o, e) =>
-        o shouldBe empty
-        e should include("Zip file does not include __main__.py")
-    })
+    if (initErrorsAreLogged)
+      checkStreams(out, err, {
+        case (o, e) =>
+          o shouldBe empty
+          e should include("Zip file does not include __main__.py")
+      })
   }
 
   it should "report error if zipped Python action has invalid virtualenv directory" in {
@@ -280,29 +320,38 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
     val (out, err) = withActionContainer() { c =>
       val (initCode, initRes) = c.init(initPayload(code, main = "main"))
       initCode should be(502)
+      if (!initErrorsAreLogged)
+        initRes.get.fields.get("error").get.toString() should include("Invalid virtualenv. Zip file does not include")
     }
-    checkStreams(out, err, {
-      case (o, e) =>
-        o shouldBe empty
-        e should include("Zip file does not include /virtualenv/bin/")
-    })
+    if (initErrorsAreLogged)
+      checkStreams(out, err, {
+        case (o, e) =>
+          o shouldBe empty
+          e should include("Zip file does not include /virtualenv/bin/")
+      })
   }
 
   it should "return on action error when action fails" in {
     val (out, err) = withActionContainer() { c =>
-      val code = """
-                |def div(x, y):
-                |    return x/y
-                |
-                |def main(dict):
-                |    return {"divBy0": div(5,0)}
-            """.stripMargin
+      val code =
+        """
+          |def div(x, y):
+          |    return x/y
+          |
+          |def main(dict):
+          |    return {"divBy0": div(5,0)}
+        """.stripMargin
 
       val (initCode, _) = c.init(initPayload(code))
       initCode should be(200)
 
       val (runCode, runRes) = c.run(runPayload(JsObject()))
-      runCode should be(502)
+      /* ActionLoop does not set 502 if there are application errors
+       * Since it only receive a string from the application
+       * it should parse the entire string  in JSON just to find it is an "error"
+       */
+      if (initErrorsAreLogged)
+        runCode should be(502)
 
       runRes shouldBe defined
       runRes.get.fields.get("error") shouldBe defined
@@ -317,29 +366,31 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
   it should "log compilation errors" in {
     val (out, err) = withActionContainer() { c =>
-      val code = """
-              | 10 PRINT "Hello!"
-              | 20 GOTO 10
-            """.stripMargin
+      val code =
+        """
+          | 10 PRINT "Hello!"
+          | 20 GOTO 10
+        """.stripMargin
 
       val (initCode, res) = c.init(initPayload(code))
       // init checks whether compilation was successful, so return 502
       initCode should be(502)
     }
-
-    checkStreams(out, err, {
-      case (o, e) =>
-        o shouldBe empty
-        e should include("Traceback")
-    })
+    if (initErrorsAreLogged)
+      checkStreams(out, err, {
+        case (o, e) =>
+          o shouldBe empty
+          e should include("Traceback")
+      })
   }
 
   it should "support application errors" in {
     val (out, err) = withActionContainer() { c =>
-      val code = """
-                |def main(args):
-                |    return { "error": "sorry" }
-            """.stripMargin
+      val code =
+        """
+          |def main(args):
+          |    return { "error": "sorry" }
+        """.stripMargin
 
       val (initCode, _) = c.init(initPayload(code))
       initCode should be(200)
@@ -360,23 +411,31 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
 
   it should "error when importing a not-supported package" in {
     val (out, err) = withActionContainer() { c =>
-      val code = """
-                |import iamnotsupported
-                |def main(args):
-                |    return { "error": "not reaching here" }
-            """.stripMargin
-
-      val (initCode, res) = c.init(initPayload(code))
-      initCode should be(200)
-
-      val (runCode, runRes) = c.run(runPayload(JsObject()))
-      runCode should be(502)
+      val code =
+        """
+          |import iamnotsupported
+          |def main(args):
+          |    return { "error": "not reaching here" }
+        """.stripMargin
+
+      if (initErrorsAreLogged) {
+        val (initCode, res) = c.init(initPayload(code))
+        initCode should be(200)
+
+        val (runCode, runRes) = c.run(runPayload(JsObject()))
+        runCode should be(502)
+      } else {
+        // action loop detects those errors at init time
+        val (initCode, initRes) = c.init(initPayload(code))
+        initCode should be(502)
+        initRes.get.fields.get("error").get.toString() should include("Traceback")
+      }
     }
-
-    checkStreams(out, err, {
-      case (o, e) =>
-        o shouldBe empty
-        e should include("Traceback")
-    })
+    if (initErrorsAreLogged)
+      checkStreams(out, err, {
+        case (o, e) =>
+          o shouldBe empty
+          e should include("Traceback")
+      })
   }
 }
diff --git a/settings.gradle b/tests/src/test/scala/runtime/actionContainers/PythonActionLoopContainerTests.scala
similarity index 54%
copy from settings.gradle
copy to tests/src/test/scala/runtime/actionContainers/PythonActionLoopContainerTests.scala
index cec472b..56a2dce 100644
--- a/settings.gradle
+++ b/tests/src/test/scala/runtime/actionContainers/PythonActionLoopContainerTests.scala
@@ -15,24 +15,22 @@
  * limitations under the License.
  */
 
-include 'tests'
+package runtime.actionContainers
 
-include 'core:pythonAction'
-include 'core:python2Action'
-include 'core:python3AiAction'
+import common.WskActorSystem
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
 
-rootProject.name = 'runtime-python'
+@RunWith(classOf[JUnitRunner])
+class PythonActionLoopContainerTests extends PythonActionContainerTests with WskActorSystem {
 
-gradle.ext.openwhisk = [
-        version: '1.0.0-SNAPSHOT'
-]
+  override lazy val imageName = "actionloop-python-v3.7"
 
-gradle.ext.scala = [
-    version: '2.12.7',
-    compileFlags: ['-feature', '-unchecked', '-deprecation', '-Xfatal-warnings', '-Ywarn-unused-import']
-]
+  override val testNoSource = TestConfig("", hasCodeStub = false)
 
-gradle.ext.scalafmt = [
-    version: '1.5.0',
-    config: new File(rootProject.projectDir, '.scalafmt.conf')
-]
+  /** indicates if strings in python are unicode by default (i.e., python3 -> true, python2.7 -> false) */
+  override lazy val pythonStringAsUnicode = true
+
+  /** actionloop based image does not log init errors - return the error in the body */
+  override lazy val initErrorsAreLogged = false
+}


[incubator-openwhisk-runtime-python] 02/02: enable actionllop tests

Posted by cs...@apache.org.
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-runtime-python.git

commit d6a89200ccc34028bbce8dc64bb72f515d432ac3
Author: Carlos Santana <cs...@apache.org>
AuthorDate: Sun Jan 13 21:34:41 2019 -0500

    enable actionllop tests
---
 .travis.yml                                        |  2 +-
 .../PythonActionContainerTests.scala               | 72 +++++++++++-----------
 tools/travis/publish.sh                            |  2 +
 3 files changed, 39 insertions(+), 37 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 968b66d..74beae5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,7 +26,7 @@ deploy:
       all_branches: true
       repo: apache/incubator-openwhisk-runtime-python
   - provider: script
-    script: "./tools/travis/publish.sh openwhisk 2 latest && ./tools/travis/publish.sh openwhisk 3 latest && ./tools/travis/publish.sh openwhisk 3-ai latest"
+    script: "./tools/travis/publish.sh openwhisk 2 latest && ./tools/travis/publish.sh openwhisk 3 latest && ./tools/travis/publish.sh openwhisk 3-ai latest && ./tools/travis/publish.sh openwhisk 3-loop latest"
     on:
       branch: master
       repo: apache/incubator-openwhisk-runtime-python
diff --git a/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala b/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
index 29333bc..edc376c 100644
--- a/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
+++ b/tests/src/test/scala/runtime/actionContainers/PythonActionContainerTests.scala
@@ -206,27 +206,30 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
           e should include("Zip file does not include")
       })
   }
-  /*
-    it should "run zipped Python action containing a virtual environment" in {
-      val zippedPythonAction = if (imageName == "python2action") "python2_virtualenv.zip" else "python3_virtualenv.zip"
-      val zippedPythonActionName = TestUtils.getTestActionFilename(zippedPythonAction)
-      val code = readAsBase64(Paths.get(zippedPythonActionName))
 
-      val (out, err) = withActionContainer() { c =>
-        val (initCode, initRes) = c.init(initPayload(code, main = "main"))
-        initCode should be(200)
-        val args = JsObject("msg" -> JsString("any"))
-        val (runCode, runRes) = c.run(runPayload(args))
-        runCode should be(200)
-        runRes.get.toString() should include("netmask")
-      }
-      checkStreams(out, err, {
-        case (o, e) =>
-          o should include("netmask")
-          e shouldBe empty
-      })
+  it should "run zipped Python action containing a virtual environment" in {
+    val zippedPythonAction =
+      if (imageName == "python2action") "python2_virtualenv.zip"
+      else if (imageName == "actionloop-python-v3.7") "python37_virtualenv.zip"
+      else "python3_virtualenv.zip"
+    val zippedPythonActionName = TestUtils.getTestActionFilename(zippedPythonAction)
+    val code = readAsBase64(Paths.get(zippedPythonActionName))
+
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, initRes) = c.init(initPayload(code, main = "main"))
+      initCode should be(200)
+      val args = JsObject("msg" -> JsString("any"))
+      val (runCode, runRes) = c.run(runPayload(args))
+      runCode should be(200)
+      runRes.get.toString() should include("netmask")
     }
-   */
+
+    checkStreams(out, err, {
+      case (o, e) =>
+        o should include("netmask")
+        e shouldBe empty
+    })
+  }
 
   it should "run zipped Python action containing a virtual environment with non-standard entry point" in {
     val zippedPythonAction =
@@ -235,24 +238,21 @@ class PythonActionContainerTests extends BasicActionRunnerTests with WskActorSys
       else "python3_virtualenv.zip"
     val zippedPythonActionName = TestUtils.getTestActionFilename(zippedPythonAction)
 
-    // temporary guard to comment out this test
-    // until python37_virtualenv.zip is available in main repo
-    if (initErrorsAreLogged) {
-      val code = readAsBase64(Paths.get(zippedPythonActionName))
-      val (out, err) = withActionContainer() { c =>
-        val (initCode, initRes) = c.init(initPayload(code, main = "naim"))
-        initCode should be(200)
-        val args = JsObject("msg" -> JsString("any"))
-        val (runCode, runRes) = c.run(runPayload(args))
-        runCode should be(200)
-        runRes.get.toString() should include("netmask")
-      }
-      checkStreams(out, err, {
-        case (o, e) =>
-          o should include("netmask")
-          e shouldBe empty
-      })
+    val code = readAsBase64(Paths.get(zippedPythonActionName))
+    val (out, err) = withActionContainer() { c =>
+      val (initCode, initRes) = c.init(initPayload(code, main = "naim"))
+      initCode should be(200)
+      val args = JsObject("msg" -> JsString("any"))
+      val (runCode, runRes) = c.run(runPayload(args))
+      runCode should be(200)
+      runRes.get.toString() should include("netmask")
     }
+    checkStreams(out, err, {
+      case (o, e) =>
+        o should include("netmask")
+        e shouldBe empty
+    })
+
   }
 
   it should "report error if zipped Python action containing a virtual environment for wrong python version" in {
diff --git a/tools/travis/publish.sh b/tools/travis/publish.sh
index 9368b7a..e18795d 100755
--- a/tools/travis/publish.sh
+++ b/tools/travis/publish.sh
@@ -36,6 +36,8 @@ elif [ ${RUNTIME_VERSION} == "3" ]; then
   RUNTIME="pythonAction"
 elif [ ${RUNTIME_VERSION} == "3-ai" ]; then
   RUNTIME="python3AiAction"
+elif [ ${RUNTIME_VERSION} == "3-loop" ]; then
+  RUNTIME="pythonActionLoop"
 fi
 
 if [[ ! -z ${DOCKER_USER} ]] && [[ ! -z ${DOCKER_PASSWORD} ]]; then