Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Salesforce login to fail after backing off 3 times #110

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
install_requires=[
'requests==2.20.0',
'singer-python==5.10.0',
'xmltodict==0.11.0'
'xmltodict==0.11.0',
'nose'
],
entry_points='''
[console_scripts]
Expand Down
59 changes: 37 additions & 22 deletions tap_salesforce/salesforce/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,18 @@
def log_backoff_attempt(details):
LOGGER.info("ConnectionError detected, triggering backoff: %d try", details.get("tries"))

def log_login_backoff(details):#what is passed in for details???
# log issue
# reset login timer
# error_message = str(e)
# if resp is None and hasattr(e, 'response') and e.response is not None: #pylint:disable=no-member
# resp = e.response #pylint:disable=no-member
# # NB: requests.models.Response is always falsy here. It is false if status code >= 400
# if isinstance(resp, requests.models.Response):
# error_message = error_message + ", Response from Salesforce: {}".format(resp.text)
# LOGGER.info(error_message)
LOGGER.info("Error logging in to Salesforce, triggering backoff: %d try", details.get("tries"))


def field_to_property_schema(field, mdata): # pylint:disable=too-many-branches
property_schema = {}
Expand Down Expand Up @@ -228,6 +240,7 @@ def __init__(self,
self.login_timer = None
self.data_url = "{}/services/data/v41.0/{}"
self.pk_chunking = False
self.refresh_time = None

# validate start_date
singer_utils.strptime(default_start_date)
Expand Down Expand Up @@ -273,6 +286,7 @@ def check_rest_quota_usage(self, headers):
factor=2,
on_backoff=log_backoff_attempt)
def _make_request(self, http_method, url, headers=None, body=None, stream=False, params=None):
self.ensure_fresh_credentials()
if http_method == "GET":
LOGGER.info("Making %s request to %s with params: %s", http_method, url, params)
resp = self.session.get(url, headers=headers, stream=stream, params=params)
Expand All @@ -293,6 +307,16 @@ def _make_request(self, http_method, url, headers=None, body=None, stream=False,

return resp

def ensure_fresh_credentials(self):
if self.refresh_time is None or time.time() > self.refresh_time:
self.login()

# pylint: disable=too-many-arguments
@backoff.on_exception(backoff.constant,
Exception,
max_tries=3,
interval=REFRESH_TOKEN_EXPIRATION_PERIOD,
on_backoff=log_login_backoff)
def login(self):
if self.is_sandbox:
login_url = 'https://test.salesforce.com/services/oauth2/token'
Expand All @@ -304,28 +328,19 @@ def login(self):

LOGGER.info("Attempting login via OAuth2")

resp = None
try:
resp = self._make_request("POST", login_url, body=login_body, headers={"Content-Type": "application/x-www-form-urlencoded"})

LOGGER.info("OAuth2 login successful")

auth = resp.json()

self.access_token = auth['access_token']
self.instance_url = auth['instance_url']
except Exception as e:
error_message = str(e)
if resp is None and hasattr(e, 'response') and e.response is not None: #pylint:disable=no-member
resp = e.response #pylint:disable=no-member
# NB: requests.models.Response is always falsy here. It is false if status code >= 400
if isinstance(resp, requests.models.Response):
error_message = error_message + ", Response from Salesforce: {}".format(resp.text)
raise Exception(error_message) from e
finally:
LOGGER.info("Starting new login timer")
self.login_timer = threading.Timer(REFRESH_TOKEN_EXPIRATION_PERIOD, self.login)
self.login_timer.start()
headers = {"Content-Type": "application/x-www-form-urlencoded"}

time_before_login = time.time()

resp = self.session.post(login_url, headers=headers, data=login_body)

LOGGER.info("OAuth2 login successful")

auth = resp.json()

self.access_token = auth['access_token']
self.instance_url = auth['instance_url']
self.refresh_time = time_before_login+REFRESH_TOKEN_EXPIRATION_PERIOD

def describe(self, sobject=None):
"""Describes all objects or a specific object"""
Expand Down
14 changes: 14 additions & 0 deletions tests/unittests/test_auth_failure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import unittest
from unittest.mock import Mock
from tap_salesforce import Salesforce

class TestAuthorizationHandling(unittest.TestCase):
def test_attempt_login_failure_with_retry(self):
"""
When we see an exception on the login, we expect the error to be raised after 3 tries
"""
salesforce_object = Salesforce(default_start_date="2021-07-15T13:25:54Z")
salesforce_object.session.post = Mock(side_effect=Exception("this is an example auth exception"))
with self.assertRaisesRegexp(Exception, "this is an example auth exception") as e:
salesforce_object.login()
self.assertEqual(3, salesforce_object.session.post.call_count)