diff --git a/brian_dashboard_manager/grafana/provision.py b/brian_dashboard_manager/grafana/provision.py index f04d851b4dbb9016cf3fae1e4c9db1793725f385..8a70538d6c125e2601169b6583cb2dde996faea6 100644 --- a/brian_dashboard_manager/grafana/provision.py +++ b/brian_dashboard_manager/grafana/provision.py @@ -11,9 +11,9 @@ from functools import reduce from concurrent.futures import Future from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor from brian_dashboard_manager.config import DEFAULT_ORGANIZATIONS, STATE_PATH -from brian_dashboard_manager.grafana.utils.request import \ - AdminRequest, \ +from brian_dashboard_manager.grafana.utils.request import AdminRequest, \ TokenRequest + from brian_dashboard_manager.grafana.organization import \ get_organizations, create_organization, create_api_token, \ delete_api_token, delete_expired_api_tokens, set_home_dashboard @@ -25,15 +25,11 @@ from brian_dashboard_manager.grafana.folder import find_folder, \ delete_folder from brian_dashboard_manager.inventory_provider.interfaces import \ get_gws_direct, get_gws_indirect, get_interfaces -from brian_dashboard_manager.templating.nren_access import generate_nrens -from brian_dashboard_manager.templating.helpers import is_re_customer, \ - is_cls_peer, is_cls, is_ias_customer, is_ias_private, is_ias_public, \ - is_ias_upstream, is_ias_peer, is_lag_backbone, is_nren, is_phy_upstream, \ - is_re_peer, is_gcs, is_cae1, is_geantopen, is_l2circuit, is_lhcone_peer, \ - is_lhcone_customer, is_lhcone, is_mdvpn, get_aggregate_dashboard_data, \ - get_interface_data, parse_backbone_name, parse_phy_upstream_name, \ - get_dashboard_data, get_aggregate_interface_data +from brian_dashboard_manager.templating.helpers import \ + get_aggregate_dashboard_data, get_interface_data, \ + get_nren_interface_data, get_dashboard_data, \ + get_nren_dashboard_data, get_aggregate_interface_data from brian_dashboard_manager.templating.gws import generate_gws, \ generate_indirect @@ -43,46 +39,37 @@ from brian_dashboard_manager.templating.render import render_dashboard logger = logging.getLogger(__name__) -def generate_all_nrens(token_request, nrens, folder_id, datasource_name): - provisioned = [] - with ThreadPoolExecutor(max_workers=8) as executor: - for dashboard in generate_nrens(nrens, datasource_name): - res = executor.submit(create_dashboard, token_request, - dashboard, folder_id) - provisioned.append(res) - return [r.result() for r in provisioned] - - -def provision_folder(token_request, folder_name, - dash, excluded_interfaces, datasource_name, - excluded_dashboards): +def provision_folder(token_request, folder_name, dash, + ds_name, excluded_dashboards): + """ + Function to provision dashboards within a folder. + """ folder = find_folder(token_request, folder_name) - - predicate = dash['predicate'] tag = dash['tag'] + interfaces = dash['interfaces'] - # dashboard will include error panel + # dashboard should include error panels errors = dash.get('errors', False) - # custom parsing function for description to dashboard name - parse_func = dash.get('parse_func') - - relevant_interfaces = filter(predicate, excluded_interfaces) - data = get_interface_data(relevant_interfaces, parse_func) - dash_data = get_dashboard_data(data, datasource_name, tag, errors) + is_nren = folder_name == 'NREN Access' + if is_nren: + data = get_nren_interface_data(interfaces) + dash_data = get_nren_dashboard_data(data, ds_name, tag) + else: + data = get_interface_data(interfaces) + dash_data = get_dashboard_data(data, ds_name, tag, errors) if not isinstance(excluded_dashboards, list): excluded_dashboards = [] else: - excluded_dashboards = list( - map(lambda s: s.lower(), excluded_dashboards)) + excluded_dashboards = [s.lower() for s in excluded_dashboards] provisioned = [] with ThreadPoolExecutor(max_workers=4) as executor: for dashboard in dash_data: - rendered = render_dashboard(dashboard) + rendered = render_dashboard(dashboard, nren=is_nren) if rendered.get('title').lower() in excluded_dashboards: executor.submit(delete_dashboard, token_request, rendered, folder['id']) @@ -92,19 +79,19 @@ def provision_folder(token_request, folder_name, return [r.result() for r in provisioned] -def provision_aggregate(token_request, agg_type, aggregate_folder, - dash, excluded_interfaces, datasource_name): - predicate = dash['predicate'] - tag = dash['tag'] +def provision_aggregate(token_request, folder, + dash, ds_name): - relevant_interfaces = filter(predicate, excluded_interfaces) - data = get_aggregate_interface_data(relevant_interfaces, agg_type) + name = dash['dashboard_name'] + tag = dash['tag'] + interfaces = dash['interfaces'] + data = get_aggregate_interface_data(interfaces, name) dashboard = get_aggregate_dashboard_data( - f'Aggregate - {agg_type}', data, datasource_name, tag) + f'Aggregate - {name}', data, ds_name, tag) rendered = render_dashboard(dashboard) - return create_dashboard(token_request, rendered, aggregate_folder['id']) + return create_dashboard(token_request, rendered, folder['id']) def provision_maybe(config): @@ -182,86 +169,130 @@ def provision(config): logger.info( f'Provisioned datasource: {datasource["name"]}') - excluded_nrens = org_config.get('excluded_nrens', []) - excluded_nrens = list(map(lambda f: f.lower(), excluded_nrens)) + excluded_nrens = org_config['excluded_nrens'] def excluded(interface): - desc = interface.get('description', '').lower() - lab = 'lab.office' in interface.get('router', '').lower() - excluded_desc = any( - nren.lower() in desc for nren in excluded_nrens) - return not (excluded_desc or lab) + desc = interface['description'].lower() + lab = 'lab.office' in interface['router'].lower() + to_exclude = any(nren.lower() in desc for nren in excluded_nrens) + return not (to_exclude or lab) - excluded_interfaces = list(filter(excluded, interfaces)) + relevant_interfaces = list(filter(excluded, interfaces)) dashboards = { + 'NREN': { + 'tag': ['customers'], + 'folder_name': 'NREN Access', + 'interfaces': [] + }, 'CLS': { - 'predicate': is_cls, - 'tag': 'CLS' + 'tag': 'CLS', + 'folder_name': 'CLS', + 'interfaces': [] }, - 'RE PEER': { - 'predicate': is_re_peer, - 'tag': 'RE_PEER' + 'RE_PEER': { + 'tag': 'RE_PEER', + 'folder_name': 'RE Peer', + 'interfaces': [] }, - 'RE CUST': { - 'predicate': is_re_customer, - 'tag': 'RE_CUST' + 'RE_CUST': { + 'tag': 'RE_CUST', + 'folder_name': 'RE Customer', + 'interfaces': [] }, 'GEANTOPEN': { - 'predicate': is_geantopen, - 'tag': 'GEANTOPEN' + 'tag': 'GEANTOPEN', + 'folder_name': 'GEANTOPEN', + 'interfaces': [] }, 'GCS': { - 'predicate': is_gcs, - 'tag': 'AUTOMATED_L2_CIRCUITS' + 'tag': 'AUTOMATED_L2_CIRCUITS', + 'folder_name': 'GCS', + 'interfaces': [] }, - 'L2 CIRCUIT': { - 'predicate': is_l2circuit, - 'tag': 'L2_CIRCUITS' + 'L2_CIRCUIT': { + 'tag': 'L2_CIRCUITS', + 'folder_name': 'L2 Circuit', + 'interfaces': [] }, - 'LHCONE PEER': { - 'predicate': is_lhcone_peer, - 'tag': 'LHCONE_PEER' + 'LHCONE_PEER': { + 'tag': 'LHCONE_PEER', + 'folder_name': 'LHCONE Peer', + 'interfaces': [] }, - 'LHCONE CUST': { - 'predicate': is_lhcone_customer, - 'tag': 'LHCONE_CUST' + 'LHCONE_CUST': { + 'tag': 'LHCONE_CUST', + 'folder_name': 'LHCONE Customer', + 'interfaces': [] }, - 'MDVPN Customers': { - 'predicate': is_mdvpn, - 'tag': 'MDVPN' + 'MDVPN_CUSTOMERS': { + 'tag': 'MDVPN', + 'folder_name': 'MDVPN Customers', + 'interfaces': [] }, - 'Infrastructure Backbone': { - 'predicate': is_lag_backbone, + 'INFRASTRUCTURE_BACKBONE': { 'tag': 'BACKBONE', 'errors': True, - 'parse_func': parse_backbone_name + 'folder_name': 'Infrastructure Backbone', + 'interfaces': [] }, - 'IAS PRIVATE': { - 'predicate': is_ias_private, - 'tag': 'IAS_PRIVATE' + 'IAS_PRIVATE': { + 'tag': 'IAS_PRIVATE', + 'folder_name': 'IAS Private', + 'interfaces': [] }, - 'IAS PUBLIC': { - 'predicate': is_ias_public, - 'tag': 'IAS_PUBLIC' + 'IAS_PUBLIC': { + 'tag': 'IAS_PUBLIC', + 'folder_name': 'IAS Public', + 'interfaces': [] }, - 'IAS CUSTOMER': { - 'predicate': is_ias_customer, - 'tag': 'IAS_CUSTOMER' + 'IAS_CUSTOMER': { + 'tag': 'IAS_CUSTOMER', + 'folder_name': 'IAS Customer', + 'interfaces': [] }, - 'IAS UPSTREAM': { - 'predicate': is_ias_upstream, - 'tag': ['IAS_UPSTREAM', 'UPSTREAM'] + 'IAS_UPSTREAM': { + 'tag': ['IAS_UPSTREAM', 'UPSTREAM'], + 'folder_name': 'IAS Upstream', + 'interfaces': [] }, - 'GWS PHY Upstream': { - 'predicate': is_phy_upstream, + 'GWS_PHY_UPSTREAM': { 'tag': ['GWS_UPSTREAM', 'UPSTREAM'], 'errors': True, - 'parse_func': parse_phy_upstream_name + 'folder_name': 'GWS PHY Upstream', + 'interfaces': [] + } + } + + agg_dashboards = { + 'CLS_PEERS': { + 'tag': 'cls_peers', + 'dashboard_name': 'CLS Peers', + 'interfaces': [] + }, + 'IAS_PEERS': { + 'tag': 'ias_peers', + 'dashboard_name': 'IAS Peers', + 'interfaces': [] + }, + 'IAS_UPSTREAM': { + 'tag': 'gws_upstreams', + 'dashboard_name': 'GWS Upstreams', + 'interfaces': [] + }, + 'LHCONE': { + 'tag': 'lhcone', + 'dashboard_name': 'LHCONE', + 'interfaces': [] + }, + 'CAE1': { + 'tag': 'cae', + 'dashboard_name': 'CAE1', + 'interfaces': [] } } # Provision dashboards, overwriting existing ones. - datasource_name = datasource.get('name', 'PollerInfluxDB') + ds_name = datasource.get('name', 'PollerInfluxDB') excluded_folders = org_config.get('excluded_folders', {}) def get_uid(prev, curr): @@ -270,8 +301,8 @@ def provision(config): # Map of dashboard UID -> whether it has been updated. # This is used to remove stale dashboards at the end. - dash_list = find_dashboard(token_request) or [] - dash_list = reduce(get_uid, dash_list, {}) + updated = find_dashboard(token_request) or [] + updated = reduce(get_uid, updated, {}) def update_dash_list(dashboards): for dashboard in dashboards: @@ -279,16 +310,35 @@ def provision(config): dashboard = dashboard.result() if dashboard is None: continue - dash_list[dashboard.get('uid')] = True + updated[dashboard.get('uid')] = True + # loop over interfaces and add them to the dashboard_name + # -> folder mapping structure `dashboards` above, for convenience. + for iface in relevant_interfaces: + for dash_name in iface['dashboards']: + + # add interface to matched dashboard + if dash_name in dashboards: + ifaces = dashboards[dash_name]['interfaces'] + ifaces.append(iface) + + # add to matched aggregate dashboard + if dash_name in agg_dashboards: + ifaces = agg_dashboards[dash_name]['interfaces'] + ifaces.append(iface) + + # provision dashboards and their folders with ProcessPoolExecutor(max_workers=4) as executor: provisioned = [] - for folder_name, dash in dashboards.items(): + for folder in dashboards.values(): + folder_name = folder['folder_name'] exclude = excluded_folders.get(folder_name) + + # boolean True means entire folder excluded + # if list, it is specific dashboard names not to provision + # so is handled at provision time. if exclude: if isinstance(exclude, bool): - # boolean True -> entire folder excluded - # list -> dashboard names not to provision executor.submit( delete_folder, token_request, folder_name) continue @@ -296,8 +346,7 @@ def provision(config): logger.info( f'Provisioning {org["name"]}/{folder_name} dashboards') res = executor.submit(provision_folder, token_request, - folder_name, dash, - excluded_interfaces, datasource_name, + folder_name, folder, ds_name, exclude) provisioned.append(res) @@ -316,11 +365,11 @@ def provision(config): delete_folder(token_request, folder_name) else: folder = find_folder(token_request, folder_name) - with ProcessPoolExecutor(max_workers=4) as executor: + with ThreadPoolExecutor(max_workers=4) as executor: gws_indirect_data = get_gws_indirect( config['inventory_provider']) provisioned = [] - dashes = generate_indirect(gws_indirect_data, datasource_name) + dashes = generate_indirect(gws_indirect_data, ds_name) for dashboard in dashes: rendered = render_dashboard(dashboard) provisioned.append(executor.submit(create_dashboard, @@ -338,11 +387,11 @@ def provision(config): delete_folder(token_request, folder_name) else: folder = find_folder(token_request, folder_name) - with ProcessPoolExecutor(max_workers=4) as executor: + with ThreadPoolExecutor(max_workers=4) as executor: gws_data = get_gws_direct(config['inventory_provider']) provisioned = [] - for dashboard in generate_gws(gws_data, datasource_name): + for dashboard in generate_gws(gws_data, ds_name): rendered = render_dashboard(dashboard) provisioned.append(executor.submit(create_dashboard, token_request, @@ -350,29 +399,6 @@ def provision(config): update_dash_list(provisioned) - aggregate_dashboards = { - 'CLS PEERS': { - 'predicate': is_cls_peer, - 'tag': 'cls_peers', - }, - 'IAS PEERS': { - 'predicate': is_ias_peer, - 'tag': 'ias_peers', - }, - 'GWS UPSTREAMS': { - 'predicate': is_ias_upstream, - 'tag': 'gws_upstreams', - }, - 'LHCONE': { - 'predicate': is_lhcone, - 'tag': 'lhcone', - }, - 'CAE1': { - 'predicate': is_cae1, - 'tag': 'cae', - } - } - exclude_agg = excluded_folders.get('Aggregates', []) if isinstance(exclude_agg, bool) and exclude_agg: @@ -382,45 +408,31 @@ def provision(config): with ProcessPoolExecutor(max_workers=4) as executor: provisioned = [] agg_folder = find_folder(token_request, 'Aggregates') - for agg_type, dash in aggregate_dashboards.items(): - if agg_type in exclude_agg: - dash_name = {'title': f'Aggregate - {agg_type}'} + for dash in agg_dashboards.values(): + if dash['dashboard_name'] in exclude_agg: + dash_name = { + 'title': f'Aggregate - {dash["dashboard_name"]}'} executor.submit(delete_dashboard, token_request, dash_name, agg_folder['id']) continue logger.info(f'Provisioning {org["name"]}' + - f'/Aggregate {agg_type} dashboards') - res = executor.submit(provision_aggregate, token_request, - agg_type, agg_folder, dash, - excluded_interfaces, datasource_name) + f'/Aggregate {dash["dashboard_name"]} dashboards') # noqa: E501 + res = executor.submit( + provision_aggregate, token_request, + agg_folder, dash, ds_name) provisioned.append(res) update_dash_list(provisioned) - # NREN Access dashboards - # uses a different template than the above. - logger.info('Provisioning NREN Access dashboards') - - folder = find_folder(token_request, 'NREN Access') - - nrens = filter(is_nren, excluded_interfaces) - provisioned = generate_all_nrens( - token_request, nrens, folder['id'], datasource_name) - - for dashboard in provisioned: - if dashboard is None: - continue - dash_list[dashboard.get('uid')] = True - - # Non-generated dashboards + # Statically defined dashboards from json files excluded_dashboards = org_config.get('excluded_dashboards', []) logger.info('Provisioning static dashboards') for dashboard in get_dashboard_definitions(): if dashboard['title'] not in excluded_dashboards: res = create_dashboard(token_request, dashboard) if res: - dash_list[res.get('uid')] = True + updated[res.get('uid')] = True else: delete_dashboard(token_request, dashboard) @@ -430,9 +442,9 @@ def provision(config): is_staff = org['name'] == 'GÉANT Staff' set_home_dashboard(token_request, is_staff) # just hardcode that we updated home dashboard - dash_list['home'] = True + updated['home'] = True - for dash, provisioned in dash_list.items(): + for dash, provisioned in updated.items(): if not provisioned: logger.info(f'Deleting stale dashboard with UID {dash}') delete_dashboard(token_request, {'uid': dash}) diff --git a/brian_dashboard_manager/templating/helpers.py b/brian_dashboard_manager/templating/helpers.py index 5ce022c39c01206662da9017ef85c27f4cb02a77..b7c9e0e87bc89ae28be0f7a7e71a1f30bd40b204 100644 --- a/brian_dashboard_manager/templating/helpers.py +++ b/brian_dashboard_manager/templating/helpers.py @@ -1,16 +1,14 @@ """ -Predicates -and helper functions used to group interfaces together and generate the -necessary data for the dashboard templates. +Helper functions used to group interfaces together and generate the +necessary data to generate the dashboards from templates. """ -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 + create_panel_target, create_dropdown_panel PANEL_HEIGHT = 12 PANEL_WIDTH = 24 @@ -18,128 +16,6 @@ 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: @@ -187,47 +63,114 @@ def letter_generator(): 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): +def get_nren_interface_data(interfaces): + """ + Helper for grouping interfaces into groups of NRENs + Extracts information from interfaces to be used in panels. + NREN dashboards have aggregate panels at the top and + dropdowns for services / physical interfaces. + """ 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', '') + description = interface['description'].strip() + interface_name = interface['name'] + host = interface['router'] + + router = host.replace('.geant.net', '') + panel_title = f"{router} - {{}} - {interface_name} - {description}" + + info = interface['dashboard_info'] + + dashboard_name = info['name'] - dashboard_name = name_parse_func(description, host) + dashboard = result.get(dashboard_name, { + 'AGGREGATES': [], + 'SERVICES': [], + 'PHYSICAL': [] + }) + + location = host.split('.')[1].upper() - peer = result.get(dashboard_name, []) + if info['interface_type'] == 'AGGREGATE': + dashboard['AGGREGATES'].append({ + 'interface': interface_name, + 'hostname': host, + 'alias': f"{location} - {dashboard_name} ({interface_name})" + }) + + # link aggregates are also shown + # under the physical dropdown + dashboard['PHYSICAL'].append({ + 'title': panel_title, + 'hostname': host, + 'interface': interface_name + }) + + elif info['interface_type'] == 'LOGICAL': + dashboard['SERVICES'].append({ + 'title': panel_title, + 'hostname': host, + 'interface': interface_name + }) + elif info['interface_type'] == 'PHYSICAL': + dashboard['PHYSICAL'].append({ + 'title': panel_title, + 'hostname': host, + 'interface': interface_name + }) + + result[dashboard_name] = dashboard + return result + + +def get_interface_data(interfaces): + """ + Helper for grouping interfaces into dashboards. + Extracts information from interfaces to be used in panels. + """ + result = {} + + for interface in interfaces: + + description = interface['description'].strip() + interface_name = interface['name'] + host = interface['router'] router = host.replace('.geant.net', '') panel_title = f"{router} - {{}} - {interface_name} - {description}" - peer.append({ + info = interface['dashboard_info'] + + dashboard_name = info['name'] + + dashboard = result.get(dashboard_name, []) + dashboard.append({ 'title': panel_title, 'interface': interface_name, 'hostname': host, 'has_v6': len(interface.get('ipv6', [])) > 0 }) - result[dashboard_name] = peer + + result[dashboard_name] = dashboard return result def get_aggregate_interface_data(interfaces, agg_type): + """ + Helper for grouping interfaces into groups of remotes + (ISP/NREN/...) used for aggregate dashboards + Extracts information from interfaces to be used in panels. + + Aggregate dashboards have aggregates at the top for all remotes + as well as aggregate panels for specific remotes. + This builds a dict with interfaces for each remote + and one with all interfaces. + """ + result = [] - # Aggregate dashboards have aggregates at the top for all remotes - # as well as aggregate panels for specific remotes. - # This builds a dict with interfaces for each remote - # and one with all interfaces. def reduce_func(prev, curr): remotes = prev.get(curr['remote'], []) remotes.append(curr) @@ -239,11 +182,10 @@ def get_aggregate_interface_data(interfaces, agg_type): for interface in interfaces: - description = interface.get('description', '').strip() interface_name = interface.get('name') host = interface.get('router', '') - remote = description.split(' ')[2].upper() + remote = interface['dashboard_info']['name'] location = host.split('.')[1].upper() result.append({ @@ -256,9 +198,11 @@ def get_aggregate_interface_data(interfaces, agg_type): return reduce(reduce_func, result, {}) -# Helper used for generating stacked aggregate panels -# with multiple target fields (ingress/egress) def get_aggregate_targets(targets): + """ + Helper used for generating panel fields for aggregate panels + with multiple target fields (ingress/egress) + """ ingress = [] egress = [] @@ -287,9 +231,11 @@ def get_aggregate_targets(targets): 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): + """ + Helper for generating a single panel, + with ingress/egress and percentile targets + """ letters = letter_generator() def get_target_data(alias, field): @@ -329,28 +275,102 @@ def get_panel_fields(panel, panel_type, datasource): }) -def get_dashboard_data(data, datasource, tag, errors=False): - id_gen = num_generator() - gridPos = gridPos_generator(id_gen) +def panel_generator(gridPos): + """ + Shared wrapper for shorter calls without + gridPos to generate panels. - def get_panel_definitions(panels, datasource): + Generates panels used in a normal dashboard + for all traffic + (conditionally) IPv6 + Errors + """ + def get_panel_definitions(panels, datasource, errors=False): result = [] + for panel in panels: - result.append(get_panel_fields( - {**panel, **next(gridPos)}, 'traffic', datasource)) + result.append(get_panel_fields({ + **panel, + **next(gridPos) + }, 'traffic', datasource)) if panel.get('has_v6', False): - result.append(get_panel_fields( - {**panel, **next(gridPos)}, 'IPv6', datasource)) + result.append(get_panel_fields({ + **panel, + **next(gridPos) + }, 'IPv6', datasource)) if errors: - result.append(get_panel_fields( - {**panel, **next(gridPos)}, 'errors', datasource)) + result.append(get_panel_fields({ + **panel, + **next(gridPos) + }, 'errors', datasource)) + return result + return get_panel_definitions + + +def get_nren_dashboard_data(data, datasource, tag): + """ + Generates all panels used in a NREN dashboard, + including dropdowns and aggregate panels. + """ + + for nren, dash in data.items(): + id_gen = num_generator() + + gridPos = gridPos_generator(id_gen, start=1) + + panel_gen = panel_generator(gridPos) + + if len(dash['AGGREGATES']) > 0: + agg_panels = create_aggregate_panel( + f'Aggregate - {nren}', + gridPos_generator(id_gen, agg=True), + dash['AGGREGATES'], datasource) + else: + # if there's no aggregate panel(s), start other stuff at y=0. + gridPos = gridPos_generator(id_gen, start=0) + agg_panels = [] + + services_dropdown = create_dropdown_panel('Services', **next(gridPos)) + service_panels = panel_gen(dash['SERVICES'], datasource) + iface_dropdown = create_dropdown_panel('Interfaces', **next(gridPos)) + phys_panels = panel_gen(dash['PHYSICAL'], datasource, True) + + result = { + 'nren_name': nren, + 'datasource': datasource, + 'aggregate_panels': agg_panels, + 'dropdown_groups': [ + { + 'dropdown': services_dropdown, + 'panels': service_panels, + }, + { + 'dropdown': iface_dropdown, + 'panels': phys_panels, + } + ] + } + if isinstance(tag, list): + result['tags'] = tag + else: + result['tag'] = tag + + yield result + + +def get_dashboard_data(data, datasource, tag, errors=False): + """ + Generates all panels used in a normal dashboard without aggregate panels + """ + id_gen = num_generator() + gridPos = gridPos_generator(id_gen) + panel_gen = panel_generator(gridPos) + for dashboard_name, panels in data.items(): result = { 'title': dashboard_name, 'datasource': datasource, - 'panels': get_panel_definitions(panels, datasource), + 'panels': list(panel_gen(panels, datasource, errors)), } if isinstance(tag, list): @@ -362,6 +382,10 @@ def get_dashboard_data(data, datasource, tag, errors=False): def create_aggregate_panel(title, gridpos, targets, datasource): + """ + Generates a single panel with multiple targets. + Each target is one interface / line on the graph + """ ingress_targets, egress_targets = get_aggregate_targets(targets) result = [] @@ -409,6 +433,19 @@ def create_aggregate_panel(title, gridpos, targets, datasource): def get_aggregate_dashboard_data(title, targets, datasource, tag): + """ + Creates three types of aggregate panels: + Aggregate Ingress/Egress that contain + every target (interface) given as parameter + + Totals Ingress/Egress which is the same as above, + but with a different line color. + + Aggregates for each remote + (all interfaces for each remote (ISP/NREN/...) + on separate graphs + """ + id_gen = num_generator() gridPos = gridPos_generator(id_gen, agg=True) diff --git a/brian_dashboard_manager/templating/nren_access.py b/brian_dashboard_manager/templating/nren_access.py deleted file mode 100644 index 67ba24cdacf17ef72b715225dc50bd35913dec74..0000000000000000000000000000000000000000 --- a/brian_dashboard_manager/templating/nren_access.py +++ /dev/null @@ -1,137 +0,0 @@ -import json -import os -import jinja2 -from concurrent.futures import ProcessPoolExecutor -from brian_dashboard_manager.templating.render import create_dropdown_panel -from brian_dashboard_manager.templating.helpers import \ - is_aggregate_interface, is_logical_interface, is_physical_interface, \ - num_generator, gridPos_generator, get_panel_fields, create_aggregate_panel - - -def get_nrens(interfaces): - result = {} - for interface in interfaces: - - description = interface.get('description', '').strip() - - nren_name = description.split(' ')[2].upper() - - nren = result.get( - nren_name, {'AGGREGATES': [], 'SERVICES': [], 'PHYSICAL': []}) - - interface_name = interface.get('name') - host = interface.get('router', '') - router = host.replace('.geant.net', '') - panel_title = f"{router} - {{}} - {interface_name} - {description}" - - location = host.split('.')[1].upper() - if is_aggregate_interface(interface): - nren['AGGREGATES'].append({ - 'interface': interface_name, - 'hostname': host, - 'alias': f"{location} - {nren_name} ({interface_name})" - }) - - # link aggregates are also shown - # under the physical dropdown - nren['PHYSICAL'].append({ - 'title': panel_title, - 'hostname': host, - 'interface': interface_name - }) - - elif is_logical_interface(interface): - nren['SERVICES'].append({ - 'title': panel_title, - 'hostname': host, - 'interface': interface_name - }) - elif is_physical_interface(interface): - nren['PHYSICAL'].append({ - 'title': panel_title, - 'hostname': host, - 'interface': interface_name - }) - - result[nren_name] = nren - return result - - -id_gen = num_generator(start=1) - -# aggregate panels have y=0, start generating at 1*height -gridPos = gridPos_generator(id_gen, start=1) - - -def get_panel_definitions(panels, datasource, errors=False): - result = [] - for panel in panels: - result.append(get_panel_fields( - {**panel, **next(gridPos)}, 'traffic', datasource)) - if panel.get('has_v6', False): - result.append(get_panel_fields( - {**panel, **next(gridPos)}, 'IPv6', datasource)) - if errors: - result.append(get_panel_fields( - {**panel, **next(gridPos)}, 'errors', datasource)) - return result - - -def build_data(nren, data, datasource): - global gridPos - if len(data['AGGREGATES']) > 0: - agg_panels = create_aggregate_panel( - f'Aggregate - {nren}', - gridPos_generator(id_gen, agg=True), - data['AGGREGATES'], datasource) - else: - # if there's no aggregate panel(s), start other stuff at y=0. - gridPos = gridPos_generator(id_gen, start=0) - agg_panels = [] - services_dropdown = create_dropdown_panel('Services', **next(gridPos)) - service_panels = get_panel_definitions(data['SERVICES'], datasource) - iface_dropdown = create_dropdown_panel('Interfaces', **next(gridPos)) - phys_panels = get_panel_definitions(data['PHYSICAL'], datasource, True) - - return { - 'nren_name': nren, - 'datasource': datasource, - 'aggregate_panels': agg_panels, - 'dropdown_groups': [ - { - 'dropdown': services_dropdown, - 'panels': service_panels, - }, - { - 'dropdown': iface_dropdown, - 'panels': phys_panels, - } - ] - } - - -def get_dashboard_data(interfaces, datasource): - nren_data = get_nrens(interfaces) - result = [] - with ProcessPoolExecutor(max_workers=4) as executor: - for nren, data in nren_data.items(): - result.append(executor.submit(build_data, nren, data, datasource)) - return [r.result() for r in result] - - -def generate_nrens(interfaces, datasource): - file = os.path.abspath(os.path.join( - os.path.dirname(__file__), - 'templates', - 'nren_access', - 'nren-dashboard.json.j2')) - - with open(file) as f: - template = jinja2.Template(f.read()) - - for dashboard in get_dashboard_data(interfaces, datasource): - rendered = template.render(dashboard) - rendered = json.loads(rendered) - rendered['uid'] = None - rendered['id'] = None - yield rendered diff --git a/test/test_aggregrate.py b/test/test_aggregrate.py index ac0df399fbcda6c2997770980f2df137db06261a..a992dc52600b5486f2ea42c60d01e4976ab7836e 100644 --- a/test/test_aggregrate.py +++ b/test/test_aggregrate.py @@ -1,7 +1,6 @@ from brian_dashboard_manager.grafana.utils.request import TokenRequest import responses -from brian_dashboard_manager.grafana.provision import provision_aggregate, \ - is_cls_peer +from brian_dashboard_manager.grafana.provision import provision_aggregate DEFAULT_REQUEST_HEADERS = { "Content-type": "application/json", @@ -9,125 +8,265 @@ DEFAULT_REQUEST_HEADERS = { } -TEST_INTERFACES = [ - { - "router": "mx1.ath2.gr.geant.net", - "name": "xe-1/0/1", - "bundle": [], - "bundle-parents": [], - "snmp-index": 569, - "description": "PHY RESERVED | New OTEGLOBE ATH2-VIE 10Gb LS", - "circuits": [] - }, - { - "router": "mx1.ath2.gr.geant.net", - "name": "ge-1/3/7", - "bundle": [], - "bundle-parents": [], - "snmp-index": 543, - "description": "PHY SPARE", - "circuits": [] - }, - { - "router": "mx1.ham.de.geant.net", - "name": "xe-2/2/0.13", - "bundle": [], - "bundle-parents": [], - "snmp-index": 721, - "description": "SRV_L2CIRCUIT CUSTOMER WP6T3 WP6T3 #ham_lon2-WP6-GTS_20063 |", # noqa: E501 - "circuits": [ - { - "id": 52382, - "name": "ham_lon2-WP6-GTS_20063_L2c", - "type": "", - "status": "operational" - } - ] - }, - { - "router": "mx1.fra.de.geant.net", - "name": "ae27", - "bundle": [], - "bundle-parents": [ - "xe-10/0/2", - "xe-10/3/2", - "xe-10/3/3" - ], - "snmp-index": 760, - "description": "LAG CUSTOMER ULAKBIM SRF9940983 |", - "circuits": [ - { - "id": 40983, - "name": "ULAKBIM AP2 LAG", - "type": "", - "status": "operational" - } - ] - }, - { - "router": "mx2.zag.hr.geant.net", - "name": "xe-2/1/0", - "bundle": [], - "bundle-parents": [], - "snmp-index": 739, - "description": "PHY SPARE", - "circuits": [] - }, - { - "router": "rt1.rig.lv.geant.net", - "name": "xe-0/1/5", - "bundle": [], - "bundle-parents": [], - "snmp-index": 539, - "description": "PHY SPARE", - "circuits": [] - }, - { - "router": "srx1.ch.office.geant.net", - "name": "ge-0/0/0", - "bundle": [], - "bundle-parents": [], - "snmp-index": 513, - "description": "Reserved for GEANT OC to test Virgin Media link", - "circuits": [] - }, - { - "router": "mx1.par.fr.geant.net", - "name": "xe-4/1/4.1", - "bundle": [], - "bundle-parents": [], - "snmp-index": 1516, - "description": "SRV_L2CIRCUIT INFRASTRUCTURE JRA1 JRA1 | #SDX-L2_PILOT-Br52 OF-P3_par ", # noqa: E501 - "circuits": [] - }, - { - "router": "mx1.lon.uk.geant.net", - "name": "lt-1/3/0.61", - "bundle": [], - "bundle-parents": [], - "snmp-index": 1229, - "description": "SRV_IAS INFRASTRUCTURE ACCESS GLOBAL #LON-IAS-RE-Peering | BGP Peering - IAS Side", # noqa: E501 - "circuits": [] - }, - { - "router": "mx1.sof.bg.geant.net", - "name": "xe-2/0/5", - "bundle": [], - "bundle-parents": [], - "snmp-index": 694, - "description": "PHY RESERVED | Prime Telecom Sofia-Bucharest 3_4", - "circuits": [] - }, - { - "router": "mx1.sof.bg.geant.net", - "name": "xe-2/0/5", - "bundle": [], - "bundle-parents": [], - "snmp-index": 694, - "description": "SRV_GLOBAL CUSTOMER HEANET TESTDESCRIPTION |", - "circuits": [] - } -] +TEST_DASHBOARD = { + "tag": "TEST_AGGREGATE", + "dashboard_name": "TEST CLS Peers", + "interfaces": [ + { + "router": "mx1.gen.ch.geant.net", + "name": "ae23.667", + "bundle": [], + "bundle-parents": [ + "et-1/0/5", + "et-5/0/2" + ], + "description": "SRV_CLS PRIVATE EXOSCALE #CH-EXOSCALE-CLS|ASN61098 | ", # noqa: E501 + "circuits": [ + { + "id": 663057, + "name": "CH-EXOSCALE-CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 1276, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "EXOSCALE", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "62.40.100.26/31" + ], + "ipv6": [ + "2001:798::29/126" + ] + }, + { + "router": "mx1.fra.de.geant.net", + "name": "ae24.0", + "bundle": [], + "bundle-parents": [ + "xe-10/1/5" + ], + "description": "SRV_CLS PRIVATE EXOSCALE #DE-EXOSCALE-CLS|ASN61098 | ", # noqa: E501 + "circuits": [ + { + "id": 708254, + "name": "DE-EXOSCALE_CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 1251, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "EXOSCALE", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "62.40.100.20/31" + ], + "ipv6": [ + "2001:798::51/126" + ] + }, + { + "router": "mx1.fra.de.geant.net", + "name": "ae25.0", + "bundle": [], + "bundle-parents": [ + "xe-10/2/2" + ], + "description": "SRV_CLS PRIVATE T-SYSTEMS #DE-T-SYSTEMS-CLS|ASN6878 | ", # noqa: E501 + "circuits": [ + { + "id": 708258, + "name": "DE-T-SYSTEMS-CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 1079, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "T-SYSTEMS", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "94.100.252.88/31" + ], + "ipv6": [] + }, + { + "router": "mx1.fra.de.geant.net", + "name": "ae29.0", + "bundle": [], + "bundle-parents": [ + "xe-10/1/2" + ], + "description": "SRV_CLS PRIVATE AWS #DE-AWS-CLS |ASN16509 | ", + "circuits": [ + { + "id": 708273, + "name": "DE-AWS-CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 1208, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "AWS", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "52.95.219.129/31" + ], + "ipv6": [ + "2620:107:4008:27b::2/64" + ] + }, + { + "router": "mx1.fra.de.geant.net", + "name": "ae35.0", + "bundle": [], + "bundle-parents": [ + "xe-11/2/3" + ], + "description": "SRV_CLS PRIVATE CLOUDFERRO #DE-CLOUDFERRO-CLS|ASN200999 | ", # noqa: E501 + "circuits": [ + { + "id": 708235, + "name": "DE-CLOUDFERRO-CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 1218, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "CLOUDFERRO", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "45.92.241.127/31" + ], + "ipv6": [] + }, + { + "router": "mx1.fra.de.geant.net", + "name": "ae37.0", + "bundle": [], + "bundle-parents": [ + "xe-3/3/0" + ], + "description": "SRV_CLS PRIVATE ORACLE #DE-ORACLE-CLS|ASN31898 ", + "circuits": [ + { + "id": 708312, + "name": "DE-ORACLE-CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 1281, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "ORACLE", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "130.61.6.73/31" + ], + "ipv6": [ + "2603:c000:280::5/127" + ] + }, + { + "router": "mx1.vie.at.geant.net", + "name": "ae25.0", + "bundle": [], + "bundle-parents": [ + "xe-4/0/4" + ], + "description": "SRV_CLS PRIVATE AWS #AT-AWS-CLS|ASN16509 | ", + "circuits": [ + { + "id": 708166, + "name": "AT-AWS-CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 942, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "AWS", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "52.95.219.37/31" + ], + "ipv6": [ + "2620:107:4008:251::2/64" + ] + }, + { + "router": "mx1.lon.uk.geant.net", + "name": "ae24.0", + "bundle": [], + "bundle-parents": [ + "xe-3/1/7" + ], + "description": "SRV_CLS PRIVATE ORACLE #UK-ORACLE-CLS |ASN31898", + "circuits": [ + { + "id": 708307, + "name": "UK-ORACLE-CLS", + "type": "GEANT CLOUD PEERING", + "status": "operational" + } + ], + "snmp-index": 976, + "dashboards": [ + "CLS", + "CLS_PEERS" + ], + "dashboard_info": { + "name": "ORACLE", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "132.145.7.81/31" + ], + "ipv6": [ + "2603:c000:380::9/127" + ] + } + ] +} def generate_folder(data): @@ -151,27 +290,19 @@ def generate_folder(data): @responses.activate def test_provision_aggregate(data_config, mocker, client): - TEST_DATASOURCE = [{ - "name": "brian-influx-datasource", - "type": "influxdb", - "access": "proxy", - "url": "http://test-brian-datasource.geant.org:8086", - "database": "test-db", - "basicAuth": False, - "isDefault": True, - "readOnly": False - }] + def create_dashboard(_, dash, folder=None): + return dash - _mocked_create_dashboard = mocker.patch( - 'brian_dashboard_manager.grafana.provision.create_dashboard') - # we dont care about this, tested separately - _mocked_create_dashboard.return_value = None + mocker.patch( + 'brian_dashboard_manager.grafana.provision.create_dashboard', + create_dashboard) request = TokenRequest(**data_config, token='test') fake_folder = generate_folder({'uid': 'aggtest', 'title': 'aggtest'}) - dash = { - 'predicate': is_cls_peer, - 'tag': 'cls_peers', - } - provision_aggregate(request, 'MY FAKE PEERS', fake_folder, - dash, TEST_INTERFACES, TEST_DATASOURCE[0]['name']) + result = provision_aggregate(request, fake_folder, TEST_DASHBOARD, + 'test_datasource') + panels = result['panels'] + expected_title = f'Aggregate - {TEST_DASHBOARD["dashboard_name"]}' + assert result['title'] == expected_title + assert len(panels) == 14 + assert len(panels[0]['targets']) == len(TEST_DASHBOARD['interfaces']) diff --git a/test/test_update.py b/test/test_update.py index 55050de6be23493d7b201cdecb1dfaa0f36ad556..16735679523bb7a3d7a69e8348bc9f18beb23e11 100644 --- a/test/test_update.py +++ b/test/test_update.py @@ -1,15 +1,10 @@ import responses import json import re -from brian_dashboard_manager.grafana.utils.request import TokenRequest -from brian_dashboard_manager.templating.nren_access import get_nrens + from brian_dashboard_manager.grafana.provision import provision_folder, \ - generate_all_nrens, provision -from brian_dashboard_manager.grafana.provision import is_re_customer, \ - is_cls, is_ias_customer, is_ias_private, is_ias_public, is_ias_upstream, \ - is_lag_backbone, is_phy_upstream, is_re_peer, is_gcs, \ - is_geantopen, is_l2circuit, is_lhcone_peer, is_lhcone_customer, is_mdvpn,\ - parse_backbone_name, parse_phy_upstream_name + provision + DEFAULT_REQUEST_HEADERS = { "Content-type": "application/json", @@ -19,121 +14,458 @@ DEFAULT_REQUEST_HEADERS = { TEST_INTERFACES = [ { - "router": "mx1.ath2.gr.geant.net", - "name": "xe-1/0/1", + "router": "srx2.ch.office.geant.net", + "name": "ge-0/0/8", "bundle": [], "bundle-parents": [], - "snmp-index": 569, - "description": "PHY RESERVED | New OTEGLOBE ATH2-VIE 10Gb LS", - "circuits": [] + "description": "PHY CUSTOMER GEANT CORPORATE SRF000001 | GEANT Corporate to MX1.LON - Via Vodafone", # noqa: E501 + "circuits": [ + { + "id": 679232, + "name": "GEANT CORPORATE TO MX1.LON - VIA VODAFONE", + "type": "GEANT IP", + "status": "non-monitored" + } + ], + "snmp-index": 523, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "PHYSICAL" + }, + "ipv4": [], + "ipv6": [] }, { - "router": "mx1.ath2.gr.geant.net", - "name": "ge-1/3/7", + "router": "srx2.ch.office.geant.net", + "name": "ge-0/0/8.10", "bundle": [], "bundle-parents": [], + "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_CORPORATE-ViaVodafone | GEANT Corporate to mx1.lon - Via Vodafone ", # noqa: E501 + "circuits": [ + { + "id": 679360, + "name": "GEANT_CORPORATE-VIAVODAFONE", + "type": "GEANT IP", + "status": "non-monitored" + } + ], "snmp-index": 543, - "description": "PHY SPARE", - "circuits": [] + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "62.40.127.131/31" + ], + "ipv6": [ + "2001:798:99:1::ca/126" + ] }, { - "router": "mx1.ham.de.geant.net", - "name": "xe-2/2/0.13", + "router": "srx2.ch.office.geant.net", + "name": "ge-0/0/8.11", "bundle": [], "bundle-parents": [], - "snmp-index": 721, - "description": "SRV_L2CIRCUIT CUSTOMER WP6T3 WP6T3 #ham_lon2-WP6-GTS_20063 |", # noqa: E501 + "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_CORPORATE_ViaVodafone-VRF | GEANT Corporate to mx1.lon - Via Vodafone - for VRF", # noqa: E501 "circuits": [ { - "id": 52382, - "name": "ham_lon2-WP6-GTS_20063_L2c", - "type": "", - "status": "operational" + "id": 712144, + "name": "GEANT_CORPORATE_VIAVODAFONE-VRF", + "type": "GEANT IP", + "status": "non-monitored" } - ] + ], + "snmp-index": 545, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "10.101.0.6/30" + ], + "ipv6": [] }, { - "router": "mx1.fra.de.geant.net", - "name": "ae27", + "router": "srx2.ch.office.geant.net", + "name": "ge-0/0/8.12", "bundle": [], - "bundle-parents": [ - "xe-10/0/2", - "xe-10/3/2", - "xe-10/3/3" - ], - "snmp-index": 760, - "description": "LAG CUSTOMER ULAKBIM SRF9940983 |", + "bundle-parents": [], + "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_CORPORATE_ViaVodafone-VRF-TEST | GEANT Corporate to mx1.lon - Via Vodafone - DASHBOARD BGP TEST VLAN", # noqa: E501 "circuits": [ { - "id": 40983, - "name": "ULAKBIM AP2 LAG", - "type": "", - "status": "operational" + "id": 678920, + "name": "GEANT_CORPORATE_VIAVODAFONE-VRF-TEST (DO NOT OPEN A TICKET)", # noqa: E501 + "type": "GEANT IP", + "status": "non-monitored" } - ] + ], + "snmp-index": 522, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "62.40.127.141/31" + ], + "ipv6": [] }, { - "router": "mx2.zag.hr.geant.net", - "name": "xe-2/1/0", + "router": "srx2.ch.office.geant.net", + "name": "ge-0/0/8.996", "bundle": [], "bundle-parents": [], - "snmp-index": 739, - "description": "PHY SPARE", - "circuits": [] + "description": "SRV_GLOBAL CUSTOMER GEANT #GEANT_OPERATIONS_LabConnectivity | GEANT MX1.LON Infinera VRF to Operations Lab", # noqa: E501 + "circuits": [ + { + "id": 678999, + "name": "GEANT_OPERATIONS_LABCONNECTIVITY", + "type": "GEANT IP", + "status": "non-monitored" + } + ], + "snmp-index": 554, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "10.1.1.1/30" + ], + "ipv6": [] }, { - "router": "rt1.rig.lv.geant.net", - "name": "xe-0/1/5", - "bundle": [], + "router": "mx1.ams.nl.geant.net", + "name": "xe-0/0/0", + "bundle": [ + "ae16" + ], "bundle-parents": [], - "snmp-index": 539, - "description": "PHY SPARE", - "circuits": [] + "description": "PHY CUSTOMER KIAE P_AE16 SRF9915671 |", + "circuits": [], + "snmp-index": 588, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "KIAE", + "interface_type": "PHYSICAL" + }, + "ipv4": [], + "ipv6": [] }, { - "router": "srx1.ch.office.geant.net", - "name": "ge-0/0/0", - "bundle": [], + "router": "mx1.ams.nl.geant.net", + "name": "xe-0/0/1", + "bundle": [ + "ae14" + ], "bundle-parents": [], - "snmp-index": 513, - "description": "Reserved for GEANT OC to test Virgin Media link", - "circuits": [] + "description": "PHY CUSTOMER SWITCH P_AE14 SRF18008 |", + "circuits": [], + "snmp-index": 589, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "SWITCH", + "interface_type": "PHYSICAL" + }, + "ipv4": [], + "ipv6": [] }, { - "router": "mx1.par.fr.geant.net", - "name": "xe-4/1/4.1", + "router": "mx1.ams.nl.geant.net", + "name": "ge-0/2/1.0", "bundle": [], "bundle-parents": [], - "snmp-index": 1516, - "description": "SRV_L2CIRCUIT INFRASTRUCTURE JRA1 JRA1 | #SDX-L2_PILOT-Br52 OF-P3_par ", # noqa: E501 - "circuits": [] + "description": "SRV_L2CIRCUIT INFRASTRUCTURE GEANT GEANT #AMS-ZAG OPENFLOW |", # noqa: E501 + "circuits": [], + "snmp-index": 687, + "dashboards": [ + "L2_CIRCUIT" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "LOGICAL" + }, + "ipv4": [], + "ipv6": [] }, { - "router": "mx1.lon.uk.geant.net", - "name": "lt-1/3/0.61", + "router": "mx1.ams.nl.geant.net", + "name": "ge-0/2/2.0", "bundle": [], "bundle-parents": [], - "snmp-index": 1229, - "description": "SRV_IAS INFRASTRUCTURE ACCESS GLOBAL #LON-IAS-RE-Peering | BGP Peering - IAS Side", # noqa: E501 - "circuits": [] + "description": "SRV_L2CIRCUIT INFRASTRUCTURE GEANT GEANT #AMS-VIE OPENFLOW |", # noqa: E501 + "circuits": [], + "snmp-index": 711, + "dashboards": [ + "L2_CIRCUIT" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "LOGICAL" + }, + "ipv4": [], + "ipv6": [] }, { - "router": "mx1.sof.bg.geant.net", - "name": "xe-2/0/5", + "router": "mx1.ams.nl.geant.net", + "name": "ge-0/2/4.0", "bundle": [], "bundle-parents": [], - "snmp-index": 694, - "description": "PHY RESERVED | Prime Telecom Sofia-Bucharest 3_4", - "circuits": [] + "description": "SRV_L2CIRCUIT INFRASTRUCTURE GEANT GEANT #AMS-FRA OPENFLOW |", # noqa: E501 + "circuits": [], + "snmp-index": 718, + "dashboards": [ + "L2_CIRCUIT" + ], + "dashboard_info": { + "name": "GEANT", + "interface_type": "LOGICAL" + }, + "ipv4": [], + "ipv6": [] + } +] + +NREN_INTERFACES = [ + { + "router": "mx1.dub2.ie.geant.net", + "name": "xe-0/0/0", + "bundle": [ + "ae10" + ], + "bundle-parents": [], + "description": "PHY CUSTOMER HEANET P_AE10 SRF9948758 | HEANET-AP2-LL3", # noqa: E501 + "circuits": [], + "snmp-index": 554, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "PHYSICAL" + }, + "ipv4": [], + "ipv6": [] }, { - "router": "mx1.sof.bg.geant.net", - "name": "xe-2/0/5", - "bundle": [], + "router": "mx1.dub2.ie.geant.net", + "name": "xe-1/0/1", + "bundle": [ + "ae10" + ], + "bundle-parents": [], + "description": "PHY CUSTOMER HEANET P_AE10 SRF0000001 | HEANET-AP2-LL2", # noqa: E501 + "circuits": [], + "snmp-index": 527, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "PHYSICAL" + }, + "ipv4": [], + "ipv6": [] + }, + { + "router": "mx1.dub2.ie.geant.net", + "name": "xe-1/1/0", + "bundle": [ + "ae10" + ], "bundle-parents": [], - "snmp-index": 694, - "description": "SRV_GLOBAL CUSTOMER HEANET TESTDESCRIPTION |", - "circuits": [] + "description": "PHY CUSTOMER HEANET P_AE10 SRF9925903 | HEANET-AP2-LL1", # noqa: E501 + "circuits": [], + "snmp-index": 528, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "PHYSICAL" + }, + "ipv4": [], + "ipv6": [] + }, + { + "router": "mx1.dub2.ie.geant.net", + "name": "ae10", + "bundle": [], + "bundle-parents": [ + "xe-0/0/0", + "xe-1/0/1", + "xe-1/1/0" + ], + "description": "LAG CUSTOMER HEANET SRF9925909 | HEANET-AP2-LAG", + "circuits": [], + "snmp-index": 596, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "AGGREGATE" + }, + "ipv4": [], + "ipv6": [] + }, + { + "router": "mx1.dub2.ie.geant.net", + "name": "ae10.12", + "bundle": [], + "bundle-parents": [ + "xe-0/0/0", + "xe-1/0/1", + "xe-1/1/0" + ], + "description": "SRV_GLOBAL CUSTOMER HEANET #HEANET-AP2 | ASN1213 | ", + "circuits": [ + { + "id": 662976, + "name": "HEANET-AP2", + "type": "GEANT IP", + "status": "operational" + } + ], + "snmp-index": 713, + "dashboards": [ + "NREN", + "RE_CUST" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "62.40.125.13/30" + ], + "ipv6": [ + "2001:0798:0018:10aa::15/126" + ] + }, + { + "router": "mx1.dub2.ie.geant.net", + "name": "ae10.30", + "bundle": [], + "bundle-parents": [ + "xe-0/0/0", + "xe-1/0/1", + "xe-1/1/0" + ], + "description": "SRV_MDVPN CUSTOMER HEANET AP2 #HEANET-BGP-LU-CoC-1 |", # noqa: E501 + "circuits": [ + { + "id": 663160, + "name": "HEANET-BGP-LU-COC-1", + "type": "MD-VPN (NATIVE)", + "status": "operational" + } + ], + "snmp-index": 712, + "dashboards": [ + "MDVPN_CUSTOMERS", + "NREN" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "62.40.102.36/31" + ], + "ipv6": [] + }, + { + "router": "mx1.dub2.ie.geant.net", + "name": "ae10.333", + "bundle": [], + "bundle-parents": [ + "xe-0/0/0", + "xe-1/0/1", + "xe-1/1/0" + ], + "description": "SRV_IAS CUSTOMER HEANET #HEANET-AP2-IAS IASPS | ASN1213 ", # noqa: E501 + "circuits": [ + { + "id": 663214, + "name": "HEANET-AP2-IAS", + "type": "GEANT PEERING", + "status": "operational" + } + ], + "snmp-index": 658, + "dashboards": [ + "IAS_CUSTOMER", + "NREN" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "LOGICAL" + }, + "ipv4": [ + "83.97.88.77/30" + ], + "ipv6": [ + "2001:798:1::61/126" + ] + }, + { + "router": "mx1.dub2.ie.geant.net", + "name": "ae10.1214", + "bundle": [], + "bundle-parents": [ + "xe-0/0/0", + "xe-1/0/1", + "xe-1/1/0" + ], + "description": "SRV_L2CIRCUIT CUSTOMER HEANET GEANT #ams-dub2-HEANET-RARE-21061 |", # noqa: E501 + "circuits": [ + { + "id": 713335, + "name": "AMS-DUB2-HEANET-RARE-21061", + "type": "GEANT PLUS", + "status": "non-monitored" + } + ], + "snmp-index": 790, + "dashboards": [ + "L2_CIRCUIT" + ], + "dashboard_info": { + "name": "HEANET", + "interface_type": "LOGICAL" + }, + "ipv4": [], + "ipv6": [] } ] @@ -159,70 +491,17 @@ def generate_folder(data): @responses.activate def test_provision_folder(data_config, mocker): dashboards = { - 'CLS TESTDASHBOARD': { - 'predicate': is_cls, - 'tag': 'CLS' - }, - 'RE PEER TESTDASHBOARD': { - 'predicate': is_re_peer, - 'tag': 'RE_PEER' - }, - 'RE CUST TESTDASHBOARD': { - 'predicate': is_re_customer, - 'tag': 'RE_CUST' - }, - 'GEANTOPEN TESTDASHBOARD': { - 'predicate': is_geantopen, - 'tag': 'GEANTOPEN' - }, - 'GCS TESTDASHBOARD': { - 'predicate': is_gcs, - 'tag': 'AUTOMATED_L2_CIRCUITS' - }, - 'L2 CIRCUIT TESTDASHBOARD': { - 'predicate': is_l2circuit, - 'tag': 'L2_CIRCUITS' - }, - 'LHCONE PEER TESTDASHBOARD': { - 'predicate': is_lhcone_peer, - 'tag': 'LHCONE_PEER' - }, - 'LHCONE CUST TESTDASHBOARD': { - 'predicate': is_lhcone_customer, - 'tag': 'LHCONE_CUST' - }, - 'MDVPN Customers TESTDASHBOARD': { - 'predicate': is_mdvpn, - 'tag': 'MDVPN' - }, - 'Infrastructure Backbone TESTDASHBOARD': { - 'predicate': is_lag_backbone, - 'tag': 'BACKBONE', - 'errors': True, - 'parse_func': parse_backbone_name + 'NREN': { + 'tag': ['customers'], + 'folder_name': 'NREN Access', + 'interfaces': [iface for iface in TEST_INTERFACES if 'NREN' in iface['dashboards']] # noqa: E501 }, - 'IAS PRIVATE TESTDASHBOARD': { - 'predicate': is_ias_private, - 'tag': 'IAS_PRIVATE' + 'RE_CUST': { + 'tag': 'RE_CUST', + 'folder_name': 'RE Customer', + 'interfaces': [iface for iface in TEST_INTERFACES if 'RE_CUST' in iface['dashboards']] # noqa: E501 }, - 'IAS PUBLIC TESTDASHBOARD': { - 'predicate': is_ias_public, - 'tag': 'IAS_PUBLIC' - }, - 'IAS CUSTOMER TESTDASHBOARD': { - 'predicate': is_ias_customer, - 'tag': 'IAS_CUSTOMER' - }, - 'IAS UPSTREAM TESTDASHBOARD': { - 'predicate': is_ias_upstream, - 'tag': 'IAS_UPSTREAM' - }, - 'GWS PHY Upstream TESTDASHBOARD': { - 'predicate': is_phy_upstream, - 'tag': 'GWS_UPSTREAM', - 'errors': True, - 'parse_func': parse_phy_upstream_name - } + } # just return a generated folder @@ -231,111 +510,42 @@ def test_provision_folder(data_config, mocker): _mocked_find_folder.return_value = generate_folder( {'uid': 'testfolderuid', 'title': 'testfolder'}) - # we don't care about testing create_dashboard - _mocked_find_folder = mocker.patch( - 'brian_dashboard_manager.grafana.provision.create_dashboard') - _mocked_find_folder.return_value = None - - for dashboard in dashboards: - provision_folder(None, 'testfolder', dashboards[dashboard], - TEST_INTERFACES, - 'testdatasource', ['CLS TESTDASHBOARD']) - - -@responses.activate -def test_provision_nrens(data_config, mocker): - NREN_INTERFACES = [ - # physical - { - "router": "mx1.dub2.ie.geant.net", - "name": "xe-0/0/0", - "bundle": ["ae10"], - "bundle-parents": [], - "snmp-index": 554, - "description": "PHY CUSTOMER HEANET P_AE10 SRF9948758 | HEANET AP2-3 LL", # noqa: E501 - "circuits": [] - }, - # aggregate - { - "router": "mx1.dub2.ie.geant.net", - "name": "ae10", - "bundle": [], - "bundle-parents": ["xe-0/0/0", "xe-1/0/1", "xe-1/1/0"], - "snmp-index": 596, - "description": "LAG CUSTOMER HEANET SRF9925909 |", - "circuits": [ - { - "id": 25909, - "name": "HEANET AP2 LAG", - "type": "", - "status": "operational" - } - ] - }, - # logical - { - "router": "mx1.dub2.ie.geant.net", - "name": "ae10.12", - "bundle": [], - "bundle-parents": [ - "xe-0/0/0", - "xe-1/0/1", - "xe-1/1/0" - ], - "snmp-index": 713, - "description": "SRV_GLOBAL CUSTOMER HEANET #HEANET_AP2 | ASN1213 | ", # noqa: E501 - "circuits": [ - { - "id": 48776, - "name": "HEANET AP2", - "type": "ip access", - "status": "operational" - }, - { - "id": 31347, - "name": "HEANET AP2 L2c", - "type": "", - "status": "operational" - } - ] - } - ] + def create_dashboard(request, dashboard, folder_id=None): + return dashboard - UID = '1337' + mocker.patch( + 'brian_dashboard_manager.grafana.provision.create_dashboard', + create_dashboard) - def get_callback(request): - query = request.params.get('query') - return 200, {}, json.dumps({'uid': UID, 'title': query}) + excluded_dashboards = [] + nren_result = provision_folder(None, 'testfolder', dashboards['NREN'], + 'testdatasource', excluded_dashboards) - responses.add_callback( - method=responses.GET, - url=re.compile(f"http://{data_config['hostname']}/api/search"), - callback=get_callback) + assert len(nren_result) == 3 + assert nren_result[0]['title'] == 'GEANT' + assert nren_result[1]['title'] == 'KIAE' + assert nren_result[2]['title'] == 'SWITCH' - def post_callback(request): - dashboard = json.loads(request.body).get('dashboard') - title = dashboard.get('title') - return 200, {}, json.dumps({'uid': UID, 'title': title}) + excluded_dashboards = ['SWITCH'] + nren_excluded = provision_folder(None, 'testfolder', dashboards['NREN'], + 'testdatasource', excluded_dashboards) - responses.add_callback( - method=responses.POST, - url=re.compile(f"http://{data_config['hostname']}/api/dashboards/db"), - callback=post_callback) + assert len(nren_excluded) == 2 + assert nren_excluded[0]['title'] == 'GEANT' + assert nren_excluded[1]['title'] == 'KIAE' - nrens = get_nrens(NREN_INTERFACES) - assert len(nrens) == 1 and nrens.get('HEANET') is not None - assert len(nrens.get('HEANET').get('AGGREGATES')) == 1 - assert len(nrens.get('HEANET').get('SERVICES')) == 1 - assert len(nrens.get('HEANET').get('PHYSICAL')) == 2 - token_request = TokenRequest(token='testtoken', **data_config) - generate_all_nrens(token_request, NREN_INTERFACES, 1, 'testdatasource') + cust_result = provision_folder(None, 'testfolder', dashboards['RE_CUST'], + 'testdatasource', ['GEANT']) + assert len(cust_result) == 2 + assert cust_result[0]['title'] == 'KIAE' + assert cust_result[1]['title'] == 'SWITCH' @responses.activate def test_provision(data_config, mocker, client): def get_callback(request): - return 200, {}, json.dumps(TEST_INTERFACES) + return 200, {}, json.dumps(NREN_INTERFACES) responses.add_callback( method=responses.GET,