Beispiel #1
0
def create_recipe_node(recipe=None,
                       node_name=None,
                       condition=None,
                       job=None,
                       sub_recipe=None,
                       save=False,
                       is_original=True):
    """Creates a recipe_node model for unit testing

    :param recipe: The recipe containing the node
    :type recipe: :class:'recipe.models.Recipe'
    :param node_name: The node name
    :type node_name: string
    :param condition: The condition in the node
    :type condition: :class:'recipe.models.RecipeCondition'
    :param job: The job in the node
    :type job: :class:'job.models.Job'
    :param sub_recipe: The recipe in the node
    :type sub_recipe: :class:'recipe.models.Recipe'
    :param save: Whether to save the model
    :type save: bool
    :param is_original: Whether the recipe node is original
    :type is_original: bool
    :returns: The recipe_node model
    :rtype: :class:`recipe.models.RecipeNode`
    """

    if not recipe:
        recipe = create_recipe()

    if not node_name:
        node_name = 'Test Node Name'

    if not job and not sub_recipe:
        job = job_test_utils.create_job()

    recipe_node = RecipeNode()
    recipe_node.recipe = recipe
    recipe_node.node_name = node_name
    recipe_node.is_original = is_original
    if condition:
        recipe_node.condition = condition
    elif job:
        recipe_node.job = job
    elif sub_recipe:
        recipe_node.sub_recipe = sub_recipe

    if save:
        recipe_node.save()

    return recipe_node
Beispiel #2
0
def create_recipe_job(recipe=None, job_name=None, job=None):
    """Creates a job type model for unit testing

    :param recipe: The associated recipe
    :type recipe: :class:'recipe.models.Recipe'
    :param job_name: The associated name for the recipe job
    :type job_name: string
    :param job: The associated job
    :type job: :class:'job.models.Job'
    :returns: The recipe job model
    :rtype: :class:`recipe.models.RecipeNode`
    """
    if not recipe:
        recipe = create_recipe()

    if not job_name:
        job_name = 'Test Job Name'

    if not job:
        job = job_test_utils.create_job()

    recipe_job = RecipeNode()
    recipe_job.node_name = job_name
    recipe_job.job = job
    recipe_job.recipe = recipe
    recipe_job.save()
    return recipe_job
Beispiel #3
0
    def _handle_recipe_jobs(self, msg_already_run, recipes, new_revision_id,
                            revisions, recipe_job_ids, job_names, all_jobs,
                            when):
        """Handles the reprocessing of the recipe jobs

        :param msg_already_run: Whether the database transaction has already occurred
        :type msg_already_run: bool
        :param recipes: The new recipe models
        :type recipes: list
        :param new_revision_id: The ID of the new recipe type revision to use for reprocessing
        :type new_revision_id: int
        :param revisions: Recipe type revisions stored by revision ID
        :type revisions: dict
        :param recipe_job_ids: Dict where recipe ID maps to a dict where job_name maps to a list of job IDs
        :type recipe_job_ids: dict
        :param job_names: The job names within the recipes to force reprocess
        :type job_names: list
        :param all_jobs: If True then all jobs within the recipe should be reprocessed, False otherwise
        :type all_jobs: bool
        :param when: The time that the jobs were superseded
        :type when: :class:`datetime.datetime`
        :return: A list of messages that should be sent regarding the superseded jobs
        :rtype: list
        """

        superseded_job_ids = []
        unpublish_job_ids = []
        recipe_job_models = []
        recipe_job_count = 0
        new_graph = revisions[new_revision_id].get_recipe_definition(
        ).get_graph()

        for recipe in recipes:
            job_ids = recipe_job_ids[
                recipe.
                superseded_recipe_id]  # Get job IDs for superseded recipe
            old_graph = revisions[
                recipe.recipe_type_rev_id].get_recipe_definition().get_graph()
            names = old_graph.get_topological_order(
            ) if all_jobs else job_names

            # Compute the job differences between recipe revisions (force reprocess for jobs in job_names)
            graph_delta = RecipeGraphDelta(old_graph, new_graph)
            for job_name in names:
                graph_delta.reprocess_identical_node(job_name)

            # Jobs that are identical from old recipe to new recipe are just copied to new recipe
            if not msg_already_run:
                for identical_job_name in graph_delta.get_identical_nodes():
                    if identical_job_name in job_ids:
                        for job_id in job_ids[identical_job_name]:
                            recipe_job = RecipeNode()
                            recipe_job.job_id = job_id
                            recipe_job.node_name = identical_job_name
                            recipe_job.recipe_id = recipe.id
                            recipe_job.is_original = False
                            recipe_job_count += 1
                            recipe_job_models.append(recipe_job)
                            if len(recipe_job_models) >= MODEL_BATCH_SIZE:
                                RecipeNode.objects.bulk_create(
                                    recipe_job_models)
                                recipe_job_models = []

            # Jobs that changed from old recipe to new recipe should be superseded
            for changed_job_name in graph_delta.get_changed_nodes():
                if changed_job_name in job_ids:
                    superseded_job_ids.extend(job_ids[changed_job_name])

            # Jobs that were deleted from old recipe to new recipe should be superseded and unpublished
            for deleted_job_name in graph_delta.get_deleted_nodes():
                if deleted_job_name in job_ids:
                    superseded_job_ids.extend(job_ids[deleted_job_name])
                    unpublish_job_ids.extend(job_ids[deleted_job_name])

        # Finish creating any remaining RecipeNode models
        if recipe_job_models and not msg_already_run:
            RecipeNode.objects.bulk_create(recipe_job_models)
        logger.info('Copied %d job(s) to the new recipe(s)', recipe_job_count)

        # Supersede recipe jobs that were not copied over to a new recipe
        if not msg_already_run:
            Job.objects.supersede_jobs(superseded_job_ids, when)
        logger.info('Superseded %d job(s)', len(superseded_job_ids))
        logger.info('Found %d job(s) that should be unpublished',
                    len(unpublish_job_ids))

        # Create messages to unpublish and cancel jobs
        messages = create_cancel_jobs_messages(superseded_job_ids, when)
        messages.extend(create_unpublish_jobs_messages(unpublish_job_ids,
                                                       when))
        return messages
Beispiel #4
0
    def execute(self):
        """See :meth:`messaging.messages.message.CommandMessage.execute`
        """

        when = now()
        blocked_job_ids = set()
        pending_job_ids = set()
        completed_recipe_ids = []
        updated_batch_ids = []
        num_jobs_with_input = 0
        job_ids_ready_for_first_queue = []

        with transaction.atomic():
            # Lock recipes
            Recipe.objects.get_locked_recipes(self._recipe_ids)

            # Process all recipe handlers
            recipes = list(Recipe.objects.get_recipes_with_definitions(self._recipe_ids))
            while len(recipes) > 0:
                # Gather up a list of recipes that doesn't exceed the job number limit
                recipe_list = [recipes.pop()]
                # TODO: eventually use job count per recipe, right now assume 10
                num_jobs = 10
                while len(recipes) > 0 and num_jobs + 10 < MAX_JOBS_AT_A_TIME:
                    num_jobs += 10
                    recipe_list.append(recipes.pop())

                # Process handlers for the list of recipes
                handlers = Recipe.objects.get_recipe_handlers(recipe_list)

                # Create any jobs needed
                jobs_to_create = []
                recipe_handler_dicts = {}
                for handler in handlers:
                    handler_dict = handler.get_jobs_to_create()
                    recipe_handler_dicts[handler.recipe.id] = handler_dict
                    for job_list in handler_dict.values():
                        for job in job_list:
                            jobs_to_create.append(job)
                if jobs_to_create:
                    Job.objects.bulk_create(jobs_to_create)
                recipe_jobs_to_create = []
                for handler in handlers:
                    recipe_id = handler.recipe.id
                    handler_dict = recipe_handler_dicts[recipe_id]
                    recipe_jobs = []
                    for job_name, job_list in handler_dict.iteritems():
                        for job in job_list:
                            recipe_job = RecipeNode()
                            recipe_job.job = job
                            recipe_job.node_name = job_name
                            recipe_job.recipe_id = recipe_id
                            recipe_jobs.append(recipe_job)
                    recipe_jobs_to_create.extend(recipe_jobs)
                    handler.add_jobs(recipe_jobs)
                if recipe_jobs_to_create:
                    RecipeNode.objects.bulk_create(recipe_jobs_to_create)

                for handler in handlers:
                    for blocked_job in handler.get_blocked_jobs():
                        blocked_job_ids.add(blocked_job.id)
                    for pending_job in handler.get_pending_jobs():
                        pending_job_ids.add(pending_job.id)
                    jobs_with_input = handler.get_jobs_ready_for_input()
                    num_jobs_with_input += len(jobs_with_input)
                    for job in jobs_with_input:
                        job.update_database_with_input(when)
                    for job in handler.get_jobs_ready_for_first_queue():
                        job_ids_ready_for_first_queue.append(job.id)
                    if handler.is_completed() and not handler.recipe.is_completed:
                        completed_recipe_ids.append(handler.recipe.id)
                        if handler.recipe.batch_id:
                            updated_batch_ids.append(handler.recipe.batch_id)

            Recipe.objects.complete_recipes(completed_recipe_ids, when)

        # Create new messages
        self.new_messages.extend(create_blocked_jobs_messages(blocked_job_ids, when))
        self.new_messages.extend(create_pending_jobs_messages(pending_job_ids, when))
        # Jobs ready for their first queue need to have their input processed
        self.new_messages.extend(create_process_job_input_messages(job_ids_ready_for_first_queue))

        logger.info('Found %d job(s) that should transition to BLOCKED', len(blocked_job_ids))
        logger.info('Found %d job(s) that should transition to PENDING', len(pending_job_ids))
        logger.info('Found %d job(s) that received their input', num_jobs_with_input)
        logger.info('Found %d job(s) that are ready to be queued', len(job_ids_ready_for_first_queue))

        return True