Commit a0dcc97e by Brian Wilson

tasks now extract task_input and course_id from InstructorTask, instead of passing explicitly.

parent a67674fe
...@@ -277,7 +277,7 @@ def submit_task(request, task_type, task_class, course_id, task_input, task_key) ...@@ -277,7 +277,7 @@ def submit_task(request, task_type, task_class, course_id, task_input, task_key)
# submit task: # submit task:
task_id = instructor_task.task_id task_id = instructor_task.task_id
task_args = [instructor_task.id, course_id, task_input, _get_xmodule_instance_args(request)] task_args = [instructor_task.id, _get_xmodule_instance_args(request)]
task_class.apply_async(task_args, task_id=task_id) task_class.apply_async(task_args, task_id=task_id)
return instructor_task return instructor_task
""" """
This file contains tasks that are designed to perform background operations on the This file contains tasks that are designed to perform background operations on the
running state of a course. running state of a course.
""" """
from celery import task from celery import task
from instructor_task.tasks_helper import (_update_problem_module_state, from instructor_task.tasks_helper import (update_problem_module_state,
_rescore_problem_module_state, rescore_problem_module_state,
_reset_problem_attempts_module_state, reset_attempts_module_state,
_delete_problem_module_state) delete_problem_module_state)
@task @task
def rescore_problem(entry_id, course_id, task_input, xmodule_instance_args): def rescore_problem(entry_id, xmodule_instance_args):
"""Rescores problem in `course_id`. """Rescores problem in `course_id`.
`entry_id` is the id value of the InstructorTask entry that corresponds to this task. `entry_id` is the id value of the InstructorTask entry that corresponds to this task.
...@@ -29,19 +29,15 @@ def rescore_problem(entry_id, course_id, task_input, xmodule_instance_args): ...@@ -29,19 +29,15 @@ def rescore_problem(entry_id, course_id, task_input, xmodule_instance_args):
to instantiate an xmodule instance. to instantiate an xmodule instance.
""" """
action_name = 'rescored' action_name = 'rescored'
update_fcn = _rescore_problem_module_state update_fcn = rescore_problem_module_state
filter_fcn = lambda(modules_to_update): modules_to_update.filter(state__contains='"done": true') filter_fcn = lambda(modules_to_update): modules_to_update.filter(state__contains='"done": true')
problem_url = task_input.get('problem_url') return update_problem_module_state(entry_id,
student_ident = None update_fcn, action_name, filter_fcn=filter_fcn,
if 'student' in task_input: xmodule_instance_args=xmodule_instance_args)
student_ident = task_input['student']
return _update_problem_module_state(entry_id, course_id, problem_url, student_ident,
update_fcn, action_name, filter_fcn=filter_fcn,
xmodule_instance_args=xmodule_instance_args)
@task @task
def reset_problem_attempts(entry_id, course_id, task_input, xmodule_instance_args): def reset_problem_attempts(entry_id, xmodule_instance_args):
"""Resets problem attempts to zero for `problem_url` in `course_id` for all students. """Resets problem attempts to zero for `problem_url` in `course_id` for all students.
`entry_id` is the id value of the InstructorTask entry that corresponds to this task. `entry_id` is the id value of the InstructorTask entry that corresponds to this task.
...@@ -54,15 +50,14 @@ def reset_problem_attempts(entry_id, course_id, task_input, xmodule_instance_arg ...@@ -54,15 +50,14 @@ def reset_problem_attempts(entry_id, course_id, task_input, xmodule_instance_arg
to instantiate an xmodule instance. to instantiate an xmodule instance.
""" """
action_name = 'reset' action_name = 'reset'
update_fcn = _reset_problem_attempts_module_state update_fcn = reset_attempts_module_state
problem_url = task_input.get('problem_url') return update_problem_module_state(entry_id,
return _update_problem_module_state(entry_id, course_id, problem_url, None, update_fcn, action_name, filter_fcn=None,
update_fcn, action_name, filter_fcn=None, xmodule_instance_args=xmodule_instance_args)
xmodule_instance_args=xmodule_instance_args)
@task @task
def delete_problem_state(entry_id, course_id, task_input, xmodule_instance_args): def delete_problem_state(entry_id, xmodule_instance_args):
"""Deletes problem state entirely for `problem_url` in `course_id` for all students. """Deletes problem state entirely for `problem_url` in `course_id` for all students.
`entry_id` is the id value of the InstructorTask entry that corresponds to this task. `entry_id` is the id value of the InstructorTask entry that corresponds to this task.
...@@ -75,8 +70,7 @@ def delete_problem_state(entry_id, course_id, task_input, xmodule_instance_args) ...@@ -75,8 +70,7 @@ def delete_problem_state(entry_id, course_id, task_input, xmodule_instance_args)
to instantiate an xmodule instance. to instantiate an xmodule instance.
""" """
action_name = 'deleted' action_name = 'deleted'
update_fcn = _delete_problem_module_state update_fcn = delete_problem_module_state
problem_url = task_input.get('problem_url') return update_problem_module_state(entry_id,
return _update_problem_module_state(entry_id, course_id, problem_url, None, update_fcn, action_name, filter_fcn=None,
update_fcn, action_name, filter_fcn=None, xmodule_instance_args=xmodule_instance_args)
xmodule_instance_args=xmodule_instance_args)
""" """
This file contains tasks that are designed to perform background operations on the This file contains tasks that are designed to perform background operations on the
running state of a course. running state of a course.
...@@ -50,7 +50,7 @@ class UpdateProblemModuleStateError(Exception): ...@@ -50,7 +50,7 @@ class UpdateProblemModuleStateError(Exception):
def _perform_module_state_update(course_id, module_state_key, student_identifier, update_fcn, action_name, filter_fcn, def _perform_module_state_update(course_id, module_state_key, student_identifier, update_fcn, action_name, filter_fcn,
xmodule_instance_args): xmodule_instance_args):
""" """
Performs generic update by visiting StudentModule instances with the update_fcn provided. Performs generic update by visiting StudentModule instances with the update_fcn provided.
...@@ -161,7 +161,7 @@ def _save_course_task(course_task): ...@@ -161,7 +161,7 @@ def _save_course_task(course_task):
course_task.save() course_task.save()
def _update_problem_module_state(entry_id, course_id, module_state_key, student_ident, update_fcn, action_name, filter_fcn, def update_problem_module_state(entry_id, update_fcn, action_name, filter_fcn,
xmodule_instance_args): xmodule_instance_args):
""" """
Performs generic update by visiting StudentModule instances with the update_fcn provided. Performs generic update by visiting StudentModule instances with the update_fcn provided.
...@@ -195,15 +195,20 @@ def _update_problem_module_state(entry_id, course_id, module_state_key, student_ ...@@ -195,15 +195,20 @@ def _update_problem_module_state(entry_id, course_id, module_state_key, student_
result object that Celery creates. result object that Celery creates.
""" """
task_id = current_task.request.id
fmt = 'Starting to update problem modules as task "{task_id}": course "{course_id}" problem "{state_key}": nothing {action} yet'
TASK_LOG.info(fmt.format(task_id=task_id, course_id=course_id, state_key=module_state_key, action=action_name))
# get the InstructorTask to be updated. If this fails, then let the exception return to Celery. # get the InstructorTask to be updated. If this fails, then let the exception return to Celery.
# There's no point in catching it here. # There's no point in catching it here.
entry = InstructorTask.objects.get(pk=entry_id) entry = InstructorTask.objects.get(pk=entry_id)
entry.task_id = task_id
_save_course_task(entry) # get inputs to use in this task from the entry:
task_id = entry.task_id
course_id = entry.course_id
task_input = json.loads(entry.task_input)
module_state_key = task_input.get('problem_url')
student_ident = task_input['student'] if 'student' in task_input else None
fmt = 'Starting to update problem modules as task "{task_id}": course "{course_id}" problem "{state_key}": nothing {action} yet'
TASK_LOG.info(fmt.format(task_id=task_id, course_id=course_id, state_key=module_state_key, action=action_name))
# add task_id to xmodule_instance_args, so that it can be output with tracking info: # add task_id to xmodule_instance_args, so that it can be output with tracking info:
if xmodule_instance_args is not None: if xmodule_instance_args is not None:
...@@ -212,6 +217,16 @@ def _update_problem_module_state(entry_id, course_id, module_state_key, student_ ...@@ -212,6 +217,16 @@ def _update_problem_module_state(entry_id, course_id, module_state_key, student_
# now that we have an entry we can try to catch failures: # now that we have an entry we can try to catch failures:
task_progress = None task_progress = None
try: try:
# check that the task_id submitted in the InstructorTask matches the current task
# that is running.
request_task_id = current_task.request.id
if task_id != request_task_id:
fmt = 'Requested task "{task_id}" did not match actual task "{actual_id}"'
message = fmt.format(task_id=task_id, course_id=course_id, state_key=module_state_key, actual_id=request_task_id)
TASK_LOG.error(message)
raise UpdateProblemModuleStateError(message)
# now do the work:
with dog_stats_api.timer('courseware.tasks.module.{0}.overall_time'.format(action_name)): with dog_stats_api.timer('courseware.tasks.module.{0}.overall_time'.format(action_name)):
task_progress = _perform_module_state_update(course_id, module_state_key, student_ident, update_fcn, task_progress = _perform_module_state_update(course_id, module_state_key, student_ident, update_fcn,
action_name, filter_fcn, xmodule_instance_args) action_name, filter_fcn, xmodule_instance_args)
...@@ -280,7 +295,7 @@ def _get_module_instance_for_task(course_id, student, module_descriptor, xmodule ...@@ -280,7 +295,7 @@ def _get_module_instance_for_task(course_id, student, module_descriptor, xmodule
@transaction.autocommit @transaction.autocommit
def _rescore_problem_module_state(module_descriptor, student_module, xmodule_instance_args=None): def rescore_problem_module_state(module_descriptor, student_module, xmodule_instance_args=None):
''' '''
Takes an XModule descriptor and a corresponding StudentModule object, and Takes an XModule descriptor and a corresponding StudentModule object, and
performs rescoring on the student's problem submission. performs rescoring on the student's problem submission.
...@@ -330,7 +345,7 @@ def _rescore_problem_module_state(module_descriptor, student_module, xmodule_ins ...@@ -330,7 +345,7 @@ def _rescore_problem_module_state(module_descriptor, student_module, xmodule_ins
@transaction.autocommit @transaction.autocommit
def _reset_problem_attempts_module_state(_module_descriptor, student_module, xmodule_instance_args=None): def reset_attempts_module_state(_module_descriptor, student_module, xmodule_instance_args=None):
""" """
Resets problem attempts to zero for specified `student_module`. Resets problem attempts to zero for specified `student_module`.
...@@ -356,7 +371,7 @@ def _reset_problem_attempts_module_state(_module_descriptor, student_module, xmo ...@@ -356,7 +371,7 @@ def _reset_problem_attempts_module_state(_module_descriptor, student_module, xmo
@transaction.autocommit @transaction.autocommit
def _delete_problem_module_state(_module_descriptor, student_module, xmodule_instance_args=None): def delete_problem_module_state(_module_descriptor, student_module, xmodule_instance_args=None):
""" """
Delete the StudentModule entry. Delete the StudentModule entry.
......
...@@ -24,7 +24,7 @@ from instructor_task.api import (get_running_instructor_tasks, ...@@ -24,7 +24,7 @@ from instructor_task.api import (get_running_instructor_tasks,
submit_delete_problem_state_for_all_students) submit_delete_problem_state_for_all_students)
from instructor_task.api_helper import (QUEUING, from instructor_task.api_helper import (QUEUING,
AlreadyRunningError, # AlreadyRunningError,
encode_problem_and_student_input, encode_problem_and_student_input,
) )
...@@ -61,12 +61,12 @@ class TaskSubmitTestCase(TestCase): ...@@ -61,12 +61,12 @@ class TaskSubmitTestCase(TestCase):
task_input, task_key = encode_problem_and_student_input(self.problem_url, student) task_input, task_key = encode_problem_and_student_input(self.problem_url, student)
instructor_task = InstructorTaskFactory.create(course_id=TEST_COURSE_ID, instructor_task = InstructorTaskFactory.create(course_id=TEST_COURSE_ID,
requester=self.instructor, requester=self.instructor,
task_input=json.dumps(task_input), task_input=json.dumps(task_input),
task_key=task_key, task_key=task_key,
task_id=task_id, task_id=task_id,
task_state=task_state, task_state=task_state,
task_output=progress_json) task_output=progress_json)
return instructor_task return instructor_task
def _create_failure_entry(self): def _create_failure_entry(self):
...@@ -101,6 +101,7 @@ class TaskSubmitTestCase(TestCase): ...@@ -101,6 +101,7 @@ class TaskSubmitTestCase(TestCase):
self.assertEquals(set(task_ids), set(progress_task_ids)) self.assertEquals(set(task_ids), set(progress_task_ids))
def _get_instructor_task_status(self, task_id): def _get_instructor_task_status(self, task_id):
"""Returns status corresponding to task_id via api method."""
request = Mock() request = Mock()
request.REQUEST = {'task_id': task_id} request.REQUEST = {'task_id': task_id}
return instructor_task_status(request) return instructor_task_status(request)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Integration Test for LMS instructor-initiated background tasks Integration Test for LMS instructor-initiated background tasks
Runs tasks on answers to course problems to validate that code Runs tasks on answers to course problems to validate that code
paths actually work. paths actually work.
""" """
import logging import logging
...@@ -32,7 +32,6 @@ from instructor_task.api import (submit_rescore_problem_for_all_students, ...@@ -32,7 +32,6 @@ from instructor_task.api import (submit_rescore_problem_for_all_students,
submit_reset_problem_attempts_for_all_students, submit_reset_problem_attempts_for_all_students,
submit_delete_problem_state_for_all_students) submit_delete_problem_state_for_all_students)
from instructor_task.models import InstructorTask from instructor_task.models import InstructorTask
from instructor_task.tests.factories import InstructorTaskFactory
from instructor_task.views import instructor_task_status from instructor_task.views import instructor_task_status
...@@ -235,6 +234,7 @@ class TestRescoringBase(LoginEnrollmentTestCase, ModuleStoreTestCase): ...@@ -235,6 +234,7 @@ class TestRescoringBase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.assertGreater(len(state['student_answers']), 0) self.assertGreater(len(state['student_answers']), 0)
def get_task_status(self, task_id): def get_task_status(self, task_id):
"""Use api method to fetch task status, using mock request."""
mock_request = Mock() mock_request = Mock()
mock_request.REQUEST = {'task_id': task_id} mock_request.REQUEST = {'task_id': task_id}
response = instructor_task_status(mock_request) response = instructor_task_status(mock_request)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment