diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index faeabb7185..b3d1a528c4 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -12,6 +12,7 @@ import tempfile import ddt import pytz +from boto.exception import BotoServerError from django.conf import settings from django.contrib.auth.models import User from django.core import mail @@ -3020,6 +3021,27 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment body.endswith('"{user_id}","41","42"\n'.format(user_id=self.students[-1].id)) ) + @patch('lms.djangoapps.instructor_task.models.logger.error') + @patch.dict(settings.GRADES_DOWNLOAD, {'STORAGE_TYPE': 's3'}) + def test_list_report_downloads_error(self, mock_error): + """ + Tests the Rate-Limit exceeded is handled and does not raise 500 error. + """ + ex_status = 503 + ex_reason = 'Slow Down' + url = reverse('list_report_downloads', kwargs={'course_id': self.course.id.to_deprecated_string()}) + with patch('openedx.core.storage.S3ReportStorage.listdir', side_effect=BotoServerError(ex_status, ex_reason)): + response = self.client.post(url, {}) + mock_error.assert_called_with( + u'Fetching files failed for course: %s, status: %s, reason: %s', + self.course.id, + ex_status, + ex_reason, + ) + + res_json = json.loads(response.content) + self.assertEqual(res_json, {"downloads": []}) + def test_list_report_downloads(self): url = reverse('list_report_downloads', kwargs={'course_id': self.course.id.to_deprecated_string()}) with patch('lms.djangoapps.instructor_task.models.DjangoStorageReportStore.links_for') as mock_links_for: diff --git a/lms/djangoapps/instructor_task/models.py b/lms/djangoapps/instructor_task/models.py index f990a49939..1d3a658eba 100644 --- a/lms/djangoapps/instructor_task/models.py +++ b/lms/djangoapps/instructor_task/models.py @@ -15,9 +15,11 @@ ASSUMPTIONS: modules have unique IDs, even across different module_types import csv import hashlib import json +import logging import os.path from uuid import uuid4 +from boto.exception import BotoServerError from django.conf import settings from django.contrib.auth.models import User from django.core.files.base import ContentFile @@ -26,6 +28,8 @@ from django.db import models, transaction from openedx.core.djangoapps.xmodule_django.models import CourseKeyField from openedx.core.storage import get_storage +logger = logging.getLogger(__name__) + # define custom states used by InstructorTask QUEUING = 'QUEUING' PROGRESS = 'PROGRESS' @@ -283,6 +287,14 @@ class DjangoStorageReportStore(ReportStore): # Django's FileSystemStorage fails with an OSError if the course # dir does not exist; other storage types return an empty list. return [] + except BotoServerError as ex: + logger.error( + u'Fetching files failed for course: %s, status: %s, reason: %s', + course_id, + ex.status, + ex.reason + ) + return [] files = [(filename, os.path.join(course_dir, filename)) for filename in filenames] files.sort(key=lambda f: self.storage.modified_time(f[1]), reverse=True) return [