diff mbox series

[net-next,v7,07/10] selftests: openvswitch: add psample action

Message ID 20240630195740.1469727-8-amorenoz@redhat.com (mailing list archive)
State Accepted
Commit 60ccf62d3ceb37c89391e60d5ba402f52b720b62
Headers show
Series net: openvswitch: Add sample multicasting. | expand

Commit Message

Adrián Moreno June 30, 2024, 7:57 p.m. UTC
Add sample and psample action support to ovs-dpctl.py.

Refactor common attribute parsing logic into an external function.

Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 .../selftests/net/openvswitch/ovs-dpctl.py    | 162 +++++++++++++++++-
 1 file changed, 161 insertions(+), 1 deletion(-)

Comments

Aaron Conole July 1, 2024, 6:40 p.m. UTC | #1
Adrian Moreno <amorenoz@redhat.com> writes:

> Add sample and psample action support to ovs-dpctl.py.
>
> Refactor common attribute parsing logic into an external function.
>
> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
> ---

Reviewed-by: Aaron Conole <aconole@redhat.com>

>  .../selftests/net/openvswitch/ovs-dpctl.py    | 162 +++++++++++++++++-
>  1 file changed, 161 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> index 182a09975975..dcc400a21a22 100644
> --- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> +++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
> @@ -8,6 +8,7 @@ import argparse
>  import errno
>  import ipaddress
>  import logging
> +import math
>  import multiprocessing
>  import re
>  import socket
> @@ -60,6 +61,7 @@ OVS_FLOW_CMD_DEL = 2
>  OVS_FLOW_CMD_GET = 3
>  OVS_FLOW_CMD_SET = 4
>  
> +UINT32_MAX = 0xFFFFFFFF
>  
>  def macstr(mac):
>      outstr = ":".join(["%02X" % i for i in mac])
> @@ -281,6 +283,75 @@ def parse_extract_field(
>      return str_skipped, data
>  
>  
> +def parse_attrs(actstr, attr_desc):
> +    """Parses the given action string and returns a list of netlink
> +    attributes based on a list of attribute descriptions.
> +
> +    Each element in the attribute description list is a tuple such as:
> +        (name, attr_name, parse_func)
> +    where:
> +        name: is the string representing the attribute
> +        attr_name: is the name of the attribute as defined in the uAPI.
> +        parse_func: is a callable accepting a string and returning either
> +            a single object (the parsed attribute value) or a tuple of
> +            two values (the parsed attribute value and the remaining string)
> +
> +    Returns a list of attributes and the remaining string.
> +    """
> +    def parse_attr(actstr, key, func):
> +        actstr = actstr[len(key) :]
> +
> +        if not func:
> +            return None, actstr
> +
> +        delim = actstr[0]
> +        actstr = actstr[1:]
> +
> +        if delim == "=":
> +            pos = strcspn(actstr, ",)")
> +            ret = func(actstr[:pos])
> +        else:
> +            ret = func(actstr)
> +
> +        if isinstance(ret, tuple):
> +            (datum, actstr) = ret
> +        else:
> +            datum = ret
> +            actstr = actstr[strcspn(actstr, ",)"):]
> +
> +        if delim == "(":
> +            if not actstr or actstr[0] != ")":
> +                raise ValueError("Action contains unbalanced parentheses")
> +
> +            actstr = actstr[1:]
> +
> +        actstr = actstr[strspn(actstr, ", ") :]
> +
> +        return datum, actstr
> +
> +    attrs = []
> +    attr_desc = list(attr_desc)
> +    while actstr and actstr[0] != ")" and attr_desc:
> +        found = False
> +        for i, (key, attr, func) in enumerate(attr_desc):
> +            if actstr.startswith(key):
> +                datum, actstr = parse_attr(actstr, key, func)
> +                attrs.append([attr, datum])
> +                found = True
> +                del attr_desc[i]
> +
> +        if not found:
> +            raise ValueError("Unknown attribute: '%s'" % actstr)
> +
> +        actstr = actstr[strspn(actstr, ", ") :]
> +
> +    if actstr[0] != ")":
> +        raise ValueError("Action string contains extra garbage or has "
> +                         "unbalanced parenthesis: '%s'" % actstr)
> +
> +    return attrs, actstr[1:]
> +
> +
>  class ovs_dp_msg(genlmsg):
>      # include the OVS version
>      # We need a custom header rather than just being able to rely on
> @@ -299,7 +370,7 @@ class ovsactions(nla):
>          ("OVS_ACTION_ATTR_SET", "ovskey"),
>          ("OVS_ACTION_ATTR_PUSH_VLAN", "none"),
>          ("OVS_ACTION_ATTR_POP_VLAN", "flag"),
> -        ("OVS_ACTION_ATTR_SAMPLE", "none"),
> +        ("OVS_ACTION_ATTR_SAMPLE", "sample"),
>          ("OVS_ACTION_ATTR_RECIRC", "uint32"),
>          ("OVS_ACTION_ATTR_HASH", "none"),
>          ("OVS_ACTION_ATTR_PUSH_MPLS", "none"),
> @@ -318,8 +389,85 @@ class ovsactions(nla):
>          ("OVS_ACTION_ATTR_ADD_MPLS", "none"),
>          ("OVS_ACTION_ATTR_DEC_TTL", "none"),
>          ("OVS_ACTION_ATTR_DROP", "uint32"),
> +        ("OVS_ACTION_ATTR_PSAMPLE", "psample"),
>      )
>  
> +    class psample(nla):
> +        nla_flags = NLA_F_NESTED
> +
> +        nla_map = (
> +            ("OVS_PSAMPLE_ATTR_UNSPEC", "none"),
> +            ("OVS_PSAMPLE_ATTR_GROUP", "uint32"),
> +            ("OVS_PSAMPLE_ATTR_COOKIE", "array(uint8)"),
> +        )
> +
> +        def dpstr(self, more=False):
> +            args = "group=%d" % self.get_attr("OVS_PSAMPLE_ATTR_GROUP")
> +
> +            cookie = self.get_attr("OVS_PSAMPLE_ATTR_COOKIE")
> +            if cookie:
> +                args += ",cookie(%s)" % \
> +                        "".join(format(x, "02x") for x in cookie)
> +
> +            return "psample(%s)" % args
> +
> +        def parse(self, actstr):
> +            desc = (
> +                ("group", "OVS_PSAMPLE_ATTR_GROUP", int),
> +                ("cookie", "OVS_PSAMPLE_ATTR_COOKIE",
> +                    lambda x: list(bytearray.fromhex(x)))
> +            )
> +
> +            attrs, actstr = parse_attrs(actstr, desc)
> +
> +            for attr in attrs:
> +                self["attrs"].append(attr)
> +
> +            return actstr
> +
> +    class sample(nla):
> +        nla_flags = NLA_F_NESTED
> +
> +        nla_map = (
> +            ("OVS_SAMPLE_ATTR_UNSPEC", "none"),
> +            ("OVS_SAMPLE_ATTR_PROBABILITY", "uint32"),
> +            ("OVS_SAMPLE_ATTR_ACTIONS", "ovsactions"),
> +        )
> +
> +        def dpstr(self, more=False):
> +            args = []
> +
> +            args.append("sample={:.2f}%".format(
> +                100 * self.get_attr("OVS_SAMPLE_ATTR_PROBABILITY") /
> +                UINT32_MAX))
> +
> +            actions = self.get_attr("OVS_SAMPLE_ATTR_ACTIONS")
> +            if actions:
> +                args.append("actions(%s)" % actions.dpstr(more))
> +
> +            return "sample(%s)" % ",".join(args)
> +
> +        def parse(self, actstr):
> +            def parse_nested_actions(actstr):
> +                subacts = ovsactions()
> +                parsed_len = subacts.parse(actstr)
> +                return subacts, actstr[parsed_len :]
> +
> +            def percent_to_rate(percent):
> +                percent = float(percent.strip('%'))
> +                return int(math.floor(UINT32_MAX * (percent / 100.0) + .5))
> +
> +            desc = (
> +                ("sample", "OVS_SAMPLE_ATTR_PROBABILITY", percent_to_rate),
> +                ("actions", "OVS_SAMPLE_ATTR_ACTIONS", parse_nested_actions),
> +            )
> +            attrs, actstr = parse_attrs(actstr, desc)
> +
> +            for attr in attrs:
> +                self["attrs"].append(attr)
> +
> +            return actstr
> +
>      class ctact(nla):
>          nla_flags = NLA_F_NESTED
>  
> @@ -683,6 +831,18 @@ class ovsactions(nla):
>                  self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact])
>                  parsed = True
>  
> +            elif parse_starts_block(actstr, "sample(", False):
> +                sampleact = self.sample()
> +                actstr = sampleact.parse(actstr[len("sample(") : ])
> +                self["attrs"].append(["OVS_ACTION_ATTR_SAMPLE", sampleact])
> +                parsed = True
> +
> +            elif parse_starts_block(actstr, "psample(", False):
> +                psampleact = self.psample()
> +                actstr = psampleact.parse(actstr[len("psample(") : ])
> +                self["attrs"].append(["OVS_ACTION_ATTR_PSAMPLE", psampleact])
> +                parsed = True
> +
>              actstr = actstr[strspn(actstr, ", ") :]
>              while parencount > 0:
>                  parencount -= 1
diff mbox series

Patch

diff --git a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
index 182a09975975..dcc400a21a22 100644
--- a/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
+++ b/tools/testing/selftests/net/openvswitch/ovs-dpctl.py
@@ -8,6 +8,7 @@  import argparse
 import errno
 import ipaddress
 import logging
+import math
 import multiprocessing
 import re
 import socket
@@ -60,6 +61,7 @@  OVS_FLOW_CMD_DEL = 2
 OVS_FLOW_CMD_GET = 3
 OVS_FLOW_CMD_SET = 4
 
+UINT32_MAX = 0xFFFFFFFF
 
 def macstr(mac):
     outstr = ":".join(["%02X" % i for i in mac])
@@ -281,6 +283,75 @@  def parse_extract_field(
     return str_skipped, data
 
 
+def parse_attrs(actstr, attr_desc):
+    """Parses the given action string and returns a list of netlink
+    attributes based on a list of attribute descriptions.
+
+    Each element in the attribute description list is a tuple such as:
+        (name, attr_name, parse_func)
+    where:
+        name: is the string representing the attribute
+        attr_name: is the name of the attribute as defined in the uAPI.
+        parse_func: is a callable accepting a string and returning either
+            a single object (the parsed attribute value) or a tuple of
+            two values (the parsed attribute value and the remaining string)
+
+    Returns a list of attributes and the remaining string.
+    """
+    def parse_attr(actstr, key, func):
+        actstr = actstr[len(key) :]
+
+        if not func:
+            return None, actstr
+
+        delim = actstr[0]
+        actstr = actstr[1:]
+
+        if delim == "=":
+            pos = strcspn(actstr, ",)")
+            ret = func(actstr[:pos])
+        else:
+            ret = func(actstr)
+
+        if isinstance(ret, tuple):
+            (datum, actstr) = ret
+        else:
+            datum = ret
+            actstr = actstr[strcspn(actstr, ",)"):]
+
+        if delim == "(":
+            if not actstr or actstr[0] != ")":
+                raise ValueError("Action contains unbalanced parentheses")
+
+            actstr = actstr[1:]
+
+        actstr = actstr[strspn(actstr, ", ") :]
+
+        return datum, actstr
+
+    attrs = []
+    attr_desc = list(attr_desc)
+    while actstr and actstr[0] != ")" and attr_desc:
+        found = False
+        for i, (key, attr, func) in enumerate(attr_desc):
+            if actstr.startswith(key):
+                datum, actstr = parse_attr(actstr, key, func)
+                attrs.append([attr, datum])
+                found = True
+                del attr_desc[i]
+
+        if not found:
+            raise ValueError("Unknown attribute: '%s'" % actstr)
+
+        actstr = actstr[strspn(actstr, ", ") :]
+
+    if actstr[0] != ")":
+        raise ValueError("Action string contains extra garbage or has "
+                         "unbalanced parenthesis: '%s'" % actstr)
+
+    return attrs, actstr[1:]
+
+
 class ovs_dp_msg(genlmsg):
     # include the OVS version
     # We need a custom header rather than just being able to rely on
@@ -299,7 +370,7 @@  class ovsactions(nla):
         ("OVS_ACTION_ATTR_SET", "ovskey"),
         ("OVS_ACTION_ATTR_PUSH_VLAN", "none"),
         ("OVS_ACTION_ATTR_POP_VLAN", "flag"),
-        ("OVS_ACTION_ATTR_SAMPLE", "none"),
+        ("OVS_ACTION_ATTR_SAMPLE", "sample"),
         ("OVS_ACTION_ATTR_RECIRC", "uint32"),
         ("OVS_ACTION_ATTR_HASH", "none"),
         ("OVS_ACTION_ATTR_PUSH_MPLS", "none"),
@@ -318,8 +389,85 @@  class ovsactions(nla):
         ("OVS_ACTION_ATTR_ADD_MPLS", "none"),
         ("OVS_ACTION_ATTR_DEC_TTL", "none"),
         ("OVS_ACTION_ATTR_DROP", "uint32"),
+        ("OVS_ACTION_ATTR_PSAMPLE", "psample"),
     )
 
+    class psample(nla):
+        nla_flags = NLA_F_NESTED
+
+        nla_map = (
+            ("OVS_PSAMPLE_ATTR_UNSPEC", "none"),
+            ("OVS_PSAMPLE_ATTR_GROUP", "uint32"),
+            ("OVS_PSAMPLE_ATTR_COOKIE", "array(uint8)"),
+        )
+
+        def dpstr(self, more=False):
+            args = "group=%d" % self.get_attr("OVS_PSAMPLE_ATTR_GROUP")
+
+            cookie = self.get_attr("OVS_PSAMPLE_ATTR_COOKIE")
+            if cookie:
+                args += ",cookie(%s)" % \
+                        "".join(format(x, "02x") for x in cookie)
+
+            return "psample(%s)" % args
+
+        def parse(self, actstr):
+            desc = (
+                ("group", "OVS_PSAMPLE_ATTR_GROUP", int),
+                ("cookie", "OVS_PSAMPLE_ATTR_COOKIE",
+                    lambda x: list(bytearray.fromhex(x)))
+            )
+
+            attrs, actstr = parse_attrs(actstr, desc)
+
+            for attr in attrs:
+                self["attrs"].append(attr)
+
+            return actstr
+
+    class sample(nla):
+        nla_flags = NLA_F_NESTED
+
+        nla_map = (
+            ("OVS_SAMPLE_ATTR_UNSPEC", "none"),
+            ("OVS_SAMPLE_ATTR_PROBABILITY", "uint32"),
+            ("OVS_SAMPLE_ATTR_ACTIONS", "ovsactions"),
+        )
+
+        def dpstr(self, more=False):
+            args = []
+
+            args.append("sample={:.2f}%".format(
+                100 * self.get_attr("OVS_SAMPLE_ATTR_PROBABILITY") /
+                UINT32_MAX))
+
+            actions = self.get_attr("OVS_SAMPLE_ATTR_ACTIONS")
+            if actions:
+                args.append("actions(%s)" % actions.dpstr(more))
+
+            return "sample(%s)" % ",".join(args)
+
+        def parse(self, actstr):
+            def parse_nested_actions(actstr):
+                subacts = ovsactions()
+                parsed_len = subacts.parse(actstr)
+                return subacts, actstr[parsed_len :]
+
+            def percent_to_rate(percent):
+                percent = float(percent.strip('%'))
+                return int(math.floor(UINT32_MAX * (percent / 100.0) + .5))
+
+            desc = (
+                ("sample", "OVS_SAMPLE_ATTR_PROBABILITY", percent_to_rate),
+                ("actions", "OVS_SAMPLE_ATTR_ACTIONS", parse_nested_actions),
+            )
+            attrs, actstr = parse_attrs(actstr, desc)
+
+            for attr in attrs:
+                self["attrs"].append(attr)
+
+            return actstr
+
     class ctact(nla):
         nla_flags = NLA_F_NESTED
 
@@ -683,6 +831,18 @@  class ovsactions(nla):
                 self["attrs"].append(["OVS_ACTION_ATTR_CT", ctact])
                 parsed = True
 
+            elif parse_starts_block(actstr, "sample(", False):
+                sampleact = self.sample()
+                actstr = sampleact.parse(actstr[len("sample(") : ])
+                self["attrs"].append(["OVS_ACTION_ATTR_SAMPLE", sampleact])
+                parsed = True
+
+            elif parse_starts_block(actstr, "psample(", False):
+                psampleact = self.psample()
+                actstr = psampleact.parse(actstr[len("psample(") : ])
+                self["attrs"].append(["OVS_ACTION_ATTR_PSAMPLE", psampleact])
+                parsed = True
+
             actstr = actstr[strspn(actstr, ", ") :]
             while parencount > 0:
                 parencount -= 1