Confd and Confcli
confcli
to set up routing rulesConfiguration 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": {
"trustedProxies": [],
"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>
.
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: redirecting
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: dns
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 providedonMatch
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 thespreadFactor
.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 theonMiss
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
hashAlgorithm (default: MD5):
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,
"hashAlgorithm": "MD5",
"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 groupsin_any_session_group(str sg_name, ...)
: True if session belongs to any specified session groupin_subnet(str subnet_name)
: True if client IP belongs to the named subnetgt(str si_var, number value)
: True if selection_inputs[si_var] > valuegt(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] >= valuege(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] < valuelt(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] <= valuele(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] == valueeq(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] != valueneq(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 ofalways()
.
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: anonymousIp
2: asnIds
3: contentUrlPath
4: contentUrlQueryParameters
5: geoip
6: hostName
7: ipranges
8: random
9: regexMatcher
10: stringMatcher
11: subnet
12: userAgent
Choose element index or name: userAgent
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: redirecting
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: host
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: 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: ): 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: 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: ): 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.