-
-
Save dmfigol/19d0b9a70a9b38cdc197d374666f00cc to your computer and use it in GitHub Desktop.
| import logging | |
| import time | |
| import traceback | |
| from functools import wraps | |
| from typing import Union, Type, Tuple, Optional, Callable, TypeVar | |
| T = TypeVar("T") | |
| logger = logging.getLogger(__name__) | |
| def retry( | |
| exceptions: Union[Type[Exception], Tuple[Type[Exception], ...]], | |
| max_retries: int = 5, | |
| delay: int = 3, | |
| delay_multiplier: int = 2, | |
| exc_retry_condition: Optional[Callable[[Exception], bool]] = None, | |
| exc_retry_bypass_action_log: bool = True, | |
| exc_retry_bypass_action_raise: bool = True, | |
| ) -> Callable[[T], T]: | |
| """ | |
| Retry calling the decorated function using an exponential backoff. | |
| Args: | |
| exceptions: A single or a tuple of Exceptions to trigger retry | |
| max_retries: Number of times to retry before failing. | |
| delay: Initial delay between retries in seconds. | |
| delay_multiplier: Delay multiplier (e.g. value of 2 will double the delay | |
| each retry). | |
| exc_retry_condition: A function where a raised exception will be passed | |
| as an argument. It checks if the retry mechanism should be bypassed | |
| exc_retry_bypass_action_log: when the exception retry condition is | |
| set but not satisfied, where a log message should be emitted | |
| exc_retry_bypass_action_raise: when the exception retry condition is | |
| set but not satisfied, where an exception should be emitted | |
| """ | |
| def _retry_deco(f): | |
| @wraps(f) | |
| def f_retry(*args, **kwargs): | |
| attempt_num = 0 | |
| mdelay = delay | |
| while attempt_num < max_retries: | |
| try: | |
| return f(*args, **kwargs) | |
| except exceptions as e: | |
| if exc_retry_condition is not None and not exc_retry_condition(e): | |
| if exc_retry_bypass_action_log: | |
| logger.error( | |
| "Exception occurred for which retry is bypassed:", | |
| exc_info=True, | |
| ) | |
| if exc_retry_bypass_action_raise: | |
| raise | |
| else: | |
| return | |
| attempt_num += 1 | |
| logger.warning( | |
| "Retry attempt #%d/%d in %d seconds ...\n%s", | |
| attempt_num, | |
| max_retries, | |
| mdelay, | |
| traceback.format_exc(limit=1), | |
| ) | |
| time.sleep(mdelay) | |
| mdelay *= delay_multiplier | |
| return f(*args, **kwargs) | |
| return f_retry | |
| return _retry_deco |
How do you use it? I need to see the code to troubleshoot it.
I use it, as you describe it:
exceptions = (NetMikoTimeoutException, NetMikoAuthenticationException)
open_connection_retry = retry(exceptions_)(task.host.open_connection)
open_connection_retry("netmiko", configuration=task.nornir.config)_
I have commented problematic part of the code, but it looks there is another problem. After first exception (wrong password in inventory file)
NetMikoAuthenticationException, next log message is displayed:
Retry attempt #1/5 in 3 seconds ...
That is OK and during second retry next exception is trow:
nornir.core.exceptions.ConnectionAlreadyOpen: netmiko
It looks connection needs to be closed before nornir tries to opene it again.
BR Milan
Yeah, it is a known issue: nornir-automation/nornir#350
the fix for this didn't make it to the core yet.
is there a workaround?
Hi, something is wrong with the code, line 45:
(Pdb) pp exc_retry_condition(e)
*** TypeError: 'NoneType' object is not callable