Skip to content
Snippets Groups Projects
Commit 96c89102 authored by Erik Reid's avatar Erik Reid
Browse files

Finished feature DBOARD3-406-bgp-groups-api.

parents e184a1c5 b960eaa0
No related branches found
No related tags found
No related merge requests found
...@@ -14,7 +14,33 @@ These endpoints are intended for use by MSR. ...@@ -14,7 +14,33 @@ These endpoints are intended for use by MSR.
.. autofunction:: inventory_provider.routes.msr.access_services .. autofunction:: inventory_provider.routes.msr.access_services
/msr/logical-system-peerings</name> /msr/bgp/logical-systems
------------------------------------- -------------------------------------
.. autofunction:: inventory_provider.routes.msr.logical_system_peerings .. autofunction:: inventory_provider.routes.msr.get_logical_systems
\ No newline at end of file
/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
helpers
-------------------------------------
.. autofunction:: inventory_provider.routes.msr._handle_peering_group_list_request
.. autofunction:: inventory_provider.routes.msr._handle_peering_group_request
...@@ -38,8 +38,14 @@ ACCESS_SERVICES_LIST_SCHEMA = { ...@@ -38,8 +38,14 @@ ACCESS_SERVICES_LIST_SCHEMA = {
"items": {"$ref": "#/definitions/service"} "items": {"$ref": "#/definitions/service"}
} }
PEERING_GROUP_LIST_SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "array",
"items": {"type": "string"}
}
LOGICAL_SYSTEM_PEERING_LIST_SCHEMA = { PEERING_LIST_SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"definitions": { "definitions": {
"peering-instance": { "peering-instance": {
...@@ -50,13 +56,16 @@ LOGICAL_SYSTEM_PEERING_LIST_SCHEMA = { ...@@ -50,13 +56,16 @@ LOGICAL_SYSTEM_PEERING_LIST_SCHEMA = {
"logical-system": {"type": "string"}, "logical-system": {"type": "string"},
"group": {"type": "string"}, "group": {"type": "string"},
"hostname": {"type": "string"}, "hostname": {"type": "string"},
"remote-asn": {"type": "integer"} "remote-asn": {"type": "integer"},
"local-asn": {"type": "integer"},
"instance": {"type": "string"}
}, },
# only vrr peerings have remote-asn # 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": [ "required": [
"address", "address",
"description",
"logical-system",
"group", "group",
"hostname"], "hostname"],
"additionalProperties": False "additionalProperties": False
...@@ -114,30 +123,32 @@ def access_services(): ...@@ -114,30 +123,32 @@ def access_services():
return jsonify(result) return jsonify(result)
@routes.route("/logical-system-peerings", methods=['GET', 'POST']) def _handle_peering_group_request(name, cache_key, group_key_base):
@routes.route("/logical-system-peerings/<name>", methods=['GET', 'POST'])
@common.require_accepts_json
def logical_system_peerings(name=None):
""" """
Handler for `/msr/logical-system-peerings` 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 This method will return a list of all peerings configured
for the requested logical-system name on any router, or for any for the specified group `name on any router,
logical system if no parameter is given. or for all group names if `name` None.
The response will be formatted according to the following schema: The response will be formatted according to the following schema:
.. asjson:: .. asjson::
inventory_provider.routes.msr.LOGICAL_SYSTEM_PEERING_LIST_SCHEMA inventory_provider.routes.msr.PEERING_LIST_SCHEMA
:return: :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() r = common.get_current_redis()
def _get_all_ls_keys(): def _get_all_subkeys():
keys = [] keys = []
for k in r.scan_iter('juniper-peerings:logical-system:*', count=1000): for k in r.scan_iter(f'{group_key_base}:*', count=1000):
keys.append(k.decode('utf-8')) keys.append(k.decode('utf-8'))
return keys return keys
...@@ -146,7 +157,6 @@ def logical_system_peerings(name=None): ...@@ -146,7 +157,6 @@ def logical_system_peerings(name=None):
if value: if value:
yield from json.loads(value.decode('utf-8')) yield from json.loads(value.decode('utf-8'))
cache_key = 'classifier-cache:msr:logical-system-peerings'
if name: if name:
cache_key = f'{cache_key}:{name}' cache_key = f'{cache_key}:{name}'
...@@ -156,9 +166,9 @@ def logical_system_peerings(name=None): ...@@ -156,9 +166,9 @@ def logical_system_peerings(name=None):
items = json.loads(items.decode('utf-8')) items = json.loads(items.decode('utf-8'))
else: else:
if name: if name:
items = _load_list_items(f'juniper-peerings:logical-system:{name}') items = _load_list_items(f'{group_key_base}:{name}')
else: else:
gen_list = list(map(_load_list_items, _get_all_ls_keys())) gen_list = list(map(_load_list_items, _get_all_subkeys()))
items = itertools.chain(*gen_list) items = itertools.chain(*gen_list)
items = list(items) items = list(items)
...@@ -171,3 +181,117 @@ def logical_system_peerings(name=None): ...@@ -171,3 +181,117 @@ def logical_system_peerings(name=None):
r.set(cache_key, json.dumps(items).encode('utf-8')) r.set(cache_key, json.dumps(items).encode('utf-8'))
return jsonify(items) return jsonify(items)
@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 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:group-peerings',
group_key_base='juniper-peerings:group')
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 = r.get(cache_key)
if names:
names = json.loads(names.decode('utf-8'))
else:
names = list(_get_all_subkeys())
if not names:
return Response(
response='no groups found',
status=404,
mimetype="text/html")
names = sorted(names)
r.set(cache_key, json.dumps(names).encode('utf-8'))
return jsonify(names)
@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')
...@@ -3,8 +3,8 @@ import jsonschema ...@@ -3,8 +3,8 @@ import jsonschema
import pytest import pytest
from inventory_provider.routes.msr \ from inventory_provider.routes.msr import ACCESS_SERVICES_LIST_SCHEMA, \
import ACCESS_SERVICES_LIST_SCHEMA, LOGICAL_SYSTEM_PEERING_LIST_SCHEMA PEERING_LIST_SCHEMA, PEERING_GROUP_LIST_SCHEMA
DEFAULT_REQUEST_HEADERS = { DEFAULT_REQUEST_HEADERS = {
"Content-type": "application/json", "Content-type": "application/json",
...@@ -26,25 +26,26 @@ def test_access_services(client): ...@@ -26,25 +26,26 @@ def test_access_services(client):
def test_logical_system_peerings_all(client): def test_logical_system_peerings_all(client):
rv = client.get( rv = client.get(
'/msr/logical-system-peerings', '/msr/bgp/logical-system-peerings',
headers=DEFAULT_REQUEST_HEADERS) headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 200 assert rv.status_code == 200
assert rv.is_json assert rv.is_json
response_data = json.loads(rv.data.decode('utf-8')) response_data = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(response_data, LOGICAL_SYSTEM_PEERING_LIST_SCHEMA) jsonschema.validate(response_data, PEERING_LIST_SCHEMA)
assert response_data # test data is non-empty assert response_data # test data is non-empty
assert all('logical-system' in p for p in response_data)
@pytest.mark.parametrize('name', ['VRR', 'VPN-PROXY']) @pytest.mark.parametrize('name', ['VRR', 'VPN-PROXY'])
def test_logical_system_peerings_specific(client, name): def test_logical_system_peerings_specific(client, name):
rv = client.get( rv = client.get(
f'/msr/logical-system-peerings/{name}', f'/msr/bgp/logical-system-peerings/{name}',
headers=DEFAULT_REQUEST_HEADERS) headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 200 assert rv.status_code == 200
assert rv.is_json assert rv.is_json
response_data = json.loads(rv.data.decode('utf-8')) response_data = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(response_data, LOGICAL_SYSTEM_PEERING_LIST_SCHEMA) jsonschema.validate(response_data, PEERING_LIST_SCHEMA)
assert response_data # test data is non-empty assert response_data # test data is non-empty
assert all(p['logical-system'] == name for p in response_data) assert all(p['logical-system'] == name for p in response_data)
...@@ -59,6 +60,53 @@ def test_logical_system_peerings_specific(client, name): ...@@ -59,6 +60,53 @@ def test_logical_system_peerings_specific(client, name):
]) ])
def test_logical_system_peerings_404(client, name): def test_logical_system_peerings_404(client, name):
rv = client.get( rv = client.get(
f'/msr/logical-system-peerings/{name}', f'/msr/bgp/logical-system-peerings/{name}',
headers=DEFAULT_REQUEST_HEADERS) headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 404 assert rv.status_code == 404
def test_group_peerings_all(client):
rv = client.get(
'/msr/bgp/group-peerings',
headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 200
assert rv.is_json
response_data = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(response_data, PEERING_LIST_SCHEMA)
assert response_data # test data is non-empty
@pytest.mark.parametrize('name', ['BGPLU', 'eGEANT', 'eGEANT-mcast'])
def test_group_peerings_specific(client, name):
rv = client.get(
f'/msr/bgp/group-peerings/{name}',
headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 200
assert rv.is_json
response_data = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(response_data, PEERING_LIST_SCHEMA)
assert response_data # test data is non-empty
assert all(p['group'] == name for p in response_data)
@pytest.mark.parametrize('name', ['EGEANT', 'eGEANT mcast'])
def test_group_peerings_404(client, name):
rv = client.get(
f'/msr/bgp/logical-system-peerings/{name}',
headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 404
@pytest.mark.parametrize('uri', [
'/msr/bgp/logical-systems',
'/msr/bgp/groups'])
def test_peerings_group_list(client, uri):
rv = client.get(uri, headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 200
assert rv.is_json
response_data = json.loads(rv.data.decode('utf-8'))
jsonschema.validate(response_data, PEERING_GROUP_LIST_SCHEMA)
assert response_data # test data is non-empty
...@@ -9,7 +9,7 @@ import jsonschema ...@@ -9,7 +9,7 @@ import jsonschema
from inventory_provider.tasks import worker from inventory_provider.tasks import worker
from inventory_provider.tasks import common from inventory_provider.tasks import common
from inventory_provider.routes.msr import LOGICAL_SYSTEM_PEERING_LIST_SCHEMA from inventory_provider.routes import msr
def backend_db(): def backend_db():
...@@ -141,7 +141,7 @@ def test_build_juniper_peering_db(mocked_worker_module): ...@@ -141,7 +141,7 @@ def test_build_juniper_peering_db(mocked_worker_module):
"additionalProperties": False "additionalProperties": False
} }
PEERING_LIST_SCHEMA = { DETAILED_PEERING_LIST_SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"definitions": { "definitions": {
"top-level-peering": TOP_LEVEL_PEERING_SCHEMA, "top-level-peering": TOP_LEVEL_PEERING_SCHEMA,
...@@ -194,15 +194,16 @@ def test_build_juniper_peering_db(mocked_worker_module): ...@@ -194,15 +194,16 @@ def test_build_juniper_peering_db(mocked_worker_module):
assert address == canonical assert address == canonical
continue continue
jsonschema.validate(value, PEERING_LIST_SCHEMA) jsonschema.validate(value, DETAILED_PEERING_LIST_SCHEMA)
if 'logical-system:' in key: if 'logical-system:' in key:
jsonschema.validate(value, LOGICAL_SYSTEM_PEERING_LIST_SCHEMA) jsonschema.validate(value, msr.PEERING_LIST_SCHEMA)
m = re.match(r'.*logical-system:(.+)$', key) m = re.match(r'.*logical-system:(.+)$', key)
assert all(p['logical-system'] == m.group(1) for p in value) assert all(p['logical-system'] == m.group(1) for p in value)
found_logical_system = True found_logical_system = True
if 'group:' in key: if 'group:' in key:
jsonschema.validate(value, msr.PEERING_LIST_SCHEMA)
m = re.match(r'.*group:(.+)$', key) m = re.match(r'.*group:(.+)$', key)
assert all(p['group'] == m.group(1) for p in value) assert all(p['group'] == m.group(1) for p in value)
found_group = True found_group = True
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment