diff mbox series

[ovs-dev,RFC,08/10] python: ovs: flowviz: Add Openflow cookie format.

Message ID 20231201191449.2386134-9-amorenoz@redhat.com
State RFC
Delegated to: Simon Horman
Headers show
Series Add flow visualization utility | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/intel-ovs-compilation fail test: fail
ovsrobot/github-robot-_Build_and_Test fail github build: failed

Commit Message

Adrian Moreno Dec. 1, 2023, 7:14 p.m. UTC
When anaylizing OVN issues, it might be useful to see what OpenFlow
flows were generated from each logical flow. In order to make it simpler
to visualize this, add a cookie format that simply sorts the flows first
by cookie, then by table.

Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 python/ovs/flowviz/ofp/cli.py   | 57 ++++++++++++++++++++++++++++-
 python/ovs/flowviz/ofp/logic.py | 63 ++++++++++++++++++++++++++++++++-
 2 files changed, 118 insertions(+), 2 deletions(-)

Comments

Eelco Chaudron Jan. 30, 2024, 3:55 p.m. UTC | #1
On 1 Dec 2023, at 20:14, Adrian Moreno wrote:

> When anaylizing OVN issues, it might be useful to see what OpenFlow
> flows were generated from each logical flow. In order to make it simpler
> to visualize this, add a cookie format that simply sorts the flows first
> by cookie, then by table.

The code looks good to me, however, did not try with ovs-detrace. One comment on code that needs to move to the previous patch.

Maybe add an example in the commit message.

Acked-by: Eelco Chaudron <echaudro@redhat.com>

> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
> ---
>  python/ovs/flowviz/ofp/cli.py   | 57 ++++++++++++++++++++++++++++-
>  python/ovs/flowviz/ofp/logic.py | 63 ++++++++++++++++++++++++++++++++-
>  2 files changed, 118 insertions(+), 2 deletions(-)
>
> diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py
> index 6b1435ea1..9658d00d3 100644
> --- a/python/ovs/flowviz/ofp/cli.py
> +++ b/python/ovs/flowviz/ofp/cli.py
> @@ -18,7 +18,7 @@ import click
>
>  from ovs.flowviz.main import maincli
>  from ovs.flowviz.ofp.html import HTMLProcessor
> -from ovs.flowviz.ofp.logic import LogicFlowProcessor
> +from ovs.flowviz.ofp.logic import CookieProcessor, LogicFlowProcessor
>  from ovs.flowviz.process import (
>      OpenFlowFactory,
>      JSONProcessor,
> @@ -182,6 +182,61 @@ def logic(
>      processor.print(show_flows)
>
>
> +@openflow.command()
> +@click.option(
> +    "-d",
> +    "--ovn-detrace",
> +    "ovn_detrace_flag",
> +    is_flag=True,
> +    show_default=True,
> +    help="Use ovn-detrace to extract cookie information",
> +)
> +@click.option(
> +    "--ovn-detrace-path",
> +    default="/usr/bin",
> +    type=click.Path(),
> +    help="Use an alternative path to where ovn_detrace.py is located. "
> +    "Instead of using this option you can just set PYTHONPATH accordingly",
> +    show_default=True,
> +    callback=ovn_detrace_callback,
> +)
> +@click.option(
> +    "--ovnnb-db",
> +    default=os.getenv("OVN_NB_DB") or "unix:/var/run/ovn/ovnnb_db.sock",
> +    help="Specify the OVN NB database string (implies -d). "
> +    "If the OVN_NB_DB environment variable is set, it's used as default. "
> +    "Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
> +    callback=ovn_detrace_callback,
> +)
> +@click.option(
> +    "--ovnsb-db",
> +    default=os.getenv("OVN_SB_DB") or "unix:/var/run/ovn/ovnsb_db.sock",
> +    help="Specify the OVN NB database string (implies -d). "
> +    "If the OVN_NB_DB environment variable is set, it's used as default. "
> +    "Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
> +    callback=ovn_detrace_callback,
> +)
> +@click.option(
> +    "-o",
> +    "--ovn-filter",
> +    help="Specify a filter to be run on ovn-detrace information (implied -d). "
> +    "Format: python regular expression "
> +    "(see https://docs.python.org/3/library/re.html)",
> +    callback=ovn_detrace_callback,
> +)
> +@click.pass_obj
> +def cookie(
> +    opts, ovn_detrace_flag, ovn_detrace_path, ovnnb_db, ovnsb_db, ovn_filter
> +):
> +    """Print the flow tables sorted by cookie."""
> +    if ovn_detrace_flag:
> +        opts["ovn_detrace_flag"] = True
> +
> +    processor = CookieProcessor(opts)
> +    processor.process()
> +    processor.print()
> +
> +
>  @openflow.command()
>  @click.pass_obj
>  def html(opts):
> diff --git a/python/ovs/flowviz/ofp/logic.py b/python/ovs/flowviz/ofp/logic.py
> index cb4568cf1..9d244d137 100644
> --- a/python/ovs/flowviz/ofp/logic.py
> +++ b/python/ovs/flowviz/ofp/logic.py
> @@ -200,7 +200,7 @@ class LogicFlowProcessor(OpenFlowFactory, FileProcessor):
>                  if len(self.heat_map) > 0 and len(table.values()) > 0:
>                      for i, field in enumerate(self.heat_map):
>                          (min_val, max_val) = self.min_max[name][i]
> -                        self.console.style.set_value_style(
> +                        formatter.style.set_value_style(

This probably needs to move to patch 7.

>                              field, heat_pallete(min_val, max_val)
>                          )
>
> @@ -301,3 +301,64 @@ cookie_style_gen = hash_pallete(
>      saturation=[0.5],
>      value=[0.5 + x / 10 * (0.85 - 0.5) for x in range(0, 10)],
>  )
> +
> +
> +class CookieProcessor(OpenFlowFactory, FileProcessor):
> +    """Processor that sorts flows into cookies and tables."""
> +
> +    def __init__(self, opts):
> +        super().__init__(opts)
> +        self.data = dict()
> +        self.ovn_detrace = (
> +            OVNDetrace(opts) if opts.get("ovn_detrace_flag") else None
> +        )
> +
> +    def start_file(self, name, filename):
> +        self.cookies = dict()
> +
> +    def stop_file(self, name, filename):
> +        self.data[name] = self.cookies
> +
> +    def process_flow(self, flow, name):
> +        """Sort the flows by table and logical flow."""
> +        cookie = flow.info.get("cookie") or 0
> +        if not self.cookies.get(cookie):
> +            self.cookies[cookie] = dict()
> +
> +        table = flow.info.get("table") or 0
> +        if not self.cookies[cookie].get(table):
> +            self.cookies[cookie][table] = list()
> +        self.cookies[cookie][table].append(flow)
> +
> +    def print(self):
> +        ofconsole = ConsoleFormatter(opts=self.opts)
> +        console = ofconsole.console
> +        for name, cookies in self.data.items():
> +            console.print("\n")
> +            console.print(file_header(name))
> +            tree = Tree("Ofproto Cookie Tree")
> +
> +            for cookie, tables in cookies.items():
> +                ovn_info = None
> +                if self.ovn_detrace:
> +                    ovn_info = self.ovn_detrace.get_ovn_info(cookie)
> +                    if self.opts.get("ovn_filter"):
> +                        ovn_regexp = re.compile(self.opts.get("ovn_filter"))
> +                        if not ovn_regexp.search(ovn_info):
> +                            continue
> +
> +                cookie_tree = tree.add("** Cookie {} **".format(hex(cookie)))
> +                if ovn_info:
> +                    ovn = cookie_tree.add("OVN Info")
> +                    for part in ovn_info.split("\n"):
> +                        if part.strip():
> +                            ovn.add(part.strip())
> +
> +                tables_tree = cookie_tree.add("Tables")
> +                for table, flows in tables.items():
> +                    table_tree = tables_tree.add("* Table {} * ".format(table))
> +                    for flow in flows:
> +                        buf = ConsoleBuffer(Text())
> +                        ofconsole.format_flow(buf, flow)
> +                        table_tree.add(buf.text)
> +            console.print(tree)
> -- 
> 2.43.0
>
> _______________________________________________
> dev mailing list
> dev@openvswitch.org
> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
Adrian Moreno Feb. 2, 2024, 10:53 a.m. UTC | #2
On 1/30/24 16:55, Eelco Chaudron wrote:
> On 1 Dec 2023, at 20:14, Adrian Moreno wrote:
> 
>> When anaylizing OVN issues, it might be useful to see what OpenFlow
>> flows were generated from each logical flow. In order to make it simpler
>> to visualize this, add a cookie format that simply sorts the flows first
>> by cookie, then by table.
> 
> The code looks good to me, however, did not try with ovs-detrace. One comment on code that needs to move to the previous patch.
>  > Maybe add an example in the commit message.

Sure.

> 
> Acked-by: Eelco Chaudron <echaudro@redhat.com>
> 
>> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
>> ---
>>   python/ovs/flowviz/ofp/cli.py   | 57 ++++++++++++++++++++++++++++-
>>   python/ovs/flowviz/ofp/logic.py | 63 ++++++++++++++++++++++++++++++++-
>>   2 files changed, 118 insertions(+), 2 deletions(-)
>>
>> diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py
>> index 6b1435ea1..9658d00d3 100644
>> --- a/python/ovs/flowviz/ofp/cli.py
>> +++ b/python/ovs/flowviz/ofp/cli.py
>> @@ -18,7 +18,7 @@ import click
>>
>>   from ovs.flowviz.main import maincli
>>   from ovs.flowviz.ofp.html import HTMLProcessor
>> -from ovs.flowviz.ofp.logic import LogicFlowProcessor
>> +from ovs.flowviz.ofp.logic import CookieProcessor, LogicFlowProcessor
>>   from ovs.flowviz.process import (
>>       OpenFlowFactory,
>>       JSONProcessor,
>> @@ -182,6 +182,61 @@ def logic(
>>       processor.print(show_flows)
>>
>>
>> +@openflow.command()
>> +@click.option(
>> +    "-d",
>> +    "--ovn-detrace",
>> +    "ovn_detrace_flag",
>> +    is_flag=True,
>> +    show_default=True,
>> +    help="Use ovn-detrace to extract cookie information",
>> +)
>> +@click.option(
>> +    "--ovn-detrace-path",
>> +    default="/usr/bin",
>> +    type=click.Path(),
>> +    help="Use an alternative path to where ovn_detrace.py is located. "
>> +    "Instead of using this option you can just set PYTHONPATH accordingly",
>> +    show_default=True,
>> +    callback=ovn_detrace_callback,
>> +)
>> +@click.option(
>> +    "--ovnnb-db",
>> +    default=os.getenv("OVN_NB_DB") or "unix:/var/run/ovn/ovnnb_db.sock",
>> +    help="Specify the OVN NB database string (implies -d). "
>> +    "If the OVN_NB_DB environment variable is set, it's used as default. "
>> +    "Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
>> +    callback=ovn_detrace_callback,
>> +)
>> +@click.option(
>> +    "--ovnsb-db",
>> +    default=os.getenv("OVN_SB_DB") or "unix:/var/run/ovn/ovnsb_db.sock",
>> +    help="Specify the OVN NB database string (implies -d). "
>> +    "If the OVN_NB_DB environment variable is set, it's used as default. "
>> +    "Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
>> +    callback=ovn_detrace_callback,
>> +)
>> +@click.option(
>> +    "-o",
>> +    "--ovn-filter",
>> +    help="Specify a filter to be run on ovn-detrace information (implied -d). "
>> +    "Format: python regular expression "
>> +    "(see https://docs.python.org/3/library/re.html)",
>> +    callback=ovn_detrace_callback,
>> +)
>> +@click.pass_obj
>> +def cookie(
>> +    opts, ovn_detrace_flag, ovn_detrace_path, ovnnb_db, ovnsb_db, ovn_filter
>> +):
>> +    """Print the flow tables sorted by cookie."""
>> +    if ovn_detrace_flag:
>> +        opts["ovn_detrace_flag"] = True
>> +
>> +    processor = CookieProcessor(opts)
>> +    processor.process()
>> +    processor.print()
>> +
>> +
>>   @openflow.command()
>>   @click.pass_obj
>>   def html(opts):
>> diff --git a/python/ovs/flowviz/ofp/logic.py b/python/ovs/flowviz/ofp/logic.py
>> index cb4568cf1..9d244d137 100644
>> --- a/python/ovs/flowviz/ofp/logic.py
>> +++ b/python/ovs/flowviz/ofp/logic.py
>> @@ -200,7 +200,7 @@ class LogicFlowProcessor(OpenFlowFactory, FileProcessor):
>>                   if len(self.heat_map) > 0 and len(table.values()) > 0:
>>                       for i, field in enumerate(self.heat_map):
>>                           (min_val, max_val) = self.min_max[name][i]
>> -                        self.console.style.set_value_style(
>> +                        formatter.style.set_value_style(
> 
> This probably needs to move to patch 7.
> 

Yep!

>>                               field, heat_pallete(min_val, max_val)
>>                           )
>>
>> @@ -301,3 +301,64 @@ cookie_style_gen = hash_pallete(
>>       saturation=[0.5],
>>       value=[0.5 + x / 10 * (0.85 - 0.5) for x in range(0, 10)],
>>   )
>> +
>> +
>> +class CookieProcessor(OpenFlowFactory, FileProcessor):
>> +    """Processor that sorts flows into cookies and tables."""
>> +
>> +    def __init__(self, opts):
>> +        super().__init__(opts)
>> +        self.data = dict()
>> +        self.ovn_detrace = (
>> +            OVNDetrace(opts) if opts.get("ovn_detrace_flag") else None
>> +        )
>> +
>> +    def start_file(self, name, filename):
>> +        self.cookies = dict()
>> +
>> +    def stop_file(self, name, filename):
>> +        self.data[name] = self.cookies
>> +
>> +    def process_flow(self, flow, name):
>> +        """Sort the flows by table and logical flow."""
>> +        cookie = flow.info.get("cookie") or 0
>> +        if not self.cookies.get(cookie):
>> +            self.cookies[cookie] = dict()
>> +
>> +        table = flow.info.get("table") or 0
>> +        if not self.cookies[cookie].get(table):
>> +            self.cookies[cookie][table] = list()
>> +        self.cookies[cookie][table].append(flow)
>> +
>> +    def print(self):
>> +        ofconsole = ConsoleFormatter(opts=self.opts)
>> +        console = ofconsole.console
>> +        for name, cookies in self.data.items():
>> +            console.print("\n")
>> +            console.print(file_header(name))
>> +            tree = Tree("Ofproto Cookie Tree")
>> +
>> +            for cookie, tables in cookies.items():
>> +                ovn_info = None
>> +                if self.ovn_detrace:
>> +                    ovn_info = self.ovn_detrace.get_ovn_info(cookie)
>> +                    if self.opts.get("ovn_filter"):
>> +                        ovn_regexp = re.compile(self.opts.get("ovn_filter"))
>> +                        if not ovn_regexp.search(ovn_info):
>> +                            continue
>> +
>> +                cookie_tree = tree.add("** Cookie {} **".format(hex(cookie)))
>> +                if ovn_info:
>> +                    ovn = cookie_tree.add("OVN Info")
>> +                    for part in ovn_info.split("\n"):
>> +                        if part.strip():
>> +                            ovn.add(part.strip())
>> +
>> +                tables_tree = cookie_tree.add("Tables")
>> +                for table, flows in tables.items():
>> +                    table_tree = tables_tree.add("* Table {} * ".format(table))
>> +                    for flow in flows:
>> +                        buf = ConsoleBuffer(Text())
>> +                        ofconsole.format_flow(buf, flow)
>> +                        table_tree.add(buf.text)
>> +            console.print(tree)
>> -- 
>> 2.43.0
>>
>> _______________________________________________
>> dev mailing list
>> dev@openvswitch.org
>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev
>
diff mbox series

Patch

diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py
index 6b1435ea1..9658d00d3 100644
--- a/python/ovs/flowviz/ofp/cli.py
+++ b/python/ovs/flowviz/ofp/cli.py
@@ -18,7 +18,7 @@  import click
 
 from ovs.flowviz.main import maincli
 from ovs.flowviz.ofp.html import HTMLProcessor
-from ovs.flowviz.ofp.logic import LogicFlowProcessor
+from ovs.flowviz.ofp.logic import CookieProcessor, LogicFlowProcessor
 from ovs.flowviz.process import (
     OpenFlowFactory,
     JSONProcessor,
@@ -182,6 +182,61 @@  def logic(
     processor.print(show_flows)
 
 
+@openflow.command()
+@click.option(
+    "-d",
+    "--ovn-detrace",
+    "ovn_detrace_flag",
+    is_flag=True,
+    show_default=True,
+    help="Use ovn-detrace to extract cookie information",
+)
+@click.option(
+    "--ovn-detrace-path",
+    default="/usr/bin",
+    type=click.Path(),
+    help="Use an alternative path to where ovn_detrace.py is located. "
+    "Instead of using this option you can just set PYTHONPATH accordingly",
+    show_default=True,
+    callback=ovn_detrace_callback,
+)
+@click.option(
+    "--ovnnb-db",
+    default=os.getenv("OVN_NB_DB") or "unix:/var/run/ovn/ovnnb_db.sock",
+    help="Specify the OVN NB database string (implies -d). "
+    "If the OVN_NB_DB environment variable is set, it's used as default. "
+    "Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+    callback=ovn_detrace_callback,
+)
+@click.option(
+    "--ovnsb-db",
+    default=os.getenv("OVN_SB_DB") or "unix:/var/run/ovn/ovnsb_db.sock",
+    help="Specify the OVN NB database string (implies -d). "
+    "If the OVN_NB_DB environment variable is set, it's used as default. "
+    "Otherwise, the default is unix:/var/run/ovn/ovnnb_db.sock",
+    callback=ovn_detrace_callback,
+)
+@click.option(
+    "-o",
+    "--ovn-filter",
+    help="Specify a filter to be run on ovn-detrace information (implied -d). "
+    "Format: python regular expression "
+    "(see https://docs.python.org/3/library/re.html)",
+    callback=ovn_detrace_callback,
+)
+@click.pass_obj
+def cookie(
+    opts, ovn_detrace_flag, ovn_detrace_path, ovnnb_db, ovnsb_db, ovn_filter
+):
+    """Print the flow tables sorted by cookie."""
+    if ovn_detrace_flag:
+        opts["ovn_detrace_flag"] = True
+
+    processor = CookieProcessor(opts)
+    processor.process()
+    processor.print()
+
+
 @openflow.command()
 @click.pass_obj
 def html(opts):
diff --git a/python/ovs/flowviz/ofp/logic.py b/python/ovs/flowviz/ofp/logic.py
index cb4568cf1..9d244d137 100644
--- a/python/ovs/flowviz/ofp/logic.py
+++ b/python/ovs/flowviz/ofp/logic.py
@@ -200,7 +200,7 @@  class LogicFlowProcessor(OpenFlowFactory, FileProcessor):
                 if len(self.heat_map) > 0 and len(table.values()) > 0:
                     for i, field in enumerate(self.heat_map):
                         (min_val, max_val) = self.min_max[name][i]
-                        self.console.style.set_value_style(
+                        formatter.style.set_value_style(
                             field, heat_pallete(min_val, max_val)
                         )
 
@@ -301,3 +301,64 @@  cookie_style_gen = hash_pallete(
     saturation=[0.5],
     value=[0.5 + x / 10 * (0.85 - 0.5) for x in range(0, 10)],
 )
+
+
+class CookieProcessor(OpenFlowFactory, FileProcessor):
+    """Processor that sorts flows into cookies and tables."""
+
+    def __init__(self, opts):
+        super().__init__(opts)
+        self.data = dict()
+        self.ovn_detrace = (
+            OVNDetrace(opts) if opts.get("ovn_detrace_flag") else None
+        )
+
+    def start_file(self, name, filename):
+        self.cookies = dict()
+
+    def stop_file(self, name, filename):
+        self.data[name] = self.cookies
+
+    def process_flow(self, flow, name):
+        """Sort the flows by table and logical flow."""
+        cookie = flow.info.get("cookie") or 0
+        if not self.cookies.get(cookie):
+            self.cookies[cookie] = dict()
+
+        table = flow.info.get("table") or 0
+        if not self.cookies[cookie].get(table):
+            self.cookies[cookie][table] = list()
+        self.cookies[cookie][table].append(flow)
+
+    def print(self):
+        ofconsole = ConsoleFormatter(opts=self.opts)
+        console = ofconsole.console
+        for name, cookies in self.data.items():
+            console.print("\n")
+            console.print(file_header(name))
+            tree = Tree("Ofproto Cookie Tree")
+
+            for cookie, tables in cookies.items():
+                ovn_info = None
+                if self.ovn_detrace:
+                    ovn_info = self.ovn_detrace.get_ovn_info(cookie)
+                    if self.opts.get("ovn_filter"):
+                        ovn_regexp = re.compile(self.opts.get("ovn_filter"))
+                        if not ovn_regexp.search(ovn_info):
+                            continue
+
+                cookie_tree = tree.add("** Cookie {} **".format(hex(cookie)))
+                if ovn_info:
+                    ovn = cookie_tree.add("OVN Info")
+                    for part in ovn_info.split("\n"):
+                        if part.strip():
+                            ovn.add(part.strip())
+
+                tables_tree = cookie_tree.add("Tables")
+                for table, flows in tables.items():
+                    table_tree = tables_tree.add("* Table {} * ".format(table))
+                    for flow in flows:
+                        buf = ConsoleBuffer(Text())
+                        ofconsole.format_flow(buf, flow)
+                        table_tree.add(buf.text)
+            console.print(tree)