Merge pull request #18697 from edx/bbaker/LEARNER-6004

Properly handle errors in award program certificates
This commit is contained in:
Brandon Baker
2018-08-01 10:25:13 -04:00
committed by GitHub
2 changed files with 61 additions and 4 deletions

View File

@@ -174,11 +174,32 @@ def award_program_certificates(self, username):
try:
award_program_certificate(credentials_client, username, program_uuid)
LOGGER.info('Awarded certificate for program %s to user %s', program_uuid, username)
except exceptions.HttpClientError:
except exceptions.HttpNotFoundError:
LOGGER.exception(
'Certificate for program %s not configured, unable to award certificate to %s',
program_uuid, username
"""Certificate for program {uuid} could not be found. Unable to award certificate to user
{username}. The program might not be configured.""".format(uuid=program_uuid, username=username)
)
except exceptions.HttpClientError as exc:
# Grab the status code from the client error, because our API
# client handles all 4XX errors the same way. In the future,
# we may want to fork slumber, add 429 handling, and use that
# in edx_rest_api_client.
# A status code looks like:
# "Client Error 429: http://example-endpoint/"
status_code = int(str(exc).split(':')[0][-3:])
if status_code == 429:
rate_limit_countdown = 60
LOGGER.info(
"""Rate limited. Retrying task to award certificates to user {username} in {countdown}
seconds""".format(username=username, countdown=rate_limit_countdown)
)
# Retry after 60 seconds, when we should be in a new throttling window
raise self.retry(exc=exc, countdown=rate_limit_countdown, max_retries=MAX_RETRIES)
else:
LOGGER.exception(
"""Unable to award certificate to user {username} for program {uuid}. The program might not be
configured.""".format(username=username, uuid=program_uuid)
)
except Exception: # pylint: disable=broad-except
# keep trying to award other certs, but retry the whole task to fix any missing entries
LOGGER.warning('Failed to award certificate for program {uuid} to user {username}.'.format(

View File

@@ -292,6 +292,42 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
self.assertEqual(mock_get_certified_programs.call_count, 2)
self.assertEqual(mock_award_program_certificate.call_count, 1)
def test_retry_on_credentials_api_429_error(
self,
mock_get_completed_programs,
mock_get_certified_programs, # pylint: disable=unused-argument
mock_award_program_certificate,
):
"""
Verify that a 429 error causes the task to fail and then retry.
"""
mock_get_completed_programs.return_value = [1, 2]
mock_award_program_certificate.side_effect = self._make_side_effect(
[exceptions.HttpClientError('Client Error 429: http://example-endpoint/'), None]
)
tasks.award_program_certificates.delay(self.student.username).get()
self.assertEqual(mock_award_program_certificate.call_count, 3)
def test_no_retry_oncredentials_api_404_error(
self,
mock_get_completed_programs,
mock_get_certified_programs, # pylint: disable=unused-argument
mock_award_program_certificate,
):
"""
Verify that a 404 error causes the task to fail but there is no retry.
"""
mock_get_completed_programs.return_value = [1, 2]
mock_award_program_certificate.side_effect = self._make_side_effect(
[exceptions.HttpNotFoundError(), None]
)
tasks.award_program_certificates.delay(self.student.username).get()
self.assertEqual(mock_award_program_certificate.call_count, 2)
def test_no_retry_on_credentials_api_not_found_errors(
self,
mock_get_completed_programs,
@@ -301,7 +337,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo
mock_get_completed_programs.return_value = [1, 2]
mock_get_certified_programs.side_effect = [[], [2]]
mock_award_program_certificate.side_effect = self._make_side_effect(
[exceptions.HttpClientError(), None]
[exceptions.HttpClientError('Client Error 418: http://example-endpoint/'), None]
)
tasks.award_program_certificates.delay(self.student.username).get()