import requests import logging import jsonschema from requests.exceptions import HTTPError from enum import Enum, auto from functools import reduce logger = logging.getLogger(__name__) class INTERFACE_TYPES(Enum): UNKNOWN = auto() LOGICAL = auto() PHYSICAL = auto() AGGREGATE = auto() class BRIAN_DASHBOARDS(Enum): CLS = auto() RE_PEER = auto() RE_CUST = auto() GEANTOPEN = auto() GCS = auto() L2_CIRCUIT = auto() LHCONE_PEER = auto() LHCONE_CUST = auto() MDVPN_CUSTOMERS = auto() INFRASTRUCTURE_BACKBONE = auto() IAS_PRIVATE = auto() IAS_PUBLIC = auto() IAS_CUSTOMER = auto() IAS_UPSTREAM = auto() GWS_PHY_UPSTREAM = auto() GBS_10G = auto() # aggregate dashboards CLS_PEERS = auto() IAS_PEERS = auto() GWS_UPSTREAMS = auto() LHCONE = auto() CAE1 = auto() IC1 = auto() COPERNICUS = auto() ANA = auto() # NREN customer NREN = auto() class PORT_TYPES(Enum): ACCESS = auto() SERVICE = auto() UNKNOWN = auto() # only used in INTERFACE_LIST_SCHEMA and sphinx docs _DASHBOARD_IDS = [d.name for d in list(BRIAN_DASHBOARDS)] _PORT_TYPES = [t.name for t in list(PORT_TYPES)] _INTERFACE_TYPES = [i.name for i in list(INTERFACE_TYPES)] ROUTER_INTERFACES_SCHEMA = { "type": "array", "items": { "type": "object", "properties": { "name": {"type": "string"}, "description": {"type": "string"}, "router": {"type": "string"}, "bundle": { "type": "array", "items": {"type": "string"} }, "ipv4": { "type": "array", "items": {"type": "string"} }, "ipv6": { "type": "array", "items": {"type": "string"} }, "logical-system": {"type": "string"}, }, "required": ["name", "router", "ipv4", "ipv6"] } } INTERFACE_LIST_SCHEMA = { 'definitions': { 'service': { 'type': 'object', 'properties': { 'id': {'type': 'integer'}, 'name': {'type': 'string'}, 'type': {'type': 'string'}, 'status': {'type': 'string'}, }, 'required': ['id', 'name', 'type', 'status'] }, 'db_info': { 'type': 'object', 'properties': { 'name': {'type': 'string'}, 'interface_type': {'type': 'string'}, }, 'required': ['name', 'interface_type'] }, 'interface': { 'type': 'object', 'properties': { 'router': {'type': 'string'}, 'name': {'type': 'string'}, 'description': {'type': 'string'}, 'dashboards': { 'type': 'array', 'items': {'type': 'string'} }, 'dashboards_info': { 'type': 'array', 'items': {'$ref': '#/definitions/db_info'} }, 'port_type': {'enum': _PORT_TYPES} }, 'required': [ 'router', 'name', 'description', 'dashboards'] }, }, 'type': 'array', 'items': {'$ref': '#/definitions/interface'} } GWS_DIRECT_DATA_SCHEMA = { 'definitions': { 'oid': { 'type': 'string', 'pattern': r'^(\d+\.)*\d+$' }, 'snmp-v2': { 'type': 'object', 'properties': { 'community': {'type': 'string'} }, 'required': ['community'] }, 'snmp-v3-cred': { 'type': 'object', 'properties': { 'protocol': {'enum': ['MD5', 'DES']}, 'password': {'type': 'string'} }, 'required': ['protocol', 'password'] }, 'snmp-v3': { 'type': 'object', 'properties': { 'sec-name': {'type': 'string'}, 'auth': {'$ref': '#/definitions/snmp-v3-cred'}, 'priv': {'$ref': '#/definitions/snmp-v3-cred'} }, 'required': ['sec-name'] }, 'counter': { 'type': 'object', 'properties': { 'field': { 'enum': [ 'discards_in', 'discards_out', 'errors_in', 'errors_out', 'traffic_in', 'traffic_out' ] }, 'oid': {'$ref': '#/definitions/oid'}, 'snmp': { 'oneOf': [ {'$ref': '#/definitions/snmp-v2'}, {'$ref': '#/definitions/snmp-v3'} ] } }, 'required': ['field', 'oid'] }, 'interface-counters': { 'type': 'object', 'properties': { 'nren': {'type': 'string'}, 'isp': {'type': 'string'}, 'hostname': {'type': 'string'}, 'tag': {'type': 'string'}, 'counters': { 'type': 'array', 'items': {'$ref': '#/definitions/counter'}, 'minItems': 1 }, 'info': {'type': 'string'} }, 'required': ['nren', 'isp', 'hostname', 'tag', 'counters'] } }, 'type': 'array', 'items': {'$ref': '#/definitions/interface-counters'} } MULTICAST_SUBSCRIPTION_LIST_SCHEMA = { 'definitions': { 'ipv4-address': { 'type': 'string', 'pattern': r'^(\d+\.){3}\d+$' }, 'subscription': { 'type': 'object', 'properties': { 'router': {'type': 'string'}, 'subscription': {'$ref': '#/definitions/ipv4-address'}, 'endpoint': {'$ref': '#/definitions/ipv4-address'}, 'oid': { 'type': 'string', 'pattern': r'^(\d+\.)*\d+$' }, 'community': {'type': 'string'} }, 'required': [ 'router', 'subscription', 'endpoint', 'oid', 'community'] }, }, 'type': 'array', 'items': {'$ref': '#/definitions/subscription'} } NREN_REGION_LIST_SCHEMA = { 'definitions': { 'nren_region': { 'type': 'object', 'properties': { 'nren': {'type': 'string'}, 'region': {'type': 'string'} } } }, 'type': 'array', 'items': {'$ref': '#/definitions/nren_region'} } def _get_ip_info(host): """ Get IP information for all interfaces on all routers. :param host: Hostname to perform the request to. :return: A lookup table of the form: { 'router1': { 'interface1': { 'ipv4': [ '62.40.109.193/30' ], 'ipv6': [ '2001:798:cc:1::4a/126' ] }, 'interface2': { 'ipv4': [ '62.40.109.193/30' ], 'ipv6': [ '2001:798:cc:1::4a/126' ] } }, 'router2': { 'interface1': { 'ipv4': [ '62.40.109.193/30' ], 'ipv6': [ '2001:798:cc:1::4a/126' ] }, } } """ def reduce_func(prev, curr): """ Reduce function to build the lookup table. :param prev: The accumulator. The lookup table. :param curr: The current interface. :return: The updated lookup table. """ interface_name = curr.get('name') router_name = curr.get('router') if interface_name and router_name: router = prev.get(router_name, {}) interface = router.get(interface_name, {}) ipv4 = curr.get('ipv4', []) ipv6 = curr.get('ipv6', []) interface['ipv4'] = ipv4 interface['ipv6'] = ipv6 router[interface_name] = interface prev[router_name] = router return prev 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, {}) def get_interfaces(host): """ Get all interfaces that have dashboards assigned to them. :param host: Hostname to perform the request to. :return: A list of interfaces with IP information added, if present. """ r = requests.get(f'{host}/poller/interfaces') 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) def enrich(interface): router_name = interface.get('router') router = ip_info.get(router_name) if not router: return interface ip = router.get(interface['name']) if not ip: return interface ipv4 = ip['ipv4'] ipv6 = ip['ipv6'] interface['ipv4'] = ipv4 interface['ipv6'] = ipv6 return interface filtered = filter(lambda i: len(i['dashboards']) > 0, interfaces) enriched = list(map(enrich, filtered)) return enriched def get_gws_direct(host): """ Get all GWS Direct data. Follows the schema defined in GWS_DIRECT_DATA_SCHEMA. :param host: Hostname to perform the request to. :return: GWS direct data """ r = requests.get(f'{host}/poller/gws/direct') 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 def get_gws_indirect(host): """ Get all GWS Indirect data. :param host: Hostname to perform the request to. :return: GWS Indirect data """ 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 def get_eumetsat_multicast_subscriptions(host): """ Get all EUMETSAT multicast subscriptions. :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 = [] jsonschema.validate(data, MULTICAST_SUBSCRIPTION_LIST_SCHEMA) return data def get_nren_regions(host): """ Get all NREN regions, where specified. :param host: Hostname to perform the request to. :return: A list of NRENs and regions. """ try: r = requests.get(f'{host}/poller/regions') r.raise_for_status() data = r.json() except HTTPError: logger.exception('Failed to get NREN regions') data = [] jsonschema.validate(data, NREN_REGION_LIST_SCHEMA) # ugly hack: if a region isn't on the list of recognised regions, prune it from the region list # TODO: figure out something that isn't this recognised_regions = ['EAP'] data = [d for d in data if d['region'] in recognised_regions] return data