def create_subsequent_tasks_api(request): project_id = load_encoded_json(request.body)['project_id'] try: project = Project.objects.get(id=project_id) except Project.DoesNotExist: raise BadRequest('Project not found for the given id.') create_subsequent_tasks(project)
def create_project_with_tasks(workflow_slug, description, priority, task_class, project_data, review_document_url, workflow_version_slug=None): # Allow backwards compatibility with calls that pass in a version slug in # the 'workflow_slug' variable. # TODO(dhaas): be less backward-compatible? if workflow_version_slug is None: try: workflow_version = WorkflowVersion.objects.get(slug=workflow_slug) except WorkflowVersion.MultipleObjectsReturned: raise ValueError('No workflow slug passed, and version slug {} is ' 'not unique.'.format(workflow_slug)) else: workflow_version = WorkflowVersion.objects.get( slug=workflow_version_slug, workflow__slug=workflow_slug) project = Project.objects.create(workflow_version=workflow_version, short_description=description, priority=priority, project_data=project_data, task_class=task_class, review_document_url=review_document_url) create_project_slack_group(project) create_project_google_folder(project) create_subsequent_tasks(project) return project
def test_preassign_workers(self, mock_mail, mock_slack): request_cause = StaffBotRequest.RequestCause.TASK_POLICY.value staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() project = self.projects['staffbot_assignment_policy'] # Create first task in test project create_subsequent_tasks(project) address_staffing_requests() self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) # Mock mail should be called if we autostaff self.assertTrue(mock_mail.called) self.assertTrue(mock_slack.called) # Assert we created new StaffingRequestInquirys because of autostaff new_staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() self.assertTrue(staffing_request_count < new_staffing_request_count) self.assertEqual(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEqual(related_task.assignments.count(), 0) self.assertEqual(related_task.status, Task.Status.AWAITING_PROCESSING)
def test_role_counter_required_for_new_task(self): task = TaskFactory(status=Task.Status.COMPLETE) with self.assertRaises(TaskAssignmentError): role_counter_required_for_new_task(task) project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 task = project.tasks.first() counter = role_counter_required_for_new_task(task) self.assertEquals(counter, 0) initial_task = assign_task(self.workers[0].id, task.id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) counter = role_counter_required_for_new_task(initial_task) self.assertEquals(counter, 1) initial_task = assign_task(self.workers[1].id, task.id) initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[1]) counter = role_counter_required_for_new_task(initial_task) self.assertEquals(counter, 2)
def test_malformed_creation_policy(self): project = self.projects['creation_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[ workflow_version.slug]['creation_policy_step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, creation_policy={}, ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot have an invalid blob for the creation_policy with self.assertRaises(CreationPolicyError): create_subsequent_tasks(project)
def setUp(self): super().setUp() setup_models(self) machine_workflow_version = ( self.workflow_versions['machine_workflow_version']) self.step = machine_workflow_version.steps.get(slug='machine_step') self.assertIsNotNone(self.step) self.step.execution_function = { 'path': 'orchestra.tests.helpers.workflow.machine_task_function', } self.step.save() patcher = patch( 'orchestra.tests.helpers.workflow.machine_task_function', return_value={'data': ''}) self.machine_function_mock = patcher.start() self.addCleanup(patcher.stop) self.project = ProjectFactory( workflow_version=machine_workflow_version) create_subsequent_tasks(self.project) self.task = self.project.tasks.first() self.assertIsNotNone(self.task) # Since create_subsequent_tasks will automatically run the machine task # we need to reset the task each time it's called self._reset_task()
def test_malformed_creation_policy(self): project = self.projects['creation_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[ workflow_version.slug]['creation_policy_step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, creation_policy={}, ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot have an invalid blob for the creation_policy with self.assertRaises(CreationPolicyError): create_subsequent_tasks(project)
def test_todolist_templates_to_apply(self): project = self.projects['assignment_policy'] mock = MagicMock(return_value=True) with patch('orchestra.utils.task_lifecycle.add_todolist_template', new=mock): # Create first task in test project create_subsequent_tasks(project) assert mock.called_once assert mock.call_args[0][0] == 'project-checklist'
def test_task_form_init(self): """ Test task form initialization for new, human and machine tasks """ # Create new task form # (Test form init with no task instance) TaskForm() project = Project.objects.get(workflow_slug='test_workflow_2') self.assertEquals(Task.objects.filter(project=project).count(), 0) create_subsequent_tasks(project) # Human task was created but not assigned # (Test form init with empty assignment history) self.assertEquals(Task.objects.filter(project=project).count(), 1) human_task = Task.objects.filter(project=project).first() form = TaskForm(instance=human_task) self.assertEquals(form.fields['currently_assigned_to'].initial, None) # Human task assigned to entry_level worker # (Test form init with a single entry-level worker) human_task = assign_task(self.workers[0].id, human_task.id) form = TaskForm(instance=human_task) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=True): human_task = submit_task(human_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[0].id) # Human task under review # (Test form init with both an entry-level worker and reviewer) human_task = assign_task(self.workers[1].id, human_task.id) form = TaskForm(instance=human_task) with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): human_task = submit_task(human_task.id, {}, TaskAssignment.SnapshotType.ACCEPT, self.workers[1], 0) self.assertEquals(form.fields['currently_assigned_to'].initial, self.workers[1].id) # Machine task was created # (Test form init with a machine task) self.assertEquals(Task.objects.filter(project=project).count(), 2) machine_task = (Task.objects.filter(project=project) .exclude(id=human_task.id).first()) form = TaskForm(instance=machine_task) self.assertEquals(form.fields['currently_assigned_to'].initial, None)
def test_complete_all_tasks_slack_annoucement(self, mock_slack_archive): project = self.projects['single_human_step'] create_subsequent_tasks(project) task = Task.objects.get(project=project) assign_task(self.workers[1].id, task.id) self.assertEqual(project.status, Project.Status.ACTIVE) self.assertFalse(mock_slack_archive.called) complete_and_skip_task(task.id) create_subsequent_tasks(project) project.refresh_from_db() self.assertEqual(project.status, Project.Status.COMPLETED) self.assertTrue(mock_slack_archive.called)
def create_project_with_tasks(workflow_slug, workflow_version_slug, description, priority, task_class, project_data): workflow_version = WorkflowVersion.objects.get( slug=workflow_version_slug, workflow__slug=workflow_slug) project = Project.objects.create(workflow_version=workflow_version, short_description=description, priority=priority, project_data=project_data, task_class=task_class) create_project_google_folder(project) create_project_slack_group(project) create_subsequent_tasks(project) return project
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[4], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue( is_worker_assigned_to_task(self.workers[4], related_task)) # Reset project project.tasks.all().delete()
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[0], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, TaskAssignment.SnapshotType.SUBMIT, self.workers[4], 0) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue(is_worker_assigned_to_task(self.workers[4], related_task)) # Reset project project.tasks.all().delete()
def test_schedule_machine_tasks(self, mock_schedule): project = self.projects['test_human_and_machine'] # Create first task in project create_subsequent_tasks(project) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[0].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() create_subsequent_tasks(project) self.assertEqual(mock_schedule.call_count, 1) self.assertEqual(mock_schedule.call_args[0][0], project) steps = list(project.workflow_version.steps.filter(is_human=False)) self.assertEqual(mock_schedule.call_args[0][1], steps)
def test_schedule_machine_tasks_failed(self, mock_schedule, mock_preassign): project = self.projects['test_human_and_machine'] # Create first task in project create_subsequent_tasks(project) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[0].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() mock_preassign.side_effect = Exception with self.assertRaises(Exception): create_subsequent_tasks(project) mock_schedule.assert_not_called()
def execute(project_id, step_slug): project = Project.objects.get(id=project_id) step = Step.objects.get(slug=step_slug, workflow_version=project.workflow_version) task = Task.objects.get(project=project, step=step) # Run machine function if step.is_human: raise MachineExecutionError('Step worker type is not machine') if task.status == Task.Status.COMPLETE: raise MachineExecutionError('Task assignment already completed') # Machine tasks are only assigned to one worker/machine, # so they should only have one task assignment, # and should never be submitted for review. count = task.assignments.count() if count > 1: raise MachineExecutionError('At most 1 assignment per machine task') elif count == 1: task_assignment = task.assignments.first() if task_assignment.status == TaskAssignment.Status.SUBMITTED: raise MachineExecutionError('Task assignment completed ' 'but task is not!') else: task_assignment = ( TaskAssignment.objects .create(task=task, status=TaskAssignment.Status.PROCESSING, in_progress_task_data={}, snapshots={})) prerequisites = previously_completed_task_data(task) function_module = import_module(step.execution_function['module']) function = getattr(function_module, step.execution_function['name']) task_data = function(project.project_data, prerequisites) task_assignment.status = TaskAssignment.Status.SUBMITTED task_assignment.in_progress_task_data = task_data task_assignment.save() task.status = Task.Status.COMPLETE task.save() create_subsequent_tasks(project)
def test_always_create_policy(self): project = self.projects['creation_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should not be created, it never is with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEqual(project.tasks.count(), 1)
def test_unarchive_slack_channel_api(self, mock_archive, mock_unarchive): project = self.projects['single_human_step'] create_subsequent_tasks(project) task = Task.objects.get(project=project) assign_task(self.workers[1].id, task.id) complete_and_skip_task(task.id) create_subsequent_tasks(project) self.assertTrue(mock_archive.called) response = self.api_client.post(reverse( ('orchestra:orchestra:project_management' ':unarchive_slack_channel')), json.dumps({ 'project_id': project.id, }), content_type='application/json') self.assertEqual(response.status_code, 200) self.assertTrue(mock_unarchive.called)
def test_task_creation(self): """ Test human and machine task creation """ Task.objects.filter(status=Task.Status.AWAITING_PROCESSING).delete() project = self.projects['test_human_and_machine'] self.assertEquals(Task.objects.filter(project=project).count(), 0) create_subsequent_tasks(project) # Human Task was created self.assertEquals(Task.objects.filter(project=project).count(), 1) response = (self.clients[0].get( '/orchestra/api/interface/new_task_assignment/entry_level/')) self.assertEquals(response.status_code, 200) human_step = self.workflow_steps['test_workflow_2']['step4'] task = Task.objects.get(step=human_step, project=project) data = {'submit_key1': 'submit_val1'} # user 0 submits a task response = self._submit_assignment(self.clients[0], task.id, data=data) self.assertEquals(response.status_code, 200) # Machine Task was created self.assertEquals(Task.objects.filter(project=project).count(), 2) machine_step = self.workflow_steps['test_workflow_2']['simple_machine'] machine_task_assignment = ( TaskAssignment.objects .filter(task__step=machine_step, task__project=project)[0]) self.assertTrue(machine_task_assignment.status, TaskAssignment.Status.SUBMITTED) self.assertTrue(machine_task_assignment.in_progress_task_data, {'simple': 'json'}) self.assertTrue(machine_task_assignment.task.status, Task.Status.COMPLETE)
def create_project_with_tasks(workflow_slug, description, priority, task_class, project_data, review_document_url): project = Project.objects.create(workflow_slug=workflow_slug, short_description=description, priority=priority, project_data=project_data, task_class=task_class, review_document_url=review_document_url) create_project_slack_group(project) create_project_google_folder(project) create_subsequent_tasks(project) return project
def test_completion_ends_project_false(self, mock_slack_archive): project = self.projects['test_human_and_machine'] create_subsequent_tasks(project) task = project.tasks.first() assign_task(self.workers[1].id, task.id) self.assertEqual(project.status, Project.Status.ACTIVE) self.assertFalse(mock_slack_archive.called) task.status = Task.Status.COMPLETE task.save() create_subsequent_tasks(project) project.refresh_from_db() incomplete_tasks = (Task.objects.filter(project=project).exclude( Q(status=Task.Status.COMPLETE) | Q(status=Task.Status.ABORTED))).count() self.assertTrue(incomplete_tasks > 0) self.assertEqual(project.status, Project.Status.ACTIVE) self.assertFalse(mock_slack_archive.called)
def execute(project_id, step_slug): project = Project.objects.get(id=project_id) workflow = get_workflow_by_slug(project.workflow_slug) step = workflow.get_step(step_slug) task = Task.objects.get(project=project, step_slug=step_slug) # Run machine function if step.worker_type != Step.WorkerType.MACHINE: raise MachineExecutionError('Step worker type is not machine') if task.status == Task.Status.COMPLETE: raise MachineExecutionError('Task assignment already completed') # Machine tasks are only assigned to one worker/machine, # so they should only have one task assignment, # and should never be submitted for review. count = task.assignments.count() if count > 1: raise MachineExecutionError('At most 1 assignment per machine task') elif count == 1: task_assignment = task.assignments.first() if task_assignment.status == TaskAssignment.Status.SUBMITTED: raise MachineExecutionError('Task assignment completed ' 'but task is not!') else: task_assignment = ( TaskAssignment.objects .create(task=task, status=TaskAssignment.Status.PROCESSING, in_progress_task_data={}, snapshots={})) prerequisites = previously_completed_task_data(task) task_data = step.function(project.project_data, prerequisites) task_assignment.status = TaskAssignment.Status.SUBMITTED task_assignment.in_progress_task_data = task_data task_assignment.save() task.status = Task.Status.COMPLETE task.save() create_subsequent_tasks(project)
def test_complete_and_skip_task_api(self): # This task, when submitted, will be moved into reviewing # state because it's already been assigned. task = self.tasks['project_management_task'] response = self.api_client.post( reverse( 'orchestra:orchestra:project_management:' 'complete_and_skip_task'), json.dumps({ 'task_id': task.id, }), content_type='application/json') self.assertEqual(response.status_code, 200) task.refresh_from_db() self.assertEqual(task.status, Task.Status.REVIEWING) assignment_statuses = [ TaskAssignment.Status.SUBMITTED, TaskAssignment.Status.SUBMITTED, TaskAssignment.Status.PROCESSING] for index, assignment in enumerate( task.assignments.all().order_by('assignment_counter')): self.assertEqual( assignment.status, assignment_statuses[index]) # Check that dependent tasks have already been created num_tasks = task.project.tasks.count() create_subsequent_tasks(task.project) self.assertEqual(task.project.tasks.count(), num_tasks) # This task, when submitted, will be moved into a completed # state because it has no assignments. task = self.tasks['awaiting_processing'] response = self.api_client.post( reverse( 'orchestra:orchestra:project_management:' 'complete_and_skip_task'), json.dumps({ 'task_id': task.id, }), content_type='application/json') self.assertEqual(response.status_code, 200) task.refresh_from_db() self.assertEqual(task.status, Task.Status.COMPLETE)
def test_complete_and_skip_task_api(self): task = self.tasks['project_management_task'] response = self.api_client.post(reverse( 'orchestra:orchestra:project_management:' 'complete_and_skip_task'), json.dumps({ 'task_id': task.id, }), content_type='application/json') self.assertEquals(response.status_code, 200) task.refresh_from_db() self.assertEquals(task.status, Task.Status.COMPLETE) for assignment in task.assignments.all(): self.assertEquals(assignment.status, TaskAssignment.Status.SUBMITTED) # Check that dependent tasks have already been created # TODO(jrbotros): Create a `get_dependent_tasks` function num_tasks = task.project.tasks.count() create_subsequent_tasks(task.project) self.assertEquals(task.project.tasks.count(), num_tasks)
def create_project_with_tasks(workflow_slug, workflow_version_slug, description, priority, task_class, project_data): workflow_version = WorkflowVersion.objects.get( slug=workflow_version_slug, workflow__slug=workflow_slug) project = Project.objects.create(workflow_version=workflow_version, short_description=description, priority=priority, project_data=project_data, task_class=task_class) create_project_slack_group(project) create_project_google_folder(project) create_subsequent_tasks(project) return project
def execute(project_id, step_slug): project = Project.objects.get(id=project_id) step = Step.objects.get(slug=step_slug, workflow_version=project.workflow_version) task = Task.objects.get(project=project, step=step) # Run machine function if step.is_human: raise MachineExecutionError("Step worker type is not machine") if task.status == Task.Status.COMPLETE: raise MachineExecutionError("Task assignment already completed") # Machine tasks are only assigned to one worker/machine, # so they should only have one task assignment, # and should never be submitted for review. count = task.assignments.count() if count > 1: raise MachineExecutionError("At most 1 assignment per machine task") elif count == 1: task_assignment = task.assignments.first() if task_assignment.status == TaskAssignment.Status.SUBMITTED: raise MachineExecutionError("Task assignment completed " "but task is not!") else: task_assignment = TaskAssignment.objects.create( task=task, status=TaskAssignment.Status.PROCESSING, in_progress_task_data={}, snapshots={} ) prerequisites = previously_completed_task_data(task) function_module = import_module(step.execution_function["module"]) function = getattr(function_module, step.execution_function["name"]) task_data = function(project.project_data, prerequisites) task_assignment.status = TaskAssignment.Status.SUBMITTED task_assignment.in_progress_task_data = task_data task_assignment.save() task.status = Task.Status.COMPLETE task.save() create_subsequent_tasks(project)
def test_complete_and_skip_task_api(self): task = self.tasks['project_management_task'] response = self.api_client.post( reverse( 'orchestra:orchestra:project_management:' 'complete_and_skip_task'), json.dumps({ 'task_id': task.id, }), content_type='application/json') self.assertEquals(response.status_code, 200) task.refresh_from_db() self.assertEquals(task.status, Task.Status.COMPLETE) for assignment in task.assignments.all(): self.assertEquals( assignment.status, TaskAssignment.Status.SUBMITTED) # Check that dependent tasks have already been created # TODO(jrbotros): Create a `get_dependent_tasks` function num_tasks = task.project.tasks.count() create_subsequent_tasks(task.project) self.assertEquals(task.project.tasks.count(), num_tasks)
def test_task_creation(self): """ Test human and machine task creation """ Task.objects.filter(status=Task.Status.AWAITING_PROCESSING).delete() project = self.projects['test_human_and_machine'] self.assertEqual(Task.objects.filter(project=project).count(), 0) create_subsequent_tasks(project) # Human Task was created self.assertEqual(Task.objects.filter(project=project).count(), 1) response = (self.clients[0].get( '/orchestra/api/interface/new_task_assignment/entry_level/')) self.assertEqual(response.status_code, 200) human_step = self.workflow_steps['test_workflow_2']['step4'] task = Task.objects.get(step=human_step, project=project) data = {'submit_key1': 'submit_val1'} # user 0 submits a task response = self._submit_assignment(self.clients[0], task.id, data=data) self.assertEqual(response.status_code, 200) # Machine Task was created self.assertEqual(Task.objects.filter(project=project).count(), 2) machine_step = self.workflow_steps['test_workflow_2']['simple_machine'] machine_task_assignment = (TaskAssignment.objects.filter( task__step=machine_step, task__project=project)[0]) self.assertTrue(machine_task_assignment.status, TaskAssignment.Status.SUBMITTED) self.assertTrue(machine_task_assignment.in_progress_task_data, {'simple': 'json'}) self.assertTrue(machine_task_assignment.task.status, Task.Status.COMPLETE)
def test_malformed_assignment_policy(self): project = self.projects['assignment_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[workflow_version.slug]['step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, assignment_policy={ 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['step_0'] }, } } }, creation_policy=get_default_creation_policy(), ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot preassign machine task with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset project project.tasks.all().delete() # Machine should not be member of assignment policy first_step.assignment_policy = { 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['machine_step'] }, }, } } first_step.save() with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project)
def test_malformed_assignment_policy(self): project = self.projects['assignment_policy'] workflow_version = project.workflow_version first_step = self.workflow_steps[workflow_version.slug]['step_0'] # Create an invalid machine step with an assignment policy malformed_step = StepFactory( workflow_version=workflow_version, slug='machine_step', is_human=False, assignment_policy={ 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['step_0'] }, } } }, creation_policy=get_default_creation_policy(), ) malformed_step.creation_depends_on.add(first_step) # Create first task in project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot preassign machine task with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset project project.tasks.all().delete() # Machine should not be member of assignment policy first_step.assignment_policy = { 'policy_function': { 'entry_level': { 'path': ('orchestra.assignment_policies.' 'previously_completed_steps'), 'kwargs': { 'related_steps': ['machine_step'] }, }, } } first_step.save() with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project)
def test_preassign_workers(self, mock_mail, mock_slack): request_cause = StaffBotRequest.RequestCause.AUTOSTAFF.value staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() project = self.projects['staffbot_assignment_policy'] # Create first task in test project create_subsequent_tasks(project) send_staffing_requests() self.assertEqual(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) # Mock mail should be called if we autostaff self.assertTrue(mock_mail.called) self.assertTrue(mock_slack.called) # Assert we created new StaffingRequestInquirys because of autostaff new_staffing_request_count = StaffingRequestInquiry.objects.filter( request__request_cause=request_cause).count() self.assertTrue(staffing_request_count < new_staffing_request_count) self.assertEqual(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEqual(related_task.assignments.count(), 0) self.assertEqual(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete()
def test_malformed_assignment_policy(self): project = self.projects['assignment_policy'] # Machine should not have an assignment policy workflow = get_workflow_by_slug('assignment_policy_workflow') machine_step = Step( slug='machine_step', worker_type=Step.WorkerType.MACHINE, assignment_policy={'policy': 'previously_completed_steps', 'steps': ['step_0']}, creation_depends_on=[workflow.get_step('step_0')], function=lambda *args: None, ) workflow.add_step(machine_step) # Create first task in project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 and mark as complete initial_task = assign_task(self.workers[4].id, project.tasks.first().id) initial_task.status = Task.Status.COMPLETE initial_task.save() # Cannot preassign machine task with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset project project.tasks.all().delete() # Machine should not be member of assignment policy (workflow.get_step('step_0') .assignment_policy) = {'policy': 'previously_completed_steps', 'steps': ['machine_step']} with self.assertRaises(AssignmentPolicyError): create_subsequent_tasks(project) # Reset workflow and project (workflow.get_step('step_0') .assignment_policy) = {'policy': 'anyone_certified'} del workflow.steps['machine_step'] project.tasks.all().delete()
def test_create_and_handle_sanity_checks(self, mock_slack): # Initialize relevant project. create_subsequent_tasks(self.projects['sanitybot']) # Initialize irrelevant project. create_subsequent_tasks(self.projects['test_human_and_machine']) # No sanity checks exist, and Slack hasn't received a message. sanity_checks = SanityCheck.objects.all() self.assertEqual(sanity_checks.count(), 0) self.assertEqual(mock_slack.call_count, 0) create_and_handle_sanity_checks() # Of the four sanity checks for this project, three were # triggered by orchestra.tests.helpers.workflow.check_project. sanity_checks = SanityCheck.objects.all() self.assertEqual( dict(Counter(sanity_checks.values_list('check_slug', flat=True))), { 'frequently_repeating_check': 1, 'infrequently_repeating_check': 1, 'onetime_check': 1 }) for sanity_check in sanity_checks: self.assertEqual( sanity_check.project.workflow_version.slug, 'sanitybot_workflow') # Three slack messages for three sanity checks. self.assertEqual(mock_slack.call_count, 3) # Too little time has passed, so we expect no new sanity checks. create_and_handle_sanity_checks() # Still only three sanity check exists for the relevant project. sanity_checks = SanityCheck.objects.all() self.assertEqual(sanity_checks.count(), 3) # Slack didn't get called again. self.assertEqual(mock_slack.call_count, 3) # Mark the sanity checks as having happened two days ago. Only # the `frequently_repeating_check` should trigger. for sanity_check in sanity_checks: sanity_check.created_at = ( sanity_check.created_at - timedelta(days=2)) sanity_check.save() create_and_handle_sanity_checks() # Look for the three old sanity checks and a new # `frequently_repeating_check`. sanity_checks = SanityCheck.objects.all() self.assertEqual( dict(Counter(sanity_checks.values_list('check_slug', flat=True))), { 'frequently_repeating_check': 2, 'infrequently_repeating_check': 1, 'onetime_check': 1 }) for sanity_check in sanity_checks: self.assertEqual( sanity_check.project.workflow_version.slug, 'sanitybot_workflow') # Slack got called another time. self.assertEqual(mock_slack.call_count, 4) create_and_handle_sanity_checks() # Still only four sanity checks exists for the relevant project. sanity_checks = SanityCheck.objects.all() self.assertEqual(sanity_checks.count(), 4) # Slack didn't get called again. self.assertEqual(mock_slack.call_count, 4)
def execute(project_id, step_slug): project = Project.objects.get(id=project_id) step = Step.objects.get(slug=step_slug, workflow_version=project.workflow_version) task = Task.objects.get(project=project, step=step) # Run machine function if step.is_human: raise MachineExecutionError('Step worker type is not machine') if task.status == Task.Status.COMPLETE: raise MachineExecutionError('Task assignment already completed') # Machine tasks are only assigned to one worker/machine, # so they should only have one task assignment, # and should never be submitted for review. with transaction.atomic(): # Uniqueness constraint on assignnment_counter and task prevents # concurrent creation of more than one assignment task_assignment, created = TaskAssignment.objects.get_or_create( assignment_counter=0, task=task, defaults={ 'status': TaskAssignment.Status.PROCESSING, 'in_progress_task_data': {}}) if created: task.status = Task.Status.PROCESSING task.save() Iteration.objects.create( assignment=task_assignment, start_datetime=task_assignment.start_datetime) else: # Task assignment already exists if task_assignment.status == TaskAssignment.Status.FAILED: # Pick up failed task for reprocessing task_assignment.status = TaskAssignment.Status.PROCESSING task_assignment.save() else: # Task assignment already processing raise MachineExecutionError( 'Task already picked up by another machine') prerequisites = get_previously_completed_task_data(task.step, task.project) function = locate(step.execution_function['path']) kwargs = step.execution_function.get('kwargs', {}) try: project_data = project.project_data project_data['project_id'] = project_id task_data = function(project_data, prerequisites, **kwargs) except: task_assignment.status = TaskAssignment.Status.FAILED logger.exception('Machine task has failed') task_assignment.save() return task_assignment.status = TaskAssignment.Status.SUBMITTED task_assignment.in_progress_task_data = task_data task_assignment.save() if task.project.status == Project.Status.ABORTED: # If a long-running task's project was aborted while running, we ensure # the aborted state on the task. task.status = Task.Status.ABORTED task.save() else: task.status = Task.Status.COMPLETE task.save() iteration = get_latest_iteration(task_assignment) iteration.status = Iteration.Status.REQUESTED_REVIEW iteration.submitted_data = task_data iteration.end_datetime = timezone.now() iteration.save() create_subsequent_tasks(project)
def execute(project_id, step_slug): project = Project.objects.get(id=project_id) step = Step.objects.get(slug=step_slug, workflow_version=project.workflow_version) task = Task.objects.get(project=project, step=step) # Run machine function if step.is_human: raise MachineExecutionError('Step worker type is not machine') if task.status == Task.Status.COMPLETE: raise MachineExecutionError('Task assignment already completed') # Machine tasks are only assigned to one worker/machine, # so they should only have one task assignment, # and should never be submitted for review. with transaction.atomic(): # Uniqueness constraint on assignnment_counter and task prevents # concurrent creation of more than one assignment task_assignment, created = TaskAssignment.objects.get_or_create( assignment_counter=0, task=task, defaults={ 'status': TaskAssignment.Status.PROCESSING, 'in_progress_task_data': {}}) if created: task.status = Task.Status.PROCESSING task.save() Iteration.objects.create( assignment=task_assignment, start_datetime=task_assignment.start_datetime) else: # Task assignment already exists if task_assignment.status == TaskAssignment.Status.FAILED: # Pick up failed task for reprocessing task_assignment.status = TaskAssignment.Status.PROCESSING task_assignment.save() else: # Task assignment already processing raise MachineExecutionError( 'Task already picked up by another machine') prerequisites = previously_completed_task_data(task) function = locate(step.execution_function['path']) kwargs = step.execution_function.get('kwargs', {}) try: project_data = project.project_data project_data['project_id'] = project_id task_data = function(project_data, prerequisites, **kwargs) except: task_assignment.status = TaskAssignment.Status.FAILED logger.exception('Machine task has failed') task_assignment.save() return task_assignment.status = TaskAssignment.Status.SUBMITTED task_assignment.in_progress_task_data = task_data task_assignment.save() if task.project.status == Project.Status.ABORTED: # If a long-running task's project was aborted while running, we ensure # the aborted state on the task. task.status = Task.Status.ABORTED task.save() else: task.status = Task.Status.COMPLETE task.save() iteration = get_latest_iteration(task_assignment) iteration.status = Iteration.Status.REQUESTED_REVIEW iteration.submitted_data = task_data iteration.end_datetime = timezone.now() iteration.save() create_subsequent_tasks(project)
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; verify we use the reviewer assignment policy mock_preassign_workers = MagicMock(return_value=initial_task) patch_path = 'orchestra.utils.task_lifecycle._preassign_workers' with patch(patch_path, new=mock_preassign_workers): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) mock_preassign_workers.assert_called_once_with( initial_task, AssignmentPolicyType.REVIEWER) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[4]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue( related_task.is_worker_assigned(self.workers[4]))
def test_preassign_workers(self): project = self.projects['assignment_policy'] # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 0 not certified for related tasks, so should not have been # auto-assigned self.assertEquals(related_task.assignments.count(), 0) self.assertEquals(related_task.status, Task.Status.AWAITING_PROCESSING) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 0 initial_task = assign_task(self.workers[0].id, project.tasks.first().id) # Submit task; verify we use the reviewer assignment policy mock_preassign_workers = MagicMock(return_value=initial_task) patch_path = 'orchestra.utils.task_lifecycle._preassign_workers' with patch(patch_path, new=mock_preassign_workers): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[0]) mock_preassign_workers.assert_called_once_with( initial_task, AssignmentPolicyType.REVIEWER) # Reset project project.tasks.all().delete() # Create first task in test project create_subsequent_tasks(project) self.assertEquals(project.tasks.count(), 1) # Assign initial task to worker 4 initial_task = assign_task(self.workers[4].id, project.tasks.first().id) # Submit task; next task should be created with patch('orchestra.utils.task_lifecycle._is_review_needed', return_value=False): initial_task = submit_task(initial_task.id, {}, Iteration.Status.REQUESTED_REVIEW, self.workers[4]) self.assertEquals(project.tasks.count(), 2) related_task = project.tasks.exclude(id=initial_task.id).first() # Worker 4 is certified for related task and should have been assigned self.assertEquals(related_task.assignments.count(), 1) self.assertEquals(related_task.status, Task.Status.PROCESSING) self.assertTrue(related_task.is_worker_assigned(self.workers[4])) # Reset project project.tasks.all().delete()