Skip to content

Commit 99f02d2

Browse files
committed
SNOW-19748: added better error message in case of 403, 502 and 504. Corrected timeout calculations.
1 parent 373359a commit 99f02d2

File tree

3 files changed

+174
-26
lines changed

3 files changed

+174
-26
lines changed

errors.py

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

165-
def __init__(self, **_):
166-
Error.__init__(self, msg=u'HTTP 500: InternalServerError')
165+
def __init__(self, **kwargs):
166+
Error.__init__(
167+
self,
168+
msg=kwargs.get('msg') or u'HTTP 500: Internal Server Error',
169+
errno=kwargs.get('errno'),
170+
sqlstate=kwargs.get('sqlstate'),
171+
sfqid=kwargs.get('sfqid'))
167172

168173

169174
class ServiceUnavailableError(Error):
170175
u"""Exception for 503 HTTP code for retry"""
171176

172-
def __init__(self, **_):
173-
Error.__init__(self, msg=u'HTTP 503: ServiceUnavailable')
177+
def __init__(self, **kwargs):
178+
Error.__init__(
179+
self, msg=kwargs.get('msg') or u'HTTP 503: Service Unavailable',
180+
errno=kwargs.get('errno'),
181+
sqlstate=kwargs.get('sqlstate'),
182+
sfqid=kwargs.get('sfqid'))
174183

175184

176185
class GatewayTimeoutError(Error):
177186
u"""Exception for 504 HTTP error for retry"""
178187

179-
def __init__(self, **_):
180-
Error.__init__(self, msg=u'HTTP 504: GatewayTimeout')
188+
def __init__(self, **kwargs):
189+
Error.__init__(
190+
self, msg=kwargs.get('msg') or u'HTTP 504: Gateway Timeout',
191+
errno=kwargs.get('errno'),
192+
sqlstate=kwargs.get('sqlstate'),
193+
sfqid=kwargs.get('sfqid'))
181194

182195

183196
class ForbiddenError(Error):
184197
"""Exception for 403 HTTP error for retry"""
185198

186-
def __init__(self, **_):
187-
Error.__init__(self, msg=u'HTTP 403: Forbidden')
199+
def __init__(self, **kwargs):
200+
Error.__init__(
201+
self, msg=kwargs.get('msg') or u'HTTP 403: Forbidden',
202+
errno=kwargs.get('errno'),
203+
sqlstate=kwargs.get('sqlstate'),
204+
sfqid=kwargs.get('sfqid'))
188205

189206

190207
class RequestTimeoutError(Error):
191208
u"""Exception for 408 HTTP error for retry"""
192209

193-
def __init__(self, **_):
194-
Error.__init__(self, msg=u'HTTP 408: RequestTimeout')
210+
def __init__(self, **kwargs):
211+
Error.__init__(
212+
self, msg=kwargs.get('msg') or u'HTTP 408: Request Timeout',
213+
errno=kwargs.get('errno'),
214+
sqlstate=kwargs.get('sqlstate'),
215+
sfqid=kwargs.get('sfqid'))
195216

196217

197218
class BadRequest(Error):
198219
u"""Exception for 400 HTTP error for retry"""
199220

200-
def __init__(self, **_):
201-
Error.__init__(self, msg=u'HTTP 400: BadRequest')
221+
def __init__(self, **kwargs):
222+
Error.__init__(
223+
self, msg=kwargs.get('msg') or u'HTTP 400: Bad Request',
224+
errno=kwargs.get('errno'),
225+
sqlstate=kwargs.get('sqlstate'),
226+
sfqid=kwargs.get('sfqid'))
202227

203228

204229
class BadGatewayError(Error):
205230
u"""Exception for 502 HTTP error for retry"""
206231

207-
def __init__(self, **_):
208-
Error.__init__(self, msg=u'HTTP 502: BadGateway')
232+
def __init__(self, **kwargs):
233+
Error.__init__(
234+
self, msg=kwargs.get('msg') or u'HTTP 502: Bad Gateway',
235+
errno=kwargs.get('errno'),
236+
sqlstate=kwargs.get('sqlstate'),
237+
sfqid=kwargs.get('sfqid'))

network.py

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import copy
77
import gzip
88
import json
9-
import logging
109
import platform
1110
import sys
1211
import time
@@ -276,9 +275,43 @@ def authenticate(self, account, user, password, master_token=None,
276275
"body['data']: %s",
277276
{k: v for (k, v) in body[u'data'].items() if k != u'PASSWORD'})
278277

279-
ret = self._post_request(
280-
url, headers, json.dumps(body),
281-
timeout=self._connection._login_timeout)
278+
try:
279+
ret = self._post_request(
280+
url, headers, json.dumps(body),
281+
timeout=self._connection._login_timeout)
282+
except ForbiddenError as err:
283+
# HTTP 403
284+
raise err.__class__(
285+
msg=(u"Failed to connect to DB. "
286+
u"Verify the account name is correct: {host}:{port}, "
287+
u"proxies={proxy_host}:{proxy_port}, "
288+
u"proxy_user={proxy_user}. {message}").format(
289+
host=self._host,
290+
port=self._port,
291+
proxy_host=self._proxy_host,
292+
proxy_port=self._proxy_port,
293+
proxy_user=self._proxy_user,
294+
message=TO_UNICODE(err)
295+
),
296+
errno=ER_FAILED_TO_CONNECT_TO_DB,
297+
sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED)
298+
except (ServiceUnavailableError, BadGatewayError) as err:
299+
# HTTP 502/504
300+
raise err.__class__(
301+
msg=(u"Failed to connect to DB. "
302+
u"Service is unavailable: {host}:{port}, "
303+
u"proxies={proxy_host}:{proxy_port}, "
304+
u"proxy_user={proxy_user}. {message}").format(
305+
host=self._host,
306+
port=self._port,
307+
proxy_host=self._proxy_host,
308+
proxy_port=self._proxy_port,
309+
proxy_user=self._proxy_user,
310+
message=TO_UNICODE(err)
311+
),
312+
errno=ER_FAILED_TO_CONNECT_TO_DB,
313+
sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED)
314+
282315
# this means we are waiting for MFA authentication
283316
if ret[u'data'].get(u'nextAction') and ret[u'data'][
284317
u'nextAction'] == u'EXT_AUTHN_DUO_ALL':
@@ -699,8 +732,7 @@ def request_thread(result_queue):
699732
ProtocolError,
700733
OpenSSL.SSL.SysCallError, ValueError) as err:
701734
logger.exception('who is hitting error?')
702-
if logger.getEffectiveLevel() <= logging.DEBUG:
703-
logger.debug(err)
735+
logger.debug(err)
704736
if not isinstance(err, OpenSSL.SSL.SysCallError) or \
705737
err.args[0] in (
706738
errno.ECONNRESET,
@@ -714,12 +746,14 @@ def request_thread(result_queue):
714746
except ConnectionError as err:
715747
logger.exception(u'ConnectionError: %s', err)
716748
result_queue.put((OperationalError(
749+
# no full_url is required in the message
750+
# as err includes all information
717751
msg=u'Failed to connect: {0}'.format(err),
718752
errno=ER_FAILED_TO_SERVER,
719753
sqlstate=SQLSTATE_CONNECTION_WAS_NOT_ESTABLISHED
720754
), False))
721755
except ValueError as err:
722-
logger.exception(u'Return value is not JSON: %s', err)
756+
logger.exception(u'Return value is NOT JSON: %s', err)
723757
result_queue.put((InterfaceError(
724758
msg=u"Failed to decode JSON output",
725759
errno=ER_FAILED_TO_REQUEST,
@@ -752,10 +786,19 @@ def request_thread(result_queue):
752786
request_thread_timeout,
753787
request_timeout,
754788
retry_cnt + 1)
789+
start_request_thread = time.time()
755790
th.join(timeout=request_thread_timeout)
756791
logger.debug('request thread joined')
792+
if request_timeout is not None:
793+
request_timeout -= min(
794+
int(time.time() - start_request_thread),
795+
request_timeout)
796+
start_get_queue = time.time()
757797
return_object, retryable = request_result_queue.get(
758-
timeout=int(request_thread_timeout / 2))
798+
timeout=int(request_thread_timeout / 4))
799+
if request_timeout is not None:
800+
request_timeout -= min(
801+
int(time.time() - start_get_queue), request_timeout)
759802
logger.debug('request thread returned object')
760803
if retryable:
761804
raise RequestRetry()
@@ -797,7 +840,41 @@ def request_thread(result_queue):
797840
time.sleep(sleeping_time)
798841
retry_cnt += 1
799842

800-
if isinstance(return_object, Error):
843+
if return_object is None:
844+
if data:
845+
try:
846+
decoded_data = json.loads(data)
847+
if decoded_data.get(
848+
'data') and decoded_data['data'].get('PASSWORD'):
849+
# masking the password
850+
decoded_data['data']['PASSWORD'] = '********'
851+
data = json.dumps(decoded_data)
852+
except:
853+
logger.info("data is not JSON")
854+
logger.error(
855+
u'Failed to get the response. Hanging? '
856+
u'method: {method}, url: {url}, headers:{headers}, '
857+
u'data: {data}, proxies: {proxies}'.format(
858+
method=method,
859+
url=full_url,
860+
headers=headers,
861+
data=data,
862+
proxies=proxies
863+
)
864+
)
865+
Error.errorhandler_wrapper(
866+
conn, None, OperationalError,
867+
{
868+
u'msg': u'Failed to get the response. Hanging? '
869+
u'method: {method}, url: {url}, '
870+
u'proxies: {proxies}'.format(
871+
method=method,
872+
url=full_url,
873+
proxies=proxies
874+
),
875+
u'errno': ER_FAILED_TO_REQUEST,
876+
})
877+
elif isinstance(return_object, Error):
801878
Error.errorhandler_wrapper(conn, None, return_object)
802879
elif isinstance(return_object, Exception):
803880
Error.errorhandler_wrapper(
@@ -844,7 +921,7 @@ def authenticate_by_saml(self, authenticator, account, user, password):
844921
Error.errorhandler_wrapper(
845922
self._connection, None, DatabaseError,
846923
{
847-
u'msg': (u"failed to connect to DB: {host}:{port}, "
924+
u'msg': (u"Failed to connect to DB: {host}:{port}, "
848925
u"proxies={proxy_host}:{proxy_port}, "
849926
u"proxy_user={proxy_user}, "
850927
u"{message}").format(

test/test_connection.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import snowflake.connector
1010
from snowflake.connector import (
1111
DatabaseError,
12-
ProgrammingError)
13-
from snowflake.connector.errors import ForbiddenError
12+
ProgrammingError, InterfaceError, OperationalError)
13+
from snowflake.connector.errors import (ForbiddenError)
1414

1515

1616
def test_basic(conn_testaccount):
@@ -238,3 +238,45 @@ def test_invalid_account_timeout():
238238
password='test',
239239
login_timeout=5
240240
)
241+
242+
243+
@pytest.mark.timeout(15)
244+
def test_invalid_host():
245+
with pytest.raises(InterfaceError):
246+
snowflake.connector.connect(
247+
account='bogus',
248+
user='test',
249+
password='test',
250+
host='bogus.com',
251+
login_timeout=5
252+
)
253+
254+
255+
@pytest.mark.timeout(15)
256+
def test_invalid_port(db_parameters):
257+
with pytest.raises(OperationalError):
258+
snowflake.connector.connect(
259+
protocol='http',
260+
account='testaccount',
261+
user=db_parameters['user'],
262+
password=db_parameters['password'],
263+
host=db_parameters['host'],
264+
port=12345,
265+
login_timeout=5,
266+
)
267+
268+
269+
@pytest.mark.timeout(15)
270+
def test_invalid_proxy(db_parameters):
271+
with pytest.raises(OperationalError):
272+
snowflake.connector.connect(
273+
protocol='http',
274+
account='testaccount',
275+
user=db_parameters['user'],
276+
password=db_parameters['password'],
277+
host=db_parameters['host'],
278+
port=db_parameters['port'],
279+
login_timeout=5,
280+
proxy_host='localhost',
281+
proxy_port='3333'
282+
)

0 commit comments

Comments
 (0)