diff --git a/README.md b/README.md index 5e71995c65bf57fe2dd8d1249d68d87f086c5616..ce5bae612ad114335184d67bb025824aea71cf29 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ Any non-empty responses are JSON formatted messages. The `address` parameter should be the ip address of a remote peer. If this address is found in the system then information about the interface is returned, otherwise - 404 is returned. + 404 is returned (or 422 if the address can't be parsed) The response will be formatted according to the following syntax: @@ -391,6 +391,13 @@ Any non-empty responses are JSON formatted messages. {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'} ] }, + "interface-address": { + "type": "string", + "oneOf": [ + {"pattern": r'^(\d+\.){3}\d+/\d+$'}, + {"pattern": r'^[a-f\d:]+/\d+$'} + ] + }, "vpn-rr-peer": { "type": "object", "properties": { @@ -425,7 +432,19 @@ Any non-empty responses are JSON formatted messages. "type": "array", "items": {"$ref": "#/definitions/ip-address"} }, - + "interface-info": { + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/ip-address"}, + "interface address": { + "$ref": "#/definitions/interface-address"}, + "interface name": {"type": "string"}, + "router": {"type": "string"} + }, + "required": [ + "name", "interface address", "interface name", "router"], + "additionalProperties": False + } }, "type": "object", @@ -433,7 +452,11 @@ Any non-empty responses are JSON formatted messages. "ix-public-peer-info": {"$ref": "#/definitions/ix-public-peer"}, "ix-public-peer-group": { "$ref": "#/definitions/ix-public-peer-group"}, - "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"} + "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"}, + "interfaces": { + "type": "array", + "items": {"$ref": "#/definitions/interface-info"} + } }, "additionalProperties": False } diff --git a/inventory_provider/routes/classifier.py b/inventory_provider/routes/classifier.py index a4e46342066311abaa1f8cfaad4d4ca55b1e4e6c..dde5fa03c85456a91903a11d9571df9a0e00a73b 100644 --- a/inventory_provider/routes/classifier.py +++ b/inventory_provider/routes/classifier.py @@ -46,36 +46,59 @@ def get_trap_metadata(source_equipment, interface): return Response(result, mimetype="application/json") -@routes.route("/peer-info/<address>", methods=['GET', 'POST']) -@common.require_accepts_json -def peer_info(address): +def ix_peering_group(address, description): + """ + TODO: this is probably the least efficient way of doing this + (if it's a problem, pre-compute these lists) - def _related_ix_peers(ix_public_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 + :return: + """ - :param ix_public_peer_info: ix public peer info loaded for address - :return: - """ + protocol = type(address).__name__ + keyword = description.split(' ')[0] # regex needed??? (e.g. tabs???) - protocol = type(ipaddress.ip_address(address)).__name__ + 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): + continue + peer_address = ipaddress.ip_address(peer['name']) + if protocol == type(peer_address).__name__: + yield peer['name'] + + +def find_interfaces(address): + """ + TODO: this is probably the least efficient way of doing this + (if it's a problem, pre-compute these lists) + + :param address: an ipaddress object + :return: + """ + r = common.get_redis() + for k in r.keys('reverse_interface_addresses:*'): + info = r.get(k.decode('utf-8')).decode('utf-8') + info = json.loads(info) + interface = ipaddress.ip_interface(info['interface address']) + if address in interface.network: + yield info - description = ix_public_peer_info['description'] - assert description is not None # sanity: at least empty string - keyword = description.split(' ')[0] # regex needed??? (e.g. tabs???) - r = common.get_redis() +@routes.route("/peer-info/<address>", methods=['GET', 'POST']) +@common.require_accepts_json +def peer_info(address): - 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): - continue - peer_address = ipaddress.ip_address(peer['name']) - if protocol == type(peer_address).__name__: - yield peer['name'] + 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") r = common.get_redis() @@ -85,14 +108,20 @@ def peer_info(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( - _related_ix_peers(result['ix-public-peer-info'])) + ix_peering_group(address_obj, description)) 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(address_obj)) + if interfaces: + result['interfaces'] = interfaces + if not result: return Response( response='no peering info found for %s' % address, diff --git a/test/test_classifier_routes.py b/test/test_classifier_routes.py index 40241caccf010a91144dc329bdf3ceaa5f71b916..e4ec2b5d6e696bd9d130b5e87a0c3ed08cb34b8c 100644 --- a/test/test_classifier_routes.py +++ b/test/test_classifier_routes.py @@ -23,10 +23,11 @@ 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'} +IX_PUBLIC_PEER_INFO_KEYS = { + 'ix-public-peer-info', 'ix-public-peer-group', 'interfaces'} -@pytest.mark.parametrize("peer_address,expected_response_keys", [ +@pytest.mark.parametrize('peer_address,expected_response_keys', [ ('109.105.110.54', VPN_RR_PEER_INFO_KEYS), ('2001:07f8:001c:024a:0000:0000:316e:0001', IX_PUBLIC_PEER_INFO_KEYS), ('2001:07f8:000b:0100:01d1:a5d1:0310:0029', IX_PUBLIC_PEER_INFO_KEYS), @@ -47,6 +48,13 @@ def test_peer_info( {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'} ] }, + "interface-address": { + "type": "string", + "oneOf": [ + {"pattern": r'^(\d+\.){3}\d+/\d+$'}, + {"pattern": r'^[a-f\d:]+/\d+$'} + ] + }, "vpn-rr-peer": { "type": "object", "properties": { @@ -81,7 +89,19 @@ def test_peer_info( "type": "array", "items": {"$ref": "#/definitions/ip-address"} }, - + "interface-info": { + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/ip-address"}, + "interface address": { + "$ref": "#/definitions/interface-address"}, + "interface name": {"type": "string"}, + "router": {"type": "string"} + }, + "required": [ + "name", "interface address", "interface name", "router"], + "additionalProperties": False + } }, "type": "object", @@ -89,7 +109,11 @@ def test_peer_info( "ix-public-peer-info": {"$ref": "#/definitions/ix-public-peer"}, "ix-public-peer-group": { "$ref": "#/definitions/ix-public-peer-group"}, - "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"} + "vpn-rr-peer-info": {"$ref": "#/definitions/vpn-rr-peer"}, + "interfaces": { + "type": "array", + "items": {"$ref": "#/definitions/interface-info"} + } }, "additionalProperties": False } @@ -105,9 +129,15 @@ def test_peer_info( assert set(response_data.keys()) == expected_response_keys -def test_peer_not_found(client_with_mocked_data): - +def test_peer_invalid_address(client_with_mocked_data): rv = client_with_mocked_data.get( '/classifier/peer-info/1.2.3.4.5', headers=DEFAULT_REQUEST_HEADERS) + assert rv.status_code == 422 + + +def test_peer_not_found(client_with_mocked_data): + rv = client_with_mocked_data.get( + '/classifier/peer-info/1.2.3.4', + headers=DEFAULT_REQUEST_HEADERS) assert rv.status_code == 404