Skip to content
Snippets Groups Projects
data.py 5.22 KiB
import json
import logging
import re

from flask import Blueprint, jsonify, Response, current_app, request

from inventory_provider.routes import common
from inventory_provider.routes.common import _ignore_cache_or_retrieve

logger = logging.getLogger(__name__)

routes = Blueprint("inventory-data-query-routes", __name__)

ROUTERS_RESPONSE_SCHEMA = {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "array",
    "items": {"type": "string"}
}

ROUTER_INTERFACES_SCHEMA = {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "description": {"type": "string"},
            "router": {"type": "string"},
            "bundle": {
                "type": "array",
                "items": {"type": "string"}
            },
            "ipv4": {
                "type": "array",
                "items": {"type": "string"}
            },
            "ipv6": {
                "type": "array",
                "items": {"type": "string"}
            },
            # only if not the default
            "logical-system": {"type": "string"},
        },
        "required": ["name", "description", "ipv4", "router", "ipv6"],
        "additionalProperties": False
    }
}

POP_RESPONSE_SCHEMA = {
    "$schema": "http://json-schema.org/draft-07/schema#",

    "definitions": {
        "pop-info": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "abbreviation": {"type": "string"},
                "country": {"type": "string"},
                "city": {"type": "string"},
                "longitude": {"type": "number"},
                "latitude": {"type": "number"}
            },
            "required": [
                "name",
                "abbreviation",
                "country",
                "city",
                "longitude",
                "latitude"
            ],
            "additionalProperties": False
        },
        "equipment-info": {
            "type": "object",
            "properties": {
                'equipment-name': {"type": "string"},
                'status': {"type": "string"},
                'pop': {"$ref": "#/definitions/pop-info"}
            },
            "required": [
                "equipment-name",
                "status",
                "pop"
            ],
            "additionalProperties": False

        }
    },

    "type": "array",
    "items": {"$ref": "#/definitions/equipment-info"}
}


@routes.after_request
def after_request(resp):
    return common.after_request(resp)


@routes.route("/routers", methods=['GET', 'POST'])
@common.require_accepts_json
def routers():
    """
    Handler for `/data/routers`.

    The response will be a list of router hostnames
    for which information is available and will formatted
    according to the following schema:

    .. asjson:: inventory_provider.routes.data.ROUTERS_RESPONSE_SCHEMA

    :return:
    """
    r = common.get_current_redis()
    result = []
    for k in r.keys('netconf:*'):
        m = re.match('^netconf:(.+)$', k.decode('utf-8'))
        assert m
        result.append(m.group(1))
    return jsonify(result)


@routes.route("/interfaces", methods=['GET', 'POST'])
@routes.route("/interfaces/<hostname>", methods=['GET', 'POST'])
@common.require_accepts_json
def router_interfaces(hostname=None):
    """
    Handler for `/data/interfaces</hostname>`.

    The response will be a list of information about
    the interfaces present on the requested host
    and will be formatted as follows:

    .. asjson:: inventory_provider.routes.data.ROUTER_INTERFACES_SCHEMA

    :param hostname: optional hostname
    :return:
    """
    cache_key = f'classifier-cache:netconf-interfaces:{hostname}' \
        if hostname else 'classifier-cache:netconf-interfaces:all'

    r = common.get_current_redis()

    result = _ignore_cache_or_retrieve(request, cache_key, r)

    if not result:
        key_pattern = f'netconf-interfaces:{hostname}:*' \
            if hostname else 'netconf-interfaces:*'
        config = current_app.config['INVENTORY_PROVIDER_CONFIG']

        result = []
        for ifc in common.load_json_docs(config, key_pattern):
            key_fields = ifc['key'].split(':')
            ifc['value']['router'] = key_fields[1]
            result.append(ifc['value'])

        if not result:
            return Response(
                response="no available interface info for '%s'" % hostname,
                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")


@routes.route("/pop/<equipment_name>", methods=['GET', 'POST'])
@common.require_accepts_json
@common.ims_hostname_decorator('equipment_name')
def equipment_location(equipment_name):
    redis = common.get_current_redis()
    result = redis.get(f'ims:location:{equipment_name}')

    if not result:
        return Response(
            response="no available info for {}".format(equipment_name),
            status=404,
            mimetype="text/html")

    return Response(result, mimetype="application/json")