-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #102 from Duke-GCB/k8s-api-retry-simple
Implements simple k8s API retries
- Loading branch information
Showing
7 changed files
with
138 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from tenacity import retry, wait_exponential, retry_if_exception_type, stop_after_attempt, before_sleep_log | ||
import logging | ||
import os | ||
|
||
|
||
class RetryParameters(object): | ||
MULTIPLIER = float(os.getenv('RETRY_MULTIPLIER', 5)) # Unit for multiplying the exponent | ||
MIN = float(os.getenv('RETRY_MIN', 5)) # Min time for retrying | ||
MAX = float(os.getenv('RETRY_MAX', 1200)) # Max interval between retries | ||
ATTEMPTS = int(os.getenv('RETRY_ATTEMPTS', 10)) # Max number of retries before giving up | ||
|
||
|
||
def retry_exponential_if_exception_type(exc_type, logger): | ||
""" | ||
Decorator function that returns the tenacity @retry decorator with our commonly-used config | ||
:param exc_type: Type of exception (or tuple of types) to retry if encountered | ||
:param logger: A logger instance to send retry logs to | ||
:return: Result of tenacity.retry decorator function | ||
""" | ||
return retry(retry=retry_if_exception_type(exc_type), | ||
wait=wait_exponential(multiplier=RetryParameters.MULTIPLIER, min=RetryParameters.MIN, max=RetryParameters.MAX), | ||
stop=stop_after_attempt(RetryParameters.ATTEMPTS), | ||
before_sleep=before_sleep_log(logger, logging.DEBUG), | ||
reraise=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
from calrissian.retry import retry_exponential_if_exception_type | ||
from unittest import TestCase | ||
from unittest.mock import Mock, patch | ||
|
||
|
||
class RetryTestCase(TestCase): | ||
def setUp(self): | ||
self.logger = Mock() | ||
self.mock = Mock() | ||
|
||
def setup_mock_retry_parameters(self, mock_retry_parameters): | ||
mock_retry_parameters.MULTIPLIER = 0.001 | ||
mock_retry_parameters.MIN = 0.001 | ||
mock_retry_parameters.MAX = 0.010 | ||
mock_retry_parameters.ATTEMPTS = 5 | ||
|
||
def test_retry_calls_wrapped_function(self): | ||
@retry_exponential_if_exception_type(ValueError, self.logger) | ||
def func(): | ||
return self.mock() | ||
|
||
result = func() | ||
self.assertEqual(result, self.mock.return_value) | ||
self.assertEqual(self.mock.call_count, 1) | ||
|
||
@patch('calrissian.retry.RetryParameters') | ||
def test_retry_gives_up_and_raises(self, mock_retry_parameters): | ||
self.setup_mock_retry_parameters(mock_retry_parameters) | ||
self.mock.side_effect = ValueError('value error') | ||
|
||
@retry_exponential_if_exception_type(ValueError, self.logger) | ||
def func(): | ||
self.mock() | ||
|
||
with self.assertRaisesRegex(ValueError, 'value error'): | ||
func() | ||
|
||
self.assertEqual(self.mock.call_count, 5) | ||
|
||
@patch('calrissian.retry.RetryParameters') | ||
def test_retry_eventually_succeeds_without_exception(self, mock_retry_parameters): | ||
self.setup_mock_retry_parameters(mock_retry_parameters) | ||
|
||
@retry_exponential_if_exception_type(ValueError, self.logger) | ||
def func(): | ||
r = self.mock() | ||
if self.mock.call_count < 3: | ||
raise ValueError('value error') | ||
return r | ||
|
||
result = func() | ||
|
||
self.assertEqual(result, self.mock.return_value) | ||
self.assertEqual(self.mock.call_count, 3) | ||
|
||
@patch('calrissian.retry.RetryParameters') | ||
def test_retry_raises_other_exceptions_without_second_attempt(self, mock_retry_parameters): | ||
self.setup_mock_retry_parameters(mock_retry_parameters) | ||
|
||
class ExceptionA(Exception): pass | ||
class ExceptionB(Exception): pass | ||
|
||
self.mock.side_effect = ExceptionA('exception a') | ||
|
||
@retry_exponential_if_exception_type(ExceptionB, self.logger) | ||
def func(): | ||
self.mock() | ||
|
||
with self.assertRaisesRegex(ExceptionA, 'exception a'): | ||
func() | ||
|
||
self.assertEqual(self.mock.call_count, 1) |