msr.py 11.61 KiB
"""
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')