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