Пример #1
0
    def parse_start_node(self, start_node):
        """
        The start node is the entry point for a workflow job, it indicates the
        first workflow node the workflow job must transition to.

        When a workflow is started, it automatically transitions to the
        node specified in the start.

        A workflow definition must have one start node.
        """
        # Theoretically this could cause conflicts, but it is very unlikely
        start_name = "start_node_" + str(uuid.uuid4())[:4]
        mapper = StartMapper(
            oozie_node=start_node,
            name=start_name,
            dag_name=self.workflow.dag_name,
            props=self.props,
            trigger_rule=TriggerRule.DUMMY,
        )

        p_node = ParsedActionNode(mapper)
        p_node.add_downstream_node_name(start_node.attrib["to"])

        mapper.on_parse_node()

        logging.info(f"Parsed {mapper.name} as Start Node.")
        self.workflow.nodes[start_name] = p_node
Пример #2
0
    def parse_join_node(self, join_node):
        """
        Join nodes wait for the corresponding beginning fork node paths to
        finish. As the parser we are assuming the Oozie workflow follows the
        schema perfectly.
        """
        mapper = DummyMapper(oozie_node=join_node,
                             name=join_node.attrib["name"],
                             dag_name=self.workflow.dag_name)

        p_node = ParsedActionNode(mapper)
        p_node.add_downstream_node_name(join_node.attrib["to"])

        mapper.on_parse_node()

        logging.info(f"Parsed {mapper.name} as Join Node.")
        self.workflow.nodes[join_node.attrib["name"]] = p_node
Пример #3
0
    def parse_join_node(self, join_node):
        """
        Join nodes wait for the corresponding beginning fork node paths to
        finish. As the parser we are assuming the Oozie workflow follows the
        schema perfectly.
        """
        map_class = self.control_map["join"]
        mapper = map_class(oozie_node=join_node, name=join_node.attrib["name"])

        p_node = ParsedActionNode(mapper)
        p_node.add_downstream_node_name(join_node.attrib["to"])

        mapper.on_parse_node()

        logging.info(f"Parsed {mapper.name} as Join Node.")
        self.workflow.nodes[join_node.attrib["name"]] = p_node
        self.workflow.dependencies.update(mapper.required_imports())
Пример #4
0
    def parse_action_node(self, action_node: ET.Element):
        """
        Action nodes are the mechanism by which a workflow triggers the
        execution of a computation/processing task.

        Action nodes are required to have an action-choice (map-reduce, etc.),
        ok, and error node in the xml.
        """
        # The 0th element of the node is the actual action tag.
        # In the form of 'action'
        action_operation_node = action_node[0]
        action_name = action_operation_node.tag

        if action_name not in self.action_map:
            action_name = "unknown"

        map_class = self.action_map[action_name]
        mapper = map_class(
            oozie_node=action_operation_node,
            name=action_node.attrib["name"],
            params=self.params,
            dag_name=self.workflow.dag_name,
            action_mapper=self.action_map,
            control_mapper=self.control_map,
            input_directory_path=self.workflow.input_directory_path,
            output_directory_path=self.workflow.output_directory_path,
        )

        p_node = ParsedActionNode(mapper)
        ok_node = action_node.find("ok")
        if ok_node is None:
            raise Exception("Missing ok node in {}".format(action_node))
        p_node.add_downstream_node_name(ok_node.attrib["to"])
        error_node = action_node.find("error")
        if error_node is None:
            raise Exception("Missing error node in {}".format(action_node))
        p_node.set_error_node_name(error_node.attrib["to"])

        mapper.on_parse_node()

        logging.info(
            f"Parsed {mapper.name} as Action Node of type {action_name}.")
        self.workflow.dependencies.update(mapper.required_imports())

        self.workflow.nodes[mapper.name] = p_node
Пример #5
0
    def parse_fork_node(self, root, fork_node):
        """
        Fork nodes need to be dummy operators with multiple parallel downstream
        tasks.

        This parses the fork node, the action nodes that it references and then
        the join node at the end.

        This will only parse well-formed xml-adhering workflows where all paths
        end at the join node.
        """
        fork_name = fork_node.attrib["name"]
        mapper = DummyMapper(oozie_node=fork_node,
                             name=fork_name,
                             dag_name=self.workflow.dag_name)
        p_node = ParsedActionNode(mapper)

        mapper.on_parse_node()

        logging.info(f"Parsed {mapper.name} as Fork Node.")
        paths = []
        for node in fork_node:
            if "path" in node.tag:
                # Parse all the downstream tasks that can run in parallel.
                curr_name = node.attrib["start"]
                paths.append(xml_utils.find_node_by_name(root, curr_name))

        self.workflow.nodes[fork_name] = p_node

        for path in paths:
            p_node.add_downstream_node_name(path.attrib["name"])
            logging.info(
                f"Added {mapper.name}'s downstream: {path.attrib['name']}")

            # Theoretically these will all be action nodes, however I don't
            # think that is guaranteed.
            # The end of the execution path has not been reached
            self.parse_node(root, path)
            if path.attrib["name"] not in self.workflow.nodes:
                root.remove(path)
Пример #6
0
    def parse_decision_node(self, decision_node):
        """
        A decision node enables a workflow to make a selection on the execution
        path to follow.

        The behavior of a decision node can be seen as a switch-case statement.

        A decision node consists of a list of predicates-transition pairs plus
        a default transition. Predicates are evaluated in order or appearance
        until one of them evaluates to true and the corresponding transition is
        taken. If none of the predicates evaluates to true the default
        transition is taken.

        example oozie wf decision node:

        <decision name="[NODE-NAME]">
            <switch>
                <case to="[NODE_NAME]">[PREDICATE]</case>
                ...
                <case to="[NODE_NAME]">[PREDICATE]</case>
                <default to="[NODE_NAME]"/>
            </switch>
        </decision>
        """
        mapper = DecisionMapper(
            oozie_node=decision_node,
            name=decision_node.attrib["name"],
            dag_name=self.workflow.dag_name,
            props=self.props,
        )

        p_node = ParsedActionNode(mapper)
        for cases in decision_node[0]:
            p_node.add_downstream_node_name(cases.attrib["to"])

        mapper.on_parse_node()

        logging.info(f"Parsed {mapper.name} as Decision Node.")
        self.workflow.nodes[decision_node.attrib["name"]] = p_node
Пример #7
0
    def parse_start_node(self, start_node):
        """
        The start node is the entry point for a workflow job, it indicates the
        first workflow node the workflow job must transition to.

        When a workflow is started, it automatically transitions to the
        node specified in the start.

        A workflow definition must have one start node.
        """
        map_class = self.control_map["start"]
        # Theoretically this could cause conflicts, but it is very unlikely
        start_name = "start_node_" + str(uuid.uuid4())[:4]
        mapper = map_class(oozie_node=start_node, name=start_name)

        p_node = ParsedActionNode(mapper)
        p_node.add_downstream_node_name(start_node.attrib["to"])

        mapper.on_parse_node()

        logging.info(f"Parsed {mapper.name} as Start Node.")
        self.workflow.nodes[start_name] = p_node
        self.workflow.dependencies.update(mapper.required_imports())