Pragmatism in the real world

Serverless Swift on OpenWhisk

I’m interested in serverless computing and as I write Swift, the OpenWhisk platform comes up high when you Google. This turns out to be a really good choice as OpenWhisk is Open Source so I can read the source code (and have done!). In principle, I can also run my own instance of it if I need to to for regulatory reasons, or just to avoid vendor lock-in.

Commercially, the whole point of Serverless (aka Functions as a Service) is that it deal with everything infrastructure related other than the function I am writing, and so I actually host my OpenWhisk functions with IBM’s Bluemix.

In a serverless environment we write separate functions that are event driven. Each function can even be in a different language as they are all independent. Also, our functions are stateless which, as an API person, I’m comfortable with. There’s more than one way to trigger a function, but I’ve started with the simplest: an HTTP request.

Far more information is in the docs and I can’t recommend the OpenWhisk-Team Slack channel enough; very helpful people on there.

This is my intro post on getting going with OpenWhisk mainly so that all the info I need is in one place!

Notes on Getting Started

There’s plenty of blog posts about getting started with OpenWhisk on Bluemix, so this is mostly an aide-memoir for myself as I had to go back on a couple of things that I didn’t understand the first time.

Create a Bluemix account:

  • Log into your Bluemix account or create one.
  • OpenWhisk is only provisioned in the US South region
  • Make a note of your organisation and space, you’ll need them later!
    (If you’re setting up Bluemix for the first time, call your first space “dev”)

Set up OpenWhisk:

  • Go to https://console.ng.bluemix.net/openwhisk/
  • Click “Download OpenWhisk CLI” and install the wsk executable
  • Make sure you run the “New Authentication” and “Unset Namespace” commands in your terminal

You should now have a working wsk command line tool. wsk is remarkably helpful. Add -h and it’ll give you help.

First Swift action

As OpenWhisk is serverless, we have a single entry function to an action. In Swift has this signature:

func main(args: [String:Any]) -> [String:Any]

This means that we receive a dictionary of arguments (which are called parameters elsewhere in OpenWhisk) and must return a dictionary. (In Swift, a dictionary is what is called an associative array or hash in other languages.). The returned dictionary is the data returned to the caller.

First action

To create an action, we need a swift source file. This can have any name, but my general rule of thumb is to name it the same as the action name. As OpenWhisk looks quite APIish, we’ll create a “ping” action and so our file is called ping.swift

ping.swift

func main(args: [String:Any]) -> [String:Any] {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let now = formatter.string(from: Date())

    return ["ack": now]
}

Packages

Always put your actions in packages. These work a little like namespaces in that you can group together related actions, triggers, rules and what not. You can also attach default parameters to packages that are then available to every action which can be very useful. Interestingly, you can “import” a namespace into another one (known as binding) and when you do so, you can override the parameter values for that package for this specific binding.

To create a package called “P1”:

$ wsk package create P1

Upload to OpenWhisk

To upload your function to OpenWhisk:

$ wsk action update P1/ping ping.swift

(You can also use create in place of update, but as update will create the action if it doesn’t
exist, you may as well just always use update.)

Viewing all actions

To view your actions, list them:

$ wsk action list
actions
/19FT_dev/P1/ping                                 private swift:3

In this case, I have one action, list. It’s fully qualified name is “/19FT_dev/P1/ping” and as it’s part of a private package, it’s private and written in Swift 3. The language information is provided as OpenWhisk supports Java, NodeJS and Python actions in addition to Swift.

Running the action

There are many ways to run the action. The first way is to use the wsk tool’s action invoke command:

$ wsk action invoke --blocking --result P1/ping
{
    "ack": "2017-02-26 11:03:59"
}

The blocking parameter tells the command to wait until the action completes before returning. If you leave it out, then the action is invoked, but you don’t get the result as it’s in “fire and forget” mode.

Alternatively, you can use curl.

To do this, you make a POST request to https://openwhisk.ng.bluemix.net/api/v1/namespaces/{NAMESPACE}/actions/{ACTION}?blocking=true with your API key in the Authorization header. As Basic Auth require the credentials to be Base64 encoded, the easiest way to get the information in the right format is:

$ wsk property get --auth | awk '{printf("%s", $3)}' | openssl base64 | tr -d "\n"

You also need your namespace, which has the format of {organisation name}_{space name} as you can see in the fully qualified action name in the output of wsk action list. In my case, this is: 19FT_dev.

We can then use this with our curl command:

$ AUTH=$(wsk property get --auth | awk '{printf("%s", $3)}' | openssl base64 | tr -d "\n")
$ curl -X POST -H "Authorization: Basic $AUTH" \
https://openwhisk.ng.bluemix.net/api/v1/namespaces/19FT_dev/actions/P1/ping?blocking=true

{
  "duration": 2448,
  "name": "ping",
  "subject": "rob@19ft.com",
  "activationId": "21f3c9b1bcad40fc84e0fbcf7fb356cd",
  "publish": false,
  "annotations": [{
    "key": "limits",
    "value": {
      "timeout": 60000,
      "memory": 256,
      "logs": 10
    }
  }, {
    "key": "path",
    "value": "19FT_dev/P1/ping"
  }],
  "version": "0.0.9",
  "response": {
    "result": {
      "ack": "2017-02-25 23:17:53"
    },
    "success": true,
    "status": "success"
  },
  "end": 1488064673564,
  "logs": [],
  "start": 1488064671116,
  "namespace": "19FT_dev"
}

As you can see, you get a lot of info back, but the key bit is in the response -> result property:

"result": {
    "ack": "2017-02-25 23:17:53"
}


As you don’t want to share your API key with anyone, there are other ways to call this action via HTTP: Web Action and API Gateway. We’ll explore these in a separate post.

Something wrong? Viewing the logs

If something goes wrong, the place to look is the logs. To get an ongoing up to date view, open a new terminal window and run this in it:

$ wsk activation poll

The works a lot like tail -f. Invoke your action and you’ll see the information for it.

Alternatively, to view the last log, read LornaJane‘s “One-Line Command For Newest OpenWhisk Logs” article.

The command you need is:

$ wsk activation list -l1 | tail -n1 | cut -d ' ' -f1 | xargs wsk activation logs

That’s a bit of mouthful, so put it in a script or alias it.

Fin

That’s it. Getting started with Swift actions on OpenWhisk is remarkably easy and lots of fun. If you want to poke around a more fully featured app, have a look at my DrinkChooser project.