[ovs-dev] [RFC PATCH 02/10] python/ovs-ofparse: Create basic flow processing

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


Create a FlowProcessor base class that can read files from multiple
input files, create flows based on a provided factory object and offer
some hooks for expansion. Derived classes can implement different
processing logics by overriding some of the hook methods

During the processing, flows can be filtered by adding a "--filter"
option to the main command line. Filtering at this stage can be disabled
since some formats might want to perform other kind of filtering.

Signed-off-by: Adrian Moreno <amorenoz at redhat.com>
---
 python/automake.mk                |   3 +-
 python/ovs/ovs_ofparse/main.py    |  63 ++++++++++++++++-
 python/ovs/ovs_ofparse/process.py | 113 ++++++++++++++++++++++++++++++
 3 files changed, 177 insertions(+), 2 deletions(-)
 create mode 100644 python/ovs/ovs_ofparse/process.py

diff --git a/python/automake.mk b/python/automake.mk
index cfe91c17b..e21e6b7d9 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -56,7 +56,8 @@ ovs_pyfiles = \
 	python/ovs/ovs_ofparse/main.py \
 	python/ovs/ovs_ofparse/datapath.py \
 	python/ovs/ovs_ofparse/openflow.py \
-	python/ovs/ovs_ofparse/ovs-ofparse
+	python/ovs/ovs_ofparse/ovs-ofparse \
+	python/ovs/ovs_ofparse/process.py
 
 
 ovs_tests = \
diff --git a/python/ovs/ovs_ofparse/main.py b/python/ovs/ovs_ofparse/main.py
index 09fb08460..789b3b970 100644
--- a/python/ovs/ovs_ofparse/main.py
+++ b/python/ovs/ovs_ofparse/main.py
@@ -2,6 +2,7 @@ import click
 import os.path
 import configparser
 
+from ovs.flows.filter import OFFilter
 from pkg_resources import resource_filename
 
 _default_config_file = "ovs-ofparse.conf"
@@ -63,8 +64,16 @@ def validate_input(ctx, param, value):
     type=click.Path(),
     callback=validate_input,
 )
+ at click.option(
+    "-f",
+    "--filter",
+    help="Filter flows that match the filter expression. Run 'ofparse filter'"
+    "for a detailed description of the filtering syntax",
+    type=str,
+    show_default=False,
+)
 @click.pass_context
-def maincli(ctx, config, filename):
+def maincli(ctx, config, filename, filter):
     """
     OpenFlow Parse utility.
 
@@ -75,6 +84,11 @@ def maincli(ctx, config, filename):
     """
     ctx.obj = Options()
     ctx.obj["filename"] = filename or None
+    if filter:
+        try:
+            ctx.obj["filter"] = OFFilter(filter)
+        except Exception as e:
+            raise click.BadParameter("Wrong filter syntax: {}".format(e))
 
     config_file = config or _default_config_path
     parser = configparser.ConfigParser()
@@ -82,6 +96,53 @@ def maincli(ctx, config, filename):
 
     ctx.obj["config"] = parser
 
+
+ at maincli.command(hidden=True)
+ at click.pass_context
+def filter(ctx):
+    """
+    \b
+    Filter Syntax
+    *************
+
+     [! | not ] {key}[[.subkey]...] [OPERATOR] {value})] [LOGICAL OPERATOR] ...
+
+    \b
+    Comparison operators are:
+        =   equality
+        <   less than
+        >   more than
+        ~=  masking (valid for IP and Ethernet fields)
+
+    \b
+    Logical operators are:
+        !{expr}:  NOT
+        {expr} && {expr}: AND
+        {expr} || {expr}: OR
+
+    \b
+    Matches and flow metadata:
+        To compare against a match or info field, use the field directly, e.g:
+            priority=100
+            n_bytes>10
+        Use simple keywords for flags:
+            tcp and ip_src=192.168.1.1
+    \b
+    Actions:
+        Actions values might be dictionaries, use subkeys to access individual
+        values, e.g:
+            output.port=3
+        Use simple keywords for flags
+            drop
+
+    \b
+    Examples of valid filters.
+        nw_addr~=192.168.1.1 && (tcp_dst=80 || tcp_dst=443)
+        arp=true && !arp_tsa=192.168.1.1
+        n_bytes>0 && drop=true"""
+    click.echo(ctx.command.get_help(ctx))
+
+
 def main():
     """
     Main Function
diff --git a/python/ovs/ovs_ofparse/process.py b/python/ovs/ovs_ofparse/process.py
new file mode 100644
index 000000000..660dd7243
--- /dev/null
+++ b/python/ovs/ovs_ofparse/process.py
@@ -0,0 +1,113 @@
+""" Defines common flow processing functionality
+"""
+import sys
+import click
+
+
+class FlowProcessor(object):
+    """Base class for file-based Flow processing. It is able to create flows
+    from strings found in a file (or stdin).
+
+    The process of parsing the flows is extendable in many ways by deriving
+    this class.
+
+    When process() is called, the base class will:
+        - call self.start_file() for each new file that get's processed
+        - call self.create_flow() for each flow line
+        - apply the filter defined in opts if provided (can be optionally
+            disabled)
+        - call self.process_flow() for after the flow has been filtered
+        - call self.stop_file() after the file has been processed entirely
+
+    In the case of stdin, the filename and file alias is 'stdin'
+
+    Args:
+        opts (dict): Options dictionary
+        factory (object): Factory object to use to build flows
+            The factory object must have a function as:
+                from_string(line, idx)
+    """
+
+    def __init__(self, opts, factory):
+        self.opts = opts
+        self.factory = factory
+
+    # Methods that must be implemented by derived classes
+    def init(self):
+        """Called before the flow processing begins"""
+        pass
+
+    def start_file(self, alias, filename):
+        """Called before the processing of a file begins
+        Args:
+            alias(str): The alias name of the filename
+            filename(str): The filename string
+        """
+        pass
+
+    def create_flow(self, line, idx):
+        """Called for each line in the file
+        Args:
+            line(str): The flow line
+            idx(int): The line index
+
+        Returns a Flow
+        """
+        return self.factory.from_string(line, idx)
+
+    def process_flow(self, flow, name):
+        """Called for built flow (after filtering)
+        Args:
+            flow(Flow): The flow created by create_flow
+            name(str): The name of the file from which the flow comes
+        """
+        pass
+
+    def stop_file(self, alias, filename):
+        """Called after the processing of a file ends
+        Args:
+            alias(str): The alias name of the filename
+            filename(str): The filename string
+        """
+        pass
+
+    def end(self):
+        """Called after the processing ends"""
+        pass
+
+    def process(self, do_filter=True):
+        idx = 0
+        filenames = self.opts.get("filename")
+        filt = self.opts.get("filter") if do_filter else None
+        self.init()
+        if filenames:
+            for alias, filename in filenames:
+                try:
+                    with open(filename) as f:
+                        self.start_file(alias, filename)
+                        for line in f:
+                            flow = self.create_flow(line, idx)
+                            idx += 1
+                            if not flow or (filt and not filt.evaluate(flow)):
+                                continue
+                            self.process_flow(flow, alias)
+                        self.stop_file(alias, filename)
+                except IOError as e:
+                    raise click.BadParameter(
+                        "Failed to read from file {} ({}): {}".format(
+                            filename, e.errno, e.strerror
+                        )
+                    )
+        else:
+            data = sys.stdin.read()
+            self.start_file("stdin", "stdin")
+            for line in data.split("\n"):
+                line = line.strip()
+                if line:
+                    flow = self.create_flow(line, idx)
+                    idx += 1
+                    if not flow or (filt and not filt.evaluate(flow)):
+                        continue
+                    self.process_flow(flow, alias)
+            self.stop_file("stdin", "stdin")
+        self.end()
-- 
2.31.1



More information about the dev mailing list