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, \ 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, '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', }) 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(): result = { '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]) result = { 'title': title, 'datasource': datasource, 'panels': panels, } if isinstance(tag, list): result['tags'] = tag else: result['tag'] = tag return result