diff --git a/brian_dashboard_manager/dashboards/peers.json b/brian_dashboard_manager/dashboards/peers.json index d4bc21ba04b92d2f3ce00ed6a527edf06a1c7b3e..2f7e1e8ddbb2543af06f4ddd76463abbf7af3bb7 100755 --- a/brian_dashboard_manager/dashboards/peers.json +++ b/brian_dashboard_manager/dashboards/peers.json @@ -21,7 +21,7 @@ "asDropdown": true, "icon": "external link", "tags": [ - "copernicus" + "peer-aggregate" ], "title": "Aggregates", "type": "dashboards" diff --git a/brian_dashboard_manager/grafana/folder.py b/brian_dashboard_manager/grafana/folder.py index a67945eb30a8d64a323683b7ea39048903d5642b..5343768262c423fe06b36ed4160bc3f5f58447c6 100644 --- a/brian_dashboard_manager/grafana/folder.py +++ b/brian_dashboard_manager/grafana/folder.py @@ -19,13 +19,24 @@ def delete_folder(request: TokenRequest, title=None, uid=None): :return: True if folder is considered deleted, False otherwise """ if uid: - r = request.delete(f'api/folders/{uid}').json() + try: + r = request.delete(f'api/folders/{uid}').json() + except HTTPError: + logger.exception(f'Error when deleting folder: {uid}') + return False return r is not None else: folder = find_folder(request, title, False) + if folder is None: return True - r = request.delete(f'api/folders/{folder.get("uid")}').json() + try: + r = request.delete(f'api/folders/{folder.get("uid")}') + r = r.json() + except HTTPError: + logger.exception(f'Error when deleting folder: {title}') + return False + logger.info(f'Deleted folder: {title}') return r is not None @@ -48,8 +59,10 @@ def find_folder(request: TokenRequest, title, create=True): folder = None if not folder and create: - logger.info(f'Created folder: {title}') folder = create_folder(request, title) + if not folder: + return None + logger.info(f'Created folder: {title}') return folder @@ -61,7 +74,14 @@ def get_folders(request: TokenRequest): :param request: TokenRequest object :return: list of folder definitions """ - return request.get('api/folders').json() + try: + r = request.get('api/folders') + folders = r.json() + except HTTPError: + logger.exception('Error when getting Grafana folders:') + return [] + + return folders def create_folder(request: TokenRequest, title): diff --git a/brian_dashboard_manager/grafana/provision.py b/brian_dashboard_manager/grafana/provision.py index 3d71d8722b188566c7535158ee363a4e1d19dc9b..3dd9481968d2cf434102273c373bc1cb92f1a189 100644 --- a/brian_dashboard_manager/grafana/provision.py +++ b/brian_dashboard_manager/grafana/provision.py @@ -161,12 +161,22 @@ AGG_DASHBOARDS = { 'dashboard_name': 'CAE1', 'interfaces': [] }, + 'IC1': { + 'tag': ['ic1', 'peer-aggregate'], + 'dashboard_name': 'IC-1', + 'interfaces': [] + }, 'COPERNICUS': { - 'tag': ['copernicus', 'services'], + 'tag': ['copernicus', 'services', 'peer-aggregate'], 'dashboard_name': 'COPERNICUS', 'group_by': 'location', 'interfaces': [] - } + }, + 'ANA': { + 'tag': ['ana', 'peer-aggregate'], + 'dashboard_name': 'ANA', + 'interfaces': [] + }, } @@ -478,6 +488,8 @@ def _provision_gws_direct(config, org_config, ds_name, token): delete_folder(token, title=folder_name) else: folder = find_folder(token, folder_name) + if not folder: + logger.error(f'Folder {folder_name} not found') with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor: gws_data = get_gws_direct(config['inventory_provider']) provisioned = [] diff --git a/brian_dashboard_manager/inventory_provider/interfaces.py b/brian_dashboard_manager/inventory_provider/interfaces.py index dcdd6dd332c66b07880b0a5bc17bca6bd831be12..b9f574087e9e33bb30f7763ab006f14b953079e0 100644 --- a/brian_dashboard_manager/inventory_provider/interfaces.py +++ b/brian_dashboard_manager/inventory_provider/interfaces.py @@ -1,8 +1,9 @@ -from enum import Enum, auto import requests import logging import jsonschema +from requests.exceptions import HTTPError +from enum import Enum, auto from functools import reduce logger = logging.getLogger(__name__) @@ -39,7 +40,9 @@ class BRIAN_DASHBOARDS(Enum): GWS_UPSTREAMS = auto() LHCONE = auto() CAE1 = auto() + IC1 = auto() COPERNICUS = auto() + ANA = auto() # NREN customer NREN = auto() @@ -300,9 +303,14 @@ def _get_ip_info(host): prev[router_name] = router return prev - r = requests.get(f'{host}/data/interfaces') - r.raise_for_status() - interfaces = r.json() + try: + r = requests.get(f'{host}/data/interfaces') + r.raise_for_status() + interfaces = r.json() + except HTTPError: + logger.exception('Failed to get IP info') + interfaces = [] + jsonschema.validate(interfaces, ROUTER_INTERFACES_SCHEMA) return reduce(reduce_func, interfaces, {}) @@ -316,8 +324,13 @@ def get_interfaces(host): """ r = requests.get(f'{host}/poller/interfaces') - r.raise_for_status() - interfaces = r.json() + try: + r.raise_for_status() + interfaces = r.json() + except HTTPError: + logger.exception('Failed to get interfaces') + interfaces = [] + jsonschema.validate(interfaces, INTERFACE_LIST_SCHEMA) ip_info = _get_ip_info(host) @@ -336,6 +349,7 @@ def get_interfaces(host): interface['ipv4'] = ipv4 interface['ipv6'] = ipv6 return interface + filtered = filter(lambda i: len(i['dashboards']) > 0, interfaces) enriched = list(map(enrich, filtered)) return enriched @@ -351,8 +365,13 @@ def get_gws_direct(host): """ r = requests.get(f'{host}/poller/gws/direct') - r.raise_for_status() - interfaces = r.json() + try: + r.raise_for_status() + interfaces = r.json() + except HTTPError: + logger.exception('Failed to get GWS direct data') + interfaces = [] + jsonschema.validate(interfaces, GWS_DIRECT_DATA_SCHEMA) return interfaces @@ -364,10 +383,13 @@ def get_gws_indirect(host): :param host: Hostname to perform the request to. :return: GWS Indirect data """ - - r = requests.get(f'{host}/poller/gws/indirect') - r.raise_for_status() - interfaces = r.json() + try: + r = requests.get(f'{host}/poller/gws/indirect') + r.raise_for_status() + interfaces = r.json() + except HTTPError: + logger.exception('Failed to get GWS indirect data') + interfaces = [] return interfaces @@ -378,9 +400,13 @@ def get_eumetsat_multicast_subscriptions(host): :param host: Hostname to perform the request to. :return: EUMETSAT multicast subscriptions """ + try: + r = requests.get(f'{host}/poller/eumetsat-multicast') + r.raise_for_status() + data = r.json() + except HTTPError: + logger.exception('Failed to get EUMETSAT multicast subscriptions') + data = [] - r = requests.get(f'{host}/poller/eumetsat-multicast') - r.raise_for_status() - data = r.json() jsonschema.validate(data, MULTICAST_SUBSCRIPTION_LIST_SCHEMA) return data diff --git a/brian_dashboard_manager/services/api.py b/brian_dashboard_manager/services/api.py index fbefac477b8e208250db42e7431ab28efdbb724e..734819db09d28ec8b69890439b764860cf4bb6e9 100644 --- a/brian_dashboard_manager/services/api.py +++ b/brian_dashboard_manager/services/api.py @@ -1,11 +1,18 @@ +import logging import requests +logger = logging.getLogger(__name__) + def fetch_services(host): """ Fetches the current service state from the Reporting Provider host """ - r = requests.get(f'{host}/scid/current') - r.raise_for_status() - services = r.json() + try: + r = requests.get(f'{host}/scid/current') + r.raise_for_status() + services = r.json() + except requests.exceptions.HTTPError: + logger.exception('Error when fetching services:') + services = [] return services diff --git a/changelog.md b/changelog.md index e6ae78d909e1f9ef446f53e5f8bb876a66d0da0e..ae8b254f28d84155c8e14e1938afbfedf5ce7fe1 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [0.58] - 2024-04-22 +- POL1-418/POL1-804: Add ANA graphs +- POL1-703: Add IC1 aggregate dashboard + ## [0.57] - 2024-02-27 - Fix layout for Grafana 10.3.3 diff --git a/setup.py b/setup.py index bd050f0ebf7102e20d20cf7f0361cc29f32b48f6..0ad12bd7d859b8e523f5235cd91aba929d47a677 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='brian-dashboard-manager', - version="0.57", + version="0.58", author='GEANT', author_email='swd@geant.org', description='',