def assign_task(worker_id, task_id): """ Return a given task after assigning or reassigning it to the specified worker. Args: worker_id (int): The ID of the worker to be assigned. task_id (int): The ID of the task to be assigned. Returns: task (orchestra.models.Task): The newly assigned task. Raises: orchestra.core.errors.TaskAssignmentError: The specified worker is already assigned to the given task or the task status is not compatible with new assignment. orchestra.core.errors.WorkerCertificationError: The specified worker is not certified for the given task. """ worker = Worker.objects.get(id=worker_id) task = Task.objects.get(id=task_id) required_role = role_required_for_new_task(task) assignment = current_assignment(task) if not is_worker_certified_for_task(worker, task, required_role): raise WorkerCertificationError('Worker not certified for this task.') if task.is_worker_assigned(worker): raise TaskAssignmentError('Worker already assigned to this task.') assignment_counter = task.assignments.count() in_progress_task_data = {} if assignment: # In-progress task data is the latest # submission by a previous worker in_progress_task_data = assignment.in_progress_task_data previous_status = task.status if previous_status == Task.Status.AWAITING_PROCESSING: task.status = Task.Status.PROCESSING elif previous_status == Task.Status.PENDING_REVIEW: task.status = Task.Status.REVIEWING task.save() assignment = (TaskAssignment.objects.create( worker=worker, task=task, status=TaskAssignment.Status.PROCESSING, assignment_counter=assignment_counter, in_progress_task_data=in_progress_task_data)) Iteration.objects.create(assignment=assignment, start_datetime=assignment.start_datetime) add_worker_to_project_team(worker, task.project) notify_status_change(task, previous_status) return task
def end_project(project_id): """ Mark the specified project and its component tasks as aborted. Args: project_id (int): The ID of the project to abort. Returns: None """ project = Project.objects.get(id=project_id) project.status = Project.Status.ABORTED project.save() for task in project.tasks.all(): task.status = Task.Status.ABORTED task.save() notify_status_change(task, assignment_history(task))
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, 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 assign_task(worker_id, task_id): """ Return a given task after assigning or reassigning it to the specified worker. Args: worker_id (int): The ID of the worker to be assigned. task_id (int): The ID of the task to be assigned. Returns: task (orchestra.models.Task): The newly assigned task. Raises: orchestra.core.errors.TaskAssignmentError: The specified worker is already assigned to the given task or the task status is not compatible with new assignment. orchestra.core.errors.WorkerCertificationError: The specified worker is not certified for the given task. """ worker = Worker.objects.get(id=worker_id) task = Task.objects.get(id=task_id) required_role = role_required_for_new_task(task) assignment = current_assignment(task) if not is_worker_certified_for_task(worker, task, required_role): raise WorkerCertificationError('Worker not certified for this task.') if task.is_worker_assigned(worker): raise TaskAssignmentError('Worker already assigned to this task.') assignment_counter = task.assignments.count() in_progress_task_data = {} if assignment: # In-progress task data is the latest # submission by a previous worker in_progress_task_data = assignment.in_progress_task_data previous_status = task.status if previous_status == Task.Status.AWAITING_PROCESSING: task.status = Task.Status.PROCESSING elif previous_status == Task.Status.PENDING_REVIEW: task.status = Task.Status.REVIEWING task.save() assignment = ( TaskAssignment.objects .create(worker=worker, task=task, status=TaskAssignment.Status.PROCESSING, assignment_counter=assignment_counter, in_progress_task_data=in_progress_task_data)) Iteration.objects.create( assignment=assignment, start_datetime=assignment.start_datetime) add_worker_to_project_team(worker, 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.get(id=task_id) workflow = get_workflow_by_slug(task.project.workflow_slug) step = workflow.get_step(task.step_slug) 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': datetime.utcnow().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
def assign_task(worker_id, task_id): """ Return a given task after assigning or reassigning it to the specified worker. Args: worker_id (int): The ID of the worker to be assigned. task_id (int): The ID of the task to be assigned. Returns: task (orchestra.models.Task): The newly assigned task. Raises: orchestra.core.errors.TaskAssignmentError: The specified worker is already assigned to the given task or the task status is not compatible with new assignment. orchestra.core.errors.WorkerCertificationError: The specified worker is not certified for the given task. """ worker = Worker.objects.get(id=worker_id) task = Task.objects.get(id=task_id) required_role, requires_reassign = _role_required_to_assign(task) assignment = current_assignment(task) if not _worker_certified_for_task(worker, task, required_role): raise WorkerCertificationError('Worker not certified for this task.') if is_worker_assigned_to_task(worker, task): raise TaskAssignmentError('Worker already assigned to this task.') # If task is currently in progress, reassign it if requires_reassign: assignment.worker = worker assignment.save() add_worker_to_project_team(worker, task.project) return task # Otherwise, create new assignment assignment_counter = task.assignments.count() in_progress_task_data = {} if required_role == WorkerCertification.Role.REVIEWER: # In-progress task data is the latest # submission by a previous worker in_progress_task_data = assignment.in_progress_task_data previous_status = task.status if previous_status == Task.Status.AWAITING_PROCESSING: task.status = Task.Status.PROCESSING elif previous_status == Task.Status.PENDING_REVIEW: task.status = Task.Status.REVIEWING else: raise TaskAssignmentError('Status incompatible with new assignment') task.save() (TaskAssignment.objects .create(worker=worker, task=task, status=TaskAssignment.Status.PROCESSING, assignment_counter=assignment_counter, in_progress_task_data=in_progress_task_data, snapshots=empty_snapshots())) add_worker_to_project_team(worker, 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