Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/1558026/?format=api
{ "id": 1558026, "url": "http://patchwork.ozlabs.org/api/patches/1558026/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/patch/20211122112256.2011194-8-amorenoz@redhat.com/", "project": { "id": 47, "url": "http://patchwork.ozlabs.org/api/projects/47/?format=api", "name": "Open vSwitch", "link_name": "openvswitch", "list_id": "ovs-dev.openvswitch.org", "list_email": "ovs-dev@openvswitch.org", "web_url": "http://openvswitch.org/", "scm_url": "git@github.com:openvswitch/ovs.git", "webscm_url": "https://github.com/openvswitch/ovs", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20211122112256.2011194-8-amorenoz@redhat.com>", "list_archive_url": null, "date": "2021-11-22T11:22:45", "name": "[ovs-dev,v1,07/18] python: introduce OpenFlow Flow parsing", "commit_ref": null, "pull_url": null, "state": "changes-requested", "archived": false, "hash": "f20510f899e7f9c49f8ab3a6d5b0cc297c2f7ee1", "submitter": { "id": 77477, "url": "http://patchwork.ozlabs.org/api/people/77477/?format=api", "name": "Adrian Moreno", "email": "amorenoz@redhat.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/openvswitch/patch/20211122112256.2011194-8-amorenoz@redhat.com/mbox/", "series": [ { "id": 273222, "url": "http://patchwork.ozlabs.org/api/series/273222/?format=api", "web_url": "http://patchwork.ozlabs.org/project/openvswitch/list/?series=273222", "date": "2021-11-22T11:22:39", "name": "python: add flow parsing library", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/273222/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/1558026/comments/", "check": "fail", "checks": "http://patchwork.ozlabs.org/api/patches/1558026/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<ovs-dev-bounces@openvswitch.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "dev@openvswitch.org" ], "Delivered-To": [ "patchwork-incoming@bilbo.ozlabs.org", "ovs-dev@lists.linuxfoundation.org" ], "Authentication-Results": [ "bilbo.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256\n header.s=mimecast20190719 header.b=Ki/fWvbW;\n\tdkim-atps=neutral", "ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org\n (client-ip=140.211.166.137; helo=smtp4.osuosl.org;\n envelope-from=ovs-dev-bounces@openvswitch.org; receiver=<UNKNOWN>)", "smtp2.osuosl.org (amavisd-new);\n dkim=pass (1024-bit key) header.d=redhat.com", "relay.mimecast.com;\n auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=amorenoz@redhat.com" ], "Received": [ "from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest\n SHA256)\n\t(No client certificate requested)\n\tby bilbo.ozlabs.org (Postfix) with ESMTPS id 4HyQ0C2bmcz9sRR\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 22 Nov 2021 22:24:27 +1100 (AEDT)", "from localhost (localhost [127.0.0.1])\n\tby smtp4.osuosl.org (Postfix) with ESMTP id 0F44341C67;\n\tMon, 22 Nov 2021 11:24:25 +0000 (UTC)", "from smtp4.osuosl.org ([127.0.0.1])\n\tby localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n\twith ESMTP id IWGGgtfjGlSg; Mon, 22 Nov 2021 11:24:19 +0000 (UTC)", "from lists.linuxfoundation.org (lf-lists.osuosl.org\n [IPv6:2605:bc80:3010:104::8cd3:938])\n\tby smtp4.osuosl.org (Postfix) with ESMTPS id A985641C44;\n\tMon, 22 Nov 2021 11:24:17 +0000 (UTC)", "from lf-lists.osuosl.org (localhost [127.0.0.1])\n\tby lists.linuxfoundation.org (Postfix) with ESMTP id 8259AC003C;\n\tMon, 22 Nov 2021 11:24:15 +0000 (UTC)", "from smtp2.osuosl.org (smtp2.osuosl.org [140.211.166.133])\n by lists.linuxfoundation.org (Postfix) with ESMTP id 40E6AC0037\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:24:14 +0000 (UTC)", "from localhost (localhost [127.0.0.1])\n by smtp2.osuosl.org (Postfix) with ESMTP id 202AB4041B\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:23:39 +0000 (UTC)", "from smtp2.osuosl.org ([127.0.0.1])\n by localhost (smtp2.osuosl.org [127.0.0.1]) (amavisd-new, port 10024)\n with ESMTP id OnQc-x03_nTY for <dev@openvswitch.org>;\n Mon, 22 Nov 2021 11:23:35 +0000 (UTC)", "from us-smtp-delivery-124.mimecast.com\n (us-smtp-delivery-124.mimecast.com [170.10.133.124])\n by smtp2.osuosl.org (Postfix) with ESMTPS id C9F87403AB\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:23:34 +0000 (UTC)", "from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com\n [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS\n (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id\n us-mta-140-kkXvIrzMOgq16Eu0OswAoQ-1; Mon, 22 Nov 2021 06:23:30 -0500", "from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com\n [10.5.11.13])\n (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits))\n (No client certificate requested)\n by mimecast-mx01.redhat.com (Postfix) with ESMTPS id DB1C91966320\n for <dev@openvswitch.org>; Mon, 22 Nov 2021 11:23:29 +0000 (UTC)", "from amorenoz.users.ipa.redhat.com (unknown [10.2.16.196])\n by smtp.corp.redhat.com (Postfix) with ESMTP id C1CB760862;\n Mon, 22 Nov 2021 11:23:28 +0000 (UTC)" ], "X-Virus-Scanned": [ "amavisd-new at osuosl.org", "amavisd-new at osuosl.org" ], "X-Greylist": "domain auto-whitelisted by SQLgrey-1.8.0", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n s=mimecast20190719; t=1637580213;\n h=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n to:to:cc:cc:mime-version:mime-version:content-type:content-type:\n content-transfer-encoding:content-transfer-encoding:\n in-reply-to:in-reply-to:references:references;\n bh=5NFJ54aaJPVAm6bi/zGHyu+wgeN5VJNkW76Q/ud1v0Q=;\n b=Ki/fWvbWt20ZYzObTkW41sWAvfMvFJdlRiVuPMvQ4JOOv9FDtVCtQ2B3fUIFJHHvqWY5p8\n Un9UMZ4X3Am925UWHA7mvuuidvZPzz2nuSERgZTc5BJJYFnpOztK/XZ4P5imnxX+fogmVv\n vE7FwptmaDjn+IupGAsseh+57LnW4GU=", "X-MC-Unique": "kkXvIrzMOgq16Eu0OswAoQ-1", "From": "Adrian Moreno <amorenoz@redhat.com>", "To": "dev@openvswitch.org", "Date": "Mon, 22 Nov 2021 12:22:45 +0100", "Message-Id": "<20211122112256.2011194-8-amorenoz@redhat.com>", "In-Reply-To": "<20211122112256.2011194-1-amorenoz@redhat.com>", "References": "<20211122112256.2011194-1-amorenoz@redhat.com>", "MIME-Version": "1.0", "X-Scanned-By": "MIMEDefang 2.79 on 10.5.11.13", "X-Mimecast-Spam-Score": "0", "X-Mimecast-Originator": "redhat.com", "Subject": "[ovs-dev] [PATCH v1 07/18] python: introduce OpenFlow Flow parsing", "X-BeenThere": "ovs-dev@openvswitch.org", "X-Mailman-Version": "2.1.15", "Precedence": "list", "List-Id": "<ovs-dev.openvswitch.org>", "List-Unsubscribe": "<https://mail.openvswitch.org/mailman/options/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=unsubscribe>", "List-Archive": "<http://mail.openvswitch.org/pipermail/ovs-dev/>", "List-Post": "<mailto:ovs-dev@openvswitch.org>", "List-Help": "<mailto:ovs-dev-request@openvswitch.org?subject=help>", "List-Subscribe": "<https://mail.openvswitch.org/mailman/listinfo/ovs-dev>,\n <mailto:ovs-dev-request@openvswitch.org?subject=subscribe>", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Errors-To": "ovs-dev-bounces@openvswitch.org", "Sender": "\"dev\" <ovs-dev-bounces@openvswitch.org>" }, "content": "Introduce OFPFlow class and all its decoders.\n\nMost of the decoders are generic (from decoders.py). Some have special\nsyntax and need a specific implementation.\n\nDecoders for nat are moved to the common decoders.py because it's syntax\nis shared with other types of flows (e.g: dpif flows).\n\nSigned-off-by: Adrian Moreno <amorenoz@redhat.com>\n---\n python/automake.mk | 4 +-\n python/ovs/flows/decoders.py | 93 ++++++++\n python/ovs/flows/ofp.py | 400 +++++++++++++++++++++++++++++++++++\n python/ovs/flows/ofp_act.py | 233 ++++++++++++++++++++\n 4 files changed, 729 insertions(+), 1 deletion(-)\n create mode 100644 python/ovs/flows/ofp.py\n create mode 100644 python/ovs/flows/ofp_act.py", "diff": "diff --git a/python/automake.mk b/python/automake.mk\nindex 136da26bd..d1464d7f6 100644\n--- a/python/automake.mk\n+++ b/python/automake.mk\n@@ -46,7 +46,9 @@ ovs_pyfiles = \\\n \tpython/ovs/flows/decoders.py \\\n \tpython/ovs/flows/kv.py \\\n \tpython/ovs/flows/list.py \\\n-\tpython/ovs/flows/flow.py\n+\tpython/ovs/flows/flow.py \\\n+\tpython/ovs/flows/ofp.py \\\n+\tpython/ovs/flows/ofp_act.py\n \n # These python files are used at build time but not runtime,\n # so they are not installed.\ndiff --git a/python/ovs/flows/decoders.py b/python/ovs/flows/decoders.py\nindex bf7a94ae8..3def9f279 100644\n--- a/python/ovs/flows/decoders.py\n+++ b/python/ovs/flows/decoders.py\n@@ -6,6 +6,7 @@ object.\n \"\"\"\n \n import netaddr\n+import re\n \n \n class Decoder:\n@@ -358,3 +359,95 @@ class IPMask(Decoder):\n \n def to_json(self):\n return str(self)\n+\n+\n+def decode_free_output(value):\n+ \"\"\"Decodes the output value when found free\n+ (without the 'output' keyword)\"\"\"\n+ try:\n+ return \"output\", {\"port\": int(value)}\n+ except ValueError:\n+ return \"output\", {\"port\": value.strip('\"')}\n+\n+\n+ipv4 = r\"[\\d\\.]+\"\n+ipv4_capture = r\"({ipv4})\".format(ipv4=ipv4)\n+ipv6 = r\"[\\w:]+\"\n+ipv6_capture = r\"(?:\\[*)?({ipv6})(?:\\]*)?\".format(ipv6=ipv6)\n+port_range = r\":(\\d+)(?:-(\\d+))?\"\n+ip_range_regexp = r\"{ip_cap}(?:-{ip_cap})?(?:{port_range})?\"\n+ipv4_port_regex = re.compile(\n+ ip_range_regexp.format(ip_cap=ipv4_capture, port_range=port_range)\n+)\n+ipv6_port_regex = re.compile(\n+ ip_range_regexp.format(ip_cap=ipv6_capture, port_range=port_range)\n+)\n+\n+\n+def decode_ip_port_range(value):\n+ \"\"\"\n+ Decodes an IP and port range:\n+ {ip_start}-{ip-end}:{port_start}-{port_end}\n+\n+ IPv6 addresses are surrounded by \"[\" and \"]\" if port ranges are also\n+ present\n+\n+ Returns the following dictionary:\n+ {\n+ \"addrs\": {\n+ \"start\": {ip_start}\n+ \"end\": {ip_end}\n+ }\n+ \"ports\": {\n+ \"start\": {port_start},\n+ \"end\": {port_end}\n+ }\n+ (the \"ports\" key might be omitted)\n+ \"\"\"\n+ if value.count(\":\") > 1:\n+ match = ipv6_port_regex.match(value)\n+ else:\n+ match = ipv4_port_regex.match(value)\n+\n+ ip_start = match.group(1)\n+ ip_end = match.group(2)\n+ port_start = match.group(3)\n+ port_end = match.group(4)\n+\n+ result = {\n+ \"addrs\": {\n+ \"start\": netaddr.IPAddress(ip_start),\n+ \"end\": netaddr.IPAddress(ip_end or ip_start),\n+ }\n+ }\n+ if port_start:\n+ result[\"ports\"] = {\n+ \"start\": int(port_start),\n+ \"end\": int(port_end or port_start),\n+ }\n+\n+ return result\n+\n+\n+def decode_nat(value):\n+ \"\"\"Decodes the 'nat' keyword of the ct action\"\"\"\n+ if not value:\n+ return True\n+\n+ result = dict()\n+ type_parts = value.split(\"=\")\n+ result[\"type\"] = type_parts[0]\n+\n+ if len(type_parts) > 1:\n+ value_parts = type_parts[1].split(\",\")\n+ if len(type_parts) != 2:\n+ raise ValueError(\"Malformed nat action: %s\" % value)\n+\n+ ip_port_range = decode_ip_port_range(value_parts[0])\n+\n+ result = {\"type\": type_parts[0], **ip_port_range}\n+\n+ for flag in value_parts[1:]:\n+ result[flag] = True\n+\n+ return result\ndiff --git a/python/ovs/flows/ofp.py b/python/ovs/flows/ofp.py\nnew file mode 100644\nindex 000000000..e56b08967\n--- /dev/null\n+++ b/python/ovs/flows/ofp.py\n@@ -0,0 +1,400 @@\n+\"\"\" Defines the parsers needed to parse ofproto flows\n+\"\"\"\n+\n+import functools\n+\n+from ovs.flows.kv import KVParser, KVDecoders, nested_kv_decoder\n+from ovs.flows.ofp_fields import field_decoders\n+from ovs.flows.flow import Flow, Section\n+from ovs.flows.list import ListDecoders, nested_list_decoder\n+from ovs.flows.decoders import (\n+ decode_default,\n+ decode_flag,\n+ decode_int,\n+ decode_time,\n+ decode_mask,\n+ IPMask,\n+ EthMask,\n+ decode_free_output,\n+ decode_nat,\n+)\n+from ovs.flows.ofp_act import (\n+ decode_output,\n+ decode_field,\n+ decode_controller,\n+ decode_bundle,\n+ decode_bundle_load,\n+ decode_encap_ethernet,\n+ decode_load_field,\n+ decode_set_field,\n+ decode_move_field,\n+ decode_dec_ttl,\n+ decode_chk_pkt_larger,\n+ decode_zone,\n+ decode_exec,\n+ decode_learn,\n+)\n+\n+\n+class OFPFlow(Flow):\n+ \"\"\"OFPFLow represents an OpenFlow Flow\"\"\"\n+\n+ def __init__(self, sections, orig=\"\", id=None):\n+ \"\"\"Constructor\"\"\"\n+ super(OFPFlow, self).__init__(sections, orig, id)\n+\n+ def __str__(self):\n+ if self._orig:\n+ return self._orig\n+ else:\n+ return self.to_string()\n+\n+ def to_string(self):\n+ \"\"\"Print a text representation of the flow\"\"\"\n+ string = \"Info: {}\\n\" + self.info\n+ string += \"Match : {}\\n\" + self.match\n+ string += \"Actions: {}\\n \" + self.actions\n+ return string\n+\n+\n+class OFPFlowFactory:\n+ \"\"\"OpenFlow Flow Factory is a class capable of creating OFPFLow objects\"\"\"\n+\n+ def __init__(self):\n+ self.info_decoders = self._info_decoders()\n+ self.match_decoders = KVDecoders(\n+ {**self._field_decoders(), **self._flow_match_decoders()}\n+ )\n+ self.act_decoders = self._act_decoders()\n+\n+ def from_string(self, ofp_string, id=None):\n+ \"\"\"Parse a ofproto flow string\n+\n+ The string is expected to have the follwoing format:\n+ [flow data] [match] actions=[actions]\n+\n+ :param ofp_string: a ofproto string as dumped by ovs-ofctl tool\n+ :type ofp_string: str\n+\n+ :return: an OFPFlow with the content of the flow string\n+ :rtype: OFPFlow\n+ \"\"\"\n+ if \" reply \" in ofp_string:\n+ return None\n+\n+ sections = list()\n+ parts = ofp_string.split(\"actions=\")\n+ if len(parts) != 2:\n+ raise ValueError(\"malformed ofproto flow: %s\" % ofp_string)\n+\n+ actions = parts[1]\n+\n+ field_parts = parts[0].rstrip(\" \").rpartition(\" \")\n+ if len(field_parts) != 3:\n+ raise ValueError(\"malformed ofproto flow: %s\" % ofp_string)\n+\n+ info = field_parts[0]\n+ match = field_parts[2]\n+\n+ iparser = KVParser(self.info_decoders)\n+ iparser.parse(info)\n+ isection = Section(\n+ name=\"info\",\n+ pos=ofp_string.find(info),\n+ string=info,\n+ data=iparser.kv(),\n+ )\n+ sections.append(isection)\n+\n+ mparser = KVParser(self.match_decoders)\n+ mparser.parse(match)\n+ msection = Section(\n+ name=\"match\",\n+ pos=ofp_string.find(match),\n+ string=match,\n+ data=mparser.kv(),\n+ )\n+ sections.append(msection)\n+\n+ aparser = KVParser(self.act_decoders)\n+ aparser.parse(actions)\n+ asection = Section(\n+ name=\"actions\",\n+ pos=ofp_string.find(actions),\n+ string=actions,\n+ data=aparser.kv(),\n+ is_list=True,\n+ )\n+ sections.append(asection)\n+\n+ return OFPFlow(sections, ofp_string, id)\n+\n+ @classmethod\n+ def _info_decoders(cls):\n+ \"\"\"Generate the match decoders\"\"\"\n+ info = {\n+ \"table\": decode_int,\n+ \"duration\": decode_time,\n+ \"n_packet\": decode_int,\n+ \"n_bytes\": decode_int,\n+ \"cookie\": decode_int,\n+ \"idle_timeout\": decode_time,\n+ \"hard_timeout\": decode_time,\n+ \"hard_age\": decode_time,\n+ }\n+ return KVDecoders(info)\n+\n+ @classmethod\n+ def _flow_match_decoders(cls):\n+ \"\"\"Returns the decoders for key-values that are part of the flow match\n+ but not a flow field\"\"\"\n+ return {\n+ \"priority\": decode_int,\n+ }\n+\n+ @classmethod\n+ def _field_decoders(cls):\n+ shorthands = [\n+ \"eth\",\n+ \"ip\",\n+ \"ipv6\",\n+ \"icmp\",\n+ \"icmp6\",\n+ \"tcp\",\n+ \"tcp6\",\n+ \"udp\",\n+ \"udp6\",\n+ \"sctp\",\n+ \"arp\",\n+ \"rarp\",\n+ \"mpls\",\n+ \"mplsm\",\n+ ]\n+\n+ fields = {**field_decoders, **{key: decode_flag for key in shorthands}}\n+\n+ # vlan_vid field is special. Although it is technically 12 bit wide,\n+ # bit 12 is allowed to be set to 1 to indicate that the vlan header is\n+ # present (see section VLAN FIELDS in\n+ # http://www.openvswitch.org/support/dist-docs/ovs-fields.7.txt)\n+ # Therefore, override the generated vlan_vid field size\n+ fields[\"vlan_vid\"] = decode_mask(13)\n+ return fields\n+\n+ @classmethod\n+ def _output_actions_decoders(cls):\n+ \"\"\"Returns the decoders for the output actions\"\"\"\n+ return {\n+ \"output\": decode_output,\n+ \"drop\": decode_flag,\n+ \"controller\": decode_controller,\n+ \"enqueue\": nested_list_decoder(\n+ ListDecoders([(\"port\", decode_default), (\"queue\", int)]),\n+ delims=[\",\", \":\"],\n+ ),\n+ \"bundle\": decode_bundle,\n+ \"bundle_load\": decode_bundle_load,\n+ \"group\": decode_default,\n+ }\n+\n+ @classmethod\n+ def _encap_actions_decoders(cls):\n+ \"\"\"Returns the decoders for the encap actions\"\"\"\n+\n+ return {\n+ \"pop_vlan\": decode_flag,\n+ \"strip_vlan\": decode_flag,\n+ \"push_vlan\": decode_default,\n+ \"decap\": decode_flag,\n+ \"encap\": nested_kv_decoder(\n+ KVDecoders(\n+ {\n+ \"nsh\": nested_kv_decoder(\n+ KVDecoders(\n+ {\n+ \"md_type\": decode_default,\n+ \"tlv\": nested_list_decoder(\n+ ListDecoders(\n+ [\n+ (\"class\", decode_int),\n+ (\"type\", decode_int),\n+ (\"value\", decode_int),\n+ ]\n+ )\n+ ),\n+ }\n+ )\n+ ),\n+ },\n+ default=None,\n+ default_free=decode_encap_ethernet,\n+ )\n+ ),\n+ }\n+\n+ @classmethod\n+ def _field_action_decoders(cls):\n+ \"\"\"Returns the decoders for the field modification actions\"\"\"\n+ # Field modification actions\n+ field_default_decoders = [\n+ \"set_mpls_label\",\n+ \"set_mpls_tc\",\n+ \"set_mpls_ttl\",\n+ \"mod_nw_tos\",\n+ \"mod_nw_ecn\",\n+ \"mod_tcp_src\",\n+ \"mod_tcp_dst\",\n+ ]\n+ return {\n+ \"load\": decode_load_field,\n+ \"set_field\": functools.partial(\n+ decode_set_field, KVDecoders(cls._field_decoders())\n+ ),\n+ \"move\": decode_move_field,\n+ \"mod_dl_dst\": EthMask,\n+ \"mod_dl_src\": EthMask,\n+ \"mod_nw_dst\": IPMask,\n+ \"mod_nw_src\": IPMask,\n+ \"dec_ttl\": decode_dec_ttl,\n+ \"dec_mpls_ttl\": decode_flag,\n+ \"dec_nsh_ttl\": decode_flag,\n+ \"check_pkt_larger\": decode_chk_pkt_larger,\n+ **{field: decode_default for field in field_default_decoders},\n+ }\n+\n+ @classmethod\n+ def _meta_action_decoders(cls):\n+ \"\"\"Returns the decoders for the metadata actions\"\"\"\n+ meta_default_decoders = [\"set_tunnel\", \"set_tunnel64\", \"set_queue\"]\n+ return {\n+ \"pop_queue\": decode_flag,\n+ **{field: decode_default for field in meta_default_decoders},\n+ }\n+\n+ @classmethod\n+ def _fw_action_decoders(cls):\n+ \"\"\"Returns the decoders for the Firewalling actions\"\"\"\n+ return {\n+ \"ct\": nested_kv_decoder(\n+ KVDecoders(\n+ {\n+ \"commit\": decode_flag,\n+ \"zone\": decode_zone,\n+ \"table\": decode_int,\n+ \"nat\": decode_nat,\n+ \"force\": decode_flag,\n+ \"exec\": functools.partial(\n+ decode_exec,\n+ KVDecoders(\n+ {\n+ **cls._encap_actions_decoders(),\n+ **cls._field_action_decoders(),\n+ **cls._meta_action_decoders(),\n+ }\n+ ),\n+ ),\n+ \"alg\": decode_default,\n+ }\n+ )\n+ ),\n+ \"ct_clear\": decode_flag,\n+ }\n+\n+ @classmethod\n+ def _control_action_decoders(cls):\n+ return {\n+ \"resubmit\": nested_list_decoder(\n+ ListDecoders(\n+ [\n+ (\"port\", decode_default),\n+ (\"table\", decode_int),\n+ (\"ct\", decode_flag),\n+ ]\n+ )\n+ ),\n+ \"push\": decode_field,\n+ \"pop\": decode_field,\n+ \"exit\": decode_flag,\n+ \"multipath\": nested_list_decoder(\n+ ListDecoders(\n+ [\n+ (\"fields\", decode_default),\n+ (\"basis\", decode_int),\n+ (\"algorithm\", decode_default),\n+ (\"n_links\", decode_int),\n+ (\"arg\", decode_int),\n+ (\"dst\", decode_field),\n+ ]\n+ )\n+ ),\n+ }\n+\n+ @classmethod\n+ def _clone_actions_decoders(cls, action_decoders):\n+ \"\"\"Generate the decoders for clone actions\n+\n+ Args:\n+ action_decoders (dict): The decoders of the supported nested\n+ actions\n+ \"\"\"\n+ return {\n+ \"learn\": decode_learn(\n+ {\n+ **action_decoders,\n+ \"fin_timeout\": nested_kv_decoder(\n+ KVDecoders(\n+ {\n+ \"idle_timeout\": decode_time,\n+ \"hard_timeout\": decode_time,\n+ }\n+ )\n+ ),\n+ }\n+ ),\n+ \"clone\": functools.partial(\n+ decode_exec, KVDecoders(action_decoders)\n+ ),\n+ }\n+\n+ @classmethod\n+ def _other_action_decoders(cls):\n+ \"\"\"Recoders for other actions (see man(7) ovs-actions)\"\"\"\n+ return {\n+ \"conjunction\": nested_list_decoder(\n+ ListDecoders(\n+ [(\"id\", decode_int), (\"k\", decode_int), (\"n\", decode_int)]\n+ ),\n+ delims=[\",\", \"/\"],\n+ ),\n+ \"note\": decode_default,\n+ \"sample\": nested_kv_decoder(\n+ KVDecoders(\n+ {\n+ \"probability\": decode_int,\n+ \"collector_set_id\": decode_int,\n+ \"obs_domain_id\": decode_int,\n+ \"obs_point_id\": decode_int,\n+ \"sampling_port\": decode_default,\n+ \"ingress\": decode_flag,\n+ \"egress\": decode_flag,\n+ }\n+ )\n+ ),\n+ }\n+\n+ @classmethod\n+ def _act_decoders(cls):\n+ \"\"\"Generate the actions decoders\"\"\"\n+\n+ actions = {\n+ **cls._output_actions_decoders(),\n+ **cls._encap_actions_decoders(),\n+ **cls._field_action_decoders(),\n+ **cls._meta_action_decoders(),\n+ **cls._fw_action_decoders(),\n+ **cls._control_action_decoders(),\n+ **cls._other_action_decoders(),\n+ }\n+ clone_actions = cls._clone_actions_decoders(actions)\n+ actions.update(clone_actions)\n+ return KVDecoders(actions, default_free=decode_free_output)\ndiff --git a/python/ovs/flows/ofp_act.py b/python/ovs/flows/ofp_act.py\nnew file mode 100644\nindex 000000000..bc6574999\n--- /dev/null\n+++ b/python/ovs/flows/ofp_act.py\n@@ -0,0 +1,233 @@\n+\"\"\" Defines decoders for openflow actions\n+\"\"\"\n+\n+import functools\n+\n+from ovs.flows.kv import nested_kv_decoder, KVDecoders, KeyValue, KVParser\n+from ovs.flows.decoders import (\n+ decode_default,\n+ decode_time,\n+ decode_flag,\n+ decode_int,\n+)\n+from ovs.flows.ofp_fields import field_decoders\n+\n+\n+def decode_output(value):\n+ \"\"\"Decodes the output value\n+\n+ Does not support field specification\n+ \"\"\"\n+ if len(value.split(\",\")) > 1:\n+ return nested_kv_decoder()(value)\n+ try:\n+ return {\"port\": int(value)}\n+ except ValueError:\n+ return {\"port\": value.strip('\"')}\n+\n+\n+def decode_controller(value):\n+ \"\"\"Decodes the controller action\"\"\"\n+ if not value:\n+ return KeyValue(\"output\", \"controller\")\n+ else:\n+ # Try controller:max_len\n+ try:\n+ max_len = int(value)\n+ return {\n+ \"max_len\": max_len,\n+ }\n+ except ValueError:\n+ pass\n+ # controller(key[=val], ...)\n+ return nested_kv_decoder()(value)\n+\n+\n+def decode_bundle_load(value):\n+ return decode_bundle(value, True)\n+\n+\n+def decode_bundle(value, load=False):\n+ \"\"\"Decode bundle action\"\"\"\n+ result = {}\n+ keys = [\"fields\", \"basis\", \"algorithm\", \"ofport\"]\n+ if load:\n+ keys.append(\"dst\")\n+\n+ for key in keys:\n+ parts = value.partition(\",\")\n+ nvalue = parts[0]\n+ value = parts[2]\n+ if key == \"ofport\":\n+ continue\n+ result[key] = decode_default(nvalue)\n+\n+ # Handle members:\n+ mvalues = value.split(\"members:\")\n+ result[\"members\"] = [int(port) for port in mvalues[1].split(\",\")]\n+ return result\n+\n+\n+def decode_encap_ethernet(value):\n+ \"\"\"Decodes encap ethernet value\"\"\"\n+ return \"ethernet\", int(value, 0)\n+\n+\n+def decode_field(value):\n+ \"\"\"Decodes a field as defined in the 'Field Specification' of the actions\n+ man page: http://www.openvswitch.org/support/dist-docs/ovs-actions.7.txt\n+ \"\"\"\n+ parts = value.strip(\"]\\n\\r\").split(\"[\")\n+ result = {\n+ \"field\": parts[0],\n+ }\n+\n+ if len(parts) > 1 and parts[1]:\n+ field_range = parts[1].split(\"..\")\n+ start = field_range[0]\n+ end = field_range[1] if len(field_range) > 1 else start\n+ if start:\n+ result[\"start\"] = int(start)\n+ if end:\n+ result[\"end\"] = int(end)\n+\n+ return result\n+\n+\n+def decode_load_field(value):\n+ \"\"\"Decodes 'load:value->dst' actions\"\"\"\n+ parts = value.split(\"->\")\n+ if len(parts) != 2:\n+ raise ValueError(\"Malformed load action : %s\" % value)\n+\n+ # If the load action is performed within a learn() action,\n+ # The value can be specified as another field.\n+ try:\n+ return {\"value\": int(parts[0], 0), \"dst\": decode_field(parts[1])}\n+ except ValueError:\n+ return {\"src\": decode_field(parts[0]), \"dst\": decode_field(parts[1])}\n+\n+\n+def decode_set_field(field_decoders, value):\n+ \"\"\"Decodes 'set_field:value/mask->dst' actions\n+\n+ The value is decoded by field_decoders which is a KVDecoders instance\n+ Args:\n+ field_decoders\n+ \"\"\"\n+ parts = value.split(\"->\")\n+ if len(parts) != 2:\n+ raise ValueError(\"Malformed set_field action : %s\" % value)\n+\n+ val = parts[0]\n+ dst = parts[1]\n+\n+ val_result = field_decoders.decode(dst, val)\n+\n+ return {\n+ \"value\": {val_result[0]: val_result[1]},\n+ \"dst\": decode_field(dst),\n+ }\n+\n+\n+def decode_move_field(value):\n+ \"\"\"Decodes 'move:src->dst' actions\"\"\"\n+ parts = value.split(\"->\")\n+ if len(parts) != 2:\n+ raise ValueError(\"Malformed move action : %s\" % value)\n+\n+ return {\n+ \"src\": decode_field(parts[0]),\n+ \"dst\": decode_field(parts[1]),\n+ }\n+\n+\n+def decode_dec_ttl(value):\n+ \"\"\"Decodes dec_ttl and dec_ttl(id, id[2], ...) actions\"\"\"\n+ if not value:\n+ return True\n+ return [int(idx) for idx in value.split(\",\")]\n+\n+\n+def decode_chk_pkt_larger(value):\n+ \"\"\"Decodes 'check_pkt_larger(pkt_len)->dst' actions\"\"\"\n+ parts = value.split(\"->\")\n+ if len(parts) != 2:\n+ raise ValueError(\"Malformed check_pkt_larger action : %s\" % value)\n+\n+ pkt_len = int(parts[0].strip(\"()\"))\n+ dst = decode_field(parts[1])\n+ return {\"pkt_len\": pkt_len, \"dst\": dst}\n+\n+\n+# CT decoders\n+def decode_zone(value):\n+ \"\"\"Decodes the 'zone' keyword of the ct action\"\"\"\n+ try:\n+ return int(value, 0)\n+ except ValueError:\n+ pass\n+ return decode_field(value)\n+\n+\n+def decode_exec(action_decoders, value):\n+ \"\"\"Decodes the 'exec' keyword of the ct action\n+\n+ Args:\n+ decode_actions (KVDecoders): the decoders to be used to decode the\n+ nested exec\n+ value (string): the string to be decoded\n+ \"\"\"\n+ exec_parser = KVParser(action_decoders)\n+ exec_parser.parse(value)\n+ return [{kv.key: kv.value} for kv in exec_parser.kv()]\n+\n+\n+def decode_learn(action_decoders):\n+ \"\"\"Create the decoder to be used to decode the 'learn' action.\n+\n+ The learn action can include any nested action, therefore we need decoders\n+ for all possible actions.\n+\n+ Args:\n+ action_decoders (dict): dictionary of decoders to be used in nested\n+ action decoding\n+\n+ \"\"\"\n+\n+ def decode_learn_field(decoder, value):\n+ \"\"\"Generates a decoder to be used for the 'field' argument of the\n+ 'learn' action.\n+\n+ The field can hold a value that should be decoded, either as a field,\n+ or as a the value (see man(7) ovs-actions)\n+\n+ Args:\n+ decoder (callable): The decoder\n+\n+ \"\"\"\n+ if value in field_decoders.keys():\n+ # It's a field\n+ return value\n+ else:\n+ return decoder(value)\n+\n+ learn_field_decoders = {\n+ field: functools.partial(decode_learn_field, decoder)\n+ for field, decoder in field_decoders.items()\n+ }\n+ learn_decoders = {\n+ **action_decoders,\n+ **learn_field_decoders,\n+ \"idle_timeout\": decode_time,\n+ \"hard_timeout\": decode_time,\n+ \"priority\": decode_int,\n+ \"cooke\": decode_int,\n+ \"send_flow_rem\": decode_flag,\n+ \"table\": decode_int,\n+ \"delete_learned\": decode_flag,\n+ \"limit\": decode_int,\n+ \"result_dst\": decode_field,\n+ }\n+\n+ return functools.partial(decode_exec, KVDecoders(learn_decoders))\n", "prefixes": [ "ovs-dev", "v1", "07/18" ] }