Message ID | 20240710170504.2162803-11-amorenoz@redhat.com |
---|---|
State | Changes Requested |
Headers | show |
Series | Add flow visualization utility. | expand |
Context | Check | Description |
---|---|---|
ovsrobot/apply-robot | success | apply and check: success |
ovsrobot/github-robot-_Build_and_Test | success | github build: passed |
On 10 Jul 2024, at 19:04, Adrian Moreno wrote: > When anaylizing OVN issues, it might be useful to see what OpenFlow anaylizing -> analyzing > 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. > > Example: > $ export OVN_NB_DB=... > $ export OVN_SB_DB=... > $ ovs-vsctl dump-flows br-int | ovs-flowviz openflow cookie > --ovn-filter="acl.*icmp4" > $ ovs-vsctl dump-flows br-int | ovs-flowviz openflow cookie > --ovn-detrace > > Acked-by: Eelco Chaudron <echaudro@redhat.com> > Signed-off-by: Adrian Moreno <amorenoz@redhat.com> > --- Thanks for sending the v5, the changes look good to me (one spelling error in the commit message). //Eelco Acked-by: Eelco Chaudron <echaudro@redhat.com> > python/ovs/flowviz/ofp/cli.py | 57 +++++++++++++++++++++++++++++- > python/ovs/flowviz/ofp/logic.py | 61 +++++++++++++++++++++++++++++++++ > 2 files changed, 117 insertions(+), 1 deletion(-) > > diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py > index a0a94bd3b..291384bd4 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 ( > ConsoleProcessor, > JSONOpenFlowProcessor, > @@ -172,6 +172,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 dd5c29c5a..db5c2e699 100644 > --- a/python/ovs/flowviz/ofp/logic.py > +++ b/python/ovs/flowviz/ofp/logic.py > @@ -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(FileProcessor): > + """Processor that sorts flows into cookies and tables.""" > + > + def __init__(self, opts): > + super().__init__(opts, "ofp") > + 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.45.2
diff --git a/python/ovs/flowviz/ofp/cli.py b/python/ovs/flowviz/ofp/cli.py index a0a94bd3b..291384bd4 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 ( ConsoleProcessor, JSONOpenFlowProcessor, @@ -172,6 +172,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 dd5c29c5a..db5c2e699 100644 --- a/python/ovs/flowviz/ofp/logic.py +++ b/python/ovs/flowviz/ofp/logic.py @@ -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(FileProcessor): + """Processor that sorts flows into cookies and tables.""" + + def __init__(self, opts): + super().__init__(opts, "ofp") + 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)