Blame 0001-Utils-Add-decorator-for-retry.patch

8bd4720
From ac974739a53b6e3fc7a0b25d5d1c01cba7c2f6e2 Mon Sep 17 00:00:00 2001
8bd4720
From: Lukas Slebodnik <lslebodn@redhat.com>
8bd4720
Date: Tue, 5 Feb 2019 10:42:08 +0100
8bd4720
Subject: [PATCH] Utils: Add decorator for retry
8bd4720
8bd4720
It might be useful in checks which do lot of network communication.
8bd4720
---
8bd4720
 colin/utils/cmd_tools.py |  29 +++++++++++
8bd4720
 tests/unit/test_utils.py | 103 +++++++++++++++++++++++++++++++++++++--
8bd4720
 2 files changed, 129 insertions(+), 3 deletions(-)
8bd4720
8bd4720
diff --git a/colin/utils/cmd_tools.py b/colin/utils/cmd_tools.py
8bd4720
index 2da4118..f3f08a8 100644
8bd4720
--- a/colin/utils/cmd_tools.py
8bd4720
+++ b/colin/utils/cmd_tools.py
8bd4720
@@ -13,9 +13,11 @@
8bd4720
 # You should have received a copy of the GNU General Public License
8bd4720
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
8bd4720
 #
8bd4720
+import functools
8bd4720
 import logging
8bd4720
 import subprocess
8bd4720
 import threading
8bd4720
+import time
8bd4720
 
8bd4720
 try:
8bd4720
     import thread
8bd4720
@@ -120,3 +122,30 @@ def inner(*args, **kwargs):
8bd4720
         return inner
8bd4720
 
8bd4720
     return outer
8bd4720
+
8bd4720
+
8bd4720
+def retry(retry_count=5, delay=2):
8bd4720
+    """
8bd4720
+    Use as decorator to retry functions few times with delays
8bd4720
+
8bd4720
+    Exception will be raised if last call fails
8bd4720
+
8bd4720
+    :param retry_count: int could of retries in case of failures. It must be
8bd4720
+                        a positive number
8bd4720
+    :param delay: int delay between retries
8bd4720
+    """
8bd4720
+    if retry_count <= 0:
8bd4720
+        raise ValueError("retry_count have to be positive")
8bd4720
+
8bd4720
+    def decorator(f):
8bd4720
+        @functools.wraps(f)
8bd4720
+        def wrapper(*args, **kwargs):
8bd4720
+            for i in range(retry_count, 0, -1):
8bd4720
+                try:
8bd4720
+                    return f(*args, **kwargs)
8bd4720
+                except Exception:
8bd4720
+                    if i <= 1:
8bd4720
+                        raise
8bd4720
+                time.sleep(delay)
8bd4720
+        return wrapper
8bd4720
+    return decorator
8bd4720
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
8bd4720
index 0d23182..4185a56 100644
8bd4720
--- a/tests/unit/test_utils.py
8bd4720
+++ b/tests/unit/test_utils.py
8bd4720
@@ -13,12 +13,13 @@
8bd4720
 # You should have received a copy of the GNU General Public License
8bd4720
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
8bd4720
 #
8bd4720
-from time import sleep
8bd4720
+import time
8bd4720
 
8bd4720
 import pytest
8bd4720
 
8bd4720
 from colin.core.exceptions import ColinException
8bd4720
 from colin.utils.cmd_tools import exit_after
8bd4720
+from colin.utils.cmd_tools import retry
8bd4720
 
8bd4720
 
8bd4720
 @exit_after(1)
8bd4720
@@ -28,7 +29,7 @@ def fast_fce():
8bd4720
 
8bd4720
 @exit_after(1)
8bd4720
 def slow_fce():
8bd4720
-    sleep(2)
8bd4720
+    time.sleep(2)
8bd4720
 
8bd4720
 
8bd4720
 @exit_after(1)
8bd4720
@@ -52,4 +53,100 @@ def test_timeout_bad_fce():
8bd4720
 
8bd4720
 def test_timeout_dirrect():
8bd4720
     with pytest.raises(TimeoutError):
8bd4720
-        exit_after(1)(sleep)(2)
8bd4720
+        exit_after(1)(time.sleep)(2)
8bd4720
+
8bd4720
+
8bd4720
+COUNTER = 0
8bd4720
+
8bd4720
+
8bd4720
+def raise_exception():
8bd4720
+    global COUNTER
8bd4720
+    COUNTER += 1
8bd4720
+
8bd4720
+    raise Exception('I am bad function')
8bd4720
+
8bd4720
+
8bd4720
+def test_no_retry_for_success():
8bd4720
+    global COUNTER
8bd4720
+    COUNTER = 0
8bd4720
+
8bd4720
+    @retry(5, 0)
8bd4720
+    def always_success():
8bd4720
+        global COUNTER
8bd4720
+        COUNTER = COUNTER + 1
8bd4720
+
8bd4720
+        return 42
8bd4720
+
8bd4720
+    assert always_success() == 42
8bd4720
+    assert COUNTER == 1
8bd4720
+
8bd4720
+
8bd4720
+def test_retry_with_exception():
8bd4720
+    global COUNTER
8bd4720
+    COUNTER = 0
8bd4720
+
8bd4720
+    @retry(5, 0)
8bd4720
+    def always_raise_exception():
8bd4720
+        raise_exception()
8bd4720
+
8bd4720
+    with pytest.raises(Exception) as ex:
8bd4720
+        always_raise_exception()
8bd4720
+
8bd4720
+    assert str(ex.value) == 'I am bad function'
8bd4720
+    assert COUNTER == 5
8bd4720
+
8bd4720
+
8bd4720
+def test_wrong_parameter():
8bd4720
+    with pytest.raises(ValueError) as ex:
8bd4720
+        retry(-1, 1)
8bd4720
+    assert str(ex.value) == 'retry_count have to be positive'
8bd4720
+
8bd4720
+    with pytest.raises(ValueError) as ex:
8bd4720
+        retry(0, 1)
8bd4720
+    assert str(ex.value) == 'retry_count have to be positive'
8bd4720
+
8bd4720
+    @retry(5, -1)
8bd4720
+    def fail_negative_sleep():
8bd4720
+        raise_exception()
8bd4720
+
8bd4720
+    with pytest.raises(ValueError) as ex:
8bd4720
+        fail_negative_sleep()
8bd4720
+    assert str(ex.value) == 'sleep length must be non-negative'
8bd4720
+
8bd4720
+
8bd4720
+def test_retry_with_sleep():
8bd4720
+    global COUNTER
8bd4720
+    COUNTER = 0
8bd4720
+
8bd4720
+    @retry(4, .5)
8bd4720
+    def fail_and_sleep():
8bd4720
+        raise_exception()
8bd4720
+
8bd4720
+    time_start = time.time()
8bd4720
+    with pytest.raises(Exception) as ex:
8bd4720
+        fail_and_sleep()
8bd4720
+    time_end = time.time()
8bd4720
+
8bd4720
+    assert str(ex.value) == 'I am bad function'
8bd4720
+    assert COUNTER == 4
8bd4720
+
8bd4720
+    # there are 3 sleeps between 4 delays
8bd4720
+    assert time_end - time_start >= 1.5
8bd4720
+    # there were not 4 sleeps
8bd4720
+    assert time_end - time_start < 4
8bd4720
+
8bd4720
+
8bd4720
+def test_recover_after_few_failures():
8bd4720
+    global COUNTER
8bd4720
+    COUNTER = 0
8bd4720
+
8bd4720
+    @retry(5, 0)
8bd4720
+    def sleep_like_a_baby():
8bd4720
+        global COUNTER
8bd4720
+        if COUNTER < 3:
8bd4720
+            COUNTER += 1
8bd4720
+            raise Exception("sleeping")
8bd4720
+        return []
8bd4720
+
8bd4720
+    assert sleep_like_a_baby() == []
8bd4720
+    assert COUNTER == 3