Skip to content
Snippets Groups Projects
Commit 40b93bd4 authored by geant-release-service's avatar geant-release-service
Browse files

Finished release 0.112.

parents 8dd974ef 8dc9dcde
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [0.112] - 2023-11-20
- correctly handled timeout error for some RPC requests
- handle inactive bundle configs populating cache
- addressed potential issues with bundle formatting in cache for /poller/speeds endpoint
## [0.111] - 2023-11-6 ## [0.111] - 2023-11-6
- fixed retrieving netconf interface-information - fixed retrieving netconf interface-information
- return speeds in /poller/speed exclusively via netconf interface-information - return speeds in /poller/speed exclusively via netconf interface-information
......
...@@ -4,6 +4,7 @@ import re ...@@ -4,6 +4,7 @@ import re
import ipaddress import ipaddress
import ncclient import ncclient
import ncclient.operations
import ncclient.manager import ncclient.manager
from jnpr.junos import Device from jnpr.junos import Device
from jnpr.junos import exception as EzErrors from jnpr.junos import exception as EzErrors
...@@ -138,7 +139,7 @@ def _nc_connection(host_params, ssh_params): ...@@ -138,7 +139,7 @@ def _nc_connection(host_params, ssh_params):
try: try:
yield conn # wait here until caller context ends yield conn # wait here until caller context ends
except EzErrors.ConnectTimeoutError: except (EzErrors.ConnectTimeoutError, ncclient.operations.errors.TimeoutExpiredError):
raise TimeoutError raise TimeoutError
finally: finally:
conn.close_session() conn.close_session()
...@@ -232,10 +233,10 @@ def list_interfaces(netconf_config): ...@@ -232,10 +233,10 @@ def list_interfaces(netconf_config):
def _ifc_info(e): def _ifc_info(e):
# warning: this structure should match the default # warning: this structure should match the default
# returned from routes.classifier.juniper_link_info # returned from routes.classifier.juniper_link_info
name = e.find('name') _name = e.find('name')
assert name is not None, "expected interface 'name' child element" assert _name is not None, "expected interface 'name' child element"
ifc = { ifc = {
'name': name.text, 'name': _name.text,
'description': '', 'description': '',
'bundle': [], 'bundle': [],
'speed': '' # set elsewhere but needs to be included to maintain default structure 'speed': '' # set elsewhere but needs to be included to maintain default structure
...@@ -244,12 +245,11 @@ def list_interfaces(netconf_config): ...@@ -244,12 +245,11 @@ def list_interfaces(netconf_config):
if description is not None: if description is not None:
ifc['description'] = description.text ifc['description'] = description.text
for b in i.iterfind(".//bundle"): ifc['bundle'] = e.xpath(
ifc['bundle'].append(b.text) "./gigether-options[not(@inactive='inactive')]"
"/ieee-802.3ad[not(@inactive='inactive')]/bundle/text()")
ifc['ipv4'] = e.xpath('./family/inet/address/name/text()') ifc['ipv4'] = e.xpath('./family/inet/address/name/text()')
ifc['ipv6'] = e.xpath('./family/inet6/address/name/text()') ifc['ipv6'] = e.xpath('./family/inet6/address/name/text()')
return ifc return ifc
def _inactive(interface_node): def _inactive(interface_node):
......
...@@ -84,6 +84,7 @@ routes = Blueprint('poller-support-routes', __name__) ...@@ -84,6 +84,7 @@ routes = Blueprint('poller-support-routes', __name__)
Mb = 1 << 20 Mb = 1 << 20
Gb = 1 << 30 Gb = 1 << 30
OC = Mb * 51.84
class INTERFACE_TYPES(Enum): class INTERFACE_TYPES(Enum):
...@@ -713,8 +714,6 @@ def _add_speeds(interfaces): ...@@ -713,8 +714,6 @@ def _add_speeds(interfaces):
nc_ifc = netconf_interface_index.get(f"{ifc['router']}---{ifc['name']}", {}) nc_ifc = netconf_interface_index.get(f"{ifc['router']}---{ifc['name']}", {})
if 'speed' in nc_ifc: if 'speed' in nc_ifc:
ifc['speed'] = nc_ifc['speed'] ifc['speed'] = nc_ifc['speed']
else:
ifc['speed'] = ''
yield ifc yield ifc
...@@ -726,6 +725,9 @@ def _add_bundle_parents(interfaces, hostname=None): ...@@ -726,6 +725,9 @@ def _add_bundle_parents(interfaces, hostname=None):
:param hostname: hostname or None for all :param hostname: hostname or None for all
:return: generator with bundle-parents populated in each element :return: generator with bundle-parents populated in each element
""" """
def _get_base_name(name):
return name.split('.')[0]
bundles = _load_interface_bundles( bundles = _load_interface_bundles(
current_app.config['INVENTORY_PROVIDER_CONFIG'], hostname) current_app.config['INVENTORY_PROVIDER_CONFIG'], hostname)
# create a quick look-up for interface details # create a quick look-up for interface details
...@@ -734,8 +736,8 @@ def _add_bundle_parents(interfaces, hostname=None): ...@@ -734,8 +736,8 @@ def _add_bundle_parents(interfaces, hostname=None):
for ifc in interfaces: for ifc in interfaces:
router_bundle = bundles.get(ifc['router'], None) router_bundle = bundles.get(ifc['router'], None)
if router_bundle: if router_bundle:
base_ifc = ifc['name'].split('.')[0] base_ifc = _get_base_name(ifc['name'])
bundle_parents = [interface_index.get(f"{ifc['router']}---{bundle_ifc}") bundle_parents = [interface_index.get(f"{ifc['router']}---{_get_base_name(bundle_ifc)}")
for bundle_ifc in router_bundle.get(base_ifc, [])] for bundle_ifc in router_bundle.get(base_ifc, [])]
ifc['bundle-parents'] = bundle_parents ifc['bundle-parents'] = bundle_parents
yield ifc yield ifc
...@@ -878,25 +880,32 @@ def interface_speed(ifc): ...@@ -878,25 +880,32 @@ def interface_speed(ifc):
return -1 return -1
def _get_speed(ifc): def _get_speed(ifc):
rate_conversions = { rate_conversions = {
"mbps": Mb, "mbps": Mb,
"gbps": Gb "gbps": Gb
} }
if "speed" in ifc: if "speed" in ifc:
speed = ifc["speed"] speed = ifc["speed"]
match = re.match(r"(\d+)(.+)", speed) rate_match = re.match(r"(\d+)(.+)", speed)
if match: if rate_match:
value = int(match.group(1)) value = int(rate_match.group(1))
rate = match.group(2).strip().lower() rate = rate_match.group(2).strip().lower()
if rate in rate_conversions: if rate in rate_conversions:
return value * rate_conversions[rate] return int(value * rate_conversions[rate])
else: else:
logger.warning(f'unrecognised rate: {rate}, using _name_to_speed fallback') logger.warning(f'unrecognised rate: {rate}, using _name_to_speed fallback')
return _name_to_speed(ifc['name']) return _name_to_speed(ifc['name'])
else: else:
logger.warning(f'unrecognised speed: {speed}, using _name_to_speed fallback') oc_match = re.match(r"OC(\d+)", speed)
return _name_to_speed(ifc['name']) if oc_match:
value = int(oc_match.group(1))
return int(value * OC)
else:
logger.warning(f'unrecognised speed: {speed}, using _name_to_speed fallback')
return _name_to_speed(ifc['name'])
else: else:
logger.warning('no speed data for interface, using _name_to_speed fallback') logger.warning('no speed data for interface, using _name_to_speed fallback')
return _name_to_speed(ifc['name']) return _name_to_speed(ifc['name'])
...@@ -905,9 +914,9 @@ def interface_speed(ifc): ...@@ -905,9 +914,9 @@ def interface_speed(ifc):
if not ifc['name'].startswith('ae'): if not ifc['name'].startswith('ae'):
logger.warning( logger.warning(
f'ifc has bundle-parents, but name is {ifc["name"]}') f'ifc has bundle-parents, but name is {ifc["name"]}')
return sum(_get_speed(parent_ifc) for parent_ifc in ifc['bundle-parents']) return int(sum(_get_speed(parent_ifc) for parent_ifc in ifc['bundle-parents']))
return _get_speed(ifc) return int(_get_speed(ifc))
def _load_interfaces_and_speeds(hostname=None): def _load_interfaces_and_speeds(hostname=None):
......
...@@ -2,7 +2,7 @@ from setuptools import setup, find_packages ...@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name='inventory-provider', name='inventory-provider',
version="0.111", version="0.112",
author='GEANT', author='GEANT',
author_email='swd@geant.org', author_email='swd@geant.org',
description='Dashboard inventory provider', description='Dashboard inventory provider',
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
import ast import ast
import netifaces import netifaces
import ipaddress import ipaddress
import os
from lxml import etree
import pytest import pytest
from inventory_provider import juniper from inventory_provider import juniper
...@@ -89,3 +91,32 @@ def test_asn_to_int_functionality(): ...@@ -89,3 +91,32 @@ def test_asn_to_int_functionality():
with pytest.raises(ValueError): with pytest.raises(ValueError):
asn_to_int('66512.2') asn_to_int('66512.2')
asn_to_int('Test') asn_to_int('Test')
def test_DBOARD3_833():
"""Test that the bundle inactive attribute is handled correctly.
The test data contains an interface: et-4/0/2 with the bundle
configuration (for ae6) marked as 'inactive'. et-4/0/2 also
has a logical interface configured.
This test confirms that neither et-4/0/2 nor et-4/0/2.0 are
part of a bundle.
"""
test_filename = os.path.join(
os.path.dirname(__file__),
'data',
'DBOARD3-833.xml')
with open(test_filename, 'r') as f:
netconf_config = etree.XML(f.read())
interfaces = {
i['name']: i for i in juniper.list_interfaces(netconf_config)}
assert not interfaces['et-4/0/2']['bundle']
assert not interfaces['et-4/0/2.0']['bundle']
# additional sanity check: parent bundle should be empty or exactly one
for ifc in interfaces.values():
assert len(ifc['bundle']) in (0, 1)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment