diff --git a/app/celery/letters_pdf_tasks.py b/app/celery/letters_pdf_tasks.py index 0d4f77fef..d81ecb398 100644 --- a/app/celery/letters_pdf_tasks.py +++ b/app/celery/letters_pdf_tasks.py @@ -26,7 +26,9 @@ from app.letters.utils import ( get_reference_from_filename, move_scanned_pdf_to_test_or_live_pdf_bucket, upload_letter_pdf, - move_failed_pdf, ScanErrorType) + move_failed_pdf, ScanErrorType, move_error_pdf_to_scan_bucket, + get_file_names_from_error_bucket +) from app.models import ( KEY_TYPE_TEST, NOTIFICATION_CREATED, @@ -192,7 +194,9 @@ def process_virus_scan_failed(filename): ) ) - raise VirusScanError('notification id {} Virus scan failed: {}'.format(notification.id, filename)) + error = VirusScanError('notification id {} Virus scan failed: {}'.format(notification.id, filename)) + current_app.logger.exception(error) + raise error @notify_celery.task(name='process-virus-scan-error') @@ -208,8 +212,9 @@ def process_virus_scan_error(filename): updated_count ) ) - - raise VirusScanError('notification id {} Virus scan error: {}'.format(notification.id, filename)) + error = VirusScanError('notification id {} Virus scan error: {}'.format(notification.id, filename)) + current_app.logger.exception(error) + raise error def update_letter_pdf_status(reference, status): @@ -219,3 +224,29 @@ def update_letter_pdf_status(reference, status): 'status': status, 'updated_at': datetime.utcnow() }) + + +def replay_letters_in_error(filename=None): + # This method can be used to replay letters that end up in the ERROR directory. + # We had an incident where clamAV was not processing the virus scan. + if filename: + move_error_pdf_to_scan_bucket(filename) + # call task to add the filename to anti virus queue + current_app.logger.info("Calling scan_file for: {}".format(filename)) + notify_celery.send_task( + name=TaskNames.SCAN_FILE, + kwargs={'filename': filename}, + queue=QueueNames.ANTIVIRUS, + ) + else: + error_files = get_file_names_from_error_bucket() + for item in error_files: + moved_file_name = item.key.split('/')[1] + current_app.logger.info("Calling scan_file for: {}".format(moved_file_name)) + move_error_pdf_to_scan_bucket(moved_file_name) + # call task to add the filename to anti virus queue + notify_celery.send_task( + name=TaskNames.SCAN_FILE, + kwargs={'filename': moved_file_name}, + queue=QueueNames.ANTIVIRUS, + ) diff --git a/app/letters/utils.py b/app/letters/utils.py index 3902bf932..83792c7cc 100644 --- a/app/letters/utils.py +++ b/app/letters/utils.py @@ -110,6 +110,21 @@ def move_failed_pdf(source_filename, scan_error_type): _move_s3_object(scan_bucket, source_filename, scan_bucket, target_filename) +def move_error_pdf_to_scan_bucket(source_filename): + scan_bucket = current_app.config['LETTERS_SCAN_BUCKET_NAME'] + error_file = 'ERROR/' + source_filename + + _move_s3_object(scan_bucket, error_file, scan_bucket, source_filename) + + +def get_file_names_from_error_bucket(): + s3 = boto3.resource('s3') + scan_bucket = current_app.config['LETTERS_SCAN_BUCKET_NAME'] + bucket = s3.Bucket(scan_bucket) + + return bucket.objects.filter(Prefix="ERROR") + + def get_letter_pdf(notification): is_test_letter = notification.key_type == KEY_TYPE_TEST and notification.template.is_precompiled_letter if is_test_letter: diff --git a/tests/app/celery/test_letters_pdf_tasks.py b/tests/app/celery/test_letters_pdf_tasks.py index 055b711d9..eeeea3891 100644 --- a/tests/app/celery/test_letters_pdf_tasks.py +++ b/tests/app/celery/test_letters_pdf_tasks.py @@ -20,7 +20,8 @@ from app.celery.letters_pdf_tasks import ( letter_in_created_state, process_virus_scan_passed, process_virus_scan_failed, - process_virus_scan_error) + process_virus_scan_error, replay_letters_in_error +) from app.letters.utils import get_letter_pdf_filename, ScanErrorType from app.models import ( KEY_TYPE_NORMAL, @@ -363,3 +364,25 @@ def test_process_letter_task_check_virus_scan_error(sample_letter_notification, assert "Virus scan error:" in str(e) mock_move_failed_pdf.assert_called_once_with(filename, ScanErrorType.ERROR) assert sample_letter_notification.status == NOTIFICATION_TECHNICAL_FAILURE + + +def test_replay_letters_in_error_for_all_letters_in_error_bucket(notify_api, mocker): + import boto3 + mockObject = boto3.resource('s3').Object('ERROR', 'ERROR/file_name') + mocker.patch("app.celery.letters_pdf_tasks.get_file_names_from_error_bucket", return_value=[mockObject]) + mock_move = mocker.patch("app.celery.letters_pdf_tasks.move_error_pdf_to_scan_bucket") + mock_celery = mocker.patch("app.celery.letters_pdf_tasks.notify_celery.send_task") + replay_letters_in_error() + mock_move.assert_called_once_with('file_name') + mock_celery.assert_called_once_with(name='scan-file', kwargs={'filename': 'file_name'}, queue='antivirus-tasks') + + +def test_replay_letters_in_error_for_one_file(notify_api, mocker): + import boto3 + mockObject = boto3.resource('s3').Object('ERROR', 'ERROR/file_name') + mocker.patch("app.celery.letters_pdf_tasks.get_file_names_from_error_bucket", return_value=[mockObject]) + mock_move = mocker.patch("app.celery.letters_pdf_tasks.move_error_pdf_to_scan_bucket") + mock_celery = mocker.patch("app.celery.letters_pdf_tasks.notify_celery.send_task") + replay_letters_in_error("file_name") + mock_move.assert_called_once_with('file_name') + mock_celery.assert_called_once_with(name='scan-file', kwargs={'filename': 'file_name'}, queue='antivirus-tasks')