コード例 #1
0
    def validate(self, batch):
        """Validates the given batch to make sure it is valid with respect to this batch definition. The given batch
        must have all of its related fields populated, though id and root_batch_id may be None. The derived definition
        attributes, such as estimated recipe total and previous batch diff, will be populated by this method.

        :param batch: The batch model
        :type batch: :class:`batch.models.Batch`
        :returns: A list of warnings discovered during validation
        :rtype: list

        :raises :class:`batch.definition.exceptions.InvalidDefinition`: If the definition is invalid
        """

        if self.root_batch_id:
            if batch.recipe_type_id != batch.superseded_batch.recipe_type_id:
                raise InvalidDefinition('MISMATCHED_RECIPE_TYPE',
                                        'New batch and previous batch must have the same recipe type')
            if not batch.superseded_batch.is_creation_done:
                raise InvalidDefinition('PREV_BATCH_STILL_CREATING',
                                        'Previous batch must have completed creating all of its recipes')

            # Generate recipe diff against the previous batch
            recipe_def = batch.recipe_type_rev.get_definition()
            prev_recipe_def = batch.superseded_batch.recipe_type_rev.get_definition()
            self.prev_batch_diff = RecipeDiff(prev_recipe_def, recipe_def)
            if self.forced_nodes:
                self.prev_batch_diff.set_force_reprocess(self.forced_nodes)
            if not self.prev_batch_diff.can_be_reprocessed:
                raise InvalidDefinition('PREV_BATCH_NO_REPROCESS', 'Previous batch cannot be reprocessed')

        self._estimate_recipe_total(batch)
        if not self.estimated_recipes:
            raise InvalidDefinition('NO_RECIPES', 'Batch definition must result in creating at least one recipe')

        return []
コード例 #2
0
ファイル: create_recipes.py プロジェクト: matttalda/scale
    def _create_subrecipes(self):
        """Creates the recipe models for a sub-recipe message

        :returns: The list of recipe models created
        :rtype: :func:`list`
        """

        sub_recipes = {}  # {Node name: recipe model}

        superseded_sub_recipes = {}
        revision_ids = []
        # Get superseded sub-recipes from superseded recipe
        if self.superseded_recipe_id:
            superseded_sub_recipes = RecipeNode.objects.get_subrecipes(self.superseded_recipe_id)
            revision_ids = [r.recipe_type_rev_id for r in superseded_sub_recipes.values()]

        # Get recipe type revisions
        revision_tuples = [(sub.recipe_type_name, sub.recipe_type_rev_num) for sub in self.sub_recipes]
        revs_by_id = RecipeTypeRevision.objects.get_revision_map(revision_ids, revision_tuples)
        revs_by_tuple = {(rev.recipe_type.name, rev.revision_num): rev for rev in revs_by_id.values()}

        # Create new recipe models
        process_input_by_node = {}
        for sub_recipe in self.sub_recipes:
            node_name = sub_recipe.node_name
            process_input_by_node[node_name] = sub_recipe.process_input
            revision = revs_by_tuple[(sub_recipe.recipe_type_name, sub_recipe.recipe_type_rev_num)]
            superseded_recipe = superseded_sub_recipes[node_name] if node_name in superseded_sub_recipes else None
            recipe = Recipe.objects.create_recipe_v6(revision, self.event_id, root_recipe_id=self.root_recipe_id,
                                                     recipe_id=self.recipe_id, batch_id=self.batch_id,
                                                     superseded_recipe=superseded_recipe)
            sub_recipes[node_name] = recipe

        Recipe.objects.bulk_create(sub_recipes.values())
        logger.info('Created %d recipe(s)', len(sub_recipes))

        # Create recipe nodes
        recipe_nodes = RecipeNode.objects.create_subrecipe_nodes(self.recipe_id, sub_recipes)
        RecipeNode.objects.bulk_create(recipe_nodes)

        # Set up process input dict
        for sub_recipe in self.sub_recipes:
            recipe = sub_recipes[sub_recipe.node_name]
            self._process_input[recipe.id] = sub_recipe.process_input

        # Set up recipe diffs
        if self.superseded_recipe_id:
            for node_name, recipe in sub_recipes.items():
                pair = _RecipePair(recipe.superseded_recipe, recipe)
                rev_id = recipe.superseded_recipe.recipe_type_rev_id
                old_revision = revs_by_id[rev_id]
                new_revision = revs_by_tuple[(recipe.recipe_type.name, recipe.recipe_type_rev.revision_num)]
                diff = RecipeDiff(old_revision.get_definition(), new_revision.get_definition())
                if self.forced_nodes:
                    sub_forced_nodes = self.forced_nodes.get_forced_nodes_for_subrecipe(node_name)
                    if sub_forced_nodes:
                        diff.set_force_reprocess(sub_forced_nodes)
                self._recipe_diffs.append(_RecipeDiff(diff, [pair]))

        return sub_recipes.values()
コード例 #3
0
    def validate(self, batch):
        """Validates the given batch to make sure it is valid with respect to this batch definition. The given batch
        must have all of its related fields populated, though id and root_batch_id may be None. The derived definition
        attributes, such as estimated recipe total and previous batch diff, will be populated by this method.

        :param batch: The batch model
        :type batch: :class:`batch.models.Batch`
        :returns: A list of warnings discovered during validation
        :rtype: :func:`list`

        :raises :class:`batch.definition.exceptions.InvalidDefinition`: If the definition is invalid
        """
        # Re-processing a previous batch
        if self.root_batch_id:
            if batch.recipe_type_id != batch.superseded_batch.recipe_type_id:
                raise InvalidDefinition('MISMATCHED_RECIPE_TYPE',
                                        'New batch and previous batch must have the same recipe type')
            if not batch.superseded_batch.is_creation_done:
                raise InvalidDefinition('PREV_BATCH_STILL_CREATING',
                                        'Previous batch must have completed creating all of its recipes')

            # Generate recipe diff against the previous batch
            recipe_def = batch.recipe_type_rev.get_definition()
            prev_recipe_def = batch.superseded_batch.recipe_type_rev.get_definition()
            self.prev_batch_diff = RecipeDiff(prev_recipe_def, recipe_def)
            if self.forced_nodes:
                self.prev_batch_diff.set_force_reprocess(self.forced_nodes)
            if not self.prev_batch_diff.can_be_reprocessed:
                raise InvalidDefinition('PREV_BATCH_NO_REPROCESS', 'Previous batch cannot be reprocessed')
        
        # New batch - need to validate dataset parameters against recipe revision
        elif self.dataset:
            from data.interface.exceptions import InvalidInterfaceConnection
            from data.models import DataSet
            from recipe.models import RecipeTypeRevision
            
            dataset_definition = DataSet.objects.get(pk=self.dataset).get_definition()
            recipe_type_rev = RecipeTypeRevision.objects.get_revision(name=batch.recipe_type.name, revision_num=batch.recipe_type_rev.revision_num).recipe_type

            # combine the parameters
            from batch.models import Batch
            dataset_parameters = Batch.objects.merge_parameter_map(batch, DataSet.objects.get(pk=self.dataset))

            try:
                recipe_type_rev.get_definition().input_interface.validate_connection(dataset_parameters)
            except InvalidInterfaceConnection as ex:
                raise InvalidDefinition('MISMATCHED_PARAMS', 'No parameters in the dataset match the recipe type inputs. %s' % unicode(ex))
                
        self._estimate_recipe_total(batch)
        if not self.estimated_recipes:
            raise InvalidDefinition('NO_RECIPES', 'Batch definition must result in creating at least one recipe')

        return []
コード例 #4
0
    def test_init_new_required_input(self):
        """Tests creating a RecipeDiff when the newer definition has a new required input parameter"""

        interface_1 = Interface()
        interface_1.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_1.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2 = Interface()
        interface_2.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_2.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2.add_parameter(JsonParameter('json_param_2', 'object', required=True))

        definition_1 = RecipeDefinition(interface_1)
        definition_1.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_1.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_1.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_1.add_recipe_node('D', 'recipe_type_1', 1)
        definition_1.add_dependency('A', 'B')
        definition_1.add_dependency('A', 'C')
        definition_1.add_dependency('C', 'D')
        definition_1.add_recipe_input_connection('A', 'input_1', 'file_param_1')
        definition_1.add_dependency_input_connection('B', 'b_input_1', 'A', 'a_output_1')
        definition_1.add_dependency_input_connection('C', 'c_input_1', 'A', 'a_output_2')
        definition_1.add_dependency_input_connection('D', 'd_input_1', 'C', 'c_output_1')
        definition_1.add_recipe_input_connection('D', 'd_input_2', 'json_param_1')

        definition_2 = RecipeDefinition(interface_2)
        definition_2.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_2.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_2.add_job_node('C', 'job_type_3', '1.1', 1)  # Change to job type version and revision
        definition_2.add_recipe_node('D', 'recipe_type_1', 1)
        definition_2.add_dependency('A', 'B')
        definition_2.add_dependency('A', 'C')
        definition_2.add_dependency('C', 'D')
        definition_2.add_recipe_input_connection('A', 'input_1', 'file_param_1')
        definition_2.add_dependency_input_connection('B', 'b_input_1', 'A', 'a_output_1')
        definition_2.add_dependency_input_connection('C', 'c_input_1', 'A', 'a_output_2')
        definition_2.add_dependency_input_connection('D', 'd_input_1', 'C', 'c_output_1')
        definition_2.add_recipe_input_connection('D', 'd_input_2', 'json_param_1')

        diff = RecipeDiff(definition_1, definition_2)

        self.assertFalse(diff.can_be_reprocessed)
        self.assertEqual(len(diff.reasons), 1)
        self.assertEqual(diff.reasons[0].name, 'INPUT_CHANGE')
        # Cannot be reprocessed, so no nodes to copy, supersede, or unpublish
        self.assertDictEqual(diff.get_nodes_to_copy(), {})
        self.assertDictEqual(diff.get_nodes_to_supersede(), {})
        self.assertDictEqual(diff.get_nodes_to_unpublish(), {})
        # Ensure no nodes have reprocess_new_node set to true
        for node_diff in diff.graph.values():
            self.assertFalse(node_diff.reprocess_new_node)
コード例 #5
0
ファイル: create_recipes.py プロジェクト: matttalda/scale
    def _find_existing_recipes(self):
        """Searches to determine if this message already ran and the new recipes already exist

        :returns: The list of recipe models found
        :rtype: :func:`list`
        """

        recipes = []
        if self.create_recipes_type == REPROCESS_TYPE:
            qry = Recipe.objects.select_related('superseded_recipe')
            recipes = qry.filter(root_superseded_recipe_id__in=self.root_recipe_ids, event_id=self.event_id)
            # Create recipe diffs
            rev_ids = [recipe.recipe_type_rev_id for recipe in recipes]
            rev_ids.extend([recipe.superseded_recipe.recipe_type_rev_id for recipe in recipes])
            revs = RecipeTypeRevision.objects.get_revision_map(rev_ids, [])
            pair_dict = {}
            for recipe in recipes:
                rev_tuple = (recipe.superseded_recipe.recipe_type_rev_id, recipe.recipe_type_rev_id)
                pair = _RecipePair(recipe.superseded_recipe, recipe)
                if rev_tuple not in pair_dict:
                    pair_dict[rev_tuple] = []
                pair_dict[rev_tuple].append(pair)
            for pair_tuple, pairs in pair_dict.items():
                old_revision = revs[pair_tuple[0]]
                new_revision = revs[pair_tuple[1]]
                diff = RecipeDiff(old_revision.get_definition(), new_revision.get_definition())
                if self.forced_nodes:
                    diff.set_force_reprocess(self.forced_nodes)
                self._recipe_diffs.append(_RecipeDiff(diff, pairs))
        elif self.create_recipes_type == SUB_RECIPE_TYPE:
            node_names = [sub.node_name for sub in self.sub_recipes]
            qry = RecipeNode.objects.select_related('sub_recipe__superseded_recipe')
            qry = qry.filter(recipe_id=self.recipe_id, node_name__in=node_names, sub_recipe__event_id=self.event_id)
            recipes_by_node = {rn.node_name: rn.sub_recipe for rn in qry}
            recipes = list(recipes_by_node.values())
            if recipes_by_node:
                # Set up process input dict
                for sub_recipe in self.sub_recipes:
                    recipe = recipes_by_node[sub_recipe.node_name]
                    self._process_input[recipe.id] = sub_recipe.process_input
                if recipes[0].superseded_recipe:
                    # Set up recipe diffs
                    rev_ids = [recipe.recipe_type_rev_id for recipe in recipes]
                    rev_ids.extend([recipe.superseded_recipe.recipe_type_rev_id for recipe in recipes])
                    revs = RecipeTypeRevision.objects.get_revision_map(rev_ids, [])
                    for node_name, recipe in recipes_by_node.items():
                        pair = _RecipePair(recipe.superseded_recipe, recipe)
                        old_revision = revs[recipe.superseded_recipe.recipe_type_rev_id]
                        new_revision = revs[recipe.recipe_type_rev_id]
                        diff = RecipeDiff(old_revision.get_definition(), new_revision.get_definition())
                        if self.forced_nodes:
                            sub_forced_nodes = self.forced_nodes.get_forced_nodes_for_subrecipe(node_name)
                            if sub_forced_nodes:
                                diff.set_force_reprocess(sub_forced_nodes)
                        self._recipe_diffs.append(_RecipeDiff(diff, [pair]))

        return recipes
コード例 #6
0
ファイル: test_diff_v6.py プロジェクト: chedda7/scale
    def test_convert_recipe_diff_to_v6_json_with_changes(self):
        """Tests calling convert_recipe_diff_to_v6_json() with a diff containing a variety of changes"""

        interface_1 = Interface()
        interface_1.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_1.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2 = Interface()
        interface_2.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_2.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2.add_parameter(
            JsonParameter('json_param_2', 'object', required=False))

        definition_1 = RecipeDefinition(interface_1)
        definition_1.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_1.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_1.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_1.add_recipe_node('D', 'recipe_type_1', 1)
        definition_1.add_job_node('E', 'job_type_4', '1.0', 1)
        definition_1.add_dependency('A', 'B')
        definition_1.add_dependency('A', 'C')
        definition_1.add_dependency('B', 'E')
        definition_1.add_dependency('C', 'D')
        definition_1.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_1.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_1.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_1.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_1.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        definition_2 = RecipeDefinition(interface_2)
        # Nodes B and E are deleted
        definition_2.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_2.add_job_node('C', 'job_type_3', '2.1',
                                  1)  # Change to job type version and revision
        definition_2.add_recipe_node('D', 'recipe_type_1', 1)
        definition_2.add_recipe_node('F', 'recipe_type_2', 5)  # New node
        definition_2.add_dependency('A', 'C')
        definition_2.add_dependency('C', 'D')
        definition_2.add_dependency('D', 'F')
        definition_2.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_2.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_2.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_2.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')
        definition_2.add_recipe_input_connection('F', 'f_input_1',
                                                 'json_param_2')

        diff = RecipeDiff(definition_1, definition_2)
        json = convert_recipe_diff_to_v6_json(diff)
        RecipeDiffV6(diff=json.get_dict(), do_validate=True)  # Revalidate
        self.assertTrue(json.get_dict()['can_be_reprocessed'])
コード例 #7
0
ファイル: test_diff_v6.py プロジェクト: chedda7/scale
    def test_convert_recipe_diff_to_v6_json_new_required_input(self):
        """Tests calling convert_recipe_diff_to_v6_json() with a diff where there is a breaking recipe interface
        change
        """

        interface_1 = Interface()
        interface_1.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_1.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2 = Interface()
        interface_2.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_2.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2.add_parameter(
            JsonParameter('json_param_2', 'object', required=True))

        definition_1 = RecipeDefinition(interface_1)
        definition_1.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_1.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_1.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_1.add_recipe_node('D', 'recipe_type_1', 1)
        definition_1.add_dependency('A', 'B')
        definition_1.add_dependency('A', 'C')
        definition_1.add_dependency('C', 'D')
        definition_1.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_1.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_1.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_1.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_1.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        definition_2 = RecipeDefinition(interface_2)
        definition_2.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_2.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_2.add_job_node('C', 'job_type_3', '1.1',
                                  1)  # Change to job type version and revision
        definition_2.add_recipe_node('D', 'recipe_type_1', 1)
        definition_2.add_dependency('A', 'B')
        definition_2.add_dependency('A', 'C')
        definition_2.add_dependency('C', 'D')
        definition_2.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_2.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_2.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_2.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_2.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        diff = RecipeDiff(definition_1, definition_2)
        json = convert_recipe_diff_to_v6_json(diff)
        RecipeDiffV6(diff=json.get_dict(), do_validate=True)  # Revalidate
        self.assertFalse(json.get_dict()['can_be_reprocessed'])
コード例 #8
0
ファイル: test_diff_v6.py プロジェクト: chedda7/scale
    def test_convert_recipe_diff_to_v6_json_empty(self):
        """Tests calling convert_recipe_diff_to_v6_json() with an empty diff"""

        # Try diff with empty recipe definitions
        interface_a = Interface()
        interface_b = Interface()
        definition_a = RecipeDefinition(interface_a)
        definition_b = RecipeDefinition(interface_b)
        diff = RecipeDiff(definition_a, definition_b)
        json = convert_recipe_diff_to_v6_json(diff)
        RecipeDiffV6(diff=json.get_dict(), do_validate=True)  # Revalidate
        self.assertTrue(json.get_dict()['can_be_reprocessed'])
コード例 #9
0
ファイル: create_recipes.py プロジェクト: sau29/scale
    def _create_recipes_for_reprocess(self):
        """Creates the recipe models for a reprocess

        :returns: The list of recipe models created
        :rtype: list
        """

        rt = RecipeType.objects.get(name=self.recipe_type_name)
        if not rt.is_active:
            raise InactiveRecipeType("Recipe Type %s is inactive" % rt.name)

        recipes = []

        # Supersede recipes that are being reprocessed
        superseded_recipe_ids = [r.id for r in self._superseded_recipes]
        Recipe.objects.supersede_recipes(superseded_recipe_ids, self._when)

        # Get superseded recipe definitions and calculate diffs
        revision_tuples = [(self.recipe_type_name, self.recipe_type_rev_num)]
        revision_ids = [r.recipe_type_rev_id for r in self._superseded_recipes]
        revs = RecipeTypeRevision.objects.get_revision_map(
            revision_ids, revision_tuples)
        for rev in revs.values():
            if rev.recipe_type.name == self.recipe_type_name:
                if rev.revision_num == self.recipe_type_rev_num:
                    new_rev = rev
                    break
        diffs = {
            rev_id: RecipeDiff(rev.get_definition(), new_rev.get_definition())
            for rev_id, rev in revs.items()
        }
        if self.forced_nodes:
            for diff in diffs.values():
                diff.set_force_reprocess(self.forced_nodes)

        # Create new recipe models
        cannot_reprocess_count = 0
        for superseded_recipe in self._superseded_recipes:
            # Make sure that superseded recipe can be reprocessed and is not a sub-recipe
            if diffs[
                    superseded_recipe.
                    recipe_type_rev_id].can_be_reprocessed and not superseded_recipe.recipe_id:
                try:
                    recipe = Recipe.objects.create_recipe_v6(
                        new_rev,
                        self.event_id,
                        batch_id=self.batch_id,
                        superseded_recipe=superseded_recipe,
                        copy_superseded_input=True)
                    recipes.append(recipe)
                except (InvalidData, InactiveRecipeType) as ex:
                    cannot_reprocess_count += 1
            else:
                cannot_reprocess_count += 1

        Recipe.objects.bulk_create(recipes)
        logger.info('Created %d recipe(s)', len(recipes))
        logger.error('Could not reprocess %d recipe(s)',
                     cannot_reprocess_count)

        # Set up recipe diffs
        pairs_by_rev_id = {}
        for recipe in recipes:
            pair = _RecipePair(recipe.superseded_recipe, recipe)
            rev_id = recipe.superseded_recipe.recipe_type_rev_id
            if rev_id not in pairs_by_rev_id:
                pairs_by_rev_id[rev_id] = []
            pairs_by_rev_id[rev_id].append(pair)
        for rev_id, diff in diffs.items():
            if rev_id in pairs_by_rev_id:
                self._recipe_diffs.append(
                    _RecipeDiff(diff, pairs_by_rev_id[rev_id]))

        return recipes
コード例 #10
0
    def test_set_force_reprocess(self):
        """Tests calling RecipeDiff.set_force_reprocess()"""

        interface_1 = Interface()
        interface_1.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_1.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2 = Interface()
        interface_2.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_2.add_parameter(JsonParameter('json_param_1', 'object'))

        definition_1 = RecipeDefinition(interface_1)
        definition_1.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_1.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_1.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_1.add_recipe_node('D', 'recipe_type_1', 1)
        definition_1.add_job_node('E', 'job_type_4', '1.0', 1)
        definition_1.add_dependency('A', 'B')
        definition_1.add_dependency('A', 'C')
        definition_1.add_dependency('C', 'D')
        definition_1.add_dependency('C', 'E')
        definition_1.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_1.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_1.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_1.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_1.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')
        definition_1.add_dependency_input_connection('E', 'e_input_1', 'C',
                                                     'c_output_1')

        # No changes in definition 2
        definition_2 = RecipeDefinition(interface_2)
        definition_2.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_2.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_2.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_2.add_recipe_node('D', 'recipe_type_1', 1)
        definition_2.add_job_node('E', 'job_type_4', '1.0', 1)
        definition_2.add_dependency('A', 'B')
        definition_2.add_dependency('A', 'C')
        definition_2.add_dependency('C', 'D')
        definition_2.add_dependency('C', 'E')
        definition_2.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_2.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_2.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_2.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_2.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')
        definition_2.add_dependency_input_connection('E', 'e_input_1', 'C',
                                                     'c_output_1')

        recipe_d_forced_nodes = ForcedNodes()
        recipe_d_forced_nodes.add_node('1')
        recipe_d_forced_nodes.add_node('2')
        top_forced_nodes = ForcedNodes()
        top_forced_nodes.add_node('C')
        top_forced_nodes.add_subrecipe('D', recipe_d_forced_nodes)
        diff = RecipeDiff(definition_1, definition_2)
        diff.set_force_reprocess(top_forced_nodes)

        # No recipe input changes so recipe can be reprocessed
        self.assertTrue(diff.can_be_reprocessed)
        self.assertListEqual(diff.reasons, [])
        # Check each node for correct fields
        node_a = diff.graph['A']
        self.assertEqual(node_a.status, NodeDiff.UNCHANGED)
        self.assertFalse(node_a.reprocess_new_node)
        self.assertListEqual(node_a.changes, [])
        node_b = diff.graph['B']
        self.assertEqual(node_b.status, NodeDiff.UNCHANGED)
        self.assertFalse(node_b.reprocess_new_node)
        self.assertListEqual(node_b.changes, [])
        node_c = diff.graph['C']
        self.assertEqual(node_c.status, NodeDiff.UNCHANGED)
        self.assertTrue(node_c.reprocess_new_node)  # Force reprocess
        self.assertListEqual(node_c.changes, [])
        node_d = diff.graph['D']
        self.assertEqual(node_d.status, NodeDiff.UNCHANGED)
        self.assertTrue(node_d.reprocess_new_node)  # Force reprocess
        self.assertListEqual(node_d.changes, [])
        # Check forced nodes object that got passed to recipe node D
        self.assertEqual(node_d.force_reprocess_nodes, recipe_d_forced_nodes)
        node_e = diff.graph['E']
        self.assertEqual(node_e.status, NodeDiff.UNCHANGED)
        self.assertTrue(
            node_e.reprocess_new_node)  # Force reprocess due to C being forced
        self.assertListEqual(node_e.changes, [])
        # Check nodes to copy, supersede, and unpublish
        self.assertSetEqual(set(diff.get_nodes_to_copy().keys()), {'A', 'B'})
        self.assertSetEqual(set(diff.get_nodes_to_supersede().keys()),
                            {'C', 'D', 'E'})
        self.assertSetEqual(set(diff.get_nodes_to_unpublish().keys()), set())
コード例 #11
0
    def test_init_changes_in_middle_of_chains(self):
        """Tests creating a RecipeDiff where nodes are deleted from and inserted into the middle of a chain"""

        interface_1 = Interface()
        interface_1.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_1.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2 = Interface()
        interface_2.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_2.add_parameter(JsonParameter('json_param_1', 'object'))

        definition_1 = RecipeDefinition(interface_1)
        definition_1.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_1.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_1.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_1.add_recipe_node('D', 'recipe_type_1', 1)
        definition_1.add_dependency('A', 'B')
        definition_1.add_dependency('A', 'C')
        definition_1.add_dependency('C', 'D')
        definition_1.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_1.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_1.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_1.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_1.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        definition_2 = RecipeDefinition(interface_2)
        definition_2.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_2.add_job_node('B', 'job_type_2', '2.0', 1)
        # Node C is deleted
        definition_2.add_recipe_node('D', 'recipe_type_1', 1)
        definition_2.add_job_node('E', 'job_type_4', '5.0',
                                  2)  # New node inbetween A and B
        definition_2.add_dependency('A', 'E')
        definition_2.add_dependency('E', 'B')
        definition_2.add_dependency('A', 'D')
        definition_2.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_2.add_dependency_input_connection('E', 'e_input_1', 'A',
                                                     'a_output_1')
        definition_2.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_2.add_dependency_input_connection('D', 'd_input_1', 'A',
                                                     'a_output_2')
        definition_2.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        diff = RecipeDiff(definition_1, definition_2)

        # No recipe input changes so recipe can be reprocessed
        self.assertTrue(diff.can_be_reprocessed)
        self.assertListEqual(diff.reasons, [])
        # Check each node for correct fields
        node_a = diff.graph['A']
        self.assertEqual(node_a.status, NodeDiff.UNCHANGED)
        self.assertFalse(node_a.reprocess_new_node)
        self.assertListEqual(node_a.changes, [])
        node_b = diff.graph['B']
        self.assertEqual(node_b.status, NodeDiff.CHANGED)
        self.assertTrue(node_b.reprocess_new_node)
        self.assertEqual(len(node_b.changes), 2)
        self.assertEqual(node_b.changes[0].name, 'PARENT_NEW')
        self.assertEqual(node_b.changes[1].name, 'PARENT_REMOVED')
        node_c = diff.graph['C']
        self.assertEqual(node_c.status, NodeDiff.DELETED)
        self.assertFalse(node_c.reprocess_new_node)
        self.assertListEqual(node_c.changes, [])
        node_d = diff.graph['D']
        self.assertEqual(node_d.status, NodeDiff.CHANGED)
        self.assertTrue(node_d.reprocess_new_node)
        self.assertEqual(len(node_d.changes), 3)
        self.assertEqual(node_d.changes[0].name, 'PARENT_NEW')
        self.assertEqual(node_d.changes[1].name, 'PARENT_REMOVED')
        self.assertEqual(node_d.changes[2].name, 'INPUT_CHANGE')
        node_e = diff.graph['E']
        self.assertEqual(node_e.status, NodeDiff.NEW)
        self.assertTrue(node_e.reprocess_new_node)
        self.assertListEqual(node_e.changes, [])
        # Check nodes to copy, supersede, and unpublish
        self.assertSetEqual(set(diff.get_nodes_to_copy().keys()), {'A'})
        self.assertSetEqual(set(diff.get_nodes_to_supersede().keys()),
                            {'B', 'C', 'D'})
        self.assertSetEqual(set(diff.get_nodes_to_unpublish().keys()), {'C'})
コード例 #12
0
    def test_init_identical(self):
        """Tests creating a RecipeDiff between two identical recipe definitions"""

        interface_1 = Interface()
        interface_1.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_1.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2 = Interface()
        interface_2.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_2.add_parameter(JsonParameter('json_param_1', 'object'))

        definition_1 = RecipeDefinition(interface_1)
        definition_1.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_1.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_1.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_1.add_recipe_node('D', 'recipe_type_1', 1)
        definition_1.add_dependency('A', 'B')
        definition_1.add_dependency('A', 'C')
        definition_1.add_dependency('C', 'D')
        definition_1.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_1.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_1.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_1.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_1.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        definition_2 = RecipeDefinition(interface_2)
        definition_2.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_2.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_2.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_2.add_recipe_node('D', 'recipe_type_1', 1)
        definition_2.add_dependency('A', 'B')
        definition_2.add_dependency('A', 'C')
        definition_2.add_dependency('C', 'D')
        definition_2.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_2.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_2.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_2.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_2.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        diff = RecipeDiff(definition_1, definition_2)

        self.assertTrue(diff.can_be_reprocessed)
        self.assertListEqual(diff.reasons, [])
        # Every node should be unchanged and all should be copied during a reprocess
        nodes_to_copy = diff.get_nodes_to_copy()
        self.assertSetEqual(set(nodes_to_copy.keys()), {'A', 'B', 'C', 'D'})
        for node_diff in nodes_to_copy.values():
            self.assertEqual(node_diff.status, NodeDiff.UNCHANGED)
            self.assertFalse(node_diff.reprocess_new_node)
            self.assertListEqual(node_diff.changes, [])
        self.assertDictEqual(diff.get_nodes_to_supersede(), {})
        self.assertDictEqual(diff.get_nodes_to_unpublish(), {})
コード例 #13
0
    def test_init_changes(self):
        """Tests creating a RecipeDiff when the newer definition has a variety of changes in it"""

        interface_1 = Interface()
        interface_1.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_1.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2 = Interface()
        interface_2.add_parameter(FileParameter('file_param_1', ['image/gif']))
        interface_2.add_parameter(JsonParameter('json_param_1', 'object'))
        interface_2.add_parameter(
            JsonParameter('json_param_2', 'object', required=False))

        definition_1 = RecipeDefinition(interface_1)
        definition_1.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_1.add_job_node('B', 'job_type_2', '2.0', 1)
        definition_1.add_job_node('C', 'job_type_3', '1.0', 2)
        definition_1.add_recipe_node('D', 'recipe_type_1', 1)
        definition_1.add_job_node('E', 'job_type_4', '1.0', 1)
        definition_1.add_dependency('A', 'B')
        definition_1.add_dependency('A', 'C')
        definition_1.add_dependency('B', 'E')
        definition_1.add_dependency('C', 'D')
        definition_1.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_1.add_dependency_input_connection('B', 'b_input_1', 'A',
                                                     'a_output_1')
        definition_1.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_1.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_1.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')

        definition_2 = RecipeDefinition(interface_2)
        # Nodes B and E are deleted
        definition_2.add_job_node('A', 'job_type_1', '1.0', 1)
        definition_2.add_job_node('C', 'job_type_3', '2.1',
                                  1)  # Change to job type version and revision
        definition_2.add_recipe_node('D', 'recipe_type_1', 1)
        definition_2.add_recipe_node('F', 'recipe_type_2', 5)  # New node
        definition_2.add_dependency('A', 'C')
        definition_2.add_dependency('C', 'D')
        definition_2.add_dependency('D', 'F')
        definition_2.add_recipe_input_connection('A', 'input_1',
                                                 'file_param_1')
        definition_2.add_dependency_input_connection('C', 'c_input_1', 'A',
                                                     'a_output_2')
        definition_2.add_dependency_input_connection('D', 'd_input_1', 'C',
                                                     'c_output_1')
        definition_2.add_recipe_input_connection('D', 'd_input_2',
                                                 'json_param_1')
        definition_2.add_recipe_input_connection('F', 'f_input_1',
                                                 'json_param_2')

        diff = RecipeDiff(definition_1, definition_2)

        # Non-breaking recipe input changes so recipe can be reprocessed
        self.assertTrue(diff.can_be_reprocessed)
        self.assertListEqual(diff.reasons, [])
        # Check each node for correct fields
        node_a = diff.graph['A']
        self.assertEqual(node_a.status, NodeDiff.UNCHANGED)
        self.assertFalse(node_a.reprocess_new_node)
        self.assertListEqual(node_a.changes, [])
        node_b = diff.graph['B']
        self.assertEqual(node_b.status, NodeDiff.DELETED)
        self.assertFalse(node_b.reprocess_new_node)
        self.assertListEqual(node_b.changes, [])
        node_c = diff.graph['C']
        self.assertEqual(node_c.status, NodeDiff.CHANGED)
        self.assertTrue(node_c.reprocess_new_node)
        self.assertEqual(len(node_c.changes), 2)
        self.assertEqual(node_c.changes[0].name, 'JOB_TYPE_VERSION_CHANGE')
        self.assertEqual(node_c.changes[1].name, 'JOB_TYPE_REVISION_CHANGE')
        node_d = diff.graph['D']
        self.assertEqual(node_d.status, NodeDiff.CHANGED)
        self.assertTrue(node_d.reprocess_new_node)
        self.assertEqual(len(node_d.changes), 1)
        self.assertEqual(node_d.changes[0].name, 'PARENT_CHANGED')
        node_e = diff.graph['E']
        self.assertEqual(node_e.status, NodeDiff.DELETED)
        self.assertFalse(node_e.reprocess_new_node)
        self.assertListEqual(node_e.changes, [])
        node_f = diff.graph['F']
        self.assertEqual(node_f.status, NodeDiff.NEW)
        self.assertTrue(node_f.reprocess_new_node)
        self.assertListEqual(node_f.changes, [])
        # Check nodes to copy, supersede, and unpublish
        self.assertSetEqual(set(diff.get_nodes_to_copy().keys()), {'A'})
        self.assertSetEqual(set(diff.get_nodes_to_supersede().keys()),
                            {'B', 'C', 'D', 'E'})
        self.assertSetEqual(set(diff.get_nodes_to_unpublish().keys()),
                            {'B', 'E'})