This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Entities

In this section you can find more informations about OpenServerless and OpenWhisk entities.

1 - Actions

What Actions are and how to create and execute them

Actions

Actions are stateless functions that run on the OpenWhisk and OpenServerless platform. For example, an action can be used to detect the faces in an image, respond to a database change, respond to an API call, or post a Tweet. In general, an action is invoked in response to an event and produces some observable output.

An action may be created from a function programmed using a number of supported languages and runtimes, or from a binary-compatible executable.

  • The OpenServerless CLI makes it easy to create and invoke actions. Instructions for configuring and using the CLI are available here.

  • You can also use the REST API.

While the actual function code will be specific to a language and runtime, the operations to create, invoke and manage an action are the same regardless of the implementation choice.

We recommend that you review the cli and read the tutorial before moving on to advanced topics.

What you need to know about actions

  • Functions should be stateless, or idempotent. While the system does not enforce this property, there is no guarantee that any state maintained by an action will be available across invocations. In some cases, deliberately leaking state across invocations may be advantageous for performance, but also exposes some risks.

  • An action executes in a sandboxed environment, namely a container. At any given time, a single activation will execute inside the container. Subsequent invocations of the same action may reuse a previous container, and there may exist more than one container at any given time, each having its own state.

  • Invocations of an action are not ordered. If the user invokes an action twice from the command line or the REST API, the second invocation might run before the first. If the actions have side effects, they might be observed in any order.

  • There is no guarantee that actions will execute atomically. Two actions can run concurrently and their side effects can be interleaved. OpenWhisk and OpenServerless does not ensure any particular concurrent consistency model for side effects. Any concurrency side effects will be implementation-dependent.

  • Actions have two phases: an initialization phase, and a run phase. During initialization, the function is loaded and prepared for execution. The run phase receives the action parameters provided at invocation time. Initialization is skipped if an action is dispatched to a previously initialized container — this is referred to as a warm start. You can tell if an invocation was a warm activation or a cold one requiring initialization by inspecting the activation record.

  • An action runs for a bounded amount of time. This limit can be configured per action, and applies to both the initialization and the execution separately. If the action time limit is exceeded during the initialization or run phase, the activation’s response status is action developer error.

Accessing action metadata within the action body

The action environment contains several properties that are specific to the running action. These allow the action to programmatically work with OpenWhisk and OpenServerless assets via the REST API, or set an internal alarm when the action is about to use up its allotted time budget. The properties are accessible via the system environment for all supported runtimes: Node.js, Python, Swift, Java and Docker actions when using the OpenWhisk and OpenServerless Docker skeleton.

  • __OW_API_HOST the API host for the OpenWhisk and OpenServerless deployment running this action.

  • __OW_API_KEY the API key for the subject invoking the action, this key may be a restricted API key. This property is absent unless requested with the annotation explicitly provide-api-key

  • __OW_NAMESPACE the namespace for the activation (this may not be the same as the namespace for the action).

  • __OW_ACTION_NAME the fully qualified name of the running action.

  • __OW_ACTION_VERSION the internal version number of the running action.

  • __OW_ACTIVATION_ID the activation id for this running action instance.

  • __OW_DEADLINE the approximate time when this action will have consumed its entire duration quota (measured in epoch milliseconds).

2 - Web Actions

Actions annotated to quickly build web based applications

What web actions are

Web actions are OpenWhisk and OpenServerless actions annotated to quickly enable you to build web based applications. This allows you to program backend logic which your web application can access anonymously without requiring an OpenWhisk and OpenServerless authentication key. It is up to the action developer to implement their own desired authentication and authorization (i.e.OAuth flow).

Web action activations will be associated with the user that created the action. This actions defers the cost of an action activation from the caller to the owner of the action.

Let’s take the following JavaScript action hello.js,

$ cat hello.js
function main({name}) {
  var msg = 'you did not tell me who you are.';
  if (name) {
    msg = `hello ${name}!`
  }
  return {body: `<html><body><h3>${msg}</h3></body></html>`}
}

You may create a web action hello in the package demo for the namespace guest using the CLI’s --web flag with a value of true or yes:

$ ops package create demo
ok: created package demo

$ ops action create demo/hello hello.js --web true
ok: created action demo/hello

$ ops action get demo/hello --url
ok: got action hello
https://${APIHOST}/api/v1/web/guest/demo/hello

Using the --web flag with a value of true or yes allows an action to be accessible via REST interface without the need for credentials. A web action can be invoked using a URL that is structured as follows:

https://{APIHOST}/api/v1/web/{QUALIFIED ACTION NAME}.{EXT}`

The fully qualified name of an action consists of three parts: the namespace, the package name, and the action name.

The fully qualified name of the action must include its package name, which is default if the action is not in a named package.

An example is guest/demo/hello. The last part of the URI called the extension which is typically .http although other values are permitted as described later. The web action API path may be used with curl or wget without an API key. It may even be entered directly in your browser.

Try opening:

https://${APIHOST}/api/v1/web/guest/demo/hello.http?name=Jane

in your web browser. Or try invoking the action via curl:

curl https://${APIHOST}/api/v1/web/guest/demo/hello.http?name=Jane

Here is an example of a web action that performs an HTTP redirect:

function main() {
  return {
    headers: { location: 'http://openwhisk.org' },
    statusCode: 302
  }
}

Or sets a cookie:

function main() {
  return {
    headers: {
      'Set-Cookie': 'UserID=Jane; Max-Age=3600; Version=',
      'Content-Type': 'text/html'
    },
    statusCode: 200,
    body: '<html><body><h3>hello</h3></body></html>' }
}

Or sets multiple cookies:

function main() {
  return {
    headers: {
      'Set-Cookie': [
        'UserID=Jane; Max-Age=3600; Version=',
        'SessionID=asdfgh123456; Path = /'
      ],
      'Content-Type': 'text/html'
    },
    statusCode: 200,
    body: '<html><body><h3>hello</h3></body></html>' }
}

Or returns an image/png:

function main() {
    let png = <base 64 encoded string>
    return { headers: { 'Content-Type': 'image/png' },
             statusCode: 200,
             body: png };
}

Or returns application/json:

function main(params) {
    return {
        statusCode: 200,
        headers: { 'Content-Type': 'application/json' },
        body: params
    };
}

The default content-type for an HTTP response is application/json and the body may be any allowed JSON value. The default content-type may be omitted from the headers.

It is important to be aware of the response size limit for actions since a response that exceeds the predefined system limits will fail. Large objects should not be sent inline through OpenWhisk and OpenServerless, but instead deferred to an object store, for example.

Handling HTTP requests with actions

An OpenWhisk and OpenServerless action that is not a web action requires both authentication and must respond with a JSON object. In contrast, web actions may be invoked without authentication, and may be used to implement HTTP handlers that respond with headers, statusCode, and body content of different types. The web action must still return a JSON object, but the OpenWhisk and OpenServerless system (namely the controller) will treat a web action differently if its result includes one or more of the following as top level JSON properties:

  1. headers: a JSON object where the keys are header-names and the values are string, number, or boolean values for those headers (default is no headers). To send multiple values for a single header, the header’s value should be a JSON array of values.

  2. statusCode: a valid HTTP status code (default is 200 OK if body is not empty otherwise 204 No Content).

  3. body: a string which is either plain text, JSON object or array, or a base64 encoded string for binary data (default is empty response).

The body is considered empty if it is null, the empty string "" or undefined.

The controller will pass along the action-specified headers, if any, to the HTTP client when terminating the request/response. Similarly the controller will respond with the given status code when present. Lastly, the body is passed along as the body of the response. If a content-type header is not declared in the action result’s headers, the body is interpreted as application/json for non-string values, and text/html otherwise. When the content-type is defined, the controller will determine if the response is binary data or plain text and decode the string using a base64 decoder as needed. Should the body fail to decoded correctly, an error is returned to the caller.

HTTP Context

All web actions, when invoked, receives additional HTTP request details as parameters to the action input argument. They are:

  1. __ow_method (type: string): the HTTP method of the request.

  2. __ow_headers (type: map string to string): the request headers.

  3. __ow_path (type: string): the unmatched path of the request (matching stops after consuming the action extension).

  4. __ow_user (type: string): the namespace identifying the OpenWhisk and OpenServerless authenticated subject.

  5. __ow_body (type: string): the request body entity, as a base64 encoded string when content is binary or JSON object/array, or plain string otherwise.

  6. __ow_query (type: string): the query parameters from the request as an unparsed string.

A request may not override any of the named __ow_ parameters above; doing so will result in a failed request with status equal to 400 Bad Request.

The __ow_user is only present when the web action is annotated to require authentication and allows a web action to implement its own authorization policy. The __ow_query is available only when a web action elects to handle the “raw” HTTP request. It is a string containing the query parameters parsed from the URI (separated by &). The __ow_body property is present either when handling “raw” HTTP requests, or when the HTTP request entity is not a JSON object or form data. Web actions otherwise receive query and body parameters as first class properties in the action arguments with body parameters taking precedence over query parameters, which in turn take precedence over action and package parameters.

Additional features

Web actions bring some additional features that include:

  1. Content extensions: the request must specify its desired content type as one of.json,.html,.http, .svg or .text. This is done by adding an extension to the action name in the URI, so that an action /guest/demo/hello is referenced as /guest/demo/hello.http for example to receive an HTTP response back. For convenience, the .http extension is assumed when no extension is detected.

  2. Query and body parameters as input: the action receives query parameters as well as parameters in the request body. The precedence order for merging parameters is: package parameters, binding parameters, action parameters, query parameter, body parameters with each of these overriding any previous values in case of overlap . As an example /guest/demo/hello.http?name=Jane will pass the argument {name: "Jane"} to the action.

  3. Form data: in addition to the standard application/json, web actions may receive URL encoded from data application/x-www-form-urlencoded data as input.

  4. Activation via multiple HTTP verbs: a web action may be invoked via any of these HTTP methods: GET, POST, PUT, PATCH, and DELETE, as well as HEAD and OPTIONS.

  5. Non JSON body and raw HTTP entity handling: A web action may accept an HTTP request body other than a JSON object, and may elect to always receive such values as opaque values (plain text when not binary, or base64 encoded string otherwise).

The example below briefly sketches how you might use these features in a web action. Consider an action /guest/demo/hello with the following body:

function main(params) {
    return { response: params };
}

This is an example of invoking the web action using the .json extension, indicating a JSON response.

$ curl https://${APIHOST}/api/v1/web/guest/demo/hello.json
{
  "response": {
    "__ow_method": "get",
    "__ow_headers": {
      "accept": "*/*",
      "connection": "close",
      "host": "172.17.0.1",
      "user-agent": "curl/7.43.0"
    },
    "__ow_path": ""
  }
}

You can supply query parameters.

$ curl https://${APIHOST}/api/v1/web/guest/demo/hello.json?name=Jane
{
  "response": {
    "name": "Jane",
    "__ow_method": "get",
    "__ow_headers": {
      "accept": "*/*",
      "connection": "close",
      "host": "172.17.0.1",
      "user-agent": "curl/7.43.0"
    },
    "__ow_path": ""
  }
}

You may use form data as input.

$ curl https://${APIHOST}/api/v1/web/guest/demo/hello.json -d "name":"Jane"
{
  "response": {
    "name": "Jane",
    "__ow_method": "post",
    "__ow_headers": {
      "accept": "*/*",
      "connection": "close",
      "content-length": "10",
      "content-type": "application/x-www-form-urlencoded",
      "host": "172.17.0.1",
      "user-agent": "curl/7.43.0"
    },
    "__ow_path": ""
  }
}

You may also invoke the action with a JSON object.

$ curl https://${APIHOST}/api/v1/web/guest/demo/hello.json -H 'Content-Type: application/json' -d '{"name":"Jane"}'
{
  "response": {
    "name": "Jane",
    "__ow_method": "post",
    "__ow_headers": {
      "accept": "*/*",
      "connection": "close",
      "content-length": "15",
      "content-type": "application/json",
      "host": "172.17.0.1",
      "user-agent": "curl/7.43.0"
    },
    "__ow_path": ""
  }
}

You see above that for convenience, query parameters, form data, and JSON object body entities are all treated as dictionaries, and their values are directly accessible as action input properties. This is not the case for web actions which opt to instead handle HTTP request entities more directly, or when the web action receives an entity that is not a JSON object.

Here is an example of using a “text” content-type with the same example shown above.

$ curl https://${APIHOST}/api/v1/web/guest/demo/hello.json -H 'Content-Type: text/plain' -d "Jane"
{
  "response": {
    "__ow_method": "post",
    "__ow_headers": {
      "accept": "*/*",
      "connection": "close",
      "content-length": "4",
      "content-type": "text/plain",
      "host": "172.17.0.1",
      "user-agent": "curl/7.43.0"
    },
    "__ow_path": "",
    "__ow_body": "Jane"
  }
}

Content extensions

A content extension is generally required when invoking a web action; the absence of an extension assumes .http as the default. The fully qualified name of the action must include its package name, which is default if the action is not in a named package.

Protected parameters

Action parameters are protected and treated as immutable. Parameters are automatically finalized when enabling web actions.

$ ops action create /guest/demo/hello hello.js \
      --parameter name Jane \
      --web true

The result of these changes is that the name is bound to Jane and may not be overridden by query or body parameters because of the final annotation. This secures the action against query or body parameters that try to change this value whether by accident or intentionally.

Securing web actions

By default, a web action can be invoked by anyone having the web action’s invocation URL. Use the require-whisk-auth web action annotation to secure the web action. When the require-whisk-auth annotation is set to true, the action will authenticate the invocation request’s Basic Authorization credentials to confirm they represent a valid OpenWhisk and OpenServerless identity. When set to a number or a case-sensitive string, the action’s invocation request must include a X-Require-Whisk-Auth header having this same value. Secured web actions will return a Not Authorized when credential validation fails.

Alternatively, use the --web-secure flag to automatically set the require-whisk-auth annotation. When set to true a random number is generated as the require-whisk-auth annotation value. When set to false the require-whisk-auth annotation is removed. When set to any other value, that value is used as the require-whisk-auth annotation value.

ops action update /guest/demo/hello hello.js --web true --web-secure my-secret

or

ops action update /guest/demo/hello hello.js --web true -a require-whisk-auth my-secret

curl https://${APIHOST}/api/v1/web/guest/demo/hello.json?name=Jane -X GET -H "X-Require-Whisk-Auth: my-secret"

It’s important to note that the owner of the web action owns all of the web action’s activations records and will incur the cost of running the action in the system regardless of how the action was invoked.

Disabling web actions

To disable a web action from being invoked via web API (https://APIHOST/api/v1/web/), pass a value of false or no to the --web flag while updating an action with the CLI.

ops action update /guest/demo/hello hello.js --web false

Raw HTTP handling

A web action may elect to interpret and process an incoming HTTP body directly, without the promotion of a JSON object to first class properties available to the action input (e.g., args.name vs parsing args.__ow_query). This is done via a raw-http annotation. Using the same example show earlier, but now as a “raw” HTTP web action receiving name both as a query parameters and as JSON value in the HTTP request body:

$ curl https://${APIHOST}/api/v1/web/guest/demo/hello.json?name=Jane -X POST -H "Content-Type: application/json" -d '{"name":"Jane"}'
{
  "response": {
    "__ow_method": "post",
    "__ow_query": "name=Jane",
    "__ow_body": "eyJuYW1lIjoiSmFuZSJ9",
    "__ow_headers": {
      "accept": "*/*",
      "connection": "close",
      "content-length": "15",
      "content-type": "application/json",
      "host": "172.17.0.1",
      "user-agent": "curl/7.43.0"
    },
    "__ow_path": ""
  }
}

Enabling raw HTTP handling

Raw HTTP web actions are enabled via the --web flag using a value of raw.

ops action create /guest/demo/hello hello.js --web raw

Disabling raw HTTP handling

Disabling raw HTTP can be accomplished by passing a value of false or no to the --web flag.

ops update create /guest/demo/hello hello.js --web false

Decoding binary body content from Base64

When using raw HTTP handling, the __ow_body content will be encoded in Base64 when the request content-type is binary. Below are functions demonstrating how to decode the body content in Node, Python, and PHP. Simply save a method shown below to file, create a raw HTTP web action utilizing the saved artifact, and invoke the web action.

Node

function main(args) {
    decoded = new Buffer(args.__ow_body, 'base64').toString('utf-8')
    return {body: decoded}
}

Python

def main(args):
    try:
        decoded = args['__ow_body'].decode('base64').strip()
        return {"body": decoded}
    except:
        return {"body": "Could not decode body from Base64."}

PHP

<?php

function main(array $args) : array
{
    $decoded = base64_decode($args['__ow_body']);
    return ["body" => $decoded];
}

As an example, save the Node function as decode.js and execute the following commands:

$ ops action create decode decode.js --web raw
ok: created action decode
$ curl -k -H "content-type: application" -X POST -d "Decoded body" https://${APIHOST}/api/v1/web/guest/default/decodeNode.json
{
  "body": "Decoded body"
}

Options Requests

By default, an OPTIONS request made to a web action will result in CORS headers being automatically added to the response headers. These headers allow all origins and the options, get, delete, post, put, head, and patch HTTP verbs. In addition, the header Access-Control-Request-Headers is echoed back as the header Access-Control-Allow-Headers if it is present in the HTTP request. Otherwise, a default value is generated as shown below.

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: OPTIONS, GET, DELETE, POST, PUT, HEAD, PATCH
Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, User-Agent

Alternatively, OPTIONS requests can be handled manually by a web action. To enable this option add a web-custom-options annotation with a value of true to a web action. When this feature is enabled, CORS headers will not automatically be added to the request response. Instead, it is the developer’s responsibility to append their desired headers programmatically. Below is an example of creating custom responses to OPTIONS requests.

function main(params) {
  if (params.__ow_method == "options") {
    return {
      headers: {
        'Access-Control-Allow-Methods': 'OPTIONS, GET',
        'Access-Control-Allow-Origin': 'example.com'
      },
      statusCode: 200
    }
  }
}

Save the above function to custom-options.js and execute the following commands:

$ ops action create custom-option custom-options.js --web true -a web-custom-options true
$ curl https://${APIHOST}/api/v1/web/guest/default/custom-options.http -kvX OPTIONS
< HTTP/1.1 200 OK
< Server: nginx/1.11.13
< Content-Length: 0
< Connection: keep-alive
< Access-Control-Allow-Methods: OPTIONS, GET
< Access-Control-Allow-Origin: example.com

Web Actions in Shared Packages

A web action in a shared (i.e., public) package is accessible as a web action either directly via the package’s fully qualified name, or via a package binding. It is important to note that a web action in a public package will be accessible for all bindings of the package even if the binding is private. This is because the web action annotation is carried on the action and cannot be overridden. If you do not wish to expose a web action through your package bindings, then you should clone-and-own the package instead.

Action parameters are inherited from its package, and the binding if there is one. You can make package parameters immutable by defining their values through a package binding.

Error Handling

When an OpenWhisk and OpenServerless action fails, there are two different failure modes. The first is known as an application error and is analogous to a caught exception: the action returns a JSON object containing a top level error property. The second is a developer error which occurs when the action fails catastrophically and does not produce a response (this is similar to an uncaught exception). For web actions, the controller handles application errors as follows:

  1. The controller projects an error property from the response object.

  2. The controller applies the content handling implied by the action extension to the value of the error property.

Developers should be aware of how web actions might be used and generate error responses accordingly. For example, a web action that is used with the .http extension should return an HTTP response, for example: {error: { statusCode: 400 }. Failing to do so will in a mismatch between the implied content-type from the extension and the action content-type in the error response. Special consideration must be given to web actions that are sequences, so that components that make up a sequence can generate adequate errors when necessary.

3 - Parameters

Supply data to actions adding parameters

Introduction to parameters

When working with serverless actions, data is supplied by adding parameters to the actions; these are in the parameter declared as an argument to the main serverless function. All data arrives this way and the values can be set in a few different ways. The first option is to supply parameters when an action or package is created (or updated). This approach is useful for data that stays the same on every execution, equivalent to environment variables on other platforms, or for default values that might be overridden at invocation time. The second option is to supply parameters when the action is invoked - and this approach will override any parameters already set.

This page outlines how to configure parameters when deploying packages and actions, and how to supply parameters when invoking an action. There is also information on how to use a file to store the parameters and pass the filename, rather than supplying each parameter individually on the command-line.

Passing parameters to an action at invoke time

Parameters can be passed to the action when it is invoked. These examples use JavaScript but all the other languages work the same way.

  1. Use parameters in the action. For example, create ‘hello.js’ file with the following content:
function main(params) {
    return {payload:  'Hello, ' + params.name + ' from ' + params.place};
}

The input parameters are passed as a JSON object parameter to the main function. Notice how the name and place parameters are retrieved from the params object in this example.

  1. Update the action so it is ready to use:
ops action update hello hello.js
  1. Parameters can be provided explicitly on the command-line, or by supplying a file containing the desired parameters

To pass parameters directly through the command-line, supply a key/value pair to the --param flag: ops action invoke --result hello --param name Dorothy --param place Kansas

This produces the result:

{
    "payload": "Hello, Dorothy from Kansas"
}

Notice the use of the --result option: it implies a blocking invocation where the CLI waits for the activation to complete and then displays only the result. For convenience, this option may be used without --blocking which is automatically inferred.

Additionally, if parameter values specified on the command-line are valid JSON, then they will be parsed and sent to your action as a structured object. For example, if we update our hello action to:

function main(params) {
    return {payload:  'Hello, ' + params.person.name + ' from ' + params.person.place};
}

Now the action expects a single person parameter to have fields name and place. If we invoke the action with a single person parameter that is valid JSON:

ops action invoke --result hello -p person '{"name": "Dorothy", "place": "Kansas"}'

The result is the same because the CLI automatically parses the person parameter value into the structured object that the action now expects: json { "payload": "Hello, Dorothy from Kansas" }

Setting default parameters on an action

Actions can be invoked with multiple named parameters. Recall that the hello action from the previous example expects two parameters: the name of a person, and the place where they’re from.

Rather than pass all the parameters to an action every time, you can bind certain parameters. The following example binds the place parameter so that the action defaults to the place “Kansas”:

  1. Update the action by using the --param option to bind parameter values, or by passing a file that contains the parameters to --param-file (for examples of using files, see the section on working with parameter files.

To specify default parameters explicitly on the command-line, provide a key/value pair to the param flag:

ops action update hello --param place Kansas
  1. Invoke the action, passing only the name parameter this time.
ops action invoke --result hello --param name Dorothy

{
    "payload": "Hello, Dorothy from Kansas"
}

Notice that you did not need to specify the place parameter when you invoked the action. Bound parameters can still be overwritten by specifying the parameter value at invocation time.

  1. Invoke the action, passing both name and place values, and observe the output:
ops action invoke --result hello --param name Dorothy --param place "Washington, DC"

{
    "payload": "Hello, Dorothy from Washington, DC"
}

Despite a parameter set on the action when it was created/updated, this is overridden by a parameter that was supplied when invoking the action.

Setting default parameters on a package

Parameters can be set at the package level, and these will serve as default parameters for actions unless:

  • The action itself has a default parameter.

  • The action has a parameter supplied at invoke time, which will always be the “winner” where more than one parameter is available.

The following example sets a default parameter of name on the MyApp package and shows an action making use of it.

  1. Create a package with a parameter set:
ops package update MyApp --param name World
  1. Create an action in this package:
   function main(params) {
       return {payload: "Hello, " + params.name};
   }

ops action update MyApp/hello hello.js
  1. Invoke the action, and observe the default package parameter in use:
ops action invoke --result MyApp/hello

   {
       "payload": "Hello, World"
   }

Working with parameter files

It’s also possible to put parameters into a file in JSON format, and then pass the parameters in by supplying the filename with the param-file flag. This works for both packages and actions when creating/updating them, and when invoking actions.

  1. As an example, consider the very simple hello example from earlier. Using hello.js with this content:
function main(params) {
    return {payload:  'Hello, ' + params.name + ' from ' + params.place};
}
  1. Update the action with the updated contents of hello.js:
ops action update hello hello.js
  1. Create a parameter file called parameters.json containing JSON-formatted parameters:
{
    "name": "Dorothy",
    "place": "Kansas"
}
  1. Use the parameters.json filename when invoking the action, and observe the output
ops action invoke --result hello --param-file parameters.json

{
    "payload": "Hello, Dorothy from Kansas"
}

4 - Annotations

How to use annotations to decorate actions

Annotations

OpenWhisk and OpenServerless actions, triggers, rules and packages (collectively referred to as assets) may be decorated with annotations. Annotations are attached to assets just like parameters with a key that defines a name and value that defines the value. It is convenient to set them from the command line interface (CLI) via --annotation or -a for short.

Rationale: Annotations were added to OpenWhisk and OpenServerless to allow for experimentation without making changes to the underlying asset schema. We had, until the writing of this document, deliberately not defined what annotations are permitted. However as we start to use annotations more heavily to impart semantic changes, it’s important that we finally start to document them.

The most prevalent use of annotations to date is to document actions and packages. You’ll see many of the packages in the OpenWhisk and OpenServerless catalog carry annotations such as a description of the functionality offered by their actions, which parameters are required at package binding time, and which are invoke-time parameters, whether a parameter is a “secret” (e.g., password), or not. We have invented these as needed, for example to allow for UI integration.

Here is a sample set of annotations for an echo action which returns its input arguments unmodified (e.g., function main(args) { return args }). This action may be useful for logging input parameters for example as part of a sequence or rule.

ops action create echo echo.js \
    -a description 'An action which returns its input. Useful for logging input to enable debug/replay.' \
    -a parameters  '[{ "required":false, "description": "Any JSON entity" }]' \
    -a sampleInput  '{ "msg": "Five fuzzy felines"}' \
    -a sampleOutput '{ "msg": "Five fuzzy felines"}'

The annotations we have used for describing packages are:

  • description: a pithy description of the package

  • parameters: an array describing parameters that are scoped to the package (described further below)

Similarly, for actions:

  • description: a pithy description of the action

  • parameters: an array describing actions that are required to execute the action

  • sampleInput: an example showing the input schema with typical values

  • sampleOutput: an example showing the output schema, usually for the sampleInput

The annotations we have used for describing parameters include:

  • name: the name of the parameter

  • description: a pithy description of the parameter

  • doclink: a link to further documentation for parameter (useful for OAuth tokens for example)

  • required: true for required parameters and false for optional ones

  • bindTime: true if the parameter should be specified when a package is bound

  • type: the type of the parameter, one of password, array (but may be used more broadly)

The annotations are not checked. So while it is conceivable to use the annotations to infer if a composition of two actions into a sequence is legal, for example, the system does not yet do that.

Annotations for all actions

The following annotations on an action are available.

  • provide-api-key: This annotation may be attached to actions which require an API key, for example to make REST API calls to the OpenWhisk and OpenServerless host. For newly created actions, if not specified, it defaults to a false value. For existing actions, the absence of this annotation, or its presence with a value that is not falsy (i.e., a value that is different from zero, null, false, and the empty string) will cause an API key to be present in the action execution context.

Annotations specific to web actions

Web actions are enabled with explicit annotations which decorate individual actions. The annotations only apply to the web actions API, and must be present and explicitly set to true to have an affect. The annotations have no meaning otherwise in the system. The annotations are:

  • web-export: Makes its corresponding action accessible to REST calls without authentication. We call these web actions because they allow one to use OpenWhisk and OpenServerless actions from a browser for example. It is important to note that the owner of the web action incurs the cost of running them in the system (i.e., the owner of the action also owns the activations record). The rest of the annotations described below have no effect on the action unless this annotation is also set.

  • final: Makes all of the action parameters that are already defined immutable. A parameter of an action carrying the annotation may not be overridden by invoke-time parameters once the parameter has a value defined through its enclosing package or the action definition.

  • raw-http: When set, the HTTP request query and body parameters are passed to the action as reserved properties.

  • web-custom-options: When set, this annotation enables a web action to respond to OPTIONS requests with customized headers, otherwise a default CORS response applies.

  • require-whisk-auth: This annotation protects the web action so that it is only invoked by requests that provide appropriate authentication credentials. When set to a boolean value, it controls whether or not the request’s Basic Authentication value (i.e. Whisk auth key) will be authenticated - a value of true will authenticate the credentials, a value of false will invoke the action without any authentication. When set to a number or a string, this value must match the request’s X-Require-Whisk-Auth header value. In both cases, it is important to note that the owner of the web action will still incur the cost of running them in the system (i.e., the owner of the action also owns the activations record).

Annotations specific to activations

The system decorates activation records with annotations as well. They are:

  • path: the fully qualified path name of the action that generated the activation. Note that if this activation was the result of an action in a package binding, the path refers to the parent package.

  • binding: the entity path of the package binding. Note that this is only present for actions in a package binding.

  • kind: the kind of action executed, and one of the support OpenWhisk and OpenServerless runtime kinds.

  • limits: the time, memory and log limits that this activation were subject to.

Additionally for sequence related activations, the system will generate the following annotations:

  • topmost: this is only present for an outermost sequence action.

  • causedBy: this is only present for actions that are contained in a sequence.

Lastly, and in order to provide you with some performance transparency, activations also record:

  • waitTime: the time spent waiting in the internal OpenWhisk and OpenServerless system. This is roughly the time spent between the controller receiving the activation request and when the invoker provisioned a container for the action.

  • initTime: the time spent initializing the function. If this value is present, the action required initialization and represents a cold start. A warm activation will skip initialization, and in this case, the annotation is not generated.

An example of these annotations as they would appear in an activation record is shown below.

"annotations": [
  {
    "key": "path",
    "value": "guest/echo"
  },
  {
    "key": "waitTime",
    "value": 66
  },
  {
    "key": "kind",
    "value": "nodejs:6"
  },
  {
    "key": "initTime",
    "value": 50
  },
  {
    "key": "limits",
    "value": {
      "logs": 10,
      "memory": 256,
      "timeout": 60000
    }
  }
]

5 - Packages

Create and Use packages

In OpenWhisk and OpenServerless, you can use packages to bundle together a set of related actions, and share them with others.

A package can include actions and feeds. - An action is a piece of code that runs on OpenWhisk. For example, the Cloudant package includes actions to read and write records to a Cloudant database. - A feed is used to configure an external event source to fire trigger events. For example, the Alarm package includes a feed that can fire a trigger at a specified frequency.

Every OpenWhisk and OpenServerless entity, including packages, belongs in a namespace, and the fully qualified name of an entity is /namespaceName[/packageName]/entityName. Refer to the naming guidelines for more information.

The following sections describe how to browse packages and use the triggers and feeds in them. In addition, if you are interested in contributing your own packages to the catalog, read the sections on creating and sharing packages.

Browsing packages

Several packages are registered with OpenWhisk and OpenServerless. You can get a list of packages in a namespace, list the entities in a package, and get a description of the individual entities in a package.

  1. Get a list of packages in the /nuvolaris namespace.
$ ops package list /nuvolaris

packages
/nuvolaris/openai                                       private
/nuvolaris/mastrogpt                                    private
/nuvolaris/examples                                     private
  1. Get a list of entities in the /nuvolaris/openai package.
$ ops package get --summary /nuvolaris/openai
package /nuvolaris/openai
   (parameters: none defined)
 action /nuvolaris/openai/models
   (parameters: none defined)
 action /nuvolaris/openai/chat
   (parameters: none defined)

Note: Parameters listed under the package with a prefix * are predefined, bound parameters. Parameters without a * are those listed under the annotations for each entity. Furthermore, any parameters with the prefix ** are finalized bound parameters. This means that they are immutable, and cannot be changed by the user. Any entity listed under a package inherits specific bound parameters from the package. To view the list of known parameters of an entity belonging to a package, you will need to run a get --summary of the individual entity.

  1. Get a description of the /nuvolaris/openai/chat action.
$ ops action get --summary /nuvolaris/openai/chat
action /nuvolaris/openai/chat: Returns a result based on parameters OPENAI_API_HOST and OPENAI_API_KEY
   (parameters: **OPENAI_API_HOST, **OPENAI_API_KEY)

NOTE: Notice that the parameters listed for the action read were expanded upon from the action summary compared to the package summary above. To see the official bound parameters for actions and triggers listed under packages, run an individual get summary for the particular entity.

Creating a package

A package is used to organize a set of related actions and feeds. It also allows for parameters to be shared across all entities in the package.

To create a custom package with a simple action in it, try the following example:

  1. Create a package called custom.
$ ops package create custom
ok: created package custom
  1. Get a summary of the package.
$ ops package get --summary custom
package /myNamespace/custom
   (parameters: none defined)

Notice that the package is empty.

  1. Create a file called identity.js that contains the following action code. This action returns all input parameters.
function main(args) { return args; }
  1. Create an identity action in the custom package.
$ ops action create custom/identity identity.js
ok: created action custom/identity

Creating an action in a package requires that you prefix the action name with a package name. Package nesting is not allowed. A package can contain only actions and can’t contain another package.

  1. Get a summary of the package again.
$ ops package get --summary custom
package /myNamespace/custom
  (parameters: none defined)
 action /myNamespace/custom/identity
  (parameters: none defined)

You can see the custom/identity action in your namespace now.

  1. Invoke the action in the package.
$ ops action invoke --result custom/identity
{}

You can set default parameters for all the entities in a package. You do this by setting package-level parameters that are inherited by all actions in the package. To see how this works, try the following example:

  1. Update the custom package with two parameters: city and country.
$ ops package update custom --param city Austin --param country USA
ok: updated package custom
  1. Display the parameters in the package and action, and see how the identity action in the package inherits parameters from the package.
$ ops package get custom
ok: got package custom
...
"parameters": [
    {
        "key": "city",
        "value": "Austin"
    },
    {
        "key": "country",
        "value": "USA"
    }
]
...

$ ops action get custom/identity
ok: got action custom/identity
...
"parameters": [
    {
        "key": "city",
        "value": "Austin"
    },
    {
        "key": "country",
        "value": "USA"
    }
]
...
  1. Invoke the identity action without any parameters to verify that the action indeed inherits the parameters.
$ ops action invoke --result custom/identity
{
    "city": "Austin",
    "country": "USA"
}
  1. Invoke the identity action with some parameters. Invocation parameters are merged with the package parameters; the invocation parameters override the package parameters.
$ ops action invoke --result custom/identity --param city Dallas --param state Texas
{
    "city": "Dallas",
    "country": "USA",
    "state": "Texas"
}

Sharing a package

After the actions and feeds that comprise a package are debugged and tested, the package can be shared with all OpenWhisk and OpenServerless users. Sharing the package makes it possible for the users to bind the package, invoke actions in the package, and author OpenWhisk and OpenServerless rules and sequence actions.

  1. Share the package with all users:
$ ops package update custom --shared yes
ok: updated package custom
  1. Display the publish property of the package to verify that it is now true.
$ ops package get custom
ok: got package custom
...
"publish": true,
...

Others can now use your custom package, including binding to the package or directly invoking an action in it. Other users must know the fully qualified names of the package to bind it or invoke actions in it. Actions and feeds within a shared package are public. If the package is private, then all of its contents are also private.

  1. Get a description of the package to show the fully qualified names of the package and action.
$ ops package get --summary custom
package /myNamespace/custom: Returns a result based on parameters city and country
   (parameters: *city, *country)
 action /myNamespace/custom/identity
   (parameters: none defined)

In the previous example, you’re working with the myNamespace namespace, and this namespace appears in the fully qualified name.

6 - Feeds

Implement Feeds

OpenWhisk and OpenServerless support an open API, where any user can expose an event producer service as a feed in a package. This section describes architectural and implementation options for providing your own feed.

This material is intended for advanced OpenWhisk and OpenServerless users who intend to publish their own feeds. Most OpenWhisk and OpenServerless users can safely skip this section.

Feed Architecture

There are at least 3 architectural patterns for creating a feed: Hooks, Polling and Connections.

Hooks

In the Hooks pattern, we set up a feed using a webhook facility exposed by another service. In this strategy, we configure a webhook on an external service to POST directly to a URL to fire a trigger. This is by far the easiest and most attractive option for implementing low-frequency feeds.

Polling

In the Polling pattern, we arrange for an OpenWhisk and OpenServerless action to poll an endpoint periodically to fetch new data. This pattern is relatively easy to build, but the frequency of events will of course be limited by the polling interval.

Connections

In the Connections pattern, we stand up a separate service somewhere that maintains a persistent connection to a feed source. The connection based implementation might interact with a service endpoint via long polling, or to set up a push notification.

Difference between Feed and Trigger

Feeds and triggers are closely related, but technically distinct concepts.

  • OpenWhisk and OpenServerless process events which flow into the system.

  • A trigger is technically a name for a class of events. Each event belongs to exactly one trigger; by analogy, a trigger resembles a topic in topic-based pub-sub systems. A rule T → A means “whenever an event from trigger T arrives, invoke action A with the trigger payload.

  • A feed is a stream of events which all belong to some trigger T. A feed is controlled by a feed action which handles creating, deleting, pausing, and resuming the stream of events which comprise a feed. The feed action typically interacts with external services which produce the events, via a REST API that manages notifications.

Implementing Feed Actions

The feed action is a normal OpenWhisk and OpenServerless action, but it should accept the following parameters:

  • lifecycleEvent: one of ‘CREATE’, ‘READ’, ‘UPDATE’, ‘DELETE’, ‘PAUSE’, or ‘UNPAUSE’.
  • triggerName: the fully-qualified name of the trigger which contains events produced from this feed.
  • authKey: the Basic auth credentials of the OpenWhisk and OpenServerless user who owns the trigger just mentioned.

The feed action can also accept any other parameters it needs to manage the feed. For example the cloudant changes feed action expects to receive parameters including `dbname’, `username’, etc.

When the user creates a trigger from the CLI with the –feed parameter, the system automatically invokes the feed action with the appropriate parameters.

For example, assume the user has created a mycloudant binding for the cloudant package with their username and password as bound parameters. When the user issues the following command from the CLI:

ops trigger create T --feed mycloudant/changes -p dbName myTable

then under the covers the system will do something equivalent to:

ops action invoke mycloudant/changes -p lifecycleEvent CREATE -p triggerName T -p authKey <userAuthKey> -p password <password value from mycloudant binding> -p username <username value from mycloudant binding> -p dbName mytype

The feed action named changes takes these parameters, and is expected to take whatever action is necessary to set up a stream of events from Cloudant, with the appropriate configuration, directed to the trigger T.

For the Cloudant changes feed, the action happens to talk directly to a cloudant trigger service we’ve implemented with a connection-based architecture. We’ll discuss the other architectures below.

A similar feed action protocol occurs for ops trigger delete, ops trigger update and ops trigger get.

Implementing Feeds with Hooks

It is easy to set up a feed via a hook if the event producer supports a webhook/callback facility.

With this method there is no need to stand up any persistent service outside of OpenWhisk and OpenServerless. All feed management happens naturally though stateless OpenWhisk and OpenServerless feed actions, which negotiate directly with a third part webhook API.

When invoked with CREATE, the feed action simply installs a webhook for some other service, asking the remote service to POST notifications to the appropriate fireTrigger URL in OpenWhisk and OpenServerless.

The webhook should be directed to send notifications to a URL such as:

POST /namespaces/{namespace}/triggers/{triggerName}

The form with the POST request will be interpreted as a JSON document defining parameters on the trigger event. OpenWhisk and OpenServerless rules pass these trigger parameters to any actions to fire as a result of the event.

Implementing Feeds with Polling

It is possible to set up an OpenWhisk and OpenServerless action to poll a feed source entirely within OpenWhisk and OpenServerless, without the need to stand up any persistent connections or external service.

For feeds where a webhook is not available, but do not need high-volume or low latency response times, polling is an attractive option.

To set up a polling-based feed, the feed action takes the following steps when called for CREATE:

  1. The feed action sets up a periodic trigger (T) with the desired frequency, using the whisk.system/alarms feed.

  2. The feed developer creates a pollMyService action which simply polls the remote service and returns any new events.

  3. The feed action sets up a rule T → pollMyService.

This procedure implements a polling-based trigger entirely using OpenWhisk and OpenServerless actions, without any need for a separate service.

Implementing Feeds via Connections

The previous 2 architectural choices are simple and easy to implement. However, if you want a high-performance feed, there is no substitute for persistent connections and long-polling or similar techniques.

Since OpenWhisk and OpenServerless actions must be short-running, an action cannot maintain a persistent connection to a third party . Instead, we must stand up a separate service (outside of OpenWhisk and OpenServerless) that runs all the time. We call these provider services. A provider service can maintain connections to third party event sources that support long polling or other connection-based notifications.

The provider service should provide a REST API that allows the OpenWhisk and OpenServerless feed action to control the feed. The provider service acts as a proxy between the event provider and OpenWhisk and OpenServerless – when it receives events from the third party, it sends them on to OpenWhisk and OpenServerless by firing a trigger.

The connection-based architecture is the highest performance option, but imposes more overhead on operations compared to the polling and hook architectures.