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 <[email protected]> --- 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() + + [email protected]() [email protected]( + "-s", + "--show-flows", + is_flag=True, + default=False, + show_default=True, + help="Show the full flows under each logical flow", +) [email protected]( + "-c", + "--cookie", + "cookie_flag", + is_flag=True, + default=False, + show_default=True, + help="Consider the cookie in the logical flow", +) [email protected]( + "-h", + "--heat-map", + is_flag=True, + default=False, + show_default=True, + help="Create heat-map with packet and byte counters (when -s is used)", +) [email protected]_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 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
