On 28 Jun 2024, at 15:31, Adrián Moreno wrote:
> On Thu, Jun 27, 2024 at 03:42:38PM GMT, Eelco Chaudron wrote: >> >> >> On 27 Jun 2024, at 12:45, Adrián Moreno wrote: >> >>> On Wed, Jun 26, 2024 at 02:08:27PM GMT, Eelco Chaudron wrote: >>>> On 5 Jun 2024, at 22:23, Adrian Moreno wrote: >>>> >>>>> Add support for parsing and formatting the new action. >>>>> >>>>> Also, flag OVS_ACTION_ATTR_SAMPLE as requiring datapath assistance if it >>>>> contains a nested OVS_ACTION_ATTR_EMIT_SAMPLE. The reason is that the >>>>> sampling rate form the parent "sample" is made available to the nested >>>>> "emit_sample" by the kernel. >>>> >>>> Hi Adrian, >>>> >>>> Thanks for this series! This email kicks off my review of the series, >>>> see comments below and in the other patches. >>>> >>>> Cheers, >>>> >>>> Eelco >>>> >>>>> Signed-off-by: Adrian Moreno <[email protected]> >>>>> --- >>>>> include/linux/openvswitch.h | 25 +++++++++ >>>>> lib/dpif-netdev.c | 1 + >>>>> lib/dpif.c | 1 + >>>>> lib/odp-execute.c | 25 ++++++++- >>>>> lib/odp-util.c | 103 +++++++++++++++++++++++++++++++++++ >>>>> lib/odp-util.h | 3 + >>>>> ofproto/ofproto-dpif-ipfix.c | 1 + >>>>> ofproto/ofproto-dpif-sflow.c | 1 + >>>>> python/ovs/flow/odp.py | 8 +++ >>>>> tests/odp.at | 16 ++++++ >>>>> 10 files changed, 183 insertions(+), 1 deletion(-) >>>>> >>>>> diff --git a/include/linux/openvswitch.h b/include/linux/openvswitch.h >>>>> index d9fb991ef..b4e0647bd 100644 >>>>> --- a/include/linux/openvswitch.h >>>>> +++ b/include/linux/openvswitch.h >>>>> @@ -992,6 +992,30 @@ struct check_pkt_len_arg { >>>>> }; >>>>> #endif >>>>> >>>>> +#define OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE 16 >>>>> +/** >>>>> + * enum ovs_emit_sample_attr - Attributes for >>>>> %OVS_ACTION_ATTR_EMIT_SAMPLE >>>>> + * action. >>>>> + * >>>>> + * @OVS_EMIT_SAMPLE_ATTR_GROUP: 32-bit number to identify the source of >>>>> the >>>>> + * sample. >>>>> + * @OVS_EMIT_SAMPLE_ATTR_COOKIE: A variable-length binary cookie that >>>>> contains >>>>> + * user-defined metadata. The maximum length is 16 bytes. >>>>> + * >>>>> + * Sends the packet to the psample multicast group with the specified >>>>> group and >>>>> + * cookie. It is possible to combine this action with the >>>>> + * %OVS_ACTION_ATTR_TRUNC to limit the size of the packet being emitted. >>>> >>>> I'll start the discussion again on the naming. The name "emit_sample()" >>>> does not seem appropriate. This function's primary role is to copy the >>>> packet and send it to a local collector, which varies depending on the >>>> datapath. For the kernel datapath, this collector is psample, while for >>>> userspace, it will likely be some kind of probe. This action is distinct >>>> from the sample() action by design; it is a standalone action that can >>>> be combined with others. >>>> >>>> Furthermore, the action itself does not involve taking a sample; it >>>> consistently pushes the packet to the local collector. Therefore, I >>>> suggest renaming "emit_sample()" to "emit_local()". This same goes for >>>> all the derivative ATTR naming. >>>> >>> >>> Let's discuss this in the kernel ml. >>> >>>>> + */ >>>>> +enum ovs_emit_sample_attr { >>>>> + OVS_EMIT_SAMPLE_ATTR_UNPSEC, >>>>> + OVS_EMIT_SAMPLE_ATTR_GROUP, /* u32 number. */ >>>>> + OVS_EMIT_SAMPLE_ATTR_COOKIE, /* Optional, user specified cookie. */ >>>> >>>> Fix comment alignment? Maybe also change the order to be alphabetical? >>>> >>> >>> What do you mean by comment alignment? >> >> Well you are using tabs to index which might result in it looking like this: >> >> + OVS_EMIT_SAMPLE_ATTR_GROUP, /* u32 number. */ >> + OVS_EMIT_SAMPLE_ATTR_COOKIE, /* Optional, user specified cookie. */ >> >> (Most) of the other comments use spaces after the definition. >> >>>>> + __OVS_EMIT_SAMPLE_ATTR_MAX >>>>> +}; >>>>> + >>>>> +#define OVS_EMIT_SAMPLE_ATTR_MAX (__OVS_EMIT_SAMPLE_ATTR_MAX - 1) >>>>> + >>>>> + >>>>> /** >>>>> * enum ovs_action_attr - Action types. >>>>> * >>>>> @@ -1087,6 +1111,7 @@ enum ovs_action_attr { >>>>> OVS_ACTION_ATTR_ADD_MPLS, /* struct ovs_action_add_mpls. */ >>>>> OVS_ACTION_ATTR_DEC_TTL, /* Nested OVS_DEC_TTL_ATTR_*. */ >>>>> OVS_ACTION_ATTR_DROP, /* u32 xlate_error. */ >>>>> + OVS_ACTION_ATTR_EMIT_SAMPLE, /* Nested OVS_EMIT_SAMPLE_ATTR_*. */ >>>>> >>>>> #ifndef __KERNEL__ >>>>> OVS_ACTION_ATTR_TUNNEL_PUSH, /* struct ovs_action_push_tnl*/ >>>>> diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c >>>>> index c7f9e1490..c1171890c 100644 >>>>> --- a/lib/dpif-netdev.c >>>>> +++ b/lib/dpif-netdev.c >>>>> @@ -9519,6 +9519,7 @@ dp_execute_cb(void *aux_, struct dp_packet_batch >>>>> *packets_, >>>>> case OVS_ACTION_ATTR_DROP: >>>>> case OVS_ACTION_ATTR_ADD_MPLS: >>>>> case OVS_ACTION_ATTR_DEC_TTL: >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: >>>>> case __OVS_ACTION_ATTR_MAX: >>>>> OVS_NOT_REACHED(); >>>>> } >>>>> diff --git a/lib/dpif.c b/lib/dpif.c >>>>> index 23eb18495..489d6a095 100644 >>>>> --- a/lib/dpif.c >>>>> +++ b/lib/dpif.c >>>>> @@ -1192,6 +1192,7 @@ dpif_execute_helper_cb(void *aux_, struct >>>>> dp_packet_batch *packets_, >>>>> case OVS_ACTION_ATTR_TUNNEL_PUSH: >>>>> case OVS_ACTION_ATTR_TUNNEL_POP: >>>>> case OVS_ACTION_ATTR_USERSPACE: >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: >>>>> case OVS_ACTION_ATTR_RECIRC: { >>>>> struct dpif_execute execute; >>>>> struct ofpbuf execute_actions; >>>>> diff --git a/lib/odp-execute.c b/lib/odp-execute.c >>>>> index 081e4d432..967abfd0a 100644 >>>>> --- a/lib/odp-execute.c >>>>> +++ b/lib/odp-execute.c >>>>> @@ -818,13 +818,13 @@ requires_datapath_assistance(const struct nlattr *a) >>>>> case OVS_ACTION_ATTR_RECIRC: >>>>> case OVS_ACTION_ATTR_CT: >>>>> case OVS_ACTION_ATTR_METER: >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: >>>>> return true; >>>>> >>>>> case OVS_ACTION_ATTR_SET: >>>>> case OVS_ACTION_ATTR_SET_MASKED: >>>>> case OVS_ACTION_ATTR_PUSH_VLAN: >>>>> case OVS_ACTION_ATTR_POP_VLAN: >>>>> - case OVS_ACTION_ATTR_SAMPLE: >>>>> case OVS_ACTION_ATTR_HASH: >>>>> case OVS_ACTION_ATTR_PUSH_MPLS: >>>>> case OVS_ACTION_ATTR_POP_MPLS: >>>>> @@ -841,6 +841,28 @@ requires_datapath_assistance(const struct nlattr *a) >>>>> case OVS_ACTION_ATTR_DROP: >>>>> return false; >>>>> >>>>> + case OVS_ACTION_ATTR_SAMPLE: { >>>>> + /* Nested "emit_sample" actions rely on the datapath executing >>>>> the >>>>> + * parent "sample", storing the probability and making it >>>>> available >>>>> + * when the nested "emit_sample" is run. >>>>> + */ >>>> >>>> Comments in this file have the closing mark on the last line, so please do >>>> this here also. >>>> >>> >>> Ack >>> >>>>> + const struct nlattr *attr; >>>>> + unsigned int left; >>>> >>>> Please add a new line after variable declarations. >>> >>> Ack. >>>> >>>>> + NL_NESTED_FOR_EACH (attr, left, a) { >>>> >>>> Also can we get a test case for this code path? The patch 9/9 is not >>>> hitting >>>> this. >>>> >>> >>> I know, I have been thinking in ways to test this. Ideally the test >>> should be to add "debug_slow" to make the execution go to userspace but >>> then add "sample(prob=X,action(emit_sample()))" and check the emitted >>> smaple contains the right probability (which indicates the "sample" was >>> also executed in the kernel. >>> >>> However, even if I put a probability of 0xFFFE that gives the test >>> 0.0015% changes of failing... >>> >>> Anyway, I guess sending enough packets and checking at least some are >>> received can reduce the probability to something negligible. >> >> Will send it always (100%) not work? > > If the OFP action has 100% probability, xlate layer will optimize out > the SAMPLE action, so instead of installing > "sample(prob=100%,actions(emit_sample()))" it writes "emit_sample()" > directly so this code path is not exercised. Forgot about that optimization… >>>>> + if (nl_attr_type(attr) == OVS_SAMPLE_ATTR_ACTIONS) { >>>>> + const struct nlattr *act; >>>>> + unsigned int act_left; >>>>> + >>>>> + NL_NESTED_FOR_EACH (act, act_left, attr) { >>>>> + if (nl_attr_type(act) == >>>>> OVS_ACTION_ATTR_EMIT_SAMPLE) { >>>>> + return true; >>>>> + } >>>>> + } >>>>> + } >>>>> + } >>>>> + return false; >>>>> + } >>>>> + >>>>> case OVS_ACTION_ATTR_UNSPEC: >>>>> case __OVS_ACTION_ATTR_MAX: >>>>> OVS_NOT_REACHED(); >>>>> @@ -1229,6 +1251,7 @@ odp_execute_actions(void *dp, struct >>>>> dp_packet_batch *batch, bool steal, >>>>> case OVS_ACTION_ATTR_CT: >>>>> case OVS_ACTION_ATTR_UNSPEC: >>>>> case OVS_ACTION_ATTR_DEC_TTL: >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: >>>>> case __OVS_ACTION_ATTR_MAX: >>>>> /* The following actions are handled by the scalar >>>>> implementation. */ >>>>> case OVS_ACTION_ATTR_POP_VLAN: >>>>> diff --git a/lib/odp-util.c b/lib/odp-util.c >>>>> index 724e6f2bc..02452083f 100644 >>>>> --- a/lib/odp-util.c >>>>> +++ b/lib/odp-util.c >>>>> @@ -145,6 +145,7 @@ odp_action_len(uint16_t type) >>>>> case OVS_ACTION_ATTR_ADD_MPLS: return sizeof(struct >>>>> ovs_action_add_mpls); >>>>> case OVS_ACTION_ATTR_DEC_TTL: return ATTR_LEN_VARIABLE; >>>>> case OVS_ACTION_ATTR_DROP: return sizeof(uint32_t); >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: return ATTR_LEN_VARIABLE; >>>>> >>>>> case OVS_ACTION_ATTR_UNSPEC: >>>>> case __OVS_ACTION_ATTR_MAX: >>>>> @@ -1150,6 +1151,36 @@ format_dec_ttl_action(struct ds *ds, const struct >>>>> nlattr *attr, >>>>> ds_put_format(ds, "))"); >>>>> } >>>>> >>>>> +static void >>>>> +format_odp_emit_sample_action(struct ds *ds, const struct nlattr *attr) >>>>> +{ >>>>> + const struct nlattr *a; >>>>> + unsigned int left; >>>>> + >>>>> + ds_put_cstr(ds, "emit_sample("); >>>>> + >>>>> + NL_ATTR_FOR_EACH (a, left, >>>>> + nl_attr_get(attr), nl_attr_get_size(attr)) { >>>>> + switch (a->nla_type) { >>>>> + case OVS_EMIT_SAMPLE_ATTR_GROUP: >>>>> + ds_put_format(ds, "group=%"PRIu32",", nl_attr_get_u32(a)); >>>>> + break; >>>>> + case OVS_EMIT_SAMPLE_ATTR_COOKIE: { >>>>> + const uint8_t *cookie = nl_attr_get(a); >>>>> + int i; >>>>> + >>>>> + ds_put_cstr(ds, "cookie="); >>>>> + for (i = 0; i < nl_attr_get_size(a); i++) { >>>>> + ds_put_format(ds, "%02x", cookie[i]); >>>>> + } >>>> >>>> There is a ds_put_hex() function, it adds '0x' which I guess is not desired >>>> here (or maybe we should as it's more clear its a hex string). >>>> >>> >>> Ack. >>> >>>>> + break; >>>>> + } >>>>> + } >>>> >>>> nit: Maybe swap the two case statements to avoid the odd looking double }. >>> >>> Ack. >>> >>>>> + } >>>>> + ds_chomp(ds, ','); >>>>> + ds_put_char(ds, ')'); >>>>> +} >>>>> + >>>>> static void >>>>> format_odp_action(struct ds *ds, const struct nlattr *a, >>>>> const struct hmap *portno_names) >>>>> @@ -1309,6 +1340,9 @@ format_odp_action(struct ds *ds, const struct >>>>> nlattr *a, >>>>> case OVS_ACTION_ATTR_DROP: >>>>> ds_put_cstr(ds, "drop"); >>>>> break; >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: >>>>> + format_odp_emit_sample_action(ds, a); >>>>> + break; >>>>> case OVS_ACTION_ATTR_UNSPEC: >>>>> case __OVS_ACTION_ATTR_MAX: >>>>> default: >>>>> @@ -2358,6 +2392,50 @@ out: >>>>> return ret; >>>>> } >>>>> >>>>> +static int >>>>> +parse_odp_emit_sample_action(const char *s, struct ofpbuf *actions) >>>>> +{ >>>>> + uint8_t cookie[OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE]; >>>>> + char buf[2 * OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE + 1]; >>>> >>>> Reverse Christmas tree order. >>>> >>> >>> Ack. >>> >>>>> + bool has_group = false; >>>>> + size_t cookie_len = 0; >>>>> + uint32_t group; >>>>> + int n = 0; >>>>> + >>>>> + if (!ovs_scan_len(s, &n, "emit_sample(")) { >>>>> + return -EINVAL; >>>>> + } >>>>> + >>>>> + while (s[n] != ')') { >>>>> + n += strspn(s + n, delimiters); >>>>> + >>>>> + if (!has_group && ovs_scan_len(s, &n, "group=%"SCNi32, &group)) { >>>>> + has_group = true; >>>>> + continue; >>>>> + } >>>>> + >>>>> + if (!cookie_len && >>>>> + ovs_scan_len(s, &n, "cookie=%32[0-9a-fA-F]", buf) && n > 7) { >>>>> + struct ofpbuf b; >>>>> + >>>>> + ofpbuf_use_stub(&b, cookie, OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE); >>>>> + ofpbuf_put_hex(&b, buf, &cookie_len); >>>>> + ofpbuf_uninit(&b); >>>>> + continue; >>>>> + } >>>>> + return -EINVAL; >>>>> + } >>>>> + n++; >>>>> + >>>>> + if (!has_group) { >>>>> + return -EINVAL; >>>>> + } >>>>> + >>>>> + odp_put_emit_sample_action(actions, group, cookie_len ? cookie : >>>>> NULL, >>>>> + cookie_len); >>>>> + return n; >>>>> +} >>>>> + >>>>> static int >>>>> parse_action_list(struct parse_odp_context *context, const char *s, >>>>> struct ofpbuf *actions) >>>>> @@ -2719,6 +2797,12 @@ parse_odp_action__(struct parse_odp_context >>>>> *context, const char *s, >>>>> } >>>>> } >>>>> >>>>> + { >>>> >>>> You don' t need the extra {} set here, it's only needed if you have local >>>> variables defined. See pop_vlan for example. >>>> >>> >>> Looking at other actions in this function (e.g: "clone", "push_nsh", >>> "ct_clear") I thought "I don't see the reason but it looks like someone >>> did this on purpose". I missed other counter-examples. Will remove them. >> >> Thanks, yes we might need to cleanup some others. >> >>>>> + if (!strncmp(s, "emit_sample(", 12)) { >>>>> + return parse_odp_emit_sample_action(s, actions); >>>>> + } >>>>> + } >>>>> + >>>>> { >>>>> struct ovs_action_push_tnl data; >>>>> int n; >>>>> @@ -7828,6 +7912,25 @@ odp_put_tnl_push_action(struct ofpbuf *odp_actions, >>>>> nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_TUNNEL_PUSH, data, >>>>> size); >>>>> } >>>>> >>>>> +void >>>>> +odp_put_emit_sample_action(struct ofpbuf *odp_actions, >>>>> + uint32_t group_id, >>>>> + uint8_t *cookie, >>>>> + size_t cookie_len) >>>>> +{ >>>>> + size_t offset = nl_msg_start_nested_with_flag(odp_actions, >>>>> + >>>>> OVS_ACTION_ATTR_EMIT_SAMPLE); >>>>> + >>>>> + nl_msg_put_u32(odp_actions, OVS_EMIT_SAMPLE_ATTR_GROUP, group_id); >>>>> + if (cookie && cookie_len) { >>>>> + ovs_assert(cookie_len <= OVS_EMIT_SAMPLE_COOKIE_MAX_SIZE); >>>>> + nl_msg_put_unspec(odp_actions, OVS_EMIT_SAMPLE_ATTR_COOKIE, >>>>> cookie, >>>>> + cookie_len); >>>>> + } >>>>> + >>>>> + nl_msg_end_nested(odp_actions, offset); >>>>> +} >>>>> + >>>>> >>>>> /* The commit_odp_actions() function and its helpers. */ >>>>> >>>>> diff --git a/lib/odp-util.h b/lib/odp-util.h >>>>> index 8c7baa680..2159f0897 100644 >>>>> --- a/lib/odp-util.h >>>>> +++ b/lib/odp-util.h >>>>> @@ -376,6 +376,9 @@ void odp_put_pop_eth_action(struct ofpbuf >>>>> *odp_actions); >>>>> void odp_put_push_eth_action(struct ofpbuf *odp_actions, >>>>> const struct eth_addr *eth_src, >>>>> const struct eth_addr *eth_dst); >>>>> +void odp_put_emit_sample_action(struct ofpbuf *odp_actions, >>>>> + uint32_t group_id, uint8_t *cookie, >>>>> + size_t cookie_len); >>>>> >>>>> static inline void odp_decode_gbp_raw(uint32_t gbp_raw, >>>>> ovs_be16 *id, >>>>> diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c >>>>> index cd65dae7e..15e92ba74 100644 >>>>> --- a/ofproto/ofproto-dpif-ipfix.c >>>>> +++ b/ofproto/ofproto-dpif-ipfix.c >>>>> @@ -3136,6 +3136,7 @@ dpif_ipfix_read_actions(const struct flow *flow, >>>>> case OVS_ACTION_ATTR_DROP: >>>>> case OVS_ACTION_ATTR_ADD_MPLS: >>>>> case OVS_ACTION_ATTR_DEC_TTL: >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: >>>>> case __OVS_ACTION_ATTR_MAX: >>>>> default: >>>>> break; >>>>> diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c >>>>> index 4a68e9b94..9db499fd4 100644 >>>>> --- a/ofproto/ofproto-dpif-sflow.c >>>>> +++ b/ofproto/ofproto-dpif-sflow.c >>>>> @@ -1237,6 +1237,7 @@ dpif_sflow_read_actions(const struct flow *flow, >>>>> case OVS_ACTION_ATTR_DROP: >>>>> case OVS_ACTION_ATTR_ADD_MPLS: >>>>> case OVS_ACTION_ATTR_DEC_TTL: >>>>> + case OVS_ACTION_ATTR_EMIT_SAMPLE: >>>>> case __OVS_ACTION_ATTR_MAX: >>>>> default: >>>>> break; >>>>> diff --git a/python/ovs/flow/odp.py b/python/ovs/flow/odp.py >>>>> index 7d9b165d4..896ae34bd 100644 >>>>> --- a/python/ovs/flow/odp.py >>>>> +++ b/python/ovs/flow/odp.py >>>>> @@ -343,6 +343,14 @@ class ODPFlow(Flow): >>>>> } >>>>> ) >>>>> ), >>>>> + "emit_sample": nested_kv_decoder( >>>>> + KVDecoders( >>>>> + { >>>>> + "group": decode_int, >>>>> + "cookie": decode_default, >>>>> + } >>>>> + ) >>>>> + ) >>>>> } >>>>> >>>>> _decoders["sample"] = nested_kv_decoder( >>>>> diff --git a/tests/odp.at b/tests/odp.at >>>>> index ba20604e4..e5c2ee07c 100644 >>>>> --- a/tests/odp.at >>>>> +++ b/tests/odp.at >>>>> @@ -393,6 +393,10 @@ check_pkt_len(size=200,gt(ct(nat)),le(drop)) >>>>> >>>>> check_pkt_len(size=200,gt(set(eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15))),le(set(eth(src=00:01:02:03:04:06,dst=10:11:12:13:14:16)))) >>>>> lb_output(1) >>>>> add_mpls(label=200,tc=7,ttl=64,bos=1,eth_type=0x8847) >>>>> +emit_sample(group=12,cookie=01020304050607080910111213141516) >>>>> +emit_sample(group=12) >>>>> +sample(sample=50.0%,actions(emit_sample(group=12,cookie=01020304))) >>>>> +sample(sample=50.0%,actions(userspace(pid=42,userdata(0102030400000000)),emit_sample(group=12))) >>>>> ]) >>>>> AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], >>>>> [`cat actions.txt` >>>>> @@ -406,11 +410,23 @@ AT_DATA([actions.txt], [dnl >>>>> encap_nsh@:{@ >>>>> >>>>> tnl_push(tnl_port(6),header(size=94,type=112,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=43,tclass=0x0,hlimit=64),srv6(segments_left=2,segs(2001:cafe::90,2001:cafe::91))),out_port(1)) >>>>> >>>>> tnl_push(tnl_port(6),header(size=126,type=112,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=43,tclass=0x0,hlimit=64),srv6(segments_left=2,segs(2001:cafe::90,2001:cafe::91,2001:cafe::92,2001:cafe::93))),out_port(1)) >>>>> +emit_sample(group_id=12,cookie=0102030405060708090a0b0c0d0e0f0f0f) >>>>> +emit_sample(cookie=010203) >>>>> +emit_sample(group=12,cookie=010203,group=12) >>>>> +emit_sample(group=abc) >>>>> +emit_sample(group=12,cookie=wrong) >>>>> +emit_sample() >>>>> ]) >>>>> AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0], >>>>> [dnl >>>>> odp_actions_from_string: error >>>>> odp_actions_from_string: error >>>>> odp_actions_from_string: error >>>>> +odp_actions_from_string: error >>>>> +odp_actions_from_string: error >>>>> +odp_actions_from_string: error >>>>> +odp_actions_from_string: error >>>>> +odp_actions_from_string: error >>>>> +odp_actions_from_string: error >>>>> ]) >>>>> AT_CLEANUP >>>>> >>>>> -- >>>>> 2.45.1 >>>> >> _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
