Skip to content

Commit 8e9e4c6

Browse files
committed
SNOW-27443: Fixed the error handling in network connection
1 parent c44a2c0 commit 8e9e4c6

File tree

4 files changed

+55
-24
lines changed

4 files changed

+55
-24
lines changed

connection.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
u'ocsp_response_cache_filename': None, # snowflake internal
7575
u'converter_class': SnowflakeConverter, # snowflake internal
7676
u'chunk_downloader_class': SnowflakeChunkDownloader, # snowflake internal
77+
u'retry_connection_auth': True, # snowflake internal
7778
}
7879

7980
APPLICATION_RE = re.compile(r'[\w\d_]+')
@@ -451,6 +452,7 @@ def __open_connection(self, mfa_callback, password_callback):
451452
mfa_callback=mfa_callback,
452453
password_callback=password_callback,
453454
session_parameters=self._session_parameters,
455+
retry_connection_auth=self._retry_connection_auth,
454456
)
455457
self._password = None
456458
self._token = self._con.token

errors.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,47 +162,47 @@ class NotSupportedError(DatabaseError):
162162
class InternalServerError(Error):
163163
u"""Exception for 500 HTTP code for retry"""
164164

165-
def __init__(self):
165+
def __init__(self, **_):
166166
Error.__init__(self, msg=u'HTTP 500: InternalServerError')
167167

168168

169169
class ServiceUnavailableError(Error):
170170
u"""Exception for 503 HTTP code for retry"""
171171

172-
def __init__(self):
172+
def __init__(self, **_):
173173
Error.__init__(self, msg=u'HTTP 503: ServiceUnavailable')
174174

175175

176176
class GatewayTimeoutError(Error):
177177
u"""Exception for 504 HTTP error for retry"""
178178

179-
def __init__(self):
179+
def __init__(self, **_):
180180
Error.__init__(self, msg=u'HTTP 504: GatewayTimeout')
181181

182182

183183
class ForbiddenError(Error):
184184
"""Exception for 403 HTTP error for retry"""
185185

186-
def __init__(self):
186+
def __init__(self, **_):
187187
Error.__init__(self, msg=u'HTTP 403: Forbidden')
188188

189189

190190
class RequestTimeoutError(Error):
191191
u"""Exception for 408 HTTP error for retry"""
192192

193-
def __init__(self):
193+
def __init__(self, **_):
194194
Error.__init__(self, msg=u'HTTP 408: RequestTimeout')
195195

196196

197197
class BadRequest(Error):
198198
u"""Exception for 400 HTTP error for retry"""
199199

200-
def __init__(self):
200+
def __init__(self, **_):
201201
Error.__init__(self, msg=u'HTTP 400: BadRequest')
202202

203203

204204
class BadGatewayError(Error):
205205
u"""Exception for 502 HTTP error for retry"""
206206

207-
def __init__(self):
207+
def __init__(self, **_):
208208
Error.__init__(self, msg=u'HTTP 502: BadGateway')

network.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
from .version import VERSION
4747

4848
"""
49-
Moneky patch for PyOpenSSL Socket wrapper
49+
Monkey patch for PyOpenSSL Socket wrapper
5050
"""
5151
ssl_wrap_socket.inject_into_urllib3()
5252

@@ -209,7 +209,7 @@ def authenticate(self, account, user, password, master_token=None,
209209
warehouse=None, role=None, passcode=None,
210210
passcode_in_password=False, saml_response=None,
211211
mfa_callback=None, password_callback=None,
212-
session_parameters=None):
212+
session_parameters=None, retry_connection_auth=True):
213213
self.logger.info(u'authenticate')
214214

215215
if token and master_token:
@@ -296,9 +296,10 @@ def authenticate(self, account, user, password, master_token=None,
296296
"body['data']: %s",
297297
{k: v for (k, v) in body[u'data'].items() if k != u'PASSWORD'})
298298

299-
# retry 10 times for authentication
299+
max_retry = 10 if retry_connection_auth else 1
300+
# max_retry times for authentication
300301
ret = self._post_request(
301-
url, headers, json.dumps(body), retry=10)
302+
url, headers, json.dumps(body), retry=max_retry)
302303
# this means we are waiting for MFA authentication
303304
if ret[u'data'].get(u'nextAction') and ret[u'data'][
304305
u'nextAction'] == u'EXT_AUTHN_DUO_ALL':
@@ -307,8 +308,9 @@ def authenticate(self, account, user, password, master_token=None,
307308
self.ret = None
308309

309310
def post_request_wrapper(self, url, headers, body):
310-
# retry 10 times for MFA approval
311-
self.ret = self._post_request(url, headers, body, retry=10)
311+
# max_retry times for MFA approval
312+
self.ret = self._post_request(
313+
url, headers, body, retry=max_retry)
312314

313315
# send new request to wait until MFA is approved
314316
t = Thread(target=post_request_wrapper,
@@ -327,9 +329,9 @@ def post_request_wrapper(self, url, headers, body):
327329
body = copy.deepcopy(body_template)
328330
body[u'inFlightCtx'] = ret[u'data'][u'inFlightCtx']
329331
# final request to get tokens
330-
# retry 10 times
332+
# max_retry times
331333
ret = self._post_request(
332-
url, headers, json.dumps(body), retry=10)
334+
url, headers, json.dumps(body), retry=max_retry)
333335

334336
elif ret[u'data'].get(u'nextAction') and ret[u'data'][
335337
u'nextAction'] == u'PWD_CHANGE':
@@ -339,9 +341,9 @@ def post_request_wrapper(self, url, headers, body):
339341
body[u'data'][u"LOGIN_NAME"] = user
340342
body[u'data'][u"PASSWORD"] = password
341343
body[u'data'][u'CHOSEN_NEW_PASSWORD'] = password_callback()
342-
# retry 10 times for New Password input
344+
# max_retry times for New Password input
343345
ret = self._post_request(
344-
url, headers, json.dumps(body), retry=10)
346+
url, headers, json.dumps(body), retry=max_retry)
345347

346348
self.logger.debug(u'completed authentication')
347349
if not ret[u'success']:
@@ -560,7 +562,7 @@ def _post_request(self, url, headers, body, token=None, retry=10,
560562
max_connection_pool=self._max_connection_pool)
561563
self.logger.debug(
562564
u'ret[code] = {code}, after post request'.format(
563-
code=(ret[u'code'] if u'code' in ret else u'N/A')))
565+
code=(ret.get(u'code', u'N/A'))))
564566

565567
if u'code' in ret and ret[u'code'] == SESSION_EXPIRED_GS_CODE:
566568
ret = self._renew_session()
@@ -685,7 +687,7 @@ def request_thread(result_queue):
685687
elif raw_ret.status_code in STATUS_TO_EXCEPTION:
686688
# retryable exceptions
687689
result_queue.put(
688-
(STATUS_TO_EXCEPTION[raw_ret.status_code], True))
690+
(STATUS_TO_EXCEPTION[raw_ret.status_code](), True))
689691
elif raw_ret.status_code == UNAUTHORIZED and \
690692
catch_okta_unauthorized_error:
691693
# OKTA Unauthorized errors
@@ -757,6 +759,7 @@ def request_thread(result_queue):
757759
sleeping_time = 1
758760
return_object = None
759761
for retry_cnt in range(retry):
762+
return_object = None
760763
request_result_queue = Queue()
761764
th = Thread(name='request_thread', target=request_thread,
762765
args=(request_result_queue,))
@@ -801,8 +804,17 @@ def request_thread(result_queue):
801804
retry,
802805
sleeping_time)
803806
time.sleep(sleeping_time)
804-
return_object = None
805807

808+
if isinstance(return_object, Error):
809+
Error.errorhandler_wrapper(conn, None, return_object)
810+
elif isinstance(return_object, Exception):
811+
Error.errorhandler_wrapper(
812+
conn, None, OperationalError,
813+
{
814+
u'msg': u'Failed to execute request: {0}'.format(
815+
return_object),
816+
u'errno': ER_FAILED_TO_REQUEST,
817+
})
806818
return return_object
807819

808820
def authenticate_by_saml(self, authenticator, account, user, password):

test/test_connection.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
import pytest
88

99
import snowflake.connector
10-
from snowflake.connector import (DatabaseError,
11-
ProgrammingError)
10+
from snowflake.connector import (
11+
DatabaseError,
12+
ProgrammingError)
13+
from snowflake.connector.errors import ForbiddenError
1214

1315

1416
def test_basic(conn_testaccount):
@@ -120,7 +122,9 @@ def test_bogus(db_parameters):
120122
password='bogus',
121123
host=db_parameters['host'],
122124
port=db_parameters['port'],
123-
account=db_parameters['account'])
125+
account=db_parameters['account'],
126+
retry_connection_auth=False
127+
)
124128

125129
with pytest.raises(DatabaseError):
126130
snowflake.connector.connect(
@@ -130,7 +134,8 @@ def test_bogus(db_parameters):
130134
account='testaccount123',
131135
host=db_parameters['host'],
132136
port=db_parameters['port'],
133-
insecure_mode=True)
137+
insecure_mode=True,
138+
retry_connection_auth=False)
134139

135140
with pytest.raises(DatabaseError):
136141
snowflake.connector.connect(
@@ -140,6 +145,7 @@ def test_bogus(db_parameters):
140145
account='testaccount123',
141146
host=db_parameters['host'],
142147
port=db_parameters['port'],
148+
retry_connection_auth=False
143149
)
144150

145151
with pytest.raises(ProgrammingError):
@@ -150,6 +156,7 @@ def test_bogus(db_parameters):
150156
account='testaccount123',
151157
host=db_parameters['host'],
152158
port=db_parameters['port'],
159+
retry_connection_auth=False
153160
)
154161

155162

@@ -224,3 +231,13 @@ def exe(sql):
224231
db_parameters['database']))
225232
exe('drop role snowdog_role')
226233
exe('drop user if exists snowdog')
234+
235+
236+
def test_invalid_account():
237+
with pytest.raises(ForbiddenError):
238+
snowflake.connector.connect(
239+
account='bogus',
240+
user='test',
241+
password='test',
242+
retry_connection_auth=False
243+
)

0 commit comments

Comments
 (0)