diff mbox series

[ovs-dev,v1,16/18] python: add unit tests for openflow parsing

Message ID 20211122112256.2011194-17-amorenoz@redhat.com
State Changes Requested
Headers show
Series python: add flow parsing library | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed

Commit Message

Adrian Moreno Nov. 22, 2021, 11:22 a.m. UTC
Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
---
 python/automake.mk           |   3 +-
 python/ovs/tests/test_ofp.py | 524 +++++++++++++++++++++++++++++++++++
 2 files changed, 526 insertions(+), 1 deletion(-)
 create mode 100644 python/ovs/tests/test_ofp.py

Comments

Eelco Chaudron Dec. 24, 2021, 1:14 p.m. UTC | #1
Other than ending comments with a dot and an extra newline, it looks fine.

Acked-by: Eelco Chaudron <echaudro@redhat.com>

On 22 Nov 2021, at 12:22, Adrian Moreno wrote:

> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
> ---
>  python/automake.mk           |   3 +-
>  python/ovs/tests/test_ofp.py | 524 +++++++++++++++++++++++++++++++++++
>  2 files changed, 526 insertions(+), 1 deletion(-)
>  create mode 100644 python/ovs/tests/test_ofp.py
>
> diff --git a/python/automake.mk b/python/automake.mk
> index 41973797c..713f1d1a4 100644
> --- a/python/automake.mk
> +++ b/python/automake.mk
> @@ -55,7 +55,8 @@ ovs_pyfiles = \
>
>  ovs_tests = \
>  	python/ovs/tests/test_kv.py \
> -	python/ovs/tests/test_list.py
> +	python/ovs/tests/test_list.py \
> +	python/ovs/tests/test_ofp.py
>
>
>  # These python files are used at build time but not runtime,
> diff --git a/python/ovs/tests/test_ofp.py b/python/ovs/tests/test_ofp.py
> new file mode 100644
> index 000000000..975be17d1
> --- /dev/null
> +++ b/python/ovs/tests/test_ofp.py
> @@ -0,0 +1,524 @@
> +import netaddr
> +import pytest
> +
> +from ovs.flows.ofp import OFPFlowFactory
> +from ovs.flows.kv import KeyValue
> +from ovs.flows.decoders import EthMask, IPMask, decode_mask
> +
> +
> +@pytest.mark.parametrize(
> +    "input_string,expected",
> +    [
> +        (
> +            "actions=local,3,4,5,output:foo",
> +            [
> +                KeyValue("output", {"port": "local"}),
> +                KeyValue("output", {"port": 3}),
> +                KeyValue("output", {"port": 4}),
> +                KeyValue("output", {"port": 5}),
> +                KeyValue("output", {"port": "foo"}),
> +            ],
> +        ),
> +        (
> +            "actions=controller,controller:200",
> +            [
> +                KeyValue("output", "controller"),
> +                KeyValue("controller", {"max_len": 200}),
> +            ],
> +        ),
> +        (
> +            "actions=enqueue(foo,42),enqueue:foo:42,enqueue(bar,4242)",
> +            [
> +                KeyValue("enqueue", {"port": "foo", "queue": 42}),
> +                KeyValue("enqueue", {"port": "foo", "queue": 42}),
> +                KeyValue("enqueue", {"port": "bar", "queue": 4242}),
> +            ],
> +        ),
> +        (
> +            "actions=bundle(eth_src,0,hrw,ofport,members:4,8)",
> +            [
> +                KeyValue(
> +                    "bundle",
> +                    {
> +                        "fields": "eth_src",
> +                        "basis": 0,
> +                        "algorithm": "hrw",
> +                        "members": [4, 8],
> +                    },
> +                ),
> +            ],
> +        ),
> +        (
> +            "actions=bundle_load(eth_src,0,hrw,ofport,reg0,members:4,8)",
> +            [
> +                KeyValue(
> +                    "bundle_load",
> +                    {
> +                        "fields": "eth_src",
> +                        "basis": 0,
> +                        "algorithm": "hrw",
> +                        "dst": "reg0",
> +                        "members": [4, 8],
> +                    },
> +                ),
> +            ],
> +        ),
> +        (
> +            "actions=group:3",
> +            [KeyValue("group", 3)],
> +        ),
> +        (
> +            "actions=strip_vlan",
> +            [KeyValue("strip_vlan", True)],
> +        ),
> +        (
> +            "actions=pop_vlan",
> +            [KeyValue("pop_vlan", True)],
> +        ),
> +        (
> +            "actions=push_vlan:0x8100",
> +            [KeyValue("push_vlan", 0x8100)],
> +        ),
> +        (
> +            "actions=push_mpls:0x8848",
> +            [KeyValue("push_mpls", 0x8848)],
> +        ),
> +        (
> +            "actions=pop_mpls:0x8848",
> +            [KeyValue("pop_mpls", 0x8848)],
> +        ),
> +        (
> +            "actions=pop_mpls:0x8848",
> +            [KeyValue("pop_mpls", 0x8848)],
> +        ),
> +        (
> +            "actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))",
> +            [
> +                KeyValue(
> +                    "encap",
> +                    {
> +                        "nsh": {
> +                            "md_type": 2,
> +                            "tlv": {
> +                                "class": 0x1000,
> +                                "type": 10,
> +                                "value": 0x12345678,
> +                            },
> +                        }
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=encap(0x0800)",
> +            [
> +                KeyValue(
> +                    "encap",
> +                    {"ethernet": 0x800},
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=load:0x001122334455->eth_src",
> +            [
> +                KeyValue(
> +                    "load",
> +                    {"value": 0x001122334455, "dst": {"field": "eth_src"}},
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=load:1->eth_src[1]",
> +            [
> +                KeyValue(
> +                    "load",
> +                    {
> +                        "value": 1,
> +                        "dst": {"field": "eth_src", "start": 1, "end": 1},
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=learn(load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[])",
> +            [
> +                KeyValue(
> +                    "learn",
> +                    [
> +                        {
> +                            "load": {
> +                                "src": {"field": "NXM_NX_TUN_ID"},
> +                                "dst": {"field": "NXM_NX_TUN_ID"},
> +                            }
> +                        }
> +                    ],
> +                ),
> +            ],
> +        ),
> +        (
> +            "actions=set_field:00:11:22:33:44:55->eth_src",
> +            [
> +                KeyValue(
> +                    "set_field",
> +                    {
> +                        "value": {"eth_src": EthMask("00:11:22:33:44:55")},
> +                        "dst": {"field": "eth_src"},
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_src",
> +            [
> +                KeyValue(
> +                    "set_field",
> +                    {
> +                        "value": {
> +                            "eth_src": EthMask(
> +                                "01:00:00:00:00:00/01:00:00:00:00:00"
> +                            )
> +                        },
> +                        "dst": {"field": "eth_src"},
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=set_field:0x10ff->vlan_vid",
> +            [
> +                KeyValue(
> +                    "set_field",
> +                    {
> +                        "value": {"vlan_vid": decode_mask(13)("0x10ff")},
> +                        "dst": {"field": "vlan_vid"},
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=move:reg0[0..5]->reg1[16..31]",
> +            [
> +                KeyValue(
> +                    "move",
> +                    {
> +                        "src": {"field": "reg0", "start": 0, "end": 5},
> +                        "dst": {"field": "reg1", "start": 16, "end": 31},
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=mod_dl_dst:00:11:22:33:44:55",
> +            [KeyValue("mod_dl_dst", EthMask("00:11:22:33:44:55"))],
> +        ),
> +        (
> +            "actions=mod_nw_dst:192.168.1.1",
> +            [KeyValue("mod_nw_dst", IPMask("192.168.1.1"))],
> +        ),
> +        (
> +            "actions=mod_nw_dst:fe80::ec17:7bff:fe61:7aac",
> +            [KeyValue("mod_nw_dst", IPMask("fe80::ec17:7bff:fe61:7aac"))],
> +        ),
> +        (
> +            "actions=dec_ttl,dec_ttl(1,2,3)",
> +            [KeyValue("dec_ttl", True), KeyValue("dec_ttl", [1, 2, 3])],
> +        ),
> +        (
> +            "actions=set_mpls_label:0x100,set_mpls_tc:2,set_mpls_ttl:10",
> +            [
> +                KeyValue("set_mpls_label", 0x100),
> +                KeyValue("set_mpls_tc", 2),
> +                KeyValue("set_mpls_ttl", 10),
> +            ],
> +        ),
> +        (
> +            "actions=check_pkt_larger(100)->reg0[10]",
> +            [
> +                KeyValue(
> +                    "check_pkt_larger",
> +                    {
> +                        "pkt_len": 100,
> +                        "dst": {"field": "reg0", "start": 10, "end": 10},
> +                    },
> +                ),
> +            ],
> +        ),
> +        (
> +            "actions=pop_queue,set_tunnel:0x10,set_tunnel64:0x65000,set_queue=3",   # noqa: E501
> +            [
> +                KeyValue("pop_queue", True),
> +                KeyValue("set_tunnel", 0x10),
> +                KeyValue("set_tunnel64", 0x65000),
> +                KeyValue("set_queue", 3),
> +            ],
> +        ),
> +        (
> +            "actions=ct(zone=10,table=2,nat(snat=192.168.0.0-192.168.0.200:1000-2000,random))",  # noqa: E501
> +            [
> +                KeyValue(
> +                    "ct",
> +                    {
> +                        "zone": 10,
> +                        "table": 2,
> +                        "nat": {
> +                            "type": "snat",
> +                            "addrs": {
> +                                "start": netaddr.IPAddress("192.168.0.0"),
> +                                "end": netaddr.IPAddress("192.168.0.200"),
> +                            },
> +                            "ports": {
> +                                "start": 1000,
> +                                "end": 2000,
> +                            },
> +                            "random": True,
> +                        },
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=ct(commit,zone=NXM_NX_REG13[0..15],table=2,exec(load:0->NXM_NX_CT_LABEL[0]))",  # noqa: E501
> +            [
> +                KeyValue(
> +                    "ct",
> +                    {
> +                        "commit": True,
> +                        "zone": {
> +                            "field": "NXM_NX_REG13",
> +                            "start": 0,
> +                            "end": 15,
> +                        },
> +                        "table": 2,
> +                        "exec": [
> +                            {
> +                                "load": {
> +                                    "value": 0,
> +                                    "dst": {
> +                                        "field": "NXM_NX_CT_LABEL",
> +                                        "start": 0,
> +                                        "end": 0,
> +                                    },
> +                                },
> +                            },
> +                        ],
> +                    },
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=load:0x1->NXM_NX_REG10[7],learn(table=69,delete_learned,cookie=0xda6f52b0,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.30.204.105,nw_proto=6,NXM_OF_TCP_SRC[]=NXM_OF_TCP_DST[],load:0x1->NXM_NX_REG10[7])",  # noqa: E501
> +            [
> +                KeyValue(
> +                    "load",
> +                    {
> +                        "value": 1,
> +                        "dst": {"field": "NXM_NX_REG10", "start": 7, "end": 7},
> +                    },
> +                ),
> +                KeyValue(
> +                    "learn",
> +                    [
> +                        {"table": 69},
> +                        {"delete_learned": True},
> +                        {"cookie": 3664728752},
> +                        {"OXM_OF_METADATA[]": True},
> +                        {"eth_type": 2048},
> +                        {"NXM_OF_IP_SRC[]": True},
> +                        {"ip_dst": IPMask("172.30.204.105/32")},
> +                        {"nw_proto": 6},
> +                        {"NXM_OF_TCP_SRC[]": "NXM_OF_TCP_DST[]"},
> +                        {
> +                            "load": {
> +                                "value": 1,
> +                                "dst": {
> +                                    "field": "NXM_NX_REG10",
> +                                    "start": 7,
> +                                    "end": 7,
> +                                },
> +                            }
> +                        },
> +                    ],
> +                ),
> +            ],
> +        ),
> +        (
> +            "actions=resubmit(,8),resubmit:3,resubmit(1,2,ct)",
> +            [
> +                KeyValue("resubmit", {"port": "", "table": 8}),
> +                KeyValue("resubmit", {"port": 3}),
> +                KeyValue("resubmit", {"port": 1, "table": 2, "ct": True}),
> +            ],
> +        ),
> +        (
> +            "actions=clone(ct_clear,load:0->NXM_NX_REG11[],load:0->NXM_NX_REG12[],load:0->NXM_NX_REG13[],load:0x1d->NXM_NX_REG13[],load:0x1f->NXM_NX_REG11[],load:0x1c->NXM_NX_REG12[],load:0x11->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],load:0->NXM_NX_REG10[],load:0->NXM_NX_REG15[],load:0->NXM_NX_REG0[],load:0->NXM_NX_REG1[],load:0->NXM_NX_REG2[],load:0->NXM_NX_REG3[],load:0->NXM_NX_REG4[],load:0->NXM_NX_REG5[],load:0->NXM_NX_REG6[],load:0->NXM_NX_REG7[],load:0->NXM_NX_REG8[],load:0->NXM_NX_REG9[],resubmit(,8))",  # noqa: E501
> +            [
> +                KeyValue(
> +                    "clone",
> +                    [
> +                        {"ct_clear": True},
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG11"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG12"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG13"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 29,
> +                                "dst": {"field": "NXM_NX_REG13"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 31,
> +                                "dst": {"field": "NXM_NX_REG11"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 28,
> +                                "dst": {"field": "NXM_NX_REG12"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 17,
> +                                "dst": {"field": "OXM_OF_METADATA"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 2,
> +                                "dst": {"field": "NXM_NX_REG14"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG10"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG15"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG0"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG1"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG2"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG3"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG4"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG5"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG6"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG7"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG8"},
> +                            }
> +                        },
> +                        {
> +                            "load": {
> +                                "value": 0,
> +                                "dst": {"field": "NXM_NX_REG9"},
> +                            }
> +                        },
> +                        {"resubmit": {"port": "", "table": 8}},
> +                    ],
> +                )
> +            ],
> +        ),
> +        (
> +            "actions=conjunction(1234, 1/2),note:00.00.11.22.33.ff,sample(probability=123,collector_set_id=0x123,obs_domain_id=0x123,obs_point_id=0x123,sampling_port=inport0,ingress)",  # noqa: E501
> +            [
> +                KeyValue("conjunction", {"id": 1234, "k": 1, "n": 2}),
> +                KeyValue("note", "00.00.11.22.33.ff"),
> +                KeyValue(
> +                    "sample",
> +                    {
> +                        "probability": 123,
> +                        "collector_set_id": 0x123,
> +                        "obs_domain_id": 0x123,
> +                        "obs_point_id": 0x123,
> +                        "sampling_port": "inport0",
> +                        "ingress": True,
> +                    },
> +                ),
> +            ],
> +        ),
> +    ],
> +)

Add new line… (also by other tests?)

> +def test_act(input_string, expected):
> +    ofp = OFPFlowFactory().from_string(input_string)
> +    actions = ofp.actions_kv
> +    for i in range(len(expected)):
> +        assert expected[i].key == actions[i].key
> +        assert expected[i].value == actions[i].value
> +
> +        # Assert positions relative to action string are OK
> +        apos = ofp.section("actions").pos
> +        astring = ofp.section("actions").string
> +
> +        kpos = actions[i].meta.kpos
> +        kstr = actions[i].meta.kstring
> +        vpos = actions[i].meta.vpos
> +        vstr = actions[i].meta.vstring
> +        assert astring[kpos : kpos + len(kstr)] == kstr
> +        if vpos != -1:
> +            assert astring[vpos : vpos + len(vstr)] == vstr
> +
> +        # assert astring meta is correct
> +        assert input_string[apos : apos + len(astring)] == astring
> -- 
> 2.31.1
Adrian Moreno Jan. 13, 2022, 4:23 p.m. UTC | #2
On 12/24/21 14:14, Eelco Chaudron wrote:
> Other than ending comments with a dot and an extra newline, it looks fine.
> 
> Acked-by: Eelco Chaudron <echaudro@redhat.com>
> 
> On 22 Nov 2021, at 12:22, Adrian Moreno wrote:
> 
>> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
>> ---
>>   python/automake.mk           |   3 +-
>>   python/ovs/tests/test_ofp.py | 524 +++++++++++++++++++++++++++++++++++
>>   2 files changed, 526 insertions(+), 1 deletion(-)
>>   create mode 100644 python/ovs/tests/test_ofp.py
>>
>> diff --git a/python/automake.mk b/python/automake.mk
>> index 41973797c..713f1d1a4 100644
>> --- a/python/automake.mk
>> +++ b/python/automake.mk
>> @@ -55,7 +55,8 @@ ovs_pyfiles = \
>>
>>   ovs_tests = \
>>   	python/ovs/tests/test_kv.py \
>> -	python/ovs/tests/test_list.py
>> +	python/ovs/tests/test_list.py \
>> +	python/ovs/tests/test_ofp.py
>>
>>
>>   # These python files are used at build time but not runtime,
>> diff --git a/python/ovs/tests/test_ofp.py b/python/ovs/tests/test_ofp.py
>> new file mode 100644
>> index 000000000..975be17d1
>> --- /dev/null
>> +++ b/python/ovs/tests/test_ofp.py
>> @@ -0,0 +1,524 @@
>> +import netaddr
>> +import pytest
>> +
>> +from ovs.flows.ofp import OFPFlowFactory
>> +from ovs.flows.kv import KeyValue
>> +from ovs.flows.decoders import EthMask, IPMask, decode_mask
>> +
>> +
>> +@pytest.mark.parametrize(
>> +    "input_string,expected",
>> +    [
>> +        (
>> +            "actions=local,3,4,5,output:foo",
>> +            [
>> +                KeyValue("output", {"port": "local"}),
>> +                KeyValue("output", {"port": 3}),
>> +                KeyValue("output", {"port": 4}),
>> +                KeyValue("output", {"port": 5}),
>> +                KeyValue("output", {"port": "foo"}),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=controller,controller:200",
>> +            [
>> +                KeyValue("output", "controller"),
>> +                KeyValue("controller", {"max_len": 200}),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=enqueue(foo,42),enqueue:foo:42,enqueue(bar,4242)",
>> +            [
>> +                KeyValue("enqueue", {"port": "foo", "queue": 42}),
>> +                KeyValue("enqueue", {"port": "foo", "queue": 42}),
>> +                KeyValue("enqueue", {"port": "bar", "queue": 4242}),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=bundle(eth_src,0,hrw,ofport,members:4,8)",
>> +            [
>> +                KeyValue(
>> +                    "bundle",
>> +                    {
>> +                        "fields": "eth_src",
>> +                        "basis": 0,
>> +                        "algorithm": "hrw",
>> +                        "members": [4, 8],
>> +                    },
>> +                ),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=bundle_load(eth_src,0,hrw,ofport,reg0,members:4,8)",
>> +            [
>> +                KeyValue(
>> +                    "bundle_load",
>> +                    {
>> +                        "fields": "eth_src",
>> +                        "basis": 0,
>> +                        "algorithm": "hrw",
>> +                        "dst": "reg0",
>> +                        "members": [4, 8],
>> +                    },
>> +                ),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=group:3",
>> +            [KeyValue("group", 3)],
>> +        ),
>> +        (
>> +            "actions=strip_vlan",
>> +            [KeyValue("strip_vlan", True)],
>> +        ),
>> +        (
>> +            "actions=pop_vlan",
>> +            [KeyValue("pop_vlan", True)],
>> +        ),
>> +        (
>> +            "actions=push_vlan:0x8100",
>> +            [KeyValue("push_vlan", 0x8100)],
>> +        ),
>> +        (
>> +            "actions=push_mpls:0x8848",
>> +            [KeyValue("push_mpls", 0x8848)],
>> +        ),
>> +        (
>> +            "actions=pop_mpls:0x8848",
>> +            [KeyValue("pop_mpls", 0x8848)],
>> +        ),
>> +        (
>> +            "actions=pop_mpls:0x8848",
>> +            [KeyValue("pop_mpls", 0x8848)],
>> +        ),
>> +        (
>> +            "actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))",
>> +            [
>> +                KeyValue(
>> +                    "encap",
>> +                    {
>> +                        "nsh": {
>> +                            "md_type": 2,
>> +                            "tlv": {
>> +                                "class": 0x1000,
>> +                                "type": 10,
>> +                                "value": 0x12345678,
>> +                            },
>> +                        }
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=encap(0x0800)",
>> +            [
>> +                KeyValue(
>> +                    "encap",
>> +                    {"ethernet": 0x800},
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=load:0x001122334455->eth_src",
>> +            [
>> +                KeyValue(
>> +                    "load",
>> +                    {"value": 0x001122334455, "dst": {"field": "eth_src"}},
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=load:1->eth_src[1]",
>> +            [
>> +                KeyValue(
>> +                    "load",
>> +                    {
>> +                        "value": 1,
>> +                        "dst": {"field": "eth_src", "start": 1, "end": 1},
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=learn(load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[])",
>> +            [
>> +                KeyValue(
>> +                    "learn",
>> +                    [
>> +                        {
>> +                            "load": {
>> +                                "src": {"field": "NXM_NX_TUN_ID"},
>> +                                "dst": {"field": "NXM_NX_TUN_ID"},
>> +                            }
>> +                        }
>> +                    ],
>> +                ),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=set_field:00:11:22:33:44:55->eth_src",
>> +            [
>> +                KeyValue(
>> +                    "set_field",
>> +                    {
>> +                        "value": {"eth_src": EthMask("00:11:22:33:44:55")},
>> +                        "dst": {"field": "eth_src"},
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_src",
>> +            [
>> +                KeyValue(
>> +                    "set_field",
>> +                    {
>> +                        "value": {
>> +                            "eth_src": EthMask(
>> +                                "01:00:00:00:00:00/01:00:00:00:00:00"
>> +                            )
>> +                        },
>> +                        "dst": {"field": "eth_src"},
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=set_field:0x10ff->vlan_vid",
>> +            [
>> +                KeyValue(
>> +                    "set_field",
>> +                    {
>> +                        "value": {"vlan_vid": decode_mask(13)("0x10ff")},
>> +                        "dst": {"field": "vlan_vid"},
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=move:reg0[0..5]->reg1[16..31]",
>> +            [
>> +                KeyValue(
>> +                    "move",
>> +                    {
>> +                        "src": {"field": "reg0", "start": 0, "end": 5},
>> +                        "dst": {"field": "reg1", "start": 16, "end": 31},
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=mod_dl_dst:00:11:22:33:44:55",
>> +            [KeyValue("mod_dl_dst", EthMask("00:11:22:33:44:55"))],
>> +        ),
>> +        (
>> +            "actions=mod_nw_dst:192.168.1.1",
>> +            [KeyValue("mod_nw_dst", IPMask("192.168.1.1"))],
>> +        ),
>> +        (
>> +            "actions=mod_nw_dst:fe80::ec17:7bff:fe61:7aac",
>> +            [KeyValue("mod_nw_dst", IPMask("fe80::ec17:7bff:fe61:7aac"))],
>> +        ),
>> +        (
>> +            "actions=dec_ttl,dec_ttl(1,2,3)",
>> +            [KeyValue("dec_ttl", True), KeyValue("dec_ttl", [1, 2, 3])],
>> +        ),
>> +        (
>> +            "actions=set_mpls_label:0x100,set_mpls_tc:2,set_mpls_ttl:10",
>> +            [
>> +                KeyValue("set_mpls_label", 0x100),
>> +                KeyValue("set_mpls_tc", 2),
>> +                KeyValue("set_mpls_ttl", 10),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=check_pkt_larger(100)->reg0[10]",
>> +            [
>> +                KeyValue(
>> +                    "check_pkt_larger",
>> +                    {
>> +                        "pkt_len": 100,
>> +                        "dst": {"field": "reg0", "start": 10, "end": 10},
>> +                    },
>> +                ),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=pop_queue,set_tunnel:0x10,set_tunnel64:0x65000,set_queue=3",   # noqa: E501
>> +            [
>> +                KeyValue("pop_queue", True),
>> +                KeyValue("set_tunnel", 0x10),
>> +                KeyValue("set_tunnel64", 0x65000),
>> +                KeyValue("set_queue", 3),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=ct(zone=10,table=2,nat(snat=192.168.0.0-192.168.0.200:1000-2000,random))",  # noqa: E501
>> +            [
>> +                KeyValue(
>> +                    "ct",
>> +                    {
>> +                        "zone": 10,
>> +                        "table": 2,
>> +                        "nat": {
>> +                            "type": "snat",
>> +                            "addrs": {
>> +                                "start": netaddr.IPAddress("192.168.0.0"),
>> +                                "end": netaddr.IPAddress("192.168.0.200"),
>> +                            },
>> +                            "ports": {
>> +                                "start": 1000,
>> +                                "end": 2000,
>> +                            },
>> +                            "random": True,
>> +                        },
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=ct(commit,zone=NXM_NX_REG13[0..15],table=2,exec(load:0->NXM_NX_CT_LABEL[0]))",  # noqa: E501
>> +            [
>> +                KeyValue(
>> +                    "ct",
>> +                    {
>> +                        "commit": True,
>> +                        "zone": {
>> +                            "field": "NXM_NX_REG13",
>> +                            "start": 0,
>> +                            "end": 15,
>> +                        },
>> +                        "table": 2,
>> +                        "exec": [
>> +                            {
>> +                                "load": {
>> +                                    "value": 0,
>> +                                    "dst": {
>> +                                        "field": "NXM_NX_CT_LABEL",
>> +                                        "start": 0,
>> +                                        "end": 0,
>> +                                    },
>> +                                },
>> +                            },
>> +                        ],
>> +                    },
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=load:0x1->NXM_NX_REG10[7],learn(table=69,delete_learned,cookie=0xda6f52b0,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.30.204.105,nw_proto=6,NXM_OF_TCP_SRC[]=NXM_OF_TCP_DST[],load:0x1->NXM_NX_REG10[7])",  # noqa: E501
>> +            [
>> +                KeyValue(
>> +                    "load",
>> +                    {
>> +                        "value": 1,
>> +                        "dst": {"field": "NXM_NX_REG10", "start": 7, "end": 7},
>> +                    },
>> +                ),
>> +                KeyValue(
>> +                    "learn",
>> +                    [
>> +                        {"table": 69},
>> +                        {"delete_learned": True},
>> +                        {"cookie": 3664728752},
>> +                        {"OXM_OF_METADATA[]": True},
>> +                        {"eth_type": 2048},
>> +                        {"NXM_OF_IP_SRC[]": True},
>> +                        {"ip_dst": IPMask("172.30.204.105/32")},
>> +                        {"nw_proto": 6},
>> +                        {"NXM_OF_TCP_SRC[]": "NXM_OF_TCP_DST[]"},
>> +                        {
>> +                            "load": {
>> +                                "value": 1,
>> +                                "dst": {
>> +                                    "field": "NXM_NX_REG10",
>> +                                    "start": 7,
>> +                                    "end": 7,
>> +                                },
>> +                            }
>> +                        },
>> +                    ],
>> +                ),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=resubmit(,8),resubmit:3,resubmit(1,2,ct)",
>> +            [
>> +                KeyValue("resubmit", {"port": "", "table": 8}),
>> +                KeyValue("resubmit", {"port": 3}),
>> +                KeyValue("resubmit", {"port": 1, "table": 2, "ct": True}),
>> +            ],
>> +        ),
>> +        (
>> +            "actions=clone(ct_clear,load:0->NXM_NX_REG11[],load:0->NXM_NX_REG12[],load:0->NXM_NX_REG13[],load:0x1d->NXM_NX_REG13[],load:0x1f->NXM_NX_REG11[],load:0x1c->NXM_NX_REG12[],load:0x11->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],load:0->NXM_NX_REG10[],load:0->NXM_NX_REG15[],load:0->NXM_NX_REG0[],load:0->NXM_NX_REG1[],load:0->NXM_NX_REG2[],load:0->NXM_NX_REG3[],load:0->NXM_NX_REG4[],load:0->NXM_NX_REG5[],load:0->NXM_NX_REG6[],load:0->NXM_NX_REG7[],load:0->NXM_NX_REG8[],load:0->NXM_NX_REG9[],resubmit(,8))",  # noqa: E501
>> +            [
>> +                KeyValue(
>> +                    "clone",
>> +                    [
>> +                        {"ct_clear": True},
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG11"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG12"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG13"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 29,
>> +                                "dst": {"field": "NXM_NX_REG13"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 31,
>> +                                "dst": {"field": "NXM_NX_REG11"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 28,
>> +                                "dst": {"field": "NXM_NX_REG12"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 17,
>> +                                "dst": {"field": "OXM_OF_METADATA"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 2,
>> +                                "dst": {"field": "NXM_NX_REG14"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG10"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG15"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG0"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG1"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG2"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG3"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG4"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG5"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG6"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG7"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG8"},
>> +                            }
>> +                        },
>> +                        {
>> +                            "load": {
>> +                                "value": 0,
>> +                                "dst": {"field": "NXM_NX_REG9"},
>> +                            }
>> +                        },
>> +                        {"resubmit": {"port": "", "table": 8}},
>> +                    ],
>> +                )
>> +            ],
>> +        ),
>> +        (
>> +            "actions=conjunction(1234, 1/2),note:00.00.11.22.33.ff,sample(probability=123,collector_set_id=0x123,obs_domain_id=0x123,obs_point_id=0x123,sampling_port=inport0,ingress)",  # noqa: E501
>> +            [
>> +                KeyValue("conjunction", {"id": 1234, "k": 1, "n": 2}),
>> +                KeyValue("note", "00.00.11.22.33.ff"),
>> +                KeyValue(
>> +                    "sample",
>> +                    {
>> +                        "probability": 123,
>> +                        "collector_set_id": 0x123,
>> +                        "obs_domain_id": 0x123,
>> +                        "obs_point_id": 0x123,
>> +                        "sampling_port": "inport0",
>> +                        "ingress": True,
>> +                    },
>> +                ),
>> +            ],
>> +        ),
>> +    ],
>> +)
> 
> Add new line… (also by other tests?)
> 

Funny you mention. I kept adding the space and my formatter (I use black[1] for 
automatic python formatting) kept removing it. Then I figured (rememberd) all 
this is actually a decorator of the function and I _think_ decorators should be 
immediatly above the function declaration.

[1] https://github.com/psf/black

>> +def test_act(input_string, expected):
>> +    ofp = OFPFlowFactory().from_string(input_string)
>> +    actions = ofp.actions_kv
>> +    for i in range(len(expected)):
>> +        assert expected[i].key == actions[i].key
>> +        assert expected[i].value == actions[i].value
>> +
>> +        # Assert positions relative to action string are OK
>> +        apos = ofp.section("actions").pos
>> +        astring = ofp.section("actions").string
>> +
>> +        kpos = actions[i].meta.kpos
>> +        kstr = actions[i].meta.kstring
>> +        vpos = actions[i].meta.vpos
>> +        vstr = actions[i].meta.vstring
>> +        assert astring[kpos : kpos + len(kstr)] == kstr
>> +        if vpos != -1:
>> +            assert astring[vpos : vpos + len(vstr)] == vstr
>> +
>> +        # assert astring meta is correct
>> +        assert input_string[apos : apos + len(astring)] == astring
>> -- 
>> 2.31.1
>
Eelco Chaudron Jan. 14, 2022, 11:15 a.m. UTC | #3
On 13 Jan 2022, at 17:23, Adrian Moreno wrote:

> On 12/24/21 14:14, Eelco Chaudron wrote:
>> Other than ending comments with a dot and an extra newline, it looks fine.
>>
>> Acked-by: Eelco Chaudron <echaudro@redhat.com>
>>
>> On 22 Nov 2021, at 12:22, Adrian Moreno wrote:
>>
>>> Signed-off-by: Adrian Moreno <amorenoz@redhat.com>
>>> ---
>>>   python/automake.mk           |   3 +-
>>>   python/ovs/tests/test_ofp.py | 524 +++++++++++++++++++++++++++++++++++
>>>   2 files changed, 526 insertions(+), 1 deletion(-)
>>>   create mode 100644 python/ovs/tests/test_ofp.py
>>>
>>> diff --git a/python/automake.mk b/python/automake.mk
>>> index 41973797c..713f1d1a4 100644
>>> --- a/python/automake.mk
>>> +++ b/python/automake.mk
>>> @@ -55,7 +55,8 @@ ovs_pyfiles = \
>>>
>>>   ovs_tests = \
>>>   	python/ovs/tests/test_kv.py \
>>> -	python/ovs/tests/test_list.py
>>> +	python/ovs/tests/test_list.py \
>>> +	python/ovs/tests/test_ofp.py
>>>
>>>
>>>   # These python files are used at build time but not runtime,
>>> diff --git a/python/ovs/tests/test_ofp.py b/python/ovs/tests/test_ofp.py
>>> new file mode 100644
>>> index 000000000..975be17d1
>>> --- /dev/null
>>> +++ b/python/ovs/tests/test_ofp.py
>>> @@ -0,0 +1,524 @@
>>> +import netaddr
>>> +import pytest
>>> +
>>> +from ovs.flows.ofp import OFPFlowFactory
>>> +from ovs.flows.kv import KeyValue
>>> +from ovs.flows.decoders import EthMask, IPMask, decode_mask
>>> +
>>> +
>>> +@pytest.mark.parametrize(
>>> +    "input_string,expected",
>>> +    [
>>> +        (
>>> +            "actions=local,3,4,5,output:foo",
>>> +            [
>>> +                KeyValue("output", {"port": "local"}),
>>> +                KeyValue("output", {"port": 3}),
>>> +                KeyValue("output", {"port": 4}),
>>> +                KeyValue("output", {"port": 5}),
>>> +                KeyValue("output", {"port": "foo"}),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=controller,controller:200",
>>> +            [
>>> +                KeyValue("output", "controller"),
>>> +                KeyValue("controller", {"max_len": 200}),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=enqueue(foo,42),enqueue:foo:42,enqueue(bar,4242)",
>>> +            [
>>> +                KeyValue("enqueue", {"port": "foo", "queue": 42}),
>>> +                KeyValue("enqueue", {"port": "foo", "queue": 42}),
>>> +                KeyValue("enqueue", {"port": "bar", "queue": 4242}),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=bundle(eth_src,0,hrw,ofport,members:4,8)",
>>> +            [
>>> +                KeyValue(
>>> +                    "bundle",
>>> +                    {
>>> +                        "fields": "eth_src",
>>> +                        "basis": 0,
>>> +                        "algorithm": "hrw",
>>> +                        "members": [4, 8],
>>> +                    },
>>> +                ),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=bundle_load(eth_src,0,hrw,ofport,reg0,members:4,8)",
>>> +            [
>>> +                KeyValue(
>>> +                    "bundle_load",
>>> +                    {
>>> +                        "fields": "eth_src",
>>> +                        "basis": 0,
>>> +                        "algorithm": "hrw",
>>> +                        "dst": "reg0",
>>> +                        "members": [4, 8],
>>> +                    },
>>> +                ),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=group:3",
>>> +            [KeyValue("group", 3)],
>>> +        ),
>>> +        (
>>> +            "actions=strip_vlan",
>>> +            [KeyValue("strip_vlan", True)],
>>> +        ),
>>> +        (
>>> +            "actions=pop_vlan",
>>> +            [KeyValue("pop_vlan", True)],
>>> +        ),
>>> +        (
>>> +            "actions=push_vlan:0x8100",
>>> +            [KeyValue("push_vlan", 0x8100)],
>>> +        ),
>>> +        (
>>> +            "actions=push_mpls:0x8848",
>>> +            [KeyValue("push_mpls", 0x8848)],
>>> +        ),
>>> +        (
>>> +            "actions=pop_mpls:0x8848",
>>> +            [KeyValue("pop_mpls", 0x8848)],
>>> +        ),
>>> +        (
>>> +            "actions=pop_mpls:0x8848",
>>> +            [KeyValue("pop_mpls", 0x8848)],
>>> +        ),
>>> +        (
>>> +            "actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))",
>>> +            [
>>> +                KeyValue(
>>> +                    "encap",
>>> +                    {
>>> +                        "nsh": {
>>> +                            "md_type": 2,
>>> +                            "tlv": {
>>> +                                "class": 0x1000,
>>> +                                "type": 10,
>>> +                                "value": 0x12345678,
>>> +                            },
>>> +                        }
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=encap(0x0800)",
>>> +            [
>>> +                KeyValue(
>>> +                    "encap",
>>> +                    {"ethernet": 0x800},
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=load:0x001122334455->eth_src",
>>> +            [
>>> +                KeyValue(
>>> +                    "load",
>>> +                    {"value": 0x001122334455, "dst": {"field": "eth_src"}},
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=load:1->eth_src[1]",
>>> +            [
>>> +                KeyValue(
>>> +                    "load",
>>> +                    {
>>> +                        "value": 1,
>>> +                        "dst": {"field": "eth_src", "start": 1, "end": 1},
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=learn(load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[])",
>>> +            [
>>> +                KeyValue(
>>> +                    "learn",
>>> +                    [
>>> +                        {
>>> +                            "load": {
>>> +                                "src": {"field": "NXM_NX_TUN_ID"},
>>> +                                "dst": {"field": "NXM_NX_TUN_ID"},
>>> +                            }
>>> +                        }
>>> +                    ],
>>> +                ),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=set_field:00:11:22:33:44:55->eth_src",
>>> +            [
>>> +                KeyValue(
>>> +                    "set_field",
>>> +                    {
>>> +                        "value": {"eth_src": EthMask("00:11:22:33:44:55")},
>>> +                        "dst": {"field": "eth_src"},
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_src",
>>> +            [
>>> +                KeyValue(
>>> +                    "set_field",
>>> +                    {
>>> +                        "value": {
>>> +                            "eth_src": EthMask(
>>> +                                "01:00:00:00:00:00/01:00:00:00:00:00"
>>> +                            )
>>> +                        },
>>> +                        "dst": {"field": "eth_src"},
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=set_field:0x10ff->vlan_vid",
>>> +            [
>>> +                KeyValue(
>>> +                    "set_field",
>>> +                    {
>>> +                        "value": {"vlan_vid": decode_mask(13)("0x10ff")},
>>> +                        "dst": {"field": "vlan_vid"},
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=move:reg0[0..5]->reg1[16..31]",
>>> +            [
>>> +                KeyValue(
>>> +                    "move",
>>> +                    {
>>> +                        "src": {"field": "reg0", "start": 0, "end": 5},
>>> +                        "dst": {"field": "reg1", "start": 16, "end": 31},
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=mod_dl_dst:00:11:22:33:44:55",
>>> +            [KeyValue("mod_dl_dst", EthMask("00:11:22:33:44:55"))],
>>> +        ),
>>> +        (
>>> +            "actions=mod_nw_dst:192.168.1.1",
>>> +            [KeyValue("mod_nw_dst", IPMask("192.168.1.1"))],
>>> +        ),
>>> +        (
>>> +            "actions=mod_nw_dst:fe80::ec17:7bff:fe61:7aac",
>>> +            [KeyValue("mod_nw_dst", IPMask("fe80::ec17:7bff:fe61:7aac"))],
>>> +        ),
>>> +        (
>>> +            "actions=dec_ttl,dec_ttl(1,2,3)",
>>> +            [KeyValue("dec_ttl", True), KeyValue("dec_ttl", [1, 2, 3])],
>>> +        ),
>>> +        (
>>> +            "actions=set_mpls_label:0x100,set_mpls_tc:2,set_mpls_ttl:10",
>>> +            [
>>> +                KeyValue("set_mpls_label", 0x100),
>>> +                KeyValue("set_mpls_tc", 2),
>>> +                KeyValue("set_mpls_ttl", 10),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=check_pkt_larger(100)->reg0[10]",
>>> +            [
>>> +                KeyValue(
>>> +                    "check_pkt_larger",
>>> +                    {
>>> +                        "pkt_len": 100,
>>> +                        "dst": {"field": "reg0", "start": 10, "end": 10},
>>> +                    },
>>> +                ),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=pop_queue,set_tunnel:0x10,set_tunnel64:0x65000,set_queue=3",   # noqa: E501
>>> +            [
>>> +                KeyValue("pop_queue", True),
>>> +                KeyValue("set_tunnel", 0x10),
>>> +                KeyValue("set_tunnel64", 0x65000),
>>> +                KeyValue("set_queue", 3),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=ct(zone=10,table=2,nat(snat=192.168.0.0-192.168.0.200:1000-2000,random))",  # noqa: E501
>>> +            [
>>> +                KeyValue(
>>> +                    "ct",
>>> +                    {
>>> +                        "zone": 10,
>>> +                        "table": 2,
>>> +                        "nat": {
>>> +                            "type": "snat",
>>> +                            "addrs": {
>>> +                                "start": netaddr.IPAddress("192.168.0.0"),
>>> +                                "end": netaddr.IPAddress("192.168.0.200"),
>>> +                            },
>>> +                            "ports": {
>>> +                                "start": 1000,
>>> +                                "end": 2000,
>>> +                            },
>>> +                            "random": True,
>>> +                        },
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=ct(commit,zone=NXM_NX_REG13[0..15],table=2,exec(load:0->NXM_NX_CT_LABEL[0]))",  # noqa: E501
>>> +            [
>>> +                KeyValue(
>>> +                    "ct",
>>> +                    {
>>> +                        "commit": True,
>>> +                        "zone": {
>>> +                            "field": "NXM_NX_REG13",
>>> +                            "start": 0,
>>> +                            "end": 15,
>>> +                        },
>>> +                        "table": 2,
>>> +                        "exec": [
>>> +                            {
>>> +                                "load": {
>>> +                                    "value": 0,
>>> +                                    "dst": {
>>> +                                        "field": "NXM_NX_CT_LABEL",
>>> +                                        "start": 0,
>>> +                                        "end": 0,
>>> +                                    },
>>> +                                },
>>> +                            },
>>> +                        ],
>>> +                    },
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=load:0x1->NXM_NX_REG10[7],learn(table=69,delete_learned,cookie=0xda6f52b0,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.30.204.105,nw_proto=6,NXM_OF_TCP_SRC[]=NXM_OF_TCP_DST[],load:0x1->NXM_NX_REG10[7])",  # noqa: E501
>>> +            [
>>> +                KeyValue(
>>> +                    "load",
>>> +                    {
>>> +                        "value": 1,
>>> +                        "dst": {"field": "NXM_NX_REG10", "start": 7, "end": 7},
>>> +                    },
>>> +                ),
>>> +                KeyValue(
>>> +                    "learn",
>>> +                    [
>>> +                        {"table": 69},
>>> +                        {"delete_learned": True},
>>> +                        {"cookie": 3664728752},
>>> +                        {"OXM_OF_METADATA[]": True},
>>> +                        {"eth_type": 2048},
>>> +                        {"NXM_OF_IP_SRC[]": True},
>>> +                        {"ip_dst": IPMask("172.30.204.105/32")},
>>> +                        {"nw_proto": 6},
>>> +                        {"NXM_OF_TCP_SRC[]": "NXM_OF_TCP_DST[]"},
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 1,
>>> +                                "dst": {
>>> +                                    "field": "NXM_NX_REG10",
>>> +                                    "start": 7,
>>> +                                    "end": 7,
>>> +                                },
>>> +                            }
>>> +                        },
>>> +                    ],
>>> +                ),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=resubmit(,8),resubmit:3,resubmit(1,2,ct)",
>>> +            [
>>> +                KeyValue("resubmit", {"port": "", "table": 8}),
>>> +                KeyValue("resubmit", {"port": 3}),
>>> +                KeyValue("resubmit", {"port": 1, "table": 2, "ct": True}),
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=clone(ct_clear,load:0->NXM_NX_REG11[],load:0->NXM_NX_REG12[],load:0->NXM_NX_REG13[],load:0x1d->NXM_NX_REG13[],load:0x1f->NXM_NX_REG11[],load:0x1c->NXM_NX_REG12[],load:0x11->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],load:0->NXM_NX_REG10[],load:0->NXM_NX_REG15[],load:0->NXM_NX_REG0[],load:0->NXM_NX_REG1[],load:0->NXM_NX_REG2[],load:0->NXM_NX_REG3[],load:0->NXM_NX_REG4[],load:0->NXM_NX_REG5[],load:0->NXM_NX_REG6[],load:0->NXM_NX_REG7[],load:0->NXM_NX_REG8[],load:0->NXM_NX_REG9[],resubmit(,8))",  # noqa: E501
>>> +            [
>>> +                KeyValue(
>>> +                    "clone",
>>> +                    [
>>> +                        {"ct_clear": True},
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG11"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG12"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG13"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 29,
>>> +                                "dst": {"field": "NXM_NX_REG13"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 31,
>>> +                                "dst": {"field": "NXM_NX_REG11"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 28,
>>> +                                "dst": {"field": "NXM_NX_REG12"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 17,
>>> +                                "dst": {"field": "OXM_OF_METADATA"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 2,
>>> +                                "dst": {"field": "NXM_NX_REG14"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG10"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG15"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG0"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG1"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG2"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG3"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG4"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG5"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG6"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG7"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG8"},
>>> +                            }
>>> +                        },
>>> +                        {
>>> +                            "load": {
>>> +                                "value": 0,
>>> +                                "dst": {"field": "NXM_NX_REG9"},
>>> +                            }
>>> +                        },
>>> +                        {"resubmit": {"port": "", "table": 8}},
>>> +                    ],
>>> +                )
>>> +            ],
>>> +        ),
>>> +        (
>>> +            "actions=conjunction(1234, 1/2),note:00.00.11.22.33.ff,sample(probability=123,collector_set_id=0x123,obs_domain_id=0x123,obs_point_id=0x123,sampling_port=inport0,ingress)",  # noqa: E501
>>> +            [
>>> +                KeyValue("conjunction", {"id": 1234, "k": 1, "n": 2}),
>>> +                KeyValue("note", "00.00.11.22.33.ff"),
>>> +                KeyValue(
>>> +                    "sample",
>>> +                    {
>>> +                        "probability": 123,
>>> +                        "collector_set_id": 0x123,
>>> +                        "obs_domain_id": 0x123,
>>> +                        "obs_point_id": 0x123,
>>> +                        "sampling_port": "inport0",
>>> +                        "ingress": True,
>>> +                    },
>>> +                ),
>>> +            ],
>>> +        ),
>>> +    ],
>>> +)
>>
>> Add new line… (also by other tests?)
>>
>
> Funny you mention. I kept adding the space and my formatter (I use black[1] for automatic python formatting) kept removing it. Then I figured (rememberd) all this is actually a decorator of the function and I _think_ decorators should be immediatly above the function declaration.

Yes, I’ve seen it before, but I just don’t like some of its parentheses handling, but I guess that is just my personal preference ;)

But you are right, as it’s a decorator normally it’s added without a new line. So please leave it as is, and make sure it’s the same for all tests.

> [1] https://github.com/psf/black
>
>>> +def test_act(input_string, expected):
>>> +    ofp = OFPFlowFactory().from_string(input_string)
>>> +    actions = ofp.actions_kv
>>> +    for i in range(len(expected)):
>>> +        assert expected[i].key == actions[i].key
>>> +        assert expected[i].value == actions[i].value
>>> +
>>> +        # Assert positions relative to action string are OK
>>> +        apos = ofp.section("actions").pos
>>> +        astring = ofp.section("actions").string
>>> +
>>> +        kpos = actions[i].meta.kpos
>>> +        kstr = actions[i].meta.kstring
>>> +        vpos = actions[i].meta.vpos
>>> +        vstr = actions[i].meta.vstring
>>> +        assert astring[kpos : kpos + len(kstr)] == kstr
>>> +        if vpos != -1:
>>> +            assert astring[vpos : vpos + len(vstr)] == vstr
>>> +
>>> +        # assert astring meta is correct
>>> +        assert input_string[apos : apos + len(astring)] == astring
>>> -- 
>>> 2.31.1
>>
>
> -- 
> Adrián Moreno
diff mbox series

Patch

diff --git a/python/automake.mk b/python/automake.mk
index 41973797c..713f1d1a4 100644
--- a/python/automake.mk
+++ b/python/automake.mk
@@ -55,7 +55,8 @@  ovs_pyfiles = \
 
 ovs_tests = \
 	python/ovs/tests/test_kv.py \
-	python/ovs/tests/test_list.py
+	python/ovs/tests/test_list.py \
+	python/ovs/tests/test_ofp.py
 
 
 # These python files are used at build time but not runtime,
diff --git a/python/ovs/tests/test_ofp.py b/python/ovs/tests/test_ofp.py
new file mode 100644
index 000000000..975be17d1
--- /dev/null
+++ b/python/ovs/tests/test_ofp.py
@@ -0,0 +1,524 @@ 
+import netaddr
+import pytest
+
+from ovs.flows.ofp import OFPFlowFactory
+from ovs.flows.kv import KeyValue
+from ovs.flows.decoders import EthMask, IPMask, decode_mask
+
+
+@pytest.mark.parametrize(
+    "input_string,expected",
+    [
+        (
+            "actions=local,3,4,5,output:foo",
+            [
+                KeyValue("output", {"port": "local"}),
+                KeyValue("output", {"port": 3}),
+                KeyValue("output", {"port": 4}),
+                KeyValue("output", {"port": 5}),
+                KeyValue("output", {"port": "foo"}),
+            ],
+        ),
+        (
+            "actions=controller,controller:200",
+            [
+                KeyValue("output", "controller"),
+                KeyValue("controller", {"max_len": 200}),
+            ],
+        ),
+        (
+            "actions=enqueue(foo,42),enqueue:foo:42,enqueue(bar,4242)",
+            [
+                KeyValue("enqueue", {"port": "foo", "queue": 42}),
+                KeyValue("enqueue", {"port": "foo", "queue": 42}),
+                KeyValue("enqueue", {"port": "bar", "queue": 4242}),
+            ],
+        ),
+        (
+            "actions=bundle(eth_src,0,hrw,ofport,members:4,8)",
+            [
+                KeyValue(
+                    "bundle",
+                    {
+                        "fields": "eth_src",
+                        "basis": 0,
+                        "algorithm": "hrw",
+                        "members": [4, 8],
+                    },
+                ),
+            ],
+        ),
+        (
+            "actions=bundle_load(eth_src,0,hrw,ofport,reg0,members:4,8)",
+            [
+                KeyValue(
+                    "bundle_load",
+                    {
+                        "fields": "eth_src",
+                        "basis": 0,
+                        "algorithm": "hrw",
+                        "dst": "reg0",
+                        "members": [4, 8],
+                    },
+                ),
+            ],
+        ),
+        (
+            "actions=group:3",
+            [KeyValue("group", 3)],
+        ),
+        (
+            "actions=strip_vlan",
+            [KeyValue("strip_vlan", True)],
+        ),
+        (
+            "actions=pop_vlan",
+            [KeyValue("pop_vlan", True)],
+        ),
+        (
+            "actions=push_vlan:0x8100",
+            [KeyValue("push_vlan", 0x8100)],
+        ),
+        (
+            "actions=push_mpls:0x8848",
+            [KeyValue("push_mpls", 0x8848)],
+        ),
+        (
+            "actions=pop_mpls:0x8848",
+            [KeyValue("pop_mpls", 0x8848)],
+        ),
+        (
+            "actions=pop_mpls:0x8848",
+            [KeyValue("pop_mpls", 0x8848)],
+        ),
+        (
+            "actions=encap(nsh(md_type=2,tlv(0x1000,10,0x12345678)))",
+            [
+                KeyValue(
+                    "encap",
+                    {
+                        "nsh": {
+                            "md_type": 2,
+                            "tlv": {
+                                "class": 0x1000,
+                                "type": 10,
+                                "value": 0x12345678,
+                            },
+                        }
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=encap(0x0800)",
+            [
+                KeyValue(
+                    "encap",
+                    {"ethernet": 0x800},
+                )
+            ],
+        ),
+        (
+            "actions=load:0x001122334455->eth_src",
+            [
+                KeyValue(
+                    "load",
+                    {"value": 0x001122334455, "dst": {"field": "eth_src"}},
+                )
+            ],
+        ),
+        (
+            "actions=load:1->eth_src[1]",
+            [
+                KeyValue(
+                    "load",
+                    {
+                        "value": 1,
+                        "dst": {"field": "eth_src", "start": 1, "end": 1},
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=learn(load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[])",
+            [
+                KeyValue(
+                    "learn",
+                    [
+                        {
+                            "load": {
+                                "src": {"field": "NXM_NX_TUN_ID"},
+                                "dst": {"field": "NXM_NX_TUN_ID"},
+                            }
+                        }
+                    ],
+                ),
+            ],
+        ),
+        (
+            "actions=set_field:00:11:22:33:44:55->eth_src",
+            [
+                KeyValue(
+                    "set_field",
+                    {
+                        "value": {"eth_src": EthMask("00:11:22:33:44:55")},
+                        "dst": {"field": "eth_src"},
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=set_field:01:00:00:00:00:00/01:00:00:00:00:00->eth_src",
+            [
+                KeyValue(
+                    "set_field",
+                    {
+                        "value": {
+                            "eth_src": EthMask(
+                                "01:00:00:00:00:00/01:00:00:00:00:00"
+                            )
+                        },
+                        "dst": {"field": "eth_src"},
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=set_field:0x10ff->vlan_vid",
+            [
+                KeyValue(
+                    "set_field",
+                    {
+                        "value": {"vlan_vid": decode_mask(13)("0x10ff")},
+                        "dst": {"field": "vlan_vid"},
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=move:reg0[0..5]->reg1[16..31]",
+            [
+                KeyValue(
+                    "move",
+                    {
+                        "src": {"field": "reg0", "start": 0, "end": 5},
+                        "dst": {"field": "reg1", "start": 16, "end": 31},
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=mod_dl_dst:00:11:22:33:44:55",
+            [KeyValue("mod_dl_dst", EthMask("00:11:22:33:44:55"))],
+        ),
+        (
+            "actions=mod_nw_dst:192.168.1.1",
+            [KeyValue("mod_nw_dst", IPMask("192.168.1.1"))],
+        ),
+        (
+            "actions=mod_nw_dst:fe80::ec17:7bff:fe61:7aac",
+            [KeyValue("mod_nw_dst", IPMask("fe80::ec17:7bff:fe61:7aac"))],
+        ),
+        (
+            "actions=dec_ttl,dec_ttl(1,2,3)",
+            [KeyValue("dec_ttl", True), KeyValue("dec_ttl", [1, 2, 3])],
+        ),
+        (
+            "actions=set_mpls_label:0x100,set_mpls_tc:2,set_mpls_ttl:10",
+            [
+                KeyValue("set_mpls_label", 0x100),
+                KeyValue("set_mpls_tc", 2),
+                KeyValue("set_mpls_ttl", 10),
+            ],
+        ),
+        (
+            "actions=check_pkt_larger(100)->reg0[10]",
+            [
+                KeyValue(
+                    "check_pkt_larger",
+                    {
+                        "pkt_len": 100,
+                        "dst": {"field": "reg0", "start": 10, "end": 10},
+                    },
+                ),
+            ],
+        ),
+        (
+            "actions=pop_queue,set_tunnel:0x10,set_tunnel64:0x65000,set_queue=3",   # noqa: E501
+            [
+                KeyValue("pop_queue", True),
+                KeyValue("set_tunnel", 0x10),
+                KeyValue("set_tunnel64", 0x65000),
+                KeyValue("set_queue", 3),
+            ],
+        ),
+        (
+            "actions=ct(zone=10,table=2,nat(snat=192.168.0.0-192.168.0.200:1000-2000,random))",  # noqa: E501
+            [
+                KeyValue(
+                    "ct",
+                    {
+                        "zone": 10,
+                        "table": 2,
+                        "nat": {
+                            "type": "snat",
+                            "addrs": {
+                                "start": netaddr.IPAddress("192.168.0.0"),
+                                "end": netaddr.IPAddress("192.168.0.200"),
+                            },
+                            "ports": {
+                                "start": 1000,
+                                "end": 2000,
+                            },
+                            "random": True,
+                        },
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=ct(commit,zone=NXM_NX_REG13[0..15],table=2,exec(load:0->NXM_NX_CT_LABEL[0]))",  # noqa: E501
+            [
+                KeyValue(
+                    "ct",
+                    {
+                        "commit": True,
+                        "zone": {
+                            "field": "NXM_NX_REG13",
+                            "start": 0,
+                            "end": 15,
+                        },
+                        "table": 2,
+                        "exec": [
+                            {
+                                "load": {
+                                    "value": 0,
+                                    "dst": {
+                                        "field": "NXM_NX_CT_LABEL",
+                                        "start": 0,
+                                        "end": 0,
+                                    },
+                                },
+                            },
+                        ],
+                    },
+                )
+            ],
+        ),
+        (
+            "actions=load:0x1->NXM_NX_REG10[7],learn(table=69,delete_learned,cookie=0xda6f52b0,OXM_OF_METADATA[],eth_type=0x800,NXM_OF_IP_SRC[],ip_dst=172.30.204.105,nw_proto=6,NXM_OF_TCP_SRC[]=NXM_OF_TCP_DST[],load:0x1->NXM_NX_REG10[7])",  # noqa: E501
+            [
+                KeyValue(
+                    "load",
+                    {
+                        "value": 1,
+                        "dst": {"field": "NXM_NX_REG10", "start": 7, "end": 7},
+                    },
+                ),
+                KeyValue(
+                    "learn",
+                    [
+                        {"table": 69},
+                        {"delete_learned": True},
+                        {"cookie": 3664728752},
+                        {"OXM_OF_METADATA[]": True},
+                        {"eth_type": 2048},
+                        {"NXM_OF_IP_SRC[]": True},
+                        {"ip_dst": IPMask("172.30.204.105/32")},
+                        {"nw_proto": 6},
+                        {"NXM_OF_TCP_SRC[]": "NXM_OF_TCP_DST[]"},
+                        {
+                            "load": {
+                                "value": 1,
+                                "dst": {
+                                    "field": "NXM_NX_REG10",
+                                    "start": 7,
+                                    "end": 7,
+                                },
+                            }
+                        },
+                    ],
+                ),
+            ],
+        ),
+        (
+            "actions=resubmit(,8),resubmit:3,resubmit(1,2,ct)",
+            [
+                KeyValue("resubmit", {"port": "", "table": 8}),
+                KeyValue("resubmit", {"port": 3}),
+                KeyValue("resubmit", {"port": 1, "table": 2, "ct": True}),
+            ],
+        ),
+        (
+            "actions=clone(ct_clear,load:0->NXM_NX_REG11[],load:0->NXM_NX_REG12[],load:0->NXM_NX_REG13[],load:0x1d->NXM_NX_REG13[],load:0x1f->NXM_NX_REG11[],load:0x1c->NXM_NX_REG12[],load:0x11->OXM_OF_METADATA[],load:0x2->NXM_NX_REG14[],load:0->NXM_NX_REG10[],load:0->NXM_NX_REG15[],load:0->NXM_NX_REG0[],load:0->NXM_NX_REG1[],load:0->NXM_NX_REG2[],load:0->NXM_NX_REG3[],load:0->NXM_NX_REG4[],load:0->NXM_NX_REG5[],load:0->NXM_NX_REG6[],load:0->NXM_NX_REG7[],load:0->NXM_NX_REG8[],load:0->NXM_NX_REG9[],resubmit(,8))",  # noqa: E501
+            [
+                KeyValue(
+                    "clone",
+                    [
+                        {"ct_clear": True},
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG11"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG12"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG13"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 29,
+                                "dst": {"field": "NXM_NX_REG13"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 31,
+                                "dst": {"field": "NXM_NX_REG11"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 28,
+                                "dst": {"field": "NXM_NX_REG12"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 17,
+                                "dst": {"field": "OXM_OF_METADATA"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 2,
+                                "dst": {"field": "NXM_NX_REG14"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG10"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG15"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG0"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG1"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG2"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG3"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG4"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG5"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG6"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG7"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG8"},
+                            }
+                        },
+                        {
+                            "load": {
+                                "value": 0,
+                                "dst": {"field": "NXM_NX_REG9"},
+                            }
+                        },
+                        {"resubmit": {"port": "", "table": 8}},
+                    ],
+                )
+            ],
+        ),
+        (
+            "actions=conjunction(1234, 1/2),note:00.00.11.22.33.ff,sample(probability=123,collector_set_id=0x123,obs_domain_id=0x123,obs_point_id=0x123,sampling_port=inport0,ingress)",  # noqa: E501
+            [
+                KeyValue("conjunction", {"id": 1234, "k": 1, "n": 2}),
+                KeyValue("note", "00.00.11.22.33.ff"),
+                KeyValue(
+                    "sample",
+                    {
+                        "probability": 123,
+                        "collector_set_id": 0x123,
+                        "obs_domain_id": 0x123,
+                        "obs_point_id": 0x123,
+                        "sampling_port": "inport0",
+                        "ingress": True,
+                    },
+                ),
+            ],
+        ),
+    ],
+)
+def test_act(input_string, expected):
+    ofp = OFPFlowFactory().from_string(input_string)
+    actions = ofp.actions_kv
+    for i in range(len(expected)):
+        assert expected[i].key == actions[i].key
+        assert expected[i].value == actions[i].value
+
+        # Assert positions relative to action string are OK
+        apos = ofp.section("actions").pos
+        astring = ofp.section("actions").string
+
+        kpos = actions[i].meta.kpos
+        kstr = actions[i].meta.kstring
+        vpos = actions[i].meta.vpos
+        vstr = actions[i].meta.vstring
+        assert astring[kpos : kpos + len(kstr)] == kstr
+        if vpos != -1:
+            assert astring[vpos : vpos + len(vstr)] == vstr
+
+        # assert astring meta is correct
+        assert input_string[apos : apos + len(astring)] == astring