From 96ca88345e13ad68785c2b06d896f50aedd568cd Mon Sep 17 00:00:00 2001 From: Erik Reid <erik.reid@geant.org> Date: Fri, 5 Mar 2021 08:57:03 +0100 Subject: [PATCH] refactored to share some code between both routes --- inventory_provider/routes/poller.py | 150 ++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 33 deletions(-) diff --git a/inventory_provider/routes/poller.py b/inventory_provider/routes/poller.py index f7ef02d5..a9282bca 100644 --- a/inventory_provider/routes/poller.py +++ b/inventory_provider/routes/poller.py @@ -176,36 +176,78 @@ def _load_interfaces(hostname): } -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 @@ -236,7 +278,7 @@ 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', @@ -250,6 +292,60 @@ def interfaces(hostname=None): 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 + """ + GB = 1000000000 + # GB = 1 << 30 + + 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 @@ -260,8 +356,9 @@ def interface_speeds(hostname=None): which returns information for either all interfaces or those on the requested hostname. - The response is a list of speed information for all - known interfaces. + 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 @@ -279,20 +376,7 @@ def interface_speeds(hostname=None): if result: result = result.decode('utf-8') else: - - def _speed(ifc): - # TODO - return -1 - - def _result_ifc(ifc): - return { - 'router': ifc['router'], - 'name': ifc['name'], - 'speed': _speed(ifc) - } - - result = list(map(_result_ifc, _load_interfaces(hostname))) - + result = list(_load_interfaces_and_speeds(hostname)) if not result: return Response( response='no interfaces found', -- GitLab