Skip to content
Snippets Groups Projects
Commit f3efbd0f authored by Release Webservice's avatar Release Webservice
Browse files

Finished release 0.16.

parents 13f2084e f91fc85e
No related branches found
Tags 0.16
No related merge requests found
Showing
with 631 additions and 60 deletions
......@@ -77,6 +77,8 @@ DEFAULT_ORGANIZATIONS = [
"IAS PUBLIC": True,
"IAS UPSTREAM": True,
"GWS PHY Upstream": True,
"GWS Direct": True,
"GWS Indirect": True,
}
},
{
......@@ -116,7 +118,9 @@ DEFAULT_ORGANIZATIONS = [
"IAS PRIVATE": True,
"IAS PUBLIC": True,
"IAS UPSTREAM": True,
"GWS PHY Upstream": True
"GWS PHY Upstream": True,
"GWS Direct": True,
"GWS Indirect": True,
}
}
]
......
......@@ -46,7 +46,7 @@
"folderId": null,
"gridPos": {
"h": 25,
"w": 6,
"w": 4,
"x": 0,
"y": 0
},
......@@ -114,8 +114,8 @@
"folderId": null,
"gridPos": {
"h": 25,
"w": 6,
"x": 6,
"w": 4,
"x": 4,
"y": 0
},
"headings": false,
......@@ -182,8 +182,8 @@
"folderId": null,
"gridPos": {
"h": 25,
"w": 6,
"x": 12,
"w": 4,
"x": 8,
"y": 0
},
"headings": false,
......@@ -250,8 +250,8 @@
"folderId": null,
"gridPos": {
"h": 25,
"w": 6,
"x": 18,
"w": 4,
"x": 12,
"y": 0
},
"headings": false,
......@@ -306,6 +306,142 @@
"timeShift": null,
"title": "IAS PRIVATE",
"type": "dashlist"
},
{
"datasource": null,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"folderId": null,
"gridPos": {
"h": 25,
"w": 4,
"x": 16,
"y": 0
},
"headings": false,
"id": 6,
"limit": 100,
"pluginVersion": "7.1.4",
"query": "",
"recent": false,
"search": true,
"starred": false,
"tags": [
"GWS_DIRECT"
],
"targets": [
{
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"orderByTime": "ASC",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": []
}
],
"timeFrom": null,
"timeShift": null,
"title": "GWS Direct",
"type": "dashlist"
},
{
"datasource": null,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"folderId": null,
"gridPos": {
"h": 25,
"w": 4,
"x": 20,
"y": 0
},
"headings": false,
"id": 7,
"limit": 100,
"pluginVersion": "7.1.4",
"query": "",
"recent": false,
"search": true,
"starred": false,
"tags": [
"GWS_INDIRECT"
],
"targets": [
{
"groupBy": [
{
"params": [
"$__interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"orderByTime": "ASC",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
]
],
"tags": []
}
],
"timeFrom": null,
"timeShift": null,
"title": "GWS Indirect",
"type": "dashlist"
}
],
"schemaVersion": 26,
......@@ -338,3 +474,4 @@
"title": "IAS",
"version": 1
}
......@@ -8,6 +8,7 @@ import time
import json
import datetime
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 \
......@@ -23,7 +24,7 @@ from brian_dashboard_manager.grafana.datasource import \
from brian_dashboard_manager.grafana.folder import find_folder, \
delete_folder
from brian_dashboard_manager.inventory_provider.interfaces import \
get_interfaces
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, \
......@@ -34,6 +35,9 @@ from brian_dashboard_manager.templating.helpers import is_re_customer, \
get_interface_data, parse_backbone_name, parse_phy_upstream_name, \
get_dashboard_data, get_aggregate_interface_data
from brian_dashboard_manager.templating.gws import generate_gws, \
generate_indirect
from brian_dashboard_manager.templating.render import render_dashboard
logger = logging.getLogger(__name__)
......@@ -117,6 +121,9 @@ def provision_maybe(config):
now = datetime.datetime.now()
write_timestamp(now.timestamp(), val)
provision(config)
except Exception as e:
logger.exception('Uncaught Exception:')
raise e
finally:
now = datetime.datetime.now()
write_timestamp(now.timestamp(), False)
......@@ -180,7 +187,10 @@ def provision(config):
def excluded(interface):
desc = interface.get('description', '').lower()
return not any(nren.lower() in desc for nren in excluded_nrens)
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)
excluded_interfaces = list(filter(excluded, interfaces))
......@@ -263,6 +273,14 @@ def provision(config):
dash_list = find_dashboard(token_request) or []
dash_list = reduce(get_uid, dash_list, {})
def update_dash_list(dashboards):
for dashboard in dashboards:
if isinstance(dashboard, Future):
dashboard = dashboard.result()
if dashboard is None:
continue
dash_list[dashboard.get('uid')] = True
with ProcessPoolExecutor(max_workers=4) as executor:
provisioned = []
for folder_name, dash in dashboards.items():
......@@ -282,14 +300,55 @@ def provision(config):
excluded_interfaces, datasource_name,
exclude)
provisioned.append(res)
for result in provisioned:
folder = result.result()
if folder is None:
continue
for dashboard in folder:
if dashboard is None:
continue
dash_list[dashboard.get('uid')] = True
update_dash_list(folder)
# fetch GWS direct data and provision related dashboards
logger.info('Provisioning GWS Indirect dashboards')
folder_name = 'GWS Indirect'
exclude_indirect = excluded_folders.get(folder_name, False)
if isinstance(exclude_indirect, bool) and exclude_indirect:
# don't provision GWS Direct folder
delete_folder(token_request, folder_name)
else:
folder = find_folder(token_request, folder_name)
with ProcessPoolExecutor(max_workers=4) as executor:
gws_indirect_data = get_gws_indirect(
config['inventory_provider'])
provisioned = []
dashes = generate_indirect(gws_indirect_data, datasource_name)
for dashboard in dashes:
rendered = render_dashboard(dashboard)
provisioned.append(executor.submit(create_dashboard,
token_request,
rendered, folder['id']))
update_dash_list(provisioned)
# fetch GWS direct data and provision related dashboards
logger.info('Provisioning GWS Direct dashboards')
folder_name = 'GWS Direct'
exclude_gws = excluded_folders.get(folder_name, False)
if isinstance(exclude_gws, bool) and exclude_gws:
# don't provision GWS Direct folder
delete_folder(token_request, folder_name)
else:
folder = find_folder(token_request, folder_name)
with ProcessPoolExecutor(max_workers=4) as executor:
gws_data = get_gws_direct(config['inventory_provider'])
provisioned = []
for dashboard in generate_gws(gws_data, datasource_name):
rendered = render_dashboard(dashboard)
provisioned.append(executor.submit(create_dashboard,
token_request,
rendered, folder['id']))
update_dash_list(provisioned)
aggregate_dashboards = {
'CLS PEERS': {
......@@ -319,7 +378,6 @@ def provision(config):
if isinstance(exclude_agg, bool) and exclude_agg:
# don't provision aggregate folder
delete_folder(token_request, 'Aggregates')
pass
else:
with ProcessPoolExecutor(max_workers=4) as executor:
provisioned = []
......@@ -338,11 +396,7 @@ def provision(config):
excluded_interfaces, datasource_name)
provisioned.append(res)
for result in provisioned:
dashboard = result.result()
if dashboard is None:
continue
dash_list[dashboard.get('uid')] = True
update_dash_list(provisioned)
# NREN Access dashboards
# uses a different template than the above.
......
import requests
import logging
from requests.models import HTTPError
logger = logging.getLogger(__name__)
class Request(object):
......@@ -11,47 +15,34 @@ class Request(object):
self.BASE_URL = url
def get(self, endpoint: str, headers=None, **kwargs):
r = requests.get(
self.BASE_URL + endpoint,
headers={**headers, **self.headers} if headers else self.headers,
**kwargs
)
r.raise_for_status()
def do_request(self, method, endpoint, *args, **kwargs):
r = requests.request(method, self.BASE_URL + endpoint,
*args,
**kwargs,
headers={
**kwargs.get('headers', {}),
**self.headers
})
try:
r.raise_for_status()
except HTTPError as e:
if e.response.status_code < 500:
logger.error(e.response.content.decode('utf-8'))
raise e
return r.json()
def post(self, endpoint: str, headers=None, **kwargs):
def get(self, endpoint: str, *args, **kwargs):
return self.do_request('get', endpoint, *args, **kwargs)
r = requests.post(
self.BASE_URL + endpoint,
headers={**headers, **self.headers} if headers else self.headers,
**kwargs
)
r.raise_for_status()
def post(self, endpoint: str, data=None, **kwargs):
return self.do_request('post', endpoint, data=data, **kwargs)
return r.json()
def put(self, endpoint: str, **kwargs):
return self.do_request('put', endpoint, **kwargs)
def put(self, endpoint: str, headers=None, **kwargs):
r = requests.put(
self.BASE_URL + endpoint,
headers={**headers, **self.headers} if headers else self.headers,
**kwargs
)
r.raise_for_status()
return r.json()
def delete(self, endpoint: str, headers=None, **kwargs):
r = requests.delete(
self.BASE_URL + endpoint,
headers={**headers, **self.headers} if headers else self.headers,
**kwargs
)
r.raise_for_status()
return r.json()
def delete(self, endpoint: str, **kwargs):
return self.do_request('delete', endpoint, **kwargs)
class AdminRequest(Request):
......
......@@ -40,6 +40,8 @@ def get_interfaces(host): # pragma: no cover
return interface
ip = router.get(interface['name'])
if not ip:
return interface
ipv4 = ip['ipv4']
ipv6 = ip['ipv6']
interface['ipv4'] = ipv4
......@@ -47,3 +49,17 @@ def get_interfaces(host): # pragma: no cover
return interface
enriched = list(map(enrich, interfaces))
return enriched
def get_gws_direct(host):
r = requests.get(f'{host}/poller/gws/direct')
r.raise_for_status()
interfaces = r.json()
return interfaces
def get_gws_indirect(host):
r = requests.get(f'{host}/poller/gws/indirect')
r.raise_for_status()
interfaces = r.json()
return interfaces
......@@ -40,6 +40,13 @@ def should_provision():
timestamp = datetime.datetime.fromtimestamp(
state.get('timestamp', 1))
now = datetime.datetime.now()
if provisioning and (now - timestamp).total_seconds() > 86400:
# if we stay in provisioning state
# for over a day, we probably restarted
# and the state file is out of sync.
provisioning = False
can_provision = not provisioning
return can_provision, timestamp
except FileNotFoundError:
......
from typing import DefaultDict
from brian_dashboard_manager.templating.helpers import get_dashboard_data
def get_panel_data(interfaces):
result = DefaultDict(list)
count = {}
def add_num(iface):
name = iface['nren'] + iface['isp'] + iface['tag']
count[name] = count.get(name, 0) + 1
iface['num'] = count[name]
return iface
# Add a number to multiple interfaces for same nren/isp combination,
# as they share the same tag if the hostnames are different.
interfaces = list(map(add_num, interfaces))
for interface in interfaces:
isp = interface.get('isp')
nren = interface.get('nren')
hostname = interface.get('hostname')
# identifier for interface, as we don't have their names
interface_tag = interface.get('tag')
if_num = interface.get('num')
counters = interface.get('counters')
skip = True
for counter in counters:
if counter.get('field') in ['traffic_in', 'traffic_out']:
skip = False
if skip:
# only process interfaces where we are polling traffic data
continue
gws_measurement = 'gwsd_rates'
title = f'{nren} GWS Direct {isp} Interface {if_num} ({hostname})'
result[f'GWS Direct - {isp}'].append({
'isp': isp,
'nren': nren,
'measurement': gws_measurement,
'title': title,
'interface_tag': interface_tag,
'hostname': hostname,
'has_v6': False
})
return result
def get_gws_indirect_panel_data(interfaces):
result = DefaultDict(list)
for interface in interfaces:
hostname = interface.get('hostname').replace('.geant.net', '')
if_name = interface.get('interface')
service_name = interface.get('name')
customer = interface.get('customer')
measurement = 'dscp32_rates'
panel_title = f'{hostname} - {{}} - {if_name} - #{service_name} IASGWS'
result[f'GWS Indirect - {customer}'].append({
'measurement': measurement,
'title': panel_title,
'interface': if_name,
'hostname': interface.get('hostname'),
'has_v6': False
})
return result
def generate_gws(gws_data, datasource):
panel_data = get_panel_data(gws_data)
for dash in get_dashboard_data(panel_data, datasource, 'GWS_DIRECT'):
yield dash
def generate_indirect(gws_data, datasource):
panel_data = get_gws_indirect_panel_data(gws_data)
for dash in get_dashboard_data(panel_data, datasource, 'GWS_INDIRECT'):
yield dash
......@@ -294,7 +294,9 @@ def get_panel_fields(panel, panel_type, datasource):
def get_target_data(alias, field):
return {
**panel, # panel has target hostname and interface
# panel includes identifying information
# such as hostname, interface, etc.
**panel,
'alias': alias,
'refId': next(letters),
'select_field': field,
......@@ -344,9 +346,9 @@ def get_dashboard_data(data, datasource, tag, errors=False):
{**panel, **next(gridPos)}, 'errors', datasource))
return result
for peer, panels in data.items():
for dashboard_name, panels in data.items():
result = {
'title': peer,
'title': dashboard_name,
'datasource': datasource,
'panels': get_panel_definitions(panels, datasource),
}
......
......@@ -12,7 +12,11 @@
}
{% endif %}
],
{% if measurement %}
"measurement": "{{ measurement }}",
{% else %}
"measurement": "interface_rates",
{% endif %}
"orderByTime": null,
"policy": null,
"refId": "{{ refId }}",
......@@ -41,6 +45,7 @@
]
],
"tags": [
{% if not isp %}
{
"condition": null,
"key": "hostname",
......@@ -53,5 +58,25 @@
"operator": "=",
"value": "{{ interface }}"
}
{% else %}
{
"condition": null,
"key": "tag",
"operator": "=",
"value": "{{ interface_tag }}"
},
{
"condition": "AND",
"key": "isp",
"operator": "=",
"value": "{{ isp }}"
},
{
"condition": "AND",
"key": "nren",
"operator": "=",
"value": "{{ nren }}"
}
{% endif %}
]
}
\ No newline at end of file
......@@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.
## [0.16] - 2021-07-13
- Add GWS Direct/Indirect graphs + related changes
- Improve request logging for easier debugging
## [0.15] - 2021-05-17
- [POL1-433] Only provision aggregate panels for NRENs if LAG interfaces are present.
......
......@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name='brian-dashboard-manager',
version="0.15",
version="0.16",
author='GEANT',
author_email='swd@geant.org',
description='',
......
import responses
import json
from brian_dashboard_manager.templating.gws import generate_gws
from brian_dashboard_manager.inventory_provider.interfaces import \
get_gws_direct
TEST_DATA = [
{
"nren": "ARNES",
"isp": "Cogent",
"hostname": "88.200.0.63",
"tag": "a",
"counters": [
{
"field": "discards_in",
"oid": "1.3.6.1.2.1.2.2.1.13.533",
"community": "gn2nocT3st"
},
{
"field": "discards_out",
"oid": "1.3.6.1.2.1.2.2.1.19.533",
"community": "gn2nocT3st"
},
{
"field": "errors_in",
"oid": "1.3.6.1.2.1.2.2.1.14.533",
"community": "gn2nocT3st"
},
{
"field": "errors_out",
"oid": "1.3.6.1.2.1.2.2.1.20.533",
"community": "gn2nocT3st"
}
]
},
{
"nren": "ARNES",
"isp": "Cogent",
"hostname": "88.200.0.63",
"tag": "b",
"counters": [
{
"field": "traffic_in",
"oid": "1.3.6.1.2.1.31.1.1.1.6.531",
"community": "gn2nocT3st"
},
{
"field": "traffic_out",
"oid": "1.3.6.1.2.1.31.1.1.1.10.531",
"community": "gn2nocT3st"
}
]
},
{
"nren": "ARNES",
"isp": "Cogent",
"hostname": "88.200.0.63",
"tag": "c",
"counters": [
{
"field": "traffic_in",
"oid": "1.3.6.1.2.1.31.1.1.1.6.525",
"community": "gn2nocT3st"
},
{
"field": "traffic_out",
"oid": "1.3.6.1.2.1.31.1.1.1.10.525",
"community": "gn2nocT3st"
}
]
},
{
"nren": "ARNES",
"isp": "Cogent",
"hostname": "88.200.0.63",
"tag": "d",
"counters": [
{
"field": "traffic_in",
"oid": "1.3.6.1.2.1.31.1.1.1.6.553",
"community": "gn2nocT3st"
},
{
"field": "traffic_out",
"oid": "1.3.6.1.2.1.31.1.1.1.10.553",
"community": "gn2nocT3st"
}
]
},
{
"nren": "ARNES",
"isp": "Telia",
"hostname": "62.40.124.6",
"tag": "a",
"counters": [
{
"field": "traffic_in",
"oid": "1.3.6.1.2.1.31.1.1.1.6.611",
"community": "gn2nocT3st"
},
{
"field": "traffic_out",
"oid": "1.3.6.1.2.1.31.1.1.1.10.611",
"community": "gn2nocT3st"
}
]
}
]
@responses.activate
def test_gws(data_config, mocker, client):
def get_callback(request):
return 200, {}, json.dumps(TEST_DATA)
responses.add_callback(
method=responses.GET,
url=f"{data_config['inventory_provider']}/poller/gws/direct",
callback=get_callback)
gws_data = get_gws_direct(data_config['inventory_provider'])
dashboards = list(generate_gws(gws_data, 'testdatasource'))
assert len(dashboards) == 2
assert dashboards[0]['title'] == 'GWS Direct - Cogent'
assert len(dashboards[0]['panels']) == 3
assert dashboards[1]['title'] == 'GWS Direct - Telia'
assert len(dashboards[1]['panels']) == 1
import responses
import json
from brian_dashboard_manager.templating.gws import generate_indirect
from brian_dashboard_manager.inventory_provider.interfaces import \
get_gws_indirect
TEST_DATA = [
{
"id": 712361,
"name": "FCCN-AP3-IAS",
"customer": "FCCN",
"speed": 107374182400,
"pop": "PORTO",
"hostname": "rt1.por.pt.geant.net",
"interface": "ae10.333",
"type": "GWS - INDIRECT",
"status": "operational"
},
{
"id": 661222,
"name": "FCCN-AP2-IAS",
"customer": "FCCN",
"speed": 42949672960,
"pop": "LISBON 2",
"hostname": "mx1.lis.pt.geant.net",
"interface": "ae10.333",
"type": "GWS - INDIRECT",
"status": "operational"
},
{
"id": 661500,
"name": "IUCC-AP1-IAS",
"customer": "IUCC",
"speed": 32212254720,
"pop": "LONDON",
"hostname": "mx1.lon.uk.geant.net",
"interface": "ae21.333",
"type": "GWS - INDIRECT",
"status": "operational"
},
{
"id": 663112,
"name": "ROEDUNET_AP1_IAS",
"customer": "ROEDUNET",
"speed": 42949672960,
"pop": "BUCHAREST",
"hostname": "mx1.buc.ro.geant.net",
"interface": "ae11.333",
"type": "GWS - INDIRECT",
"status": "operational"
},
{
"id": 663228,
"name": "IUCC-AP2-IAS",
"customer": "IUCC",
"speed": 32212254720,
"pop": "FRANKFURT",
"hostname": "mx1.fra.de.geant.net",
"interface": "ae21.333",
"type": "GWS - INDIRECT",
"status": "operational"
},
{
"id": 661641,
"name": "FCCN-AP1-IAS",
"customer": "FCCN",
"speed": 42949672960,
"pop": "LISBON",
"hostname": "mx2.lis.pt.geant.net",
"interface": "ae10.333",
"type": "GWS - INDIRECT",
"status": "operational"
}
]
@responses.activate
def test_gws(data_config, mocker, client):
def get_callback(request):
return 200, {}, json.dumps(TEST_DATA)
responses.add_callback(
method=responses.GET,
url=f"{data_config['inventory_provider']}/poller/gws/indirect",
callback=get_callback)
gws_data = get_gws_indirect(data_config['inventory_provider'])
dashboards = list(generate_indirect(gws_data, 'testdatasource'))
assert len(dashboards) == 3
assert dashboards[0]['title'] == 'GWS Indirect - FCCN'
assert len(dashboards[0]['panels']) == 3
assert dashboards[1]['title'] == 'GWS Indirect - IUCC'
assert len(dashboards[1]['panels']) == 2
assert dashboards[2]['title'] == 'GWS Indirect - ROEDUNET'
assert len(dashboards[2]['panels']) == 1
......@@ -460,6 +460,14 @@ def test_provision(data_config, mocker, client):
_mocked_get_dashboard_definitions = mocker.patch(
'brian_dashboard_manager.grafana.provision.get_dashboard_definitions')
_mocked_gws = mocker.patch(
'brian_dashboard_manager.grafana.provision.get_gws_direct')
_mocked_gws.return_value = []
_mocked_gws_indirect = mocker.patch(
'brian_dashboard_manager.grafana.provision.get_gws_indirect')
_mocked_gws_indirect.return_value = []
UID = 1
ID = 1
VERSION = 1
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment