1 - Configuration JSON Overview

A brief overview of the configuration JSON and its components

Configuration of ESB3024 Router is done by PUT:ing a JSON object to a REST API located at https://<router-host>:5001/v2/configuration by default.

This document will go through the JSON object, briefly describe its fields and link to more in-depth documentation where appropriate. For information on the JSON language syntax, please refer to the official JSON documentation.

Top-level fields

Here we’ll go through all the fields that may exist at the first level of the JSON object. In the following sections we’ll explore some of them in more detail while some others are complex enough to warrant their own pages.

  • "cdns" (array, objects)
    A list of all CDNs associated with this router, described further down.
  • "content_server" (object)
    Parameters for the content HTTP interface, described further down.
  • "custom_lua" (string)
    Container-specific path to the parent folder under which the /v1/lua/<path str>.lua PUT endpoint will cache the uploaded Lua script files with relative paths as specified by the <path str>.lua argument. Uploaded Lua scripts are then loaded into the Lua environment available in request/response translation functions as well as routing weight functions. The /v1/lua/<path str>.lua PUT endpoint will attempt to create the folder and any required subdirectories on the specified path, should they not exist. Full read-write access for the router need to be maintained to the parent folder, so that the configured folder can be created by the router, to the configured folder itself, its children and every contained .lua script.
  • "hosts" (array, objects)
    A list of all hosts available in the CDNs listed above, described further down.
  • "id" (string)
    Always "ACD", read only.
  • "image_tag" (string)
    A string unique to the version of software installed, read only.
  • "managed_sessions" (object)
    Settings for how to configure managed sessions, described further down.
  • "metadata"(object)
    Contains metadata about the configuration, described further down.
  • "request_translation_function" (string)
    A Lua function body for changing incoming HTTP requests, described further down.
  • "response_translation_function" (string)
    A Lua function body for changing outgoing HTTP responses, described further down.
  • "rest_api_server" (object)
    Settings for the API endpoint used for e.g. PUT:ing the configuration, described further down.
  • "routing" (object)
    Client routing rules, described further down.
  • "session_groups" (array, objects)
    Classification rules for incoming requests, described further down.
  • "settings" (object)
    Settings for functionality that can be enabled or disabled, described further down.
  • "standard_lua" (string)
    Container-specific path to a folder containing pre-installed Lua script files that are loaded into the Lua environment available in request/response translation functions as well as routing weight functions.
  • "tuning" (object)
    Adjustments of timings, log level, thresholds etc, described further down.
  • "version" (string)
    Always "v2", read only.

"cdns"

A list containing CDN definitions. Each object contains the following data:

  • "disabled_hosts" (array, strings)
    Hostnames temporarily taken out of routing, e.g. due to maintenance or because they are known to be broken. For redirecting CDNs, these hosts are added to an Exclude-Host header telling the CDN not to use them.
  • "http_port" (integer)
    The HTTP port the hosts in the CDN listen to.
  • "https_port" (integer)
    The HTTPS port the hosts in the CDN listen to.
  • "id" (string, unique)
    Unique internal name used to reference the CDN within the router.
  • "manifest_availability_check" (object)
    Specifies if a CDN should be checked for manifest availability, and if so for which session groups.
    • "enabled" (boolean)
      Toggle availability check.
    • "session_group_ids" (array, integers)
      List of the session groups for which to perform check.
  • "redirecting" (boolean)
    Setting to true makes the router request a redirect URL from the CDN to forward to the client, otherwise the router constructs its own redirect response with the selected host in the Location header.
  • "response_code_rules" (array, objects)
    List of response actions per matching response code from redirecting CDN. Each object contains the following data:
    • "pattern" (string)
      Sequence of digits and / or wildcards in the form of the letter x. Example: "4xx" which matches response codes 400-499.
    • "action" (string)
      Specifies which action to take when the response code from a redirecting CDN matches the pattern. Valid values are "default", which lets the router proceed as-if no pattern was matching, "continue", to proceed with selecting the next host in the CDN, "redirect" to let the router redirect the request to the URL returned by the redirecting CDN, and "proxy" which returns the response from the redirecting CDN back to the client as-if the response originated from the router itself.
    • "new_response_code" (integer)
      The overridden response code to send back to the client irrespective of "action". It replaces the response code in the otherwise verbatim response for a selected "proxy" action.
  • "timeout_action" (string)
    Specifies which action to take when the request to the redirecting CDN times out without detecting a response. Valid values are "abort" which stops selection, and "continue" which continues with selecting the next host in the CDN.

"content_server"

Settings for the routing interface that clients request content through.

  • "http_enable" (boolean)
    Toggles if HTTP interface is available or not.
  • "http_port" (integer)
    Which port the HTTP interface should listen on.
  • "https_enable" (boolean)
    Toggles if encrypted HTTPS interface is available or not.
  • "https_port" (integer)
    Which port the HTTPS interface should listen on.

"hosts"

A list containing CDN host definitions. Each object contains the following data:

  • "cdn_id" (string)
    A reference to a CDN in the cdns array.
  • "host" (string)
    The IP or hostname for the host.
  • "ipv6_address" (string)
    If the client connects via IPv6 or has put an IPv6 address in the X-Forwarded-For header, then the router will connect to the CDN using the address from ipv6_address. If ipv6_address is not given, the connection will be made using the address from host.
  • "id" (string)
    A unique identifier used in the routing rule tree.
  • "proxy_url" (string)
    When using EDNS routing, the incoming request is sent to a proxy server located at proxy_url that creates and sends an EDNS request based on the client’s HTTP request. A default installation of ESB3024 includes an EDNS proxy located at http://localhost:8888.

"managed_sessions"

Settings for managed client sessions.

  • "fraction" (number)
    The fraction of sessions to be managed, between 0.0 and 1.0.
  • "max_active" (integer)
    The maximum number of active managed sessions.
  • "session_types" (array, objects)
    A list of the various types of managed sessions and their respective settings. Each object contains the following data:
    • "enabled" (boolean)
      description: Toggle this session type.
    • "max_active" (integer)
      The maximum number of active sessions belonging to this session type.
    • "session_group_ids" (array, integers)
      The session groups allowed to belong to this session type, or empty if any.
    • "type" (string)
      The type of managed session. The only valid value is "instream".

"metadata"

Metadata mostly generated from the configuration.

  • "etag" (string)
    The MD5 hash of the configuration’s size in memory.
  • "extra_info" (object)
    This field can store any arbitrary key-value pairs, e.g. to tag or name a configuration. The only configurable field in "metadata".
  • "source_ip" (string)
    The IP address from which the configuration was sent.
  • "timestamp"(string)
    The ISO-8601 formatted timestamp of when the configuration was applied.

"request_translation_function"

The content of this field is a Lua function body that can be used to modify an incoming request before passing it on to be classified and routed. See Lua Hooks for details.

"response_translation_function"

The content of this field is a Lua function body that can be used to modify an outgoing response before it is sent back to the requesting client. See Lua Hooks for details.

"rest_api_server"

Settings for the configuration REST endpoint itself.

  • "enable" (boolean)
    Determines whether the REST API is turned on or not.
  • "port" (integer)
    HTTPS port to listen to.

"routing"

A tree structure of objects setting up a routing algorithm. See Routing Engine for more details.

"session_groups"

A list containing session group definitions. See Session Groups and Classification for details.

"settings"

Various behavioral settings related to content requests and CDN requirements.

  • "reversed_filename_comparison" (object)
    Compares segment filenames from the end when trying to match requests with content. This allows the router to be compatible with CDNs that put e.g. a timestamp first in the filename.
    • "enabled" (boolean)
      Toggle Akamai support on or off.
  • "allowed_clients" (array, strings)
    If non-empty, this array contains all IP:s proxies that are allowed to use the X-Forwarded-For HTTP header.
  • "dash_manifest_rewrite" (object)
    Rewrite DASH manifests to use absolute URLs for clients that don’t support HTTP redirect properly.
    • "enabled" (boolean)
      Enable or disable DASH manifest rewrite.
    • "session_group_ids" (array, integers)
      A list of session groups for which to perform manifest rewrite so it only has to be done for e.g. a set of user agents known to be faulty.
    • "extended_content_identifier" (object)
      Sets a list of query parameters that may be used to identify a specific content along with the path, used for caching.
      • "enabled" (boolean)
        Turns on or off query parameter content identifiers.
      • "included_query_params" (array, strings)
        The list of query parameter names to allow.
  • "hls_manifest_rewrite" (object)
    Rewrite HLS manifests to use absolute URLs for clients that don’t support HTTP redirect properly.
    • "enabled" (boolean)
      Enable or disable DASH manifest rewrite.
    • "session_group_ids" (array, integers)
      A list of session groups for which to perform manifest rewrite so it only has to be done for e.g. a set of user agents known to be faulty.
  • "usage_log" (object)
    Usage logging settings. By default, usage logs such as the number of redirected requests handled during a time period are written to. "/opt/edgeware/acd/router/log/usage.log" unless the environment variable USAGE_LOGGING_DIR has been set to something else.
    • "enabled" (boolean)
      Enables usage logging.
    • "log_interval" (integer)
      Number of milliseconds between each usage log write.

"tuning"

Settings for tuning the behavior of the router.

  • "access_log" (boolean)
    Enable or disable access logging.
  • "content_cache_size_full_manifests" (integer)
    Size of cache for storing full manifests and associated metadata, in number of manifests.
  • "content_cache_size_light_manifests" (integer)
    Size of cache for storing simplified manifest metadata, in number of manifests.
  • "content_light_cache_time_milliseconds" (integer)
    Number of milliseconds before a light manifest content expires.
  • "content_live_cache_time_milliseconds" (integer)
    Number of milliseconds before a full manifest live content expires.
  • "content_vod_cache_time_milliseconds" (integer)
    Number of milliseconds before a full manifest VOD content expires.
  • "cout_flush_rate_milliseconds" (integer)
    How often to flush logs to stdout even if buffer isn’t full to make sure logs arrive at a timely fashion.
  • "cpu_load_window_size" (integer)
    Window size used to calculate a running average of the CPU load.
  • "eager_cdn_switching" (boolean)
    Tells the router to evaluate the routing tree for every request of a session, even if there are no known issues for the client.
  • "http_pipelining_enable" (boolean)
    Allows the router to send requests to the CDN while waiting for responses to earlier requests.
  • "log_level" (integer)
    The verbosity of log printouts. Higher value means more logs: 0 -> Critical, 1 -> Error, 2 -> Warnings, 3 -> Info, 4 -> Debug, 5 -> Trace.
  • "max_connections_per_host" (integer)
    The number of connections the router is allowed to have to a single CDN host.
  • "overload_threshold" (integer)
    How long the task queue may be before router is considered overloaded.
  • "ready_threshold" (integer)
    How short the task queue must become before an overloaded router is no longer considered overloaded.
  • "redirecting_cdn_manifest_download_retries" (integer)
    Number of times the router can try requesting a manifest or redirect location from a CDN before giving up.
  • "repeated_session_start_threshold_seconds" (integer)
    How long time a session must wait between requests to avoid being flagged for repeating requests too often, possibly leading to lowered quality of service.
  • "selection_input_item_limit" (integer)
    The maximum number of key:value items that can be stored on the router using the /v1/selection_input API. Only the leaf values in the input JSON object count towards this limit.
  • "selection_input_metrics_timeout_seconds" (integer)
    How many seconds a value send to the /v1/selection_input API remains before it’s considered obsolete.
  • "session_idle_deactivate_timeout_milliseconds" (integer)
    Number of milliseconds between requests before a session is considered idle.
  • "session_idle_delete_timeout_milliseconds" (integer)
    Number of milliseconds an idle session stays alive to be resumed before it is purged.
  • "target_connect_timeout_seconds" (integer)
    Number of seconds allowed to establish a connection to a CDN.
  • "target_max_idle_time_seconds" (integer)
    Number of seconds a connection to a CDN is kept alive even if no new requests occur.
  • "target_request_attempts" (integer)
    Number of times the router will make requests to different hosts, when it gets 5xx error responses, before giving up and sending an error response to the client.
  • "target_response_timeout_seconds" (integer)
    Number of seconds before a request to a CDN times out.
  • "target_retry_connect_timeout_seconds" (integer)
    Number of seconds allowed when retrying to establish a connection to a CDN.
  • "target_retry_response_timeout_seconds" (integer)
    Number of seconds before a retry request to a CDN times out.

A Full Configuration Example

{
  "cdns": [
    {
      "disabled_hosts": [],
      "http_port": 80,
      "https_port": 443,
      "id": "allowed-cdn",
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      },
      "redirecting": false
    },
    {
      "disabled_hosts": [],
      "http_port": 80,
      "https_port": 443,
      "id": "offload-cdn",
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      },
      "redirecting": false
    }
  ],
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "custom_lua": "/tmp/custom_lua",
  "hosts": [
    {
      "cdn_id": "allowed-cdn",
      "host": "allowed-host4.example",
      "id": "allowed-host-4"
    },
    {
      "cdn_id": "allowed-cdn",
      "host": "allowed-host6.example",
      "id": "allowed-host-6"
    },
    {
      "cdn_id": "offload-cdn",
      "host": "offload-host.example",
      "id": "offload-host"
    }
  ],
  "managed_sessions": {
    "fraction": 0.0,
    "max_active": 0,
    "session_types": [
      {
        "enabled": false,
        "max_active": 0,
        "session_group_ids": [],
        "type": "instream"
      }
    ]
  },
  "metadata": {
    "extra_info": {
      "field_1": "value_1",
      "config_name": "large_config",
      "purpose": "demonstration",
      "arbitrary": "value"
    }
  },
  "request_translation_function": "",
  "response_translation_function": "",
  "rest_api_server": {
    "enable": true,
    "port": 5001
  },
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "test-subnet-name",
        "member_order": "weighted",
        "members": [],
        "url_rewrite_function": "",
        "weight_function": "print(tostring(request.subnet)); return 0"
      },
      {
        "id": "test-host-4",
        "member_order": "weighted",
        "members": [],
        "url_rewrite_function": "",
        "weight_function": "retval = request.subnet == 'test_net_4' and 1 or 0;\
            if retval == 1 then print('### ipv4') end; return 0"
      },
      {
        "id": "test-host-6",
        "member_order": "weighted",
        "members": [],
        "url_rewrite_function": "",
        "weight_function": "retval = request.subnet == 'test_net_6' and 1 or 0;\
            if retval == 1 then print('### ipv6') end; return 0"
      },
      {
        "id": "test-offload",
        "member_order": "weighted",
        "members": [],
        "url_rewrite_function": "",
        "weight_function": "retval = ((request.subnet == 'test_net_6')\
            or (request.subnet == 'test_net_4')) and 1 or 0;\
            if retval == 0 then print('### offload') end; return 0"
      },
      {
        "id": "allowed-host-4",
        "member_order": "weighted",
        "members": [],
        "url_rewrite_function": "",
        "weight_function": "return request.subnet == 'test_net_4' and 1 or 0"
      },
      {
        "id": "allowed-host-6",
        "member_order": "weighted",
        "members": [],
        "url_rewrite_function": "",
        "weight_function": "return request.subnet == 'test_net_6' and 1 or 0"
      },
      {
        "id": "offlaod-host",
        "member_order": "weighted",
        "members": [],
        "url_rewrite_function": "",
        "weight_function": "return 1"
      }
    ],
    "url_rewrite_function": "",
    "weight_function": "return 100"
  },
  "session_groups": [
    {
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "hls",
            "rule": {
              "pattern": "/([^\\.]+)\\.news_reports_\\d+/\\.example\\.com.*",
              "rule_type": "regex_rule",
              "source": "session/content_url_path"
            }
          }
        ]
      ],
      "id": 1,
      "name": "hls"
    },
    {
      "classifiers": [
        [
          {
            "id": 2,
            "inverted": false,
            "name": "vod",
            "rule": {
              "pattern": "*vod*",
              "rule_type": "string_match_rule",
              "source": "session/content_url_path"
            }
          }
        ]
      ],
      "id": 2,
      "name": "vod"
    },
    {
      "classifiers": [
        [
          {
            "id": 5,
            "inverted": true,
            "name": "offload_no_peering_subnet",
            "rule": {
              "ip_ranges": [
                "158.174.0.0/16",
                "95.192.0.0/12"
              ],
              "rule_type": "ip_ranges_rule",
              "source": "session/client_ip"
            }
          }
        ]
      ],
      "id": 5,
      "name": "offload_no_peering_subnet"
    },
    {
      "classifiers": [
        [
          {
            "id": 6,
            "inverted": true,
            "name": "offload_country_name",
            "rule": {
              "country": "Sweden",
              "rule_type": "geoip_rule",
              "source": "session/client_ip"
            }
          }
        ]
      ],
      "id": 6,
      "name": "offload_country_name"
    },
    {
      "classifiers": [
        [
          {
            "id": 7,
            "inverted": false,
            "name": "onload_city_name",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip"
            }
          }
        ]
      ],
      "id": 7,
      "name": "onload_city_name"
    },
    {
      "classifiers": [
        [
          {
            "id": 8,
            "inverted": false,
            "name": "offload_no_peering_asn_name",
            "rule": {
              "asn": "Tele*",
              "rule_type": "geoip_rule",
              "source": "session/client_ip"
            }
          }
        ]
      ],
      "id": 8,
      "name": "offload_no_peering_asn_name"
    }
  ],
  "settings": {
    "reversed_filename_comparison": {
      "enabled": false
    },
    "dash_manifest_rewrite": {
      "enabled": false,
      "session_group_ids": []
    },
    "extended_content_identifier": {
      "enabled": false,
      "included_query_params": []
    },
    "hls_manifest_rewrite": {
      "enabled": false,
      "session_group_ids": []
    }
  },
  "standard_lua": "/tmp/standard_lua",
  "tuning": {
    "access_log": false,
    "content_cache_size_full_manifests": 1000,
    "content_cache_size_light_manifests": 10000,
    "content_light_cache_time_milliseconds": 86400000,
    "content_live_cache_time_milliseconds": 5000,
    "content_vod_cache_time_milliseconds": 10000,
    "cout_flush_rate_milliseconds": 1000,
    "cpu_load_window_size": 10,
    "eager_cdn_switching": false,
    "http_pipelining_enable": false,
    "log_level": 5,
    "max_connections_per_host": 5,
    "overload_threshold": 32,
    "ready_threshold": 8,
    "redirecting_cdn_manifest_download_retries": 2,
    "repeated_session_start_threshold_seconds": 30,
    "selection_input_metrics_timeout_seconds": 86400,
    "session_idle_deactivate_timeout_milliseconds": 20000,
    "session_idle_delete_timeout_milliseconds": 1800000,
    "target_connect_timeout_seconds": 1,
    "target_max_idle_time_seconds": 30,
    "target_request_attempts": 2,
    "target_response_timeout_seconds": 1,
    "target_retry_connect_timeout_seconds": 2,
    "target_retry_response_timeout_seconds": 2
  }
}

2 - WebUI Configuration

How to use the web user interface for configuration.

The web based user interface is installed as a separate component and can be used to configure many common use cases. After navigating to the UI, a login screen will be presented.

Login Screen

Enter your credentials and log in. In the top left corner is a menu to select what section of the configuration to change. The configuration that will be active on the router is added in the Routing Workflow view. However, basic elements such as classification rules and routing targets, etc must be added first. Hence the following main steps are required to produce a proper configuration:

  1. Create classifiers serving as basic elements to create session groups.
  2. Create session groups which, using the classifiers, tag requests/clients for later use in the routing logic. of the incoming traffic.
  3. Define offload rules.
  4. Define rules to control behavior of internal traffic.
  5. Define backup rules to be used if the routing targets in the above step are unavailable.
  6. Finally, create the desired routing workflow using the elements defined in the previous steps.

A simplified concrete example of the above steps could be:

  • Create two classifiers “smartphone” and “off-net”.
  • Create a session group “mobile off-net”.
  • Offload off-net traffic from mobile phones to a public CDN.
  • Route other traffic to a private CDN.
  • If the private CDN has an outage, use the public CDN for all traffic.

Hence, to start with, define the classifiers you will need. Those are based on information in the incoming request, optionally in combination with GeoIP databases or subnet information configured via the Subnet API. Here we show how to set up a GeoIP classifier. Note that the Director ships with a compatible snapshot of the GeoIP database, but for a production system a licensed and updated database is required.

GeoIP Classifier

Click the plus sign indicated in the picture above to create a new GeoIP classifier. You will be presented with the following view:

GeoIP Classifier Create

Here you can enter the geographical data on which to match, or check the “Inverted” check box to match anything except the entered geographical data.

The other kinds of classifiers are configured in a similar way.

After having added all the classifiers you need, it is time to create the session groups. Those are named filters that group incoming requests, typically video playback sessions in a video streaming CDN, and are defined with the help of the classifiers. For example, a session group “off-net mobile devices” could be composed of the classifiers “off-net traffic” and “mobile devices”.

Open the Session Groups view from the menu and hit the plus sign to add a new session group.

Session Groups Session Group Create

Define the new sessions groups by combining the previously created classifiers. It is often convenient to define an “All” session group that matches any incoming request.

Next go the “CDN Offload” view:

CDN Offload

Here you define conditions for CDN offload. Each row defines a rule for offloading a specified session group. The rule makes use of the Selection Input API. This is an integration API that provides a way to supply additional data for use in the routing decision. Common examples are current bitrates or availability status. The selection input variables to use must be defined in the “Selection Input Types” view in the “Administration” section of the menu:

Selection Input Types

Reach out to the solution engineers from Agile Content in order to perform this integration in the best way. If no external data is required, such that the offload rule can be based solely based on session groups, this is not necessary and the condition field can be set to “Always” or “Disabled”.

When clicking the plus sign to add a new CDN Offload rule, the following view is presented:

CDN Offload Create

The selection input rule is phrased in terms of a variable being above or below a threshold, but also a state such as “available” taking values 0 or 1 can be supported by for instance checking if “available” is below 1.

Moving on, if an incoming request is not offloaded, it will be handled by the Primary CDN section of the routing configuration.

Primary CDN

Add all hosts in your primary CDN, together with a weight. A row in this table will be selected by random weighted load balancing. If each weight is the same, each row will be selected with the same probability. Another example would be three rows with weights 100, 100 and 200 which would randomly balance 50% of the load on the last row and the remaining load on the first two rows, i.e. 25% on each of the first and second row. If a Primary CDN host is unavailable, that host will not take part in the random selection.

If all hosts are unavailable, as a final resort the routing evaluation will go to the final Backup CDN step:

Backup CDN

Here you can define what to do when all else fail. If not all requests are covered, for example with an “All” session group, then the request will fail with 403 Forbidden.

Now you have defined the basic elements and it is time to define the routing workflow. Select “Routing Workflow” from the menu, as pictured below. Here you can combine the elements previously created to achieve the desired routing behavior.

Routing Workflow

When everything seems correct, open the “Publish Routing” view from the menu:

Publish Routing

Hit “Publish All Changes” and verify that you get a successful result.

3 - Confd and Confcli

Using the command line tool confcli to set up routing rules

Configuration of a complex routing tree can be difficult. The command line interface tool called confcli has been developed to make it simpler. It combines building blocks, representing simple routing decisions, into complex routing trees capable of satisfying almost any routing requirements.

These blocks are translated into an ESB3024 Router configuration which is automatically sent to the router, overwriting existing routing rules, CDN list and host list.

Installation and Usage

The confcli tools are installed alongside ESB3024 Router, on the same host, and the confcli command line tool itself is made available on the host machine.

Simply type confcli in a shell on the host to see the current routing configuration:

$ confcli
{
    "services": {
        "routing": {
            "settings": {
                "allowedProxies": [],
                "contentPopularity": {
                    "algorithm": "score_based",
                    "sessionGroupNames": []
                },
                "extendedContentIdentifier": {
                    "enabled": false,
                    "includedQueryParams": []
                },
                "instream": {
                    "dashManifestRewrite": {
                        "enabled": false,
                        "sessionGroupNames": []
                    },
                    "hlsManifestRewrite": {
                        "enabled": false,
                        "sessionGroupNames": []
                    },
                    "reversedFilenameComparison": false
                },
                "usageLog": {
                    "enabled": false,
                    "logInterval": 3600000
                }
            },
            "tuning": {
                "content": {
                    "cacheSizeFullManifests": 1000,
                    "cacheSizeLightManifests": 10000,
                    "lightCacheTimeMilliseconds": 86400000,
                    "liveCacheTimeMilliseconds": 100,
                    "vodCacheTimeMilliseconds": 10000
                },
                "general": {
                    "accessLog": false,
                    "coutFlushRateMilliseconds": 1000,
                    "cpuLoadWindowSize": 10,
                    "eagerCdnSwitching": false,
                    "httpPipeliningEnable": false,
                    "logLevel": 3,
                    "maxConnectionsPerHost": 5,
                    "overloadThreshold": 32,
                    "readyThreshold": 8,
                    "redirectingCdnManifestDownloadRetries": 2,
                    "repeatedSessionStartThresholdSeconds": 30,
                    "selectionInputMetricsTimeoutSeconds": 30
                },
                "session": {
                    "idleDeactivateTimeoutMilliseconds": 20000,
                    "idleDeleteTimeoutMilliseconds": 1800000
                },
                "target": {
                    "responseTimeoutSeconds": 5,
                    "retryConnectTimeoutSeconds": 2,
                    "retryResponseTimeoutSeconds": 2,
                    "connectTimeoutSeconds": 5,
                    "maxIdleTimeSeconds": 30,
                    "requestAttempts": 3
                }
            },
            "sessionGroups": [],
            "classifiers": [],
            "hostGroups": [],
            "rules": [],
            "entrypoint": "",
            "applyConfig": true
        }
    }
}

The CLI tool can be used to modify, add and delete values by providing it with the “path” to the object to change. The path is constructed by joining the field names leading up to the value with a period between each name, e.g. the path to the entrypoint is services.routing.entrypoint since entrypoint is nested under the routing object, which in turn is under the services root object. Lists use an index number in place of a field name, where 0 indicates the very first element in the list, 1 the second element and so on.

If the list contains objects which have a field with the name name, the index number can be replaced by the unique name of the object of interest.

Tab completion is supported by confcli. Pressing tab once will complete as far as possible, and pressing tab twice will list all available alternatives at the path constructed so far.

Display the values at a specific path:

$ confcli services.routing.hostGroups
{
    "hostGroups": [
        {
            "name": "internal",
            "type": "redirecting",
            "httpPort": 80,
            "httpsPort": 443,
            "hosts": [
                {
                    "name": "rr1",
                    "hostname": "rr1.example.com",
                    "ipv6_address": ""
                }
            ]
        },
        {
            "name": "external",
            "type": "host",
            "httpPort": 80,
            "httpsPort": 443,
            "hosts": [
                {
                    "name": "offload-streamer1",
                    "hostname": "streamer1.example.com",
                    "ipv6_address": ""
                },
                {
                    "name": "offload-streamer2",
                    "hostname": "streamer2.example.com",
                    "ipv6_address": ""
                }
            ]
        }
    ]
}

Display the values in a specific list index:

$ confcli services.routing.hostGroups.1
{
    "1": {
        "name": "external",
        "type": "host",
        "httpPort": 80,
        "httpsPort": 443,
        "hosts": [
            {
                "name": "offload-streamer1",
                "hostname": "streamer1.example.com",
                "ipv6_address": ""
            },
            {
                "name": "offload-streamer2",
                "hostname": "streamer2.example.com",
                "ipv6_address": ""
            }
        ]
    }
}

Display the values in a specific list index using the object’s name:

$ confcli services.routing.hostGroups.1.hosts.offload-streamer2
{
    "offload-streamer2": {
        "name": "offload-streamer2",
        "hostname": "streamer2.example.com",
        "ipv6_address": ""
    }
}

Modify a single value:

confcli services.routing.hostGroups.1.hosts.offload-streamer2.hostname new-streamer.example.com
services.routing.hostGroups.1.hosts.offload-streamer2.hostname = 'new-streamer.example.com'

Delete an entry:

$ confcli services.routing.sessionGroups.Apple.classifiers.
{
    "classifiers": [
        "Apple",
        ""
    ]
}

$ confcli services.routing.sessionGroups.Apple.classifiers.1 -d
http://localhost:5000/config/__active/services/routing/sessionGroups/Apple/classifiers/1 reset to default/deleted

$ confcli services.routing.sessionGroups.Apple.classifiers.
{
    "classifiers": [
        "Apple"
    ]
}

Adding new values in objects and lists is done using a wizard by invoking confcli with a path and the -w argument. This will be shown extensively in the examples further down in this document rather than here.

If you have a JSON file with a previously generated confcli configuration output it can be applied to a system by typing confcli -i <file path>.

Session Classification

In order to perform routing it is necessary to classify incoming sessions according to the relevant parameters. This is done through session groups and their associated classifiers.

There are different ways of classifying a request:

  • Strings with wildcards: Simple case-insensitive string pattern with support for adding asterisks (’*’) in order to match any value at that point in the pattern.
  • String with regular expressions: A complex string matching pattern capable of matching more complicated strings than the simple wildcard matching type.

Valid string matching sources are content_url_path, content_url_query_parameters, hostname and user_agent, examples of which will be shown below.

  • GeoIP: Based on the geographic location of the client, supporting wildcard matching. Geographic location data is provided by MaxMind. The possible values to match with are any combinations of:
    • Continent
    • Country
    • Cities
    • ASN
  • IP range: Based on whether a client’s IP belongs to a set of IP ranges or not.
  • Subnet: Tests if a client’s IP belongs to a named subnet, see Subnets for more details.
  • ASN ID list: Checks to see if a client’s IP belongs to any of the specified ASN IDs.

A session group may have more than one classifier. If it does, all the classifiers must match the incoming client request for it to belong to the session group. It is also possible for a request to belong to multiple session groups, or to none.

To send certain clients to a specific host you first need to create a suitable classifier using confcli in wizard mode. The wizard will guide you through the process of creating a new entry, asking you what value to input for each field and helping you by telling you what inputs are allowed for restricted fields such as the string comparison source mentioned above:

$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 3
  Adding a 'geoip' element
    classifier : {
      name (default: ): sweden_matcher
      type (default: geoip): ⏎
      inverted (default: False): ⏎
      continent (default: ): ⏎
      country (default: ): sweden
      cities : [
        city (default: ): ⏎
        Add another 'city' element to array 'cities'? [y/N]: ⏎
      ]
      asn (default: ): ⏎
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "sweden_matcher",
      "type": "geoip",
      "inverted": false,
      "continent": "",
      "country": "sweden",
      "cities": [
        ""
      ],
      "asn": ""
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 5
  Adding a 'ipranges' element
    classifier : {
      name (default: ): company_matcher
      type (default: ipranges): ⏎
      inverted (default: False): ⏎
      ipranges : [
        iprange (default: ): 90.128.0.0/12
        Add another 'iprange' element to array 'ipranges'? [y/N]: ⏎
      ]
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "company_matcher",
      "type": "ipranges",
      "inverted": false,
      "ipranges": [
        "90.128.0.0/12"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 7
  Adding a 'stringMatcher' element
    classifier : {
      name (default: ): apple_matcher
      type (default: stringMatcher): ⏎
      inverted (default: False): ⏎
      source (default: content_url_path): user_agent
      pattern (default: ): *apple*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "apple_matcher",
      "type": "stringMatcher",
      "inverted": false,
      "source": "user_agent",
      "pattern": "*apple*"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 6
  Adding a 'regexMatcher' element
    classifier : {
      name (default: ): content_matcher
      type (default: regexMatcher): ⏎
      inverted (default: False): ⏎
      source (default: content_url_path): ⏎
      pattern (default: ): .*/(live|news_channel)/.*m3u8
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "content_matcher",
      "type": "regexMatcher",
      "inverted": false,
      "source": "content_url_path",
      "pattern": ".*/(live|news_channel)/.*m3u8"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: subnet
  Adding a 'subnet' element
    classifier : {
      name (default: ): company_matcher
      type (default: subnet): ⏎
      inverted (default: False): ⏎
      pattern (default: ): company
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "company_matcher",
      "type": "subnet",
      "inverted": false,
      "pattern": "company"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 4
  Adding a 'hostName' element
    classifier : {
      name (default: ): host_name_classifier
      type (default: hostName): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): ⏎
      pattern (default: ): *live.example*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: n
]
Generated config:
{
  "classifiers": [
    {
      "name": "host_name_classifier",
      "type": "hostName",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*live.example*"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 1
  Adding a 'contentUrlPath' element
    classifier : {
      name (default: ): vod_matcher
      type (default: contentUrlPath): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): ⏎
      pattern (default: ): *vod*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: n
]
Generated config:
{
  "classifiers": [
    {
      "name": "vod_matcher",
      "type": "contentUrlPath",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*vod*"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 2
  Adding a 'contentUrlQueryParameters' element
    classifier : {
      name (default: ): bitrate_matcher
      type (default: contentUrlQueryParameters): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): regex
      pattern (default: ): .*bitrate=100000.*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: n
]
Generated config:
{
  "classifiers": [
    {
      "name": "bitrate_matcher",
      "type": "contentUrlQueryParameters",
      "inverted": false,
      "patternType": "regex",
      "pattern": ".*bitrate=100000.*"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 9
  Adding a 'userAgent' element
    classifier : {
      name (default: ): iphone_matcher
      type (default: userAgent): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): regex
      pattern (default: ): i(P|p)hone
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: n
]
Generated config:
{
  "classifiers": [
    {
      "name": "iphone_matcher",
      "type": "userAgent",
      "inverted": false,
      "patternType": "regex",
      "pattern": "i(P|p)hone"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: asnIds
    2: contentUrlPath
    3: contentUrlQueryParameters
    4: geoip
    5: hostName
    6: ipranges
    7: regexMatcher
    8: stringMatcher
    9: subnet
    10: userAgent
  Choose element index or name: asnIds
  Adding a 'asnIds' element
    classifier : {
      name (default: ): asn_matcher
      type (default: asnIds): ⏎
      inverted (default: False): ⏎
      asnIds <The list of ASN IDs to accept. (default: [])>: [
        asnId: 1
        Add another 'asnId' element to array 'asnIds'? [y/N]: y
        asnId: 2
        Add another 'asnId' element to array 'asnIds'? [y/N]: y
        asnId: 3
        Add another 'asnId' element to array 'asnIds'? [y/N]: ⏎
      ]
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "asn_matcher",
      "type": "asnIds",
      "inverted": false,
      "asnIds": [
        1,
        2,
        3
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  

These classifiers can now be used to construct session groups and properly classify clients. Using the examples above, let’s create a session group classifying clients from Sweden using an Apple device:

$ confcli services.routing.sessionGroups -w
Running wizard for resource 'sessionGroups'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

sessionGroups : [
  sessionGroup : {
    name (default: ): inSwedenUsingAppleDevice
    classifiers : [
      classifier (default: ): sweden_matcher
      Add another 'classifier' element to array 'classifiers'? [y/N]: y
      classifier (default: ): apple_matcher
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "inSwedenUsingAppleDevice",
      "classifiers": [
        "sweden_matcher",
        "apple_matcher"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

Clients classified by the sweden_matcher and apple_matcher classifiers will now be put in the session group inSwedenUsingAppleDevice. Using session groups in routing will be demonstrated later in this document.

Advanced Classification

The above example will simply apply all classifiers in the list, and as long as they all evaluate to true for a session, that session will be tagged with the session group. For situations where this isn’t enough, classifiers can instead be combined using simple logic statements to form complex rules.

A first simple example can be a session group that accepts any viewers in either ASN 1, 2 or 3 (corresponding to the classifier asn_matcher or living in Sweden. This can be done by creating a session group, and adding the following logic statement:

'sweden_matcher' OR 'asn_matcher'

A slightly more advanced case is where a session group should only contain sessions neither in any of the three ASNs nor in Sweden. This is done by negating the previous example:

NOT ('sweden_matcher' OR 'asn_matcher')

A single classifier can also be negated, rather than the whole statement, for example to accept any Swedish viewers except those in the three ASNs:

'sweden_matcher' AND NOT 'asn_matcher'

Arbitrarily complex statements can be created using classifier names, parentheses, and the keywords AND, OR and NOT.

For example a session group accepting any Swedish viewers except those in the Stockholm region unless they are also Apple users:

'sweden_matcher' AND (NOT 'stockholm_matcher' OR 'apple_matcher')

Note that the classifier names must be enclosed in single quotes when using this syntax.

Applying this kind of complex classifier using confcli is no more difficult than adding a single classifier at a time:

$ confcli services.routing.sessionGroups. -w
Running wizard for resource 'sessionGroups'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

sessionGroups : [
  sessionGroup : {
    name (default: ): complex_group
    classifiers : [
      classifier (default: ): 'sweden_matcher' AND (NOT 'stockholm_matcher' OR 'apple_matcher')
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "complex_group",
      "classifiers": [
        "'sweden_matcher' AND (NOT 'stockholm_matcher' OR 'apple_matcher')"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  

CDNs and Hosts

Configuration using confcli has no real concept of CDNs, instead it has groups of hosts that share some common settings such as HTTP(S) port and whether they return a redirection URL, serve content directly or perform a DNS lookup. Of these three variants, the two former share the same parameters, while the DNS variant is slightly different.

Each host belongs to a host group and may itself be an entire CDN using a single public hostname or a single streamer server, all depending on the needs of the user.

Host Health

When creating a host in the confd configuration, you have the option to define a list of health check functions. Each health check function must return true for a host to be selected. This means that the host will only be considered available if all the defined health check functions evaluate to true. If any of the health check functions return false, the host will be considered unavailable and will not be selected for routing. All health check functions are detailed in the section Health Check Functions.

$ confcli services.routing.hostGroups -w
Running wizard for resource 'hostGroups'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

hostGroups : [
  hostGroup can be one of
    1: dns
    2: host
    3: redirecting
  Choose element index or name: 3
  Adding a 'redirecting' element
    hostGroup : {
      name (default: ): edgeware
      type (default: redirecting): ⏎
      httpPort (default: 80): ⏎
      httpsPort (default: 443): ⏎
      hosts : [
        host : {
          name (default: ): rr1
          hostname (default: ): convoy-rr1.example.com
          ipv6_address (default: ): ⏎
          healthChecks : [
            healthCheck (default: always()): basic_health_check()
            Add another 'healthCheck' element to array 'healthChecks'? [y/N]: n
          ]
        }
        Add another 'host' element to array 'hosts'? [y/N]: y
        host : {
          name (default: ): rr2
          hostname (default: ): convoy-rr2.example.com
          ipv6_address (default: ): ⏎
          healthChecks : [
            healthCheck (default: always()): ⏎
            Add another 'healthCheck' element to array 'healthChecks'? [y/N]: n
          ]
        }
        Add another 'host' element to array 'hosts'? [y/N]: ⏎
      ]
    }
  Add another 'hostGroup' element to array 'hostGroups'? [y/N]: ⏎
]
Generated config:
{
  "hostGroups": [
    {
      "name": "edgeware",
      "type": "redirecting",
      "httpPort": 80,
      "httpsPort": 443,
      "hosts": [
        {
          "name": "rr1",
          "hostname": "convoy-rr1.example.com",
          "ipv6_address": "",
          "healthChecks": [
            "basic_health_check()"
          ]
        },
        {
          "name": "rr2",
          "hostname": "convoy-rr2.example.com",
          "ipv6_address": "",
          "healthChecks": [
            "always()"
          ]
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.hostGroups -w
Running wizard for resource 'hostGroups'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

hostGroups : [
  hostGroup can be one of
    1: dns
    2: host
    3: redirecting
  Choose element index or name: 1
  Adding a 'dns' element
    hostGroup : {
      name (default: ): external-dns
      type (default: dns): ⏎
      hosts : [
        host : {
          name (default: ): dns-host
          hostname (default: ): dns.example.com
          ipv6_address (default: ): ⏎
          healthChecks : [
            healthCheck (default: always()): ⏎
            Add another 'healthCheck' element to array 'healthChecks'? [y/N]: n
          ]
        }
        Add another 'host' element to array 'hosts'? [y/N]: ⏎
      ]
    }
  Add another 'hostGroup' element to array 'hostGroups'? [y/N]: ⏎
]
Generated config:
{
  "hostGroups": [
    {
      "name": "external-dns",
      "type": "dns",
      "hosts": [
        {
          "name": "dns-host",
          "hostname": "dns.example.com",
          "ipv6_address": "",
          "healthChecks": [
            "always()"
          ]
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  

Rule Blocks

The routing configuration using confcli is done using a combination of logical building blocks, or rules. Each block evaluates the incoming request in some way and sends it on to one or more sub-blocks. If the block is of the host type described above, the client is sent to that host and the evaluation is done.

Existing blocks

Currently supported blocks are:

  • allow: Incoming requests, for which a given rule function matches, are immediately sent to the provided onMatch target.
  • consistentHashing: Splits incoming requests randomly between preferred hosts, determined by the proprietary consistent hashing algorithm. The amount of hosts to split between is controlled by the spreadFactor.
  • contentPopularity: Splits incoming requests into two sub-blocks depending on how popular the requested content is.
  • deny: Incoming requests, for which a given rule function matches, are immediately denied, and all non-matching requests are sent to the onMiss target.
  • firstMatch: Incoming requests are matched by an ordered series of rules, where the request will be handled by the first rule for which the condition evaluates to true.
  • random: Splits incoming requests randomly and equally between a list of target sub-blocks. Useful for simple load balancing.
  • split: Splits incoming requests between two sub-blocks depending on how the request is evaluated by a provided function. Can be used for sending clients to different hosts depending on e.g. geographical location or client hardware type.
  • weighted: Randomly splits incoming requests between a list of target sub-blocks, weighted according to each target’s associated weight rule. A higher weight means a higher portion of requests will be routed to a sub-block. Rules can be used to decide whether or not to pick a target.
  • rawGroup: Contains a raw ESB3024 Router configuration routing tree node, to be inserted as is in the generated configuration. This is only meant to be used in the rare cases when it’s impossible to construct the required routing behavior in any other way.
  • rawHost: A host reference for use as endpoints in rawGroup trees.
$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: allow
  Adding a 'allow' element
    rule : {
      name (default: ): allow
      type (default: allow): ⏎
      condition (default: ): customFunction()
      onMatch (default: ): rr1
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "content",
      "type": "contentPopularity",
      "condition": "customFunction()",
      "onMatch": "rr1"
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: consistentHashing
  Adding a 'consistentHashing' element
    rule : {
      name (default: ): consistentHashingRule
      type (default: consistentHashing): 
      spreadFactor (default: 1): 2
      targets : [
        target : {
          target (default: ): rr1
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): rr2
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): rr3
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: n
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: n
]
Generated config:
{
  "rules": [
    {
      "name": "consistentHashingRule",
      "type": "consistentHashing",
      "spreadFactor": 2,
      "targets": [
        {
          "target": "rr1",
          "enabled": true
        },
        {
          "target": "rr2",
          "enabled": true
        },
        {
          "target": "rr3",
          "enabled": true
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: contentPopularity
  Adding a 'contentPopularity' element
    rule : {
      name (default: ): content
      type (default: contentPopularity): ⏎
      contentPopularityCutoff (default: 10): 20
      onPopular (default: ): rr1
      onUnpopular (default: ): rr2
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "content",
      "type": "contentPopularity",
      "contentPopularityCutoff": 20.0,
      "onPopular": "rr1",
      "onUnpopular": "rr2"
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: deny
  Adding a 'deny' element
    rule : {
      name (default: ): deny
      type (default: deny): ⏎
      condition (default: ): customFunction()
      onMiss (default: ): rr1
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "content",
      "type": "contentPopularity",
      "condition": "customFunction()",
      "onMiss": "rr1"
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: firstMatch
  Adding a 'firstMatch' element
    rule : {
      name (default: ): firstMatch
      type (default: firstMatch): ⏎
      targets : [
        target : {
          onMatch (default: ): rr1
          rule (default: ): customFunction()
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          onMatch (default: ): rr2
          rule (default: ): otherCustomFunction()
        }
        Add another 'target' element to array 'targets'? [y/N]: n
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: n
]
Generated config:
{
  "rules": [
    {
      "name": "firstMatch",
      "type": "firstMatch",
      "targets": [
        {
          "onMatch": "rr1",
          "condition": "customFunction()"
        },
        {
          "onMatch": "rr2",
          "condition": "otherCustomFunction()"
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: random
  Adding a 'random' element
    rule : {
      name (default: ): random
      type (default: random): ⏎
      targets : [
        target (default: ): rr1
        Add another 'target' element to array 'targets'? [y/N]: y
        target (default: ): rr2
        Add another 'target' element to array 'targets'? [y/N]: ⏎
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "random",
      "type": "random",
      "targets": [
        "rr1",
        "rr2"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: split
  Adding a 'split' element
    rule : {
      name (default: ): split
      type (default: split): ⏎
      condition (default: ): custom_function()
      onMatch (default: ): rr2
      onMiss (default: ): rr1
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "split",
      "type": "split",
      "condition": "custom_function()",
      "onMatch": "rr2",
      "onMiss": "rr1"
    }
  ]
}
Merge and apply the config? [y/n]: y
  
$ confcli services.routing.rules. -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: weighted
  Adding a 'weighted' element
    rule : {
      name (default: ): weight
      type (default: weighted): ⏎
      targets : [
        target : {
          target (default: ): rr1
          weight (default: 100): ⏎
          condition (default: always()): always()
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): rr2
          weight (default: 100): si('rr2-input-weight')
          condition (default: always()): gt('rr2-bandwidth', 1000000)
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): rr2
          weight (default: 100): custom_func()
          condition (default: always()): always()
        }
        Add another 'target' element to array 'targets'? [y/N]: ⏎
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "weight",
      "type": "weighted",
      "targets": [
        {
          "target": "rr1",
          "weight": "100",
          "condition": "always()"
        },
        {
          "target": "rr2",
          "weight": "si('rr2-input-weight')",
          "condition": "gt('rr2-bandwith', 1000000)"
        },
        {
          "target": "rr2",
          "weight": "custom_func()",
          "condition": "always()"
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  
>> First add a raw host block that refers to a regular host

$ confcli services.routing.rules. -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: rawHost
  Adding a 'rawHost' element
    rule : {
      name (default: ): raw-host
      type (default: rawHost): ⏎
      hostId (default: ): rr1
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "raw-host",
      "type": "rawHost",
      "hostId": "rr1"
    }
  ]
}
Merge and apply the config? [y/n]: y

>> And then add a rule using the host node

$ confcli services.routing.rules. -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: rawGroup
  Adding a 'rawGroup' element
    rule : {
      name (default: ): raw-node
      type (default: rawGroup): ⏎
      memberOrder (default: sequential): ⏎
      members : [
        member : {
          target (default: ): raw-host
          weightFunction (default: ): return 1
        }
        Add another 'member' element to array 'members'? [y/N]: ⏎
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "raw-node",
      "type": "rawGroup",
      "memberOrder": "sequential",
      "members": [
        {
          "target": "raw-host",
          "weightFunction": "return 1"
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
  

Rule Language

Some blocks, such as the split and firstMatch types, have a rule field that contains a small function in a very simple programming language. This field is used to filter any incoming client requests in order to determine how to rule block should react.

In the case of a split block, the rule is evaluated and if it is true the client is sent to the onMatch part of the block, otherwise it is sent to the onMiss part for further evaluation.

In the case of a firstMatch block, the rule for each target will be evaluated top to bottom in order until either a rule evaluates to true or the list is exhausted. If a rule evaluates to true, the client will be sent to the onMatch part of the block, otherwise the next target in the list will be tried. If all targets have been exhausted, then the entire rule evaluation will fail, and the routing tree will be restarted with the firstMatch block effectively removed.

Example of Boolean Functions

Let’s say we have an ESB3024 Router set up with a session group that matches Apple devices (named “Apple”). To route all Apple devices to a specific streamer one would simply create a split block with the following rule:

in_session_group('Apple')

In order to make more complex rules it’s possible to combine several checks like this in the same rule. Let’s extend the hypothetical ESB3024 Router above with a configured subnet with all IP addresses in Europe (named “Europe”). To make a rule that accepts any clients using an Apple device and living outside of Europe, but only as long as the reported load on the streamer (as indicated by the selection input variable “europe_load_mbps”) is less than 1000 megabits per second one could make an offload block with the following rule (without linebreaks):

in_session_group('Apple')
    and not in_subnet('Europe')
    and lt('europe_load_mbps', 1000)

In this example in_session_group('Apple') will be true if the client belongs to the session group named ‘Apple’. The function call in_subnet('Europe') is true if the client’s IP belongs to the subnet named ‘Europe’, but the word not in front of it reverses the value so the entire section ends up being false if the client is in Europe. Finally lt('europe_load_mbps', 1000) is true if there is a selection input variable named “europe_load_mbps” and its value is less than 1000.

Since the three parts are conjoined with the and keyword they must all be true for the entire rule to match. If the keyword or had been used instead it would have been enough for any of the parts to be true for the rule to match.

Example of Numeric Functions

A hypothetical CDN has two streamers with different capacity; Host_1 has roughly twice the capacity of Host_2. A simple random load balancing would put undue stress on the second host since it will receive as much traffic as the more capable Host_1.

This can be solved by using a weighted random distribution rule block with suitable rules for the two hosts:

{
    "targets": [
        {
            "target": "Host_1",
            "condition": "always()",
            "weight": "100"
        }
        {
            "target": "Host_2",
            "condition": "always()",
            "weight": "50"
        },
    ]
}

resulting in Host_1 receiving twice as many requests as Host_2 as its weight function is double that of Host_2.

If the CDN is capable of reporting the free capacity of the hosts, for example by writing to a selection input variable for each host, it’s easy to write a more intelligent load balancing rule by making the weights correspond to the amount of capacity left on each host:

{
    "targets": [
        {
            "target": "Host_1",
            "condition": "always()",
            "weight": "si('free_capacity_host_1')"
        }
        {
            "target": "Host_2",
            "condition": "always()",
            "weight": "si('free_capacity_host_2')"
        },
    ]
}

It is also possible to write custom Lua functions that return suitable weights, perhaps taking the host as an argument:

{
    "targets": [
        {
            "target": "Host_1",
            "condition": "always()",
            "weight": "intelligent_weight_function('Host_1')"
        }
        {
            "target": "Host_2",
            "condition": "always()",
            "weight": "intelligent_weight_function('Host_1')"
        },
    ]
}

These different weight rules can of course be combined in the same rule block, with one target having a hard coded number, another using a dynamically updated selection input variable and yet another having a custom-built function.

Due to limitations in the random number generator used to distribute requests, it’s better to use somewhat large values, around 100–1000 or so, than to use small values near 0.

Built-in Functions

The following built-in functions are available when writing rules:

  • in_session_group(str name): True if session belongs to session group <name>
  • in_all_session_groups(str sg_name, ...): True if session belongs to all specified session groups
  • in_any_session_group(str sg_name, ...): True if session belongs to any specified session group
  • in_subnet(str subnet_name): True if client IP belongs to the named subnet
  • gt(str si_var, number value): True if selection_inputs[si_var] > value
  • gt(str si_var1, str si_var2): True if selection_inputs[si_var1] > selection_inputs[si_var2]
  • ge(str si_var, number value): True if selection_inputs[si_var] >= value
  • ge(str si_var1, str si_var2): True if selection_inputs[si_var1] >= selection_inputs[si_var2]
  • lt(str si_var, number value): True if selection_inputs[si_var] < value
  • lt(str si_var1, str si_var2): True if selection_inputs[si_var1] < selection_inputs[si_var2]
  • le(str si_var, number value): True if selection_inputs[si_var] <= value
  • le(str si_var1, str si_var2): True if selection_inputs[si_var1] <= selection_inputs[si_var2]
  • eq(str si_var, number value): True if selection_inputs[si_var] == value
  • eq(str si_var1, str si_var2): True if selection_inputs[si_var1] == selection_inputs[si_var2]
  • neq(str si_var, number value): True if selection_inputs[si_var] != value
  • neq(str si_var1, str si_var2): True if selection_inputs[si_var1] != selection_inputs[si_var2]
  • si(str si_var): Returns the value of selection_inputs[si_var] if it is defined and non-negative, otherwise it returns 0.
  • always(): Returns true, useful when creating weighted rule blocks.
  • never(): Returns false, opposite of always().

These functions, as well as custom functions written in Lua and uploaded to the ESB3024 Router, can be combined to make suitably precise rules.

Combining Multiple Boolean Functions

In order to make the rule language easy to work with, it is fairly restricted and simple. One restriction is that it’s only possible to chain multiple function results together using either and or or, but not a combination of both conjunctions.

Statements joined with and or or keywords are evaluated one by one, starting with the left-most statement and moving right. As soon as the end result of the entire expression is certain, the evaluation ends. This means that evaluation ends with the first false statement for and expressions since a single false component means the entire expression must also be false. It also means that evaluation ends with the first true statement for or expressions since only one component must be true for the entire statement to be true as well. This is known as short-circuit or lazy evaluation.

Custom Functions

It is possible to write extremely complex Lua functions that take many parameters or calculations into consideration when evaluating an incoming client request. By writing such functions and making sure that they return only non-negative integer values and uploading them to the router they can be used from the rule language. Simply call them like any of the built-in functions listed above, using strings and numbers as arguments if necessary, and their result will be used to determine the routing path to use.

Formal Syntax

The full syntax of the language can be described in just a few lines of BNF grammar:

<rule>               := <weight_rule> | <match_rule> | <value_rule>
<weight_rule>        := "if" <compound_predicate> "then" <weight> "else" <weight>
<match_rule>         := <compound_predicate>
<value_rule>         := <weight>
<compound_predicate> := <logical_predicate> |
                        <logical_predicate> ["and" <logical_predicate> ...] |
                        <logical_predicate> ["or" <logical_predicate> ...] |
<logical_predicate>  := ["not"] <predicate>
<predicate>          := <function_name> "(" ")" |
                        <function_name> "(" <argument> ["," <argument> ...] ")"
<function_name>      := <letter> [<function_name_tail> ...]
<function_name_tail> := empty | <letter> | <digit> | "_"
<argument>           := <string> | <number>
<weight>             := integer | <predicate>
<number>             := float | integer
<string>             := "'" [<letter> | <digit> | <symbol> ...] "'"

Building a Routing Configuration

This example sets up an entire routing configuration for a system with a ESB3008 Request Router, two streamers and the Apple devices outside of Europe example used earlier in this document. Any clients not matching the criteria will be sent to an offload CDN with two streamers in a simple uniformly randomized load balancing setup.

Set up Session Group

First make a classifier and a session group that uses it:

$ confcli services.routing.classifiers -w
Running wizard for resource 'classifiers'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

classifiers : [
  classifier can be one of
    1: contentUrlPath
    2: contentUrlQueryParameters
    3: geoip
    4: hostName
    5: ipranges
    6: regexMatcher
    7: stringMatcher
    8: subnet
    9: userAgent
  Choose element index or name: 9
  Adding a 'userAgent' element
    classifier : {
      name (default: ): Apple
      type (default: userAgent): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): ⏎
      pattern (default: ): *apple*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "Apple",
      "type": "userAgent",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*apple*"
    }
  ]
}
Merge and apply the config? [y/n]: y

$ confcli services.routing.sessionGroups -w
Running wizard for resource 'sessionGroups'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

sessionGroups : [
  sessionGroup : {
    name (default: ): Apple
    classifiers : [
      classifier (default: ): Apple
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "Apple",
      "classifiers": [
        "Apple"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

Set up Hosts

Create two host groups and add a Request Router to the first and two streamers to the second, which will be used for offload:

$ confcli services.routing.hostGroups -w
Running wizard for resource 'hostGroups'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

hostGroups : [
  hostGroup can be one of
    1: dns
    2: host
    3: redirecting
  Choose element index or name: 3
  Adding a 'redirecting' element
    hostGroup : {
      name (default: ): internal
      type (default: redirecting): ⏎
      httpPort (default: 80): ⏎
      httpsPort (default: 443): ⏎
      hosts : [
        host : {
          name (default: ): rr1
          hostname (default: ): rr1.example.com
          ipv6_address (default: ): ⏎
        }
        Add another 'host' element to array 'hosts'? [y/N]: ⏎
      ]
    }
  Add another 'hostGroup' element to array 'hostGroups'? [y/N]: y
  hostGroup can be one of
    1: dns
    2: host
    3: redirecting
  Choose element index or name: 2
  Adding a 'host' element
    hostGroup : {
      name (default: ): external
      type (default: host): ⏎
      httpPort (default: 80): ⏎
      httpsPort (default: 443): ⏎
      hosts : [
        host : {
          name (default: ): offload-streamer1
          hostname (default: ): streamer1.example.com
          ipv6_address (default: ): ⏎
        }
        Add another 'host' element to array 'hosts'? [y/N]: y
        host : {
          name (default: ): offload-streamer2
          hostname (default: ): streamer2.example.com
          ipv6_address (default: ): ⏎
        }
        Add another 'host' element to array 'hosts'? [y/N]: ⏎
      ]
    }
  Add another 'hostGroup' element to array 'hostGroups'? [y/N]: ⏎
]
Generated config:
{
  "hostGroups": [
    {
      "name": "internal",
      "type": "redirecting",
      "httpPort": 80,
      "httpsPort": 443,
      "hosts": [
        {
          "name": "rr1",
          "hostname": "rr1.example.com",
          "ipv6_address": ""
        }
      ]
    },
    {
      "name": "external",
      "type": "host",
      "httpPort": 80,
      "httpsPort": 443,
      "hosts": [
        {
          "name": "offload-streamer1",
          "hostname": "streamer1.example.com",
          "ipv6_address": ""
        },
        {
          "name": "offload-streamer2",
          "hostname": "streamer2.example.com",
          "ipv6_address": ""
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

Create Load Balancing and Offload Block

Add both offload streamers as targets in a randomgroup block:

$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: deny
    2: contentPopularity
    3: firstMatch
    4: random
    5: rawGroup
    6: rawHost
    7: split
    8: weighted
    9: allow
  Choose element index or name: random
  Adding a 'random' element
    rule : {
      name (default: ): balancer
      type (default: random): ⏎
      targets : [
        target (default: ): offload-streamer1
        Add another 'target' element to array 'targets'? [y/N]: y
        target (default: ): offload-streamer2
        Add another 'target' element to array 'targets'? [y/N]: ⏎
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "balancer",
      "type": "random",
      "targets": [
        "offload-streamer1",
        "offload-streamer2"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

Then create a split block with the request router and the load balanced CDN as targets:

$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: deny
    2: contentPopularity
    3: firstMatch
    4: random
    5: rawGroup
    6: rawHost
    7: split
    8: weighted
    9: allow
  Choose element index or name: split
  Adding a 'split' element
    rule : {
      name (default: ): offload
      type (default: split): ⏎
      rule (default: ): in_session_group('Apple') and not in_subnet('Europe') and lt('europe_load_mbps', 1000)
      onMatch (default: ): rr1
      onMiss (default: ): balancer
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "offload",
      "type": "split",
      "condition": "in_session_group('Apple') and not in_subnet('Europe') and lt('europe_load_mbps', 1000)",
      "onMatch": "rr1",
      "onMiss": "balancer"
    }
  ]
}
Merge and apply the config? [y/n]: y

The last step required is to set the entrypoint of the routing tree so the router knows where to start evaluating:

$ confcli services.routing.entrypoint offload
services.routing.entrypoint = 'offload'

Evaluate

Now that all the rules have been set up properly and the router has been reconfigured. The translated configuration can be read from the router’s configuration API:

$ curl -k https://router-host:5001/v2/configuration  2> /dev/null | jq .routing
{
  "id": "offload",
  "member_order": "sequential",
  "members": [
    {
      "host_id": "rr1",
      "id": "offload.rr1",
      "weight_function": "return ((in_session_group('Apple') ~= 0) and
                          (in_subnet('Europe') == 0) and
                          (lt('europe_load_mbps', 1000) ~= 0) and 1) or 0 "
    },
    {
      "id": "offload.balancer",
      "member_order": "weighted",
      "members": [
        {
          "host_id": "offload-streamer1",
          "id": "offload.balancer.offload-streamer1",
          "weight_function": "return 100"
        },
        {
          "host_id": "offload-streamer2",
          "id": "offload.balancer.offload-streamer2",
          "weight_function": "return 100"
        }
      ],
      "weight_function": "return 1"
    }
  ],
  "weight_function": "return 100"
}

Note that the configuration language code has been translated into its Lua equivalent.

4 - Session Groups and Classification

How to classify clients into session groups and use them in routing

ESB3024 Router provides a flexible classification engine, allowing the assignment of clients into session groups that can then be used to base routing decisions on.

Session Groups

A session group is a subset of clients satisfying certain conditions. Define session groups reflecting the environment and objectives.

A client can be part of multiple session groups.

Classification

The session_groups field of the configuration comprises a list of sessions groups, each with their respective classification criteria specified in the corresponding classifiers field.

Each session group consists of the fields

  • id (number) - ID of the session group, must be globally unique from all other session group IDs
  • name (string) - name of the session group
  • classifiers (array of array of objects) - list of the session group’s classifiers.

Classification Logic

Session groups support combining classifiers using AND and OR semantics. This is achieved by nesting classifiers within evaluation groups, as seen in the pseudo-JSON below:

{
  "session_groups": [
    {
      "id": 1,
      "name": "example_AND_vs_OR",
      "classifiers": [
        [ // All classifiers within evaluation group are AND evaluated
          {
            // classifier 1
          },
          {
            // classifier 2
          }
        ],
        // Separate evaluation groups are OR evaluated
        [
          {
            // classifier 3
          }
        ]
      ]
    }
  ]
}

This particular session group defines an evaluation group containing classifier 1 and classifier 2. Both classifiers have to evaluate to true for the whole classifier group to evaluate to true.

This session group also defines another evaluation group containing classifier 3. Separate evaluation groups are OR evaluated, meaning that either the first or second evaluation group needs to evaluate to true for a client to assigned to the session group.

Logically, this session group’s classification criterion can be visualized as
( classifier 1 AND classifier 2 ) OR classifier 3.

Classifier Structure

Each classifier consists of the fields

  • id (number): ID of the classifier, must be unique within the same session group
  • name (string): name of the classifier
  • inverted (boolean): the resulting classification should be inverted (negated) before evaluation
  • rule (object): the classifier logic

rule contains different fields defining classifier behaviour. The first mandatory field is rule_type which defines the classification method and can be one of string_match_rule, geoip_rule, asn_ids_rule, regex_rule, subnet_rule and ip_ranges_rule. The second mandatory field is source, defining the data source on which the classification will be evaluated and can be one of session/content_url_path, session/content_url_query_params, session/user_agent, session/client_ip or session/hostname.

Examples of all rule types and sources can be seen in the JSON below.

{
  "session_groups": [
    {
      "id": 1,
      "name": "example_string_match_rule_live_and_subtitle",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "live_wildcard_match",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/content_url_path",
              "pattern": "*live*"
            }
          },
          {
            "id": 2,
            "inverted": false,
            "name": "subtitle_wildcard_match",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/content_url_query_params",
              "pattern": "*subtitle=eng*"
            }
          }
        ]
      ]
    },
    {
      "id": 2,
      "name": "example_string_match_rule_vod_or_hostname",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "live_wildcard_match",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/content_url_path",
              "pattern": "*vod*"
            }
          }
        ],
        [
          {
            "id": 3,
            "inverted": false,
            "name": "hostname_match",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/hostname",
              "pattern": "mycdn.com"
            }
          }
        ]
      ]
    },
    {
      "id": 3,
      "name": "example_string_match_rule_ipv4",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "ipv4_wildcard_match",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/client_ip",
              "pattern": "*.*"
            }
          }
        ]
      ]
    },
    {
      "id": 4,
      "name": "example_string_match_rule_ipv6",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "ipv6_wildcard_match",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/client_ip",
              "pattern": "*:*"
            }
          }
        ]
      ]
    },
    {
      "id": 5,
      "name": "example_geo_location",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "geo_location_specific",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "continent": "Europe",
              "country": "Norway",
              "region": "Innlandet",
              "cities": ["Elverum"],
              "geoname_id": 3144096,
              "asn": "Telenor Norge AS"
            }
          }
        ]
      ]
    },
    {
      "id": 6,
      "name": "example_geo_location_asn_wildcard",
      "classifiers": [
        [
          {
          "id": 1,
          "inverted": false,
          "name": "asn_wildcard_match",
          "rule": {
            "rule_type": "geoip_rule",
            "source": "session/client_ip",
            "asn": "Telia*"
            }
          }
        ]
      ]
    },
    {
      "id": 7,
      "name": "example_regex",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "ios_version_5.1.1",
            "rule": {
              "rule_type": "regex_rule",
              "source": "session/user_agent",
              "pattern": "/OS ((\\d+_?){2,3})\\s/"
            }
          }
        ]
      ]
    },
    {
      "id": 8,
      "name": "example_ip_ranges",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": true,
            "name": "offload_no_peering_subnet",
            "rule": {
              "rule_type": "ip_ranges_rule",
              "source": "session/client_ip",
              "ip_ranges": ["158.174.0.0/16", "95.192.0.0/12"]
            }
          }
        ]
      ]
    },
    {
      "id": 9,
      "name": "example_subnets",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "subnets_match",
            "rule": {
              "rule_type": "subnet_rule",
              "source": "session/client_ip",
              "pattern": "stock*"
            }
          }
        ]
      ]
    },
    {
      "id": 10,
      "name": "example_asn_list",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "asn_list_match",
            "rule": {
              "rule_type": "asn_ids_rule",
              "source": "session/client_ip",
              "asn_ids": [1, 2, 3]
            }
          }
        ]
      ]
    }
  ]
}

As seen in "pattern": "/OS ((\\d+_?){2,3})\\s/", backslash characters must always be escaped in JSON. If a tool was used to construct the JSON code, escaping should be done automatically and it must not be included it manually.

The rule types work as following:

  • string_match_rule flexible string matching on any source
    Tip: Supports wildcard matching
  • regex_rule - regex matching on the source, based on C++ regex implementation
    Tip: For basic IPv4 vs. IPv6 differentiation of client IP addresses, a simple string_match_rule on session/client_ip is more efficient and maintainable than a more expressive regex_rule (which may come with the additional benefit of validating the format of the matched field).
  • ip_ranges_rule - a list of IP ranges on the form ‘0.0.0.0/24’. Any client with an IP within any of the specified ranges will be matched by the classifier
  • subnet_rule - string matching of the pattern on all subnets the client’s IP matches. Only supports the source session/client_ip. If a client matches into multiple subnets, only one of them needs to match the pattern.
    Tip: Supports wildcard matching
  • geoip_rule allows geolocation based matching. The client’s IP address is used to poll the MaxMind GeoIP2 database for geolocations. Only supports the source session/client_ip. The possible fields within this rule are
    • continent (string) - the desired continent
    • country (string) - the desired country
    • region (string) - the desired region
    • cities (array of strings) - the desired cities
      • at least one city must match for successful classification
    • asn (string) - the desired ASN
      Tip: Supports wildcard matching
    • geoname_id (number) - MaxMind’s internal id matching
      Note: Using geoname_id requires insight into a MaxMind GeoIP2 database to find correct IDs

All fields within the GeoIP rule are optional. For successful classification, all defined fields must match.

If a client is successfully classified, it will labeled as part of that session group. The session group can be then be accessed in Lua contexts, e.g.

{
  "hosts": [
    {
      "id": "geo-location-host",
      "cdn_id": "basic-cdn",
      "host": "geo-location-host.example"
    },
    {
      "id": "ip-ranges-host",
      "cdn_id": "basic-cdn",
      "host": "ip-ranges-host.example"
    }
  ],
  "routing": {
    "id": "session_group_routing",
    "member_order": "sequential",
    "members": [
      {
        "id": "geo_location_session_group",
        "host_id": "geo-location-host",
        "weight_function": "return session_groups.example_geo_location and 1 or 0"
      },
      {
        "id": "ip_ranges_session_group",
        "host_id": "ip-ranges-host",
        "weight_function": "return session_groups.example_ip_ranges and 1 or 0"
      }
    ]
  }
}

5 - Advanced features

Detailed descriptions and examples of advanced features within ESB3024

5.1 - Content popularity

How to tune content popularity parameters and use it in routing

ESB3024 Router allows routing decisions based on content popularity. All incoming content requests are tracked to continuously update a content popularity ranking list. The popularity ranking algorithm is designed to let popular content quickly rise to the top while unpopular content decays and sinks towards the bottom.

Configuration

All configuration parameters for content popularity reside in the settings object of the configuration, an example of which can be seen below:

{
  "settings": {
    "content_popularity": {
      "algorithm": "scored_based",
      "session_group_names": ["vod_only"],
      "score_based:": {
        "requests_between_popularity_decay": 1000,
        "popularity_list_max_size": 100000,
        "popularity_prediction_factor": 2.5,
        "popularity_decay_fraction": 0.2
      },
      "time_based": {
        "intervals_per_hour": 10
      }
    }
  }
}

The field algorithm dictates which content popularity tracking algorithm to use, can either be score_based or time_based.

The field session_group_names defines the sessions for which content popularity should be tracked. In the example above, session belonging to the vod_only session group will be tracked for content popularity. If left empty, content popularity will be tracked for all sessions.

The remaining configuration parameters are algorithm specific.

Score based algorithm

The field popularity_list_max_size defines the maximum amount of unique contents to track for popularity. This can be used to limit memory growth. A single entry in the popularity ranking list will at most consume 180 B of memory, giving an upper bound memory growth of \(180n_{\text{requests}}\) bytes. E.g. using "popularity_list_max_size": 1000 would consume at most 180⋅1,000 = 180,000 B = 0.18 MB. If the content popularity list is full, a request to unique content would replace the least popular content.

Setting a very high max size won’t impact performance, it will only consume more memory.

The field requests_between_popularity_decay defines the number of requests between each popularity decay update, an integral component of this feature.

The fields popularity_prediction_factor and popularity_decay_fraction tune the behaviour of the content popularity ranking algorithm, explained further below.

Decay update

To allow for popular content to quickly rise in popularity and unpopular content to sink, a dynamic popularity ranking algorithm is used. The goal of the algorithm is to track content popularity in real time, allowing routing decisions based on the requested content’s popularity. The algorithm is applied every decay update.

The algorithm uses current trending content to predict content popularity. The field popularity_prediction_factor regulates how much the algorithm should rely on predicted popularity. A high prediction factor allows rising content to quickly rise to high popularity but can also cause unpopular content with a sudden burst of requests to wrongfully rise to the top. A low prediction factor can cause stagnation in the popularity ranking, not allowing new popular content to rise to the top.

Unpopular content decays in popularity, the magnitude of which is regulated by popularity_decay_fraction. A high value will aggressively decay content popularity every decay update while a low value will bloat the ranking, causing stagnation. Once content decays to a trivially low popularity score, it is pruned from the content popularity list.

When configuring these tuning parameters, the most crucial data to consider is the size of your asset catalog, i.e. the number of unique contents you offer. The recommended values, obtained through testing, are presented in the table below. Note that the field popularity_prediction_factor is the principal factor in controlling the algorithm’s behaviour.

Catalog size \(n\)popularity_prediction_factorpopularity_decay_fraction
\(n\) < 10002.20.2
1000 < \(n\) < 50002.30.2
5000 < \(n\) < 100002.50.2
\(n\) > 100002.60.2

Time based algorithm

The time based algorithm only requires the configuration parameter intervals_per_hour. E.g., the value "intervals_per_hour": 10 would give 10 six minute intervals per hour. During each interval, all unique content requests has an associated counter, increasing by one for each incoming request. After an hour, all intervals have been cycled through. The counters in the first interval will be reset and all incoming content requests will increase the counters in the first interval again. This cycle continues forever.

When determining a single content’s popularity, the sum of each content’s counter in all intervals is used to determine a popularity ranking.

Usage in routing

Content popularity ranking is available in Lua through the simple member session.content_global_popularity, returning the requested content’s popularity ranking. E.g., the routing configuration below will route all requests to content with a popularity ranking lower than 10, i.e. top 10 most popular content, to the host edge-streamer while requests to content outside of the top 10 most popular will be routed to the host offload.

"routing": {
  "id": "routing_table",
  "member_order": "sequential",
  "members": [
    {
      "id": "edge",
      "weight_function": "return session.content_global_popularity < 11 and 1 or 0",
      "host_id": "edge-streamer"
    },
    {
      "id": "offload",
      "weight_function": "return 1",
      "host_id": "offload"
    }
  ]
}

5.2 - Consistent Hashing

Details and configuration considerations for using consistent hashing based routing

Consistent hashing based routing is a feature that can be used to distribute requests to a set of hosts in a cache friendly manner. By using Agile Content’s consistent distributed hash algorithm, the amount of cache redistribution is minimized within a set of hosts. Requests for a content will always be routed to the same set of hosts, the amount of which is configured by the spread factor, allowing high cache usage. When adding or removing hosts, the algorithm minimizes cache redistribution.

Say you have the host group [s1, s2, s3, s4, s5] and have configured spreadFactor = 3. A request for a content asset1 would then be routed to the same three hosts with one of them being selected randomly for each request. Requests for a different content asset2 would also be routed to one of three different hosts, most likely a different combination of hosts than requests for content asset1.

Example routing results with spreadFactor = 3:

  • Request for asset1 → route to one of [s1, s3, s4].
  • Request for asset2 → route to one of [s2, s4, s5].
  • Request for asset3 → route to one of [s1, s2, s5].

Since consistent hashing based routing ensures that requests for a specific content always get routed to the same set of hosts, the risk of cache misses are lowered on the hosts since they will be served the same content requests over and over again.

Note that the maximum value of spreadFactor is 64. Consequently, the highest amount of hosts you can use in a consistentHashing rule block is 64.

Configuration

Configuring consistent hashing based routing is easily done using confcli. Let’s configure the example described above:

confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: consistentHashing
  Adding a 'consistentHashing' element
    rule : {
      name (default: ): consistentHashingRule 
      type (default: consistentHashing): 
      spreadFactor (default: 1): 3
      targets : [
        target : {
          target (default: ): s1
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): s2
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): s3
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): s4
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): s5
          enabled (default: True): 
        }
        Add another 'target' element to array 'targets'? [y/N]: n
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: n
]
Generated config:
{
  "rules": [
    {
      "name": "consistentHashingRule",
      "type": "consistentHashing",
      "spreadFactor": 3,
      "targets": [
        {
          "target": "s1",
          "enabled": true
        },
        {
          "target": "s2",
          "enabled": true
        },
        {
          "target": "s3",
          "enabled": true
        },
        {
          "target": "s4",
          "enabled": true
        },
        {
          "target": "s5",
          "enabled": true
        }
      ]
    }
  ]
}

Adding hosts

Adding a host to the list will give an additional target for the consistent hashing algorithm to route requests to. This will shift content distribution onto the new host.

confcli services.routing.rules.consistentHashingRule.targets -w
Running wizard for resource 'targets'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

targets : [
  target : {
    target (default: ): s6
    enabled (default: True): 
  }
  Add another 'target' element to array 'targets'? [y/N]: n
]
Generated config:
{
  "targets": [
    {
      "target": "s6",
      "enabled": true
    }
  ]
}
Merge and apply the config? [y/n]: y

Removing hosts

There is one very important caveat of using a consistent hashing rule block. As long as you don’t modify the list of hosts, the consistent hashing algorithm will keep routing requests to the same hosts. However, if you remove a host from the block in any position except the last, the consistent hashing algorithm’s behaviour will change and the algorithm cannot maintain a minimum amount of cache redistribution.

If you’re in a situation where you have to remove a host from the routing targets but want to keep the same consistent hashing behaviour, e.g. during very high load, you’ll have to toggle that target’s enabled field to false. E.g., disabling requests to s2 can be accomplished by:

$ confcli services.routing.rules.consistentHashingRule.targets.1.enabled false
services.routing.rules.consistentHashingRule.targets.1.enabled = False
$ confcli services.routing.rules.consistentHashingRule.targets.1
{
    "1": {
        "target": "s2",
        "enabled": false
    }
}

If you modify the list order or remove hosts, it is highly recommended to do so during moments where a higher rate of cache misses are acceptable.

5.3 - Predictive load balancing

Configuring predictive load balancing in ESB3024 Router

Why use predictive load balancing?

Predictive load balancing is a tool that can be used to avoid overloading hosts with traffic. Consider the case where a popular event starts at a certain time, let’s say 12 PM. A spike in traffic will be routed to the hosts that are streaming the content at 12 PM, most of them starting at low bitrates. A host might have sufficient bandwidth left to take on more clients but when the recently connected clients start ramping up in video quality and increase their bitrate, the host can quickly become overloaded, possibly dropping incoming requests or going offline. Predictive load balancing solves this issue by considering how many times a host recently been redirected to.

Basic configuration

The router allows predicting incoming load for individual hosts. Through the use of the selection input API, the routing engine can predict incoming load by tracking recent host selections and make routing decisions accordingly.

What is considered recent is configurable and defaults to 500 milliseconds:

$ confcli services.routing.tuning.target.recentDurationMilliseconds
{
    "recentDurationMilliseconds": 500
}
$ confcli services.routing.tuning.target.recentDurationMilliseconds 1000
services.routing.tuning.target.recentDurationMilliseconds = 1000
{
  "tuning": {
    "target_recent_duration_milliseconds": 500
  }
}

How to use predictive load balancing

The standard Lua library provides four functions for predictive load balancing that can be used when constructing conditions/weight functions: host_bitrate() , host_bitrate_custom(), host_has_bw() and host_has_bw_custom(). All require data to be supplied to the selection input API and apply only to leaf nodes in the routing tree. In order for predictive load balancing to work properly the data must be updated at regular intervals. The data needs to be supplied by the target system.

These functions are suitable to used as host health checks. To configure host health checks, see configuring CDNs and hosts.

Note that host_bitrate() and host_has_bw() rely on data supplied by metrics agents, detailed in Cache hardware metrics: monitoring and routing.

host_bitrate_custom() and host_has_bw_custom() rely on manually supplied selection input data, detailed in selection input API. The bitrate unit depends on the data submitted to the selection input API.

host_bitrate()

host_bitrate() returns the predicted bitrate (in megabits per second) of the host after the recently connected clients start ramping up in streaming quality. The function accepts up to four arguments:

  • interface: The name of the interface to use for bitrate prediction.
  • Optional avg_bitrate: the average bitrate per client, defaults to 6 megabits per second.
  • Optional num_routers: the number of routers that can route to this host, defaults to 1. This is important to accurately predict the incoming load if multiple routers are used.
  • Optional host: The name of the host to use for bitrate prediction. Defaults to the current host if not provided.

Examples of usage:

host_bitrate({interface="eths0"})
host_bitrate({avg_bitrate=1, interface="eths0"})
host_bitrate({num_routers=2, interface="eths0"})
host_bitrate({avg_bitrate=1, num_routers=4, interface="eths0"})
host_bitrate({avg_bitrate=1, num_routers=4, host="custom_host", interface="eths0"})

host_bitrate() calculates the predicted bitrate as:

predicted_host_bitrate = current_host_bitrate + (recent_connections * avg_bitrate * num_routers)

host_bitrate_custom()

Same functionality as host_bitrate() but uses a custom selection input variable as bitrate input instead of accessing hardware metrics. The function accepts up to three arguments:

  • custom_bitrate_var: The name of the selection input variable to be used for accessing current host bitrate.
  • Optional avg_bitrate: see host_bitrate() documentation above.
  • Optional num_routers: see host_bitrate() documentation above.
host_bitrate_custom({custom_bitrate_var="host1_current_bitrate"})
host_bitrate_custom({avg_bitrate=1, custom_bitrate_var="host1_current_bitrate"})
host_bitrate_custom({num_routers=4, custom_bitrate_var="host1_current_bitrate"})

host_has_bw()

Instead of accessing the predicted bitrate of a host through host_bitrate(), host_has_bw() returns 1 if the host is predicted to have enough bandwidth left to take on more clients after recent connections ramp up in bitrate, otherwise it returns 0. The function accepts up to five arguments:

  • interface: see host_bitrate() documentation above.
  • Optional avg_bitrate: see host_bitrate() documentation above.
  • Optional num_routers: see host_bitrate() documentation above.
  • Optional host: see host_bitrate() documentation above.
  • Optional margin: the bitrate (megabits per second) headroom that should be taken into account during calculation, defaults to 0.

host_has_bw() returns whether or not the following statement is true:

predicted_host_bitrate + margin < host_bitrate_capacity

Examples of usage:

host_has_bw({interface="eths0"})
host_has_bw({margin=10, interface="eth0"})
host_has_bw({avg_bitrate=1, interface="eth0"})
host_has_bw({num_routers=4, interface="eth0"})
host_has_bw({host="custom_host", interface="eth0"})

host_has_bw_custom()

Same functionality as host_has_bw() but uses a custom selection input variable as bitrate and capacity input. The function accepts up to five arguments:

  • custom_capacity_var: the name of the selection input variable to be used for accessing host capacity.
  • custom_bitrate_var: see host_bitrate_custom() documentation
  • Optional margin: see host_has_bw() documentation above. above.
  • Optional avg_bitrate: see host_bitrate() documentation above.
  • Optional num_routers: see host_bitrate() documentation above.

Examples of usage:

host_has_bw_custom({custom_capacity_var="host1_capacity", custom_bitrate_var="host1_current_bitrate"})
host_has_bw_custom({margin=10, custom_capacity_var="host1_capacity", custom_bitrate_var="host1_current_bitrate"})
host_has_bw_custom({avg_bitrate=1, custom_capacity_var="host1_capacity", custom_bitrate_var="host1_current_bitrate"})
host_has_bw_custom({num_routers=4, custom_capacity_var="host1_capacity", custom_bitrate_var="host1_current_bitrate"})

Examples in routing

When using confcli it’s not recommended to use host_bitrate() since it’s not possible to make comparisons using <, > or similar operators. Using host_has_bw() makes it easy to construct conditions utilizing predictive load balancing.

host_has_bw() and host_has_bw_custom() are excellent candidates to be used as health check functions for hosts, see configuring CDNs and hosts for more details.

$ confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: weighted
  Adding a 'weighted' element
    rule : {
      name (default: ): weighted_and_has_bw
      type (default: weighted):
      targets : [
        target : {
          target (default: ): host1
          weight (default: 100):
          condition (default: always()): host_has_bw({interface="eths0"})
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): host2
          weight (default: 100): 100
          condition (default: always()): host_has_bw({margin=1000, interface="eths0"})
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): host3
          weight (default: 100):
          condition (default: always()): host_has_bw({avg_bitrate=1, num_routers=2, margin=5000, interface="eths0"})
        }
        Add another 'target' element to array 'targets'? [y/N]: n
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: n
]
Generated config:
{
  "rules": [
    {
      "name": "weighted_and_has_bw",
      "type": "weighted",
      "targets": [
        {
          "target": "host1",
          "weight": "100",
          "condition": "host_has_bw({interface="eths0"})"
        },
        {
          "target": "host2",
          "weight": "100",
          "condition": "host_has_bw({margin=1000, interface="eths0"})"
        },
        {
          "target": "host3",
          "weight": "100",
          "condition": "host_has_bw({avg_bitrate=1, num_routers=2, margin=5000, interface="eths0"})"
        }
      ]
    }
  ]
}
"routing": {
  "id": "weighted_and_has_bw",
  "member_order": "sorted",
  "members": [
    {
      "id": "1",
      "host_id": "host1",
      "weight_function": "return host_bitrate({interface="eths0"}) > 0 and 1 or 0"
    },
    {
      "id": "2",
      "host_id": "host2",
      "weight_function": "return host_bitrate({avg_bitrate=1, interface="eths0"}) > 10000 and 1 or 0"
    },
    {
      "id": "3",
      "host_id": "host3",
      "weight_function": "return host_has_bw({avg_bitrate=1, num_routers=2, margin=5000, interface="eths0"}) and 1 or 0"
    }
  ],
  "weight_function": "return 1"
}

5.4 - Security token verification

Only allow requests that contain a correct security token

The security token verification feature allows for ESB3024 Router to only process requests that contain a correct security token. The token is generated by the client, for example in the portal, using an algorithm that it shares with the router. The router verifies the token and rejects the request if the token is incorrect.

It is beyond the scope of this document to describe how the token is generated, that is described in the Security Tokens application note that is installed with the ESB3024 Router’s extra documentation.

Setting up a routing rule

The token verification is performed by calling the verify_security_token() function from a routing rule. The function returns 1 if the token is correct, otherwise it returns 0. It should typically be called from the first routing rule, to make requests with bad tokens fail as early as possible.

The confcli example assumes that the router already has rules configured, with an entry point named select_cdn. Token verification is enabled by inserting an “allow” rule first in the rule list.

confcli services.routing.rules -w
Running wizard for resource 'rules'

Hint: Hitting return will set a value to its default.
Enter '?' to receive the help string

rules : [
  rule can be one of
    1: allow
    2: consistentHashing
    3: contentPopularity
    4: deny
    5: firstMatch
    6: random
    7: rawGroup
    8: rawHost
    9: split
    10: weighted
  Choose element index or name: allow
  Adding a 'allow' element
    rule : {
      name (default: ): token_verification
      type (default: allow):
      condition (default: always()): verify_security_token()
      onMatch (default: ): select_cdn
    }
  Add another 'rule' element to array 'rules'? [y/N]: n
]
Generated config:
{
  "rules": [
    {
      "name": "token_verification",
      "type": "allow",
      "condition": "verify_security_token()",
      "onMatch": "select_cdn"
    }
  ]
}
Merge and apply the config? [y/n]: y

$ confcli services.routing.entrypoint token_verification
services.routing.entrypoint = 'token_verification'
"routing": {
  "id": "token_verification",
  "member_order": "sequential",
  "members": [
    {
      "id": "token_verification.0.select_cdn",
      "member_order": "weighted",
      "members": [
        ...
      ],
      "weight_function": "return verify_security_token() ~= 0"
    },
    {
      "id": "token_verification.1.rejected",
      "member_order": "sequential",
      "members": [],
      "weight_function": "return 1"
    }
  ],
  "weight_function": "return 100"
},

Configuring security token options

The secret parameter is not part of the router request, but needs to be configured separately in the router. That can be done with the host-config tool that is installed with the router.

Besides configuring the secret, host-config can also configure floating sessions and a URL prefix. Floating sessions are sessions that are not tied to a specific IP address. When that is enabled, the token verification will not take the IP address into account when verifying the token.

The security token verification is configured per host, where a host is the name of the host that the request was sent to. This makes it possible for a router to support multiple customer accounts, each with their own secret. If no configuration is found for a host, a configuration with the name default is used.

host-config supports three commands: print, set and delete.

Print

The print command prints the current configuration for a host. The following parameters are supported:

host-config print [-n <host-name>]

By default it prints the configuration for all hosts, but if the optional -n flag is given it will print the configuration for a single host.

Set

The set command sets the configuration for a host. The configuration is given as command line parameters. The following parameters are supported:

host-config set
    -n <host-name>
    [-f floating]
    [-p url-prefix]
    [-r <secret-to-remove>]
    [-s <secret-to-add>]
  • -n <host-name> - The name of the host to configure.
  • -f floating - A boolean option that specifies if floating sessions are accepted. The parameter accepts the values true and false.
  • -p url-prefix - A URL prefix that is used for identifying requests that come from a certain account. This is not used when verifying tokens.
  • -r <secret-to-remove> - A secret that should be removed from the list of secrets.
  • -s <secret-to-add> - A secret that should be added to the list of secrets.

For example, to set the secret “secret-1” and enable floating sessions for the default host, the following command can be used:

host-config set -n default -s secret-1 -f true

The set command only touches the configuration options that are mentioned on the command line, so the following command line will add a second secret to the default host without changing the floating session setting:

host-config set -n default -s secret-2

It is possible to set multiple secrets per host. This is useful when updating a secret, then both the old and the new secret can be valid during the transition period. After the transition period the old secret can be removed by typing:

host-config set -n default -r secret-1

Delete

The delete command deletes the configuration for a host. It supports the following parameters:

host-config delete -n <host-name>

For example, to delete the configuration for example.com, the following command can be used:

host-config delete -n example.com

Global options

host-config also has a few global options. They are:

  • -k <security-key> - The security key that is used when communicating with the router. This is normally retrieved automatically.
  • -h - Print a help message and exit.
  • -r <router> - The router to connect to. This default to localhost, but can be changed to connect to a remote router.
  • -v - Verbose output, can be given multiple times.

Debugging security token verification

The security token verification only logs messages when the log level is set to 4 or higher. Then it will only log some errors. It is possible to enable more verbose logging using the security-token-config that is installed together with the router.

When verbose logging is enabled, the router will log information about the token verification, including the configured token secrets, so it needs to be used with care.

The logged lines are prefixed with verify_security_token.

The security-token-config tool supports the commands print and set.

The print command prints the current configuration. If nothing is configured it will not print anything.

Set

The set command sets the configuration. The following parameters are supported:

security-token-config set
    [-d <enabled>]
  • -d <enabled> - A boolean option that specifies if debug logging should be enabled or not. The parameter accepts the values true and false.

5.5 - Subnets API

How to match clients into named subnets and use them in routing

ESB3024 Router provides utilities to quickly match clients into subnets. Any combination of IPv4 and IPv6 addresses can be used. To begin, a JSON file is needed, defining all subnets, e.g:

{
  "255.255.255.255/24": "area1",
  "255.255.255.255/16": "area2",
  "255.255.255.255/8": "area3",
  "90.90.1.3/16": "area4",
  "5.5.0.4/8": "area5",
  "2a02:2e02:9bc0::/48": "area6",
  "2a02:2e02:9bc0::/32": "area7",
  "2a02:2e02:9bc0::/16": "area8",
  "2a02:2e02:9de0::/44": "combined_area",
  "2a02:2e02:ada0::/44": "combined_area"
}

and PUT it to the endpoint :5001/v1/subnets or :5001/v2/subnets, the API version doesn’t matter for subnets:

curl -k -T subnets.json -H "Content-Type: application/json" https://router-host:5001/v1/subnets

Note that it is possible for several subnet CIDR strings to share the same label, effectively grouping them together.

This will load the subnets into the router which will then be usable within any Lua functions, accessed from the request and session tables respectively, e.g.

{
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "node1",
        "host_id": "host1",
        "weight_function": "return request.subnet == 'area1' and 1 or 0"
      }
    ]
  }
}

Subnet matching will use the client IP fetched from the request or session tables. When possible, the subnet with the longest matching subnet mask will be chosen, i.e. 255.255.255.255/28will be chosen over 255.255.255.255/24. If no matching subnets are found, the boolean value false will be returned.

NOTE: To use booleans in strings in Lua, calling tostring(bool) is required. Therefore, since subnet lookup might return false, the subnet call should be wrapped as: tostring(session.subnet) or tostring(requst.subnet).

Invalid ip-addresses will be omitted during subnet list construction accompanied by a message in the log displaying the invalid IP address.

5.6 - Lua Features

Detailed descriptions and examples of Lua features offered by ESB3024 Router.

5.6.1 - Health Check Functions

Built in Lua health check functions

This section details built in Lua functions that are meant to be used for host health checks. Note that these functions rely on data supplied by metric agents detailed in Cache hardware metrics: monitoring and routing. Make sure cache hardware metrics are supplied to the router before using any of these functions.

cpu_load_ok()

Parameters

The function accepts an optional argument table with the following keys:

  • Optional hostname: The name of the host. Defaults to the hostname of the selected host if not provided.
  • Optional cpu_load5_limit: The acceptable limit for the 5-minute CPU load. Defaults to 0.9 if not provided.

Returns

The function returns 1 if the five minute CPU load average is below their respective limits, and 0 otherwise.

Usage examples

cpu_load_ok()
cpu_load_ok({hostname = "custom_host"})
cpu_load_ok({cpu_load5_limit = 0.8})
cpu_load_ok({hostname = "custom_host", cpu_load5_limit = 0.8})

memory_usage_ok()

Parameters

The function accepts an optional argument table with the following keys:

  • Optional hostname: The name of the host. Defaults to the hostname of the selected host if not provided.
  • Optional memory_usage_limit: The acceptable limit for the memory usage. Defaults to 0.9 if not provided.

Returns

The function returns 1 if the memory usage is below the limit, and 0 otherwise.

Usage examples

memory_usage_ok()
memory_usage_ok({hostname = "custom_host"})
memory_usage_ok({memory_usage_limit = 0.7})
memory_usage_ok({hostname = "custom_host", memory_usage_limit = 0.7})

interfaces_online()

Parameters

The function accepts an argument table with the following keys:

  • Required interfaces: A string or a table of strings representing the network interfaces to check.
  • Optional hostname: The name of the host. Defaults to the hostname of the selected host if not provided.

Returns

The function returns 1 if all the specified interfaces are online, and 0 otherwise.

Usage examples

interfaces_online({interfaces = "eth0"})
interfaces_online({interfaces = {"eth0", "eth1"}})
interfaces_online({hostname = "custom_host", interfaces = "eth0"})
interfaces_online({hostname = "custom_host", interfaces = {"eth0", "eth1"}})

health_check()

Parameters

The function accepts an optional argument table with the following keys:

  • Required interfaces: A string or a table of strings representing the network interfaces to check.
  • Optional hostname: The name of the host. Defaults to the hostname of the selected host if not provided.
  • Optional cpu_load5_limit: The acceptable limit for the 5-minute CPU load. Defaults to 0.9 if not provided.
  • Optional memory_usage_limit: The acceptable limit for the memory usage. Defaults to 0.9 if not provided.

Returns

The function returns 1 if all the specified interfaces are online, and 0 otherwise.

Usage examples

health_check({interfaces = "eths0"})
health_check({hostname = "custom_host", interfaces = "eths0"})
health_check({cpu_load5_limit = 0.7, memory_usage_limit = 0.8, interfaces = "eth0"})
health_check({hostname = "custom_host", cpu_load5_limit = 0.7, memory_usage_limit = 0.8, interfaces = {"eth0", "eth1"}})

5.6.2 - Request Translation Function

Instructions for how to write a function to modify incoming requests before routing decisions are being made.

Specifies the body of a Lua function that inspects every incoming HTTP request and overwrites individual fields before further processing by the router.

Returns nil when nothing is to be changed, or HTTPRequest(t) where t is a table with any of the following optional fields:

  • Method
    • Description: Replaces the HTTP request method in the request being processed.
    • Type: string
    • Example: 'GET', 'POST'
  • Path
    • Description: Replaces the request path in the request being processed.
    • Type: string
    • Example: '/mycontent/superman.m3u8'
  • ClientIp
    • Description: Replaces client IP address in the request being processed.
    • Type: string
    • Example: '172.16.238.128'
  • Body
    • Description: Replaces body in the request being processed.
    • Type: string or nil
    • Example: '{"foo": "bar"}'
  • QueryParameters
    • Description: Adds, removes or replaces individual query parameters in the request being processed.
    • Type: nested table (indexed by number) representing an array of query parameters as {[1]='Name',[2]='Value'} pairs that are added to the request being processed, or overwriting existing query parameters with colliding names. To remove a query parameter from the request, specify nil as value, i.e. QueryParameters={..., {[1]='foo',[2]=nil} ...}. Returning a query parameter with a name but no value, such as a in the request '/index.m3u8?a&b=22' is currently not supported.
  • Headers
    • Description: Adds, removes or replaces individual headers in the request being processed.
    • Type: nested table (indexed by number) representing an array of request headers as {[1]='Name',[2]='Value'} pairs that are added to the request being processed, or overwriting existing request headers with colliding names. To remove a header from the request, specify nil as value, i.e. Headers={..., {[1]='foo',[2]=nil} ...}. Duplicate names are supported. A multi-value header such as Foo: bar1,bar2 is defined by specifying Headers={..., {[1]='foo',[2]='bar1'}, {[1]='foo',[2]='bar2'}, ...}.

Example of a request_translation_function body that sets the request path to a hardcoded value and adds the hardcoded query parameter a=b:

-- Statements go here
print('Setting hardcoded Path and QueryParameters')
return HTTPRequest({
  Path = '/content.mpd',
  QueryParameters = {
    {'a','b'}
  }
})

Arguments

The following (iterable) arguments will be known by the function:

QueryParameters

  • Type: nested table (indexed by number).

  • Description: Array of query parameters as {[1]='Name',[2]='Value'} pairs that were present in the query string of the request. Format identical to the HTTPRequest.QueryParameters-field specified for the return value above.

  • Example usage:

    for _, queryParam in pairs(QueryParameters) do
      print(queryParam[1]..'='..queryParam[2])
    end
    

Headers

  • Type: nested table (indexed by number).

  • Description: Array of request headers as {[1]='Name',[2]='Value'} pairs that were present in the request. Format identical to the HTTPRequest.Headers-field specified for the return value above. A multi-value header such as Foo: bar1,bar2 is seen in request_translation_function as Headers={..., {[1]='foo',[2]='bar1'}, {[1]='foo',[2]='bar1'}, ...}.

  • Example usage:

    for _, header in pairs(Headers) do
      print(header[1]..'='..header[2])
    end
    

Global metatables

In addition to the arguments above, the following (non-iterable) global metatables will be populated with fields that may be retrieved by the request_translation_function:

  • Note that metatables may only be accessed by already known keys:

Metatable request

  • request.method
    • Description: HTTP request method.
    • Type: string
    • Example: 'GET', 'POST'
  • request.body
    • Description: HTTP request body string.
    • Type: string or nil
    • Example: '{"foo": "bar"}'
  • request.major_version
    • Description: Major HTTP version such as x in HTTP/x.1.
    • Type: integer
    • Example: 1
  • request.minor_version
    • Description: Minor HTTP version such as x in HTTP/1.x.
    • Type: integer
    • Example: 1
  • request.protocol
    • Description: Transfer protocol variant.
    • Type: string
    • Example: 'HTTP', 'HTTPS'
  • request.client_ip
    • Description: IP address of the client issuing the request.
    • Type: string
    • Example: '172.16.238.128'
  • request.path_with_query_params
    • Description: Full request path including query parameters.
    • Type: string
    • Example: '/mycontent/superman.m3u8?b=y&c=z&a=x'
  • request.path
    • Description: Request path without query parameters.
    • Type: string
    • Example: '/mycontent/superman.m3u8'
  • request.query_params
    • Description: The query parameter string.
    • Type: string
    • Example: 'b=y&c=z&a=x'
  • request.filename
    • Description: The part of the path following the final slash, if any.
    • Type: string
    • Example: 'superman.m3u8'
  • request.subnet
    • Description: Subnet of client_ip.
    • Type: string or nil
    • Example: 'all'

Metatable request_query_params

Contains the query parameters keyed by name.

Example:

print(request_query_params.a)

Metatable request_headers

Contains the request headers keyed by name.

Example:

print(request_headers.a)

Multiple values are separated with a comma.

Global tables

In addition to the (non-iterable) metatables and (iterable) arguments above, the following global iterable tables are available from all Lua functions:

Table selection_input

Contains arbitrary, custom fields fed into the router by clients. Be careful to document any dependencies between the translation functions and selection inputs.

Example usage:

if selection_input then
    for k, v in pairs(selection_input) do
        print('here is '..'selection_input!')
        print(k..'='..v)
    end
else
    print('selection_input is nil')
end

Upon returning from request_translation_function, values in the request, request_query_params and request_headers metatables will reflect the new current status of the updated HTTP Request.

5.6.3 - Response Translation Function

Instructions for how to write a function to modify outgoing responses after a routing decision has been made.

Specifies the body of a Lua function that inspects every outgoing HTTP response and overwrites individual fields before being sent to the client.

Returns nil when nothing is to be changed, or HTTPResponse(t) where t is a table with any of the following optional fields:

  • Code
    • Description: Replaces status code in the response being sent.
    • Type: integer
    • Example: 200, 404
  • Text
    • Description: Replaces status text in the response being sent.
    • Type: string
    • Example: 'OK', 'Not found'
  • MajorVersion
    • Description: Replaces major HTTP version such as x in HTTP/x.1 in the response being sent.
    • Type: integer
    • Example: 1
  • MinorVersion
    • Description: Replaces minor HTTP version such as x in HTTP/1.x in the response being sent.
    • Type: integer
    • Example: 1
  • Protocol
    • Description: Replaces protocol in the response being sent.
    • Type: string
    • Example: 'HTTP', 'HTTPS'
  • Body
    • Description: Replaces body in the response being sent.
    • Type: string or nil
    • Example: '{"foo": "bar"}'
  • Headers
    • Description: Adds, removes or replaces individual headers in the response being sent.
    • Type: nested table (indexed by number) representing an array of response headers as {[1]='Name',[2]='Value'} pairs that are added to the response being sent, or overwriting existing request headers with colliding names. To remove a header from the response, specify nil as value, i.e. Headers={..., {[1]='foo',[2]=nil} ...}. Duplicate names are supported. A multi-value header such as Foo: bar1,bar2 is defined by specifying Headers={..., {[1]='foo',[2]='bar1'}, {[1]='foo',[2]='bar2'}, ...}.

Example of a response_translation_function body that sets the Location header to a hardcoded value:

-- Statements go here
print('Setting hardcoded Location')
return HTTPResponse({
  Headers = {
    {'Location', 'cdn1.com/content.mpd?a=b'}
  }
})

Arguments

The following (iterable) arguments will be known by the function:

Headers

  • Type: nested table (indexed by number).

  • Description: Array of response headers as {[1]='Name',[2]='Value'} pairs that are present in the response being sent. Format identical to the HTTPResponse.Headers-field specified for the return value above. A multi-value header such as Foo: bar1,bar2 is seen in response_translation_function as Headers={..., {[1]='foo',[2]='bar1'}, {[1]='foo',[2]='bar1'}, ...}.

  • Example usage:

    for _, header in pairs(Headers) do
      print(header[1]..'='..header[2])
    end
    

Global metatables

In addition to the arguments above, the following (non-iterable) global metatables will be populated with fields that may be retrieved by the response_translation_function:

Note that metatables may only be accessed by already known keys.

Metatable request

See documentation for request_translation_function. If the request translation function has modified the incoming request, the request metatable will contain those changes.

Metatable request_query_params

See documentation for request_translation_function. If the request translation function has modified the incoming request, the request_query_params metatable will contain those changes.

Metatable session_query_params

Alias for metatable request_query_params.

Metatable request_headers

See documentation for request_translation_function. If the request translation function has modified the incoming request, the request_headers metatable will contain those changes.

Metatable response

Contains all the parts of the outgoing response apart from the headers.

  • response.body
    • Description: HTTP response body string.
    • Type: string or nil
    • Example: '{"foo": "bar"}'
  • response.code
    • Description: HTTP response status code.
    • Type: integer
    • Example: 200, 404
  • response.text
    • Description: HTTP response status text.
    • Type: string
    • Example: 'OK', 'Not found'
  • response.major_version
    • Description: Major HTTP version such as x in HTTP/x.1.
    • Type: integer
    • Example: 1
  • response.minor_version
    • Description: Minor HTTP version such as x in HTTP/1.x.
    • Type: integer
    • Example: 1
  • response.protocol
    • Description: Transfer protocol variant.
    • Type: string
    • Example: 'HTTP', 'HTTPS'

Metatable response_headers

Contains the response headers keyed by name. Example:

print(response_headers.a)

Multiple values are separated with a comma.

Metatable session

See documentation for session_translation_function. If the session translation function has modified the incoming request, the session metatable will contain those changes.

Metatable session_groups

See documentation for session_translation_function.

Global tables

In addition to the (non-iterable) metatables and (iterable) arguments above, the following global iterable tables are available from all Lua functions:

Table selection_input

See documentation for request_translation_function. Values in the selection_input metatable will remain unchanged since a prior call to a request_translation_function.

5.6.4 - Session Translation Function

Instructions for how to write a function to modify a client session to affect how it is handled by the router.

Specifies the body of a Lua function that inspects a newly created session and may override its suggested type from “initial” to “instream” or vice versa.

Returns nil when the session type is to remain unchanged, or Session(t) where t is a table with a single field:

  • Type
    • Description: New type of the session.
    • Type: string
    • Example: 'instream', 'initial'

Example of a session_translation_function body that unconditionally makes all sessions 'instream':

-- Statements go here
print('Modifying session type')
return Session({['Type'] = 'instream'})

Arguments

The following (iterable) arguments are passed to the function:

Type

  • Description: The suggested type of the session.
  • Type: string
  • Example: 'instream', 'initial'

Example usage:

-- Flip session type
local newType = 'initial'
if Type == 'initial' then
    newType = 'instream'
end
print('Changing session type from ' .. Type .. ' to ' .. newType)
return Session({['Type'] = newType})

Global metatables

In addition to the arguments above, the following (non-iterable) global metatables will be populated with fields that may be retrieved by the response translation function.

Metatable request

See documentation for request_translation_function. If the request translation function has modified the incoming request, the request metatable will contain those changes.

Metatable request_query_params

See documentation for request_translation_function. If the request translation function has modified the incoming request, the request_query_params metatable will contain those changes.

Metatable session_query_params

Alias for metatable request_query_params.

Metatable request_headers

See documentation for request_translation_function. If the request translation function has modified the incoming request, the request_headers metatable will contain those changes.

Metatable session

  • session.client_ip
  • session.path_with_query_params
  • session.path
  • session.query_params
  • session.filename
  • session.subnet
  • session.host
    • Description: ID of the currently selected host for the session.
    • Type: string or nil
    • Example: 'host1'
  • session.id
    • Description: ID of the session.
    • Type: string
    • Example: '8eb2c1bdc106-17d2ff-00000000'
  • session.session_type
    • Description: Type of the session.
    • Type: string
    • Example: 'initial' or 'instream'. Identical to the value of the Type argument of the session translation function.
  • session.is_managed
    • Description: Identifies managed sessions.
    • Type: boolean
    • Example: true if Type/session.session_type is 'instream'

Metatable session_groups

Defines a mapping from session group name to boolean, indicating whether the session belongs to the session group or not.

Example usage:

if session_groups.vod then print('vod') else print('not vod') end

or

if session_groups['vod'] then print('vod') else print('not vod') end

Global tables

In addition to the (non-iterable) metatables and (iterable) arguments above, the following global iterable tables are available from all Lua functions:

Table selection_input

See documentation for See documentation for request_translation_function. Values in the selection_input metatable will remain unchanged since a prior call to a request_translation_function.

6 - Routing Engine

How the routing engine works and how to configure it

The central component of ESB3024 Router is the routing engine. It is what analyses an incoming video request and determines which CDN, if any, to pass it on to based on a variety of factors such as geographic location, content type and current server load.

Like most of the router, the routing engine is configured through the /v2/configuration API, see Configuration for general configuration information. The basic structure looks something like this:

{
  // ...
  "cdns": [
    {
      "id": "cdn1",
      "http_port": 80,
      "https_port": 443,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      },
      "redirecting": false
    }
  ],
  "hosts": [
    {
      "id": "host1"
      "cdn_id": "cdn1",
      "host": "host1.example.com",
      "ipv6_address": "fc00:ed6e:0:201:0:aff:fe10:306b"
    },
    {
      "id": "host2"
      "cdn_id": "cdn1",
      "host": "host2.example.com",
    }
  ],
  // ...
  "routing": {
    "id": "routing_table",
    "member_order": "weighted",
    "members": [
      {
        "id": "node1",
        "host_id": "host1",
        "weight_function": "return 100"
      },
      {
        "id": "node2",
        "host_id": "host2",
        "weight_function": "return 100"
      }
    ],
    "weight_function": "return 1"
  },
  // ...
}

In the above example the routing engine configuration is contained in the "routing" field of the JSON structure. The "cdns" and "hosts" fields are included to provide a context and show where the "host_id" values come from.

Tree components

The "routing" part of the configuration is a tree of routing nodes. These nodes can be either of two types: A leaf node or a branch node. Leaf nodes contain references to CDN hosts while branch nodes contain a list of member nodes but do not reference any hosts itself.

All nodes, regardless of type, have a unique ID as well as a weight function. The ID is used to identify the node in e.g. rule evaluation metrics and the weight function is used to dynamically determine if a node should be evaluated for the incoming request.

Let us go through the two structures one by one and look at their fields and possible values.

Branch node

Branch nodes do not refer to any hosts by themselves, but contain member nodes that may be traversed in order to find a suitable host.

{
  "id": "branch_node_example",
  "members": [],
  "member_order": "weighted",
  "weight_function": "return 1"
}
  • "id": The unique identifier for a node. Its value cannot be used on any other node, not even a leaf node.
  • "weight_function": A Lua function body returning an integer value. Exactly how the return value is used depends on the parent node’s member_order field, but a higher value generally indicates higher likelihood of the node being traversed. The value of 0 or less means that this node should not be used.
  • "members": A list of child nodes that will be evaluated if this node is selected by the routing engine. How they are evaluated depends on the value of the member_order field.
  • "member_order: A string determining how this node is to be evaluated. Allowed values are "sequential", "sorted" and "weighted".

Member Order Evaluation

As mentioned above, there are three different member orders and they affect how children of a branch node are traversed.

"sequential" means that all children are evaluated in the order they are listed in the configuration. If a child’s weight function returns a positive value, it will be picked for traversal even if one of its siblings has a higher weight. In order to skip a child its weight function must return 0.

If a child is picked for traversal but none of the leaf nodes in its subtree return a positive weight the routing engine will move on to the next sibling and attempt to find a leaf node there instead. This will continue until a leaf node is found or the entire tree is exhausted. If no valid leaf is found an error will be sent to the client.

"sorted" will cause the route engine to compute the weight of each child node, using each child’s "weight_function" Lua code, sort them from highest to lowest and pick the first node.

If no leaf node within that child’s subtree is accepted, i.e. they all return 0 or less, then the next child will be traversed and so on until a suitable host is found or there are no more subtrees left to evaluate. If no host is found, an error message will be sent to the client.

"weighted" tells the routing engine to evaluate the weight functions for all children, and then pick one of them randomly with a probability equal to the child’s weight divided by the sum of the weights of all immediate children.

Just as with the other two variants, if no suitable leaf node is found within the child’s subtree one of its siblings is picked instead. The same random method is used to pick subsequent children until a leaf node is accepted or the entire tree is exhausted. An error message will be sent to the client if no host is found.

Leaf node

Leaf nodes contain a reference to a host object rather than more child nodes.

{
  "id": "leaf_node_example",
  "host_id": "cdn_host_id",
  "weight_function": "return 100"
}
  • "id": The unique identifier for a node. Its value cannot be used on any other node, not even a branch node.
  • "host_id": The ID of one of the hosts in the "hosts" list. Represents the host the client will be routed through if this leaf node is selected.
  • "weight_function": A Lua function body that returns the weight of this particular node. The return value is used by the parent node to determine which leaf node to select for the current request.

A Note on Weight Functions

While the primary and mandatory purpose of the weight function is to return a value to let the routing engine determine which leaf node is most suitable for a client request, it can do more things. It is, however, vital that a weight function always returns an integer value.

There is a global Lua context for the functions to use to set and read global states, third party software can be used to keep track of external states and values through the selection_input API. It is even possible to set up a dummy node that prints out debug information but always returns 0, in effect having nothing to do with the actual routing.

Due to the global nature of variables in Lua it is important to remember to declare any variables used for temporary calculations as local or risk cross-contamination between client requests. It is generally recommended to avoid using global variables at all in the router unless absolutely certain what the consequences may be.

A Practical Example

Imagine we have a system for a Swedish market, with capacity enough for most of the time, but with rented offload capacity in the third party CDN for peak hour traffic as well as for any clients connecting from outside of Sweden. The private CDN is separated in a host for live content and one for VOD, but the offload CDN has a single host capable of handling anything. The private CDN in this example will also report its remaining capacity in per cent to the selection_input variable capacity_percent and as long as at least 10 % is unused it should accept new requests.

The router configuration will have to set up a few hosts, classifiers for incoming requests and finally a routing tree which we will go through in detail further down:

{
  "cdns": [
    {
      "http_port": 80,
      "https_port": 443,
      "id": "offload",
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      },
      "redirecting": false
    },
    {
      "http_port": 80,
      "https_port": 443,
      "id": "cdn",
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      },
      "redirecting": false
    }
  ],
  "hosts": [
    {
      "cdn_id": "offload",
      "host": "offload.example",
      "ipv6_address": "fc00:ed6e:0:201:0:aff:fe10:306b",
      "id": "offload"
    },
    {
      "cdn_id": "cdn",
      "host": "vod.cdn.example",
      "id": "cdn_vod"
    },
    {
      "cdn_id": "cdn",
      "host": "live.cdn.example",
      "id": "cdn_live"
    }
  ],
  "session_groups": [
    {
      "id": 1,
      "name": "Not Sweden",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": true,
            "name": "Not Sweden",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "country": "Sweden"
            }
          }
        ]
      ]
    },
    {
      "id": 2,
      "name": "IsLive",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "Is live content",
            "rule": {
              "pattern": "*/live/*",
              "rule_type": "string_match_rule",
              "source": "session/content_url_path"
            }
          }
        ]
      ]
    }
  ],
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "Offload if not Sweden",
        "host_id": "offload",
        "weight_function": "local retval = 0; if session_groups[\"Not Sweden\"] \
          then retval = 1; end; return retval"
      },
      {
        "id": "Private CDN",
        "member_order": "sorted",
        "members": [
          {
            "id": "live",
            "host_id": "cdn_live",
            "weight_function": "local retval = 0; if (selection_input.capacity_percent \
              or 0) > 10 and session_groups.IsLive then retval = 1; end; return retval"
          },
          {
            "id": "vod",
            "host_id": "cdn_vod",
            "weight_function": "local retval = 1; if (selection_input.capacity_percent \
              or 0) < 10 or session_groups.IsLive then retval = 0; end; return retval"
          }
        ],
        "weight_function": "return 1"
      },
      {
        "id": "Offload if no match",
        "host_id": "offload",
        "weight_function": "return 1"
      }
    ],
    "weight_function": "return 1"
  }
}

In order to understand this configuration, let us pretend some content is requested from a client:

  • Client IP: 94.127.35.102
  • Content path: "/live/news.m3u8"

The first thing that will happen is that the session group classifiers will analyse the request.

The IP is geolocated to Stockholm, Sweden. This means that the classifier named "Not Sweden" will be negative1, which in turn means that the session group named "Not Sweden" will also be negative.

The content path contains the string "/live/" which causes the classifier named "Is live content" to be positive and the corresponding session group "IsLive" will also become positive as a result.

With the classifiers done, it is time to traverse the routing tree.2

First the root node is evaluated. Its weight function returns a positive value, so its child nodes will be traversed.3 The member order is set to "sequential" so the routing engine goes through them one by one in the order they are written in the configuration and picks the first one to return a positive value.

"Offload if not Sweden" comes first, so its weight function is called. Since the session group "Not Sweden" is false, the weight function will return 0 and the engine moves on to the next sibling.

"Private CDN" has two children and the member order "sorted" meaning that they will both be evaluated, and the one with the highest weight will be picked. Assuming that the CDN capacity is sufficient, the child "live" will return 1 and "vod" will return 0, meaning that the host object with ID "cdn_live" will be selected and the client redirected to live.cdn.example/live/news.m3u8.

However, if the capacity is insufficient, neither child will return a positive value and the engine continues on to "Offload if no match" which always returns 1 and is therefore guaranteed to match and cause any unclaimed requests to end up at offload.example/live/news.m3u8.


  1. The classifier rule actually evaluates positively against IP:s located in Sweden, but since it is marked as inverted, the result is flipped and the whole thing ends up being negative instead. This is much simpler than trying to write rules that match against everything except Sweden. ↩︎

  2. Note that some of the weight functions are too long to fit on a single line. They have been split into multiple lines with a backslash indicating the break point. These backslashes and line breaks must be removed for the configuration to be valid and accepted by the router. ↩︎

  3. This is not strictly necessary. By default any node without an explicit weight function will have "return 100" instead. Explicit functions are used in this example for clarity. ↩︎