[ovs-dev] [RFC PATCH 07/10] python/ovs-ofparse: add datapath logic processing

Adrian Moreno amorenoz at redhat.com
Mon Nov 22 15:01:54 UTC 2021


Create a generic Datapath Tree procesing engine.
A FlowTree arranges datapath flows based on their recirc_id.

The logic is made generic as long as the elements in the tree is derived
from TreElem

Filtering is not performed on normal processing time. Instead the
filtering is performed after the tree is built in order to include all
the subtrees that has a subflow that matches the filter

Signed-off-by: Adrian Moreno <amorenoz at redhat.com>
---
 python/automake.mk                 |   4 +-
 python/ovs/ovs_ofparse/datapath.py | 132 ++++++++++++++++++++++++++++-
 python/ovs/ovs_ofparse/dp_tree.py  | 128 ++++++++++++++++++++++++++++
 3 files changed, 262 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/ovs_ofparse/dp_tree.py

diff --git a/python/automake.mk b/python/automake.mk
index 17ff0da92..843570420 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -61,7 +61,9 @@ ovs_pyfiles = \
 	python/ovs/ovs_ofparse/format.py \
 	python/ovs/ovs_ofparse/console.py \
 	python/ovs/ovs_ofparse/etc/ovs-ofparse.conf \
-	python/ovs/ovs_ofparse/ofp_logic.py
+	python/ovs/ovs_ofparse/ofp_logic.py \
+	python/ovs/ovs_ofparse/dp_tree.py
+
 
 
 
diff --git a/python/ovs/ovs_ofparse/datapath.py b/python/ovs/ovs_ofparse/datapath.py
index fd64345ce..19992982e 100644
--- a/python/ovs/ovs_ofparse/datapath.py
+++ b/python/ovs/ovs_ofparse/datapath.py
@@ -1,11 +1,24 @@
 import click
-from ovs.ovs_ofparse.main import maincli
+from rich.tree import Tree
+from rich.text import Text
+from rich.style import Style
 
+from ovs.ovs_ofparse.main import maincli
 from ovs.flows.odp import ODPFlowFactory
 from ovs.ovs_ofparse.process import (
+    FlowProcessor,
     JSONProcessor,
     ConsoleProcessor,
 )
+from ovs.ovs_ofparse.console import (
+    ConsoleFormatter,
+    ConsoleBuffer,
+    print_context,
+    hash_pallete,
+    heat_pallete,
+    file_header,
+)
+from ovs.ovs_ofparse.dp_tree import FlowTree, FlowElem
 
 factory = ODPFlowFactory()
 
@@ -43,3 +56,120 @@ def console(opts, heat_map):
     )
     proc.process()
     proc.print()
+
+
+ at datapath.command()
+ at click.option(
+    "-h",
+    "--heat-map",
+    is_flag=True,
+    default=False,
+    show_default=True,
+    help="Create heat-map with packet and byte counters",
+)
+ at click.pass_obj
+def logic(opts, heat_map):
+    """Print the flows in a tree based on the 'recirc_id'"""
+    processor = ConsoleTreeProcessor(opts, factory)
+    processor.process()
+    processor.print(heat_map)
+
+
+class ConsoleTreeProcessor(FlowProcessor):
+    def __init__(self, opts, factory):
+        super().__init__(opts, factory)
+        self.data = dict()
+        self.ofconsole = ConsoleFormatter(self.opts)
+
+        # Generate a color pallete for cookies
+        recirc_style_gen = hash_pallete(
+            hue=[x / 50 for x in range(0, 50)], saturation=[0.7], value=[0.8]
+        )
+
+        style = self.ofconsole.style
+        style.set_default_value_style(Style(color="bright_black"))
+        style.set_key_style("output", Style(color="green"))
+        style.set_value_style("output", Style(color="green"))
+        style.set_value_style("recirc", recirc_style_gen)
+        style.set_value_style("recirc_id", recirc_style_gen)
+
+    def start_file(self, name, filename):
+        self.tree = ConsoleTree(self.ofconsole, self.opts)
+
+    def process_flow(self, flow, name):
+        self.tree.add(flow)
+
+    def process(self):
+        super().process(False)
+
+    def stop_file(self, name, filename):
+        self.data[name] = self.tree
+
+    def print(self, heat_map):
+        for name, tree in self.data.items():
+            self.ofconsole.console.print("\n")
+            self.ofconsole.console.print(file_header(name))
+            tree.build()
+            if self.opts.get("filter"):
+                tree.filter(self.opts.get("filter"))
+            tree.print(heat_map)
+
+
+class ConsoleTree(FlowTree):
+    """ConsoleTree is a FlowTree that prints the tree in a console
+
+    Args:
+        console (ConsoleFormatter): console to use for printing
+        opts (dict): Options dictionary
+    """
+
+    class ConsoleElem(FlowElem):
+        def __init__(self, flow=None, is_root=False):
+            self.tree = None
+            super(ConsoleTree.ConsoleElem, self).__init__(
+                flow, is_root=is_root
+            )
+
+    def __init__(self, console, opts):
+        self.console = console
+        self.root = self.ConsoleElem(is_root=True)
+        self.opts = opts
+        super(ConsoleTree, self).__init__()
+
+    def _new_elem(self, flow, _):
+        """Override _new_elem to provide ConsoleElems"""
+        return self.ConsoleElem(flow)
+
+    def _append_to_tree(self, elem, parent):
+        """Callback to be used for FlowTree._build
+        Appends the flow to the rich.Tree
+        """
+        if elem.is_root:
+            elem.tree = Tree("Datapath Flows (logical)")
+            return
+
+        buf = ConsoleBuffer(Text())
+        highlighted = None
+        if self.opts.get("highlight"):
+            result = self.opts.get("highlight").evaluate(elem.flow)
+            if result:
+                highlighted = result.kv
+        self.console.format_flow(buf, elem.flow, highlighted)
+        elem.tree = parent.tree.add(buf.text)
+
+    def print(self, heat=False):
+        """Print the Flow Tree
+        Args:
+            heat (bool): Optional; whether heat-map style shall be applied
+        """
+        if heat:
+            for field in ["packets", "bytes"]:
+                values = []
+                for flow_list in self._flows.values():
+                    values.extend([f.info.get(field) or 0 for f in flow_list])
+                self.console.style.set_value_style(
+                    field, heat_pallete(min(values), max(values))
+                )
+        self.traverse(self._append_to_tree)
+        with print_context(self.console.console, self.opts):
+            self.console.console.print(self.root.tree)
diff --git a/python/ovs/ovs_ofparse/dp_tree.py b/python/ovs/ovs_ofparse/dp_tree.py
new file mode 100644
index 000000000..2db491c86
--- /dev/null
+++ b/python/ovs/ovs_ofparse/dp_tree.py
@@ -0,0 +1,128 @@
+class TreeElem:
+    """Element in the tree
+    Args:
+        children (list[TreeElem]): Optional, list of children
+        is_root (bool): Optional; whether this is the root elemen
+    """
+
+    def __init__(self, children=None, is_root=False):
+        self.children = children or list()
+        self.is_root = is_root
+
+    def append(self, child):
+        self.children.append(child)
+
+
+class FlowElem(TreeElem):
+    """An element that contains a flow
+    Args:
+        flow (Flow): The flow that this element contains
+        children (list[TreeElem]): Optional, list of children
+        is_root (bool): Optional; whether this is the root elemen
+    """
+
+    def __init__(self, flow, children=None, is_root=None):
+        self.flow = flow
+        super(FlowElem, self).__init__(children, is_root)
+
+    def evaluate_any(self, filter):
+        """Evaluate the filter on the element and all its children
+        Args:
+            filter(OFFilter): the filter to evaluate
+
+        Returns:
+            True if ANY of the flows (including self and children) evaluates
+            true
+        """
+        if filter.evaluate(self.flow):
+            return True
+
+        return any([child.evaluate_any(filter) for child in self.children])
+
+
+class FlowTree:
+    """
+    A Flow tree is a a class that processes datapath flows into a tree based
+    on recirculation ids
+
+    Args:
+        flows (list[ODPFlow]: Optional, initial list of flows
+    """
+
+    root = None
+
+    def __init__(self, flows=None):
+        self._flows = flows or {}  # flow list indexed by recirc_id
+        self.root = self.root or TreeElem(is_root=True)
+
+    def add(self, flow):
+        """Add a flow"""
+        rid = flow.match.get("recirc_id") or 0
+        if not self._flows.get(rid):
+            self._flows[rid] = list()
+        self._flows[rid].append(flow)
+
+    def build(self):
+        """Build the flow tree."""
+        self._build(self.root, 0)
+
+    def traverse(self, callback):
+        """Traverses the tree calling callback on each element
+        callback: callable that accepts two TreeElem, the current one being
+            traversed and its parent
+            func callback(elem parent):
+                ...
+            Note parent can be None if it's the first element
+        """
+        self._traverse(self.root, None, callback)
+
+    def _traverse(self, elem, parent, callback):
+        callback(elem, parent)
+
+        for child in elem.children:
+            self._traverse(child, elem, callback)
+
+    def _build(self, parent, recirc):
+        """
+        Build the subtree starting at a specific recirc_id. Recursive function.
+        Args:
+            parent (TreeElem): parent of the (sub)tree
+            recirc(int): the recirc_id subtree to build
+        """
+        flows = self._flows.get(recirc)
+        if not flows:
+            return
+        for flow in sorted(
+            flows, key=lambda x: x.info.get("packets") or 0, reverse=True
+        ):
+            next_recirc = next(
+                (kv.value for kv in flow.actions_kv if kv.key == "recirc"),
+                None,
+            )
+
+            elem = self._new_elem(flow, parent)
+            parent.append(elem)
+
+            if next_recirc:
+                self._build(elem, next_recirc)
+
+    def _new_elem(self, flow, parent):
+        """Creates a new TreeElem
+        Default implementation is to create a FlowElem. Derived classes can
+        override this method to return any derived TreeElem
+        """
+        return FlowElem(flow)
+
+    def filter(self, filter):
+        """Removes the first level subtrees if none of its sub-elements match
+        the filter
+        Args:
+            filter(OFFilter): filter to apply
+        """
+        to_remove = list()
+        for l0 in self.root.children:
+            passes = l0.evaluate_any(filter)
+            if not passes:
+                to_remove.append(l0)
+        for elem in to_remove:
+            self.root.children.remove(elem)
-- 
2.31.1



More information about the dev mailing list