""" MSR Support Endpoints ========================= These endpoints are intended for use by MSR. .. contents:: :local: /msr/access-services --------------------------------- .. autofunction:: inventory_provider.routes.msr.access_services /msr/bgp/logical-systems ------------------------------------- .. autofunction:: inventory_provider.routes.msr.get_logical_systems /msr/bgp/logical-system-peerings</name> ------------------------------------------ .. autofunction:: inventory_provider.routes.msr.logical_system_peerings /msr/bgp/groups ------------------------------------- .. autofunction:: inventory_provider.routes.msr.get_peering_groups /msr/bgp/group-peerings</name> ------------------------------------- .. autofunction:: inventory_provider.routes.msr.bgp_group_peerings /msr/bgp/routing-instances ------------------------------------- .. autofunction:: inventory_provider.routes.msr.get_peering_routing_instances /msr/bgp/routing-instance-peeringns</name> -------------------------------------------- .. autofunction:: inventory_provider.routes.msr.bgp_routing_instance_peerings helpers ------------------------------------- .. autofunction:: inventory_provider.routes.msr._handle_peering_group_list_request .. autofunction:: inventory_provider.routes.msr._handle_peering_group_request """ import itertools import json from flask import Blueprint, Response, request from inventory_provider.routes import common from inventory_provider.routes.common import _ignore_cache_or_retrieve routes = Blueprint("msr-query-routes", __name__) ACCESS_SERVICES_LIST_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "service": { "type": "object", "properties": { "id": {"type": "integer"}, "name": {"type": "string"}, "equipment": {"type": "string"}, "pop_name": {"type": "string"}, "other_end_equipment": {"type": "string"}, "other_end_pop_name": {"type": "string"}, "speed_value": {"type": "integer"}, "speed_unit": {"type": "string"} }, "required": [ "id", "name", "pop_name", "equipment", "other_end_pop_name", "other_end_equipment", "speed_value", "speed_unit" ], "additionalProperties": False } }, "type": "array", "items": {"$ref": "#/definitions/service"} } PEERING_GROUP_LIST_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema#", "type": "array", "items": {"type": "string"} } PEERING_LIST_SCHEMA = { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "peering-instance": { "type": "object", "properties": { "address": {"type": "string"}, "description": {"type": "string"}, "logical-system": {"type": "string"}, "group": {"type": "string"}, "hostname": {"type": "string"}, "remote-asn": {"type": "integer"}, "local-asn": {"type": "integer"}, "instance": {"type": "string"} }, # only vrr peerings have remote-asn # only group peerings have local-asn or instance # not all group peerings have 'description' # and only vrr or vpn-proxy peerings are within a logical system "required": [ "address", "group", "hostname"], "additionalProperties": False } }, "type": "array", "items": {"$ref": "#/definitions/peering-instance"} } @routes.after_request def after_request(resp): return common.after_request(resp) @routes.route("/access-services", methods=['GET', 'POST']) @common.require_accepts_json def access_services(): """ Handler for `/msr/access-services`. This method is in development, not yet used. The response will be formatted according to the following schema: .. asjson:: inventory_provider.routes.msr.ACCESS_SERVICES_LIST_SCHEMA :return: """ redis = common.get_current_redis() def _services(): for k in redis.scan_iter('ims:access_services:*'): service = redis.get(k.decode('utf-8')).decode('utf-8') yield json.loads(service) cache_key = 'classifier-cache:msr:access-services' result = _ignore_cache_or_retrieve(request, cache_key, redis) if not result: result = list(_services()) if not result: return Response( response='no access services found', status=404, mimetype="text/html") # cache this data for the next call result = json.dumps(result) redis.set(cache_key, result.encode('utf-8')) return Response(result, mimetype="application/json") def _handle_peering_group_request(name, cache_key, group_key_base): """ Common method for used by :meth:`inventory_provider.routes.msr.logical_system_peerings` and :meth:`inventory_provider.routes.msr.bgp_group_peerings`. This method will return a list of all peerings configured for the specified group `name on any router, or for all group names if `name` None. The response will be formatted according to the following schema: .. asjson:: inventory_provider.routes.msr.PEERING_LIST_SCHEMA :param name: group/logical-system name, or None :param cache_key: base cache key for this type of request :param group_key_base: key above which the peerings are grouped :return: a json list, formatted as above """ r = common.get_current_redis() def _get_all_subkeys(): keys = [] for k in r.scan_iter(f'{group_key_base}:*', count=1000): keys.append(k.decode('utf-8')) return keys def _load_list_items(key): value = r.get(key) if value: yield from json.loads(value.decode('utf-8')) if name: cache_key = f'{cache_key}:{name}' items = _ignore_cache_or_retrieve(request, cache_key, r) if not items: if name: items = list(_load_list_items(f'{group_key_base}:{name}')) else: gen_list = list(map(_load_list_items, _get_all_subkeys())) items = list(itertools.chain(*gen_list)) if not items: return Response( response='no peerings found', status=404, mimetype="text/html") items = json.dumps(items) r.set(cache_key, items.encode('utf-8')) return Response(items, mimetype="application/json") @routes.route("/bgp/logical-system-peerings", methods=['GET', 'POST']) @routes.route("/bgp/logical-system-peerings/<name>", methods=['GET', 'POST']) @common.require_accepts_json def logical_system_peerings(name=None): """ Handler for `/msr/bgp/logical-system-peerings` This method will return a list of all peerings configured for the requested logical-system name on any router, or for any logical system if no parameter is given. :return: see :meth:`inventory_provider.routes.msr._handle_peering_group_request` """ # noqa: E501 return _handle_peering_group_request( name=name, cache_key='classifier-cache:msr:logical-system-peerings', group_key_base='juniper-peerings:logical-system') @routes.route("/bgp/group-peerings", methods=['GET', 'POST']) @routes.route("/bgp/group-peerings/<name>", methods=['GET', 'POST']) @common.require_accepts_json def bgp_group_peerings(name=None): """ Handler for `/msr/bgp/group-peerings` This method will return a list of all peerings configured for the requested group name on any router, or for any group system if no parameter is given. :return: see :meth:`inventory_provider.routes.msr._handle_peering_group_request` """ # noqa: E501 return _handle_peering_group_request( name=name, cache_key='classifier-cache:msr:group-peerings', group_key_base='juniper-peerings:group') @routes.route("/bgp/routing-instance-peerings", methods=['GET', 'POST']) @routes.route("/bgp/routing-instance-peerings/<name>", methods=['GET', 'POST']) @common.require_accepts_json def bgp_routing_instance_peerings(name=None): """ Handler for `/msr/bgp/routing-instance-peerings` This method will return a list of all peerings configured for the requested routing-instance name on any router, or for any routing instance if no parameter is given. :return: see :meth:`inventory_provider.routes.msr._handle_peering_group_request` """ # noqa: E501 return _handle_peering_group_request( name=name, cache_key='classifier-cache:msr:routing-instance-peerings', group_key_base='juniper-peerings:routing-instance') def _handle_peering_group_list_request(cache_key, group_key_base): """ Common method for used by :meth:`inventory_provider.routes.msr.get_logical_systems` and :meth:`inventory_provider.routes.msr.get_peering_groups`. This method will return a list of all immediate subkeys of `group_key_base`. The response will be formatted according to the following schema: .. asjson:: inventory_provider.routes.msr.PEERING_GROUP_LIST_SCHEMA :param cache_key: base cache key for this type of request :param group_key_base: key above which the peerings are grouped :return: a json list, formatted as above """ r = common.get_current_redis() def _get_all_subkeys(): for k in r.scan_iter(f'{group_key_base}:*', count=1000): k = k.decode('utf-8') yield k[len(group_key_base) + 1:] names = _ignore_cache_or_retrieve(request, cache_key, r) if not names: names = list(_get_all_subkeys()) if not names: return Response( response='no groups found', status=404, mimetype="text/html") names = json.dumps(sorted(names)) r.set(cache_key, names.encode('utf-8')) return Response(names, mimetype="application/json") @routes.route("/bgp/logical-systems", methods=['GET', 'POST']) @common.require_accepts_json def get_logical_systems(): """ Handler for `/msr/bgp/logical-systems` Returns a list of logical system names for which peering information is available. :return: see :meth:`inventory_provider.routes.msr._handle_peering_group_list_request` """ # noqa: E501 return _handle_peering_group_list_request( cache_key='classifier-cache:msr:logical-systems', group_key_base='juniper-peerings:logical-system') @routes.route("/bgp/groups", methods=['GET', 'POST']) @common.require_accepts_json def get_peering_groups(): """ Handler for `/msr/bgp/groups` Returns a list of group names for which peering information is available. :return: see :meth:`inventory_provider.routes.msr._handle_peering_group_list_request` """ # noqa: E501 return _handle_peering_group_list_request( cache_key='classifier-cache:msr:peering-groups', group_key_base='juniper-peerings:group') @routes.route("/bgp/routing-instances", methods=['GET', 'POST']) @common.require_accepts_json def get_peering_routing_instances(): """ Handler for `/msr/bgp/routing-instances` Returns a list of routing-instance names for which peering information is available. :return: see :meth:`inventory_provider.routes.msr._handle_peering_group_list_request` """ # noqa: E501 return _handle_peering_group_list_request( cache_key='classifier-cache:msr:routing-instances', group_key_base='juniper-peerings:routing-instance')