lnetd.py 5.13 KiB
"""
LnetD support
=========================
This endpoint is intended for use with LnetD
.. contents:: :local:
/LnetD/interfaces</hostname>
---------------------------------
.. autofunction:: inventory_provider.routes.lnetd.interfaces
"""
import json
import logging
import re
from flask import Blueprint, Response, current_app, request
from inventory_provider import juniper
from inventory_provider.routes import common
from inventory_provider.routes.common import _ignore_cache_or_retrieve
logger = logging.getLogger(__name__)
routes = Blueprint('lnetd-support-routes', __name__)
INTERFACE_LIST_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'definitions': {
'ipv4-interface-address': {
'type': 'string',
'pattern': r'^(\d+\.){3}\d+/\d+$'
},
'ipv6-interface-address': {
'type': 'string',
'pattern': r'^[a-f\d:]+/\d+$'
},
'interface': {
'type': 'object',
'properties': {
'hostname': {'type': 'string'},
'interface': {'type': 'string'},
'ifIndex': {'type': 'integer', 'minimum': 1},
'ipv4': {
'type': 'array',
'items': {'$ref': '#/definitions/ipv4-interface-address'}
},
'ipv6': {
'type': 'array',
'items': {'$ref': '#/definitions/ipv6-interface-address'}
},
},
'required': ['hostname', 'interface', 'ifIndex', 'ipv4', 'ipv6'],
'additionalProperties': False
},
},
'type': 'array',
'items': {'$ref': '#/definitions/interface'}
}
def _add_snmp_indexes(interfaces, hostname=None):
"""
generator that adds snmp ifIndex to each interface, if available
(only interfaces with an snmp index are yielded)
:param interfaces: result of _load_interfaces
:param hostname: hostname or None for all
:return: generator that yields interfaces with 'ifIndex' added
"""
snmp_indexes = common.load_snmp_indexes(hostname)
for ifc in interfaces:
hostname = ifc['hostname']
if hostname not in snmp_indexes:
continue
interface = ifc['interface']
if interface not in snmp_indexes[hostname]:
continue
ifc['ifIndex'] = snmp_indexes[hostname][interface]['index']
yield ifc
def _load_router_interfaces(hostname):
"""
loads basic interface data for production & lab routers
:param hostname:
:return:
"""
def _load_docs(key_pattern):
m = re.match(r'^(.*netconf:).+', key_pattern)
assert m # sanity
key_prefix_len = len(m.group(1))
assert key_prefix_len >= len('netconf:') # sanity
for doc in common.load_xml_docs(
config_params=current_app.config['INVENTORY_PROVIDER_CONFIG'],
key_pattern=key_pattern,
num_threads=10):
router = doc['key'][key_prefix_len:]
for ifc in juniper.list_interfaces(doc['value']):
if not ifc['description']:
continue
# seems we're only interested in interfaces with addresses?
if not ifc['ipv4'] and not ifc['ipv6']:
continue
yield {
'hostname': router,
'interface': ifc['name'],
'ipv4': ifc['ipv4'],
'ipv6': ifc['ipv6']
}
base_key_pattern = f'netconf:{hostname}*' if hostname else 'netconf:*'
yield from _load_docs(base_key_pattern)
yield from _load_docs(f'lab:{base_key_pattern}')
def _load_interfaces(hostname=None):
"""
prepares the result of a call to /interfaces
:param hostname: hostname or None for all
:return: generator yielding interface elements
"""
basic_interfaces = _load_router_interfaces(hostname)
return _add_snmp_indexes(basic_interfaces, hostname)
@routes.route("/interfaces", methods=['GET', 'POST'])
@routes.route('/interfaces/<hostname>', methods=['GET', 'POST'])
@common.require_accepts_json
def interfaces(hostname=None):
"""
Handler for `/LnetD/interfaces` and
`/LnetD/interfaces/<hostname>`
which returns information for either all interfaces
or those on the requested hostname.
.. asjson::
inventory_provider.routes.lnetd.INTERFACE_LIST_SCHEMA
:param hostname: optional, if present should be a router hostname
:return:
"""
cache_key = f'classifier-cache:lnetd-interfaces:{hostname}' \
if hostname else 'classifier-cache:lnetd-interfaces:all'
r = common.get_current_redis()
result = _ignore_cache_or_retrieve(request, cache_key, r)
if not result:
result = list(_load_interfaces(hostname))
if not result:
return Response(
response='no interfaces found',
status=404,
mimetype='text/html')
result = json.dumps(result)
# cache this data for the next call
r.set(cache_key, result.encode('utf-8'))
return Response(result, mimetype="application/json")