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 2018/02/10 02:31:38 UTC

[incubator-openwhisk-runtime-swift] branch master updated: stop clone and own for actionproxy (#18)

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-swift.git


The following commit(s) were added to refs/heads/master by this push:
     new 8d2e23c  stop clone and own for actionproxy (#18)
8d2e23c is described below

commit 8d2e23cd92d1c082380b5465c2fb34ee3e42288d
Author: Carlos Santana <cs...@gmail.com>
AuthorDate: Fri Feb 9 21:31:36 2018 -0500

    stop clone and own for actionproxy (#18)
---
 core/actionProxy/actionproxy.py              | 283 ---------------------------
 core/actionProxy/invoke.py                   | 102 ----------
 core/swift3.1.1Action/Dockerfile             |   3 +-
 core/swift3.1.1Action/build.gradle           |  12 --
 core/swift4Action/Dockerfile                 |   3 +-
 core/swift4Action/build.gradle               |  11 --
 tests/src/test/scala/sdk/SwiftSDKTests.scala |  12 ++
 7 files changed, 14 insertions(+), 412 deletions(-)

diff --git a/core/actionProxy/actionproxy.py b/core/actionProxy/actionproxy.py
deleted file mode 100644
index 7021615..0000000
--- a/core/actionProxy/actionproxy.py
+++ /dev/null
@@ -1,283 +0,0 @@
-"""Executable Python script for a proxy service to dockerSkeleton.
-
-Provides a proxy service (using Flask, a Python web microframework)
-that implements the required /init and /run routes to interact with
-the OpenWhisk invoker service.
-
-The implementation of these routes is encapsulated in a class named
-ActionRunner which provides a basic framework for receiving code
-from an invoker, preparing it for execution, and then running the
-code when required.
-
-/*
- * 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.
- */
-"""
-
-import sys
-import os
-import json
-import subprocess
-import codecs
-import flask
-from gevent.wsgi import WSGIServer
-import zipfile
-import io
-import base64
-
-
-class ActionRunner:
-    """ActionRunner."""
-    LOG_SENTINEL = 'XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX'
-
-    # initializes the runner
-    # @param source the path where the source code will be located (if any)
-    # @param binary the path where the binary will be located (may be the
-    # same as source code path)
-    def __init__(self, source=None, binary=None, zipdest=None):
-        defaultBinary = '/action/exec'
-        self.source = source if source else defaultBinary
-        self.binary = binary if binary else defaultBinary
-        self.zipdest = zipdest if zipdest else os.path.dirname(self.source)
-
-    def preinit(self):
-        return
-
-    # extracts from the JSON object message a 'code' property and
-    # writes it to the <source> path. The source code may have an
-    # an optional <epilogue>. The source code is subsequently built
-    # to produce the <binary> that is executed during <run>.
-    # @param message is a JSON object, should contain 'code'
-    # @return True iff binary exists and is executable
-    def init(self, message):
-        def prep():
-            self.preinit()
-            if 'code' in message and message['code'] is not None:
-                binary = message['binary'] if 'binary' in message else False
-                if not binary:
-                    return self.initCodeFromString(message)
-                else:
-                    return self.initCodeFromZip(message)
-            else:
-                return False
-
-        if prep():
-            try:
-                # write source epilogue if any
-                # the message is passed along as it may contain other
-                # fields relevant to a specific container.
-                if self.epilogue(message) is False:
-                    return False
-                # build the source
-                if self.build(message) is False:
-                    return False
-            except Exception:
-                return False
-        # verify the binary exists and is executable
-        return self.verify()
-
-    # optionally appends source to the loaded code during <init>
-    def epilogue(self, init_arguments):
-        return
-
-    # optionally builds the source code loaded during <init> into an executable
-    def build(self, init_arguments):
-        return
-
-    # @return True iff binary exists and is executable, False otherwise
-    def verify(self):
-        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 '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
-        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.
-    # @param args is a JSON object representing the input to the action
-    # @param env is the environment for the action to run in (defined edge
-    # host, auth key)
-    # return JSON object result of running the action or an error dictionary
-    # if action failed
-    def run(self, args, env):
-        def error(msg):
-            # fall through (exception and else case are handled the same way)
-            sys.stdout.write('%s\n' % msg)
-            return (502, {'error': 'The action did not return a dictionary.'})
-
-        try:
-            input = json.dumps(args)
-            if len(input) > 131071:             # MAX_ARG_STRLEN (131071) linux/binfmts.h
-                # pass argument via stdin
-                p = subprocess.Popen(
-                    [self.binary],
-                    stdin=subprocess.PIPE,
-                    stdout=subprocess.PIPE,
-                    stderr=subprocess.PIPE,
-                    env=env)
-            else:
-                # pass argument via stdin and command parameter
-                p = subprocess.Popen(
-                    [self.binary, input],
-                    stdin=subprocess.PIPE,
-                    stdout=subprocess.PIPE,
-                    stderr=subprocess.PIPE,
-                    env=env)
-            # run the process and wait until it completes.
-            # stdout/stderr will always be set because we passed PIPEs to Popen
-            (o, e) = p.communicate(input=input.encode())
-
-        except Exception as e:
-            return error(e)
-
-        # stdout/stderr may be either text or bytes, depending on Python
-        # version, so if bytes, decode to text. Note that in Python 2
-        # a string will match both types; so also skip decoding in that case
-        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')
-
-        # get the last line of stdout, even if empty
-        lastNewLine = o.rfind('\n', 0, len(o)-1)
-        if lastNewLine != -1:
-            # this is the result string to JSON parse
-            lastLine = o[lastNewLine+1:].strip()
-            # emit the rest as logs to stdout (including last new line)
-            sys.stdout.write(o[:lastNewLine+1])
-        else:
-            # either o is empty or it is the result string
-            lastLine = o.strip()
-
-        if e:
-            sys.stderr.write(e)
-
-        try:
-            json_output = json.loads(lastLine)
-            if isinstance(json_output, dict):
-                return (200, json_output)
-            else:
-                return error(lastLine)
-        except Exception:
-            return error(lastLine)
-
-    # initialize code from inlined string
-    def initCodeFromString(self, message):
-        with codecs.open(self.source, 'w', 'utf-8') as fp:
-            fp.write(message['code'])
-        return True
-
-    # initialize code from base64 encoded archive
-    def initCodeFromZip(self, message):
-        try:
-            bytes = base64.b64decode(message['code'])
-            bytes = io.BytesIO(bytes)
-            archive = zipfile.ZipFile(bytes)
-            archive.extractall(self.zipdest)
-            archive.close()
-            return True
-        except Exception as e:
-            print('err', str(e))
-            return False
-
-proxy = flask.Flask(__name__)
-proxy.debug = False
-runner = None
-
-
-def setRunner(r):
-    global runner
-    runner = r
-
-
-@proxy.route('/init', methods=['POST'])
-def init():
-    message = flask.request.get_json(force=True, silent=True)
-    if message and not isinstance(message, dict):
-        flask.abort(404)
-    else:
-        value = message.get('value', {}) if message else {}
-
-    if not isinstance(value, dict):
-        flask.abort(404)
-
-    try:
-        status = runner.init(value)
-    except Exception as e:
-        status = False
-
-    if status is True:
-        return ('OK', 200)
-    else:
-        response = flask.jsonify({'error': 'The action failed to generate or locate a binary. See logs for details.'})
-        response.status_code = 502
-        return complete(response)
-
-
-@proxy.route('/run', methods=['POST'])
-def run():
-    def error():
-        response = flask.jsonify({'error': 'The action did not receive a dictionary as an argument.'})
-        response.status_code = 404
-        return complete(response)
-
-    message = flask.request.get_json(force=True, silent=True)
-    if message and not isinstance(message, dict):
-        return error()
-    else:
-        args = message.get('value', {}) if message else {}
-        if not isinstance(args, dict):
-            return error()
-
-    if runner.verify():
-        try:
-            code, result = runner.run(args, runner.env(message or {}))
-            response = flask.jsonify(result)
-            response.status_code = code
-        except Exception as e:
-            response = flask.jsonify({'error': 'Internal error. {}'.format(e)})
-            response.status_code = 500
-    else:
-        response = flask.jsonify({'error': 'The action failed to locate a binary. See logs for details.'})
-        response.status_code = 502
-    return complete(response)
-
-
-def complete(response):
-    # Add sentinel to stdout/stderr
-    sys.stdout.write('%s\n' % ActionRunner.LOG_SENTINEL)
-    sys.stdout.flush()
-    sys.stderr.write('%s\n' % ActionRunner.LOG_SENTINEL)
-    sys.stderr.flush()
-    return response
-
-
-def main():
-    port = int(os.getenv('FLASK_PROXY_PORT', 8080))
-    server = WSGIServer(('', port), proxy, log=None)
-    server.serve_forever()
-
-if __name__ == '__main__':
-    setRunner(ActionRunner())
-    main()
diff --git a/core/actionProxy/invoke.py b/core/actionProxy/invoke.py
deleted file mode 100755
index 92d8a6d..0000000
--- a/core/actionProxy/invoke.py
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/usr/bin/env python
-"""Executable Python script for testing the action proxy.
-
-  This script is useful for testing the action proxy (or its derivatives)
-  by simulating invoker interactions. Use it in combination with
-  delete-build-run.sh which builds and starts up the action proxy.
-  Examples:
-     ./delete-build-run.sh &
-     ./invoke.py init <action source file> # should return OK
-     ./invoke.py run '{"some":"json object as a string"}'
-/*
- * 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.
- */
-"""
-
-import os
-import re
-import sys
-import json
-import requests
-import codecs
-
-DOCKER_HOST = "localhost"
-if "DOCKER_HOST" in os.environ:
-    try:
-        DOCKER_HOST = re.compile("tcp://(.*):[\d]+").findall(
-            os.environ["DOCKER_HOST"])[0]
-    except Exception:
-        print("cannot determine docker host from %s" %
-              os.environ["DOCKER_HOST"])
-        sys.exit(-1)
-DEST = "http://%s:8080" % DOCKER_HOST
-
-
-def content_from_args(args):
-    if len(args) == 0:
-        return None
-
-    if len(args) == 1 and os.path.exists(args[0]):
-        with open(args[0]) as fp:
-            return json.load(fp)
-
-    # else...
-    in_str = " ".join(args)
-    try:
-        d = json.loads(in_str)
-        if isinstance(d, dict):
-            return d
-        else:
-            raise "Not a dict."
-    except:
-        return in_str
-
-
-def init(args):
-    main = args[1] if len(args) == 2 else "main"
-    args = args[0] if len(args) >= 1 else None
-
-    if args and args.endswith(".zip"):
-        with open(args, "rb") as fp:
-            contents = fp.read().encode("base64")
-        binary = True
-    elif args:
-        with(codecs.open(args, "r", "utf-8")) as fp:
-            contents = fp.read()
-        binary = False
-    else:
-        contents = None
-        binary = False
-
-    r = requests.post("%s/init" % DEST, json={"value": {"code": contents,
-                                                        "binary": binary,
-                                                        "main": main}})
-    print(r.text)
-
-
-def run(args):
-    value = content_from_args(args)
-    # print("Sending value: %s..." % json.dumps(value)[0:40])
-    r = requests.post("%s/run" % DEST, json={"value": value})
-    print(r.text)
-
-
-if sys.argv[1] == "init":
-    init(sys.argv[2:])
-elif sys.argv[1] == "run":
-    run(sys.argv[2:])
-else:
-    print("usage: 'init <filename>' or 'run JSON-as-string'")
diff --git a/core/swift3.1.1Action/Dockerfile b/core/swift3.1.1Action/Dockerfile
index 352767f..d378947 100755
--- a/core/swift3.1.1Action/Dockerfile
+++ b/core/swift3.1.1Action/Dockerfile
@@ -10,8 +10,7 @@ RUN apt-get -y update \
  && apt-get -y install --fix-missing python2.7 python-gevent python-flask zip
 
 # Add the action proxy
-RUN mkdir -p /actionProxy
-ADD actionproxy.py /actionProxy
+ADD https://raw.githubusercontent.com/apache/incubator-openwhisk-runtime-docker/dockerskeleton%401.1.0/core/actionProxy/actionproxy.py /actionProxy/actionproxy.py
 
 # Add files needed to build and run action
 RUN mkdir -p /swift3Action
diff --git a/core/swift3.1.1Action/build.gradle b/core/swift3.1.1Action/build.gradle
index c977a30..ecc3760 100755
--- a/core/swift3.1.1Action/build.gradle
+++ b/core/swift3.1.1Action/build.gradle
@@ -1,14 +1,2 @@
 ext.dockerImageName = 'action-swift-v3.1.1'
 apply from: '../../gradle/docker.gradle'
-distDocker.dependsOn 'copyProxy'
-distDocker.finalizedBy('cleanup')
-
-task copyProxy(type: Copy) {
-    from '../actionProxy/actionproxy.py'
-    into './actionproxy.py'
-}
-
-task cleanup(type: Delete) {
-    delete 'actionproxy.py'
-    delete 'swift3runner.py'
-}
diff --git a/core/swift4Action/Dockerfile b/core/swift4Action/Dockerfile
index 71a28ac..f14159d 100755
--- a/core/swift4Action/Dockerfile
+++ b/core/swift4Action/Dockerfile
@@ -10,8 +10,7 @@ RUN apt-get -y update \
  && apt-get -y install --fix-missing python2.7 python-gevent python-flask zip
 
 # Add the action proxy
-RUN mkdir -p /actionProxy
-ADD actionproxy.py /actionProxy
+ADD https://raw.githubusercontent.com/apache/incubator-openwhisk-runtime-docker/dockerskeleton%401.1.0/core/actionProxy/actionproxy.py /actionProxy/actionproxy.py
 
 # Add files needed to build and run action
 RUN mkdir -p /swift4Action/spm-build/Sources/Action
diff --git a/core/swift4Action/build.gradle b/core/swift4Action/build.gradle
index 9ad1f58..d4357c5 100755
--- a/core/swift4Action/build.gradle
+++ b/core/swift4Action/build.gradle
@@ -1,13 +1,2 @@
 ext.dockerImageName = 'action-swift-v4'
 apply from: '../../gradle/docker.gradle'
-distDocker.dependsOn 'copyProxy'
-distDocker.finalizedBy('cleanup')
-
-task copyProxy(type: Copy) {
-    from '../actionProxy/actionproxy.py'
-    into '.'
-}
-
-task cleanup(type: Delete) {
-    delete 'actionproxy.py'
-}
diff --git a/tests/src/test/scala/sdk/SwiftSDKTests.scala b/tests/src/test/scala/sdk/SwiftSDKTests.scala
index ad77386..14fcbee 100644
--- a/tests/src/test/scala/sdk/SwiftSDKTests.scala
+++ b/tests/src/test/scala/sdk/SwiftSDKTests.scala
@@ -98,10 +98,22 @@ abstract class SwiftSDKTests extends TestHelpers with WskTestHelpers with Matche
   it should "allow Swift actions to trigger events" in withAssetCleaner(wskprops) { (wp, assetHelper) =>
     // create a trigger
     val triggerName = s"TestTrigger ${System.currentTimeMillis()}"
+    val ruleName = s"TestTriggerRule ${System.currentTimeMillis()}"
+    val ruleActionName = s"TestTriggerAction ${System.currentTimeMillis()}"
     assetHelper.withCleaner(wsk.trigger, triggerName) { (trigger, _) =>
       trigger.create(triggerName)
     }
 
+    // create a dummy action
+    assetHelper.withCleaner(wsk.action, ruleActionName) { (action, name) =>
+      val dummyFile = Some(new File(actionTypeDir, "hello.swift").toString())
+      action.create(name, dummyFile, kind = Some(actionKind))
+    }
+    // create a dummy rule
+    assetHelper.withCleaner(wsk.rule, ruleName) { (rule, name) =>
+      rule.create(name, trigger = triggerName, action = ruleActionName)
+    }
+
     // create an action that fires the trigger
     val file = Some(new File(actionTypeDir, "trigger.swift").toString())
     val actionName = "ActionThatTriggers"

-- 
To stop receiving notification emails like this one, please contact
csantanapr@apache.org.