diff --git a/inventory_provider/__init__.py b/inventory_provider/__init__.py index a8adb17fc82ed6aca7c697d723990e18c98cb710..5de6a3115f4d1af8539195df786e39e9e0d1906b 100644 --- a/inventory_provider/__init__.py +++ b/inventory_provider/__init__.py @@ -29,6 +29,9 @@ def create_app(): from inventory_provider.routes import classifier app.register_blueprint(classifier.routes, url_prefix='/classifier') + from inventory_provider.routes import poller + app.register_blueprint(poller.routes, url_prefix='/poller') + if "SETTINGS_FILENAME" not in os.environ: assert False, \ "environment variable SETTINGS_FILENAME' must be defined" diff --git a/inventory_provider/routes/common.py b/inventory_provider/routes/common.py new file mode 100644 index 0000000000000000000000000000000000000000..857d644e3e30b3704e58c3f627c93ff2a89cb9a8 --- /dev/null +++ b/inventory_provider/routes/common.py @@ -0,0 +1,34 @@ +import functools + +from flask import request, Response, current_app +import redis + + +def require_accepts_json(f): + """ + used as a route handler decorator to return an error + unless the request allows responses with type "application/json" + :param f: the function to be decorated + :return: the decorated function + """ + @functools.wraps(f) + def decorated_function(*args, **kwargs): + # TODO: use best_match to disallow */* ...? + if not request.accept_mimetypes.accept_json: + return Response( + response="response will be json", + status=406, + mimetype="text/html") + return f(*args, **kwargs) + return decorated_function + + +def redis_connection(): + """ + just a common place for acquiring a redis connection + :return: a redis connection + """ + redis_config = current_app.config["INVENTORY_PROVIDER_CONFIG"]["redis"] + return redis.StrictRedis( + host=redis_config["hostname"], + port=redis_config["port"]) diff --git a/inventory_provider/routes/poller.py b/inventory_provider/routes/poller.py new file mode 100644 index 0000000000000000000000000000000000000000..962660e63122e1fea2fe185c02f72404b804e65d --- /dev/null +++ b/inventory_provider/routes/poller.py @@ -0,0 +1,74 @@ +import json + +from flask import Blueprint, Response, jsonify +from lxml import etree +from inventory_provider import juniper +from inventory_provider.routes import common + +routes = Blueprint('poller-support-routes', __name__) + + +@routes.route('/interfaces/<hostname>', methods=['GET', 'POST']) +@common.require_accepts_json +def poller_interface_oids(hostname): + r = common.redis_connection() + + netconf_string = r.hget(hostname, 'netconf') + if not netconf_string: + return Response( + response='no netconf available info for %r' % hostname, + status=404, + mimetype='text/html') + + snmp_data_string = r.hget(hostname, 'snmp-interfaces') + if not snmp_data_string: + return Response( + response='no snmp available info for '%r % hostname, + status=404, + mimetype='text/html') + + def _ifc_name(ifc): + if 'v4InterfaceName' in ifc: + return ifc['v4InterfaceName'] + if 'v6InterfaceName' in ifc: + return ifc['v6InterfaceName'] + assert False, 'sanity failure: no interface name found' + + snmp_indexes = dict([ + (_ifc_name(ifc), int(ifc['index'])) for ifc in + json.loads(snmp_data_string.decode('utf-8')) + ]) + + interfaces = list(juniper.list_interfaces( + etree.XML(netconf_string.decode('utf-8')))) + + if not interfaces: + return Response( + response='no interfaces found for %r' % hostname, + status=404, + mimetype='text/html') + + result = [] + for ifc in interfaces: + if not ifc['description']: + continue + + ifc_data = { + 'name': ifc['name'], + 'snmp-index': snmp_indexes.get(ifc['name'], None), + 'description': ifc['description'], + 'circuits': [] + } + + circuits = r.hget( + 'interface_services', '%s::%s' % (hostname, ifc['name'])) + if circuits: + ifc_data['circuits'] = [ + {'type': c['circuit_type'], 'id': c['id']} + for c in json.loads(circuits.decode('utf-8')) + + ] + + result.append(ifc_data) + + return jsonify(result)