On Fri, Jun 14, 2024 at 6:45 AM Dumitru Ceara <dce...@redhat.com> wrote: > > It improves the debugging experience if we can easily get a list of > OpenFlow rules and groups that contribute to the creation of a datapath > flow. > > The suggested workflow is: > a. dump datapath flows (along with UUIDs), this also prints the core IDs > (PMD IDs) when applicable. > > $ ovs-appctl dpctl/dump-flows -m > flow-dump from pmd on cpu core: 7 > ufid:7460db8f..., recirc_id(0), .... > > b. dump related OpenFlow rules and groups: > $ ovs-appctl upcall/dump-ufid-rules ufid:7460db8f... pmd=7 > cookie=0x12345678, table=0 > priority=100,ip,in_port=2,nw_dst=10.0.0.2,actions=resubmit(,1) > cookie=0x0, table=1 priority=200,actions=group:1 > group_id=1,bucket=bucket_id:0,actions=ct(commit,table=2,nat(dst=20.0.0.2)) > cookie=0x0, table=2 actions=output:1 > > The new command only shows rules and groups attached to ukeys that are > in states UKEY_VISIBLE or UKEY_OPERATIONAL. That should be fine as all > other ukeys should not be relevant for the use case presented above. > > For ukeys that don't have an xcache populated yet, the command goes > ahead and populates one. In theory this is creates a slight overhead as > those ukeys might not need an xcache until they see traffic (and get > revalidated) but in practice the overhead should be minimal. > > This commit tries to mimic the output format of the ovs-ofctl > dump-flows/dump-groups commands. For groups it actually uses > ofputil_group/_bucket functions for formatting. For rules it uses > flow_stats_ds() (the original function was exported and renamed to > ofproto_rule_stats_ds()). > > Signed-off-by: Dumitru Ceara <dce...@redhat.com> > --- > V3: > - Addressed Mike's comments: > - use ds_put_char() > - make the test less flaky > V2: > - Addressed Adrian's comments: > - check return value of populate_xcache() > - use flow_stats_ds() (renamed to ofproto_rule_stats_ds()) instead of > custom printing > - move ukey->state check to caller > - handle case when group bucket is not available > - update test case to cover the point above > --- > NEWS | 3 + > include/openvswitch/ofp-group.h | 7 ++ > lib/ofp-group.c | 110 +++++++++++++++++++------------- > ofproto/ofproto-dpif-upcall.c | 85 ++++++++++++++++++++++++ > ofproto/ofproto-dpif.c | 24 +++++++ > ofproto/ofproto-dpif.h | 2 + > ofproto/ofproto-provider.h | 1 + > ofproto/ofproto.c | 85 ++++++++++++------------ > tests/ofproto-dpif.at | 47 ++++++++++++++ > tests/ofproto-macros.at | 9 ++- > 10 files changed, 285 insertions(+), 88 deletions(-) > > diff --git a/NEWS b/NEWS > index 5ae0108d552b..1bc085f97045 100644 > --- a/NEWS > +++ b/NEWS > @@ -9,6 +9,9 @@ Post-v3.3.0 > https://github.com/openvswitch/ovs.git > - DPDK: > * OVS validated with DPDK 23.11.1. > + - ovs-appctl: > + * Added 'upcall/dump-ufid-rules' to output the set of OpenFlow rules and > + groups that contributed to the creation of a specific datapath flow. > > > v3.3.0 - 16 Feb 2024 > diff --git a/include/openvswitch/ofp-group.h b/include/openvswitch/ofp-group.h > index cd7af0ebff9c..79fcb3a4c0d1 100644 > --- a/include/openvswitch/ofp-group.h > +++ b/include/openvswitch/ofp-group.h > @@ -70,6 +70,11 @@ struct ofputil_bucket *ofputil_bucket_find(const struct > ovs_list *, > bool ofputil_bucket_check_duplicate_id(const struct ovs_list *); > struct ofputil_bucket *ofputil_bucket_list_front(const struct ovs_list *); > struct ofputil_bucket *ofputil_bucket_list_back(const struct ovs_list *); > +void ofputil_bucket_format(const struct ofputil_bucket *, > + enum ofp11_group_type, enum ofp_version, > + const struct ofputil_port_map *, > + const struct ofputil_table_map *, > + struct ds *); > > static inline bool > ofputil_bucket_has_liveness(const struct ofputil_bucket *bucket) > @@ -88,6 +93,8 @@ struct ofputil_group_props { > void ofputil_group_properties_destroy(struct ofputil_group_props *); > void ofputil_group_properties_copy(struct ofputil_group_props *to, > const struct ofputil_group_props *from); > +void ofputil_group_properties_format(const struct ofputil_group_props *, > + struct ds *); > /* Protocol-independent group_mod. */ > struct ofputil_group_mod { > uint16_t command; /* One of OFPGC15_*. */ > diff --git a/lib/ofp-group.c b/lib/ofp-group.c > index 737f48047b10..0be453d15203 100644 > --- a/lib/ofp-group.c > +++ b/lib/ofp-group.c > @@ -58,14 +58,16 @@ ofputil_group_from_string(const char *s, uint32_t > *group_idp) > return true; > } > > -/* Appends to 's' a string representation of the OpenFlow group ID > 'group_id'. > - * Most groups' string representation is just the number, but for special > - * groups, e.g. OFPG_ALL, it is the name, e.g. "ALL". */ > +/* Appends to 's' a string representation of the OpenFlow group 'group_id'. > + * Most groups' string representation is just 'group_id=' followed by the ID, > + * but for special groups, e.g. OFPG_ALL, the ID is replaced by the name, > + * e.g. "ALL". */ > void > ofputil_format_group(uint32_t group_id, struct ds *s) > { > char name[MAX_GROUP_NAME_LEN + 1]; > > + ds_put_cstr(s, "group_id="); > ofputil_group_to_string(group_id, name, sizeof name); > ds_put_cstr(s, name); > } > @@ -297,7 +299,7 @@ ofputil_group_desc_request_format(struct ds *string, > const struct ofp_header *oh) > { > uint32_t group_id = ofputil_decode_group_desc_request(oh); > - ds_put_cstr(string, " group_id="); > + ds_put_char(string, ' '); > ofputil_format_group(group_id, string); > > return 0; > @@ -585,7 +587,7 @@ ofputil_group_stats_request_format(struct ds *string, > return error; > } > > - ds_put_cstr(string, " group_id="); > + ds_put_char(string, ' '); > ofputil_format_group(group_id, string); > return 0; > } > @@ -1526,6 +1528,31 @@ ofputil_group_properties_destroy(struct > ofputil_group_props *gp) > free(gp->fields.values); > } > > +void > +ofputil_group_properties_format(const struct ofputil_group_props *gp, > + struct ds *ds) > +{ > + if (!gp->selection_method[0]) { > + return; > + } > + > + ds_put_format(ds, ",selection_method=%s", gp->selection_method); > + if (gp->selection_method_param) { > + ds_put_format(ds, ",selection_method_param=%"PRIu64, > + gp->selection_method_param); > + } > + > + size_t n = bitmap_count1(gp->fields.used.bm, MFF_N_IDS); > + if (n == 1) { > + ds_put_cstr(ds, ",fields="); > + oxm_format_field_array(ds, &gp->fields); > + } else if (n > 1) { > + ds_put_cstr(ds, ",fields("); > + oxm_format_field_array(ds, &gp->fields); > + ds_put_char(ds, ')'); > + } > +} > + > static enum ofperr > parse_group_prop_ntr_selection_method(struct ofpbuf *payload, > enum ofp11_group_type group_type, > @@ -1813,6 +1840,37 @@ ofp_print_bucket_id(struct ds *s, const char *label, > uint32_t bucket_id, > ds_put_char(s, ','); > } > > +void > +ofputil_bucket_format(const struct ofputil_bucket *bucket, > + enum ofp11_group_type type, enum ofp_version > ofp_version, > + const struct ofputil_port_map *port_map, > + const struct ofputil_table_map *table_map, > + struct ds *s) > +{ > + ds_put_cstr(s, "bucket="); > + > + ofp_print_bucket_id(s, "bucket_id:", bucket->bucket_id, ofp_version); > + if (bucket->weight != (type == OFPGT11_SELECT ? 1 : 0)) { > + ds_put_format(s, "weight:%"PRIu16",", bucket->weight); > + } > + if (bucket->watch_port != OFPP_NONE) { > + ds_put_cstr(s, "watch_port:"); > + ofputil_format_port(bucket->watch_port, port_map, s); > + ds_put_char(s, ','); > + } > + if (bucket->watch_group != OFPG_ANY) { > + ds_put_format(s, "watch_group:%"PRIu32",", bucket->watch_group); > + } > + > + ds_put_cstr(s, "actions="); > + struct ofpact_format_params fp = { > + .port_map = port_map, > + .table_map = table_map, > + .s = s, > + }; > + ofpacts_format(bucket->ofpacts, bucket->ofpacts_len, &fp); > +} > + > static void > ofp_print_group(struct ds *s, uint32_t group_id, uint8_t type, > const struct ovs_list *p_buckets, > @@ -1831,23 +1889,7 @@ ofp_print_group(struct ds *s, uint32_t group_id, > uint8_t type, > ds_put_format(s, ",type=%s", type_str[type > 4 ? 4 : type]); > } > > - if (props->selection_method[0]) { > - ds_put_format(s, ",selection_method=%s", props->selection_method); > - if (props->selection_method_param) { > - ds_put_format(s, ",selection_method_param=%"PRIu64, > - props->selection_method_param); > - } > - > - size_t n = bitmap_count1(props->fields.used.bm, MFF_N_IDS); > - if (n == 1) { > - ds_put_cstr(s, ",fields="); > - oxm_format_field_array(s, &props->fields); > - } else if (n > 1) { > - ds_put_cstr(s, ",fields("); > - oxm_format_field_array(s, &props->fields); > - ds_put_char(s, ')'); > - } > - } > + ofputil_group_properties_format(props, s); > > if (!p_buckets) { > return; > @@ -1856,28 +1898,8 @@ ofp_print_group(struct ds *s, uint32_t group_id, > uint8_t type, > ds_put_char(s, ','); > > LIST_FOR_EACH (bucket, list_node, p_buckets) { > - ds_put_cstr(s, "bucket="); > - > - ofp_print_bucket_id(s, "bucket_id:", bucket->bucket_id, ofp_version); > - if (bucket->weight != (type == OFPGT11_SELECT ? 1 : 0)) { > - ds_put_format(s, "weight:%"PRIu16",", bucket->weight); > - } > - if (bucket->watch_port != OFPP_NONE) { > - ds_put_cstr(s, "watch_port:"); > - ofputil_format_port(bucket->watch_port, port_map, s); > - ds_put_char(s, ','); > - } > - if (bucket->watch_group != OFPG_ANY) { > - ds_put_format(s, "watch_group:%"PRIu32",", bucket->watch_group); > - } > - > - ds_put_cstr(s, "actions="); > - struct ofpact_format_params fp = { > - .port_map = port_map, > - .table_map = table_map, > - .s = s, > - }; > - ofpacts_format(bucket->ofpacts, bucket->ofpacts_len, &fp); > + ofputil_bucket_format(bucket, type, ofp_version, > + port_map, table_map, s); > ds_put_char(s, ','); > } > > diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c > index 83609ec62b63..e2fec413f461 100644 > --- a/ofproto/ofproto-dpif-upcall.c > +++ b/ofproto/ofproto-dpif-upcall.c > @@ -383,6 +383,9 @@ static void upcall_unixctl_disable_ufid(struct > unixctl_conn *, int argc, > const char *argv[], void *aux); > static void upcall_unixctl_enable_ufid(struct unixctl_conn *, int argc, > const char *argv[], void *aux); > +static void upcall_unixctl_dump_ufid_rules(struct unixctl_conn *, int argc, > + const char *argv[], void *aux); > + > static void upcall_unixctl_set_flow_limit(struct unixctl_conn *conn, int > argc, > const char *argv[], void *aux); > static void upcall_unixctl_dump_wait(struct unixctl_conn *conn, int argc, > @@ -460,6 +463,8 @@ udpif_init(void) > upcall_unixctl_disable_ufid, NULL); > unixctl_command_register("upcall/enable-ufid", "", 0, 0, > upcall_unixctl_enable_ufid, NULL); > + unixctl_command_register("upcall/dump-ufid-rules", "UFID PMD-ID", 1, > 2, > + upcall_unixctl_dump_ufid_rules, NULL); > unixctl_command_register("upcall/set-flow-limit", > "flow-limit-number", > 1, 1, upcall_unixctl_set_flow_limit, NULL); > unixctl_command_register("revalidator/wait", "", 0, 0, > @@ -2479,6 +2484,42 @@ revalidate_ukey(struct udpif *udpif, struct udpif_key > *ukey, > return result; > } > > +static void > +ukey_xcache_format(struct udpif *udpif, struct udpif_key *ukey, struct ds > *ds) > + OVS_REQUIRES(ukey->mutex) > +{ > + if (!ukey->xcache) { > + if (populate_xcache(udpif, ukey, ukey->stats.tcp_flags)) { > + return; > + } > + } > + > + struct ofpbuf entries = ukey->xcache->entries; > + struct xc_entry *entry; > + > + XC_ENTRY_FOR_EACH (entry, &entries) { > + switch (entry->type) { > + case XC_RULE: > + ofproto_rule_stats_ds(&entry->rule->up, ds, true); > + break; > + case XC_GROUP: > + group_dpif_format(entry->group.group, entry->group.bucket, ds); > + break; > + case XC_TABLE: > + case XC_BOND: > + case XC_NETDEV: > + case XC_NETFLOW: > + case XC_MIRROR: > + case XC_LEARN: > + case XC_NORMAL: > + case XC_FIN_TIMEOUT: > + case XC_TNL_NEIGH: > + case XC_TUNNEL_HEADER: > + break; > + } > + } > +} > + > static void > delete_op_init__(struct udpif *udpif, struct ukey_op *op, > const struct dpif_flow *flow) > @@ -3220,6 +3261,50 @@ upcall_unixctl_enable_ufid(struct unixctl_conn *conn, > int argc OVS_UNUSED, > "for supported datapaths"); > } > > +static void > +upcall_unixctl_dump_ufid_rules(struct unixctl_conn *conn, int argc, > + const char *argv[], void *aux OVS_UNUSED) > +{ > + unsigned int pmd_id = PMD_ID_NULL; > + const char *key_s = argv[1]; > + ovs_u128 ufid; > + > + if (odp_ufid_from_string(key_s, &ufid) <= 0) { > + unixctl_command_reply_error(conn, "failed to parse ufid"); > + return; > + } > + > + if (argc == 3) { > + const char *pmd_str = argv[2]; > + if (!ovs_scan(pmd_str, "pmd=%d", &pmd_id)) { > + unixctl_command_reply_error(conn, > + "Invalid pmd argument format. " > + "Expecting 'pmd=PMD-ID'"); > + return; > + } > + } > + > + struct ds ds = DS_EMPTY_INITIALIZER; > + struct udpif *udpif; > + > + LIST_FOR_EACH (udpif, list_node, &all_udpifs) { > + struct udpif_key *ukey = ukey_lookup(udpif, &ufid, pmd_id); > + if (!ukey) { > + continue; > + } > + > + ovs_mutex_lock(&ukey->mutex); > + /* It only makes sense to format rules for ukeys that are (still) > + * in use. */ > + if (ukey->state == UKEY_VISIBLE || ukey->state == UKEY_OPERATIONAL) { > + ukey_xcache_format(udpif, ukey, &ds); > + } > + ovs_mutex_unlock(&ukey->mutex); > + } > + unixctl_command_reply(conn, ds_cstr(&ds)); > + ds_destroy(&ds); > +} > + > /* Set the flow limit. > * > * This command is only needed for advanced debugging, so it's not > diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c > index fcd7cd753ca4..4049c439e62d 100644 > --- a/ofproto/ofproto-dpif.c > +++ b/ofproto/ofproto-dpif.c > @@ -5407,6 +5407,30 @@ group_dpif_lookup(struct ofproto_dpif *ofproto, > uint32_t group_id, > version, take_ref); > return ofgroup ? group_dpif_cast(ofgroup) : NULL; > } > + > +void > +group_dpif_format(struct group_dpif *group, struct ofputil_bucket *bucket, > + struct ds *ds) > +{ > + struct ofgroup *ofg = &group->up; > + > + ofputil_format_group(ofg->group_id, ds); > + ofputil_group_properties_format(&ofg->props, ds); > + ds_put_char(ds, ','); > + > + if (bucket) { > + ofputil_bucket_format(bucket, ofg->type, OFP15_VERSION, > + NULL, NULL, ds); > + } else { > + LIST_FOR_EACH (bucket, list_node, &ofg->buckets) { > + ofputil_bucket_format(bucket, ofg->type, OFP15_VERSION, > + NULL, NULL, ds); > + ds_put_char(ds, ','); > + } > + } > + ds_chomp(ds, ','); > + ds_put_char(ds, '\n'); > +} > > /* Sends 'packet' out 'ofport'. If 'port' is a tunnel and that tunnel type > * supports a notion of an OAM flag, sets it if 'oam' is true. > diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h > index d33f73df8aed..7be1515c4636 100644 > --- a/ofproto/ofproto-dpif.h > +++ b/ofproto/ofproto-dpif.h > @@ -151,6 +151,8 @@ void group_dpif_credit_stats(struct group_dpif *, > struct group_dpif *group_dpif_lookup(struct ofproto_dpif *, > uint32_t group_id, ovs_version_t > version, > bool take_ref); > +void group_dpif_format(struct group_dpif *, struct ofputil_bucket *, > + struct ds *); > > > /* Backers. > diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h > index 83c509fcf804..e7309c71289b 100644 > --- a/ofproto/ofproto-provider.h > +++ b/ofproto/ofproto-provider.h > @@ -449,6 +449,7 @@ struct rule { > void ofproto_rule_ref(struct rule *); > bool ofproto_rule_try_ref(struct rule *); > void ofproto_rule_unref(struct rule *); > +void ofproto_rule_stats_ds(struct rule *, struct ds *, bool offload_stats); > > static inline const struct rule_actions * rule_get_actions(const struct rule > *); > static inline bool rule_is_table_miss(const struct rule *); > diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c > index 21c6a1d8257d..aeace33d0654 100644 > --- a/ofproto/ofproto.c > +++ b/ofproto/ofproto.c > @@ -3086,6 +3086,48 @@ ofproto_rule_unref(struct rule *rule) > } > } > > +void > +ofproto_rule_stats_ds(struct rule *rule, struct ds *results, > + bool offload_stats) > +{ > + struct pkt_stats stats; > + const struct rule_actions *actions; > + long long int created, used; > + > + rule->ofproto->ofproto_class->rule_get_stats(rule, &stats, &used); > + > + ovs_mutex_lock(&rule->mutex); > + actions = rule_get_actions(rule); > + created = rule->created; > + ovs_mutex_unlock(&rule->mutex); > + > + if (rule->flow_cookie != 0) { > + ds_put_format(results, "cookie=0x%"PRIx64", ", > + ntohll(rule->flow_cookie)); > + } > + if (rule->table_id != 0) { > + ds_put_format(results, "table_id=%"PRIu8", ", rule->table_id); > + } > + ds_put_format(results, "duration=%llds, ", (time_msec() - created) / > 1000); > + ds_put_format(results, "n_packets=%"PRIu64", ", stats.n_packets); > + ds_put_format(results, "n_bytes=%"PRIu64", ", stats.n_bytes); > + if (offload_stats) { > + ds_put_format(results, "n_offload_packets=%"PRIu64", ", > + stats.n_offload_packets); > + ds_put_format(results, "n_offload_bytes=%"PRIu64", ", > + stats.n_offload_bytes); > + } > + cls_rule_format(&rule->cr, ofproto_get_tun_tab(rule->ofproto), NULL, > + results); > + ds_put_char(results, ','); > + > + ds_put_cstr(results, "actions="); > + struct ofpact_format_params fp = { .s = results }; > + ofpacts_format(actions->ofpacts, actions->ofpacts_len, &fp); > + > + ds_put_cstr(results, "\n"); > +} > + > static void > remove_rule_rcu__(struct rule *rule) > OVS_REQUIRES(ofproto_mutex) > @@ -4844,47 +4886,6 @@ handle_flow_stats_request(struct ofconn *ofconn, > return 0; > } > > -static void > -flow_stats_ds(struct ofproto *ofproto, struct rule *rule, struct ds *results, > - bool offload_stats) > -{ > - struct pkt_stats stats; > - const struct rule_actions *actions; > - long long int created, used; > - > - rule->ofproto->ofproto_class->rule_get_stats(rule, &stats, &used); > - > - ovs_mutex_lock(&rule->mutex); > - actions = rule_get_actions(rule); > - created = rule->created; > - ovs_mutex_unlock(&rule->mutex); > - > - if (rule->flow_cookie != 0) { > - ds_put_format(results, "cookie=0x%"PRIx64", ", > - ntohll(rule->flow_cookie)); > - } > - if (rule->table_id != 0) { > - ds_put_format(results, "table_id=%"PRIu8", ", rule->table_id); > - } > - ds_put_format(results, "duration=%llds, ", (time_msec() - created) / > 1000); > - ds_put_format(results, "n_packets=%"PRIu64", ", stats.n_packets); > - ds_put_format(results, "n_bytes=%"PRIu64", ", stats.n_bytes); > - if (offload_stats) { > - ds_put_format(results, "n_offload_packets=%"PRIu64", ", > - stats.n_offload_packets); > - ds_put_format(results, "n_offload_bytes=%"PRIu64", ", > - stats.n_offload_bytes); > - } > - cls_rule_format(&rule->cr, ofproto_get_tun_tab(ofproto), NULL, results); > - ds_put_char(results, ','); > - > - ds_put_cstr(results, "actions="); > - struct ofpact_format_params fp = { .s = results }; > - ofpacts_format(actions->ofpacts, actions->ofpacts_len, &fp); > - > - ds_put_cstr(results, "\n"); > -} > - > /* Adds a pretty-printed description of all flows to 'results', including > * hidden flows (e.g., set up by in-band control). */ > void > @@ -4897,7 +4898,7 @@ ofproto_get_all_flows(struct ofproto *p, struct ds > *results, > struct rule *rule; > > CLS_FOR_EACH (rule, cr, &table->cls) { > - flow_stats_ds(p, rule, results, offload_stats); > + ofproto_rule_stats_ds(rule, results, offload_stats); > } > } > } > diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at > index 0b23fd6c5ea6..f8e7a1356d80 100644 > --- a/tests/ofproto-dpif.at > +++ b/tests/ofproto-dpif.at > @@ -12136,3 +12136,50 @@ AT_CHECK([test 1 = `ovs-ofctl parse-pcap p2-tx.pcap > | wc -l`]) > > OVS_VSWITCHD_STOP > AT_CLEANUP > + > +AT_SETUP([ofproto-dpif - dump-ufid-rules]) > +OVS_VSWITCHD_START( > +[add-port br0 p1 \ > + -- set bridge br0 datapath-type=dummy \ > + -- set interface p1 type=dummy-pmd \ > + -- add-port br0 p2 \ > + -- set interface p2 type=dummy-pmd \ > + -- add-port br0 p3 \ > + -- set interface p3 type=dummy-pmd > +], [], [], [DUMMY_NUMA])
I was still having trouble running the test, sometimes the port numbers would be different. I resolved this by setting up the ports as follows: +[add-port br0 p1 \ + -- set bridge br0 datapath-type=dummy \ + -- set interface p1 type=dummy-pmd ofport_request=3 \ + -- add-port br0 p2 \ + -- set interface p2 type=dummy-pmd ofport_request=1 \ + -- add-port br0 p3 \ + -- set interface p3 type=dummy-pmd ofport_request=2 +], [], [], [DUMMY_NUMA]) But I've reviewed the rest of the patch and it looks good to me! Cheers, M > + > +dnl Add some OpenFlow rules and groups. > +AT_DATA([groups.txt], [dnl > + > group_id=1,type=select,selection_method=dp_hash,bucket=bucket_id:0,weight:100,actions=ct(commit,table=2,nat(dst=20.0.0.2)) > + group_id=2,type=all,bucket=resubmit(,3),bucket=resubmit(,4) > +]) > +AT_DATA([flows.txt], [dnl > +table=0,priority=100,cookie=0x12345678,in_port=p1,ip,nw_dst=10.0.0.2,actions=resubmit(,1) > +table=1,priority=200,actions=group:1 > +table=2,actions=group:2 > +table=3,actions=p2 > +table=4,actions=p3 > +]) > +AT_CHECK([ovs-ofctl add-groups br0 groups.txt]) > +AT_CHECK([ovs-ofctl add-flows br0 flows.txt]) > + > +AT_CHECK([ovs-appctl netdev-dummy/receive p1 > 'ipv4(src=10.0.0.1,dst=10.0.0.2,proto=6),tcp(src=1,dst=2)']) > + > +OVS_WAIT_UNTIL_EQUAL([ovs-appctl dpctl/dump-flows | sed 's/.*core: > [[0-9]]*//' | ofctl_strip | sort], [ > +recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(dst=10.0.0.2,frag=no), > packets:0, bytes:0, used:never, actions:hash(l4(0)),recirc(0x1) > +recirc_id(0x1),dp_hash(0x0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), > packets:0, bytes:0, used:never, > actions:ct(commit,nat(dst=20.0.0.2)),recirc(0x2) > +recirc_id(0x2),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), > packets:0, bytes:0, used:never, actions:2,3]) > + > +ufids=$(ovs-appctl dpctl/dump-flows -m | match_ufid) > +AT_CHECK([for ufid in $ufids; do ovs-appctl upcall/dump-ufid-rules $ufid > pmd=0; done | ofctl_strip | sort], [0], [dnl > +cookie=0x12345678, n_packets=1, n_bytes=118, n_offload_packets=0, > n_offload_bytes=0, > priority=100,ip,in_port=3,nw_dst=10.0.0.2,actions=resubmit(,1) > +group_id=1,selection_method=dp_hash,bucket=bucket_id:0,weight:100,actions=ct(commit,table=2,nat(dst=20.0.0.2)) > +group_id=2,bucket=bucket_id:0,actions=resubmit(,3),bucket=bucket_id:1,actions=resubmit(,4) > +table_id=1, n_packets=1, n_bytes=118, n_offload_packets=0, > n_offload_bytes=0, priority=200,actions=group:1 > +table_id=2, n_packets=1, n_bytes=118, n_offload_packets=0, > n_offload_bytes=0, ,actions=group:2 > +table_id=3, n_packets=1, n_bytes=118, n_offload_packets=0, > n_offload_bytes=0, ,actions=output:1 > +table_id=4, n_packets=1, n_bytes=118, n_offload_packets=0, > n_offload_bytes=0, ,actions=output:2 > +]) > + > +OVS_VSWITCHD_STOP > +AT_CLEANUP > diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at > index c22fb3c79c3f..bf442025bcd5 100644 > --- a/tests/ofproto-macros.at > +++ b/tests/ofproto-macros.at > @@ -1,7 +1,7 @@ > m4_divert_push([PREPARE_TESTS]) > [ > -# Strips out uninteresting parts of ovs-ofctl output, as well as parts > -# that vary from one run to another. > +# Strips out uninteresting parts of flow dump outputs (ofctl/dpctl), as well > +# as parts that vary from one run to another. > ofctl_strip () { > sed ' > s/ (xid=0x[0-9a-fA-F]*)// > @@ -13,6 +13,7 @@ s/ n_bytes=0,// > s/ idle_age=[0-9]*,// > s/ hard_age=[0-9]*,// > s/dp_hash=0x[0-9a-f]*\//dp_hash=0x0\// > +s/dp_hash([0-9a-fx/]*)/dp_hash(0x0)/ > s/recirc_id=0x[0-9a-f]*,/recirc_id=0x0,/ > > s/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]Z|// > s/dir\/[0-9]*\/br0.mgmt/dir\/XXXX\/br0.mgmt/ > @@ -140,6 +141,10 @@ strip_ufid () { > s/ufid:[[-0-9a-f]]* //' > } > > +match_ufid () { > + grep -oE 'ufid:[[-0-9a-f]]+' | sort -u > +} > + > # Strips packets: and bytes: from output > strip_stats () { > sed 's/packets:[[0-9]]*/packets:0/ > -- > 2.44.0 > _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev