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

mrutkowski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk.git


The following commit(s) were added to refs/heads/master by this push:
     new e9583fd  Touch-up Action loop markdown adjusting grammar/flow (#4469)
e9583fd is described below

commit e9583fd2e3a48ea211d976deeb13b3019866310e
Author: Matt Rutkowski <mrutk...@us.ibm.com>
AuthorDate: Wed May 8 17:52:59 2019 -0500

    Touch-up Action loop markdown adjusting grammar/flow (#4469)
    
    * Touch-up pass on ActionLoop docs.
    
    * Touch-up pass on ActionLoop docs.
    
    * Touch-up pass on ActionLoop docs.
    
    * Touch-up pass on ActionLoop docs.
    
    * Touch-up pass on ActionLoop docs.
    
    * Touch-up pass on ActionLoop docs.
    
    * Touch-up pass on ActionLoop docs.
    
    * complete update of launcher xlate to ruby instructions
    
    * complete update of launcher xlate to ruby instructions
---
 docs/actions-actionloop.md | 213 +++++++++++++++++++++++++--------------------
 1 file changed, 120 insertions(+), 93 deletions(-)

diff --git a/docs/actions-actionloop.md b/docs/actions-actionloop.md
index 87bd7be..62f5dc3 100644
--- a/docs/actions-actionloop.md
+++ b/docs/actions-actionloop.md
@@ -17,73 +17,82 @@
 # limitations under the License.
 #
 -->
+
 # Developing a new Runtime with the ActionLoop proxy
 
-The [runtime specification](actions-new.md) defines the expected behavior of a 
runtime. You can implement a runtime from scratch just following the spec.
+The [runtime specification](actions-new.md) defines the expected behavior of a 
runtime. You can implement a runtime from scratch just following the 
specification.
 
-However, the fastest way to develop a new runtime is reusing the *ActionLoop* 
proxy, that already implements the specification and provides just a few hooks 
to get a fully functional (and *fast*) runtime in a few hours.
+However, the fastest way to develop a new runtime is reusing the *ActionLoop* 
proxy that already implements the specification and provides just a few hooks 
to get a fully functional (and *fast*) runtime in a few hours or less.
 
 ## What is the ActionLoop proxy
 
-The ActionLoop proxy is a runtime "engine", written in Go, originally 
developed specifically to support the Go language. However it was written in a 
pretty generic way, and it has been then adopted also to implement runtimes for 
Swift, PHP, Python, Rust, Java, Ruby and Crystal. Even though it was developed 
with compiled languages in mind it works equally well with scripting languages.
-
-Using it, you can develop a new runtime in a fraction of the time needed for a 
full-fledged runtime, since you have only to write a command line protocol and 
not a fully featured web server, with an amount of corner case to take care.
+The ActionLoop proxy is a runtime "engine", written in the [Go programming 
language](https://golang.org/), originally developed specifically to support a 
Go language runtime. However, it was written in a  generic way such that it has 
since been adopted to implement runtimes for Swift, PHP, Python, Rust, Java, 
Ruby and Crystal. Even though it was developed with compiled languages in mind 
it works equally well with scripting languages.
 
-Also, you will likely get a pretty fast runtime, since it is currently the 
most rapid. It was also adopted to improve performances of existing runtimes, 
that gained from factor 2x to a factor 20x for languages like Python, Ruby, 
PHP, and Java.
+Using it, you can develop a new runtime in a fraction of the time needed for 
authoring a full-fledged runtime from scratch. This is due to the fact that you 
have only to write a command line protocol and not a fully featured web server 
(with a small amount of corner case to take care of). The results should also 
produce a runtime that is fairly fast and responsive.  In fact, the ActionLoop 
proxy has also been adopted to improve the performance of existing runtimes 
like Python, Ruby, PHP, [...]
 
-ActionLoop also supports "precompilation". You can take a raw image and use 
the docker image to perform the transformation in action. You will get a zip 
file that you can use as an action that is very fast to start because it 
contains only the binaries and not the sources.
+ActionLoop also supports "precompilation". You can use the docker image of the 
runtime to compile your source files in an action offline. You will get a ZIP 
file that you can use as an action that is very fast to start because it 
contains only the binaries and not the sources. More information on this 
approach can be found here: [Precompiling Go Sources 
Offline](https://github.com/apache/incubator-openwhisk-runtime-go/blob/master/docs/DEPLOY.md#precompile)
 which describes how to do this  [...]
 
-So it is likely are using ActionLoop a better bet than implementing the 
specification from scratch. If you are convinced and want to use it, read on: 
this page is a tutorial on how to write an ActionLoop runtime, using Ruby as an 
example.
+In summary, it is likely that using the ActionLoop is simpler and a "better 
bet" than implementing the specification from scratch. If you are convinced and 
want to use it, then read on. What follows on this page is a tutorial on how to 
write an ActionLoop runtime, using Ruby as an example target language.
 
 ## How to write a new runtime with ActionLoop
 
 The development procedure for ActionLoop requires the following steps:
 
-* building a docker image containing your target language compiler and the 
ActionLoop runtime
-*  writing a simple line-oriented protocol in your target language (converting 
a python example)
-* write (or just adapt the existing) a compilation script for your target 
language
-* write some mandatory tests for your language
+* building a docker image containing your target language compiler and the 
ActionLoop runtime.
+* writing a simple line-oriented protocol in your target language.
+* writing a compilation script for your target language.
+* writing some mandatory tests for your language.
 
-To facilitate the process, there is an `actionloop-starter-kit` in the 
devtools repository, that implements a fully working runtime for Python.  It is 
a stripped down version of the real Python runtime (removing some advanced 
details of the real one).
+To facilitate the process, there is an `actionloop-starter-kit` in the 
[openwhisk-devtools](https://github.com/apache/incubator-openwhisk-devtools/tree/master/actionloop-starter-kit)
 GitHub repository, that implements a fully working runtime for Python.  It 
contains a stripped-down version of the real Python runtime (with some advanced 
features removed) along with guided, step-by-step instructions on how to 
translate it to a different target runtime language using Ruby as an example.
 
-So you can implement your runtime translating some Python code in your target 
language. This tutorial shows step by step how to do it writing the Ruby 
runtime. This code is also used in the real Ruby runtime.
-
-Using the starter kit, the process becomes:
+In short, the starter kit provides templates you can adapt in creating an 
ActionLoop runtime for each of the steps listed above, these include :
 
 - checking out  the `actionloop-starter-kit` from the 
`incubator-openwhisk-devtools` repository
-- editing the `Dockerfile` to create the target environment for your language
-- rewrite the `launcher.py` in your language
-- edit the `compile` script to compile your action in your target language
-- write the mandatory tests for your language, adapting the 
`ActionLoopPythonBasicTests.scala`
-
-Since we need to show the code you have to translate in some language, we 
picked Python as it is one of the more readable languages, the closer to be 
real-world `pseudo-code`.
+- editing the `Dockerfile` to create the target environment for your target 
language.
+- converting (rewrite) the `launcher.py` script to an equivalent for script 
for your target language.
+- editing the `compile` script to compile your action in your target language.
+- writing the mandatory tests for your target language, by adapting the 
`ActionLoopPythonBasicTests.scala` file.
 
-You need to know a bit of Python to understand the sample `launcher.py`, just 
enough to rewrite it in your target language.
+As a starting language, we chose Python since it is one of the more 
human-readable languages (can be  treated as `pseudo-code`). Do not worry, you 
should only need just enough Python knowledge to be able to rewrite 
`launcher.py` and edit the `compile` script for your target language.
 
-You may need to write some real Python coding to edit the `compile` script, 
but basic knowledge is enough.
+Finally, you will need to update the `ActionLoopPythonBasicTests.scala` test 
file which, although written in the Scala language, only serves as a wrapper 
that you will use to embed your target language tests into.
 
-Finally, you do not need to know Scala, even if the tests are embedded in a 
Scala test, as all you need is to embed your tests in the code.
 ## Notation
 
-In this tutorial we have either terminal transcripts to show what you need to 
do at the terminal, or "diffs" to show changes to existing files.
+In each step of this tutorial, we typically show snippets of either terminal 
transcripts (i.e., commands and results) or "diffs" of changes to existing code 
files.
 
-In terminal transcripts, the prefix  `$`  means commands you have to type at 
the terminal; the rest are comments (prefixed with `#`) or sample output you 
should check to verify everything is ok. Generally in a transcript I do not put 
verbatim output of the terminal as it is generally irrelevant.
+Within terminal transcript snippets, comments are prefixed with `#` character 
and commands are prefixed by the `$` character. Lines that follow commands may 
include sample output (from their execution) which can be used to verify 
against results in your local environment.
 
-When I show changes to existing files, lines without a prefix should be left 
as is, lines  with `-` should be removed and lines with  `+` should be added.
+When snippets show changes to existing source files, lines without a prefix 
should be left "as is", lines with `-` should be removed and lines with  `+` 
should be added.
 
-## Setup the development directory
+## Prequisites
+
+* Docker engine - please have a valid [docker engine 
installed](https://docs.docker.com/install/) that supports [multi-stage 
builds](https://docs.docker.com/develop/develop-images/multistage-build/) 
(i.e., Docker 17.05 or higher) and assure the Docker daemon is running.
+
+```bash
+# Verify docker version
+$ docker --version
+Docker version 18.09.3
 
-So let's start to create our own `actionloop-demo-ruby-2.6`. First, check out 
the `devtools` repository to access the starter kit, then move it in your home 
directory to work on it.
+# Verify docker is running
+$ docker ps
 
+# The result should be a valid response listing running processes
 ```
+
+## Setup the development directory
+
+So let's start to create our own `actionloop-demo-ruby-2.6` runtime. First, 
check out the `devtools` repository to access the starter kit, then move it in 
your home directory to work on it.
+
+```bash
 $ git clone https://github.com/apache/incubator-openwhisk-devtools
 $ mv incubator-openwhisk-devtools/actionloop-starter-kit 
~/actionloop-demo-ruby-v2.6
 ```
 
-Now we take the directory `python3.7` and rename it to `ruby2.6`; we also fix 
a couple of references, in order to give a name to our new runtime.
+Now, take the directory `python3.7` and rename it to `ruby2.6` and use `sed` 
to fix the directory name references in the Gradle build files.
 
-```
-$ cd actionloop-demo-ruby-v2.6
+```bash
+$ cd ~/actionloop-demo-ruby-v2.6
 $ mv python3.7 ruby2.6
 $ sed -i.bak -e 's/python3.7/ruby2.6/' settings.gradle
 $ sed -i.bak -e 's/actionloop-demo-python-v3.7/actionloop-demo-ruby-v2.6/' 
ruby2.6/build.gradle
@@ -91,37 +100,33 @@ $ sed -i.bak -e 
's/actionloop-demo-python-v3.7/actionloop-demo-ruby-v2.6/' ruby2
 
 Let's check everything is fine building the image.
 
-```
+```bash
 # building the image
 $ ./gradlew distDocker
-... omissis ...
+# ... intermediate output omitted ...
 BUILD SUCCESSFUL in 1s
 2 actionable tasks: 2 executed
 # checking the image is available
 $ docker images actionloop-demo-ruby-v2.6
 REPOSITORY                  TAG                 IMAGE ID            CREATED    
         SIZE
-actionloop-demo-ruby-v2.6   latest              df3e77c9cd8f        8 days ago 
         94MB
+actionloop-demo-ruby-v2.6   latest              df3e77c9cd8f        2 minutes 
ago          94.3MB
 ```
 
-So we have built a new image `actionloop-demo-ruby-v2.6`. However, aside from 
the renaming, internally is still the old Python. We will change it to support 
Ruby in the rest of the tutorial.
+So we have built a new image `actionloop-demo-ruby-v2.6`. However, aside from 
the renaming, internally it will still contain a Python runtime which we will 
change as we continue in this tutorial.
 
 ## Preparing the Docker environment
 
-The `Dockerfile` has the task of preparing an environment for executing our 
actions, so we have to find (or build and deploy on Docker Hub) an image 
suitable to run our target programming language. We use multistage Docker build 
to "extract" the *ActionLoop* proxy from the Docker image.
+Our language runtime's `Dockerfile` has the task of preparing an environment 
for executing OpenWhisk Actions.
+Using the ActionLoop approach, we use a multistage Docker build to
 
-For the purposes of this tutorial, you should use the `/bin/proxy` binary you 
can find in the `openwhisk/actionlooop-v2` image on Docker Hub.
+1. derive our OpenWhisk language runtime from an existing Docker image that 
has all the target language's tools and libraries for running functions 
authored in that language. In our case, we will reference the 
`ruby:2.6.2-alpine3.9` image from the [Official Docker Images for 
Ruby](https://hub.docker.com/_/ruby) on Docker Hub.
+1. leverage the existing `openwhisk/actionlooop-v2` image on Docker Hub from 
which we will "extract"  the *ActionLoop* proxy (i.e. copy `/bin/proxy` binary) 
our runtime will use to process Activation requests from the OpenWhisk platform 
and execute Actions by using the language's tools and libraries from step #1.
 
-In your runtime image, you have then copied the ActionLoop proxy, the 
`compile` and the file `launcher.rb` we are going to write.
+### Repurpose the renamed Python Dockerfile for Ruby builds
 
-Let's rename the launcher and fix the `Dockerfile` to create the environment 
for running Ruby.
+Let's edit the `ruby2.6/Dockerfile` to use, instead of the python image, the 
official ruby image on Docker Hub, and add our files:
 
-```
-$ mv ruby2.6/lib/launcher.py ruby2.6/lib/launcher.rb
-```
-
-Now let's edit the `ruby2.6/Dockerfile` to use, instead of the python image, 
the official ruby image on Docker Hub, and add out files:
-
-```
+```dockerfile
  FROM openwhisk/actionloop-v2:latest as builder
 -FROM python:3.7-alpine
 +FROM ruby:2.6.2-alpine3.9
@@ -136,78 +141,90 @@ Now let's edit the `ruby2.6/Dockerfile` to use, instead 
of the python image, the
  ENTRYPOINT ["/bin/proxy"]
 ```
 
-Note that:
+Next, let's rename the `launcher.py` (a Python script) to one that indicates 
it is a Ruby script named `launcher.rb`.
+
+```bash
+$ mv ruby2.6/lib/launcher.py ruby2.6/lib/launcher.rb
+```
 
-1. You changed the base action to use a Ruby image
-1. You included the ruby launcher instead of the python one
-1. Since the Docker image we picked is a Ruby one, and the `compile` script is 
still a python script, we had to add it too
+Note that:
 
-Of course, you can avoid having to add python inside, but you may need to 
rewrite the entire `compile` in Ruby.  You may decide to translate the entire 
`compile` in your target language, but this is not the focus of this tutorial.
+1. You changed the base language for our target OpenWhisk runtime to use a 
Ruby language image.
+1. You changed the launcher script to be a ruby script.
+1. We had to add `python3` to our Ruby image since our `compile` script is 
written in Python for this tutorial. Of course, you could rewrite the entire 
`compile` script in Ruby if you wish to.
 
 ## Implementing the ActionLoop protocol
 
-Now you have to convert the `launcher.py` in your programming language.  Let's 
recap the ActionLoop protocol.
+This section will take you through how to convert the contents of 
`launcher.rb` (formerly `launcher.py`) to the target Ruby programming language 
and implement the `ActionLoop protocol`.
 
 ### What the launcher should do
 
-The launcher must imports your function first. It is the job of the `compile` 
script to make the function available to the launcher, as we will see in the 
next paragraph.
-
-Once the function is imported, it opens the file descriptor 3 for output then 
reads the standard input line by line.
+Let's recap the steps the launcher must accomplish to implement the 
`ActionLoop protocol` :
 
-For each line, it parses the input in JSON and expects it to be a JSON object 
(not an array nor a scalar).
+1. import the Action function's `main` method for execution.
+    * Note: the `compile` script will make the function available to the 
launcher.
+1. open the system's `file descriptor 3` which will be used to output the 
functions response.
+1. read the system's standard input, `stdin`, line-by-line. Each line is 
parsed as a JSON string and produces a JSON object (not an array nor a scalar) 
to be passed as the input `arg` to the function.
+    * Note: within the JSON object, the `value` key contains the user 
parameter data to be passed to your functions. All the other keys are made 
available as process environment variables to the function; these need to be 
uppercased and prefixed with `"__OW_"`.
+1. invoke the `main` function with the JSON object payload.
+1. encode the result of the function in JSON (ensuring it is only one line and 
it is terminated with one newline) and write it to `file descriptor 3`.
+1. Once the function returns the result, flush the contents of `stdout`, 
`stderr` and `file descriptor 3` (FD 3).
+1. Finally, include the above steps in a loop so that it continually looks for 
Activations. That's it.
 
-In this object, the key `value` is the payload to be passed to your functions. 
All the other keys will be passed as environment variables, uppercases and with 
prefix `__OW_`.
+### Converting launcher script to Ruby
 
-Finally, your function is invoked with the payload. Once the function returns 
the result, standard out and standard error is flushed. The result is encoded 
in JSON, ensuring it is only one line and it is terminated with one newline and 
it is written in file descriptor 3.
+Now, let's look at the protocol described above codified within the launcher 
script `launcher.rb` and work to convert its contents from Python to Ruby.
 
-Then the loop starts again. That's it.
+#### Import the function code
 
-### Converting `launcher.py` in `launcher.rb`
+Skipping the first few library import statements within  `launcer.rb`, which 
we will have to resolve later after we determine which ones Ruby may need, we 
see the first significant line of code importing the actual Action function.
 
-Now, let's see the protocol in code, converting the Python launcher in Ruby.
-
-The compilation script as we will see later will ensure the sources are ready 
for the launcher.
-
-You are free to decide where your source action is. I generally ensure that 
the starting point is a file named like `main__.rb`, with the two underscore 
final, as those names are pretty unusual to ensure uniqueness.
-
-Let's skip the imports as they are not interesting. So in Python, the first 
(significant) line is:
-
-```
+```python
 # now import the action as process input/output
 from main__ import main as main
 ```
 
-In Ruby, this translates in:
+In Ruby, this can be rewritten as:
 
-```
+```ruby
 # requiring user's action code
 require "./main__"
 ```
 
-Now, we open the file descriptor 3, as the proxy will invoke the action with 
this descriptor attached to a pipe where it can read the results. In Python:
+*Note that you are free to decide the path and filename for the function's 
source code. In our examples, we chose a base filename that includes the word 
`"main"` (since it is OpenWhisk's default function name) and append two 
underscores to better assure uniqueness.
 
-```
+#### Open File Descriptor (FD) 3 for function results output
+
+The `ActionLoop` proxy expects to read the results of invoking the Action 
function from File Descriptor (FD) 3.
+
+The existing Python:
+
+```python
 out = fdopen(3, "wb")
 ```
 
-becomes:
+would be rewritten in Ruby as:
 
-```
+```ruby
 out = IO.new(3)
 ```
 
-Let's read in Python line by line:
+#### Process Action's arguments from STDIN
 
-```
+Each time the function is invoked via an HTTP request, the `ActionLoop` proxy 
passes the message contents to the launcher via STDIN. The launcher must read 
STDIN line-by-line and parse it as JSON.
+
+The `launcher`'s existing Python code reads STDIN line-by-line as follows:
+
+```python
 while True:
   line = stdin.readline()
   if not line: break
   # ...continue...
 ```
 
-becomes:
+would be translated to Ruby as follows:
 
-```
+```ruby
 while true
   # JSON arguments get passed via STDIN
   line = STDIN.gets()
@@ -216,9 +233,11 @@ while true
 end
 ```
 
-Now, you have to read and parse in JSON one line, then extract the payload and 
set the other values as environment variables:
+Each line is parsed in JSON, where the `payload` is extracted from contents of 
the `"value"` key. Other keys and their values are as uppercased, `"__OW_"` 
prefixed environment variables:
 
-```
+The existing Python code for this is:
+
+```python
   # ... continuing ...
   args = json.loads(line)
   payload = {}
@@ -230,9 +249,9 @@ Now, you have to read and parse in JSON one line, then 
extract the payload and s
   # ... continue ...
 ```
 
-translated:
+would be translated to Ruby:
 
-```
+```ruby
   # ... continuing ...
   args = JSON.parse(line)
   payload = {}
@@ -247,9 +266,13 @@ translated:
   # ... continue ...
 ```
 
-We are at the point of invoking our functions. You should capture exceptions 
and produce an `{"error": <result> }` if something goes wrong. In Python:
+#### Invoking the Action function
 
-```
+You are now at the point of invoking the Action function and producing its 
result. *Note you **must** also capture exceptions and produce an `{"error": 
<result> }` if anything goes wrong during execution.*
+
+The existing Python code for this is:
+
+```python
   # ... continuing ...
   res = {}
   try:
@@ -260,9 +283,9 @@ We are at the point of invoking our functions. You should 
capture exceptions and
   # ... continue ...
 ```
 
-Translated in Ruby:
+would be translated to Ruby:
 
-```
+```ruby
   # ... continuing ...
   res = {}
   begin
@@ -274,9 +297,13 @@ Translated in Ruby:
   # ... continue ...
 ```
 
-Finally, you flush standard out and standard error and write the result back 
in file descriptor 3. In Python:
+#### Flush File Descriptor 3, STDOUT and STDERR
 
-```
+Finally, you flush standard out and standard error and write the result back 
in file descriptor 3.
+
+The existing Python code for this is:
+
+```python
   out.write(json.dumps(res, ensure_ascii=False).encode('utf-8'))
   out.write(b'\n')
   stdout.flush()
@@ -284,9 +311,9 @@ Finally, you flush standard out and standard error and 
write the result back in
   out.flush()
 ```
 
-That becomes in Ruby:
+would be translated to Ruby:
 
-```
+```ruby
   STDOUT.flush()
   STDERR.flush()
   out.puts(res.to_json)

Reply via email to