def _send_notification(job): """ helper function to encapsulate the process of sending a report via email to the user who created the bulk job :param job: a BulkJob :return: True if notification was successfully sent (passed to the Django framework, anyway); False if no notification was sent """ notification_to_address_list = [] canvas_user_profile = None logger.debug("Looking up notification email recipient address list...") try: canvas_user_profile = get_canvas_user_profile(job.created_by_user_id) notification_to_address_list = [canvas_user_profile['primary_email']] except Exception: # todo: do we need all these multilayered logs? error_text = ( "Job %s: problem getting canvas user profile for user %s" % (job.id, job.created_by_user_id)) logger.exception(error_text) _log_notification_failure(job) return False logger.debug("Building notification email...") completed_subjobs = job.get_completed_subjobs_count() failed_subjobs = job.get_failed_subjobs_count() try: term = Term.objects.get(term_id=int(job.sis_term_id)) term_display_name = term.display_name school = School.objects.get(school_id=job.school_id) school_display_name = school.title_short except Exception: error_text = ("Canvas course create bulk job %s: " "problem getting user-friendly term or school name") logger.exception(error_text) term_display_name = job.sis_term_id school_display_name = job.school_id subject = _format_notification_email_subject(school_display_name, term_display_name) body = _format_notification_email_body(school_display_name, term_display_name, completed_subjobs, failed_subjobs) logger.debug("Sending notification email to %s...", notification_to_address_list) try: send_email_helper(subject, body, notification_to_address_list) except Exception: # todo: do we need all these multilayered logs? logger.exception("Job %s: problem sending notification", job.id) _log_notification_failure(job) return False logger.debug("Notification email sent!") return True
def test_send_mail_invoked_with_correct_args(self, send_mail): """ Test that send_mail is called with expected args passed into send_email_helper """ result = send_email_helper(self.subject, self.message, self.to_address) send_mail.assert_called_with( self.subject, self.message, override_settings_dict['from_email_address'], self.to_address, fail_silently=ANY )
def handle(self, **options): """ select all the active job in the CanvasCourseGenerationJob table and check the status using the canvas_sdk.progress method """ # open and lock the file used for determining if another process is running _pid_file = getattr(settings, 'PROCESS_ASYNC_JOBS_PID_FILE', 'process_async_jobs.pid') _pid_file_handle = open(_pid_file, 'w') try: fcntl.lockf(_pid_file_handle, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as e: # another instance is running logger.warning( f"another instance of the command is already running: {e}") return start_time = datetime.now() jobs = CanvasCourseGenerationJob.objects.filter( Q(workflow_state=CanvasCourseGenerationJob.STATUS_QUEUED) | Q(workflow_state=CanvasCourseGenerationJob.STATUS_RUNNING) | Q(workflow_state=CanvasCourseGenerationJob. STATUS_PENDING_FINALIZE)) for job in jobs: try: """ TODO - it turns out we only really need the job_id of the content migration no the whole url since we are using the canvas_sdk to check the value. We should update this in the database and the setting method. In the meantime just parse out the job_id from the url. """ job_start_message = '\nProcessing course with sis_course_id %s' % ( job.sis_course_id) logger.info(job_start_message) user_profile = None # Check if the job is flagged for migration or is running the migration workflow_state = job.workflow_state if workflow_state in ( CanvasCourseGenerationJob.STATUS_QUEUED, CanvasCourseGenerationJob.STATUS_RUNNING): response = client.get(SDK_CONTEXT, job.status_url) progress_response = response.json() workflow_state = progress_response['workflow_state'] if workflow_state == CanvasCourseGenerationJob.STATUS_COMPLETED: logger.info( 'content migration complete for course with sis_course_id %s' % job.sis_course_id) # Update the Job table with the completed state immediately to indicate that the template # migration was successful job.workflow_state = CanvasCourseGenerationJob.STATUS_COMPLETED job.save(update_fields=['workflow_state']) if workflow_state in ( CanvasCourseGenerationJob.STATUS_COMPLETED, CanvasCourseGenerationJob.STATUS_PENDING_FINALIZE): logger.debug( 'Workflow state updated, starting finalization process...' ) try: update_syllabus_body(job) canvas_course_url = finalize_new_canvas_course( job.canvas_course_id, job.sis_course_id, 'sis_user_id:%s' % job.created_by_user_id, job.bulk_job_id) except Exception: # Catch exceptions from finalize method to set the workflow_state to STATUS_FINALIZE_FAILED # and then re raise it so that generic tasks like tech logger, email generation will continue # to be handled in the larger try block logger.exception( 'Exception during finalize method, ' 'setting state to STATUS_FINALIZE_FAILED ' 'for sis_course_id id %s' % job.sis_course_id) job.workflow_state = CanvasCourseGenerationJob.STATUS_FINALIZE_FAILED job.save(update_fields=['workflow_state']) raise # Update the Job table with the STATUS_FINALIZED state if finalize is successful job.workflow_state = CanvasCourseGenerationJob.STATUS_FINALIZED job.save(update_fields=['workflow_state']) # if this is not a bulk_job then proceed with email generation to user if not job.bulk_job_id: # Once finalized successfully, only the initiator needs to be emailed user_profile = get_canvas_user_profile( job.created_by_user_id) to_address = [user_profile['primary_email']] success_msg = settings.CANVAS_EMAIL_NOTIFICATION[ 'course_migration_success_body'] logger.debug( "notifying success via email: to_addr=%s and adding course url =%s" % (to_address, canvas_course_url)) # add the course url to the message complete_msg = success_msg.format(canvas_course_url) send_email_helper( settings.CANVAS_EMAIL_NOTIFICATION[ 'course_migration_success_subject'], complete_msg, to_address) elif workflow_state == CanvasCourseGenerationJob.STATUS_FAILED: error_text = 'Content migration failed for course with sis_course_id %s (HUID:%s)' \ % (job.sis_course_id, job.created_by_user_id) logger.info(error_text) tech_logger.error(error_text) # Update the Job table with the new state job.workflow_state = CanvasCourseGenerationJob.STATUS_FAILED job.save(update_fields=['workflow_state']) if not job.bulk_job_id: # send email to notify of failure if it's not a bulk fed course user_profile = get_canvas_user_profile( job.created_by_user_id) send_failure_email(user_profile['primary_email'], job.sis_course_id) else: """ if the workflow_state is 'queued' or 'running' the job is not complete and a failure has not occured on Canvas. log that we checked Note: we won't need to update the DB as we will record only the completin or failure in the job table """ message = 'content migration state is %s for course with sis_course_id %s' % ( workflow_state, job.sis_course_id) logger.info(message) except Exception as e: error_text = "There was a problem in processing the job for canvas course sis_course_id %s (HUID:%s)" \ % (job.sis_course_id, job.created_by_user_id) # Note: equivalent to .error(error_text, exc_info=1) -- logs at ERROR level logger.exception(error_text) # Use the friendly display_text for the subject of the tech_logger email if it's available if isinstance(e, RenderableException): error_text = '%s (HUID:%s)' % (e.display_text, job.created_by_user_id) tech_logger.exception(error_text) # send email if it's not a bulk created course if not job.bulk_job_id: try: # if failure happened before user profile was fetched, get the user profile # to retrieve email, else reuse the user_profile info if not user_profile: user_profile = get_canvas_user_profile( job.created_by_user_id) send_failure_email(user_profile['primary_email'], job.sis_course_id) except Exception: # If exception occurs while sending failure email, log it error_text = "There was a problem in sending the failure notification email to initiator " \ "and support staff for sis_course_id %s (HUID:%s)" \ % (job.sis_course_id, job.created_by_user_id) logger.exception(error_text) tech_logger.exception(error_text) logger.info('command took %s seconds to run', str(datetime.now() - start_time)) # unlock and close the file used for determining if another process is running try: fcntl.lockf(_pid_file_handle, fcntl.LOCK_UN) _pid_file_handle.close() except IOError: logger.error( "could not release lock on pid file or close pid file properly" )
def _send_notification(job): """ helper function to encapsulate the process of sending a report via email to the user who created the bulk job :param job: a BulkJob :return: True if notification was successfully sent (passed to the Django framework, anyway); False if no notification was sent """ notification_to_address_list = [] canvas_user_profile = None logger.debug("Looking up notification email recipient address list...") try: canvas_user_profile = get_canvas_user_profile(job.created_by_user_id) notification_to_address_list = [canvas_user_profile['primary_email']] except Exception as e: # todo: do we need all these multilayered logs? error_text = ( "Job %s: problem getting canvas user profile for user %s" % (job.id, job.created_by_user_id) ) logger.exception(error_text) _log_notification_failure(job) return False logger.debug("Building notification email...") completed_subjobs = job.get_completed_subjobs_count() failed_subjobs = job.get_failed_subjobs_count() try: term = Term.objects.get(term_id=int(job.sis_term_id)) term_display_name = term.display_name school = School.objects.get(school_id=job.school_id) school_display_name = school.title_short except Exception as e: error_text = ( "Canvas course create bulk job %s: " "problem getting user-friendly term or school name" ) logger.exception(error_text) term_display_name = job.sis_term_id school_display_name = job.school_id subject = _format_notification_email_subject( school_display_name, term_display_name ) body = _format_notification_email_body( school_display_name, term_display_name, completed_subjobs, failed_subjobs ) logger.debug("Sending notification email to %s...", notification_to_address_list) try: send_email_helper(subject, body, notification_to_address_list) except Exception as e: # todo: do we need all these multilayered logs? logger.exception("Job %s: problem sending notification", job.id) _log_notification_failure(job) return False logger.debug("Notification email sent!") return True