def get_next_task_status(task, iteration_status): """ Given current task status and snapshot type provide new task status. If the second level reviewer rejects a task then initial reviewer cannot reject it further down, but must fix and submit the task. Args: task (orchestra.models.Task): The specified task object. iteration_status (orchestra.models.Iteration.Status): The action taken upon task submission (i.e., REQUESTED_REVIEW or PROVIDED_REVIEW). Returns: next_status (orchestra.models.Task.Status): The next status of `task`, once the `iteration_status` action has been completed. Raises: orchestra.core.errors.IllegalTaskSubmission: The `iteration_status` action cannot be taken for the task in its current status. """ # Task with one of these statuses cannot be submitted illegal_statuses = [ Task.Status.AWAITING_PROCESSING, Task.Status.PENDING_REVIEW, Task.Status.COMPLETE ] if task.status in illegal_statuses: raise IllegalTaskSubmission('Task has illegal status for submit.') elif iteration_status == Iteration.Status.REQUESTED_REVIEW: if task.status in (Task.Status.PROCESSING, Task.Status.REVIEWING): if _is_review_needed(task): return Task.Status.PENDING_REVIEW else: return Task.Status.COMPLETE elif task.status == Task.Status.POST_REVIEW_PROCESSING: return Task.Status.REVIEWING else: raise IllegalTaskSubmission('Task not in submittable state.') elif iteration_status == Iteration.Status.PROVIDED_REVIEW: if task.status == Task.Status.REVIEWING: return Task.Status.POST_REVIEW_PROCESSING else: raise IllegalTaskSubmission('Task not in rejectable state.')
def get_next_task_status(task, snapshot_type): """ Given current task status and snapshot type provide new task status. If the second level reviewer rejects a task then initial reviewer cannot reject it further down, but must fix and submit the task. Args: task (orchestra.models.Task): The specified task object. task_status (orchestra.models.TaskAssignment.SnapshotType): The action to take upon task submission (e.g., SUBMIT, ACCEPT, REJECT). Returns: next_status (orchestra.models.Task.Status): The next status of `task`, once the `snapshot_type` action has been completed. Raises: orchestra.core.errors.IllegalTaskSubmission: The `snapshot_type` action cannot be taken for the task in its current status. """ if snapshot_type == TaskAssignment.SnapshotType.SUBMIT: if task.status == Task.Status.PROCESSING: if _is_review_needed(task): return Task.Status.PENDING_REVIEW return Task.Status.COMPLETE elif task.status == Task.Status.POST_REVIEW_PROCESSING: return Task.Status.REVIEWING raise IllegalTaskSubmission('Worker can only submit a task.') elif snapshot_type == TaskAssignment.SnapshotType.REJECT: if task.status == Task.Status.REVIEWING: return Task.Status.POST_REVIEW_PROCESSING raise IllegalTaskSubmission('Only reviewer can reject the task.') elif snapshot_type == TaskAssignment.SnapshotType.ACCEPT: if task.status == Task.Status.REVIEWING: if _is_review_needed(task): return Task.Status.PENDING_REVIEW return Task.Status.COMPLETE raise IllegalTaskSubmission('Only reviewer can accept the task.') raise IllegalTaskSubmission('Unknown task state.')
def submit_task(task_id, task_data, iteration_status, worker): """ Returns a dict mapping task prerequisites onto their latest task assignment information. The dict is of the form: {'previous-slug': {task_assignment_data}, ...} Args: task_id (int): The ID of the task to submit. task_data (str): A JSON blob of task data to submit. iteration_status (orchestra.models.Iteration.Status): The action taken upon task submission (i.e., REQUESTED_REVIEW or PROVIDED_REVIEW). worker (orchestra.models.Worker): The worker submitting the task. Returns: task (orchestra.models.Task): The modified task object. Raises: orchestra.core.errors.IllegalTaskSubmission: Submission prerequisites for the task are incomplete or the assignment is in a non-processing state. orchestra.core.errors.TaskAssignmentError: Worker belongs to more than one assignment for the given task. orchestra.core.errors.TaskStatusError: Task has already been completed. """ submit_datetime = timezone.now() task = Task.objects.select_related('step', 'project').get(id=task_id) step = task.step if not _are_desired_steps_completed_on_project(step.submission_depends_on, project=task.project): raise IllegalTaskSubmission('Submission prerequisites are not ' 'complete.') if task.status == Task.Status.COMPLETE: raise TaskStatusError('Task already completed') # Use select_for_update to prevent concurrency issues with save_task. # See https://github.com/b12io/orchestra/issues/2. assignments = (TaskAssignment.objects.select_for_update().filter( worker=worker, task=task)) # Worker can belong to only one assignment for a given task. if not assignments.count() == 1: raise TaskAssignmentError( 'Task assignment with worker is in broken state.') assignment = assignments[0] if assignment.status != TaskAssignment.Status.PROCESSING: raise IllegalTaskSubmission('Worker is not allowed to submit') next_status = get_next_task_status(task, iteration_status) assignment.in_progress_task_data = task_data # Submit latest iteration latest_iteration = get_latest_iteration(assignment) latest_iteration.status = iteration_status latest_iteration.submitted_data = assignment.in_progress_task_data latest_iteration.end_datetime = submit_datetime latest_iteration.save() assignment.status = TaskAssignment.Status.SUBMITTED assignment.save() previous_status = task.status task.status = next_status task.save() if task.status == Task.Status.PENDING_REVIEW: # Check the assignment policy to try to assign a reviewer automatically task = _preassign_workers(task, AssignmentPolicyType.REVIEWER) elif task.status == Task.Status.REVIEWING: update_related_assignment_status(task, assignment.assignment_counter + 1, assignment.in_progress_task_data, submit_datetime) elif task.status == Task.Status.POST_REVIEW_PROCESSING: update_related_assignment_status(task, assignment.assignment_counter - 1, assignment.in_progress_task_data, submit_datetime) elif task.status == Task.Status.COMPLETE: create_subsequent_tasks(task.project) notify_status_change(task, previous_status) return task
def submit_task(task_id, task_data, snapshot_type, worker, work_time_seconds): """ Returns a dict mapping task prerequisites onto their latest task assignment information. The dict is of the form: {'previous-slug': {task_assignment_data}, ...} Args: task_id (int): The ID of the task to submit. task_data (str): A JSON blob of task data to submit. snapshot_type (orchestra.models.TaskAssignment.SnapshotType): The action to take upon task submission (e.g., SUBMIT, ACCEPT, REJECT). worker (orchestra.models.Worker): The worker submitting the task. work_time_seconds (int): The time taken by the worker on the latest iteration of their task assignment. Returns: task (orchestra.models.Task): The modified task object. Raises: orchestra.core.errors.IllegalTaskSubmission: Submission prerequisites for the task are incomplete or the assignment is in a non-processing state. orchestra.core.errors.TaskAssignmentError: Worker belongs to more than one assignment for the given task. orchestra.core.errors.TaskStatusError: Task has already been completed. """ task = Task.objects.select_related('step', 'project').get(id=task_id) step = task.step if not _are_desired_steps_completed_on_project(step.submission_depends_on, project=task.project): raise IllegalTaskSubmission('Submission prerequisites are not ' 'complete.') if task.status == Task.Status.COMPLETE: raise TaskStatusError('Task already completed') # Use select_for_update to prevent concurrency issues with save_task. # See https://github.com/unlimitedlabs/orchestra/issues/2. assignments = (TaskAssignment.objects.select_for_update().filter( worker=worker, task=task)) # Worker can belong to only one assignment for a given task. if not assignments.count() == 1: raise TaskAssignmentError( 'Task assignment with worker is in broken state.') assignment = assignments[0] if assignment.status != TaskAssignment.Status.PROCESSING: raise IllegalTaskSubmission('Worker is not allowed to submit') next_status = get_next_task_status(task, snapshot_type) assignment.in_progress_task_data = task_data assignment.snapshots['snapshots'].append({ 'data': assignment.in_progress_task_data, 'datetime': timezone.now().isoformat(), 'type': snapshot_type, 'work_time_seconds': work_time_seconds }) assignment.status = TaskAssignment.Status.SUBMITTED assignment.save() previous_status = task.status task.status = next_status task.save() if task.status == Task.Status.REVIEWING: update_related_assignment_status(task, assignment.assignment_counter + 1, assignment.in_progress_task_data) elif task.status == Task.Status.POST_REVIEW_PROCESSING: update_related_assignment_status(task, assignment.assignment_counter - 1, assignment.in_progress_task_data) elif task.status == Task.Status.COMPLETE: create_subsequent_tasks(task.project) notify_status_change(task, previous_status) return task