def test_default_template_not_include_course_info( self, template_mock, update_course, get_default_template_for_school, **kwargs): get_default_template_for_school.return_value = Mock( template_id=template_id) template_mock.return_value = Mock(include_course_info=False) update_syllabus_body(self.course_job) assert not update_course.called
def test_template_not_include_course_info(self, template_mock, bulk_job_mock, SDK_CONTEXT, update_course, **kwargs): bulk_job_mock.return_value = Mock( template_canvas_course_id=template_id) template_mock.return_value = Mock(include_course_info=False) update_syllabus_body(self.course_job) assert not update_course.called
def test_template_include_course_info(self, template_mock, bulk_job_mock, SDK_CONTEXT, update_course, **kwargs): bulk_job_mock.return_value = Mock(template_canvas_course_id=template_id) template_mock.return_value = Mock(include_course_info=True) update_syllabus_body(self.course_job) update_course.assert_called_with( SDK_CONTEXT, canvas_course_id, course_syllabus_body=course_syllabus_body )
def test_default_template_include_course_info(self, template_mock, get_default_template_for_school, SDK_CONTEXT, update_course, **kwargs): template_mock.return_value = Mock(include_course_info=True) get_default_template_for_school.return_value = Mock(template_id=template_id) update_syllabus_body(self.course_job) update_course.assert_called_with( SDK_CONTEXT, canvas_course_id, course_syllabus_body=course_syllabus_body )
def test_template_include_course_info(self, template_mock, bulk_job_mock, SDK_CONTEXT, update_course, **kwargs): bulk_job_mock.return_value = Mock( template_canvas_course_id=template_id) template_mock.return_value = Mock(include_course_info=True) update_syllabus_body(self.course_job) update_course.assert_called_with( SDK_CONTEXT, canvas_course_id, course_syllabus_body=course_syllabus_body)
def test_default_template_include_course_info( self, template_mock, get_default_template_for_school, SDK_CONTEXT, update_course, **kwargs): template_mock.return_value = Mock(include_course_info=True) get_default_template_for_school.return_value = Mock( template_id=template_id) update_syllabus_body(self.course_job) update_course.assert_called_with( SDK_CONTEXT, canvas_course_id, course_syllabus_body=course_syllabus_body)
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 test_no_template(self, bulk_job_mock, update_course, **kwargs): bulk_job_mock.return_value = Mock(template_canvas_course_id=None) update_syllabus_body(self.course_job) assert not update_course.called
def test_no_default_template(self, update_course, get_default_template_for_school, **kwargs): get_default_template_for_school.side_effect = NoTemplateExistsForSchool( sis_course_id) update_syllabus_body(self.course_job) assert not update_course.called
def test_template_not_include_course_info(self, template_mock, bulk_job_mock, SDK_CONTEXT, update_course, **kwargs): bulk_job_mock.return_value = Mock(template_canvas_course_id=template_id) template_mock.return_value = Mock(include_course_info=False) update_syllabus_body(self.course_job) assert not update_course.called
def test_default_template_not_include_course_info(self, template_mock, update_course, get_default_template_for_school, **kwargs): get_default_template_for_school.return_value = Mock(template_id=template_id) template_mock.return_value = Mock(include_course_info=False) update_syllabus_body(self.course_job) assert not update_course.called
def test_no_default_template(self, update_course, get_default_template_for_school, **kwargs): get_default_template_for_school.side_effect = NoTemplateExistsForSchool(sis_course_id) update_syllabus_body(self.course_job) assert not update_course.called