diff --git a/README.md b/README.md index c72e501aa07c52a1e6711c91f39c872eb9d2df6e..e02b06014b530972ce2ccaf8b0a4ffe2d8c1a69a 100644 --- a/README.md +++ b/README.md @@ -379,7 +379,7 @@ Any non-empty responses are JSON formatted messages. The response will be formatted according to the following syntax: ```json - { + { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", @@ -428,10 +428,20 @@ Any non-empty responses are JSON formatted messages. "required": ["name", "description", "as"], "additionalProperties": False }, - "ix-public-peer-group": { + "ix-public-peer-list": { "type": "array", "items": {"$ref": "#/definitions/ip-address"} }, + "ix-public-peer-info": { + "type": "object", + "properties": { + "peer": {"$ref": "#/definitions/ix-public-peer"}, + "group": {"$ref": "#/definitions/ix-public-peer-list"}, + "router": {"$ref": "#/definitions/ix-public-peer-list"} + }, + "required": ["peer", "group", "router"], + "additionalProperties": False + }, "interface-info": { "type": "object", "properties": { @@ -462,9 +472,7 @@ Any non-empty responses are JSON formatted messages. "type": "object", "properties": { - "ix-public-peer-info": {"$ref": "#/definitions/ix-public-peer"}, - "ix-public-peer-group": { - "$ref": "#/definitions/ix-public-peer-group"}, + "ix-public-peer-info": {"$ref": "#/definitions/ix-public-peer-info"}, "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"}, "interfaces": { "type": "array", diff --git a/changelog b/changelog index 348bc6ac1eaa3a2182fcc12432df42ed9a06a664..761fb78d7c6bee8e5c22a24d0357c143665fc476 100644 --- a/changelog +++ b/changelog @@ -24,6 +24,6 @@ classifier metadata api read snmp community string from netconf derive active router list from junosspace - cache ix public & vpn rr peers + read most live info from netconf + precompute cached list of ix public & vpn rr peers use external logging config file - added utilities for the test environment diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py index 5a0cf4d53c5063504966df0b799c6eb7430042fc..4d156f7fb06370ac6357d28e875de07dc12e1940 100644 --- a/inventory_provider/routes/classifier.py +++ b/inventory_provider/routes/classifier.py @@ -8,6 +8,31 @@ from inventory_provider.routes import common routes = Blueprint("inventory-data-classifier-support-routes", __name__) +class ClassifierRequestError(Exception): + status_code = 500 + + def __init__(self): + super().__init__() + self.message = "Unclassified Internal Error" + + +class ClassifierProcessingError(ClassifierRequestError): + status_code = 422 + + def __init__(self, message, status_code=None): + super().__init__() + self.message = str(message) + if status_code is not None: + self.status_code = status_code + + +@routes.errorhandler(ClassifierRequestError) +def handle_request_error(error): + return Response( + response=error.message, + status=error.status_code) + + @routes.route("/trap-metadata/<source_equipment>/<path:interface>", methods=['GET', 'POST']) @common.require_accepts_json @@ -46,29 +71,50 @@ def get_trap_metadata(source_equipment, interface): return Response(result, mimetype="application/json") -def ix_peering_group(address, description): +def ix_peering_info(peer_info): """ TODO: this is probably the least efficient way of doing this (if it's a problem, pre-compute these lists) - :param ix_public_peer_info: ix public peer info loaded for address + :param peer_info: an element from ix_public_peer:address :return: """ + result = { + 'peer': peer_info, + 'group': [], + 'router': [] + } + + try: + address = ipaddress.ip_address(peer_info['name']) + except ValueError: + raise ClassifierProcessingError( + 'unable to parse %r as an ip address' % address) + + description = peer_info['description'] + assert description is not None # sanity + protocol = type(address).__name__ keyword = description.split(' ')[0] # regex needed??? (e.g. tabs???) r = common.get_redis() for k in r.keys('ix_public_peer:*'): - peer = r.get(k.decode('utf-8')).decode('utf-8') - peer = json.loads(peer) - assert peer['description'] is not None # sanity: as above... - if not peer['description'].startswith(keyword): + other = r.get(k.decode('utf-8')).decode('utf-8') + other = json.loads(other) + + if other['router'] == peer_info['router']: + result['router'].append(other['name']) + + assert other['description'] is not None # sanity: as above... + if not other['description'].startswith(keyword): continue - peer_address = ipaddress.ip_address(peer['name']) + peer_address = ipaddress.ip_address(other['name']) if protocol == type(peer_address).__name__: - yield peer['name'] + result['group'].append(other['name']) + + return result def find_interfaces(address): @@ -88,12 +134,19 @@ def find_interfaces(address): yield info -def find_interfaces_and_services(address): +def find_interfaces_and_services(address_str): """ - :param address: an ipaddress object + :param address_str: an ipaddress object :return: """ + + try: + address = ipaddress.ip_address(address_str) + except ValueError: + raise ClassifierProcessingError( + 'unable to parse %r as an ip address' % address_str) + r = common.get_redis() for interface in find_interfaces(address): @@ -124,31 +177,20 @@ def peer_info(address): if result: result = result.decode('utf-8') else: - try: - address_obj = ipaddress.ip_address(address) - except ValueError: - return Response( - response='unable to parse %r as an ip address' % address, - status=422, - mimetype="text/html") result = {} info = r.get('ix_public_peer:%s' % address) if info: info = info.decode('utf-8') - result['ix-public-peer-info'] = json.loads(info) - description = result['ix-public-peer-info']['description'] - assert description is not None # sanity - result['ix-public-peer-group'] = list( - ix_peering_group(address_obj, description)) + result['ix-public-peer-info'] = ix_peering_info(json.loads(info)) info = r.get('vpn_rr_peer:%s' % address) if info: info = info.decode('utf-8') result['vpn-rr-peer-info'] = json.loads(info) - interfaces = list(find_interfaces_and_services(address_obj)) + interfaces = list(find_interfaces_and_services(address)) if interfaces: result['interfaces'] = interfaces diff --git a/test/test_classifier_routes.py b/test/test_classifier_routes.py index 2382d76fc01e3c9f95ccbe9fad460a4665dbe1a4..308a725762c796fb715ddaadb7644f736dfb69b7 100644 --- a/test/test_classifier_routes.py +++ b/test/test_classifier_routes.py @@ -23,8 +23,7 @@ def test_trap_metadata(client_with_mocked_data): VPN_RR_PEER_INFO_KEYS = {'vpn-rr-peer-info'} -IX_PUBLIC_PEER_INFO_KEYS = { - 'ix-public-peer-info', 'ix-public-peer-group', 'interfaces'} +IX_PUBLIC_PEER_INFO_KEYS = {'ix-public-peer-info', 'interfaces'} @pytest.mark.parametrize('peer_address,expected_response_keys', [ @@ -85,10 +84,20 @@ def test_peer_info( "required": ["name", "description", "as"], "additionalProperties": False }, - "ix-public-peer-group": { + "ix-public-peer-list": { "type": "array", "items": {"$ref": "#/definitions/ip-address"} }, + "ix-public-peer-info": { + "type": "object", + "properties": { + "peer": {"$ref": "#/definitions/ix-public-peer"}, + "group": {"$ref": "#/definitions/ix-public-peer-list"}, + "router": {"$ref": "#/definitions/ix-public-peer-list"} + }, + "required": ["peer", "group", "router"], + "additionalProperties": False + }, "interface-info": { "type": "object", "properties": { @@ -119,9 +128,8 @@ def test_peer_info( "type": "object", "properties": { - "ix-public-peer-info": {"$ref": "#/definitions/ix-public-peer"}, - "ix-public-peer-group": { - "$ref": "#/definitions/ix-public-peer-group"}, + "ix-public-peer-info": { + "$ref": "#/definitions/ix-public-peer-info"}, "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"}, "interfaces": { "type": "array",