15
15
"""Collection of utilities to support zaza tests etc."""
16
16
17
17
18
+ import logging
18
19
import time
19
20
20
21
from keystoneauth1 .exceptions .connection import ConnectFailure
21
22
23
+ NEVER_RETRY_EXCEPTIONS = (
24
+ AssertionError ,
25
+ AttributeError ,
26
+ ImportError ,
27
+ IndexError ,
28
+ KeyError ,
29
+ NotImplementedError ,
30
+ OverflowError ,
31
+ RecursionError ,
32
+ ReferenceError ,
33
+ RuntimeError ,
34
+ SyntaxError ,
35
+ IndentationError ,
36
+ SystemExit ,
37
+ TypeError ,
38
+ UnicodeError ,
39
+ ZeroDivisionError ,
40
+ )
41
+
22
42
23
43
class ObjectRetrierWraps (object ):
24
44
"""An automatic retrier for an object.
@@ -76,35 +96,39 @@ def __init__(self, obj, num_retries=3, initial_interval=5.0, backoff=1.0,
76
96
If a list, then it will only retry if the exception is one of the
77
97
ones in the list.
78
98
:type retry_exceptions: List[Exception]
99
+ :param log: If False, disable logging; if None (the default) or True,
100
+ use logging.warn; otherwise use the passed param `log`.
101
+ :type param: None | Boolean | Callable
79
102
"""
80
103
# Note we use semi-private variable names that shouldn't clash with any
81
104
# on the actual object.
82
105
self .__obj = obj
106
+ if log in (None , True ):
107
+ _log = logging .warning
108
+ elif log is False :
109
+ _log = lambda * _ , ** __ : None # noqa
110
+ else :
111
+ _log = log
83
112
self .__kwargs = {
84
113
'num_retries' : num_retries ,
85
114
'initial_interval' : initial_interval ,
86
115
'backoff' : backoff ,
87
116
'max_interval' : max_interval ,
88
117
'total_wait' : total_wait ,
89
118
'retry_exceptions' : retry_exceptions ,
90
- 'log' : log or ( lambda x : None ) ,
119
+ 'log' : _log ,
91
120
}
121
+ _log (f"ObjectRetrierWraps: wrapping { self .__obj } " )
92
122
93
123
def __getattr__ (self , name ):
94
124
"""Get attribute; delegates to wrapped object."""
95
- # Note the above may generate an attribute error; we expect this and
96
- # will fail with an attribute error.
97
- attr = getattr (self .__obj , name )
98
- if callable (attr ) or hasattr (attr , "__getattr__" ):
125
+ obj = self .__obj
126
+ attr = getattr (obj , name )
127
+ if callable (attr ):
99
128
return ObjectRetrierWraps (attr , ** self .__kwargs )
100
- else :
129
+ if attr . __class__ . __module__ == 'builtins' :
101
130
return attr
102
- # TODO(ajkavanagh): Note detecting a property is a bit trickier. we
103
- # can do isinstance(attr, property), but then the act of accessing it
104
- # is what calls it. i.e. it would fail at the getattr(self.__obj,
105
- # name) stage. The solution is to check first, and if it's a property,
106
- # then treat it like the retrier. However, I think this is too
107
- # complex for the first go, and to use manual retries in that instance.
131
+ return ObjectRetrierWraps (attr , ** self .__kwargs )
108
132
109
133
def __call__ (self , * args , ** kwargs ):
110
134
"""Call the object; delegates to the wrapped object."""
@@ -126,16 +150,20 @@ def __call__(self, *args, **kwargs):
126
150
# is not in the list of retries, then raise an exception
127
151
# immediately. This means that if retry_exceptions is None,
128
152
# then the method is always retried.
153
+ if isinstance (e , NEVER_RETRY_EXCEPTIONS ):
154
+ log ("ObjectRetrierWraps: error {} is never caught; raising"
155
+ .format (str (e )))
156
+ raise
129
157
if (retry_exceptions is not None and
130
158
type (e ) not in retry_exceptions ):
131
159
raise
132
160
retry += 1
133
161
if retry > num_retries :
134
- log ("{} : exceeded number of retries, so erroring out "
135
- . format ( str ( obj )) )
162
+ log ("ObjectRetrierWraps : exceeded number of retries, "
163
+ "so erroring out" )
136
164
raise e
137
- log ("{} : call failed: retrying in {} seconds "
138
- .format (str ( obj ), wait ))
165
+ log ("ObjectRetrierWraps : call failed: retrying in {} "
166
+ "seconds" .format (wait ))
139
167
time .sleep (wait )
140
168
wait_so_far += wait
141
169
if wait_so_far >= total_wait :
0 commit comments