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

Finished feature POL1-380-basic-interface-speed-api.

parents f1a80578 2f271319
No related branches found
No related tags found
No related merge requests found
......@@ -6,7 +6,15 @@ BRIAN support Endpoints
These endpoints are intended for use by BRIAN.
/poller/interfaces
.. contents:: :local:
/poller/interfaces</hostname>
---------------------------------
.. autofunction:: inventory_provider.routes.poller.interfaces
/poller/speeds</hostname>
---------------------------------
.. autofunction:: inventory_provider.routes.poller.interface_speeds
......@@ -9,6 +9,8 @@ from inventory_provider.routes import common
logger = logging.getLogger(__name__)
routes = Blueprint('poller-support-routes', __name__)
Gb = 1 << 30
INTERFACE_LIST_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
......@@ -59,6 +61,26 @@ INTERFACE_LIST_SCHEMA = {
'items': {'$ref': '#/definitions/interface'}
}
INTERFACE_SPEED_LIST_SCHEMA = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'definitions': {
'interface': {
'type': 'object',
'properties': {
'router': {'type': 'string'},
'name': {'type': 'string'},
'speed': {'type': 'integer'}
},
'required': ['router', 'name', 'speed'],
'additionalProperties': False
},
},
'type': 'array',
'items': {'$ref': '#/definitions/interface'}
}
@routes.after_request
def after_request(resp):
......@@ -150,42 +172,83 @@ def _load_interfaces(hostname):
'name': ifc['name'],
'bundle': ifc['bundle'],
'bundle-parents': [],
'snmp-index': -1,
'description': ifc['description'],
'circuits': []
}
def _load_poller_interfaces(hostname=None):
def _add_bundle_parents(interfaces, hostname=None):
"""
generator that adds bundle-parents info to each interface.
snmp_indexes = _load_snmp_indexes(hostname)
:param interfaces: result of _load_interfaces
:param hostname: hostname or None for all
:return: generator with bundle-parents populated in each element
"""
bundles = _load_interface_bundles(hostname)
services = _load_services(hostname)
for ifc in _load_interfaces(hostname):
router_snmp = snmp_indexes.get(ifc['router'], None)
if not router_snmp or ifc['name'] not in router_snmp:
# there's no way to poll this interface
continue
ifc['snmp-index'] = router_snmp[ifc['name']]['index']
# TODO: uncomment this when it won't break poller-admin-service
# not urgent ... it looks empirically like all logical-system
# interfaces are repeated for both communities
# ifc['snmp-community'] = router_snmp[ifc['name']]['community']
for ifc in interfaces:
router_bundle = bundles.get(ifc['router'], None)
if router_bundle:
base_ifc = ifc['name'].split('.')[0]
ifc['bundle-parents'] = router_bundle.get(base_ifc, [])
yield ifc
def _add_circuits(interfaces, hostname=None):
"""
generator that adds service info to each interface.
:param interfaces: result of _load_interfaces
:param hostname: hostname or None for all
:return: generator with 'circuits' populated in each element, if present
"""
services = _load_services(hostname)
for ifc in interfaces:
router_services = services.get(ifc['router'], None)
if router_services:
ifc['circuits'] = router_services.get(ifc['name'], [])
yield ifc
def _add_snmp_indexes(interfaces, hostname=None):
"""
generator that adds snmp info to each interface, if available
:param interfaces: result of _load_interfaces
:param hostname: hostname or None for all
:return: generator with 'snmp-index' optionally added to each element
"""
snmp_indexes = _load_snmp_indexes(hostname)
for ifc in interfaces:
router_snmp = snmp_indexes.get(ifc['router'], None)
if router_snmp and ifc['name'] in router_snmp:
ifc['snmp-index'] = router_snmp[ifc['name']]['index']
# TODO: uncomment this when it won't break poller-admin-service
# not urgent ... it looks empirically like all logical-system
# interfaces are repeated for both communities
# ifc['snmp-community'] = router_snmp[ifc['name']]['community']
yield ifc
def _load_interfaces_to_poll(hostname=None):
"""
prepares the result of a call to /interfaces
:param hostname: hostname or None for all
:return: generator yielding interface elements
"""
basic_interfaces = _load_interfaces(hostname)
with_bundles = _add_bundle_parents(basic_interfaces, hostname)
with_circuits = _add_circuits(with_bundles, hostname)
with_snmp = _add_snmp_indexes(with_circuits, hostname)
def _has_snmp_index(ifc):
return 'snmp-index' in ifc
# only return interfaces that can be polled
return filter(_has_snmp_index, with_snmp)
@routes.route("/interfaces", methods=['GET', 'POST'])
@routes.route('/interfaces/<hostname>', methods=['GET', 'POST'])
@common.require_accepts_json
......@@ -216,7 +279,104 @@ def interfaces(hostname=None):
if result:
result = result.decode('utf-8')
else:
result = list(_load_poller_interfaces(hostname))
result = list(_load_interfaces_to_poll(hostname))
if not result:
return Response(
response='no interfaces found',
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")
def interface_speed(ifc):
"""
Return the maximum bits per second expected for the given interface.
cf. https://www.juniper.net/documentation/us/en/software/
vmx/vmx-getting-started/topics/task/
vmx-chassis-interface-type-configuring.html
:param ifc:
:return: an integer bits per second
"""
def _name_to_speed(ifc_name):
if ifc_name.startswith('ge'):
return Gb
if ifc_name.startswith('xe'):
return 10 * Gb
if ifc_name.startswith('et'):
return 100 * Gb
logger.warning(f'unrecognized interface name: {ifc_name}')
return -1
if ifc['bundle-parents']:
if not ifc['name'].startswith('ae'):
logger.warning(
f'ifc has bundle-parents, but name is {ifc["name"]}')
return sum(_name_to_speed(name) for name in ifc['bundle-parents'])
return _name_to_speed(ifc['name'])
def _load_interfaces_and_speeds(hostname=None):
"""
prepares the result of a call to /speeds
:param hostname: hostname or None for all
:return: generator yielding interface elements
"""
basic_interfaces = _load_interfaces(hostname)
with_bundles = _add_bundle_parents(basic_interfaces, hostname)
with_bundles = list(with_bundles)
def _result_ifc(ifc):
return {
'router': ifc['router'],
'name': ifc['name'],
'speed': interface_speed(ifc)
}
return map(_result_ifc, with_bundles)
@routes.route("/speeds", methods=['GET', 'POST'])
@routes.route('/speeds/<hostname>', methods=['GET', 'POST'])
@common.require_accepts_json
def interface_speeds(hostname=None):
"""
Handler for `/poller/speeds` and
`/poller/speeds/<hostname>`
which returns information for either all interfaces
or those on the requested hostname.
The response is a list of maximum speed information (in bits
per second) for all known interfaces.
*speed <= 0 means the max interface speed can't be determined*
.. asjson::
inventory_provider.routes.poller.INTERFACE_SPEED_LIST_SCHEMA
:param hostname: optional, if present should be a router hostname
:return:
"""
cache_key = f'classifier-cache:poller-interface-speeds:{hostname}' \
if hostname else 'classifier-cache:poller-interface-speeds:all'
r = common.get_current_redis()
result = r.get(cache_key)
if result:
result = result.decode('utf-8')
else:
result = list(_load_interfaces_and_speeds(hostname))
if not result:
return Response(
response='no interfaces found',
......
import json
import jsonschema
from inventory_provider.routes.poller import INTERFACE_LIST_SCHEMA
from inventory_provider.routes.poller \
import INTERFACE_LIST_SCHEMA, INTERFACE_SPEED_LIST_SCHEMA
DEFAULT_REQUEST_HEADERS = {
"Content-type": "application/json",
......@@ -19,3 +20,16 @@ def test_router_interfaces(router, client):
assert response # at least shouldn't be empty
response_routers = {ifc['router'] for ifc in response}
assert response_routers == {router}
def test_router_interface_speeds(router, client):
rv = client.post(
f'/poller/speeds/{router}',
headers=DEFAULT_REQUEST_HEADERS)
assert rv.status_code == 200
response = json.loads(rv.data.decode("utf-8"))
jsonschema.validate(response, INTERFACE_SPEED_LIST_SCHEMA)
assert response # at least shouldn't be empty
response_routers = {ifc['router'] for ifc in response}
assert response_routers == {router}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment