Skip to content
Snippets Groups Projects
helpers.py 11.7 KiB
Newer Older
from itertools import product
from functools import reduce
from string import ascii_uppercase
from brian_dashboard_manager.templating.render import create_panel, \
    create_panel_target

PANEL_HEIGHT = 12
PANEL_WIDTH = 24

logger = logging.getLogger(__file__)


def get_description(interface):
    return interface.get('description', '').strip()


def is_physical_interface(interface):
    return re.match('^PHY', get_description(interface))


def is_aggregate_interface(interface):
    return re.match('^LAG', get_description(interface))


def is_logical_interface(interface):
    return re.match('^SRV_', get_description(interface))


def is_nren(interface):
    regex = '(PHY|LAG|(SRV_(GLOBAL|LHCONE|MDVPN|IAS|CLS|L3VPN))) CUSTOMER'
    return re.match(regex, get_description(interface))


def is_cls(interface):
    return 'SRV_CLS' in get_description(interface)


def is_cls_peer(interface):
    return 'SRV_CLS PRIVATE' in get_description(interface)


def is_ias_public(interface):
    return 'SRV_IAS PUBLIC' in get_description(interface)


def is_ias_private(interface):
    return 'SRV_IAS PRIVATE' in get_description(interface)


def is_ias_customer(interface):
    return 'SRV_IAS CUSTOMER' in get_description(interface)


def is_ias_upstream(interface):
    return 'SRV_IAS UPSTREAM' in get_description(interface)


def is_ias_peer(interface):
    return is_ias_public(interface) or is_ias_private(interface)


def is_re_peer(interface):
    return 'SRV_GLOBAL RE_INTERCONNECT' in get_description(interface)


def is_re_customer(interface):
    regex = '(PHY|LAG|SRV_GLOBAL) CUSTOMER'
    return re.match(regex, get_description(interface))


def is_gcs(interface):
    return re.match('^SRV_GCS', get_description(interface))


def is_geantopen(interface):
    return 'GEANTOPEN' in get_description(interface)


def is_l2circuit(interface):
    return 'SRV_L2CIRCUIT' in get_description(interface)


def is_lhcone_peer(interface):
    description = get_description(interface)
    return 'LHCONE' in description and 'SRV_L3VPN RE' in description


def is_lhcone_customer(interface):
    description = get_description(interface)
    return 'LHCONE' in description and 'SRV_L3VPN CUSTOMER' in description


def is_lhcone(interface):
    regex = 'SRV_L3VPN (CUSTOMER|RE_INTERCONNECT)'
    return re.match(regex, get_description(interface))


def is_mdvpn(interface):
    return re.match('^SRV_MDVPN CUSTOMER', get_description(interface))


def is_infrastructure_backbone(interface):
    regex = '(SRV_GLOBAL|LAG|PHY) INFRASTRUCTURE BACKBONE'
    return re.match(regex, get_description(interface))


def is_lag_backbone(interface):
    is_lag = 'LAG' in get_description(interface)
    return is_infrastructure_backbone(interface) and is_lag


def is_cae1(interface):
    iface_regex = r'^ae12(\.\d+|$)$'
    is_router = interface.get('router', '').lower() == 'mx1.lon.uk.geant.net'
    is_iface = re.match(iface_regex, interface.get('name'))
    return is_router and is_iface
def parse_backbone_name(description, *args, **kwargs):
    link = description.split('|')[1].strip()
    link = link.replace('( ', '(')
    return link


def is_phy_upstream(interface):
    return re.match('^PHY UPSTREAM', get_description(interface))


def parse_phy_upstream_name(description, host):
    name = description.split(' ')[2].strip().upper()
    location = host.split('.')[1].upper()
    return f'{name} - {location}'


def num_generator(start=1):
    num = start
    while True:
        yield num
        num += 1


def gridPos_generator(id_generator, start=0, agg=False):
    num = start
    while True:
        yield {
            "height": PANEL_HEIGHT,
            "width": PANEL_WIDTH if not agg else PANEL_WIDTH // 2,
            "x": 0,
            "y": num * PANEL_HEIGHT,
            "id": next(id_generator)
        }
        if agg:
            yield {
                "height": PANEL_HEIGHT,
                "width": PANEL_WIDTH // 2,
                "x": PANEL_WIDTH // 2,
                "y": num * PANEL_HEIGHT,
                "id": next(id_generator)
            }
        num += 1


def letter_generator():
    i = 0
    j = 0
    num_letters = len(ascii_uppercase)
    while True:
        result = ascii_uppercase[i % num_letters]

        # tack on an extra letter if we are out of them
        if (i >= num_letters):
            result += ascii_uppercase[j % num_letters]
            j += 1
            if (j != 0 and j % num_letters == 0):
                i += 1
        else:
            i += 1

        yield result


# peer_predicate is a function that is used to filter the interfaces
# parse_func receives interface information and returns a peer name.
def get_interface_data(interfaces, name_parse_func=None):
    result = {}

    if not name_parse_func:
        # Most (but not all) descriptions use a format
        # which has the peer name as the third element.
        def name_parse_func(desc, *args, **kwargs):
            return desc.split(' ')[2].upper()

    for interface in interfaces:

        description = interface.get('description', '').strip()
        interface_name = interface.get('name')
        host = interface.get('router', '')

        dashboard_name = name_parse_func(description, host)

        peer = result.get(dashboard_name, [])

        router = host.replace('.geant.net', '')
        panel_title = f"{router} - {{}} - {interface_name} - {description}"

        peer.append({
            'title': panel_title,
            'interface': interface_name,
            'hostname': host
        })
        result[dashboard_name] = peer
    return result


def get_aggregate_interface_data(interfaces, agg_type):
    result = []

    def reduce_func(prev, curr):
        remotes = prev.get(curr['remote'], [])
        remotes.append(curr)
        all_agg = prev.get('EVERYSINGLEPANEL', [])
        all_agg.append(curr)
        prev[curr['remote']] = remotes
        prev['EVERYSINGLEPANEL'] = all_agg
        return prev

    for interface in interfaces:

        description = interface.get('description', '').strip()
        interface_name = interface.get('name')
        host = interface.get('router', '')

        remote = description.split(' ')[2].upper()

        result.append({
            'type': agg_type,
            'interface': interface_name,
            'hostname': host,
            'remote': remote,
            'alias': f"{host.split('.')[1].upper()} - {remote}"
        })
    return reduce(reduce_func, result, {})


# Helper used for generating stacked aggregate panels
# with multiple target fields (ingress/egress)
def get_aggregate_targets(targets):
    ingress = []
    egress = []

    # used to generate refIds
    letters = letter_generator()

    for target in targets:
        ref_id = next(letters)
        in_data = {
            **target,
            'alias': f"{target['alias']} - Ingress Traffic",
            'refId': ref_id,
            'select_field': 'ingress'
        }
        out_data = {
            **target,
            'alias': f"{target['alias']} - Egress Traffic",
            'refId': ref_id,
            'select_field': 'egress'
        }
        ingress_target = create_panel_target(in_data)
        egress_target = create_panel_target(out_data)
        ingress.append(ingress_target)
        egress.append(egress_target)

    return ingress, egress


# Helper used for generating all traffic/error panels
# with a single target field (ingress/egress or err/s)
def get_panel_fields(panel, panel_type, datasource):
    letters = letter_generator()

    def get_target_data(alias, field):
        return {
            **panel,  # panel has target hostname and interface
            'alias': alias,
            'refId': next(letters),
            'select_field': field,
            'percentile': 'percentile' in alias.lower(),
        }

    error_fields = [('Ingress Errors', 'errorsIn'),
                    ('Egress Errors', 'errorsOut')]

    ingress = ['Ingress Traffic', 'Ingress 95th Percentile']
    egress = ['Egress Traffic', 'Egress 95th Percentile']

    is_v6 = panel_type == 'IPv6'
    is_error = panel_type == 'errors'
    in_field = 'ingressv6' if is_v6 else 'ingress'
    out_field = 'egressv6' if is_v6 else 'egress'

    fields = [*product(ingress, [in_field]), *product(egress, [out_field])]

    targets = error_fields if is_error else fields

    return create_panel({
        **panel,
        'datasource': datasource,
        'title': panel['title'].format(panel_type),
        'panel_targets': [get_target_data(*target) for target in targets],
        'y_axis_type': 'errors' if is_error else 'bits',
    })


def get_dashboard_data(data, datasource, tag, errors=False):
    id_gen = num_generator()
    gridPos = gridPos_generator(id_gen)

    def get_panel_definitions(panels, datasource):
        result = []
        for panel in panels:
            result.append(get_panel_fields(
                {**panel, **next(gridPos)}, 'traffic', datasource))
            result.append(get_panel_fields(
                {**panel, **next(gridPos)}, 'IPv6', datasource))
            if errors:
                result.append(get_panel_fields(
                    {**panel, **next(gridPos)}, 'errors', datasource))
        return result
    for peer, panels in data.items():
            'title': peer,
            'datasource': datasource,
            'panels': get_panel_definitions(panels, datasource),
        if isinstance(tag, list):
            result['tags'] = tag
        else:
            result['tag'] = tag

        yield result


def create_aggregate_panel(title, gridpos, targets, datasource):

    ingress_targets, egress_targets = get_aggregate_targets(targets)
    result = []

    ingress_pos = next(gridpos)
    egress_pos = next(gridpos)

    is_total = 'totals' in title.lower()

    def reduce_alias(prev, curr):
        d = json.loads(curr)
        alias = d['alias']
        if 'egress' in alias.lower():
            prev[alias] = '#0000FF'
        else:
            prev[alias] = '#00FF00'
        return prev

    ingress_colors = reduce(reduce_alias, ingress_targets, {})
    egress_colors = reduce(reduce_alias, egress_targets, {})

    result.append(create_panel({
        **ingress_pos,
        'stack': True,
        'linewidth': 0 if is_total else 1,
        'datasource': datasource,
        'title': title + ' - ingress',
        'targets': ingress_targets,
        'y_axis_type': 'bits',
        'alias_colors': json.dumps(ingress_colors) if is_total else {}
    }))

    result.append(create_panel({
        **egress_pos,
        'stack': True,
        'linewidth': 0 if is_total else 1,
        'datasource': datasource,
        'title': title + ' - egress',
        'targets': egress_targets,
        'y_axis_type': 'bits',
        'alias_colors': json.dumps(egress_colors) if is_total else {}
    }))

    return result


def get_aggregate_dashboard_data(title, targets, datasource, tag):
    id_gen = num_generator()
    gridPos = gridPos_generator(id_gen, agg=True)

    panels = []
    all_targets = targets.get('EVERYSINGLEPANEL', [])

    ingress, egress = create_aggregate_panel(
        title, gridPos, all_targets, datasource)
    panels.extend([ingress, egress])

    totals_title = title + ' - Totals'
    t_in, t_eg = create_aggregate_panel(
        totals_title, gridPos, all_targets, datasource)
    panels.extend([t_in, t_eg])

    if 'EVERYSINGLEPANEL' in targets:
        del targets['EVERYSINGLEPANEL']

    for target in targets:
        _in, _out = create_aggregate_panel(
            title + f' - {target}', gridPos, targets[target], datasource)
        panels.extend([_in, _out])

        'title': title,
        'datasource': datasource,
        'panels': panels,
    if isinstance(tag, list):
        result['tags'] = tag
    else:
        result['tag'] = tag

    return result