diff --git a/README.md b/README.md index be345ee3961cf134eb1106d2e8c42663f46e70dc..98d04d9c0b589f04b2f69943a0e586e94449120b 100644 --- a/README.md +++ b/README.md @@ -744,3 +744,55 @@ Any non-empty responses are JSON formatted messages. "additionalProperties": False } ``` + +* `reverse_interface_addresses/<address>` + * key examples + * `reverse_interface_addresses:193.203.0.203` + * `reverse_interface_addresses:2001:07f8:00a0:0000:0000:5926:0000:0002` + * valid values: + ```json + { + "$schema": "http://json-schema.org/draft-07/schema#", + + "definitions": { + "v4a": { + "type": "string", + "pattern": r'^(\d+\.){3}\d+$' + }, + "v6a": { + "type": "string", + "pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$' + }, + "v4i": { + "type": "string", + "pattern": r'^(\d+\.){3}\d+/\d+$' + }, + "v6i": { + "type": "string", + "pattern": r'^[a-f\d:]+/\d+$' + } + }, + + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "oneOf": [ + {"$ref": "#/definitions/v4a"}, + {"$ref": "#/definitions/v6a"} + ] + }, + "interface address": { + "oneOf": [ + {"$ref": "#/definitions/v4i"}, + {"$ref": "#/definitions/v6i"} + ] + }, + "interface name": {"type": "string"}, + }, + "required": ["name", "interface address", "interface name"], + "additionalProperties": False + } + } + ``` diff --git a/inventory_provider/juniper.py b/inventory_provider/juniper.py index c0b0f87b138a090b5ad6981b88880a27bfcd8d96..468eef553af60b98d553a4c9b756b1db5069536f 100644 --- a/inventory_provider/juniper.py +++ b/inventory_provider/juniper.py @@ -292,6 +292,22 @@ def vpn_rr_peers(netconf_config): neighbor['peer-as'] = int(r.find('peer-as').text) yield neighbor + +def interface_addresses(netconf_config): + """ + yields a list of all distinct interface addresses + :param netconf_config: + :return: + """ + for ifc in list_interfaces(netconf_config): + for address in ifc['ipv4'] + ifc['ipv6']: + yield { + "name": ipaddress.ip_interface(address).ip.exploded, + "interface address": address, + "interface name": ifc['name'] + } + + # 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 ee817b29c3d1084490b6316e3d42f33975347696..075660cbecc731b4e5cda344d437a9ebaa7c01e2 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -249,7 +249,7 @@ def load_netconf_data(hostname): r = get_redis(InventoryTask.config) netconf = r.get('netconf:' + hostname) if not netconf: - return None + raise InventoryTaskError('no netconf data found for %r' % hostname) return etree.fromstring(netconf.decode('utf-8')) @@ -298,6 +298,13 @@ def refresh_vpn_rr_peers(hostname, netconf): juniper.vpn_rr_peers(netconf)) +def refresh_interface_address_lookups(hostname, netconf): + _refresh_peers( + hostname, + 'reverse_interface_addresses', + juniper.interface_addresses(netconf)) + + @app.task(base=InventoryTask, bind=True) def reload_router_config(self, hostname): logger = logging.getLogger(__name__) @@ -313,20 +320,19 @@ def reload_router_config(self, hostname): # get the timestamp for the current netconf data current_netconf_timestamp = None - netconf_doc = load_netconf_data(hostname) - if netconf_doc: + try: + netconf_doc = load_netconf_data(hostname) current_netconf_timestamp \ = juniper.netconf_changed_timestamp(netconf_doc) logger.debug( 'current netconf timestamp: %r' % current_netconf_timestamp) + except InventoryTaskError: + pass # ok at this point if not found # load new netconf data netconf_refresh_config.apply(args=[hostname]) netconf_doc = load_netconf_data(hostname) - if netconf_doc is None: - raise InventoryTaskError( - 'failure loading netconf data for %r' % hostname) # return if new timestamp is the same as the original timestamp new_netconf_timestamp = juniper.netconf_changed_timestamp(netconf_doc) @@ -352,6 +358,7 @@ def reload_router_config(self, hostname): }) refresh_ix_public_peers(hostname, netconf_doc) refresh_vpn_rr_peers(hostname, netconf_doc) + refresh_interface_address_lookups(hostname, netconf_doc) clear_cached_classifier_responses(hostname) # load snmp indexes diff --git a/test/per_router/test_juniper_data.py b/test/per_router/test_juniper_data.py index 2574209e2f74c9ea19ee67dab924c7415d415ce1..721477640e199595517261c8107d954c39160de5 100644 --- a/test/per_router/test_juniper_data.py +++ b/test/per_router/test_juniper_data.py @@ -102,3 +102,53 @@ def test_bgp_list(netconf_doc): def test_snmp_community_string(mocked_netifaces, netconf_doc): assert juniper.snmp_community_string(netconf_doc) == '0pBiFbD' + + +def test_interface_addresses_list(netconf_doc): + schema = { + "$schema": "http://json-schema.org/draft-07/schema#", + + "definitions": { + "v4a": { + "type": "string", + "pattern": r'^(\d+\.){3}\d+$' + }, + "v6a": { + "type": "string", + "pattern": r'^([a-f\d]{4}:){7}[a-f\d]{4}$' + }, + "v4i": { + "type": "string", + "pattern": r'^(\d+\.){3}\d+/\d+$' + }, + "v6i": { + "type": "string", + "pattern": r'^[a-f\d:]+/\d+$' + } + }, + + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "oneOf": [ + {"$ref": "#/definitions/v4a"}, + {"$ref": "#/definitions/v6a"} + ] + }, + "interface address": { + "oneOf": [ + {"$ref": "#/definitions/v4i"}, + {"$ref": "#/definitions/v6i"} + ] + }, + "interface name": {"type": "string"}, + }, + "required": ["name", "interface address", "interface name"], + "additionalProperties": False + } + } + + addresses = list(juniper.interface_addresses(netconf_doc)) + jsonschema.validate(addresses, schema)