diff --git a/README.md b/README.md index e9257eace8b90d53a8a1b0222940ce64cce0c4c6..93ef44eea42beb5978bfc3bef875c0abf6f8551e 100644 --- a/README.md +++ b/README.md @@ -606,17 +606,21 @@ Any non-empty responses are JSON formatted messages. * valid values: ```json { - "$schema": "http://json-schema.org/draft-07/schema#", + "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "name": { + "definitions": { + "ip-address": { "type": "string", "oneOf": [ {"pattern": r'^(\d+\.){3}\d+$'}, {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'} ] - }, + } + }, + + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/ip-address"}, "description": {"type": "string"}, "as": { "type": "object", @@ -632,3 +636,32 @@ Any non-empty responses are JSON formatted messages. "additionalProperties": False } ``` + +`vpn_rr_peers/<address>` + * key examples + * `ix_public_peer:193.203.0.203` + * valid values: + ```json + { + "$schema": "http://json-schema.org/draft-07/schema#", + + "definitions": { + "ip-address": { + "type": "string", + "oneOf": [ + {"pattern": r'^(\d+\.){3}\d+$'}, + {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'} + ] + } + }, + + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/ip-address"}, + "description": {"type": "string"}, + "peer-as": {"type": "integer"} + }, + "required": ["name", "description"], + "additionalProperties": False + } + ``` diff --git a/changelog b/changelog index 0338165edca81401557251b08794466e7366b165..c5d29d948e99d30748ddd49ad8a061404964b093 100644 --- a/changelog +++ b/changelog @@ -24,4 +24,4 @@ classifier metadata api read snmp community string from netconf derive active router list from junosspace - cache ix public peers + cache ix public & vpn rr peers diff --git a/inventory_provider/juniper.py b/inventory_provider/juniper.py index 6208546adef774b4b95635174029ceee7014f380..741ef352381f1b6213e9bfc936fcbe8ac0c3ee61 100644 --- a/inventory_provider/juniper.py +++ b/inventory_provider/juniper.py @@ -275,6 +275,21 @@ def ix_public_peers(netconf_config): } +def vpn_rr_peers(netconf_config): + for r in netconf_config.xpath( + '//configuration/logical-systems[name/text()="VRR"]/' + '/protocols/bgp/' + 'group[name/text()="VPN-RR" or name/text()="VPN-RR-INTERNAL"]/' + 'neighbor'): + neighbor = { + 'name': ipaddress.ip_address(r.find('name').text).exploded, + 'description': r.find('description').text, + } + peer_as = r.find('peer-as') + if peer_as is not None: + neighbor['peer-as'] = int(r.find('peer-as').text) + yield neighbor + # note for enabling vrr data parsing ... # def fetch_vrr_config(hostname, ssh_params): # diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py index 66321d66b82efc5fdc9510ff163b7ab7760e7466..73b4468a46679d34697985efd34578f455cc6a88 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -255,6 +255,23 @@ def refresh_ix_public_peers(hostname, netconf): json.dumps(peer)) +def refresh_vpn_rr_peers(hostname, netconf): + task_logger = logging.getLogger(constants.TASK_LOGGER_NAME) + task_logger.debug( + 'removing cached vpn rr for %r' % hostname) + r = get_redis(InventoryTask.config) + for k in r.keys('vpn_rr_peer:*'): + value = json.loads(r.get(k.decode('utf-8')).decode('utf-8')) + if value['router'] == hostname: + r.delete(k) + + for peer in juniper.vpn_rr_peers(netconf): + peer['router'] = hostname + r.set( + 'vpn_rr_peer:' + peer['name'], + json.dumps(peer)) + + @app.task def reload_router_config(hostname): task_logger = logging.getLogger(constants.TASK_LOGGER_NAME) @@ -263,11 +280,12 @@ def reload_router_config(hostname): netconf_refresh_config.apply(hostname) netconf_doc = load_netconf_data(hostname) - if not netconf_doc: + if netconf_doc is None: task_logger.error('no netconf data available for %r' % hostname) else: refresh_ix_public_peers(hostname, netconf_doc) + refresh_vpn_rr_peers(hostname, netconf_doc) community = juniper.snmp_community_string(netconf_doc) if not community: diff --git a/test/test_ix_public_peers.py b/test/per_router/test_peer_caching.py similarity index 54% rename from test/test_ix_public_peers.py rename to test/per_router/test_peer_caching.py index 75131a9f0c8160b73443f56f9006c57b5571b3e5..3111f1a3297b5a122d5ef95994bf0cf5e5251f44 100644 --- a/test/test_ix_public_peers.py +++ b/test/per_router/test_peer_caching.py @@ -12,14 +12,12 @@ TEST_DATA_DIRNAME = os.path.realpath(os.path.join( 'test', 'data')) -ROUTER_NAME = 'mx1.vie.at.geant.net' - @pytest.fixture -def netconf(): +def netconf(router): netconf_filename = os.path.join( TEST_DATA_DIRNAME, - ROUTER_NAME + '-netconf.xml') + router + '-netconf.xml') doc = etree.parse(netconf_filename) juniper.validate_netconf_config(doc) return doc @@ -30,15 +28,19 @@ def test_ix_public_peers(netconf): schema = { "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "name": { + "definitions": { + "ip-address": { "type": "string", "oneOf": [ {"pattern": r'^(\d+\.){3}\d+$'}, {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'} ] - }, + } + }, + + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/ip-address"}, "description": {"type": "string"}, "as": { "type": "object", @@ -56,3 +58,36 @@ def test_ix_public_peers(netconf): for p in juniper.ix_public_peers(netconf): jsonschema.validate(p, schema) + print(p) + + +def test_vpn_rr_peers(netconf): + + # there are actually no v6 addresses, pattern could be ommitted + # TODO: check if there's a robust justification for this + schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + + "definitions": { + "ip-address": { + "type": "string", + "oneOf": [ + {"pattern": r'^(\d+\.){3}\d+$'}, + {"pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$'} + ] + } + }, + + "type": "object", + "properties": { + "name": {"$ref": "#/definitions/ip-address"}, + "description": {"type": "string"}, + "peer-as": {"type": "integer"} + }, + "required": ["name", "description"], + "additionalProperties": False + } + + for p in juniper.vpn_rr_peers(netconf): + jsonschema.validate(p, schema) + print(p)