REST Endpoints

Interaction with a running instance of kotekan takes place via a set of REST endpoints. New stages should adhere to some basic conventions for where to put them and how to interact.

Both GET and POST endpoints are supported, messages to the latter should be formatted as json strings.

Endpoints should follow a standard structure, to help people locate them:

Framework

The kotekan framework registers a number of points

/start [POST]

Request an idle instance to begin operation, requires a (json-encoded) config to be passed.

/stop [GET]

Tell an active kotekan to shut down and clean up its current running configuration.

/status [GET]

returns the state of the system (active or not).

/config [GET]

Returns the current system configuration.

/config_md5sum [GET]

Returns an MD5 hash of the config file (based on the json string with no spaces). Only exists if kotekan was build with OpenSSL support included

/version [GET]

Returns the current kotekan version information; including build options.

/endpoints [GET]

Returns all available REST endpoints in the system.

/metrics [GET]

Returns text containing Prometheus-formatted metrics which serve a host of system state properties.

Per-stage

Individual stages can register REST endpoints using code similar to:

using namespace std::placeholders;
restServer &rest_server = restServer::instance();
// register a POST endpoint
rest_server.register_post_callback(unique_name + "/my_post_endpoint",
        std::bind(&myKotekanPorcess::endpoint_callback_func, this, _1, _2));
// register a GET endpoint
rest_server.register_get_callback(unique_name + "/my_get_endpoint",
        std::bind(&myKotekanPorcess::endpoint_callback_func, this, _1));

Stages should always register endpoints relative to /unique_name.

The endpoint should be removed in the destructor of the stage registering it:

restServer &rest_server = restServer::instance();
// Remove a GET call back
rest_server.remove_get_callback(unique_name + "/my_get_endpoint");
// Remove a POST call back
rest_server.remove_json_callback(unique_name + "/my_post_endpoint");

Shared Endpoints

If several stages need to share one endpoint, the endpoint can be created by the configUpdater.

class configUpdater

Kotekan core component that creates endpoints defined in the config that stages can subscribe to to receive updates.

An endpoint will be created for every updatable config block defined in the configuration file. updatable blocks can be anywhere in the configuration tree, but may not be inside another updatable block. They need to contain a key kotekan_update_endpoint with the value "json". They also need to contain initial values for all fields that subscribing stages will expect on an update.

Example:

foo:
    bar:
        kotekan_update_endpoint: "json"
        some_value: 0
        some_other_value: 1

In the config block of a stage that wants to get updates of a specific updatable block, either a key “updatable_config” with the full path to the updatable block as a value has to exist, or an object called “updatable_config” with a list of all updatable config blocks.

Example:

my_stage:
    updatable_config: "/foo/bar"
or
my_stage:
    updatable_config:
        bar: "/foo/bar"
        fu: "/foo/fu"

Every stage that subscribes to this update endpoint by calling

configUpdater config_updater = configUpdater::instance();
config_updater.subscribe(this, std::bind(&my_stage::my_callback, this, _1));
or
std::map<std::string, std::function<bool(nlohmann::json &)> callbacks;

callbacks["bar"] = std::bind(&my_stage::my_bar_callback, this, _1);

callbacks["fu"] = std::bind(&my_stage::my_fu_callback, this, _1);

configUpdater::instance().subscribe(this, callbacks);
will receive an initial update on each callback function with the initial values defined in the config file (in the first example {"some_value": 0, some_other_value: 1}). That’s why the stage must be ready to receive updates before it subscribes.

All and only the variables defined in the updatable config block in the config file are guaranteed to be in the json block passed to the stage callback function. It is up to the stage, though, to check the data types, sizes and the actual values in the callback function and return false if anything is wrong.

The stage must be ready to receive updates before it subscribes and it has to apply save threading principles.

Author

Rick Nitsche

Public Functions

configUpdater(const configUpdater&) = delete
void operator=(const configUpdater&) = delete
void apply_config(Config &config)

Set and apply the static config to configUpdater.

Parameters:

config – The config.

void reset()

Reset the configUpdater.

Removes all REST endpoints and clears all memory of subscribers and endpoints. This should be called before destruction of the subscribers, to prevent the callbacks being called afterwards.

void subscribe(const kotekan::Stage *subscriber, std::function<bool(nlohmann::json&)> callback)

Subscribe to the updatable blocks of a Kotekan Stage.

The callback function has to return True on success and False otherwise. The block of the calling stage in the configuration file should have a key named “updatable_config” that defines the full path to the updatable block. As usual if not found at that level, it will search up the tree.

Parameters:
  • subscriber – Reference to the subscribing stage.

  • callback – Callback function for attribute updates.

void subscribe(const kotekan::Stage *subscriber, std::map<std::string, std::function<bool(nlohmann::json&)>> callbacks)

Subscribe to all updatable blocks of a Kotekan Stage.

The callback functions have to return True on success and False otherwise. The block of the calling stage in the configuration file should have an object named “updatable_config” with values that define the full path to an updatable block, each. The names in the callbacks map refer to the names of these values. If an “updatable_config” block is not found in the current stage it will search up the config tree, but all callback keys must be contained within this single block.

Parameters:
  • subscriber – Reference to the subscribing stage.

  • callbacks – Map of value names and callback functions.

void subscribe(const std::string &name, std::function<bool(nlohmann::json&)> callback)

Subscribe to an updatable block.

This function does not enforce the config structure and should only be used in special cases (Like when called from somewhere else than a Kotekan Stage). The callback function has to return True on success and False otherwise.

Parameters:
  • name – Name of the dynamic attribute.

  • callback – Callback function for attribute updates.

void rest_callback(connectionInstance &con, nlohmann::json &json)

This should be called by restServer.

Public Static Functions

static configUpdater &instance()

Get the global configUpdater.

Returns:

A reference to the global configUpdater instance.

Aliases

To make things easier to access, it is possible to define aliases to endpoints in the config under the aliases: block in the rest_server block:

rest_server:
    aliases:
        new_name: existing_endpoint

The above maps /new_name to /existing_endpoint

The list of aliases available is given by the /endpoints endpoint.

CPU Affinity

The CPU affinity defaults to the global cpu_affinity: property

To override that and pin it to say cores 3,4:

rest_server:
    cpu_affinity: [3,4]

Binding and Addresses

The REST server binds on a specific address and port when started:

  • Use restServer::start(bind_address, port) to select the interface and port.

  • The helper restServer::isValidAddress() performs a syntactic validation of the address string before attempting to bind.

Valid address forms:

  • IPv4 literals only, e.g., 127.0.0.1, 0.0.0.0

Not accepted by isValidAddress():

  • Addresses including ports: 127.0.0.1:8080, example.com:12048

  • IPv6 literals: e.g., ::1, 2001:db8::1 (bare address, no brackets)

  • Hostnames, e.g. localhost, example-host, sub.domain

  • URIs with schemes: http://example.com

  • Bracketed IPv6 forms: [::1]

To verify that an address and port can actually be used on the host, call restServer::canBindToAddress(bind_address, port).