summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-01-30 20:06:41 +0000
committerGerrit Code Review <review@openstack.org>2015-01-30 20:06:41 +0000
commit67810cc83b37c75d5532f5cd697b578df2db6740 (patch)
tree39830738935b432b8ffadbb1f96d8c4512ffa328
parentd78e3aa12610b9c01e610843bcc44c208d34a4e1 (diff)
parenteeb7ea22bfead713ee54192130796508953b1dec (diff)
downloadoslo-db-67810cc83b37c75d5532f5cd697b578df2db6740.tar.gz
Merge "Add retry decorator allowing to retry DB operations on request"
-rw-r--r--oslo_db/api.py28
-rw-r--r--oslo_db/exception.py9
-rw-r--r--oslo_db/tests/old_import_api/test_api.py21
-rw-r--r--oslo_db/tests/test_api.py21
4 files changed, 75 insertions, 4 deletions
diff --git a/oslo_db/api.py b/oslo_db/api.py
index dc71d96..98e4d75 100644
--- a/oslo_db/api.py
+++ b/oslo_db/api.py
@@ -24,6 +24,7 @@ API methods.
"""
import logging
+import sys
import threading
import time
@@ -69,6 +70,16 @@ def retry_on_deadlock(f):
return f
+def retry_on_request(f):
+ """Retry a DB API call if RetryRequest exception was received.
+
+ wrap_db_entry will be applied to all db.api functions marked with this
+ decorator.
+ """
+ f.enable_retry_on_request = True
+ return f
+
+
class wrap_db_retry(object):
"""Retry db.api methods, if db_error raised
@@ -91,8 +102,9 @@ class wrap_db_retry(object):
:type max_retry_interval: int
"""
- def __init__(self, retry_interval, max_retries, inc_retry_interval,
- max_retry_interval, retry_on_disconnect, retry_on_deadlock):
+ def __init__(self, retry_interval=0, max_retries=0, inc_retry_interval=0,
+ max_retry_interval=0, retry_on_disconnect=False,
+ retry_on_deadlock=False, retry_on_request=False):
super(wrap_db_retry, self).__init__()
self.db_error = ()
@@ -100,6 +112,8 @@ class wrap_db_retry(object):
self.db_error += (exception.DBConnectionError, )
if retry_on_deadlock:
self.db_error += (exception.DBDeadlock, )
+ if retry_on_request:
+ self.db_error += (exception.RetryRequest, )
self.retry_interval = retry_interval
self.max_retries = max_retries
self.inc_retry_interval = inc_retry_interval
@@ -118,6 +132,10 @@ class wrap_db_retry(object):
except db_error as e:
if remaining == 0:
LOG.exception(_LE('DB exceeded retry limit.'))
+ if isinstance(e, exception.RetryRequest):
+ six.reraise(type(e.inner_exc),
+ e.inner_exc,
+ sys.exc_info()[2])
raise e
if remaining != -1:
remaining -= 1
@@ -210,15 +228,17 @@ class DBAPI(object):
retry_on_disconnect = self.use_db_reconnect and getattr(
attr, 'enable_retry_on_disconnect', False)
retry_on_deadlock = getattr(attr, 'enable_retry_on_deadlock', False)
+ retry_on_request = getattr(attr, 'enable_retry_on_request', False)
- if retry_on_disconnect or retry_on_deadlock:
+ if retry_on_disconnect or retry_on_deadlock or retry_on_request:
attr = wrap_db_retry(
retry_interval=self.retry_interval,
max_retries=self.max_retries,
inc_retry_interval=self.inc_retry_interval,
max_retry_interval=self.max_retry_interval,
retry_on_disconnect=retry_on_disconnect,
- retry_on_deadlock=retry_on_deadlock)(attr)
+ retry_on_deadlock=retry_on_deadlock,
+ retry_on_request=retry_on_request)(attr)
return attr
diff --git a/oslo_db/exception.py b/oslo_db/exception.py
index 5de7f1e..f950f6a 100644
--- a/oslo_db/exception.py
+++ b/oslo_db/exception.py
@@ -171,3 +171,12 @@ class BackendNotAvailable(Exception):
within a test suite.
"""
+
+
+class RetryRequest(Exception):
+ """Error raised when DB operation needs to be retried.
+
+ That could be intentionally raised by the code without any real DB errors.
+ """
+ def __init__(self, inner_exc):
+ self.inner_exc = inner_exc
diff --git a/oslo_db/tests/old_import_api/test_api.py b/oslo_db/tests/old_import_api/test_api.py
index 3664e24..1fe3bf3 100644
--- a/oslo_db/tests/old_import_api/test_api.py
+++ b/oslo_db/tests/old_import_api/test_api.py
@@ -279,3 +279,24 @@ class DBDeadlockTestCase(DBAPITestCase):
self.assertEqual(
0, self.test_db_api.error_counter,
'Counter not decremented, retry logic probably failed.')
+
+
+class DBRetryRequestCase(DBAPITestCase):
+ def test_retry_wrapper_succeeds(self):
+ @api.wrap_db_retry(max_retries=10, retry_on_request=True)
+ def some_method():
+ pass
+
+ some_method()
+
+ def test_retry_wrapper_reaches_limit(self):
+ max_retries = 10
+
+ @api.wrap_db_retry(max_retries=10, retry_on_request=True)
+ def some_method(res):
+ res['result'] += 1
+ raise exception.RetryRequest(ValueError())
+
+ res = {'result': 0}
+ self.assertRaises(ValueError, some_method, res)
+ self.assertEqual(max_retries + 1, res['result'])
diff --git a/oslo_db/tests/test_api.py b/oslo_db/tests/test_api.py
index 171e360..18dc586 100644
--- a/oslo_db/tests/test_api.py
+++ b/oslo_db/tests/test_api.py
@@ -175,3 +175,24 @@ class DBReconnectTestCase(DBAPITestCase):
self.assertNotEqual(
0, self.test_db_api.error_counter,
'Retry did not stop after sql_max_retries iterations.')
+
+
+class DBRetryRequestCase(DBAPITestCase):
+ def test_retry_wrapper_succeeds(self):
+ @api.wrap_db_retry(max_retries=10, retry_on_request=True)
+ def some_method():
+ pass
+
+ some_method()
+
+ def test_retry_wrapper_reaches_limit(self):
+ max_retries = 10
+
+ @api.wrap_db_retry(max_retries=10, retry_on_request=True)
+ def some_method(res):
+ res['result'] += 1
+ raise exception.RetryRequest(ValueError())
+
+ res = {'result': 0}
+ self.assertRaises(ValueError, some_method, res)
+ self.assertEqual(max_retries + 1, res['result'])