Commit 60ccf62d authored by Adrian Moreno's avatar Adrian Moreno Committed by Jakub Kicinski
Browse files

selftests: openvswitch: add psample action



Add sample and psample action support to ovs-dpctl.py.

Refactor common attribute parsing logic into an external function.

Reviewed-by: default avatarAaron Conole <aconole@redhat.com>
Signed-off-by: default avatarAdrian Moreno <amorenoz@redhat.com>
Link: https://patch.msgid.link/20240704085710.353845-8-amorenoz@redhat.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 71763d8a
Loading
Loading
Loading
Loading
+161 −1
Original line number Diff line number Diff line
@@ -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