Example #1
0
    def test_successful_with_partial_recipe(self):
        """Tests calling QueueManager.handle_job_completion() successfully with a job in a recipe."""

        # Queue the recipe
        recipe_id = Queue.objects.queue_new_recipe(self.recipe_type, self.data, self.event)

        # Fake out completing Job 1
        job_1 = RecipeJob.objects.select_related('job').get(recipe_id=recipe_id, job_name='Job 1').job
        job_exe_1 = JobExecution.objects.get(job_id=job_1.id)
        output_file_1 = product_test_utils.create_product(job_exe=job_exe_1, workspace=self.workspace)
        output_file_2 = product_test_utils.create_product(job_exe=job_exe_1, workspace=self.workspace)

        results = JobResults()
        results.add_file_list_parameter('Test Output 1', [output_file_1.id, output_file_2.id])
        JobExecution.objects.post_steps_results(job_exe_1.id, results, ResultsManifest())

        Job.objects.filter(pk=job_1.id).update(status='RUNNING')
        JobExecution.objects.filter(pk=job_exe_1.id).update(status='RUNNING')

        # Call method to test
        Queue.objects.handle_job_completion(job_exe_1.id, now())

        # Make sure processor was called
        self.assertTrue(self.mock_processor.process_completed.called)

        # Make sure Job 2 in the recipe is successfully queued
        recipe_job_2 = RecipeJob.objects.select_related('job', 'recipe').get(recipe_id=recipe_id, job_name='Job 2')
        self.assertEqual(recipe_job_2.job.status, 'QUEUED')
        self.assertIsNone(recipe_job_2.recipe.completed)
Example #2
0
    def test_successful_with_full_recipe(self):
        """Tests calling QueueManager.handle_job_completion() successfully with all jobs in a recipe."""

        # Queue the recipe
        handler = Queue.objects.queue_new_recipe(self.recipe_type, self.data,
                                                 self.event)

        # Fake out completing Job 1
        job_1 = RecipeJob.objects.select_related('job').get(
            recipe_id=handler.recipe.id, job_name='Job 1').job
        job_exe_1 = job_test_utils.create_job_exe(job=job_1, status='RUNNING')
        output_file_1 = product_test_utils.create_product(
            job_exe=job_exe_1, workspace=self.workspace)
        output_file_2 = product_test_utils.create_product(
            job_exe=job_exe_1, workspace=self.workspace)

        results = JobResults()
        results.add_file_list_parameter('Test Output 1',
                                        [output_file_1.id, output_file_2.id])
        job_exe_output_1 = JobExecutionOutput()
        job_exe_output_1.job_exe_id = job_exe_1.id
        job_exe_output_1.job_id = job_exe_1.job_id
        job_exe_output_1.job_type_id = job_exe_1.job_type_id
        job_exe_output_1.exe_num = job_exe_1.exe_num
        job_exe_output_1.output = results.get_dict()
        job_exe_output_1.save()

        Job.objects.filter(pk=job_1.id).update(status='RUNNING')

        Queue.objects.handle_job_completion(job_1.id, job_1.num_exes, now())

        # Fake out completing Job 2
        job_2 = RecipeJob.objects.select_related('job').get(
            recipe_id=handler.recipe.id, job_name='Job 2').job
        job_exe_2 = job_test_utils.create_job_exe(job=job_2, status='RUNNING')
        output_file_1 = product_test_utils.create_product(
            job_exe=job_exe_2, workspace=self.workspace)
        output_file_2 = product_test_utils.create_product(
            job_exe=job_exe_2, workspace=self.workspace)

        results = JobResults()
        results.add_file_list_parameter('Test Output 2',
                                        [output_file_1.id, output_file_2.id])
        job_exe_output_2 = JobExecutionOutput()
        job_exe_output_2.job_exe_id = job_exe_2.id
        job_exe_output_2.job_id = job_exe_2.job_id
        job_exe_output_2.job_type_id = job_exe_2.job_type_id
        job_exe_output_2.exe_num = job_exe_2.exe_num
        job_exe_output_2.output = results.get_dict()
        job_exe_output_2.save()

        Job.objects.filter(pk=job_2.id).update(status='RUNNING')

        # Call method to test
        Queue.objects.handle_job_completion(job_2.id, job_2.num_exes, now())

        # Make sure final recipe attributes are updated
        recipe = Recipe.objects.get(pk=handler.recipe.id)
        self.assertIsNotNone(recipe.completed)
Example #3
0
    def test_successful_file_list(self):
        '''Tests calling JobResults.add_output_to_data() successfully with a file list parameter'''

        output_name = u'foo'
        file_ids = [1, 2, 3, 4]
        input_name = u'bar'

        results = JobResults()
        results.add_file_list_parameter(output_name, file_ids)

        job_data = MagicMock()
        results.add_output_to_data(output_name, job_data, input_name)
        job_data.add_file_list_input.assert_called_with(input_name, file_ids)
Example #4
0
    def test_successful_file_list(self):
        """Tests calling JobResults.add_output_to_data() successfully with a file list parameter"""

        output_name = 'foo'
        file_ids = [1, 2, 3, 4]
        input_name = 'bar'

        results = JobResults()
        results.add_file_list_parameter(output_name, file_ids)

        job_data = MagicMock()
        results.add_output_to_data(output_name, job_data, input_name)
        job_data.add_file_list_input.assert_called_with(input_name, file_ids)
Example #5
0
    def test_successful_file(self):
        '''Tests calling JobResults.add_output_to_data() successfully with a file parameter'''

        output_name = u'foo'
        file_id = 1337
        input_name = u'bar'

        results = JobResults()
        results.add_file_parameter(output_name, file_id)

        job_data = MagicMock()
        results.add_output_to_data(output_name, job_data, input_name)
        job_data.add_file_input.assert_called_with(input_name, file_id)
Example #6
0
    def test_successful_with_full_recipe(self):
        """Tests calling QueueManager.handle_job_completion() successfully with all jobs in a recipe."""

        # Queue the recipe
        recipe_id = Queue.objects.queue_new_recipe(self.recipe_type, self.data, self.event)

        # Fake out completing Job 1
        job_1 = RecipeJob.objects.select_related("job").get(recipe_id=recipe_id, job_name="Job 1").job
        job_exe_1 = JobExecution.objects.get(job_id=job_1.id)
        output_file_1 = product_test_utils.create_product(job_exe=job_exe_1, workspace=self.workspace)
        output_file_2 = product_test_utils.create_product(job_exe=job_exe_1, workspace=self.workspace)

        results = JobResults()
        results.add_file_list_parameter("Test Output 1", [output_file_1.id, output_file_2.id])
        JobExecution.objects.post_steps_results(job_exe_1.id, results, ResultsManifest())

        Job.objects.filter(pk=job_1.id).update(status="RUNNING")
        JobExecution.objects.filter(pk=job_exe_1.id).update(status="RUNNING")

        Queue.objects.handle_job_completion(job_exe_1.id, now())

        # Fake out completing Job 2
        job_2 = RecipeJob.objects.select_related("job").get(recipe_id=recipe_id, job_name="Job 2").job
        job_exe_2 = JobExecution.objects.get(job_id=job_2.id)
        output_file_1 = product_test_utils.create_product(job_exe=job_exe_2, workspace=self.workspace)
        output_file_2 = product_test_utils.create_product(job_exe=job_exe_2, workspace=self.workspace)

        results = JobResults()
        results.add_file_list_parameter("Test Output 2", [output_file_1.id, output_file_2.id])
        JobExecution.objects.post_steps_results(job_exe_2.id, results, ResultsManifest())

        Job.objects.filter(pk=job_2.id).update(status="RUNNING")
        JobExecution.objects.filter(pk=job_exe_2.id).update(status="RUNNING")

        # Call method to test
        Queue.objects.handle_job_completion(job_exe_2.id, now())

        # Make sure processor was called
        self.assertEqual(self.mock_processor.process_completed.call_count, 2)

        # Make sure final recipe attributes are updated
        recipe = Recipe.objects.get(pk=recipe_id)
        self.assertIsNotNone(recipe.completed)
Example #7
0
    def test_execute(self):
        """Tests calling CompletedJobs.execute() successfully"""

        from recipe.test import utils as recipe_test_utils
        recipe_1 = recipe_test_utils.create_recipe()

        job_1 = job_test_utils.create_job(num_exes=1, status='QUEUED')
        job_test_utils.create_job_exe(job=job_1)
        job_2 = job_test_utils.create_job(num_exes=1,
                                          status='RUNNING',
                                          recipe=recipe_1)
        job_test_utils.create_job_exe(job=job_2, output=JobResults())
        job_3 = job_test_utils.create_job(num_exes=0, status='PENDING')
        job_ids = [job_1.id, job_2.id, job_3.id]

        recipe_test_utils.create_recipe_job(recipe=recipe_1, job=job_2)

        when_ended = now()

        # Add jobs to message
        message = CompletedJobs()
        message.ended = when_ended
        if message.can_fit_more():
            message.add_completed_job(CompletedJob(job_1.id, job_1.num_exes))
        if message.can_fit_more():
            message.add_completed_job(CompletedJob(job_2.id, job_2.num_exes))
        if message.can_fit_more():
            message.add_completed_job(CompletedJob(job_3.id, job_3.num_exes))

        # Execute message
        result = message.execute()
        self.assertTrue(result)

        from recipe.diff.forced_nodes import ForcedNodes
        from recipe.diff.json.forced_nodes_v6 import convert_forced_nodes_to_v6
        forced_nodes = ForcedNodes()
        forced_nodes.set_all_nodes()
        forced_nodes_dict = convert_forced_nodes_to_v6(forced_nodes).get_dict()

        jobs = Job.objects.filter(id__in=job_ids).order_by('id')
        self.assertEqual(len(message.new_messages), 3)
        update_recipe_metrics_msg = None
        update_recipe_msg = None
        publish_job_msg = None
        for msg in message.new_messages:
            if msg.type == 'update_recipe':
                update_recipe_msg = msg
            elif msg.type == 'publish_job':
                publish_job_msg = msg
            elif msg.type == 'update_recipe_metrics':
                update_recipe_metrics_msg = msg
        self.assertIsNotNone(update_recipe_msg)
        self.assertIsNotNone(publish_job_msg)
        self.assertIsNotNone(update_recipe_metrics_msg)
        self.assertEqual(publish_job_msg.job_id, job_2.id)

        # Job 1 should be completed
        self.assertEqual(jobs[0].status, 'COMPLETED')
        self.assertEqual(jobs[0].num_exes, 1)
        self.assertEqual(jobs[0].ended, when_ended)
        # Job 2 should be completed and has output, so should be in update_recipe message
        self.assertEqual(jobs[1].status, 'COMPLETED')
        self.assertEqual(jobs[1].num_exes, 1)
        self.assertEqual(jobs[1].ended, when_ended)
        self.assertEqual(update_recipe_msg.root_recipe_id, recipe_1.id)
        self.assertDictEqual(
            convert_forced_nodes_to_v6(
                update_recipe_msg.forced_nodes).get_dict(), forced_nodes_dict)
        # Job 3 should ignore update
        self.assertEqual(jobs[2].status, 'PENDING')
        self.assertEqual(jobs[2].num_exes, 0)

        # Test executing message again
        new_ended = when_ended + datetime.timedelta(minutes=5)
        message_json_dict = message.to_json()
        message = CompletedJobs.from_json(message_json_dict)
        message.ended = new_ended
        result = message.execute()
        self.assertTrue(result)

        # Should have the same messages as before
        jobs = Job.objects.filter(id__in=job_ids).order_by('id')
        self.assertEqual(len(message.new_messages), 3)
        update_recipe_metrics_msg = None
        update_recipe_msg = None
        publish_job_msg = None
        for msg in message.new_messages:
            if msg.type == 'update_recipe':
                update_recipe_msg = msg
            elif msg.type == 'publish_job':
                publish_job_msg = msg
            elif msg.type == 'update_recipe_metrics':
                update_recipe_metrics_msg = msg
        self.assertIsNotNone(update_recipe_msg)
        self.assertIsNotNone(publish_job_msg)
        self.assertIsNotNone(update_recipe_metrics_msg)
        self.assertEqual(publish_job_msg.job_id, job_2.id)

        # Job 1 should be completed
        self.assertEqual(jobs[0].status, 'COMPLETED')
        self.assertEqual(jobs[0].num_exes, 1)
        self.assertEqual(jobs[0].ended, when_ended)
        # Job 2 should be completed and has output, so should be in update_recipe message
        self.assertEqual(jobs[1].status, 'COMPLETED')
        self.assertEqual(jobs[1].num_exes, 1)
        self.assertEqual(jobs[1].ended, when_ended)
        self.assertEqual(update_recipe_msg.root_recipe_id, recipe_1.id)
        self.assertDictEqual(
            convert_forced_nodes_to_v6(
                update_recipe_msg.forced_nodes).get_dict(), forced_nodes_dict)
        # Job 3 should ignore update
        self.assertEqual(jobs[2].status, 'PENDING')
        self.assertEqual(jobs[2].num_exes, 0)
Example #8
0
    def store_output_data_files(self, data_files, job_exe):
        """Stores the given data output files

        :param data_files: Dict with each file parameter name mapping to a list of ProductFileMetadata classes
        :type data_files: {string: [`ProductFileMetadata`]}
        :param job_exe: The job execution model (with related job and job_type fields) that is storing the output data
            files
        :type job_exe: :class:`job.models.JobExecution`
        :returns: The job results
        :rtype: :class:`job.configuration.results.job_results.JobResults`
        """

        # Organize the data files
        workspace_files = {
        }  # Workspace ID -> [(absolute local file path, media type)]
        params_by_file_path = {
        }  # Absolute local file path -> output parameter name
        output_workspaces = JobData.create_output_workspace_dict(
            data_files.keys(), self, job_exe)
        for name in data_files:
            workspace_id = output_workspaces[name]
            if workspace_id in workspace_files:
                workspace_file_list = workspace_files[workspace_id]
            else:
                workspace_file_list = []
                workspace_files[workspace_id] = workspace_file_list
            data_file_entry = data_files[name]
            if isinstance(data_file_entry, list):
                for file_entry in data_file_entry:
                    file_path = os.path.normpath(file_entry.local_path)
                    if not os.path.isfile(file_path):
                        raise Exception('%s is not a valid file' % file_path)
                    params_by_file_path[file_path] = name
                    workspace_file_list.append(file_entry)
            else:
                file_path = os.path.normpath(data_file_entry.local_path)
                if not os.path.isfile(file_path):
                    raise Exception('%s is not a valid file' % file_path)
                params_by_file_path[file_path] = name
                data_file_entry.local_path = file_path
                workspace_file_list.append(data_file_entry)

        data_file_store = DATA_FILE_STORE['DATA_FILE_STORE']
        if not data_file_store:
            raise Exception('No data file store found')
        stored_files = data_file_store.store_files(workspace_files,
                                                   self.get_input_file_ids(),
                                                   job_exe)

        # Organize results
        param_file_ids = {}  # Output parameter name -> file ID or [file IDs]
        for file_path in stored_files:
            file_id = stored_files[file_path]
            name = params_by_file_path[file_path]
            if isinstance(data_files[name], list):
                if name in param_file_ids:
                    file_id_list = param_file_ids[name]
                else:
                    file_id_list = []
                    param_file_ids[name] = file_id_list
                file_id_list.append(file_id)
            else:
                param_file_ids[name] = file_id

        # Create job results
        results = JobResults()
        for name in param_file_ids:
            param_entry = param_file_ids[name]
            if isinstance(param_entry, list):
                results.add_file_list_parameter(name, param_entry)
            else:
                results.add_file_parameter(name, param_entry)
        return results
Example #9
0
    def store_output_data_files(self, data_files, job_exe):
        """Stores the given data output files

        :param data_files: Dict with each file parameter name mapping to a tuple of absolute local file path and media
            type (media type is optionally None) for a single file parameter and a list of tuples for a multiple file
            parameter
        :type data_files: {string: tuple(string, string)} or [tuple(string, string)]
        :param job_exe: The job execution model (with related job and job_type fields) that is storing the output data
            files
        :type job_exe: :class:`job.models.JobExecution`
        :returns: The job results
        :rtype: :class:`job.configuration.results.job_results.JobResults`
        """

        # Organize the data files
        workspace_files = {
        }  # Workspace ID -> [(absolute local file path, media type)]
        params_by_file_path = {
        }  # Absolute local file path -> output parameter name
        for name in data_files:
            file_output = self.data_outputs_by_name[name]
            workspace_id = file_output['workspace_id']
            if workspace_id in workspace_files:
                workspace_file_list = workspace_files[workspace_id]
            else:
                workspace_file_list = []
                workspace_files[workspace_id] = workspace_file_list
            data_file_entry = data_files[name]
            if isinstance(data_file_entry, list):
                for file_tuple in data_file_entry:
                    file_path = os.path.normpath(file_tuple[0])
                    if not os.path.isfile(file_path):
                        raise Exception('%s is not a valid file' % file_path)
                    params_by_file_path[file_path] = name
                    # Adjust file path to be relative to upload_dir
                    if len(file_tuple) == 2:
                        new_tuple = (file_path, file_tuple[1], name)
                    else:
                        new_tuple = (file_path, file_tuple[1], name,
                                     file_tuple[2])
                    workspace_file_list.append(new_tuple)
            else:
                file_path = os.path.normpath(data_file_entry[0])
                if not os.path.isfile(file_path):
                    raise Exception('%s is not a valid file' % file_path)
                params_by_file_path[file_path] = name
                # Adjust file path to be relative to upload_dir
                if len(data_file_entry) == 2:
                    new_tuple = (file_path, data_file_entry[1], name)
                else:
                    new_tuple = (file_path, data_file_entry[1], name,
                                 data_file_entry[2])
                workspace_file_list.append(new_tuple)

        data_file_store = DATA_FILE_STORE['DATA_FILE_STORE']
        if not data_file_store:
            raise Exception('No data file store found')
        stored_files = data_file_store.store_files(workspace_files,
                                                   self.get_input_file_ids(),
                                                   job_exe)

        # Organize results
        param_file_ids = {}  # Output parameter name -> file ID or [file IDs]
        for file_path in stored_files:
            file_id = stored_files[file_path]
            name = params_by_file_path[file_path]
            if isinstance(data_files[name], list):
                if name in param_file_ids:
                    file_id_list = param_file_ids[name]
                else:
                    file_id_list = []
                    param_file_ids[name] = file_id_list
                file_id_list.append(file_id)
            else:
                param_file_ids[name] = file_id

        # Create job results
        results = JobResults()
        for name in param_file_ids:
            param_entry = param_file_ids[name]
            if isinstance(param_entry, list):
                results.add_file_list_parameter(name, param_entry)
            else:
                results.add_file_parameter(name, param_entry)
        return results
Example #10
0
def create_job_exe(job_type=None,
                   job=None,
                   exe_num=None,
                   node=None,
                   timeout=None,
                   input_file_size=10.0,
                   queued=None,
                   started=None,
                   status='RUNNING',
                   error=None,
                   ended=None,
                   output=None,
                   task_results=None):
    """Creates a job_exe model for unit testing, may also create job_exe_end and job_exe_output models depending on
    status

    :returns: The job_exe model
    :rtype: :class:`job.execution.job_exe.RunningJobExecution`
    """

    when = timezone.now()
    if not job:
        job = create_job(job_type=job_type,
                         status=status,
                         input_file_size=input_file_size)
    job_type = job.job_type

    job_exe = JobExecution()
    job_exe.job = job
    job_exe.job_type = job_type
    if not exe_num:
        exe_num = job.num_exes
    job_exe.exe_num = exe_num
    job_exe.set_cluster_id('1234', job.id, job_exe.exe_num)
    if not node:
        node = node_utils.create_node()
    job_exe.node = node
    if not timeout:
        timeout = job.timeout
    job_exe.timeout = timeout
    job_exe.input_file_size = input_file_size
    job_exe.resources = job.get_resources().get_json().get_dict()
    job_exe.configuration = ExecutionConfiguration().get_dict()
    if not queued:
        queued = when
    job_exe.queued = queued
    if not started:
        started = when + datetime.timedelta(seconds=1)
    job_exe.started = started
    job_exe.save()

    if status in ['COMPLETED', 'FAILED', 'CANCELED']:
        job_exe_end = JobExecutionEnd()
        job_exe_end.job_exe_id = job_exe.id
        job_exe_end.job = job_exe.job
        job_exe_end.job_type = job_exe.job_type
        job_exe_end.exe_num = job_exe.exe_num
        if not task_results:
            task_results = TaskResults()
        job_exe_end.task_results = task_results.get_dict()
        job_exe_end.status = status
        if status == 'FAILED' and not error:
            error = error_test_utils.create_error()
        job_exe_end.error = error
        job_exe_end.node = node
        job_exe_end.queued = queued
        job_exe_end.started = started
        job_exe_end.seed_started = task_results.get_task_started('main')
        job_exe_end.seed_ended = task_results.get_task_ended('main')
        if not ended:
            ended = started + datetime.timedelta(seconds=1)
        job_exe_end.ended = ended
        job_exe_end.save()

    if status == 'COMPLETED' or output:
        job_exe_output = JobExecutionOutput()
        job_exe_output.job_exe_id = job_exe.id
        job_exe_output.job = job_exe.job
        job_exe_output.job_type = job_exe.job_type
        job_exe_output.exe_num = job_exe.exe_num
        if not output:
            output = JobResults()
        job_exe_output.output = output.get_dict()
        job_exe_output.save()

    return job_exe
Example #11
0
    def test_successful_supersede(self):
        """Tests calling QueueManager.queue_new_recipe() successfully when superseding a recipe."""

        # Queue initial recipe and complete its first job
        node = node_test_utils.create_node()
        recipe_id = Queue.objects.queue_new_recipe(self.recipe_type, self.data, self.event)
        recipe = Recipe.objects.get(id=recipe_id)
        recipe_job_1 = RecipeJob.objects.select_related('job__job_exe').get(recipe_id=recipe_id, job_name='Job 1')
        job_exe_1 = JobExecution.objects.get(job_id=recipe_job_1.job_id)
        queued_job_exe = QueuedJobExecution(Queue.objects.get(job_exe_id=job_exe_1.id))
        queued_job_exe.accepted(node, JobResources(cpus=10, mem=1000, disk_in=1000, disk_out=1000, disk_total=2000))
        Queue.objects.schedule_job_executions('123', [queued_job_exe], {})
        results = JobResults()
        results.add_file_list_parameter('Test Output 1', [product_test_utils.create_product().file_id])
        JobExecution.objects.filter(id=job_exe_1.id).update(results=results.get_dict())
        Queue.objects.handle_job_completion(job_exe_1.id, now())

        # Create a new recipe type that has a new version of job 2 (job 1 is identical)
        new_job_type_2 = job_test_utils.create_job_type(name=self.job_type_2.name, version='New Version',
                                                        interface=self.job_type_2.interface)
        new_definition = {
            'version': '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'jobs': [{
                'name': 'New Job 1',
                'job_type': {
                    'name': self.job_type_1.name,
                    'version': self.job_type_1.version,
                },
                'recipe_inputs': [{
                    'recipe_input': 'Recipe Input',
                    'job_input': 'Test Input 1',
                }]
            }, {
                'name': 'New Job 2',
                'job_type': {
                    'name': new_job_type_2.name,
                    'version': new_job_type_2.version,
                },
                'dependencies': [{
                    'name': 'New Job 1',
                    'connections': [{
                        'output': 'Test Output 1',
                        'input': 'Test Input 2',
                    }]
                }]
            }]
        }
        new_recipe_type = recipe_test_utils.create_recipe_type(name=self.recipe_type.name, definition=new_definition)
        event = trigger_test_utils.create_trigger_event()
        recipe_job_1 = RecipeJob.objects.select_related('job').get(recipe_id=recipe_id, job_name='Job 1')
        recipe_job_2 = RecipeJob.objects.select_related('job').get(recipe_id=recipe_id, job_name='Job 2')
        superseded_jobs = {'Job 1': recipe_job_1.job, 'Job 2': recipe_job_2.job}
        graph_a = self.recipe_type.get_recipe_definition().get_graph()
        graph_b = new_recipe_type.get_recipe_definition().get_graph()
        delta = RecipeGraphDelta(graph_a, graph_b)

        # Queue new recipe that supersedes the old recipe
        new_recipe_id = Queue.objects.queue_new_recipe(new_recipe_type, None, event, recipe, delta, superseded_jobs)

        # Ensure old recipe is superseded
        recipe = Recipe.objects.get(id=recipe_id)
        self.assertTrue(recipe.is_superseded)

        # Ensure new recipe supersedes old recipe
        new_recipe = Recipe.objects.get(id=new_recipe_id)
        self.assertEqual(new_recipe.superseded_recipe_id, recipe_id)

        # Ensure that job 1 is already completed (it was copied from original recipe) and that job 2 is queued
        new_recipe_job_1 = RecipeJob.objects.select_related('job').get(recipe_id=new_recipe_id, job_name='New Job 1')
        new_recipe_job_2 = RecipeJob.objects.select_related('job').get(recipe_id=new_recipe_id, job_name='New Job 2')
        self.assertEqual(new_recipe_job_1.job.status, 'COMPLETED')
        self.assertFalse(new_recipe_job_1.is_original)
        self.assertEqual(new_recipe_job_2.job.status, 'QUEUED')
        self.assertTrue(new_recipe_job_2.is_original)

        # Complete both the old and new job 2 and check that only the new recipe completes
        job_exe_2 = JobExecution.objects.get(job_id=recipe_job_2.job_id)
        queued_job_exe_2 = QueuedJobExecution(Queue.objects.get(job_exe_id=job_exe_2.id))
        queued_job_exe_2.accepted(node, JobResources(cpus=10, mem=1000, disk_in=1000, disk_out=1000, disk_total=2000))
        Queue.objects.schedule_job_executions('123', [queued_job_exe_2], {})
        Queue.objects.handle_job_completion(job_exe_2.id, now())
        new_job_exe_2 = JobExecution.objects.get(job_id=new_recipe_job_2.job_id)
        new_queued_job_exe_2 = QueuedJobExecution(Queue.objects.get(job_exe_id=new_job_exe_2.id))
        new_queued_job_exe_2.accepted(node, JobResources(cpus=10, mem=1000, disk_in=1000, disk_out=1000,
                                                         disk_total=2000))
        Queue.objects.schedule_job_executions('123', [new_queued_job_exe_2], {})
        Queue.objects.handle_job_completion(new_job_exe_2.id, now())
        recipe = Recipe.objects.get(id=recipe.id)
        new_recipe = Recipe.objects.get(id=new_recipe.id)
        self.assertIsNone(recipe.completed)
        self.assertIsNotNone(new_recipe.completed)
Example #12
0
    def store_output_data_files(self, data_files, job_exe):
        """Stores the given data output files

        :param data_files: Dict with each file parameter name mapping to a tuple of absolute local file path and media
            type (media type is optionally None) for a single file parameter and a list of tuples for a multiple file
            parameter
        :type data_files: dict of str -> tuple(str, str) or list of tuple(str, str)
        :param job_exe: The job execution model (with related job and job_type fields) that is storing the output data
            files
        :type job_exe: :class:`job.models.JobExecution`
        :returns: The job results
        :rtype: :class:`job.configuration.results.job_results.JobResults`
        """

        # Organize the data files
        workspace_files = {}  # Workspace ID -> list of (absolute local file path, media type)
        params_by_file_path = {}  # Absolute local file path -> output parameter name
        for name in data_files:
            file_output = self.data_outputs_by_name[name]
            workspace_id = file_output['workspace_id']
            if workspace_id in workspace_files:
                workspace_file_list = workspace_files[workspace_id]
            else:
                workspace_file_list = []
                workspace_files[workspace_id] = workspace_file_list
            data_file_entry = data_files[name]
            if isinstance(data_file_entry, list):
                for file_tuple in data_file_entry:
                    file_path = os.path.normpath(file_tuple[0])
                    if not os.path.isfile(file_path):
                        raise Exception('%s is not a valid file' % file_path)
                    params_by_file_path[file_path] = name
                    # Adjust file path to be relative to upload_dir
                    if len(file_tuple) == 2:
                        new_tuple = (file_path, file_tuple[1])
                    else:
                        new_tuple = (file_path, file_tuple[1], file_tuple[2])
                    workspace_file_list.append(new_tuple)
            else:
                file_path = os.path.normpath(data_file_entry[0])
                if not os.path.isfile(file_path):
                    raise Exception('%s is not a valid file' % file_path)
                params_by_file_path[file_path] = name
                # Adjust file path to be relative to upload_dir
                if len(data_file_entry) == 2:
                    new_tuple = (file_path, data_file_entry[1])
                else:
                    new_tuple = (file_path, data_file_entry[1], data_file_entry[2])
                workspace_file_list.append(new_tuple)

        data_file_store = DATA_FILE_STORE['DATA_FILE_STORE']
        if not data_file_store:
            raise Exception('No data file store found')
        stored_files = data_file_store.store_files(workspace_files, self.get_input_file_ids(), job_exe)

        # Organize results
        param_file_ids = {}  # Output parameter name -> file ID or list of file IDs
        for file_path in stored_files:
            file_id = stored_files[file_path]
            name = params_by_file_path[file_path]
            if isinstance(data_files[name], list):
                if name in param_file_ids:
                    file_id_list = param_file_ids[name]
                else:
                    file_id_list = []
                    param_file_ids[name] = file_id_list
                file_id_list.append(file_id)
            else:
                param_file_ids[name] = file_id

        # Create job results
        results = JobResults()
        for name in param_file_ids:
            param_entry = param_file_ids[name]
            if isinstance(param_entry, list):
                results.add_file_list_parameter(name, param_entry)
            else:
                results.add_file_parameter(name, param_entry)
        return results
Example #13
0
    def test_successful_supersede_mixed(self):
        """Tests calling QueueManager.queue_new_recipe() successfully when superseding a recipe where the results of a
        Seed job get passed to the input of a legacy job
        """

        workspace = storage_test_utils.create_workspace()
        source_file = source_test_utils.create_source(workspace=workspace)
        event = trigger_test_utils.create_trigger_event()

        interface_1 = {
            'seedVersion': '1.0.0',
            'job': {
                'name': 'job-type-a',
                'jobVersion': '1.0.0',
                'packageVersion': '1.0.0',
                'title': 'Job Type 1',
                'description': 'This is a description',
                'maintainer': {
                    'name': 'John Doe',
                    'email': '*****@*****.**'
                },
                'timeout': 10,
                'interface': {
                    'command': '',
                    'inputs': {
                        'files': [{
                            'name': 'test-input-a'
                        }]
                    },
                    'outputs': {
                        'files': [{
                            'name': 'test-output-a',
                            'pattern': '*.png'
                        }]
                    }
                }
            }
        }
        job_type_1 = job_test_utils.create_seed_job_type(manifest=interface_1)

        interface_2 = {
            'version':
            '1.0',
            'command':
            'test_command',
            'command_arguments':
            'test_arg',
            'input_data': [{
                'name': 'Test Input 2',
                'type': 'file',
                'media_types': ['image/png', 'image/tiff'],
            }],
            'output_data': [{
                'name': 'Test Output 2',
                'type': 'file',
            }]
        }
        job_type_2 = job_test_utils.create_job_type(interface=interface_2)

        definition = {
            'version':
            '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'jobs': [{
                'name':
                'Job 1',
                'job_type': {
                    'name': job_type_1.name,
                    'version': job_type_1.version,
                },
                'recipe_inputs': [{
                    'recipe_input': 'Recipe Input',
                    'job_input': 'test-input-a',
                }]
            }, {
                'name':
                'Job 2',
                'job_type': {
                    'name': job_type_2.name,
                    'version': job_type_2.version,
                },
                'dependencies': [{
                    'name':
                    'Job 1',
                    'connections': [{
                        'output': 'test-output-a',
                        'input': 'Test Input 2',
                    }]
                }]
            }]
        }

        recipe_definition = RecipeDefinition(definition)
        recipe_definition.validate_job_interfaces()

        recipe_type = recipe_test_utils.create_recipe_type(
            definition=definition)

        data = {
            'version': '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'file_id': source_file.id,
            }],
            'workspace_id': workspace.id,
        }
        data = LegacyRecipeData(data)

        # Queue initial recipe and complete its first job
        handler = Queue.objects.queue_new_recipe(recipe_type, data, event)
        recipe = Recipe.objects.get(id=handler.recipe.id)
        recipe_job_1 = RecipeNode.objects.select_related('job')
        recipe_job_1 = recipe_job_1.get(recipe_id=handler.recipe.id,
                                        node_name='Job 1')
        Job.objects.update_jobs_to_running([recipe_job_1.job], now())
        results = JobResults()
        results.add_file_parameter('test-output-a',
                                   product_test_utils.create_product().id)
        job_test_utils.create_job_exe(job=recipe_job_1.job,
                                      status='COMPLETED',
                                      output=results)
        Job.objects.update_jobs_to_completed([recipe_job_1.job], now())
        Job.objects.process_job_output([recipe_job_1.job_id], now())

        # Create a new recipe type that has a new version of job 2 (job 1 is identical)
        new_job_type_2 = job_test_utils.create_job_type(
            name=job_type_2.name,
            version='New Version',
            interface=job_type_2.manifest)
        new_definition = {
            'version':
            '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'jobs': [{
                'name':
                'New Job 1',
                'job_type': {
                    'name': job_type_1.name,
                    'version': job_type_1.version,
                },
                'recipe_inputs': [{
                    'recipe_input': 'Recipe Input',
                    'job_input': 'test-input-a',
                }]
            }, {
                'name':
                'New Job 2',
                'job_type': {
                    'name': new_job_type_2.name,
                    'version': new_job_type_2.version,
                },
                'dependencies': [{
                    'name':
                    'New Job 1',
                    'connections': [{
                        'output': 'test-output-a',
                        'input': 'Test Input 2',
                    }]
                }]
            }]
        }
        new_recipe_type = recipe_test_utils.create_recipe_type(
            name=recipe_type.name, definition=new_definition)
        event = trigger_test_utils.create_trigger_event()
        recipe_job_1 = RecipeNode.objects.select_related('job').get(
            recipe_id=handler.recipe.id, node_name='Job 1')
        recipe_job_2 = RecipeNode.objects.select_related('job').get(
            recipe_id=handler.recipe.id, node_name='Job 2')
        superseded_jobs = {
            'Job 1': recipe_job_1.job,
            'Job 2': recipe_job_2.job
        }
        graph_a = recipe_type.get_recipe_definition().get_graph()
        graph_b = new_recipe_type.get_recipe_definition().get_graph()
        delta = RecipeGraphDelta(graph_a, graph_b)

        # Queue new recipe that supersedes the old recipe
        new_handler = Queue.objects.queue_new_recipe(
            new_recipe_type,
            None,
            event,
            superseded_recipe=recipe,
            delta=delta,
            superseded_jobs=superseded_jobs)

        # Ensure old recipe is superseded
        recipe = Recipe.objects.get(id=handler.recipe.id)
        self.assertTrue(recipe.is_superseded)

        # Ensure new recipe supersedes old recipe
        new_recipe = Recipe.objects.get(id=new_handler.recipe.id)
        self.assertEqual(new_recipe.superseded_recipe_id, handler.recipe.id)

        # Ensure that job 1 is already completed (it was copied from original recipe) and that job 2 is queued
        new_recipe_job_1 = RecipeNode.objects.select_related('job').get(
            recipe_id=new_handler.recipe.id, node_name='New Job 1')
        new_recipe_job_2 = RecipeNode.objects.select_related('job').get(
            recipe_id=new_handler.recipe.id, node_name='New Job 2')
        self.assertEqual(new_recipe_job_1.job.status, 'COMPLETED')
        self.assertFalse(new_recipe_job_1.is_original)
        self.assertEqual(new_recipe_job_2.job.status, 'QUEUED')
        self.assertTrue(new_recipe_job_2.is_original)
Example #14
0
    def test_successful_supersede(self):
        """Tests calling QueueManager.queue_new_recipe() successfully when superseding a recipe."""

        # Queue initial recipe and complete its first job
        handler = Queue.objects.queue_new_recipe(self.recipe_type, self.data,
                                                 self.event)
        recipe = Recipe.objects.get(id=handler.recipe.id)
        recipe_job_1 = RecipeNode.objects.select_related('job')
        recipe_job_1 = recipe_job_1.get(recipe_id=handler.recipe.id,
                                        node_name='Job 1')
        Job.objects.update_jobs_to_running([recipe_job_1.job], now())
        results = JobResults()
        results.add_file_list_parameter(
            'Test Output 1', [product_test_utils.create_product().id])
        job_test_utils.create_job_exe(job=recipe_job_1.job,
                                      status='COMPLETED',
                                      output=results)
        Job.objects.update_jobs_to_completed([recipe_job_1.job], now())
        Job.objects.process_job_output([recipe_job_1.job_id], now())

        # Create a new recipe type that has a new version of job 2 (job 1 is identical)
        new_job_type_2 = job_test_utils.create_job_type(
            name=self.job_type_2.name,
            version='New Version',
            interface=self.job_type_2.manifest)
        new_definition = {
            'version':
            '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'jobs': [{
                'name':
                'New Job 1',
                'job_type': {
                    'name': self.job_type_1.name,
                    'version': self.job_type_1.version,
                },
                'recipe_inputs': [{
                    'recipe_input': 'Recipe Input',
                    'job_input': 'Test Input 1',
                }]
            }, {
                'name':
                'New Job 2',
                'job_type': {
                    'name': new_job_type_2.name,
                    'version': new_job_type_2.version,
                },
                'dependencies': [{
                    'name':
                    'New Job 1',
                    'connections': [{
                        'output': 'Test Output 1',
                        'input': 'Test Input 2',
                    }]
                }]
            }]
        }
        new_recipe_type = recipe_test_utils.create_recipe_type(
            name=self.recipe_type.name, definition=new_definition)
        event = trigger_test_utils.create_trigger_event()
        recipe_job_1 = RecipeNode.objects.select_related('job').get(
            recipe_id=handler.recipe.id, node_name='Job 1')
        recipe_job_2 = RecipeNode.objects.select_related('job').get(
            recipe_id=handler.recipe.id, node_name='Job 2')
        superseded_jobs = {
            'Job 1': recipe_job_1.job,
            'Job 2': recipe_job_2.job
        }
        graph_a = self.recipe_type.get_recipe_definition().get_graph()
        graph_b = new_recipe_type.get_recipe_definition().get_graph()
        delta = RecipeGraphDelta(graph_a, graph_b)

        # Queue new recipe that supersedes the old recipe
        new_handler = Queue.objects.queue_new_recipe(
            new_recipe_type,
            None,
            event,
            superseded_recipe=recipe,
            delta=delta,
            superseded_jobs=superseded_jobs)

        # Ensure old recipe is superseded
        recipe = Recipe.objects.get(id=handler.recipe.id)
        self.assertTrue(recipe.is_superseded)

        # Ensure new recipe supersedes old recipe
        new_recipe = Recipe.objects.get(id=new_handler.recipe.id)
        self.assertEqual(new_recipe.superseded_recipe_id, handler.recipe.id)

        # Ensure that job 1 is already completed (it was copied from original recipe) and that job 2 is queued
        new_recipe_job_1 = RecipeNode.objects.select_related('job').get(
            recipe_id=new_handler.recipe.id, node_name='New Job 1')
        new_recipe_job_2 = RecipeNode.objects.select_related('job').get(
            recipe_id=new_handler.recipe.id, node_name='New Job 2')
        self.assertEqual(new_recipe_job_1.job.status, 'COMPLETED')
        self.assertFalse(new_recipe_job_1.is_original)
        self.assertEqual(new_recipe_job_2.job.status, 'QUEUED')
        self.assertTrue(new_recipe_job_2.is_original)
Example #15
0
    def test_execute(self):
        """Tests calling UpdateRecipes.execute() successfully"""

        # Create recipes for testing the setting of jobs to BLOCKED/PENDING
        self.job_1_failed = job_test_utils.create_job(status='FAILED')
        self.job_1_pending = job_test_utils.create_job(status='PENDING')
        definition_1 = {
            'version': '1.0',
            'input_data': [],
            'jobs': [{
                'name': 'job_failed',
                'job_type': {
                    'name': self.job_1_failed.job_type.name,
                    'version': self.job_1_failed.job_type.version,
                },
            }, {
                'name': 'job_pending',
                'job_type': {
                    'name': self.job_1_pending.job_type.name,
                    'version': self.job_1_pending.job_type.version,
                },
                'dependencies': [{
                    'name': 'job_failed',
                }],
            }],
        }
        self.recipe_type_1 = recipe_test_utils.create_recipe_type(definition=definition_1)
        self.recipe_1 = recipe_test_utils.create_recipe(recipe_type=self.recipe_type_1)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_1, job_name='job_failed', job=self.job_1_failed)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_1, job_name='job_pending', job=self.job_1_pending)

        self.job_2_running = job_test_utils.create_job(status='RUNNING')
        self.job_2_blocked = job_test_utils.create_job(status='BLOCKED')
        definition_2 = {
            'version': '1.0',
            'input_data': [],
            'jobs': [{
                'name': 'job_running',
                'job_type': {
                    'name': self.job_2_running.job_type.name,
                    'version': self.job_2_running.job_type.version,
                },
            }, {
                'name': 'job_blocked',
                'job_type': {
                    'name': self.job_2_blocked.job_type.name,
                    'version': self.job_2_blocked.job_type.version,
                },
                'dependencies': [{
                    'name': 'job_running',
                }],
            }],
        }
        self.recipe_type_2 = recipe_test_utils.create_recipe_type(definition=definition_2)
        self.recipe_2 = recipe_test_utils.create_recipe(recipe_type=self.recipe_type_2)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_2, job_name='job_running', job=self.job_2_running)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_2, job_name='job_blocked', job=self.job_2_blocked)

        # Create recipe for testing the setting of input for a starting job in a recipe (no parents)
        input_name_1 = 'Test Input 1'
        output_name_1 = 'Test Output 1'
        interface_1 = {
            'version': '1.0',
            'command': 'my_cmd',
            'command_arguments': 'args',
            'input_data': [{
                'name': input_name_1,
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'output_data': [{
                'name': output_name_1,
                'type': 'files',
                'media_type': 'image/png',
            }],
        }
        job_type_3 = job_test_utils.create_job_type(interface=interface_1)
        job_3 = job_test_utils.create_job(job_type=job_type_3, status='PENDING', num_exes=0)

        input_name_2 = 'Test Input 2'
        output_name_2 = 'Test Output 2'
        interface_2 = {
            'version': '1.0',
            'command': 'my_cmd',
            'command_arguments': 'args',
            'input_data': [{
                'name': input_name_2,
                'type': 'files',
                'media_types': ['image/png', 'image/tiff'],
            }],
            'output_data': [{
                'name': output_name_2,
                'type': 'file',
            }],
        }
        job_type_4 = job_test_utils.create_job_type(interface=interface_2)
        job_4 = job_test_utils.create_job(job_type=job_type_4, status='PENDING', num_exes=0)
        workspace = storage_test_utils.create_workspace()
        file_1 = storage_test_utils.create_file(workspace=workspace, media_type='text/plain')

        definition = {
            'version': '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'jobs': [{
                'name': 'Job 1',
                'job_type': {
                    'name': job_type_3.name,
                    'version': job_type_3.version,
                },
                'recipe_inputs': [{
                    'recipe_input': 'Recipe Input',
                    'job_input': input_name_1,
                }]
            }, {
                'name': 'Job 2',
                'job_type': {
                    'name': job_type_4.name,
                    'version': job_type_4.version,
                },
                'dependencies': [{
                    'name': 'Job 1',
                    'connections': [{
                        'output': output_name_1,
                        'input': input_name_2,
                    }],
                }],
            }],
        }
        data = {
            'version': '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'file_id': file_1.id,
            }],
            'workspace_id': workspace.id,
        }
        self.recipe_type_3 = recipe_test_utils.create_recipe_type(definition=definition)
        self.recipe_3 = recipe_test_utils.create_recipe(recipe_type=self.recipe_type_3, input=data)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_3, job_name='Job 1', job=job_3)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_3, job_name='Job 2', job=job_4)

        # Create recipe for testing the setting of input for a child job
        job_5 = job_test_utils.create_job(job_type=job_type_3, status='COMPLETED')
        file_2 = storage_test_utils.create_file(workspace=workspace, media_type='text/plain')
        job_5_output_dict = {
            'version': '1.0',
            'output_data': [{
                'name': output_name_1,
                'file_ids': [file_2.id]
            }]
        }
        job_test_utils.create_job_exe(job=job_5, output=JobResults(job_5_output_dict))
        # Complete job 5 and set its output so that update recipe message can give go ahead for child job 6
        Job.objects.process_job_output([job_5.id], now())
        job_6 = job_test_utils.create_job(job_type=job_type_4, status='PENDING', num_exes=0)
        self.recipe_4 = recipe_test_utils.create_recipe(recipe_type=self.recipe_type_3, input=data)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_4, job_name='Job 1', job=job_5)
        recipe_test_utils.create_recipe_job(recipe=self.recipe_4, job_name='Job 2', job=job_6)

        # Add recipes to message
        message = UpdateRecipes()
        if message.can_fit_more():
            message.add_recipe(self.recipe_1.id)
        if message.can_fit_more():
            message.add_recipe(self.recipe_2.id)
        if message.can_fit_more():
            message.add_recipe(self.recipe_3.id)
        if message.can_fit_more():
            message.add_recipe(self.recipe_4.id)

        # Execute message
        result = message.execute()
        self.assertTrue(result)

        self.assertEqual(len(message.new_messages), 4)
        # Check message types
        blocked_jobs_msg = False
        pending_jobs_msg = False
        process_job_input_msg_job_3 = False
        process_job_input_msg_job_6 = False
        for new_msg in message.new_messages:
            if new_msg.type == 'blocked_jobs':
                blocked_jobs_msg = True
            elif new_msg.type == 'pending_jobs':
                pending_jobs_msg = True
            elif new_msg.type == 'process_job_input':
                if new_msg.job_id == job_3.id:
                    process_job_input_msg_job_3 = True
                elif new_msg.job_id == job_6.id:
                    process_job_input_msg_job_6 = True
        self.assertTrue(blocked_jobs_msg)
        self.assertTrue(pending_jobs_msg)
        self.assertTrue(process_job_input_msg_job_3)
        self.assertTrue(process_job_input_msg_job_6)
        # Make sure Job 3 has its input populated
        job = Job.objects.get(id=job_3.id)
        self.assertDictEqual(job.input, {
            'version': '1.0',
            'input_data': [{
                'name': input_name_1,
                'file_id': file_1.id,
            }],
            'output_data': [{
                'name': output_name_1,
                'workspace_id': workspace.id,
            }],
        })
        # Make sure Job 6 has its input populated
        job = Job.objects.get(id=job_6.id)
        self.assertDictEqual(job.input, {
            'version': '1.0',
            'input_data': [{
                'name': input_name_2,
                'file_ids': [file_2.id],
            }],
            'output_data': [{
                'name': output_name_2,
                'workspace_id': workspace.id,
            }],
        })

        # Test executing message again
        message_json_dict = message.to_json()
        message = UpdateRecipes.from_json(message_json_dict)
        result = message.execute()
        self.assertTrue(result)

        # Make sure the same three messages are returned
        self.assertEqual(len(message.new_messages), 4)
        # Check message types
        blocked_jobs_msg = False
        pending_jobs_msg = False
        process_job_input_msg_job_3 = False
        process_job_input_msg_job_6 = False
        for new_msg in message.new_messages:
            if new_msg.type == 'blocked_jobs':
                blocked_jobs_msg = True
            elif new_msg.type == 'pending_jobs':
                pending_jobs_msg = True
            elif new_msg.type == 'process_job_input':
                if new_msg.job_id == job_3.id:
                    process_job_input_msg_job_3 = True
                elif new_msg.job_id == job_6.id:
                    process_job_input_msg_job_6 = True
        self.assertTrue(blocked_jobs_msg)
        self.assertTrue(pending_jobs_msg)
        self.assertTrue(process_job_input_msg_job_3)
        self.assertTrue(process_job_input_msg_job_6)
Example #16
0
    def test_successful_job_1_completed(self, mock_store):
        '''Tests calling RecipeDefinition.get_next_jobs_to_queue() successfully when job 1 has been completed.'''

        definition = {
            'version': '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'jobs': [{
                'name': 'Job 1',
                'job_type': {
                    'name': self.job_type_1.name,
                    'version': self.job_type_1.version,
                },
                'recipe_inputs': [{
                    'recipe_input': 'Recipe Input',
                    'job_input': self.input_name_1,
                }]
            }, {
                'name': 'Job 2',
                'job_type': {
                    'name': self.job_type_2.name,
                    'version': self.job_type_2.version,
                },
                'dependencies': [{
                    'name': 'Job 1',
                    'connections': [{
                        'output': self.output_name_1,
                        'input': self.input_name_2,
                    }],
                }],
            }],
        }
        recipe_definition = RecipeDefinition(definition)
        recipe_definition.validate_job_interfaces()

        data = {
            'version': '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'file_id': self.file_1.id,
            }],
            'workspace_id': 1,
        }
        recipe_data = RecipeData(data)
        recipe_definition.validate_data(recipe_data)

        png_file_ids = [98, 99, 100]
        job_results = JobResults()
        job_results.add_file_list_parameter(self.output_name_1, png_file_ids)
        job_1 = Job.objects.select_related('job_type').get(pk=self.job_1.id)
        job_1.results = job_results.get_dict()
        job_1.save()
        job_2 = Job.objects.select_related('job_type').get(pk=self.job_2.id)

        results = recipe_definition.get_next_jobs_to_queue(recipe_data, {'Job 2': job_2}, {'Job 1': job_1})

        # Make sure only Job 2 is returned and that its job data is correct
        self.assertListEqual([self.job_2.id], results.keys())
        self.assertDictEqual(results[self.job_2.id].get_dict(), {
            'version': '1.0',
            'input_data': [{
                'name': self.input_name_2,
                'file_ids': png_file_ids,
            }],
            'output_data': [{
                'name': self.output_name_2,
                'workspace_id': 1,
            }],
        })
Example #17
0
    def test_process_job_output(self):
        """Tests calling JobManager.process_job_output()"""

        output_1 = JobResults()
        output_1.add_file_parameter('foo', 1)
        output_2 = JobResults()
        output_2.add_file_parameter('foo', 2)

        # These jobs have completed and have their execution results
        job_exe_1 = job_test_utils.create_job_exe(status='COMPLETED',
                                                  output=output_1)
        job_exe_2 = job_test_utils.create_job_exe(status='COMPLETED',
                                                  output=output_2)

        # These jobs have their execution results, but have not completed
        job_exe_3 = job_test_utils.create_job_exe(status='RUNNING')
        job_exe_4 = job_test_utils.create_job_exe(status='RUNNING')
        for job_exe in [job_exe_3, job_exe_4]:
            job_exe_output = JobExecutionOutput()
            job_exe_output.job_exe_id = job_exe.id
            job_exe_output.job_id = job_exe.job_id
            job_exe_output.job_type_id = job_exe.job.job_type_id
            job_exe_output.exe_num = job_exe.exe_num
            job_exe_output.output = JobResults().get_dict()
            job_exe_output.save()

        # These jobs have completed, but do not have their execution results
        job_exe_5 = job_test_utils.create_job_exe(status='RUNNING')
        job_exe_6 = job_test_utils.create_job_exe(status='RUNNING')
        for job in [job_exe_5.job, job_exe_6.job]:
            job.status = 'COMPLETED'
            job.save()

        # Test method
        job_ids = [
            job_exe.job_id for job_exe in
            [job_exe_1, job_exe_2, job_exe_3, job_exe_4, job_exe_5, job_exe_6]
        ]
        result_ids = Job.objects.process_job_output(job_ids, timezone.now())

        self.assertEqual(set(result_ids), {job_exe_1.job_id, job_exe_2.job_id})
        # Jobs 1 and 2 should have output populated, jobs 3 through 6 should not
        jobs = list(Job.objects.filter(id__in=job_ids).order_by('id'))
        self.assertEqual(len(jobs), 6)
        self.assertTrue(jobs[0].has_output())
        self.assertDictEqual(jobs[0].output, output_1.get_dict())
        self.assertTrue(jobs[1].has_output())
        self.assertDictEqual(jobs[1].output, output_2.get_dict())
        self.assertFalse(jobs[2].has_output())
        self.assertFalse(jobs[3].has_output())
        self.assertFalse(jobs[4].has_output())
        self.assertFalse(jobs[5].has_output())
Example #18
0
import django
from django.db.utils import DatabaseError, OperationalError
from django.utils.timezone import now
from django.test import TransactionTestCase
from mock import patch

from error.exceptions import ScaleDatabaseError, ScaleIOError, ScaleOperationalError
from job.configuration.results.exceptions import InvalidResultsManifest, MissingRequiredOutput
from job.configuration.results.job_results import JobResults
from job.configuration.results.results_manifest.results_manifest import ResultsManifest
from job.management.commands.scale_post_steps import Command as PostCommand
from job.models import JobExecutionOutput
from job.test import utils as job_utils
from trigger.models import TriggerEvent

JOB_RESULTS = JobResults()
RESULTS_MANIFEST = ResultsManifest()
RESULTS = (JOB_RESULTS, RESULTS_MANIFEST)


class TestPostJobSteps(TransactionTestCase):
    def setUp(self):
        django.setup()

        cmd = 'command'
        cmd_args = 'args'
        interface = {
            'version':
            '1.0',
            'command':
            cmd,
    def test_successful_job_1_completed(self, mock_store):
        '''Tests calling RecipeDefinition.get_next_jobs_to_queue() successfully when job 1 has been completed.'''

        definition = {
            'version':
            '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'type': 'file',
                'media_types': ['text/plain'],
            }],
            'jobs': [{
                'name':
                'Job 1',
                'job_type': {
                    'name': self.job_type_1.name,
                    'version': self.job_type_1.version,
                },
                'recipe_inputs': [{
                    'recipe_input': 'Recipe Input',
                    'job_input': self.input_name_1,
                }]
            }, {
                'name':
                'Job 2',
                'job_type': {
                    'name': self.job_type_2.name,
                    'version': self.job_type_2.version,
                },
                'dependencies': [{
                    'name':
                    'Job 1',
                    'connections': [{
                        'output': self.output_name_1,
                        'input': self.input_name_2,
                    }],
                }],
            }],
        }
        recipe_definition = RecipeDefinition(definition)
        recipe_definition.validate_job_interfaces()

        data = {
            'version': '1.0',
            'input_data': [{
                'name': 'Recipe Input',
                'file_id': self.file_1.id,
            }],
            'workspace_id': 1,
        }
        recipe_data = RecipeData(data)
        recipe_definition.validate_data(recipe_data)

        png_file_ids = [98, 99, 100]
        job_results = JobResults()
        job_results.add_file_list_parameter(self.output_name_1, png_file_ids)
        job_1 = Job.objects.select_related('job_type').get(pk=self.job_1.id)
        job_1.results = job_results.get_dict()
        job_1.save()
        job_2 = Job.objects.select_related('job_type').get(pk=self.job_2.id)

        results = recipe_definition.get_next_jobs_to_queue(
            recipe_data, {'Job 2': job_2}, {'Job 1': job_1})

        # Make sure only Job 2 is returned and that its job data is correct
        self.assertListEqual([self.job_2.id], results.keys())
        self.assertDictEqual(
            results[self.job_2.id].get_dict(), {
                'version':
                '1.0',
                'input_data': [{
                    'name': self.input_name_2,
                    'file_ids': png_file_ids,
                }],
                'output_data': [{
                    'name': self.output_name_2,
                    'workspace_id': 1,
                }],
            })