diff options
author | Sloane Hertel <19572925+s-hertel@users.noreply.github.com> | 2021-05-17 05:06:53 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-17 04:06:53 -0500 |
commit | 162973d1a7a09932408a2744eb5f770b9fc5afb0 (patch) | |
tree | f4d6f4038e56665207902883951aaced08104440 /lib/ansible/module_utils | |
parent | fc84de8faa9399246ba619ab4a9983e2c0800f07 (diff) | |
download | ansible-162973d1a7a09932408a2744eb5f770b9fc5afb0.tar.gz |
ansible-galaxy - increase page size and add retry decorator for throttling (#74240) (#74648)
* Get available collection versions with page_size=100 for v2 and limit=100 for v3
* Update unit tests for larger page sizes
* Add a generic retry decorator in module_utils/api.py that accepts an Iterable of delays and a callable to determine if an exception inheriting from Exception should be retried
* Use the new decorator to handle Galaxy API rate limiting
* Add unit tests for new retry decorator
* Preserve the decorated function's metadata with functools.wraps
Co-authored-by: Matt Martz <matt@sivel.net>
Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>
(cherry picked from commit ee725846f070fc6b0dd79b5e8c5199ec652faf87)
Diffstat (limited to 'lib/ansible/module_utils')
-rw-r--r-- | lib/ansible/module_utils/api.py | 50 |
1 files changed, 50 insertions, 0 deletions
diff --git a/lib/ansible/module_utils/api.py b/lib/ansible/module_utils/api.py index 46a036d374..e780ec6b50 100644 --- a/lib/ansible/module_utils/api.py +++ b/lib/ansible/module_utils/api.py @@ -26,6 +26,8 @@ The 'api' module provides the following common argument specs: from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import functools +import random import sys import time @@ -114,3 +116,51 @@ def retry(retries=None, retry_pause=1): return retried return wrapper + + +def generate_jittered_backoff(retries=10, delay_base=3, delay_threshold=60): + """The "Full Jitter" backoff strategy. + + Ref: https://www.awsarchitectureblog.com/2015/03/backoff.html + + :param retries: The number of delays to generate. + :param delay_base: The base time in seconds used to calculate the exponential backoff. + :param delay_threshold: The maximum time in seconds for any delay. + """ + for retry in range(0, retries): + yield random.randint(0, min(delay_threshold, delay_base * 2 ** retry)) + + +def retry_never(exception_or_result): + return False + + +def retry_with_delays_and_condition(backoff_iterator, should_retry_error=None): + """Generic retry decorator. + + :param backoff_iterator: An iterable of delays in seconds. + :param should_retry_error: A callable that takes an exception of the decorated function and decides whether to retry or not (returns a bool). + """ + if should_retry_error is None: + should_retry_error = retry_never + + def function_wrapper(function): + @functools.wraps(function) + def run_function(*args, **kwargs): + """This assumes the function has not already been called. + If backoff_iterator is empty, we should still run the function a single time with no delay. + """ + call_retryable_function = functools.partial(function, *args, **kwargs) + + for delay in backoff_iterator: + try: + return call_retryable_function() + except Exception as e: + if not should_retry_error(e): + raise + time.sleep(delay) + + # Only or final attempt + return call_retryable_function() + return run_function + return function_wrapper |