OpenWhisk supports several languages and runtimes but there may be other languages or runtimes that are important for your organization, and for which you want tighter integration with the platform. The OpenWhisk platform is extensible and you can add new languages or runtimes (with custom packages and third-party dependencies) following the guide described here.
The unit of execution for all functions is a Docker container which must implement a specific Action interface that, in general performs:
stdout
and stderr
logs and adds a frame marker at the end of the activation.The specifics of the Action interface and its functions are shown below.
In order for your language runtime to be properly recognized by the OpenWhisk platform, and officially recognized by the Apache OpenWhisk project, please follow these requirements and best practices:
actions-<your runtime>.md
file to the docs directory,The new runtime repository should conform to the Canonical runtime repository layout (as shown below). Further, you should automate and pass the following test suites:
Actions when created specify the desired runtime for the function via a property called “kind”. When using the wsk
CLI, this is specified as --kind <runtime-kind>
. The value is typically a string describing the language (e.g., nodejs
) followed by a colon and the version for the runtime as in nodejs:8
or php:7.3
.
The manifest is a map of runtime family names to an array of specific kinds. The details of the schema are found in the Exec Manifest. As an example, the following entry add a new runtime family called nodejs
with a single kind nodejs:10
.
{ "nodejs": [{ "kind": "nodejs:10", "default": true, "image": { "prefix": "openwhisk", "name": "nodejs10action", "tag": "latest" } }] }
The default
property indicates if the corresponding kind should be treated as the default for the runtime family. The JSON image
structure defines the Docker image name that is used for actions of this kind (e.g., openwhisk/nodejs10action:latest
for the JSON example above).
The runtime repository should follow the canonical structure used by other runtimes.
/path/to/runtime ├── build.gradle # Gradle build file to integrate with rest of build and test framework ├── core │ └── <runtime name and version> │ ├── Dockerfile # builds the runtime's Docker image │ └── ... # your runtimes files which implement the action proxy └── tests └── src # tests suits... └── ... # ... which extend canonical interface plus additional runtime specific tests
The Docker skeleton repository is an example starting point to fork and modify for your new runtime.
The standard test action is shown below in JavaScript. It should be adapted for the new language and added to the test artifacts directory with the name <runtime-kind>.txt
for plain text file or <runtime-kind>.bin
for a a binary file. The <runtime-kind>
must match the value used for kind
in the corresponding runtime manifest entry, replacing :
in the kind with a -
. For example, a plain text function for nodejs:8
becomes nodejs-8.txt
.
function main(args) { var str = args.delimiter + " ☃ " + args.delimiter; console.log(str); return { "winter": str }; }
An action consists of the user function (and its dependencies) along with a proxy that implements a canonical protocol to integrate with the OpenWhisk platform.
The proxy is a web server with two endpoints.
8080
./init
to initialize the container./run
to activate the function.The proxy also prepares the execution context, and flushes the logs produced by the function to stdout and stderr.
The initialization route is /init
. It must accept a POST
request with a JSON object as follows:
{ "value": { "name" : String, "main" : String, "code" : String, "binary": Boolean, "env": Map[String, String] } }
name
is the name of the action.main
is the name of the function to execute.code
is either plain text or a base64 encoded string for binary functions (i.e., a compiled executable).binary
is false if code
is in plain text, and true if code
is base64 encoded.env
is an map of key-value pairs of properties to export to the environment.The initialization route is called exactly once by the OpenWhisk platform, before executing a function. The route should report an error if called more than once. It is possible however that a single initialization will be followed by many activations (via /run
). If an env
property is provided, the corresponding environment variables should be defined before the action code is initialized.
Successful initialization: The route should respond with 200 OK
if the initialization is successful and the function is ready to execute. Any content provided in the response is ignored.
Failures to initialize: Any response other than 200 OK
is treated as an error to initialize. The response from the handler if provided must be a JSON object with a single field called error
describing the failure. The value of the error field may be any valid JSON value. The proxy should make sure to generate meaningful log message on failure to aid the end user in understanding the failure.
Time limit: Every action in OpenWhisk has a defined time limit (e.g., 60 seconds). The initialization must complete within the allowed duration. Failure to complete initialization within the allowed time frame will destroy the container.
Limitation: The proxy does not currently receive any of the activation context at initialization time. There are scenarios where the context is convenient if present during initialization. This will require a change in the OpenWhisk platform itself. Note that even if the context is available during initialization, it must be reset with every new activation since the information will change with every execution.
The proxy is ready to execute a function once it has successfully completed initialization. The OpenWhisk platform will invoke the function by posting an HTTP request to /run
with a JSON object providing a new activation context and the input parameters for the function. There may be many activations of the same function against the same proxy (viz. container). Currently, the activations are guaranteed not to overlap — that is, at any given time, there is at most one request to /run
from the OpenWhisk platform.
The route must accept a JSON object and respond with a JSON object, otherwise the OpenWhisk platform will treat the activation as a failure and proceed to destroy the container. The JSON object provided by the platform follows the following schema:
{ "value": JSON, "namespace": String, "action_name": String, "api_host": String, "api_key": String, "activation_id": String, "transaction_id": String, "deadline": Number }
value
is a JSON object and contains all the parameters for the function activation.namespace
is the OpenWhisk namespace for the action (e.g., whisk.system
).action_name
is the fully qualified name of the action.activation_id
is a unique ID for this activation.transaction_id
is a unique ID for the request of which this activation is part of.deadline
is the deadline for the function.api_key
is the API key used to invoke the action.The value
is the function parameters. The rest of the properties become part of the activation context which is a set of environment variables constructed by capitalizing each of the property names, and prefixing the result with __OW_
. Additionally, the context must define __OW_API_HOST
whose value is the OpenWhisk API host. This value is currently provided as an environment variable defined at container startup time and hence already available in the context.
Successful activation: The route must respond with 200 OK
if the activation is successful and the function has produced a JSON object as its result. The response body is recorded as the result of the activation.
Failed activation: Any response other than 200 OK
is treated as an activation error. The response from the handler must be a JSON object with a single field called error
describing the failure. The value of the error field may be any valid JSON value. Should the proxy fail to respond with a JSON object, the OpenWhisk platform will treat the failure as an uncaught exception. These two failures modes are distinguished by the value of the response.status
in the activation record which is “application error” if the proxy returned an “error” object, and “action developer error” otherwise.
Time limit: Every action in OpenWhisk has a defined time limit (e.g., 60 seconds). The activation must complete within the allowed duration. Failure to complete activation within the allowed time frame will destroy the container.
The proxy must flush all the logs produced during initialization and execution and add a frame marker to denote the end of the log stream for an activation. This is done by emitting the token XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX
as the last log line for the stdout
and stderr
streams. Failure to emit this marker will cause delayed or truncated activation logs.
The Action interface is enforced via a canonical test suite which validates the initialization protocol, the runtime protocol, ensures the activation context is correctly prepared, and that the logs are properly framed. Your runtime should extend this test suite, and of course include additional tests as needed.
There is a canonical test harness for validating a new runtime.
The tests verify that the proxy can handle the following scenarios:
The canonical test suite should be extended by the new runtime tests. Additional tests will be required depending on the feature set provided by the runtime.
Since the OpenWhisk platform is language and runtime agnostic, it is generally not necessary to add integration tests. That is the unit tests verifying the protocol are sufficient. However, it may be necessary in some cases to modify the wsk
CLI or other OpenWhisk clients. In which case, appropriate tests should be added as necessary. The OpenWhisk platform will perform a generic integration test as part of its basic system tests. This integration test will require a test function to be available so that the test harness can create, invoke, and delete the action.