diff --git a/Changelog.md b/Changelog.md index b6cd2534d833a6842d0aad571a8a8a7bd5b3e63b..294dda7c630383e848c2c2c962dd89b9c8ee9c2e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## [0.81] - 2022-02-17 +- POL1-521: handle RPC timeout error properly, and log errors + ## [0.80] - 2022-02-16 - POL1-487: Adjusted filtering for COPERNICUS dashboard (no longer overrides customer names) diff --git a/inventory_provider/juniper.py b/inventory_provider/juniper.py index 1976176493c60059a3b0a969039da2d102f69117..3f1f94a3860a18dfd7ed0aba68b69d1718421ac5 100644 --- a/inventory_provider/juniper.py +++ b/inventory_provider/juniper.py @@ -1,3 +1,4 @@ +import contextlib import logging import re import ipaddress @@ -114,6 +115,7 @@ class NetconfHandlingError(Exception): pass +@contextlib.contextmanager def _rpc(hostname, ssh): dev = Device( host=hostname, @@ -121,9 +123,9 @@ def _rpc(hostname, ssh): ssh_private_key_file=ssh['private-key']) try: dev.open() - except (EzErrors.ConnectError, EzErrors.RpcError) as e: - raise ConnectionError(str(e)) - return dev.rpc + yield dev.rpc + finally: + dev.close() def validate_netconf_config(config_doc): @@ -166,14 +168,18 @@ def load_config(hostname, ssh_params, validate=True): :param ssh_params: 'ssh' config element(cf. config.py:CONFIG_SCHEMA) :param validate: whether or not to validate netconf data (default True) :return: - :raises: NetconfHandlingError from validate_netconf_config + :raises: NetconfHandlingError or ConnectionError """ logger = logging.getLogger(__name__) logger.info("capturing netconf data for '%s'" % hostname) - config = _rpc(hostname, ssh_params).get_config() - if validate: - validate_netconf_config(config) - return config + try: + with _rpc(hostname, ssh_params) as router: + config = router.get_config() + if validate: + validate_netconf_config(config) + return config + except (EzErrors.ConnectError, EzErrors.RpcError) as e: + raise ConnectionError(str(e)) def list_interfaces(netconf_config): diff --git a/inventory_provider/tasks/worker.py b/inventory_provider/tasks/worker.py index 4d2ee9dbc3e62a8fd81f855f3000317939d37269..aca83241178140922013115cd2727ff337c62074 100644 --- a/inventory_provider/tasks/worker.py +++ b/inventory_provider/tasks/worker.py @@ -562,9 +562,13 @@ def reload_lab_router_config_chorded(self, hostname): hostname, community, logical_systems, self.log_info) self.log_info(f'updated configuration for lab {hostname}') - except Exception as e: - logger.error(e) + except Exception: + errmsg = f'unhandled exception loading {hostname} info' + logger.exception(errmsg) update_latch_status(InventoryTask.config, pending=True, failure=True) + self.log_error(errmsg) + # TODO: re-raise and handle in some common way for all tasks + # raise @app.task(base=InventoryTask, bind=True, name='reload_router_config') @@ -598,9 +602,13 @@ def reload_router_config_chorded(self, hostname): snmp_refresh_peerings_chorded(hostname, community, logical_systems) logger.info(f'updated configuration for {hostname}') - except Exception as e: - logger.error(e) + except Exception: + errmsg = f'unhandled exception loading {hostname} info' + logger.exception(errmsg) update_latch_status(InventoryTask.config, pending=True, failure=True) + self.log_error(errmsg) + # TODO: re-raise and handle in some common way for all tasks + # raise def retrieve_and_persist_netconf_config( diff --git a/setup.py b/setup.py index c53c286396dd23dc7b41aa4a71245306a4690a59..bb0df08010d2045b0b1c495740ce11d1284bb9f6 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages setup( name='inventory-provider', - version="0.81", + version="0.82", author='GEANT', author_email='swd@geant.org', description='Dashboard inventory provider', diff --git a/test/per_router/conftest.py b/test/per_router/conftest.py index 559167093813de008c2682b7d2cb065029f8cd92..9fc9ac4790cefd26df008c72cfc2a61539ca482e 100644 --- a/test/per_router/conftest.py +++ b/test/per_router/conftest.py @@ -54,6 +54,9 @@ class MockedJunosDevice(object): def open(self): pass + def close(self): + pass + @pytest.fixture def netconf_doc(mocker, router, data_config): diff --git a/test/per_router/test_celery_worker.py b/test/per_router/test_celery_worker.py index 28776edbf5ea5233a47c2e3011bfd95258672df3..fea7c407711f953bdb06fe3ea6074860d191478f 100644 --- a/test/per_router/test_celery_worker.py +++ b/test/per_router/test_celery_worker.py @@ -3,6 +3,9 @@ just checks that the worker methods call the right functions and some data ends up in the right place ... otherwise not very detailed """ import re + +import pytest + from inventory_provider.tasks import worker from inventory_provider.tasks.common import _get_redis @@ -18,6 +21,9 @@ def backend_db(): def test_netconf_refresh_config(mocked_worker_module, router): + if router in ['qfx.par.fr.geant.net', 'qfx.fra.de.geant.net']: + # expected to fail + pytest.skip(f'test data has no community string for {router}') del backend_db()['netconf:' + router] worker.reload_router_config_chorded(router) assert backend_db()['netconf:' + router] @@ -94,7 +100,7 @@ def test_reload_router_config(mocked_worker_module, router, mocker): _mocked_snmp_refresh_interfaces_chorded) def _mocked_snmp_refresh_peerings_chorded(*args, **kwargs): - assert len(args) == 4 + assert len(args) == 3 backend_db().update(saved_peerings) mocker.patch( 'inventory_provider.tasks.worker.snmp_refresh_peerings_chorded', diff --git a/tox.ini b/tox.ini index 1f32f60ae56f411df6f492156ae11ebf471692ef..eb2bec7d2f9b8ba10ead57bbc9cf2591a675e095 100644 --- a/tox.ini +++ b/tox.ini @@ -17,8 +17,7 @@ commands = coverage run --source inventory_provider -m py.test {posargs} coverage xml coverage html - coverage report --fail-under 75 - # coverage report --fail-under 80 + coverage report --fail-under 80 flake8 sphinx-build -M html docs/source docs/build