diff --git a/brian_dashboard_manager/templating/helpers.py b/brian_dashboard_manager/templating/helpers.py index 46e141c5eb2282444c7458f45bdfc231f051945c..9f9adf29cb3d2094dcaa2943b5e72bf729a2d5c7 100644 --- a/brian_dashboard_manager/templating/helpers.py +++ b/brian_dashboard_manager/templating/helpers.py @@ -1,8 +1,11 @@ import re import logging +import json from itertools import product +from functools import reduce from string import ascii_uppercase -from brian_dashboard_manager.templating.render import create_panel +from brian_dashboard_manager.templating.render import create_panel, \ + create_panel_target PANEL_HEIGHT = 12 PANEL_WIDTH = 24 @@ -35,6 +38,10 @@ 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) @@ -51,6 +58,10 @@ 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) @@ -82,6 +93,11 @@ def is_lhcone_customer(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)) @@ -96,6 +112,10 @@ def is_lag_backbone(interface): return is_infrastructure_backbone(interface) and is_lag +def is_cae1(interface): + return interface.get('router', '').lower() == 'mx1.lon.uk.geant.net' + + def parse_backbone_name(description, *args, **kwargs): link = description.split('|')[1].strip() link = link.replace('( ', '(') @@ -119,16 +139,24 @@ def num_generator(start=1): num += 1 -def gridPos_generator(id_generator, start=0): +def gridPos_generator(id_generator, start=0, agg=False): num = start while True: yield { "height": PANEL_HEIGHT, - "width": PANEL_WIDTH, + "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 @@ -155,12 +183,14 @@ def letter_generator(): # 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: - 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() description = interface.get('description', '').strip() interface_name = interface.get('name') @@ -182,6 +212,67 @@ def get_interface_data(interfaces, name_parse_func=None): 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): @@ -214,6 +305,7 @@ def get_panel_fields(panel, panel_type, datasource): return create_panel({ **panel, 'datasource': datasource, + 'linewidth': 1, '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', @@ -244,3 +336,82 @@ def get_dashboard_data(data, datasource, tag, errors=False): 'panels': get_panel_definitions(panels, datasource), 'tag': tag } + + +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]) + + return { + 'title': title, + 'datasource': datasource, + 'panels': panels, + 'tag': tag + }