Functions

About Functions

Functions enable dynamic call processing based on customized serverless function and callflow document as input.

Pivot allows you to receive a per-call HTTP request to decide what to do, generally to access data sources not available to KAZOO. However, if your business logic is self-contained and side-effect free (and not directly representable via the callflow JSON structure), functions allow you to create that custom logic and have it run within the KAZOO infrastructure. This minimizes the time spent processing the function (as it is run “locally”) and doesn’t require you to have to stand up separate infrastructure to handle the HTTP request and generate the JSON response.

Schema

Functions that javascript function to process a callflow document.

KeyDescriptionTypeDefaultRequiredSupport Level
enabledIs the function enabled and runningboolean()truefalsesupported
function_jsServerless function in Javascriptstring()truesupported
language_packThe language and version are required for the function runtime.string()javascriptfalsesupported
nameA descriptive name for the function.string()truesupported

User-defined Functions

Effectively, KAZOO gives you function(call) {...} and asks you to supply the .... You have the call object containing the same request parameters as you would in Pivot (such as call['To'] or call['Call-ID']).

Example 1: Say Hi!

{"name":"say hi"
 ,"function_js":"return {'module':'tts', 'data':{'text':'Thanks for calling us ' + call['Caller-ID-Name']}};"
}

All calls to this function would then run the TTS engine to say hi to the caller using the supplied Caller ID Name.

Example 2: Dates

{"name":"fun day"
 ,"function_js":"var d = new Date(); var days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']; return {'module':'tts', 'data':{'text':days[d.getDay()] + ' fun day!'}};"
}

Javascript functionality available

As of now, ECMA-262 5th edition (“ES5”) of the language, is supported.

Fetch available functions on the system

Depending on the version of the KAZOO system running, the available functions may differ. Use this API to query the system for available functions.

GET /v2/functions

curl -v -X GET \
    -H "Content-Type:application/json" \
    -H "X-Auth-Token: {AUTH_TOKEN} \
    http://{SERVER}:8000/v2/functions

Get sample payloads of all function events

GET /v2/functions/samples

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/functions/samples

Example

Request:

curl -H 'Content-Type: application/json' 'http://{SERVER}:8000/v2/functions/samples'

List functions

GET /v2/accounts/{ACCOUNT_ID}/functions

Any functions with disable_reason in the summary has been auto-disabled.

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/functions

Create function

PUT /v2/accounts/{ACCOUNT_ID}/functions

curl -v -X PUT \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{"data": {
      "name":"Say hi!"
      ,"function_js":"return {"module":"tts", "data":{"text": "Thanks for calling " + call['To']}};"
    }}' \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/functions

Get details of the function

GET /v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

curl -v -X GET \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

Update the function

POST /v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

curl -v -X POST \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

Patch function

PATCH /v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

You can also patch an existing function:

curl -v -X PATCH \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{"data":{"enabled":true}}' \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

Delete a function

DELETE /v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

curl -v -X DELETE \
    -H "X-Auth-Token: {AUTH_TOKEN}" \
    http://{SERVER}:8000/v2/accounts/{ACCOUNT_ID}/functions/{FUNCTION_ID}

Administration

Enabling the API endpoint

To add the module to your auto-started list of API endpoints:

sup crossbar_maintenance start_module cb_functions

If you just want to play with it on a local node but not persist the endpoint on restart:

sup cb_functions init

CouchDB

We leverage CouchDB’s show function to provide hosting and processing of the user’s Javascript function.

The client will create a “function” document containing the Javascript function as a string. The show function functions/wh will be run with the “function“‘s doc id and the call will be placed in the request’s query string parameters.

The wh show function will create a new function (using Javascript’s new Function()) from the stringified version on the doc and pass it the query string object from the req argument. The user’s function should return the new flow JSON (as you would using Pivot).

Work around to enable new Function() in CouchDB

Javascript’s new Function(), which is similar to eval, is used to parse a piece of Javascript code in a string into a function object, which is invoked with the call object to return the new flow JSON. It is considered more secure than eval since the new Function() code runs in a separate scope.

However, both eval and new Function() are blocked by Content Security Policy in CouchDB (see this commit. A workaround has to be applied to enable new Function().

CouchDB 2.3

Make sure --eval is explicitly added under query_servers of etc/local.ini.

[query_servers]
COUCHDB_QUERY_SERVER_JAVASCRIPT="<absolute path>/bin/couchjs --eval <absolute path>/share/server/main.js"

If CouchDB is started by dev/run from the CouchDB repo, please update dev/run with

qs_javascript = toposixpath("%s --eval %s" % (couchjs, mainjs))
CouchDB 2.2 or earlier versions or BigCouch

Make sure --eval is explicitly added under query_servers of etc/local.ini.

[query_servers]
javascript = "<absolute path>/bin/couchjs --eval <absolute path>/share/server/main.js"
Security

It is recommended that you do not run the eval/new Function() enabled CouchDB instance in your main DB cluster.

Fortunately, KAZOO allows you to define alternate CouchDB connections for specific databases or database classifications. Since all function documents are stored in the functions aggregate database, you can create a connection just for the functions database that points to a entirely separated CouchDB server or cluster:

Create a 32-char UUID: > UUID = kz_binary:rand_hex(16). => "9ee5860b4d717907dfc63274af0d06e9"

Create the connection and storage plan:

{
  "connections": {
    "{UUID}": {
      "driver": "kazoo_couch",
      "name": "functions-connection",
      "settings": {
        "ip": "localhost",
        "port": 55984
      }
    }
  },
  "plan": {
    "aggregate": {
      "connection": "{UUID}",
      "database": {
        "names": [
          "functions"
        ]
      },
      "types": {
        "function": {
          "connection": "{UUID}"
        }
      }
    }
  }
}

Apply that to the global storage plan:

curl -v -X PATCH -H "X-Auth-Token: {ADMIN_AUTH_TOKEN}" http://localhost:8000/v2/storage \
-d '{"plan":{"aggregate":{"connection":"{UUID}","database":{"names":["functions"]},"types":{"function":{"connection":"{UUID}"}}}},"connections":{"{UUID}":{"driver":"kazoo_couch","name":"functions-connection","settings":{"ip":"localhost","port":55984}}}}'

Engineering

Other design options

Besides CouchDB’s show function, we could directly communicate with couchjs that is CouchDB’s external Javascript engine. The benefit is only to expose the eval risk to Functions instead of all views and show functions in CouchDB.

Erlang_V8 has been toyed with to accommodate Javascript.

In the long term, to support a range of languages, we might look into Docker based web server to bootstrap functions implemented by the language of choice.