1 - Route on GeoIP/ASN

How to write ESB3024 Router configurations for GeoIP and ASN-based routing using classifiers and session groups.

This page describes how to write configuration for GeoIP and ASN-based routing. For configuration in general, see Configuration.

For more details on session groups and classifiers, see Session Groups and Classification.

ASN

Routing on ASN is done through a combination of session group classifiers and Lua script weight functions. We need to create ASN classifier(s) and then associate them with a session group:

$ 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: geoip
  Adding a 'geoip' element
    classifier : {
      name (default: ): allowed_asn_classifier
      type (default: geoip): ⏎
      inverted (default: False): ⏎
      continent (default: ): ⏎
      country (default: ): ⏎
      cities : [
        city (default: ): ⏎
        Add another 'city' element to array 'cities'? [y/N]: ⏎
      ]
      asn (default: ): Agile*ISP
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "allowed_asn_classifier",
      "type": "geoip",
      "inverted": false,
      "continent": "",
      "country": "",
      "cities": [],
      "asn": "Agile*ISP"
    }
  ]
}
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: ): allowed_asn
    classifiers : [
      classifier (default: ): allowed_asn_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "allowed_asn",
      "classifiers": [
        "allowed_asn_classifier"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
{
  "session_groups": [
    {
      "id": 1,
      "name": "allowed_asn",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_asn_classifier",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "asn": "Agile*ISP"
            }
          }
        ]
      ]
    }
  ]
}

The asn value is a string, matching an ISP name or similar. It supports wildcard matching using asterisks and is case insensitive.

Note that each ASN classifier has to be in its own list. This is because classifiers within the same inner list all have to match for the entire list to be true, and that is impossible for classifiers that match against different ASNs. When the classifiers are in their own lists, it’s enough that one of them matches for the outer classifier list to also match.

Simple Example

$ 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 : [
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: ): asn_split_node
      type (default: split): ⏎
      rule (default: ): return in_session_group("allowed_asn")
      onMatch (default: ): allowed-asn-host
      onMiss (default: ): offload-host
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "asn_split_node",
      "type": "split",
      "condition": "in_session_group('allowed_asn')",
      "onMatch": "allowed-asn-host",
      "onMiss": "offload-host"
    }
  ]
}
Merge and apply the config? [y/n]: y
{
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "session_groups": [
    {
      "id": 1,
      "name": "allowed_asn",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_asn_classifier_agile",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "asn": "Agile*ISP"
            }
          }
        ],
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_asn_classifier_edgeware",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "asn": "Edgeware *"
            }
          }
        ]
      ]
    }
  ],
  "cdns": [
    {
      "id": "basic-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "offload-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "default-host",
      "cdn_id": "basic-cdn",
      "address_family": "ipv4",
      "host": "cdn-host.example"
    },
    {
      "id": "offload-host",
      "cdn_id": "offload-cdn",
      "address_family": "ipv4",
      "host": "offload-host.example"
    }
  ],
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "default-node",
        "host-id": "default-host",
        "weight_function": "return in_session_group('allowed_asn')"
      },
      {
        "id": "offload-node",
        "host_id": "offload-host",
        "weight_function": "return 1"
      }
    ]
  }
}

Geographical Location

Routing on geographical location is done in a similar fashion. Once again, we create location classifier(s) and associate them with a session group:

Geographical locations are provided by a MaxMind database.

$ 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: geoip
  Adding a 'geoip' element
    classifier : {
      name (default: ): allowed_location
      type (default: geoip): ⏎
      inverted (default: False): ⏎
      continent (default: ): ⏎
      country (default: ): ⏎
      cities : [
        city (default: ): stockholm
        Add another 'city' element to array 'cities'? [y/N]: ⏎
      ]
      asn (default: ): ⏎
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "allowed_location",
      "type": "geoip",
      "inverted": false,
      "continent": "",
      "country": "",
      "cities": [
        "stockholm"
      ],
      "asn": ""
    }
  ]
}
Merge and apply the config? [y/n]: y
  "session_groups": [
    {
      "id": 1,
      "name": "allowed_location",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_location_classifier",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "continent": "",
              "country": "",
              "region": "",
              "cities": ["stockholm"]
            }
          }
        ]
      ]
    }
  ]

At least one of the optional fields continent, country, region and cities are required. The classifier matches a request when all specified fields do (for the cities list, one matching member is sufficient). All values support wildcard matching using asterisks and are case insensitive.

Note that each location classifier has to be in its own list. This is because classifiers within the same inner list all have to match for the entire list to be true, and that is impossible for classifiers that match against different locations. When the classifiers are in their own lists, it’s enough that one of them matches for the outer classifier list to also match.

Simple Example

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 : [
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: ): geographic_location_split_node
      type (default: split): ⏎
      rule (default: ): return in_session_group("allowed_location")
      onMatch (default: ): allowed-location-host
      onMiss (default: ): offload-host
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "geographic_location_split_node",
      "type": "split",
      "condition": "in_session_group('allowed_location')",
      "onMatch": "allowed-location-host",
      "onMiss": "offload-host"
    }
  ]
}
Merge and apply the config? [y/n]: y
{
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "session_groups": [
    {
      "id": 1,
      "name": "allowed_location",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_location_classifier_sweden",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "country": "Sweden"
            }
          }
        ],
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_location_classifier_oslo",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "country": "Norway",
              "cities": ["Oslo"]
            }
          }
        ]
      ]
    }
  ],
  "cdns": [
    {
      "id": "basic-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "offload-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "default-host",
      "cdn_id": "basic-cdn",
      "address_family": "ipv4",
      "host": "cdn-host.example"
    },
    {
      "id": "offload-host",
      "cdn_id": "offload-cdn",
      "address_family": "ipv4",
      "host": "offload-host.example"
    }
  ],
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "default-node",
        "host_id": "default-host",
        "weight_function": "return in_session_group('allowed_location')"
      }
      {
        "id": "offload-node",
        "host_id": "offload-host",
        "weight_function": "return 1"
      }
    ]
  }
}

2 - Route on Subnet

How to write ESB3024 Router configurations for client routing based on subnets, either by using session groups or by using the subnets API

This page describes how to write configuration for subnet-based routing. For configuration in general, see Configuration.

For details on the subnets API, see Subnets API.

Subnet-based routing can be done either by using session groups and classifiers or by using the subnets API, allowing large amounts of named subnets.

Session Group Classifiers

Routing on subnets can be done through a combination of session group classifiers and Lua script weight functions. Configuring session groups and classifiers based on subnets and using them in routing may look like:

$ 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: ipranges
  Adding a 'ipranges' element
    classifier : {
      name (default: ): ip_ranges_classifier
      type (default: ipranges): ⏎
      inverted (default: False): ⏎
      ipranges : [
        iprange (default: ): 10.0.0.0/8
        Add another 'iprange' element to array 'ipranges'? [y/N]: y
        iprange (default: ): 192.168.0.0/16
        Add another 'iprange' element to array 'ipranges'? [y/N]: y
        iprange (default: ): 2001:0db8:85a3::/128
        Add another 'iprange' element to array 'ipranges'? [y/N]: ⏎
      ]
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "ip_ranges_classifier",
      "type": "ipranges",
      "inverted": false,
      "ipranges": [
        "10.0.0.0/8",
        "192.168.0.0/16",
        "2001:0db8:85a3::/128"
      ]
    }
  ]
}
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: ): allowed_subnets
    classifiers : [
      classifier (default: ): ip_ranges_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "allowed_subnets",
      "classifiers": [
        "ip_ranges_classifier"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
{
  "session_groups": [
    {
      "id": 1,
      "name": "allowed_subnets",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_subnet_classifier",
            "rule": {
              "rule_type": "ip_ranges_rule",
              "source": "session/client_ip",
              "ip_ranges": ["10.0.0.0/8", "192.168.0.0/16", "2001:0db8:85a3::/128"]
            }
          }
        ]
      ]
    }
  ]
}

The field ip_ranges is a list of CIDR-notation strings, supporting both IPv4 and IPv6 format. Incoming requests have their IP tested against the listed ranges. The classifier evaluates as true if any of the ranges in the classifier’s list matches the IP address of the request.

These session groups can then be used in routing as follows:

$ 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: ): subnet_split_node
      type (default: split): ⏎
      rule (default: ): return in_session_group('allowed_subnets')
      onMatch (default: ): allowed-host
      onMiss (default: ): offload-host
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "subnet_split_node",
      "type": "split",
      "condition": "in_session_group('allowed_subnets')",
      "onMatch": "allowed-host",
      "onMiss": "offload-host"
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.entrypoint subnet_split_node
services.routing.entrypoint = 'subnet_split_node'
{
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "cdns": [
    {
      "id": "allowed-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "offload-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "allowed-host",
      "cdn_id": "allowed-cdn",
      "host": "allowed-host.example"
    },
    {
      "id": "offload-host",
      "cdn_id": "offload-cdn",
      "host": "offload-host.example"
    }
  ],
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "allowed-node",
        "host_id": "allowed-host",
        "weight_function": "return in_session_group('allowed_subnets')"
      },
      {
        "id": "offload-node",
        "host_id": "offload-host",
        "weight_function": "return 1"
      }
    ]
  },
  "session_groups": [
    {
      "id": 1,
      "name": "allowed_subnets",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "allowed_subnet_classifier",
            "rule": {
              "rule_type": "ip_ranges_rule",
              "source": "session/client_ip",
              "ip_ranges": ["10.0.0.0/8", "192.168.0.0/16", "2001:0db8:85a3::/128"]
            }
          }
        ]
      ]
    }
  ]
}

Named Subnets

Named subnets are injected into the router in the form of JSON payloads to the Subnets API. Once the subnet data has been fed to the router, Lua functions can access the subnet name associated with an incoming request and use the result when it performs routing.

Note that confcli cannot be used to inject named subnets into the router.

Assume we have injected the following subnet configuration into the router:

{
  "10.0.0.0/8": "test_net_4",
  "192.168.0.0/16": "test_net_4",
  "2001:0db8:85a3::/128": "test_net_6"
}

A router configuration using this subnet configuration can then be constructed as:

$ 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: ): subnet_split_node
      type (default: split): ⏎
      rule (default: ): in_subnet('test_net_4')
      onMatch (default: ): allowed-host-4
      onMiss (default: ): offload-host
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "subnet_split_node",
      "type": "split",
      "condition": "in_subnet('test_net_4')",
      "onMatch": "allowed-host-4",
      "onMiss": "offload-host"
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.entrypoint subnet_split_node
services.routing.entrypoint = 'subnet_split_node'
{
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "cdns": [
    {
      "id": "allowed-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "offload-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "allowed-host-4",
      "cdn_id": "allowed-cdn",
      "address_family": "ipv4",
      "host": "allowed-host4.example"
    },
    {
      "id": "allowed-host-6",
      "cdn_id": "allowed-cdn",
      "address_family": "ipv6",
      "host": "allowed-host6.example"
    },
    {
      "id": "offload-host",
      "cdn_id": "offload-cdn",
      "address_family": "ipv4",
      "host": "offload-host.example"
    }
  ],
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "allowed-node-4",
        "host_id": "allowed-host-4",
        "weight_function": "return in_subnet('test_net_4')"
      },
      {
        "id": "allowed-node-6",
        "host_id": "allowed-host-6",
        "weight_function": "return in_subnet('test_net_6')"
      },
      {
        "id": "offload-node",
        "host_id": "offload-host",
        "weight_function": "return 1"
      }
    ]
  }
}

Note the absence of session groups in this configuration, instead relying solely on the named subnet API to making routing decisions based on subnets.

3 - Route on Content Type

How to write ESB3024 Router configurations for content-based routing

This page describes how to write configuration for content-based routing. For configuration in general, see Configuration.

Two ways to route on content type will be demonstrated: content path based and hostname based using session groups and classifiers. For more details on session groups and classifiers, see Session Groups and Classification.

Content Path

Routing on content path is done through a combination of session group classifiers and Lua script weight functions. Two suitable classifiers for content path matching are string_match_rule and regex_rule.

The pattern is a string matching the path segment of a request URL, including the file name. When selecting the string_match_rule type, use asterisks for wildcard matching. When selecting the regex_rule follow the C++11 std::regex ECMAScript syntax to make valid patterns. Make sure to write patterns that match the entire path segment, not just part of it.

Note that each content classifier normally has to be in its own list. This is because classifiers within the same inner list all have to match for the entire list to be true, and that is impossible for classifiers that match against different content paths. When the classifiers are in their own lists, it’s enough that one of them matches for the outer classifier list to also match.

Simple Example

$ 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: contentUrlPath
  Adding a 'contentUrlPath' element
    classifier : {
      name (default: ): live_content_classifier
      type (default: contentUrlPath): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): ⏎
      pattern (default: ): *live_tv*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: y
  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: contentUrlPath
  Adding a 'contentUrlPath' element
    classifier : {
      name (default: ): news_content_classifier
      type (default: contentUrlPath): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): regex
      pattern (default: ): /([^/]+/)?news_reports_\\d+/.*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: y
  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: contentUrlPath
  Adding a 'contentUrlPath' element
    classifier : {
      name (default: ): hls_content_classifier
      type (default: contentUrlPath): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): ⏎
      pattern (default: ): *.m3u8
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: n
]
Generated config:
{
  "classifiers": [
    {
      "name": "live_content_classifier",
      "type": "contentUrlPath",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*live_tv*"
    },
    {
      "name": "news_content_classifier",
      "type": "contentUrlPath",
      "inverted": false,
      "patternType": "regex",
      "pattern": "/([^/]+/)?news_reports_\\\\d+/.*"
    },
    {
      "name": "hls_content_classifier",
      "type": "contentUrlPath",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*.m3u8"
    }
  ]
}
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: ): live_content
    classifiers : [
      classifier (default: ): live_content_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: y
  sessionGroup : {
    name (default: ): news_content
    classifiers : [
      classifier (default: ): news_content_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: y
  sessionGroup : {
    name (default: ): hls_content
    classifiers : [
      classifier (default: ): hls_content_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "live_content",
      "classifiers": [
        "live_content_classifier"
      ]
    },
    {
      "name": "news_content",
      "classifiers": [
        "news_content_classifier"
      ]
    },
    {
      "name": "hls_content",
      "classifiers": [
        "hls_content_classifier"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
{
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "session_groups": [
    {
      "id": 1,
      "name": "live_content",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "live_content_classifier",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/content_url_path",
              "pattern": "*live_tv*"
            }
          }
        ]
      ],
      [
        [
          {
            "id": 2,
            "inverted": false,
            "name": "news_content_classifier",
            "rule": {
              "rule_type": "regex_rule",
              "source": "session/content_url_path",
              "pattern": "/([^/]+/)?news_reports_\\d+/.*"
            }
          }
        ]
      ]
    },
    {
      "id": 2,
      "name": "hls_content",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "hls_content_classifier",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/content_url_path",
              "pattern": "*.m3u8"
            }
          }
        ]
      ]
    }
  ],
  "cdns": [
    {
      "id": "live-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "vod-hls-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "vod-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "live-host",
      "cdn_id": "live-cdn",
      "address_family": "ipv4",
      "host": "live-host.example"
    },
    {
      "id": "vod-hls-host",
      "cdn_id": "vod-hls-cdn",
      "address_family": "ipv4",
      "host": "vod-hls-host.example"
    },
    {
      "id": "vod-host",
      "cdn_id": "vod-cdn",
      "address_family": "ipv4",
      "host": "vod-host.example"
    }
  ],
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "live-node",
        "host_id": "live-host",
        "weight_function": "return session_groups.live_content and 1 or 0"
      },
      {
        "id": "vod-hls-node",
        "host_id": "vod-hls-host",
        "weight_function": "return session_groups.hls_content and 1 or 0"
      },
      {
        "id": "vod-node",
        "host_id": "vod-host",
        "weight_function": "return 1"
      }
    ]
  }
}

Hostname

In cases where there are separate domain or subdomain names for different content types, it’s simple to make classifiers that filter on those names.

The pattern is a string matching the hostname segment of a request URL, including the file name. When selecting the string_match_rule type, use asterisks for wildcard matching. When selecting the regex_rule follow the C++11 std::regex ECMAScript syntax to make valid patterns. Make sure to write patterns that match the entire hostname segment, not just part of it.

Note that each hostname classifier has to be in its own list. This is because classifiers within the same inner list all have to match for the entire list to be true, and that is impossible for classifiers that match against different hostnames. When the classifiers are in their own lists, it’s enough that one of them matches for the outer classifier list to also match.

Simple example

$ 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: hostName
  Adding a 'hostName' element
    classifier : {
      name (default: ): live_content_classifier
      type (default: hostName): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): ⏎
      pattern (default: ): live.example.com
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: y
  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: hostName
  Adding a 'hostName' element
    classifier : {
      name (default: ): news_content_classifier
      type (default: hostName): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): regex
      pattern (default: ): /([^\\.]+/)\\.news_reports_\\d+/\\.example\\.com
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "live_content_classifier",
      "type": "hostName",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "live.example.com"
    },
    {
      "name": "news_content_classifier",
      "type": "hostName",
      "inverted": false,
      "patternType": "regex",
      "pattern": "/([^\\\\.]+/)\\\\.news_reports_\\\\d+/\\\\.example\\\\.com"
    }
  ]
}
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: ): live_content
    classifiers : [
      classifier (default: ): live_content_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: y
  sessionGroup : {
    name (default: ): news_content
    classifiers : [
      classifier (default: ): news_content_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: y
  sessionGroup : {
    name (default: ): hls_content
    classifiers : [
      classifier (default: ): hls_content_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "live_content",
      "classifiers": [
        "live_content_classifier"
      ]
    },
    {
      "name": "news_content",
      "classifiers": [
        "news_content_classifier"
      ]
    },
    {
      "name": "hls_content",
      "classifiers": [
        "hls_content_classifier"
      ]
    }
  ]
}
{
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "session_groups": [
    {
      "id": 1,
      "name": "live_content",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": false,
            "name": "live_content_classifier",
            "rule": {
              "rule_type": "string_match_rule",
              "source": "session/hostname",
              "pattern": "live.example.com"
            }
          }
        ],
        [
          {
            "id": 2,
            "inverted": false,
            "name": "news_content_classifier",
            "rule": {
              "rule_type": "regex_rule",
              "source": "session/hostname",
              "pattern": "/([^\\.]+/)\\.news_reports_\\d+/\\.example\\.com"
            }
          }
        ]
      ]
    }
  ],
  "cdns": [
    {
      "id": "live-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "vod-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "live-host",
      "cdn_id": "live-cdn",
      "address_family": "ipv4",
      "host": "live-host.example"
    },
    {
      "id": "vod-host",
      "cdn_id": "vod-cdn",
      "address_family": "ipv4",
      "host": "vod-host.example"
    }
  ],
  "routing": {
    "id": "routing_table",
    "member_order": "sequential",
    "members": [
      {
        "id": "live-node",
        "host_id": "live-host", 
        "weight_function": "return session_groups.live_content and 1 or 0"
      },
      {
        "id": "vod-node",
        "host_id": "vod-host",  
        "weight_function": "return 1"
      }
    ]
  }
}

Using the session groups in confcli

The constructed session groups can be used in routing by creating a rule that references them:

$ 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: ): classifier_routing_rule
      type (default: firstMatch): ⏎
      targets : [
        target : {
          onMatch (default: ): live-host
          rule (default: ): in_all_session_groups('live_content', 'news_content')
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          onMatch (default: ): vod-hls-host
          rule (default: ): in_session_group('hls_content')
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          onMatch (default: ): offload-host
          rule (default: ): always()
        }
        Add another 'target' element to array 'targets'? [y/N]: ⏎
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "classifier_routing_rule",
      "type": "firstMatch",
      "targets": [
        {
          "onMatch": "live-host",
          "condition": "in_all_session_groups('live_content', 'news_content')"
        },
        {
          "onMatch": "vod-hls-host",
          "condition": "in_session_group('hls_content')"
        },
        {
          "onMatch": "offload-host",
          "condition": "always()"
        }
      ],
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.entrypoint classifier_routing_rule
services.routing.entrypoint = classifier_routing_rule

4 - Route on Content Popularity

How to write ESB3024 Router configurations for content popularity based routing

This page describes how to write configuration for content popularity routing. For configuration in general, see Configuration.

For more details on content popularity tuning and routing, see

Content Popularity.

Content Popularity

The router tracks content popularity which can be utilized for routing. Using the configuration tool confcli, creating a content popularity based routing configuration for hierarchical and multi-edge scenarios will be demonstrated.

Hierarchical

Consider a CDN setup with edge streamers that has cached popular content and a central streamer where all content is available. You can decide where to route clients based on the requested content’s popularity.

Assuming that appropriate streamer hosts have already been configured, configuration will look like:

$ 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: ): contentPopularityHierarchy
      type (default: contentPopularity): ⏎
      popularityThreshold (default: 10): 2000
      onPopular (default: ): edgeStreamer
      onUnpopular (default: ): centralStreamer
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "contentPopularityHierarchy",
      "type": "contentPopularity",
      "popularityThreshold": 2000.0,
      "onPopular": "edgeStreamer",
      "onUnpopular": "offloadStreamer"
    }
  ]
}
Merge and apply the config? [y/n]: y

where name is the name of the rule, isPopular is the rule to route to if the content is popular, otherwise the rule isUnpopular is routed to. Lastly, popularityThreshold is the threshold for which content is considered popular. Configuring popularityThreshold = 2001 means that the top 2000 most popular assets will be routed to edgeStreamer.

The rule contentPopularityHierarchy can then be used to construct your routing tree.

Multi-edge

Consider a CDN setup with three edge streamers, edge1, edge2 and edge3, configured to cache very popular content, mildly popular content and unpopular content respectively. Assuming that appropriate streamer hosts have already been configured, the following configuration can be used for correctly routing a request in this scenario:

$ 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: ): mildlyAndUnpopular
      type (default: contentPopularity): ⏎
      popularityThreshold (default: 10): 2001
      onPopular (default: ): edge2
      onUnpopular (default: ): edge3
    }
  Add another 'rule' element to array 'rules'? [y/N]: y
  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: ): multiLevelPopularity
      type (default: contentPopularity): ⏎
      popularityThreshold (default: 10): 101
      onPopular (default: ): edge1
      onUnpopular (default: ): mildyAndUnpopular
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "mildlyAndUnpopular",
      "type": "contentPopularity",
      "popularityThreshold": 2001.0,
      "onPopular": "edge2",
      "onUnpopular": "edge3"
    },
    {
      "name": "multiLevelPopularity",
      "type": "contentPopularity",
      "popularityThreshold": 101.0,
      "onPopular": "edge1",
      "onUnpopular": "mildyAndUnpopular"
    }
  ]
}
Merge and apply the config? [y/n]: y

By configuring a rule to route to multiLevelPopularity, this configuration will route requests for the top 100 most popular content to edge1, popularity ranking 101-2000 to edge2 and popularity ranking > 2000 to edge3. Since the contentPopularity rule offers a binary routing choice of either isPopular or isUnpopular, multiple contentPopularity rules can be utilized to construct multi-leveled content popularity based routing configurations.

Application

To use any of these two configurations in your installation, you’ll need to configure either a rule or the entrypoint to route to either multiLevelPopularity or contentPopularityHierarchy.

5 - Route on Selection Input

How to write ESB3024 Router configurations to route on selection input, using CDN bandwidth and number of ongoing sessions as example.

This page describes how to write configuration for routing on bandwidth usage or number of ongoing sessions in Edgeware CDN:s by using the selection input API. For configuration in general, see Configuration.

For more details on the selection input API, see Selection Input.

Selection Input

The selection input API allows you to inject custom data, that can be used during routing, into the router. For this use case, we will use CDN bandwidth and the number of ongoing sessions to demonstrate the capabilities of the selection input API.

Imagine that we have a monitor constantly polling the bandwidth and ongoing sessions of hosts in a CDN. These values are injected into the selection input API as JSON packets:

{
  "edge-host-available-bps": 1000000000,
  "edge-host-ongoing-sessions": 300
}

These keys will end up in the router Lua context as “selection inputs”, arbitrary values or JSON objects that can be used to calculate routing weights.

The selection input variable edge-host-available-bps will be available through either selection_input.edge-host-available-bps or si('edge-host-available-bps'). The function si(si_var) fetches the selection input value if it exists, otherwise it returns 0. However, when comparing selection input variables with numbers, there are a number of built-in Lua functions that will simplify things, such as gt() and lt(). For a complete list and more details on these functions, see Built-in Functions.

Simple Example

$ 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: ): selection-input-routing
      type (default: firstMatch): ⏎
      targets : [
        target : {
          onMatch (default: ): edge-host
          rule (default: ): lt('edge_host_ongoing_sessions', 1000)
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          onMatch (default: ): edge-host
          rule (default: ): gt('edge_host_available_bps', 5000000)
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          onMatch (default: ): offload-host
          rule (default: ): always()
        }
        Add another 'target' element to array 'targets'? [y/N]: ⏎
      ]
      onMiss (default: ): offload-host
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "selection-input-routing",
      "type": "firstMatch",
      "targets": [
        {
          "onMatch": "edge-host",
          "condition": "lt('edge_host_ongoing_sessions', 1000)"
        },
        {
          "onMatch": "edge-host",
          "condition": "gt('edge_host_available_bps', 5000000)"
        },
        {
          "onMatch": "offload-host",
          "condition": "always()"
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
$ confcli services.routing.entrypoint selection-input-routing
services.routing.entrypoint = 'selection-input-routing'
{
  "content_server": {
    "http_enable": true,
    "http_port": 80,
    "https_enable": true,
    "https_port": 443
  },
  "cdns": [
    {
      "id": "edge-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "offload-cdn",
      "http_port": 80,
      "https_port": 443,
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "edge-host",
      "cdn_id": "edge-cdn",
      "host": "edge-host.example"
    },
    {
      "id": "offload-host",
      "cdn_id": "offload-cdn",
      "host": "offload-host.example"
    }
  ],
  "routing": {
    "id": "routing-table",
    "member_order": "sequential",
    "members": [
      {
        "id": "edge-host-sessions-available",
        "host_id": "edge-host",
        "weight_function": "return (lt('edge_host_ongoing_sessions', 1000) ~= 0) and 1 or 0"
      },
      {
        "id": "edge-host-bandwith-available",
        "host_id": "edge-host",
        "weight_function": "return (gt('edge_host_available_bps', 5000000) ~= 0) and 1 or 0"
      },
      {
        "id": "offload",
        "host_id": "offload-host",
        "weight_function": "return 1"
      }
    ]
  }
}

6 - Adapt to multi-CDN

How to write ESB3024 Router configuration to handle multi-CDN setups with tokens.

This page describes how to write configuration for setups that require the URL sent back to the client to be rewritten based on routing results, for example by adding a token to the path or as a query string parameter. For configuration in general, see Configuration.

In order to change the redirect location URL, we need to add a response translation function to the confd configuration. By default, all translation functions are empty:

$ confcli services.routing.translationFunctions
{
    "translationFunctions": {
        "request": "",
        "response": ""
    }
}

For full documentation of the response translation function, please see Lua Hooks.

Simple example

Translation functions tend to become large, which makes it hard to write them inside a single string. Instead, the function can be written in a separate .lua script file that is uploaded to the router and referenced from the confd configuration.

The following example will take this approach, adding a script called "rewrite.lua" to the configured custom Lua folder /tmp/custom_lua on the router. For more information see the Configuration JSON Overview.

Lua script

The following script assumes the existence of the function parse_url that returns an object with URL segments that can be altered and then combined into a URL string using the tostring(obj) function again.

Beware that the router does not validate the URL in the Location header, it is up to the Lua script writer to ensure that only URLs are returned.

function response_rewrite(Headers)
   -- The live server has the token as part of the path, added first before the
   -- path from the request.
   local location = parse_url(response_headers.location)

   if location.host == "live-host.example" then
      location.path = "/live-prefix" .. location.path
      return HTTPResponse(
         {
            Headers = {
               {"location", tostring(location)}
            }
         }
      )
   end

   -- The VOD server expects the token as a query string rather than as part
   -- of the path segment.
   -- Since VOD can be used for offload, check that session_groups.is_vod
   -- is set as well.
   if location.host == "vod-host.example" and session_groups.is_vod then
      location.query["token"] = md5sum(secret_key .. request.path)
      return HTTPResponse(
         {
            Headers = {
               {"location", tostring(location)}
            }
         }
      )
   end

   -- If the request was neither in the is_live session group, nor contained
   -- the path element "vod", we return a nil object to indicate that we don't
   -- want to change anything in the response.
   return nil
end

Configuration

To configure a response translation function using with the custom Lua function response_rewrite() using confcli, run the command:

$ confcli services.routing.translationFunctions.response 'return response_rewrite()'
services.routing.translationFunctions.response = 'return response_rewrite()'

An example confd configuration utilizing this translation function based on session groups would look like this:

$ confcli services.routing
{
  "translationFunctions": {
    "request": "",
    "response": "return response_rewrite()"
  },
  "sessionGroups": [
    {
      "name": "is_live",
      "classifiers": [
          "is_live_classifier"
      ]
    },
    {
      "name": "is_vod",
      "classifiers": [
          "is_vod_classifier"
      ]
    }
  ],
  "classifiers": [
    {
      "name": "is_live_classifier",
      "type": "contentUrlPath",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*/live/*"
    },
    {
      "name": "is_vod_classifier",
      "type": "contentUrlPath",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*/vod/*"
    }
  ],
  "hostGroups": [
    {
      "name": "live-cdn",
      "type": "host",
      "httpPort": 80,
      "httpsPort": 443,
      "hosts": [
          {
              "name": "live-host",
              "hostname": "live-host.example",
              "ipv6_address": ""
          }
      ]
    },
    {
      "name": "vod-cdn",
      "type": "host",
      "httpPort": 80,
      "httpsPort": 443,
      "hosts": [
          {
              "name": "vod-host",
              "hostname": "vod-host.example",
              "ipv6_address": ""
          }
      ]
    }
  ],
  "rules": [
    {
      "name": "multiCdnExample",
      "type": "firstMatch",
      "targets": [
        {
          "condition": "in_session_group('is_live')",
          "onMatch": "live-host"
        },
        {
          "condition": "in_session_group('is_vod')",
          "onMatch": "vod-host"
        },
        // Use the vod-host as offload for any content, but don't set any
        // token for requests not classified as vod.
        {
          "condition": "always()",
          "onMatch": "vod-host"
        }
      ]
    }
  ],
  "entrypoint": "multiCdnExample",
  "applyConfig": true
}

7 - How to use ACD router for EDNS routing

How to write ACD Route configurations for EDNS routing

This page describes how to write configuration for EDNS routing. For configuration in general, see Configuration.

EDNS routing

In your CDN architecture, you might utilize EDNS routing to redirect clients. Whether it’s your own CDN or a third party CDN, the ESB3024 Router supports creating EDNS requests and sending them to any DNS server.

Configuration

To configure EDNS routing, you first need to create a DNS type host. Using confcli:

[user@acd-router ~]# 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: ): edns-cdn
      type (default: dns): ⏎
      hosts : [
        host : {
          name (default: ): edns-host
          hostname (default: ): ednshost.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": "edns-cdn",
      "type": "dns",
      "hosts": [
        {
          "name": "edns-host",
          "hostname": "ednshost.com",
          "ipv6_address": ""
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

Note that the hostname is the name of the DNS server that will receive the EDNS request. You can now use the DNS host when creating the routing tree, e.g. in a random type rule:

[user@router ~]# 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-rule
      type (default: random): ⏎
      targets : [
        target (default: ): edns-host
        Add another 'target' element to array 'targets'? [y/N]: ⏎
      ]
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "random-rule",
      "type": "random",
      "targets": [
        "edns-host"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y
[user@router ~]# confcli services.routing.entrypoint random-rule
services.routing.entrypoint = 'random-rule'

Or simply use it as the entrypoint:

confcli services.routing.entrypoint edns-host

8 - How to use ESB3024 Router with Edgeware CDN Request Router

How to use ESB3024 Router with ESB3008 Request Router

This page describes how to write configuration for using ESB3024 Router in conjunction with ESB3008 Request Router. It can request a host from an ESB3008 Request Router and forward the response to the client, perform load balancing between several Request Routers and handle fallback to an external CDN in case no Request Routers are able to service the request.

Simple Example

# The tuning parameter `target.requestAttempts` controls the number of attempts
# made to find a host. In this example we want to fallback to the external CDN
# in case both Request Routers are down, so we set it to at least 3.

$ confcli services.routing.tuning.target.requestAttempts 3
services.routing.tuning.target.requestAttempts = 3

# Create a classifier to filter incoming requests from Sweden.

$ 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: geoip
  Adding a 'geoip' element
    classifier : {
      name (default: ): sweden_classifier
      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_classifier",
      "type": "geoip",
      "inverted": false,
      "continent": "",
      "country": "sweden",
      "cities": [
        ""
      ],
      "asn": ""
    }
  ]
}
Merge and apply the config? [y/n]: y

# Create a session group for the non-Swedish requests, to use in routing rules

$ 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: ): sweden
    classifiers : [
      classifier (default: ): sweden_classifier
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "sweden",
      "classifiers": [
        "sweden_classifier"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

# The internal CDN uses Edgeware Request Routers that return a redirect
# location.  The router takes the content from the client request,
# and makes its own request to the Request Router using that same
# content.  The returned location is then forwarded to the client.
#
# In case the Request Router responds with an error code, or times out,
# the router will continue traversing the routing tree until a
# successful route is found or the entire tree has been evaluated.

$ 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-cdn
      type (default: redirecting): ⏎
      httpPort (default: 80): ⏎
      httpsPort (default: 443): ⏎
      hosts : [
        host : {
          name (default: ): internal-host-1
          hostname (default: ): internal-host-1.example.com
          ipv6_address (default: ): ⏎
        }
        Add another 'host' element to array 'hosts'? [y/N]: y
        host : {
          name (default: ): internal-host-2
          hostname (default: ): internal-host-2.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-cdn",
      "type": "redirecting",
      "httpPort": 80,
      "httpsPort": 443,
      "hosts": [
        {
          "name": "internal-host-1",
          "hostname": "internal-host-1.example.com",
          "ipv6_address": ""
        },
        {
          "name": "internal-host-2",
          "hostname": "internal-host-2.example.com",
          "ipv6_address": ""
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

# The offload CDN is non-redirecting, meaning that router does not
# make any request to its associated hosts to ask for a redirect location
# to forward to the clients, instead it simply returns a location using
# one of the CDN's hosts substituted for the router's host in the
# client request possibly with some kind of respsonse translation to add
# e.g. tokens or path prefixes.

$ 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: host
  Adding a 'host' element
    hostGroup : {
      name (default: ): offload-cdn
      type (default: host): ⏎
      httpPort (default: 80): ⏎
      httpsPort (default: 443): ⏎
      hosts : [
        host : {
          name (default: ): offload-host
          hostname (default: ): offload-host.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": "offload-cdn",
      "type": "host",
      "httpPort": 80,
      "httpsPort": 443,
      "hosts": [
        {
          "name": "offload-host",
          "hostname": "offload-host.example.com",
          "ipv6_address": ""
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

# Begin with adding a load balancing route rule between the two internal
# hosts. Let's make internal-host-2 have twice the capacity of
# internal-host-1 by using a weighted rule.

$ 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: ): balancer
      type (default: weighted): ⏎
      targets : [
        target : {
          target (default: ): internal-host-1
          weight (default: 100): 1,
          rule (default: always()): always()
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          target (default: ): internal-host-2
          weight (default: 100): 2,
          rule (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": "balancer",
      "type": "weighted",
      "targets": [
        {
          "target": "internal-host-1",
          "weight": "1",
          "rule:": "always()"
        },
        {
          "target": "internal-host-2",
          "weight": "2",
          "rule:": "always()"
        }
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

# Make an offload rule to route any traffic not in Sweden to the
# offload CDN.

$ 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: ): offload
      type (default: firstMatch): ⏎
      targets : [
        target : {
          onMatch (default: ): offload-host
          rule (default: ): not in_session_group('sweden')
        }
        Add another 'target' element to array 'targets'? [y/N]: y
        target : {
          onMatch (default: ): balancer
          rule (default: ): in_session_group('sweden')
        }
        Add another 'target' element to array 'targets'? [y/N]: ⏎
      ]
      onMiss (default: ): offload-host
    }
  Add another 'rule' element to array 'rules'? [y/N]: ⏎
]
Generated config:
{
  "rules": [
    {
      "name": "offload",
      "type": "firstMatch",
      "targets": [
        {
          "onMatch": "offload-host",
          "condition": "not in_session_group('sweden')"
        },
        {
          "onMatch": "balancer",
          "condition": "in_session_group('sweden')"
        }
      ],
      "onMiss": "offload-host"
    }
  ]
}
Merge and apply the config? [y/n]: y
{
  "tuning": {
    // "target_request_attempts" controls the number of attempts to find a host.
    // In this example we want to fallback to the external CDN in case both
    // Request Routers are down, so we set it to 3.
    "target_request_attempts": 3
  },
  "session_groups": [
    {
      "id": 1,
      "name": "not_sweden",
      "classifiers": [
        [
          {
            "id": 1,
            "inverted": true,
            "name": "not_sweden_classifier",
            "rule": {
              "rule_type": "geoip_rule",
              "source": "session/client_ip",
              "country": "Sweden"
            }
          }
        ]
      ]
    }
  ],
  "cdns": [
    {
      "id": "internal-cdn",
      "http_port": 80,
      "https_port": 443,
      // The internal CDN uses Edgeware Request Routers that return a redirect
      // location.  The router takes the content from the client request,
      // and makes its own request to the Request Router using that same
      // content.  The returned location is then forwarded to the client.
      //
      // In case the Request Router responds with an error code, or times out,
      // the router will continue traversing the routing tree until a
      // successful route is found or the entire tree has been evaluated.
      "redirecting": true,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    },
    {
      "id": "offload-cdn",
      "http_port": 80,
      "https_port": 443,
      // The offload CDN is non-redirecting, meaning that router does not
      // make any request to its associated hosts to ask for a redirect location
      // to forward to the clients, instead it simply returns a location using
      // one of the CDN's hosts substituted for the router's host in the
      // client request possibly with some kind of respsonse translation to add
      // e.g. tokens or path prefixes.
      "redirecting": false,
      "manifest_availability_check": {
        "enabled": false,
        "session_group_ids": []
      }
    }
  ],
  "hosts": [
    {
      "id": "internal-host-1",
      "cdn_id": "internal-cdn",
      "address_family": "ipv4",
      "host": "internal-host-1.example"
    },
    {
      "id": "internal-host-2",
      "cdn_id": "internal-cdn",
      "address_family": "ipv4",
      "host": "internal-host-2.example"
    },
    {
      "id": "offload-host",
      "cdn_id": "offload-cdn",
      "address_family": "ipv4",
      "host": "offload-host.example"
    }
  ],
  "routing": {
    "id": "routing_table",
    // "sequential" - Go through the rules at this level one by one, and pick
    // the first that returns a positive weight.
    "member_order": "sequential",
    "members": [
      {
        "id": "reject-node",
        "host_id": "offload-host",
        // Send filtered-out clients to an offload host.
        "weight_function": "return session_groups.not_sweden and 1 or 0"
      },
      {
        "id": "internal_routing",
        // Return 1 to make sure this tree is evaluated at all.
        "weight_function": "return 1"
        // "weighted" - Evaluate all the rules at this level, and pick one of
        // them randomly based on their respective weights.
        //
        // In this example host 1 has half the capacity of host 2, so we just
        // randomly route twice as many clients to host 2.
        "member_order": "weighted",
        "members": [
          {
            "id": "internal-node-1",
            "host_id": "internal-host-1",
            // Half the weight of the other host -> half as likely to be picked.
            "weight_function": "return 1"
          },
          {
            "id": "internal-node-2",
            "host_id": "internal-host-2",
            // Twice the weight of the other -> twice as likely to be picked.
            "weight_function": "return 2"
          }
        ]
      },
      {
        // Add a non-redirecting offload host as the last target in case all the
        // internal redirecting hosts are unavailable or respond with some kind
        // of error code.
        "id": "offload-node",
        "host_id": "offload-host",
        "weight_function": "return 1"
      }
    ]
  }
}

9 - How to use ESB3024 Router with CoreDNS

How to use ESB3024 Router with CoreDNS

CoreDNS can be configured to work as a DNS server in front of ESB3024 Router. In this mode of operation the client DNS request is terminated by CoreDNS which requests an optimal streaming location from the router.

Contact Edgeware for detailed information on how to set this up, if you don’t already have an Edgeware Support account please contact us here.

10 - How to use the Director as a replacement for Edgeware CDN Request Router

How to use the Director as a replacement for ESB3008 Request Router

This guide outlines how to replicate Convoy’s ESB3008 HTTP Request Router using the Director. This is accomplished using the Convoy Bridge, which is an integration service designed to allow the router and an existing Convoy installation to work together seamlessly.

Following the steps outlined below enables the replacement of existing ESB3008 HTTP Request Router instances with the Director. As of the current documentation, the Director is not a direct substitute for the ESB3008 HTTP Request Router. However, most of the functionality is available, and in the majority of cases, the Director can be used without any loss of functionality.

The purpose of this guide is to detail the process of transitioning from an existing Convoy installation, which includes one or more instances of ESB3008 HTTP Request Router, to a configuration utilizing the Director. This transition allows the existing Convoy installation to continue its role in CDN provisioning, content management, and analytics. Simultaneously, the router assumes responsibility for managing HTTP(S) redirects directly from clients to selected streamers.

One functional difference between ESB3008 HTTP Request Router and the Director is in communication with the streamer to determine specific interface assignments. ESB3008 uses a proprietary protocol to communicate with the streamer to help determine the best interface for the client. For the Director, in order to work with both streamers and third-party CDNs, the use of the proprietary protocol is not supported, and the router’s rule engine must be used to accomplish the same result. It is for this reason that it is recommended to configure each streaming interface as a distinct host entry in the router’s configuration. This allows the router to address each interface separately.

Prerequisites

This guide assumes both an existing Convoy installation and a partiality configured instance of the Director. The router should have all the necessary routing configuration in place for hostGroups, sessionGroups and routing rules to direct traffic to the streaming interfaces of the streamers. It is also assumed that the proper firewall rules are allowing the required traffic between the router, the Convoy Bridge integration and the Convoy management nodes.

The minimum compatible version of Convoy which can be used with the analytics synchronization feature is Convoy 3.0.0. Previous versions may be used only if Convoy is not being used for analytics.

Firewall Configuration

Proper firewall configuration is required to allow both the router and Convoy to communicate through bidirectional connections established from the Convoy Bridge integration. The Convoy Bridge does not need to be enabled for each router instance, one instance of the Convoy Bridge can synchronize multiple instances of the router simultaneously. Only the router nodes with the Convoy Bridge enabled will need access through the firewall to establish outgoing connections to the Convoy management nodes, or whichever nodes are running MariaDB and Kafka. Additionally, if multiple router instances are to be synchronized by a single Convoy Bridge instance, the firewall must allow the Convoy Bridge to establish connections to each router instance on port 5001.

The following table outlines the necessary connections to be allowed through the firewall.

SourceDestinationPortDescription
Convoy BridgeConvoy Management3306/TCPAccount Configuration
Convoy BridgeConvoy Management9092/TCPAnalytics Data
Convoy BridgeThe router5001/TCPRest API

Response Translation Functions

In order to have the router generate a redirect URL which is compatible with the format generated by the ESB3008 HTTP Request Router, a response translation function must be used to modify the redirect URL before it is returned to the client. A built-in function convoy_compatibility_response is available to be used for this purpose. This function appends both the session-id and storage-prefix based on the account and distribution information in Convoy.

On the Director, set the response translation function as follows:

> confcli services.routing.translationFunctions.response "return convoy_compatibility_response(Headers, '')"

This function depends on the account synchronization feature of the Convoy bridge to determine the correct storage prefix based on the incoming Host header value. It works by using the Host header to lookup the storage prefix from the account configuration, and then modifying the redirect URL to prepend /session/<session-id>/<prefix>. The streamer can then use this information for both origin selection and session tracking.

Enabling the Convoy Bridge

Before using the Convoy Bridge integration, a valid configuration must first be applied. All configuration for the Convoy Bridge is available in confd under the key integration.convoy.bridge. The configuration is divided into three main sections: The account synchronization feature, the convoy analytics integration, and a list of additional router instances to synchronize.

By default, the Convoy Bridge will include the current local router instance in the synchronization process. The connection details are hard-wired into the container at startup, and do not need to be specified in the configuration. Because one Convoy Bridge can serve multiple instances of the router, it is not necessary to configure the Convoy Bridge on each node separately. However, if multiple Convoy Bridge instances are used, it is recommended to configure them so that one router is served by multiple instances of the Convoy Bridge. This provides high-availability in a production environment.

As an example, if there are 4 router instances, A, B, C, and D, and 4 Convoy Bridge instances, configuring them such that Bridge 1 synchronizes A & B, 2, synchronizes B & C, 3, synchronizes C & D and 4 synchronizes D & A will provide fault tolerance in the event that one bridge is unavailable.

The account synchronization feature of the Convoy Bridge watches the MariaDB database on the Convoy management node for changes to the account and distribution configuration, and if the configuration differs from what is currently known to the router the configuration will be updated. This feature is required by both the router for use with the convoy_compatibility_response response translation function, and within the Convoy Bridge integration for setting the correct account, storage, and distribution fields with the analytics integration feature.

The analytics integration feature monitors monitors the router’s Event API for newly established sessions, and produces session-record messages over the Kafka message broker for use by Convoy. This feature requires that Convoy is both licensed and configured for use with the analytics feature. The integration works by establishing a persistent connection to each router, and listening for new session events. Due to the protocol in use by the Event API to send the events, only one instance of the Convoy Bridge will have the ability to receive each event, and only that instance will produce the session-record to the Kafka message brokers.

Synchrnoizing multiple routers with the same instance of the Convoy Bridge is performed by configuring the otherRouters section of the Convoy Bridge configuration. In addition to the local router, each otherRouter entry will have the same account and distribution information updated simultaneously, and will be used as an event source for the analytics integration. The otherRouters entries require the URL to the router’s Rest API, an optional API key, and a flag to indicate if SSL certificate validation should be enforced.

Enable the account synchronization feature by running the following command:

> confcli integration.convoy.bridge.accounts -w
Running wizard for resource 'accounts'

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

accounts : {
  enabled (default: False): true
  dbUrl (default: mysql://user:pass@localhost:3306): mysql://convoy:password@convoy-mgmt1:3306
  cmsUrl (default: http://localhost:5555): http://convoy-mgmt1:5555
  pollInterval (default: 60):
}
Generated config:
{
  "accounts": {
    "enabled": true,
    "dbUrl": "mysql://convoy:password@convoy-mgmt1:3306",
    "cmsUrl": "http://convoy-mgmt1:5555",
    "pollInterval": 60
  }
}
Merge and apply the config? [y/n]:

This will result in the Convoy Bridge continuously polling the Convoy instance running on convoy-mgmt1 every 60 seconds for changes to the account configuration. Only when the account configuration differs from the current configuration on the router will it be updated with the change.

The pollInterval parameter can be used to control how frequently the Convoy Bridge will attempt to poll the database for changes. Setting this value too high will result in a longer delay between changes being made in Convoy and the changes being reflected in the router. Setting this value too low will result in unnecessary load on the Convoy database. The default value of 60 seconds should be sufficient for most use cases.

To enable the analytics integration feature, run the following command:

> confcli integration.convoy.bridge.analytics -w
Running wizard for resource 'analytics'

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

analytics : {
  enabled (default: False): true
  brokers : [
    broker (default: ): convoy-mgmt1:9092
    Add another 'broker' element to array 'brokers'? [y/N]: y
    broker (default: ): convoy-mgmt2:9092
    Add another 'broker' element to array 'brokers'? [y/N]: y
    broker (default: ): convoy-mgmt3:9092
    Add another 'broker' element to array 'brokers'? [y/N]: n
  ]
  batchInterval (default: 10):
  maxBatchSize (default: 500):
}
Generated config:
{
  "analytics": {
    "enabled": true,
    "brokers": [
      "convoy-mgmt1:9092",
      "convoy-mgmt2:9092",
      "convoy-mgmt3:9092"
    ],
    "batchInterval": 10,
    "maxBatchSize": 500
  }
}
Merge and apply the config? [y/n]:

This will enable the analytics integration, which will produce session-record messages to the configured Kafka brokers. The brokers configuration list is used to bootstrap the initial set of brokers which will be contacted by the Convoy Bridge to then obtain the current set of active brokers. It is not necessary to include all brokers here, as the initial connection will automatically negotiate the correct broker list.

The batchInterval parameter must be set to a value much less than 60 seconds, since the streamer will send session-sample messages to Kafka every 60 seconds, and if the corresponding session-record message is not received within that time, the sample will be dropped, resulting in potentially inaccurate bandwidth statistics.

> confcli integration.convoy.bridge.otherRouters -w
Running wizard for resource 'otherRouters'
<Other routers which will be kept in sync (default: [])>

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

otherRouters <Other routers which will be kept in sync (default: [])>: [
  otherRouter : {
    url (default: ): https://router-2:5001
    apiKey (default: ): 1234
    validateCerts (default: True): false
  }
  Add another 'otherRouter' element to array 'otherRouters'? [y/N]: y
  otherRouter : {
    url (default: ): https://router-3:5001
    apiKey (default: ):
    validateCerts (default: True):
  }
  Add another 'otherRouter' element to array 'otherRouters'? [y/N]: n
]
Generated config:
{
  "otherRouters": [
    {
      "url": "https://router-2:5001",
      "apiKey": "1234",
      "validateCerts": false
    },
    {
      "url": "https://router-3:5001",
      "apiKey": "",
      "validateCerts": true
    }
  ]
}
Merge and apply the config? [y/n]:

11 - Use In-Stream Sessions

How to configure ACD Router to use In-Stream sessions

In-Stream sessions is a routing mode where the router keeps control of the session for the full session lifetime instead of only routing the session to the preferred CDN (or cache node) once. Technically it works by letting the client return to the router for manifest and segments and redirect each segment to the preferred CDN. This enables changing CDN in the middle of a session.

Configure In-Stream Sessions

In-Stream routing for all incoming requests can be enabled by running

$ confcli services.routing.translationFunctions.session "return set_session_type('instream')"
services.routing.translationFunctions.session = "return set_session_type('instream')"

See Session Translation Function for more details on controlling session types.

The following example illustrate the request flow for in-stream session

$ curl  -i http://test-acd-router/content/playlist.m3u8
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Content-Length: 0
Location: http://test-acd-router/__s/test-acd-router-a4fd86-0000004f_WyIvY2RuL3BsYXlsaXN0Lm0zdTgiLDAsImRldmNkbjEiLCJkZXZjZG4xLnN0cmVhbXBpbG90LnR2Iiw4MCwiL2Nkbi9wbGF5bGlzdC5tM3U4IiwwLDE2ODAyNjQwOTNd_c1e3fc663a47156e0894b30eddf995bb/content/playlist.m3u8
X-Service-Identity:

$ curl -i http://test-acd-router/__s/test-acd-router-a4fd86-0000004f_WyIvY2RuL3BsYXlsaXN0Lm0zdTgiLDAsImRldmNkbjEiLCJkZXZjZG4xLnN0cmVhbXBpbG90LnR2Iiw4MCwiL2Nkbi9wbGF5bGlzdC5tM3U4IiwwLDE2ODAyNjQwOTNd_c1e3fc663a47156e0894b30eddf995bb/content/playlist.m3u8
HTTP/1.1 200 OK
Accept-Ranges: bytes
Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Length,Content-Range
Connection: keep-alive
Content-Length: 306
Content-Type: application/vnd.apple.mpegurl
Date: Fri, 31 Mar 2023 10:18:42 GMT
ETag: "5d711f08-132"
Last-Modified: Thu, 05 Sep 2019 14:43:20 GMT
Server: nginx/1.14.0 (Ubuntu)
X-Service-Identity:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=500000,RESOLUTION=320x180
abr_500k.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=640x360
abr_1000k.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1700000,RESOLUTION=1280x720
abr_1700k.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=3300000,RESOLUTION=1280x720
abr_3300k.m3u8

$ curl -i http://test-acd-router/__s/test-acd-router-a4fd86-0000004f_WyIvY2RuL3BsYXlsaXN0Lm0zdTgiLDAsImRldmNkbjEiLCJkZXZjZG4xLnN0cmVhbXBpbG90LnR2Iiw4MCwiL2Nkbi9wbGF5bGlzdC5tM3U4IiwwLDE2ODAyNjQwOTNd_c1e3fc663a47156e0894b30eddf995bb/content/abr_500k.m3u8
HTTP/1.1 200 OK
Accept-Ranges: bytes
Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Content-Length,Content-Range
Connection: keep-alive
Content-Length: 276
Content-Type: application/vnd.apple.mpegurl
Date: Fri, 31 Mar 2023 12:02:04 GMT
ETag: "6426cbbc-114"
Last-Modified: Fri, 31 Mar 2023 12:02:04 GMT
Server: nginx/1.14.0 (Ubuntu)
X-Service-Identity:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:1
#EXT-X-MEDIA-SEQUENCE:112583846
#EXTINF:0.834444,
abr_500k112583846.ts
#EXTINF:0.834444,
abr_500k112583847.ts
#EXTINF:0.834444,
abr_500k112583848.ts
#EXTINF:0.834444,
abr_500k112583849.ts
#EXTINF:0.834444,
abr_500k112583850.ts

$ curl -i http://test-acd-router/__s/test-acd-router-a4fd86-0000004f_WyIvY2RuL3BsYXlsaXN0Lm0zdTgiLDAsImRldmNkbjEiLCJkZXZjZG4xLnN0cmVhbXBpbG90LnR2Iiw4MCwiL2Nkbi9wbGF5bGlzdC5tM3U4IiwwLDE2ODAyNjQwOTNd_c1e3fc663a47156e0894b30eddf995bb/content/abr_500k112583846.ts
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Content-Length: 0
Location: http://testcdn.tv/abr_500k112583846.ts
X-Service-Identity:

A Note on Confcli

Begin with configuring routing, classification and session groups in confcli, for example based on content types.

# Create a classifier for live sessions

$ 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: contentUrlPath
  Adding a 'contentUrlPath' element
    classifier : {
      name (default: ): live
      type (default: contentUrlPath): ⏎
      inverted (default: False): ⏎
      patternType (default: stringMatch): ⏎
      pattern (default: ): *live*
    }
  Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
]
Generated config:
{
  "classifiers": [
    {
      "name": "live",
      "type": "contentUrlPath",
      "inverted": false,
      "patternType": "stringMatch",
      "pattern": "*live*"
    }
  ]
}
Merge and apply the config? [y/n]: y

# Create a session group called "live" using the classifier

$ 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: ): live
    classifiers : [
      classifier (default: ): live
      Add another 'classifier' element to array 'classifiers'? [y/N]: ⏎
    ]
  }
  Add another 'sessionGroup' element to array 'sessionGroups'? [y/N]: ⏎
]
Generated config:
{
  "sessionGroups": [
    {
      "name": "live",
      "classifiers": [
        "live"
      ]
    }
  ]
}
Merge and apply the config? [y/n]: y

# Make all sessions belonging to the session group "live" In-Stream sessions

$ confcli services.routing.translationFunctions.session "return set_session_type_if_in_group('instream', 'live')"
services.routing.translationFunctions.session = "return set_session_type_if_in_group('instream', 'live')"

Monitoring

If Grafana is installed together with the ESB-3024 installer, the number of started In-Stream sessions as well as currently active In-Stream sessions can be seen in a Dashboard.

Notes on Performance

Be aware that In-Stream sessions puts additional load on the request router since all clients continuously comes back to the router to fetch new manifests and to get segment redirects. The necessary router capacity therefore scaled both with number of started sessions and the average session length.