Skip to content

Commit 2724805

Browse files
authored
Merge pull request googleapis#2040 from daspecster/retry-system3-tests
Playing with retries.
2 parents 5a6ba14 + e06815d commit 2724805

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

system_tests/bigquery.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
from gcloud import _helpers
2121
from gcloud.environment_vars import TESTS_PROJECT
2222
from gcloud import bigquery
23+
from gcloud.exceptions import Forbidden
2324

25+
from retry import Retry
2426
from system_test_utils import unique_resource_id
2527

2628

@@ -90,7 +92,15 @@ def test_update_dataset(self):
9092
after = [grant for grant in dataset.access_grants
9193
if grant.entity_id != 'projectWriters']
9294
dataset.access_grants = after
93-
dataset.update()
95+
96+
# We need to wait to stay within the rate limits.
97+
# The alternative outcome is a 403 Forbidden response from upstream.
98+
# See: https://cloud.google.com/bigquery/quota-policy
99+
@Retry(Forbidden, tries=2, delay=30)
100+
def update_dataset():
101+
dataset.update()
102+
103+
update_dataset()
94104
self.assertEqual(len(dataset.access_grants), len(after))
95105
for found, expected in zip(dataset.access_grants, after):
96106
self.assertEqual(found.role, expected.role)
@@ -188,7 +198,15 @@ def test_patch_table(self):
188198
def test_update_table(self):
189199
dataset = Config.CLIENT.dataset(DATASET_NAME)
190200
self.assertFalse(dataset.exists())
191-
dataset.create()
201+
202+
# We need to wait to stay within the rate limits.
203+
# The alternative outcome is a 403 Forbidden response from upstream.
204+
# See: https://cloud.google.com/bigquery/quota-policy
205+
@Retry(Forbidden, tries=2, delay=30)
206+
def create_dataset():
207+
dataset.create()
208+
209+
create_dataset()
192210
self.to_delete.append(dataset)
193211
TABLE_NAME = 'test_table'
194212
full_name = bigquery.SchemaField('full_name', 'STRING',

system_tests/retry.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import time
2+
from functools import wraps
3+
4+
import six
5+
6+
7+
class Retry(object):
8+
"""Retry class for retrying eventually consistent resources in testing."""
9+
10+
def __init__(self, exception, tries=4, delay=3, backoff=2, logger=None):
11+
"""Retry calling the decorated function using an exponential backoff.
12+
13+
:type exception: Exception or tuple of Exceptions
14+
:param exception: The exception to check or may be a tuple of
15+
exceptions to check.
16+
17+
:type tries: int
18+
:param tries: Number of times to try (not retry) before giving up.
19+
20+
:type delay: int
21+
:param delay: Initial delay between retries in seconds.
22+
23+
:type backoff: int
24+
:param backoff: Backoff multiplier e.g. value of 2 will double the
25+
delay each retry.
26+
27+
:type logger: logging.Logger instance
28+
:param logger: Logger to use. If None, print.
29+
"""
30+
31+
self.exception = exception
32+
self.tries = tries
33+
self.delay = delay
34+
self.backoff = backoff
35+
self.logger = logger.warning if logger else six.print_
36+
37+
def __call__(self, to_wrap):
38+
@wraps(to_wrap)
39+
def wrapped_function(*args, **kwargs):
40+
tries_counter = self.tries
41+
delay = self.delay
42+
while tries_counter > 0:
43+
try:
44+
return to_wrap(*args, **kwargs)
45+
except self.exception as caught_exception:
46+
msg = ("%s, Trying again in %d seconds..." %
47+
(str(caught_exception), delay))
48+
self.logger(msg)
49+
50+
time.sleep(delay)
51+
tries_counter -= 1
52+
delay *= self.backoff
53+
return to_wrap(*args, **kwargs)
54+
55+
return wrapped_function

0 commit comments

Comments
 (0)