[ovs-dev] [RFC PATCH 06/10] python/ovs-ofparse: add openflow logic processing

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


Create a new FlowProcessor that is able to squash "logical" flows
together. A logical flow combines flows that have:
- Same set of matches (regardless of their value)
- Same set of actions regardless of their value except for "output" and
  "resubmit" for which values do matter
- Same cookie if "--cookie | -c" option is provided

All flows that have the same "logical" representation are squashed
together. Optionally, if "--show-flows | -s" is given, the flows that
comprise the logical flow are also printed.

Signed-off-by: Adrian Moreno <amorenoz at redhat.com>
---
 python/automake.mk                  |   3 +-
 python/ovs/ovs_ofparse/ofp_logic.py | 210 ++++++++++++++++++++++++++++
 python/ovs/ovs_ofparse/openflow.py  |  45 ++++++
 3 files changed, 257 insertions(+), 1 deletion(-)
 create mode 100644 python/ovs/ovs_ofparse/ofp_logic.py

diff --git a/python/automake.mk b/python/automake.mk
index a951c0fca..17ff0da92 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -60,7 +60,8 @@ ovs_pyfiles = \
 	python/ovs/ovs_ofparse/process.py \
 	python/ovs/ovs_ofparse/format.py \
 	python/ovs/ovs_ofparse/console.py \
-	python/ovs/ovs_ofparse/etc/ovs-ofparse.conf
+	python/ovs/ovs_ofparse/etc/ovs-ofparse.conf \
+	python/ovs/ovs_ofparse/ofp_logic.py
 
 
 
diff --git a/python/ovs/ovs_ofparse/ofp_logic.py b/python/ovs/ovs_ofparse/ofp_logic.py
new file mode 100644
index 000000000..a4b488253
--- /dev/null
+++ b/python/ovs/ovs_ofparse/ofp_logic.py
@@ -0,0 +1,210 @@
+from rich.tree import Tree
+from rich.text import Text
+from rich.console import Console
+
+from ovs.ovs_ofparse.process import FlowProcessor
+from ovs.ovs_ofparse.console import (
+    ConsoleFormatter,
+    ConsoleBuffer,
+    hash_pallete,
+    file_header,
+    heat_pallete,
+    print_context,
+)
+
+# Try to make it easy to spot same cookies by printing them in different
+# colors
+cookie_style_gen = hash_pallete(
+    hue=[x / 10 for x in range(0, 10)],
+    saturation=[0.5],
+    value=[0.5 + x / 10 * (0.85 - 0.5) for x in range(0, 10)],
+)
+
+
+class LFlow:
+    """A Logical Flow represents the scheleton of a flow
+
+    Attributes:
+        flow (OFPFlow): The flow
+        match_action_keys(list): Optional; list of action keys that are
+            mathched exactly (not just the key but the value also)
+        match_cookie (bool): Optional; if cookies are part of the logical
+            flow
+    """
+
+    def __init__(self, flow, match_action_keys=[], match_cookie=False):
+        self.cookie = flow.info.get("cookie") or 0 if match_cookie else None
+        self.priority = flow.match.get("priority") or 0
+        self.match_keys = tuple([kv.key for kv in flow.match_kv])
+
+        self.action_keys = tuple(
+            [
+                kv.key
+                for kv in flow.actions_kv
+                if kv.key not in match_action_keys
+            ]
+        )
+        self.match_action_kvs = [
+            kv for kv in flow.actions_kv if kv.key in match_action_keys
+        ]
+
+    def __eq__(self, other):
+        return (
+            (self.cookie == other.cookie if self.cookie else True)
+            and self.priority == other.priority
+            and self.action_keys == other.action_keys
+            and self.equal_match_action_kvs(other)
+            and self.match_keys == other.match_keys
+        )
+
+    def equal_match_action_kvs(self, other):
+        """
+        Compares the logical flow's match action key-values with the other's
+        Args:
+            other (LFlow): The other LFlow to compare against
+
+        Returns true if both LFlow have the same action k-v
+        """
+        if len(other.match_action_kvs) != len(self.match_action_kvs):
+            return False
+
+        for kv in self.match_action_kvs:
+            found = False
+            for other_kv in other.match_action_kvs:
+                if self.match_kv(kv, other_kv):
+                    found = True
+                    break
+            if not found:
+                return False
+        return True
+
+    def match_kv(self, one, other):
+        """Compares a KeyValue
+        Args:
+            one, other (KeyValue): The objects to compare
+
+        Returns true if both KeyValue objects have the same key and value
+        """
+        return one.key == other.key and one.value == other.value
+
+    def __hash__(self):
+        hash_data = [
+            self.cookie,
+            self.priority,
+            self.action_keys,
+            tuple((kv.key, str(kv.value)) for kv in self.match_action_kvs),
+            self.match_keys,
+        ]
+        if self.cookie:
+            hash_data.append(self.cookie)
+        return tuple(hash_data).__hash__()
+
+    def format(self, buf, formatter):
+        """Format the Logical Flow into a Buffer"""
+        if self.cookie:
+            buf.append_extra(
+                "cookie={} ".format(hex(self.cookie)).ljust(18),
+                style=cookie_style_gen(str(self.cookie)),
+            )
+
+        buf.append_extra(
+            "priority={} ".format(self.priority), style="steel_blue"
+        )
+        buf.append_extra(",".join(self.match_keys), style="steel_blue")
+        buf.append_extra("  --->  ", style="bold magenta")
+        buf.append_extra(",".join(self.action_keys), style="steel_blue")
+
+        if len(self.match_action_kvs) > 0:
+            buf.append_extra(" ", style=None)
+
+        for kv in self.match_action_kvs:
+            formatter.format_kv(buf, kv, formatter.style)
+            buf.append_extra(",", style=None)
+
+
+class LogicFlowProcessor(FlowProcessor):
+    def __init__(self, opts, factory, match_cookie):
+        super().__init__(opts, factory)
+        self.data = dict()
+        self.match_cookie = match_cookie
+
+    def start_file(self, name, filename):
+        self.tables = dict()
+
+    def stop_file(self, name, filename):
+        self.data[name] = self.tables
+
+    def process_flow(self, flow, name):
+        """Sort the flows by table and logical flow"""
+        table = flow.info.get("table") or 0
+        if not self.tables.get(table):
+            self.tables[table] = dict()
+
+        # Group flows by logical hash
+        lflow = LFlow(
+            flow,
+            match_action_keys=["output", "resubmit", "drop"],
+            match_cookie=self.match_cookie,
+        )
+
+        if not self.tables[table].get(lflow):
+            self.tables[table][lflow] = list()
+
+        self.tables[table][lflow].append(flow)
+
+    def print(self, show_flows, heat_map):
+        console = Console(
+            color_system=None if self.opts["style"] is None else "256"
+        )
+        formatter = ConsoleFormatter(opts=self.opts, console=console)
+        with print_context(console, self.opts):
+            for name, tables in self.data.items():
+                console.print("\n")
+                console.print(file_header(name))
+                tree = Tree("Ofproto Flows (logical)")
+
+                for table_num in sorted(tables.keys()):
+                    table = tables[table_num]
+                    table_tree = tree.add("** TABLE {} **".format(table_num))
+
+                    if heat_map:
+                        for field in ["n_packets", "n_bytes"]:
+                            values = []
+                            for flow_list in table.values():
+                                values.extend(
+                                    [f.info.get(field) or 0 for f in flow_list]
+                                )
+                            formatter.style.set_value_style(
+                                field, heat_pallete(min(values), max(values))
+                            )
+
+                    for lflow in sorted(
+                        table.keys(),
+                        key=(lambda x: x.priority),
+                        reverse=True,
+                    ):
+                        flows = table[lflow]
+
+                        buf = ConsoleBuffer(Text())
+
+                        lflow.format(buf, formatter)
+                        buf.append_extra(
+                            " ( x {} )".format(len(flows)),
+                            style="dark_olive_green3",
+                        )
+                        lflow_tree = table_tree.add(buf.text)
+
+                        if show_flows:
+                            for flow in flows:
+                                buf = ConsoleBuffer(Text())
+                                highlighted = None
+                                if self.opts.get("highlight"):
+                                    result = self.opts.get(
+                                        "highlight"
+                                    ).evaluate(flow)
+                                    if result:
+                                        highlighted = result.kv
+                                formatter.format_flow(buf, flow, highlighted)
+                                lflow_tree.add(buf.text)
+
+                console.print(tree)
diff --git a/python/ovs/ovs_ofparse/openflow.py b/python/ovs/ovs_ofparse/openflow.py
index 190f92bdb..766d3ad41 100644
--- a/python/ovs/ovs_ofparse/openflow.py
+++ b/python/ovs/ovs_ofparse/openflow.py
@@ -2,6 +2,7 @@ import click
 
 
 from ovs.flows.ofp import OFPFlowFactory
+from ovs.ovs_ofparse.ofp_logic import LogicFlowProcessor
 from ovs.ovs_ofparse.main import maincli
 from ovs.ovs_ofparse.process import (
     JSONProcessor,
@@ -45,3 +46,47 @@ def console(opts, heat_map):
     )
     proc.process()
     proc.print()
+
+
+ at openflow.command()
+ at click.option(
+    "-s",
+    "--show-flows",
+    is_flag=True,
+    default=False,
+    show_default=True,
+    help="Show the full flows under each logical flow",
+)
+ at click.option(
+    "-c",
+    "--cookie",
+    "cookie_flag",
+    is_flag=True,
+    default=False,
+    show_default=True,
+    help="Consider the cookie in the logical flow",
+)
+ at click.option(
+    "-h",
+    "--heat-map",
+    is_flag=True,
+    default=False,
+    show_default=True,
+    help="Create heat-map with packet and byte counters (when -s is used)",
+)
+ at click.pass_obj
+def logic(opts, show_flows, cookie_flag, heat_map):
+    """
+    Print the logical structure of the flows.
+
+    First, sorts the flows based on tables and priorities.
+    Then, deduplicates logically equivalent flows: these a flows that match
+    on the same set of fields (regardless of the values they match against),
+    have the same priority, and actions (regardless of action arguments,
+    except in the case of output and recirculate).
+    Optionally, the cookie can also be considered to be part of the logical
+    flow.
+    """
+    processor = LogicFlowProcessor(opts, factory, cookie_flag)
+    processor.process()
+    processor.print(show_flows, heat_map)
-- 
2.31.1



More information about the dev mailing list