def get_pdf_for_templated_letter(self, notification_id): try: notification = get_notification_by_id(notification_id, _raise=True) letter_filename = generate_letter_pdf_filename( reference=notification.reference, created_at=notification.created_at, ignore_folder=notification.key_type == KEY_TYPE_TEST, postage=notification.postage) letter_data = { 'letter_contact_block': notification.reply_to_text, 'template': { "subject": notification.template.subject, "content": notification.template.content, "template_type": notification.template.template_type }, 'values': notification.personalisation, 'logo_filename': notification.service.letter_branding and notification.service.letter_branding.filename, 'letter_filename': letter_filename, "notification_id": str(notification_id), 'key_type': notification.key_type } encrypted_data = encryption.encrypt(letter_data) notify_celery.send_task(name=TaskNames.CREATE_PDF_FOR_TEMPLATED_LETTER, args=(encrypted_data, ), queue=QueueNames.SANITISE_LETTERS) except Exception: try: current_app.logger.exception( f"RETRY: calling create-letter-pdf task for notification {notification_id} failed" ) self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: message = f"RETRY FAILED: Max retries reached. " \ f"The task create-letter-pdf failed for notification id {notification_id}. " \ f"Notification has been updated to technical-failure" update_notification_status_by_id(notification_id, NOTIFICATION_TECHNICAL_FAILURE) raise NotificationTechnicalFailureException(message)
def deliver_sms(self, notification_id): try: current_app.logger.info("Start sending SMS for notification id: {}".format(notification_id)) notification = notifications_dao.get_notification_by_id(notification_id) if not notification: raise NoResultFound() send_to_providers.send_sms_to_provider(notification) except Exception as e: try: current_app.logger.exception( "SMS notification delivery for id: {} failed".format(notification_id) ) self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: message = "RETRY FAILED: Max retries reached. The task send_sms_to_provider failed for notification {}. " \ "Notification has been updated to technical-failure".format(notification_id) update_notification_status_by_id(notification_id, NOTIFICATION_TECHNICAL_FAILURE) raise NotificationTechnicalFailureException(message)
def timeout_notifications(): technical_failure_notifications, temporary_failure_notifications = \ dao_timeout_notifications(current_app.config.get('SENDING_NOTIFICATIONS_TIMEOUT_PERIOD')) notifications = technical_failure_notifications + temporary_failure_notifications for notification in notifications: # queue callback task only if the service_callback_api exists service_callback_api = get_service_delivery_status_callback_api_for_service(service_id=notification.service_id) if service_callback_api: encrypted_notification = create_delivery_status_callback_data(notification, service_callback_api) send_delivery_status_to_service.apply_async([str(notification.id), encrypted_notification], queue=QueueNames.CALLBACKS) current_app.logger.info( "Timeout period reached for {} notifications, status has been updated.".format(len(notifications))) if technical_failure_notifications: message = "{} notifications have been updated to technical-failure because they " \ "have timed out and are still in created.Notification ids: {}".format( len(technical_failure_notifications), [str(x.id) for x in technical_failure_notifications]) raise NotificationTechnicalFailureException(message)
def sanitise_letter(self, filename): try: reference = get_reference_from_filename(filename) notification = dao_get_notification_by_reference(reference) current_app.logger.info( 'Notification ID {} Virus scan passed: {}'.format( notification.id, filename)) if notification.status != NOTIFICATION_PENDING_VIRUS_CHECK: current_app.logger.info( 'Sanitise letter called for notification {} which is in {} state' .format(notification.id, notification.status)) return notify_celery.send_task( name=TaskNames.SANITISE_LETTER, kwargs={ 'notification_id': str(notification.id), 'filename': filename, 'allow_international_letters': notification.service.has_permission(INTERNATIONAL_LETTERS), }, queue=QueueNames.SANITISE_LETTERS, ) except Exception: try: current_app.logger.exception( "RETRY: calling sanitise_letter task for notification {} failed" .format(notification.id)) self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: message = "RETRY FAILED: Max retries reached. " \ "The task sanitise_letter failed for notification {}. " \ "Notification has been updated to technical-failure".format(notification.id) update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE) raise NotificationTechnicalFailureException(message)
def process_sanitised_letter(self, sanitise_data): letter_details = encryption.decrypt(sanitise_data) filename = letter_details['filename'] notification_id = letter_details['notification_id'] current_app.logger.info('Processing sanitised letter with id {}'.format(notification_id)) notification = get_notification_by_id(notification_id, _raise=True) if notification.status != NOTIFICATION_PENDING_VIRUS_CHECK: current_app.logger.info( 'process-sanitised-letter task called for notification {} which is in {} state'.format( notification.id, notification.status) ) return try: original_pdf_object = s3.get_s3_object(current_app.config['LETTERS_SCAN_BUCKET_NAME'], filename) if letter_details['validation_status'] == 'failed': current_app.logger.info('Processing invalid precompiled pdf with id {} (file {})'.format( notification_id, filename)) _move_invalid_letter_and_update_status( notification=notification, filename=filename, scan_pdf_object=original_pdf_object, message=letter_details['message'], invalid_pages=letter_details['invalid_pages'], page_count=letter_details['page_count'], ) return current_app.logger.info('Processing valid precompiled pdf with id {} (file {})'.format( notification_id, filename)) billable_units = get_billable_units_for_letter_page_count(letter_details['page_count']) is_test_key = notification.key_type == KEY_TYPE_TEST # Updating the notification needs to happen before the file is moved. This is so that if updating the # notification fails, the task can retry because the file is in the same place. update_letter_pdf_status( reference=notification.reference, status=NOTIFICATION_DELIVERED if is_test_key else NOTIFICATION_CREATED, billable_units=billable_units, recipient_address=letter_details['address'] ) # The original filename could be wrong because we didn't know the postage. # Now we know if the letter is international, we can check what the filename should be. upload_file_name = get_letter_pdf_filename( reference=notification.reference, crown=notification.service.crown, created_at=notification.created_at, ignore_folder=True, postage=notification.postage ) move_sanitised_letter_to_test_or_live_pdf_bucket( filename, is_test_key, notification.created_at, upload_file_name, ) # We've moved the sanitised PDF from the sanitise bucket, but still need to delete the original file: original_pdf_object.delete() except BotoClientError: # Boto exceptions are likely to be caused by the file(s) being in the wrong place, so retrying won't help - # we'll need to manually investigate current_app.logger.exception( f"Boto error when processing sanitised letter for notification {notification.id} (file {filename})" ) update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE) raise NotificationTechnicalFailureException except Exception: try: current_app.logger.exception( "RETRY: calling process_sanitised_letter task for notification {} failed".format(notification.id) ) self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: message = "RETRY FAILED: Max retries reached. " \ "The task process_sanitised_letter failed for notification {}. " \ "Notification has been updated to technical-failure".format(notification.id) update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE) raise NotificationTechnicalFailureException(message)
def fail_pii(notification, pii_type): notification.status = NOTIFICATION_CONTAINS_PII dao_update_notification(notification) raise NotificationTechnicalFailureException( "Send {} for notification id {} to provider is not allowed. Notification contains PII: {}" .format(notification.notification_type, notification.id, pii_type))
def malware_failure(notification): notification.status = NOTIFICATION_VIRUS_SCAN_FAILED dao_update_notification(notification) raise NotificationTechnicalFailureException( "Send {} for notification id {} to provider is not allowed. Notification contains malware" .format(notification.notification_type, notification.id))
def lookup_contact_info(self, notification_id): current_app.logger.info( f"Looking up contact information for notification_id:{notification_id}." ) notification = get_notification_by_id(notification_id) va_profile_id = notification.recipient_identifiers[ IdentifierType.VA_PROFILE_ID.value].id_value try: if EMAIL_TYPE == notification.notification_type: recipient = va_profile_client.get_email(va_profile_id) elif SMS_TYPE == notification.notification_type: recipient = va_profile_client.get_telephone(va_profile_id) else: raise NotImplementedError( f"The task lookup_contact_info failed for notification {notification_id}. " f"{notification.notification_type} is not supported") except VAProfileRetryableException as e: current_app.logger.exception(e) try: self.retry(queue=QueueNames.RETRY) except self.MaxRetriesExceededError: message = ( 'RETRY FAILED: Max retries reached. ' f'The task lookup_contact_info failed for notification {notification_id}. ' 'Notification has been updated to technical-failure') update_notification_status_by_id(notification_id, NOTIFICATION_TECHNICAL_FAILURE, status_reason=e.failure_reason) raise NotificationTechnicalFailureException(message) from e except NoContactInfoException as e: message = ( f'Can\'t proceed after querying VA Profile for contact information for {notification_id}. ' 'Stopping execution of following tasks. Notification has been updated to permanent-failure.' ) current_app.logger.warning(f'{e.__class__.__name__} - {str(e)}: ' + message) self.request.chain = None update_notification_status_by_id(notification_id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason) except VAProfileNonRetryableException as e: current_app.logger.exception(e) message = ( f'The task lookup_contact_info failed for notification {notification_id}. ' 'Notification has been updated to technical-failure') update_notification_status_by_id(notification_id, NOTIFICATION_TECHNICAL_FAILURE, status_reason=e.failure_reason) raise NotificationTechnicalFailureException(message) from e else: notification.to = recipient dao_update_notification(notification)