A conjunctive flow consists of two or more multiple flows with
conjunction actions. When input to the ofproto/trace command
matches a conjunctive flow, it outputs flows of all dimensions.

Acked-by: Simon Horman <ho...@ovn.org>
Signed-off-by: Nobuhiro MIKI <nm...@yahoo-corp.jp>
---
v6:
* Support multiple tables with resubmit action
v5:
* Support tunnel metadata
v4:
* Fix conj_id matching
* Fix priority matching
* Add a new test
v3:
* Remove struct flow changes.
* Use struct 'cls_rule' instead of struct 'flow'.
* Add priority and id conditionals for 'soft' arrays.
* Use 'minimask' in struct 'cls_rule' as mask.
* Use hmapx instead of ovs_list to store conj flows.
* Passe 'conj_flows' as an argument only when tracing.
v2:
* Reimplemented v1 with a safer and cleaner approach,
  since v1 was a messy implementation that rewrote const variables.
---
 lib/classifier.c             | 51 ++++++++++++++++---
 lib/classifier.h             |  4 +-
 lib/ovs-router.c             |  5 +-
 lib/tnl-ports.c              |  6 +--
 ofproto/ofproto-dpif-xlate.c | 67 +++++++++++++++++++++---
 ofproto/ofproto-dpif.c       | 25 ++++++---
 ofproto/ofproto-dpif.h       |  3 +-
 tests/classifier.at          | 99 ++++++++++++++++++++++++++++++++++++
 tests/test-classifier.c      |  8 +--
 9 files changed, 238 insertions(+), 30 deletions(-)

diff --git a/lib/classifier.c b/lib/classifier.c
index 18dbfc83ad44..0729bd190243 100644
--- a/lib/classifier.c
+++ b/lib/classifier.c
@@ -853,6 +853,32 @@ trie_ctx_init(struct trie_ctx *ctx, const struct cls_trie 
*trie)
     ctx->lookup_done = false;
 }
 
+static void
+insert_conj_flows(struct hmapx *conj_flows, uint32_t id, int priority,
+                  struct cls_conjunction_set **soft, size_t n_soft)
+{
+    struct cls_conjunction_set *conj_set;
+
+    if (!conj_flows) {
+        return;
+    }
+
+    for (size_t i = 0; i < n_soft; i++) {
+        conj_set = soft[i];
+
+        if (conj_set->priority != priority) {
+            continue;
+        }
+
+        for (size_t j = 0; j < conj_set->n; j++) {
+            if (conj_set->conj[j].id == id) {
+                hmapx_add(conj_flows, (void *) (conj_set->match->cls_rule));
+                break;
+            }
+        }
+    }
+}
+
 struct conjunctive_match {
     struct hmap_node hmap_node;
     uint32_t id;
@@ -933,11 +959,15 @@ free_conjunctive_matches(struct hmap *matches,
  * recursion within this function itself.
  *
  * 'flow' is non-const to allow for temporary modifications during the lookup.
- * Any changes are restored before returning. */
+ * Any changes are restored before returning.
+ *
+ * 'conj_flows' is an optional parameter.  If it is non-null, the matching
+ * conjunctive flows are inserted. */
 static const struct cls_rule *
 classifier_lookup__(const struct classifier *cls, ovs_version_t version,
                     struct flow *flow, struct flow_wildcards *wc,
-                    bool allow_conjunctive_matches)
+                    bool allow_conjunctive_matches,
+                    struct hmapx *conj_flows)
 {
     struct trie_ctx trie_ctx[CLS_MAX_TRIES];
     const struct cls_match *match;
@@ -1097,10 +1127,15 @@ classifier_lookup__(const struct classifier *cls, 
ovs_version_t version,
                 const struct cls_rule *rule;
 
                 flow->conj_id = id;
-                rule = classifier_lookup__(cls, version, flow, wc, false);
+                rule = classifier_lookup__(cls, version, flow, wc, false,
+                                           NULL);
                 flow->conj_id = saved_conj_id;
 
                 if (rule) {
+                    if (allow_conjunctive_matches) {
+                        insert_conj_flows(conj_flows, id, soft_pri, soft,
+                                          n_soft);
+                    }
                     free_conjunctive_matches(&matches,
                                              cm_stubs, ARRAY_SIZE(cm_stubs));
                     if (soft != soft_stub) {
@@ -1161,12 +1196,16 @@ classifier_lookup__(const struct classifier *cls, 
ovs_version_t version,
  * flow_wildcards_init_catchall()).
  *
  * 'flow' is non-const to allow for temporary modifications during the lookup.
- * Any changes are restored before returning. */
+ * Any changes are restored before returning.
+ *
+ * 'conj_flows' is an optional parameter.  If it is non-null, the matching
+ * conjunctive flows are inserted. */
 const struct cls_rule *
 classifier_lookup(const struct classifier *cls, ovs_version_t version,
-                  struct flow *flow, struct flow_wildcards *wc)
+                  struct flow *flow, struct flow_wildcards *wc,
+                  struct hmapx *conj_flows)
 {
-    return classifier_lookup__(cls, version, flow, wc, true);
+    return classifier_lookup__(cls, version, flow, wc, true, conj_flows);
 }
 
 /* Finds and returns a rule in 'cls' with exactly the same priority and
diff --git a/lib/classifier.h b/lib/classifier.h
index f646a8f7429b..f55a2cba998e 100644
--- a/lib/classifier.h
+++ b/lib/classifier.h
@@ -299,6 +299,7 @@
  * parallel to the rule's removal. */
 
 #include "cmap.h"
+#include "hmapx.h"
 #include "openvswitch/match.h"
 #include "openvswitch/meta-flow.h"
 #include "pvector.h"
@@ -398,7 +399,8 @@ static inline void classifier_publish(struct classifier *);
  * and each other. */
 const struct cls_rule *classifier_lookup(const struct classifier *,
                                          ovs_version_t, struct flow *,
-                                         struct flow_wildcards *);
+                                         struct flow_wildcards *,
+                                         struct hmapx *conj_flows);
 bool classifier_rule_overlaps(const struct classifier *,
                               const struct cls_rule *, ovs_version_t);
 const struct cls_rule *classifier_find_rule_exactly(const struct classifier *,
diff --git a/lib/ovs-router.c b/lib/ovs-router.c
index 7c04bb0e6b14..ca014d80ed31 100644
--- a/lib/ovs-router.c
+++ b/lib/ovs-router.c
@@ -115,7 +115,8 @@ ovs_router_lookup(uint32_t mark, const struct in6_addr 
*ip6_dst,
         const struct cls_rule *cr_src;
         struct flow flow_src = {.ipv6_dst = *src, .pkt_mark = mark};
 
-        cr_src = classifier_lookup(&cls, OVS_VERSION_MAX, &flow_src, NULL);
+        cr_src = classifier_lookup(&cls, OVS_VERSION_MAX, &flow_src, NULL,
+                                   NULL);
         if (cr_src) {
             struct ovs_router_entry *p_src = ovs_router_entry_cast(cr_src);
             if (!p_src->local) {
@@ -126,7 +127,7 @@ ovs_router_lookup(uint32_t mark, const struct in6_addr 
*ip6_dst,
         }
     }
 
-    cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL);
+    cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL, NULL);
     if (cr) {
         struct ovs_router_entry *p = ovs_router_entry_cast(cr);
 
diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
index f16409a0bf08..bb0b0b0c55f4 100644
--- a/lib/tnl-ports.c
+++ b/lib/tnl-ports.c
@@ -112,7 +112,7 @@ map_insert(odp_port_t port, struct eth_addr mac, struct 
in6_addr *addr,
     tnl_port_init_flow(&match.flow, mac, addr, nw_proto, tp_port);
 
     do {
-        cr = classifier_lookup(&cls, OVS_VERSION_MAX, &match.flow, NULL);
+        cr = classifier_lookup(&cls, OVS_VERSION_MAX, &match.flow, NULL, NULL);
         p = tnl_port_cast(cr);
         /* Try again if the rule was released before we get the reference. */
     } while (p && !ovs_refcount_try_ref_rcu(&p->ref_cnt));
@@ -247,7 +247,7 @@ map_delete(struct eth_addr mac, struct in6_addr *addr,
 
     tnl_port_init_flow(&flow, mac, addr, nw_proto, tp_port);
 
-    cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL);
+    cr = classifier_lookup(&cls, OVS_VERSION_MAX, &flow, NULL, NULL);
     tnl_port_unref(cr);
 }
 
@@ -305,7 +305,7 @@ odp_port_t
 tnl_port_map_lookup(struct flow *flow, struct flow_wildcards *wc)
 {
     const struct cls_rule *cr = classifier_lookup(&cls, OVS_VERSION_MAX, flow,
-                                                  wc);
+                                                  wc, NULL);
 
     return (cr) ? tnl_port_cast(cr)->portno : ODPP_NONE;
 }
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index e243773307b7..289f8a7361d2 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -229,6 +229,9 @@ struct xlate_ctx {
      * wants actions. */
     struct ofpbuf *odp_actions;
 
+    /* Set of matching conjunctive flows, or NULL. */
+    struct hmapx *conj_flows;
+
     /* Statistics maintained by xlate_table_action().
      *
      * These statistics limit the amount of work that a single flow
@@ -866,6 +869,34 @@ xlate_report_action_set(const struct xlate_ctx *ctx, const 
char *verb)
     }
 }
 
+static void
+xlate_report_conj_matches(const struct xlate_ctx *ctx,
+                          const struct ofputil_port_map *map)
+{
+    struct ds s = DS_EMPTY_INITIALIZER;
+    struct hmapx_node *node;
+    struct cls_rule *rule;
+
+    /* NOTE: The conj flows have meaning in order.  For each flow that is a
+     * component of conj flows, 'k' in 'conjunction(id, k/n)' represents the
+     * dimension.  When there are multiple flows with the same id, it may be
+     * implicitly expected that they would be output in ascending order of 'k'.
+     *
+     * However, because of the use of hmapx strucutre and the fact that the
+     * classifier returns them in arbitrary order, they are output in arbitrary
+     * order here. */
+    HMAPX_FOR_EACH (node, ctx->conj_flows) {
+        ds_clear(&s);
+
+        rule = node->data;
+
+        cls_rule_format(rule, ofproto_get_tun_tab(&ctx->xin->ofproto->up),
+                        map, &s);
+        xlate_report(ctx, OFT_DETAIL, "conj. %s", ds_cstr(&s));
+    }
+
+    ds_destroy(&s);
+}
 
 /* If tracing is enabled in 'ctx', appends a node representing 'rule' (in
  * OpenFlow table 'table_id') to the trace and makes this node the parent for
@@ -882,6 +913,8 @@ xlate_report_table(const struct xlate_ctx *ctx, struct 
rule_dpif *rule,
         return;
     }
 
+    struct ofputil_port_map map = OFPUTIL_PORT_MAP_INITIALIZER(&map);
+
     struct ds s = DS_EMPTY_INITIALIZER;
     ds_put_format(&s, "%2d. ", table_id);
     if (rule == ctx->xin->ofproto->miss_rule) {
@@ -892,8 +925,6 @@ xlate_report_table(const struct xlate_ctx *ctx, struct 
rule_dpif *rule,
         ds_put_cstr(&s, "Packets are IP fragments and "
                     "the fragment handling mode is \"drop\".");
     } else {
-        struct ofputil_port_map map = OFPUTIL_PORT_MAP_INITIALIZER(&map);
-
         if (ctx->xin->names) {
             struct ofproto_dpif *ofprotop;
             ofprotop = ofproto_dpif_lookup_by_name(ctx->xbridge->name);
@@ -904,8 +935,6 @@ xlate_report_table(const struct xlate_ctx *ctx, struct 
rule_dpif *rule,
                          ofproto_get_tun_tab(&ctx->xin->ofproto->up),
                          &map, &s, OFP_DEFAULT_PRIORITY);
 
-        ofputil_port_map_destroy(&map);
-
         if (ds_last(&s) != ' ') {
             ds_put_cstr(&s, ", ");
         }
@@ -918,6 +947,9 @@ xlate_report_table(const struct xlate_ctx *ctx, struct 
rule_dpif *rule,
     ctx->xin->trace = &oftrace_report(ctx->xin->trace, OFT_TABLE,
                                       ds_cstr(&s))->subs;
     ds_destroy(&s);
+
+    xlate_report_conj_matches(ctx, &map);
+    ofputil_port_map_destroy(&map);
 }
 
 /* If tracing is enabled in 'ctx', adds an OFT_DETAIL trace node to 'ctx'
@@ -4653,7 +4685,7 @@ xlate_table_action(struct xlate_ctx *ctx, ofp_port_t 
in_port, uint8_t table_id,
                                            ctx->xin->resubmit_stats,
                                            &ctx->table_id, in_port,
                                            may_packet_in, honor_table_miss,
-                                           ctx->xin->xcache);
+                                           ctx->xin->xcache, ctx->conj_flows);
         /* Swap back. */
         if (with_ct_orig) {
             tuple_swap(&ctx->xin->flow, ctx->wc);
@@ -4674,6 +4706,11 @@ xlate_table_action(struct xlate_ctx *ctx, ofp_port_t 
in_port, uint8_t table_id,
 
             struct ovs_list *old_trace = ctx->xin->trace;
             xlate_report_table(ctx, rule, table_id);
+
+            if (OVS_UNLIKELY(ctx->xin->trace)) {
+                hmapx_clear(ctx->conj_flows);
+            }
+
             xlate_recursively(ctx, rule, table_id <= old_table_id,
                               is_last_action, xlator);
             ctx->xin->trace = old_trace;
@@ -8044,6 +8081,13 @@ xlate_actions(struct xlate_in *xin, struct xlate_out 
*xout)
 
     COVERAGE_INC(xlate_actions);
 
+    ctx.conj_flows = NULL;
+
+    if (OVS_UNLIKELY(xin->trace)) {
+        ctx.conj_flows = xzalloc(sizeof *ctx.conj_flows);
+        hmapx_init(ctx.conj_flows);
+    }
+
     xin->trace = xlate_report(&ctx, OFT_BRIDGE, "bridge(\"%s\")",
                               xbridge->name);
     if (xin->frozen_state) {
@@ -8181,7 +8225,8 @@ xlate_actions(struct xlate_in *xin, struct xlate_out 
*xout)
         ctx.rule = rule_dpif_lookup_from_table(
             ctx.xbridge->ofproto, ctx.xin->tables_version, flow, ctx.wc,
             ctx.xin->resubmit_stats, &ctx.table_id,
-            flow->in_port.ofp_port, true, true, ctx.xin->xcache);
+            flow->in_port.ofp_port, true, true, ctx.xin->xcache,
+            ctx.conj_flows);
         if (ctx.xin->resubmit_stats) {
             rule_dpif_credit_stats(ctx.rule, ctx.xin->resubmit_stats, false);
         }
@@ -8194,6 +8239,10 @@ xlate_actions(struct xlate_in *xin, struct xlate_out 
*xout)
         }
 
         xlate_report_table(&ctx, ctx.rule, ctx.table_id);
+
+        if (OVS_UNLIKELY(ctx.xin->trace)) {
+            hmapx_clear(ctx.conj_flows);
+        }
     }
 
     /* Tunnel stats only for not-thawed packets. */
@@ -8375,6 +8424,12 @@ exit:
     ofpbuf_uninit(&scratch_actions);
     ofpbuf_delete(ctx.encap_data);
 
+    /* Clean up 'conj_flows' as it is no longer needed. */
+    if (OVS_UNLIKELY(xin->trace)) {
+        hmapx_destroy(ctx.conj_flows);
+        free(ctx.conj_flows);
+    }
+
     /* Make sure we return a "drop flow" in case of an error. */
     if (ctx.error) {
         xout->slow = 0;
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index ba5706f6adcc..808a5c23d67e 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -4383,15 +4383,20 @@ ofproto_dpif_get_tables_version(struct ofproto_dpif 
*ofproto)
  * a reference.
  *
  * 'flow' is non-const to allow for temporary modifications during the lookup.
- * Any changes are restored before returning. */
+ * Any changes are restored before returning.
+ *
+ * 'conj_flows' is an optional parameter.  If it is non-null, the matching
+ * conjunctive flows are inserted. */
 static struct rule_dpif *
 rule_dpif_lookup_in_table(struct ofproto_dpif *ofproto, ovs_version_t version,
                           uint8_t table_id, struct flow *flow,
-                          struct flow_wildcards *wc)
+                          struct flow_wildcards *wc,
+                          struct hmapx *conj_flows)
 {
     struct classifier *cls = &ofproto->up.tables[table_id].cls;
     return rule_dpif_cast(rule_from_cls_rule(classifier_lookup(cls, version,
-                                                               flow, wc)));
+                                                               flow, wc,
+                                                               conj_flows)));
 }
 
 void
@@ -4433,7 +4438,10 @@ ofproto_dpif_credit_table_stats(struct ofproto_dpif 
*ofproto, uint8_t table_id,
  * 'in_port'.  This is needed for resubmit action support.
  *
  * 'flow' is non-const to allow for temporary modifications during the lookup.
- * Any changes are restored before returning. */
+ * Any changes are restored before returning.
+ *
+ * 'conj_flows' is an optional parameter.  If it is non-null, the matching
+ * conjunctive flows are inserted. */
 struct rule_dpif *
 rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
                             ovs_version_t version, struct flow *flow,
@@ -4441,7 +4449,8 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
                             const struct dpif_flow_stats *stats,
                             uint8_t *table_id, ofp_port_t in_port,
                             bool may_packet_in, bool honor_table_miss,
-                            struct xlate_cache *xcache)
+                            struct xlate_cache *xcache,
+                            struct hmapx *conj_flows)
 {
     ovs_be16 old_tp_src = flow->tp_src, old_tp_dst = flow->tp_dst;
     ofp_port_t old_in_port = flow->in_port.ofp_port;
@@ -4497,7 +4506,8 @@ rule_dpif_lookup_from_table(struct ofproto_dpif *ofproto,
          next_id++, next_id += (next_id == TBL_INTERNAL))
     {
         *table_id = next_id;
-        rule = rule_dpif_lookup_in_table(ofproto, version, next_id, flow, wc);
+        rule = rule_dpif_lookup_in_table(ofproto, version, next_id, flow, wc,
+                                         conj_flows);
         if (stats) {
             struct oftable *tbl = &ofproto->up.tables[next_id];
             unsigned long orig;
@@ -6680,7 +6690,8 @@ ofproto_dpif_add_internal_flow(struct ofproto_dpif 
*ofproto,
 
     rule = rule_dpif_lookup_in_table(ofproto,
                                      ofproto_dpif_get_tables_version(ofproto),
-                                     TBL_INTERNAL, &match->flow, &match->wc);
+                                     TBL_INTERNAL, &match->flow, &match->wc,
+                                     NULL);
     if (rule) {
         *rulep = &rule->up;
     } else {
diff --git a/ofproto/ofproto-dpif.h b/ofproto/ofproto-dpif.h
index d8e0cd37ac5b..1fe22ab41bd9 100644
--- a/ofproto/ofproto-dpif.h
+++ b/ofproto/ofproto-dpif.h
@@ -103,7 +103,8 @@ struct rule_dpif *rule_dpif_lookup_from_table(struct 
ofproto_dpif *,
                                               ofp_port_t in_port,
                                               bool may_packet_in,
                                               bool honor_table_miss,
-                                              struct xlate_cache *);
+                                              struct xlate_cache *,
+                                              struct hmapx *conj_flows);
 
 void rule_dpif_credit_stats(struct rule_dpif *,
                             const struct dpif_flow_stats *, bool);
diff --git a/tests/classifier.at b/tests/classifier.at
index de2705653e00..93a13f32b13d 100644
--- a/tests/classifier.at
+++ b/tests/classifier.at
@@ -276,6 +276,13 @@ for src in 0 1 2 3 4 5 6 7; do
         AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=1,dl_type=0x0800,nw_src=10.0.0.$src,nw_dst=10.0.0.$dst"], [0], 
[stdout])
         AT_CHECK_UNQUOTED([tail -1 stdout], [0], [Datapath actions: $out
 ])
+        dnl Check detailed output for conjunctive match.
+        if test $out = 3; then
+            AT_CHECK_UNQUOTED([cat stdout | grep conj\\. | sort], [0], [dnl
+     -> conj. priority=100,ip,nw_dst=10.0.0.$dst
+     -> conj. priority=100,ip,nw_src=10.0.0.$src
+])
+        fi
     done
 done
 OVS_VSWITCHD_STOP
@@ -418,6 +425,98 @@ ovs-ofctl: "conjunction" actions may be used along with 
"note" but not any other
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([conjunctive match with same priority])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+AT_DATA([flows.txt], [dnl
+conj_id=1,actions=2
+conj_id=2,actions=drop
+
+priority=10,ip,ip_dst=10.0.0.1,actions=conjunction(1,1/2)
+priority=10,ip,ip_src=10.0.0.2,actions=conjunction(1,2/2)
+priority=10,ip,ip_dst=10.0.0.3,actions=conjunction(2,1/2)
+priority=10,ip,in_port=1,actions=conjunction(2,2/2)
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+# Check that "priority=10,ip,in_port=1,actions=conjunction(2,2/2)" is
+# correctly excluded from the output.
+AT_CHECK([ovs-appctl ofproto/trace br0 
"in_port=1,dl_type=0x0800,nw_dst=10.0.0.1,nw_src=10.0.0.2" | grep conj\\. | 
sort], [0], [dnl
+     -> conj. priority=10,ip,nw_dst=10.0.0.1
+     -> conj. priority=10,ip,nw_src=10.0.0.2
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conjunctive match with metadata])
+OVS_VSWITCHD_START
+AT_CHECK([ovs-ofctl add-tlv-map br0 
"{class=0xffff,type=0,len=4}->tun_metadata0"])
+AT_CHECK([ovs-ofctl add-tlv-map br0 
"{class=0xffff,type=1,len=8}->tun_metadata1"])
+AT_DATA([flows.txt], [dnl
+conj_id=7,actions=drop
+
+priority=5,tun_metadata0=0x1,actions=conjunction(7,1/2)
+priority=5,tun_metadata1=0x2,actions=conjunction(7,2/2)
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+# Check that tunnel metadata is included in the output.
+AT_CHECK([ovs-appctl ofproto/trace br0 
"tun_metadata0=0x1,tun_metadata1=0x2,in_port=br0" | grep conj\\. | sort], [0], 
[dnl
+     -> conj. priority=5,tun_metadata0=0x1
+     -> conj. priority=5,tun_metadata1=0x2
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conjunctive match with or without port map])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+AT_DATA([flows.txt], [dnl
+conj_id=1,actions=drop
+conj_id=2,actions=drop
+
+priority=10,ip,actions=conjunction(1,1/2),conjunction(2,1/2)
+priority=10,in_port=p1,actions=conjunction(1,2/2)
+priority=10,in_port=p2,actions=conjunction(1,2/2)
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+AT_CHECK([ovs-appctl ofproto/trace br0 "ip,in_port=p1" --names | grep conj\\. 
| sort], [0], [dnl
+     -> conj. priority=10,in_port=p1
+     -> conj. priority=10,ip
+])
+AT_CHECK([ovs-appctl ofproto/trace br0 "ip,in_port=p2" | grep conj\\. | sort], 
[0], [dnl
+     -> conj. priority=10,in_port=2
+     -> conj. priority=10,ip
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
+AT_SETUP([conjunctive match with resubmit])
+OVS_VSWITCHD_START
+add_of_ports br0 1 2
+AT_DATA([flows.txt], [dnl
+conj_id=1,actions=resubmit(,2)
+priority=10,ip,actions=conjunction(1,1/2)
+priority=10,in_port=p1,actions=conjunction(1,2/2)
+priority=10,in_port=p2,actions=conjunction(1,2/2)
+
+table=2,conj_id=7,actions=resubmit(,3)
+table=2,priority=20,ip,actions=conjunction(7,1/2)
+table=2,priority=20,in_port=p1,actions=conjunction(7,2/2)
+table=2,priority=20,in_port=p2,actions=conjunction(7,2/2)
+
+table=3,actions=drop
+])
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+# Check that conj_flows are reset for each table and that they are output
+# exactly once.
+AT_CHECK([ovs-appctl ofproto/trace br0 "ip,in_port=p1" --names | grep conj\\. 
| sort], [0], [dnl
+     -> conj. priority=10,in_port=p1
+     -> conj. priority=10,ip
+     -> conj. priority=20,in_port=p1
+     -> conj. priority=20,ip
+])
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 # Flow classifier a packet with excess of padding.
 AT_SETUP([flow classifier - packet with extra padding])
 OVS_VSWITCHD_START
diff --git a/tests/test-classifier.c b/tests/test-classifier.c
index cff00c8fa35e..2c1604a01e2e 100644
--- a/tests/test-classifier.c
+++ b/tests/test-classifier.c
@@ -441,7 +441,7 @@ compare_classifiers(struct classifier *cls, size_t 
n_invisible_rules,
         /* This assertion is here to suppress a GCC 4.9 array-bounds warning */
         ovs_assert(cls->n_tries <= CLS_MAX_TRIES);
 
-        cr0 = classifier_lookup(cls, version, &flow, &wc);
+        cr0 = classifier_lookup(cls, version, &flow, &wc, NULL);
         cr1 = tcls_lookup(tcls, &flow);
         assert((cr0 == NULL) == (cr1 == NULL));
         if (cr0 != NULL) {
@@ -454,7 +454,7 @@ compare_classifiers(struct classifier *cls, size_t 
n_invisible_rules,
             /* Make sure the rule should have been visible. */
             assert(cls_rule_visible_in_version(cr0, version));
         }
-        cr2 = classifier_lookup(cls, version, &flow, NULL);
+        cr2 = classifier_lookup(cls, version, &flow, NULL, NULL);
         assert(cr2 == cr0);
     }
 }
@@ -1370,10 +1370,10 @@ lookup_classifier(void *aux_)
         if (aux->use_wc) {
             flow_wildcards_init_catchall(&wc);
             cr = classifier_lookup(aux->cls, version, &aux->lookup_flows[x],
-                                   &wc);
+                                   &wc, NULL);
         } else {
             cr = classifier_lookup(aux->cls, version, &aux->lookup_flows[x],
-                                   NULL);
+                                   NULL, NULL);
         }
         if (cr) {
             hits++;
-- 
2.39.3

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to