[ovs-dev,no-slow,v2,8/8] ofproto-dpif: Don't slow-path controller actions with pause.

Message ID 1515623246-3820-8-git-send-email-jpettit@ovn.org
State Accepted
Headers show
Series
  • [ovs-dev,no-slow,v2,1/8] ofproto-dpif: Use a fixed size userspace cookie.
Related show

Commit Message

Justin Pettit Jan. 10, 2018, 10:27 p.m.
A previous patch removed slow-pathing for controller actions with the
exception of ones that specified "pause".  This commit removes that
restriction so that no controller actions are slow-pathed.

Signed-off-by: Justin Pettit <jpettit@ovn.org>
---
v1->v2: Changes suggested by Ben.
---
 lib/odp-util.c                     |   9 ++-
 lib/odp-util.h                     |   2 +-
 ofproto/ofproto-dpif-upcall.c      |  31 ++++++--
 ofproto/ofproto-dpif-xlate-cache.c |  13 ----
 ofproto/ofproto-dpif-xlate-cache.h |   1 -
 ofproto/ofproto-dpif-xlate.c       | 151 ++++++++++++++-----------------------
 ofproto/ofproto-dpif.c             |   1 -
 tests/odp.at                       |   4 +-
 tests/ofproto-dpif.at              |  14 ++--
 tests/pmd.at                       |   4 +-
 tests/tunnel-push-pop-ipv6.at      |   2 +-
 tests/tunnel-push-pop.at           |   2 +-
 12 files changed, 102 insertions(+), 132 deletions(-)

Patch

diff --git a/lib/odp-util.c b/lib/odp-util.c
index 1b4b847e1285..f8c84e17330f 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -481,12 +481,14 @@  format_odp_userspace_action(struct ds *ds, const struct nlattr *attr,
             } else if (cookie.type == USER_ACTION_COOKIE_CONTROLLER) {
                 ds_put_format(ds, ",controller(reason=%"PRIu16
                               ",dont_send=%"PRIu8
+                              ",continuation=%"PRIu8
                               ",recirc_id=%"PRIu32
                               ",rule_cookie=%#"PRIx64
                               ",controller_id=%"PRIu16
                               ",max_len=%"PRIu16,
                               cookie.controller.reason,
                               cookie.controller.dont_send ? 1 : 0,
+                              cookie.controller.continuation ? 1 : 0,
                               cookie.controller.recirc_id,
                               ntohll(get_32aligned_be64(
                                          &cookie.controller.rule_cookie)),
@@ -1146,6 +1148,7 @@  parse_odp_userspace_action(const char *s, struct ofpbuf *actions)
 
         /* USER_ACTION_COOKIE_CONTROLLER. */
         uint8_t dont_send;
+        uint8_t continuation;
         uint16_t reason;
         uint32_t recirc_id;
         ovs_be64 rule_cookie;
@@ -1227,17 +1230,19 @@  parse_odp_userspace_action(const char *s, struct ofpbuf *actions)
             cookie.ipfix.output_odp_port = u32_to_odp(output);
         } else if (ovs_scan(&s[n], ",controller(reason=%"SCNu16
                               ",dont_send=%"SCNu8
+                              ",continuation=%"SCNu8
                               ",recirc_id=%"SCNu32
                               ",rule_cookie=%"SCNx64
                               ",controller_id=%"SCNu16
                               ",max_len=%"SCNu16")%n",
-                              &reason, &dont_send, &recirc_id, &rule_cookie,
-                              &controller_id, &max_len, &n1)) {
+                              &reason, &dont_send, &continuation, &recirc_id,
+                              &rule_cookie, &controller_id, &max_len, &n1)) {
             n += n1;
             cookie.type = USER_ACTION_COOKIE_CONTROLLER;
             cookie.ofp_in_port = OFPP_NONE;
             cookie.ofproto_uuid = UUID_ZERO;
             cookie.controller.dont_send = dont_send ? true : false;
+            cookie.controller.continuation = continuation ? true : false;
             cookie.controller.reason = reason;
             cookie.controller.recirc_id = recirc_id;
             put_32aligned_be64(&cookie.controller.rule_cookie,
diff --git a/lib/odp-util.h b/lib/odp-util.h
index 981dcd8f2a23..028c9e37ce63 100644
--- a/lib/odp-util.h
+++ b/lib/odp-util.h
@@ -43,7 +43,6 @@  struct pkt_metadata;
     SPR(SLOW_LACP,       "lacp",       "Consists of LACP packets")      \
     SPR(SLOW_STP,        "stp",        "Consists of STP packets")       \
     SPR(SLOW_LLDP,       "lldp",       "Consists of LLDP packets")      \
-    SPR(SLOW_PAUSE,      "pause",      "Controller action with pause")  \
     SPR(SLOW_ACTION,     "action",                                      \
         "Uses action(s) not supported by datapath")
 
@@ -337,6 +336,7 @@  struct user_action_cookie {
         struct {
             /* USER_ACTION_COOKIE_CONTROLLER. */
             bool dont_send;         /* Don't send the packet to controller. */
+            bool continuation;      /* Send packet-in as a continuation. */
             uint16_t reason;
             uint32_t recirc_id;
             ovs_32aligned_be64 rule_cookie;
diff --git a/ofproto/ofproto-dpif-upcall.c b/ofproto/ofproto-dpif-upcall.c
index c7bfa472f24a..cb5018d9952c 100644
--- a/ofproto/ofproto-dpif-upcall.c
+++ b/ofproto/ofproto-dpif-upcall.c
@@ -1428,6 +1428,8 @@  process_upcall(struct udpif *udpif, struct upcall *upcall,
                 break;
             }
 
+            const struct frozen_state *state = &recirc_node->state;
+
             struct ofproto_async_msg *am = xmalloc(sizeof *am);
             *am = (struct ofproto_async_msg) {
                 .controller_id = cookie->controller.controller_id,
@@ -1439,7 +1441,7 @@  process_upcall(struct udpif *udpif, struct upcall *upcall,
                                               dp_packet_size(packet)),
                             .packet_len = dp_packet_size(packet),
                             .reason = cookie->controller.reason,
-                            .table_id = recirc_node->state.table_id,
+                            .table_id = state->table_id,
                             .cookie = get_32aligned_be64(
                                          &cookie->controller.rule_cookie),
                             .userdata = (recirc_node->state.userdata_len
@@ -1453,18 +1455,36 @@  process_upcall(struct udpif *udpif, struct upcall *upcall,
                 },
             };
 
+            if (cookie->controller.continuation) {
+                am->pin.up.stack = (state->stack_size
+                          ? xmemdup(state->stack, state->stack_size)
+                          : NULL),
+                am->pin.up.stack_size = state->stack_size,
+                am->pin.up.mirrors = state->mirrors,
+                am->pin.up.conntracked = state->conntracked,
+                am->pin.up.actions = (state->ofpacts_len
+                            ? xmemdup(state->ofpacts,
+                                      state->ofpacts_len) : NULL),
+                am->pin.up.actions_len = state->ofpacts_len,
+                am->pin.up.action_set = (state->action_set_len
+                               ? xmemdup(state->action_set,
+                                         state->action_set_len)
+                               : NULL),
+                am->pin.up.action_set_len = state->action_set_len,
+                am->pin.up.bridge = upcall->ofproto->uuid;
+            }
+
             /* We don't want to use the upcall 'flow', since it may be
              * more specific than the point at which the "controller"
              * action was specified. */
             struct flow frozen_flow;
 
             frozen_flow = *flow;
-            if (!recirc_node->state.conntracked) {
+            if (!state->conntracked) {
                 flow_clear_conntrack(&frozen_flow);
             }
 
-            frozen_metadata_to_flow(&recirc_node->state.metadata,
-                                    &frozen_flow);
+            frozen_metadata_to_flow(&state->metadata, &frozen_flow);
             flow_get_metadata(&frozen_flow, &am->pin.up.base.flow_metadata);
 
             ofproto_dpif_send_async_msg(upcall->ofproto, am);
@@ -1494,9 +1514,6 @@  handle_upcalls(struct udpif *udpif, struct upcall *upcalls,
      *
      *   - For SLOW_ACTION, translation executes the actions directly.
      *
-     *   - For SLOW_PAUSE, translation needs to handle a pause request
-     *     from the controller.
-     *
      * The loop fills 'ops' with an array of operations to execute in the
      * datapath. */
     n_ops = 0;
diff --git a/ofproto/ofproto-dpif-xlate-cache.c b/ofproto/ofproto-dpif-xlate-cache.c
index 5f53a525a6ae..2a2e77a42f8b 100644
--- a/ofproto/ofproto-dpif-xlate-cache.c
+++ b/ofproto/ofproto-dpif-xlate-cache.c
@@ -154,13 +154,6 @@  xlate_push_stats_entry(struct xc_entry *entry,
         tnl_neigh_lookup(entry->tnl_neigh_cache.br_name,
                          &entry->tnl_neigh_cache.d_ipv6, &dmac);
         break;
-    case XC_CONTROLLER:
-        if (entry->controller.am) {
-            ofproto_dpif_send_async_msg(entry->controller.ofproto,
-                                        entry->controller.am);
-            entry->controller.am = NULL; /* One time only. */
-        }
-        break;
     case XC_TUNNEL_HEADER:
         if (entry->tunnel_hdr.operation == ADD) {
             stats->n_bytes += stats->n_packets * entry->tunnel_hdr.hdr_size;
@@ -248,12 +241,6 @@  xlate_cache_clear_entry(struct xc_entry *entry)
         break;
     case XC_TNL_NEIGH:
         break;
-    case XC_CONTROLLER:
-        if (entry->controller.am) {
-            ofproto_async_msg_free(entry->controller.am);
-            entry->controller.am = NULL;
-        }
-        break;
     case XC_TUNNEL_HEADER:
         break;
     default:
diff --git a/ofproto/ofproto-dpif-xlate-cache.h b/ofproto/ofproto-dpif-xlate-cache.h
index 19c1ef7b95ad..e5dae6dc660e 100644
--- a/ofproto/ofproto-dpif-xlate-cache.h
+++ b/ofproto/ofproto-dpif-xlate-cache.h
@@ -52,7 +52,6 @@  enum xc_type {
     XC_FIN_TIMEOUT,      /* Calls back to ofproto. */
     XC_GROUP,
     XC_TNL_NEIGH,
-    XC_CONTROLLER,
     XC_TUNNEL_HEADER,
 };
 
diff --git a/ofproto/ofproto-dpif-xlate.c b/ofproto/ofproto-dpif-xlate.c
index a2520cf487a3..e1fc7d58a622 100644
--- a/ofproto/ofproto-dpif-xlate.c
+++ b/ofproto/ofproto-dpif-xlate.c
@@ -365,7 +365,6 @@  struct xlate_ctx {
     uint32_t dp_hash_basis;
     struct ofpbuf frozen_actions;
     const struct ofpact_controller *pause;
-    struct flow *paused_flow;
 
     /* True if a packet was but is no longer MPLS (due to an MPLS pop action).
      * This is a trigger for recirculation in cases where translating an action
@@ -4356,6 +4355,35 @@  flood_packets(struct xlate_ctx *ctx, bool all, bool is_last_action)
 }
 
 static void
+put_controller_user_action(struct xlate_ctx *ctx,
+                           bool dont_send, bool continuation,
+                           uint32_t recirc_id, int len,
+                           enum ofp_packet_in_reason reason,
+                           uint16_t controller_id)
+{
+    struct user_action_cookie cookie;
+
+    memset(&cookie, 0, sizeof cookie);
+    cookie.type = USER_ACTION_COOKIE_CONTROLLER;
+    cookie.ofp_in_port = OFPP_NONE,
+    cookie.ofproto_uuid = ctx->xbridge->ofproto->uuid;
+    cookie.controller.dont_send = dont_send;
+    cookie.controller.continuation = continuation;
+    cookie.controller.reason = reason;
+    cookie.controller.recirc_id = recirc_id;
+    put_32aligned_be64(&cookie.controller.rule_cookie, ctx->rule_cookie);
+    cookie.controller.controller_id = controller_id;
+    cookie.controller.max_len = len;
+
+    odp_port_t odp_port = ofp_port_to_odp_port(ctx->xbridge,
+                                             ctx->xin->flow.in_port.ofp_port);
+    uint32_t pid = dpif_port_get_pid(ctx->xbridge->dpif, odp_port,
+                                     flow_hash_5tuple(&ctx->xin->flow, 0));
+    odp_put_userspace_action(pid, &cookie, sizeof cookie, ODPP_NONE,
+                             false, ctx->odp_actions);
+}
+
+static void
 xlate_controller_action(struct xlate_ctx *ctx, int len,
                         enum ofp_packet_in_reason reason,
                         uint16_t controller_id,
@@ -4409,31 +4437,14 @@  xlate_controller_action(struct xlate_ctx *ctx, int len,
         nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_METER, meter_id);
     }
 
-    struct user_action_cookie cookie;
-
-    memset(&cookie, 0, sizeof cookie);
-    cookie.type = USER_ACTION_COOKIE_CONTROLLER;
-    cookie.ofp_in_port = OFPP_NONE;
-    cookie.ofproto_uuid = ctx->xbridge->ofproto->uuid;
-    cookie.controller.dont_send = false;
-    cookie.controller.reason = reason;
-    cookie.controller.recirc_id = recirc_id;
-    put_32aligned_be64(&cookie.controller.rule_cookie, ctx->rule_cookie);
-    cookie.controller.max_len = len;
-    cookie.controller.controller_id = controller_id;
-
     /* Generate the datapath flows even if we don't send the packet-in
      * so that debugging more closely represents normal state. */
+    bool dont_send = false;
     if (!ctx->xin->allow_side_effects && !ctx->xin->xcache) {
-        cookie.controller.dont_send = true;
+        dont_send = true;
     }
-
-    odp_port_t odp_port = ofp_port_to_odp_port(
-        ctx->xbridge, ctx->xin->flow.in_port.ofp_port);
-    uint32_t pid = dpif_port_get_pid(ctx->xbridge->dpif, odp_port,
-                                     flow_hash_5tuple(&ctx->xin->flow, 0));
-    odp_put_userspace_action(pid, &cookie, sizeof cookie, ODPP_NONE,
-                             false, ctx->odp_actions);
+    put_controller_user_action(ctx, dont_send, false, recirc_id, len,
+                               reason, controller_id);
 
     if (meter_id != UINT32_MAX) {
         nl_msg_end_nested(ctx->odp_actions, ac_offset);
@@ -4441,57 +4452,6 @@  xlate_controller_action(struct xlate_ctx *ctx, int len,
     }
 }
 
-static void
-emit_continuation(struct xlate_ctx *ctx, const struct frozen_state *state)
-{
-    if (!ctx->xin->allow_side_effects && !ctx->xin->xcache) {
-        return;
-    }
-
-    struct ofproto_async_msg *am = xmalloc(sizeof *am);
-    *am = (struct ofproto_async_msg) {
-        .controller_id = ctx->pause->controller_id,
-        .oam = OAM_PACKET_IN,
-        .pin = {
-            .up = {
-                .base = {
-                    .userdata = xmemdup(ctx->pause->userdata,
-                                        ctx->pause->userdata_len),
-                    .userdata_len = ctx->pause->userdata_len,
-                    .packet = xmemdup(dp_packet_data(ctx->xin->packet),
-                                      dp_packet_size(ctx->xin->packet)),
-                    .packet_len = dp_packet_size(ctx->xin->packet),
-                    .reason = ctx->pause->reason,
-                },
-                .bridge = ctx->xbridge->ofproto->uuid,
-                .stack = xmemdup(state->stack, state->stack_size),
-                .stack_size = state->stack_size,
-                .mirrors = state->mirrors,
-                .conntracked = state->conntracked,
-                .actions = xmemdup(state->ofpacts, state->ofpacts_len),
-                .actions_len = state->ofpacts_len,
-                .action_set = xmemdup(state->action_set,
-                                      state->action_set_len),
-                .action_set_len = state->action_set_len,
-            },
-            .max_len = UINT16_MAX,
-        },
-    };
-    flow_get_metadata(ctx->paused_flow, &am->pin.up.base.flow_metadata);
-
-    /* Async messages are only sent once, so if we send one now, no
-     * xlate cache entry is created.  */
-    if (ctx->xin->allow_side_effects) {
-        ofproto_dpif_send_async_msg(ctx->xbridge->ofproto, am);
-    } else /* xcache */ {
-        struct xc_entry *entry;
-
-        entry = xlate_cache_add_entry(ctx->xin->xcache, XC_CONTROLLER);
-        entry->controller.ofproto = ctx->xbridge->ofproto;
-        entry->controller.am = am;
-    }
-}
-
 /* Creates a frozen state, and allocates a unique recirc id for the given
  * state.  Returns a non-zero recirc id if it is allocated successfully.
  * Returns 0 otherwise.
@@ -4499,7 +4459,6 @@  emit_continuation(struct xlate_ctx *ctx, const struct frozen_state *state)
 static uint32_t
 finish_freezing__(struct xlate_ctx *ctx, uint8_t table)
 {
-    uint32_t id = 0;
     ovs_assert(ctx->freezing);
 
     struct frozen_state state = {
@@ -4513,27 +4472,35 @@  finish_freezing__(struct xlate_ctx *ctx, uint8_t table)
         .ofpacts_len = ctx->frozen_actions.size,
         .action_set = ctx->action_set.data,
         .action_set_len = ctx->action_set.size,
+        .userdata = ctx->pause ? CONST_CAST(uint8_t *,ctx->pause->userdata)
+                               : NULL,
+        .userdata_len = ctx->pause ? ctx->pause->userdata_len : 0,
     };
     frozen_metadata_from_flow(&state.metadata, &ctx->xin->flow);
 
+    /* Allocate a unique recirc id for the given metadata state in the
+     * flow.  An existing id, with a new reference to the corresponding
+     * recirculation context, will be returned if possible.
+     * The life-cycle of this recirc id is managed by associating it
+     * with the udpif key ('ukey') created for each new datapath flow. */
+    uint32_t recirc_id = recirc_alloc_id_ctx(&state);
+    if (!recirc_id) {
+        xlate_report_error(ctx, "Failed to allocate recirculation id");
+        ctx->error = XLATE_NO_RECIRCULATION_CONTEXT;
+        return 0;
+    }
+    recirc_refs_add(&ctx->xout->recircs, recirc_id);
+
     if (ctx->pause) {
-        if (ctx->xin->packet) {
-            emit_continuation(ctx, &state);
-        }
-    } else {
-        /* Allocate a unique recirc id for the given metadata state in the
-         * flow.  An existing id, with a new reference to the corresponding
-         * recirculation context, will be returned if possible.
-         * The life-cycle of this recirc id is managed by associating it
-         * with the udpif key ('ukey') created for each new datapath flow. */
-        id = recirc_alloc_id_ctx(&state);
-        if (!id) {
-            xlate_report_error(ctx, "Failed to allocate recirculation id");
-            ctx->error = XLATE_NO_RECIRCULATION_CONTEXT;
+        if (!ctx->xin->allow_side_effects && !ctx->xin->xcache) {
             return 0;
         }
-        recirc_refs_add(&ctx->xout->recircs, id);
 
+        put_controller_user_action(ctx, false, true, recirc_id,
+                                   ctx->pause->max_len,
+                                   ctx->pause->reason,
+                                   ctx->pause->controller_id);
+    } else {
         if (ctx->recirc_update_dp_hash) {
             struct ovs_action_hash *act_hash;
 
@@ -4544,12 +4511,12 @@  finish_freezing__(struct xlate_ctx *ctx, uint8_t table)
             act_hash->hash_alg = OVS_HASH_ALG_L4;  /* Make configurable. */
             act_hash->hash_basis = 0;              /* Make configurable. */
         }
-        nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_RECIRC, id);
+        nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_RECIRC, recirc_id);
     }
 
     /* Undo changes done by freezing. */
     ctx_cancel_freeze(ctx);
-    return id;
+    return recirc_id;
 }
 
 /* Called only when we're freezing. */
@@ -6120,8 +6087,6 @@  do_xlate_actions(const struct ofpact *ofpacts, size_t ofpacts_len,
             controller = ofpact_get_CONTROLLER(a);
             if (controller->pause) {
                 ctx->pause = controller;
-                ctx->xout->slow |= SLOW_PAUSE;
-                *ctx->paused_flow = ctx->xin->flow;
                 ctx_trigger_freeze(ctx);
                 a = ofpact_next(a);
             } else {
@@ -6756,7 +6721,6 @@  xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
     uint64_t frozen_actions_stub[1024 / 8];
     uint64_t actions_stub[256 / 8];
     struct ofpbuf scratch_actions = OFPBUF_STUB_INITIALIZER(actions_stub);
-    struct flow paused_flow;
     struct xlate_ctx ctx = {
         .xin = xin,
         .xout = xout,
@@ -6792,7 +6756,6 @@  xlate_actions(struct xlate_in *xin, struct xlate_out *xout)
         .recirc_update_dp_hash = false,
         .frozen_actions = OFPBUF_STUB_INITIALIZER(frozen_actions_stub),
         .pause = NULL,
-        .paused_flow = &paused_flow,
 
         .was_mpls = false,
         .conntracked = false,
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 5edf1a9ebf25..274d4ceb18dd 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -4586,7 +4586,6 @@  ofproto_dpif_xcache_execute(struct ofproto_dpif *ofproto,
         case XC_NORMAL:
         case XC_GROUP:
         case XC_TNL_NEIGH:
-        case XC_CONTROLLER:
         case XC_TUNNEL_HEADER:
             xlate_push_stats_entry(entry, stats);
             break;
diff --git a/tests/odp.at b/tests/odp.at
index 1f8080aed1ed..270b9ff7a82e 100644
--- a/tests/odp.at
+++ b/tests/odp.at
@@ -260,8 +260,8 @@  userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_
 userspace(pid=6633,flow_sample(probability=123,collector_set_id=1234,obs_domain_id=2345,obs_point_id=3456,output_port=10,egress),tunnel_out_port=10)
 userspace(pid=6633,ipfix(output_port=10))
 userspace(pid=6633,ipfix(output_port=10),tunnel_out_port=10)
-userspace(pid=6633,controller(reason=1,dont_send=0,recirc_id=4444,rule_cookie=0x5555,controller_id=0,max_len=65535))
-userspace(pid=6633,controller(reason=1,dont_send=1,recirc_id=4444,rule_cookie=0x5555,controller_id=0,max_len=65535))
+userspace(pid=6633,controller(reason=1,dont_send=0,continuation=1,recirc_id=4444,rule_cookie=0x5555,controller_id=0,max_len=65535))
+userspace(pid=6633,controller(reason=1,dont_send=1,continuation=0,recirc_id=4444,rule_cookie=0x5555,controller_id=0,max_len=65535))
 set(in_port(2))
 set(eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15))
 set(eth(src=00:01:02:03:04:05,dst=10:11:12:13:14:15/ff:ff:ff:00:00:00))
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 4c092bc2e3d5..a582aaf391b1 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -784,7 +784,7 @@  AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=50:54:00:00:00:
 AT_CHECK([tail -4 stdout], [0], [
 Final flow: ip,in_port=1,vlan_tci=0x0000,dl_src=50:54:00:00:00:05,dl_dst=50:54:00:00:00:07,nw_src=192.168.0.1,nw_dst=192.168.0.2,nw_proto=111,nw_tos=0,nw_ecn=0,nw_ttl=1
 Megaflow: recirc_id=0,eth,ip,in_port=1,nw_ttl=2,nw_frag=no
-Datapath actions: set(ipv4(ttl=1)),2,userspace(pid=0,controller(reason=2,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535)),4
+Datapath actions: set(ipv4(ttl=1)),2,userspace(pid=0,controller(reason=2,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535)),4
 ])
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=50:54:00:00:00:05,dst=50:54:00:00:00:07),eth_type(0x0800),ipv4(src=192.168.0.1,dst=192.168.0.2,proto=111,tos=0,ttl=3,frag=no)'], [0], [stdout])
 AT_CHECK([tail -2 stdout], [0],
@@ -1681,7 +1681,7 @@  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 
 AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/.*\(packets:\)/\1/' | sed 's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
 flow-dump from non-dpdk interfaces:
-packets:1, bytes:14, used:0.001s, actions:userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
+packets:1, bytes:14, used:0.001s, actions:userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
 ])
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
@@ -1706,7 +1706,7 @@  done
 
 AT_CHECK([ovs-appctl dpctl/dump-flows | sed 's/.*\(packets:\)/\1/' | sed 's/used:[[0-9]].[[0-9]]*s/used:0.001s/'], [0], [dnl
 flow-dump from non-dpdk interfaces:
-packets:7, bytes:98, used:0.001s, actions:sample(sample=100.0%,actions(meter(0),userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=2,rule_cookie=0,controller_id=0,max_len=65535))))
+packets:7, bytes:98, used:0.001s, actions:sample(sample=100.0%,actions(meter(0),userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=2,rule_cookie=0,controller_id=0,max_len=65535))))
 ])
 
 AT_CHECK([ovs-appctl time/warp 1], [0], [ignore])
@@ -7429,8 +7429,8 @@  for dl_src in 00 01; do
 done
 sleep 1  # wait for the datapath flow installed
 AT_CHECK_UNQUOTED([strip_ufid < ovs-vswitchd.log | filter_flow_install | strip_used], [0], [dnl
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:00),eth_type(0x8847),mpls(label=20,tc=0,ttl=32,bos=0,label=20,tc=0,ttl=32,bos=1), actions:push_mpls(label=20,tc=0,ttl=32,bos=0,eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:01),eth_type(0x8847),mpls(label=20/0x0,tc=0/0,ttl=32/0x0,bos=0/1,label=20/0xfffff,tc=0/7,ttl=32/0xff,bos=1/1), actions:pop_mpls(eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:00),eth_type(0x8847),mpls(label=20,tc=0,ttl=32,bos=0,label=20,tc=0,ttl=32,bos=1), actions:push_mpls(label=20,tc=0,ttl=32,bos=0,eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:01),eth_type(0x8847),mpls(label=20/0x0,tc=0/0,ttl=32/0x0,bos=0/1,label=20/0xfffff,tc=0/7,ttl=32/0xff,bos=1/1), actions:pop_mpls(eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
 ])
 
 OVS_VSWITCHD_STOP
@@ -7468,8 +7468,8 @@  for dl_src in 00 01; do
 done
 sleep 1  # wait for the datapath flow installed
 AT_CHECK_UNQUOTED([strip_ufid < ovs-vswitchd.log | filter_flow_install | strip_used], [0], [dnl
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:00),eth_type(0x8847),mpls(label=20,tc=0,ttl=32,bos=0,label=20,tc=0,ttl=32,bos=1), actions:push_mpls(label=20,tc=0,ttl=32,bos=0,eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
-recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:01),eth_type(0x8847),mpls(label=20/0x0,tc=0/0,ttl=32/0x0,bos=0/1,label=20/0xfffff,tc=0/7,ttl=32/0xff,bos=1/1), actions:pop_mpls(eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:00),eth_type(0x8847),mpls(label=20,tc=0,ttl=32,bos=0,label=20,tc=0,ttl=32,bos=1), actions:push_mpls(label=20,tc=0,ttl=32,bos=0,eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
+recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth(src=60:66:66:66:66:01),eth_type(0x8847),mpls(label=20/0x0,tc=0/0,ttl=32/0x0,bos=0/1,label=20/0xfffff,tc=0/7,ttl=32/0xff,bos=1/1), actions:pop_mpls(eth_type=0x8847),userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
 ])
 
 OVS_VSWITCHD_STOP
diff --git a/tests/pmd.at b/tests/pmd.at
index adfdb4c01e78..fcb007ce04f4 100644
--- a/tests/pmd.at
+++ b/tests/pmd.at
@@ -303,8 +303,8 @@  OVS_WAIT_UNTIL([ovs-appctl -t ovs-ofctl exit])
 
 dnl Make sure that both flows have been installed
 AT_CHECK([ovs-appctl dpctl/dump-flows | flow_dump_prepend_pmd], [0], [dnl
-0 recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
-1 recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
+0 recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
+1 recirc_id(0),in_port(1),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=1,rule_cookie=0,controller_id=0,max_len=65535))
 ])
 
 AT_CHECK([cat ofctl_monitor.log], [0], [dnl
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index 4d609b2f1944..7ca522ae2a0f 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -164,7 +164,7 @@  AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  5'], [0], [dnl
   port  5: rx pkts=1, bytes=98, drop=?, errs=?, frame=?, over=?, crc=?
 ])
 AT_CHECK([ovs-appctl dpif/dump-flows int-br | grep 'in_port(6081)'], [0], [dnl
-tunnel(tun_id=0x7b,ipv6_src=2001:cafe::92,ipv6_dst=2001:cafe::88,geneve({class=0xffff,type=0x80,len=4,0xa/0xf}{class=0xffff,type=0,len=4}),flags(-df-csum+key)),recirc_id(0),in_port(6081),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=3,rule_cookie=0,controller_id=0,max_len=65535))
+tunnel(tun_id=0x7b,ipv6_src=2001:cafe::92,ipv6_dst=2001:cafe::88,geneve({class=0xffff,type=0x80,len=4,0xa/0xf}{class=0xffff,type=0,len=4}),flags(-df-csum+key)),recirc_id(0),in_port(6081),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=3,rule_cookie=0,controller_id=0,max_len=65535))
 ])
 
 ovs-appctl time/warp 10000
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index cdfbb36a47d9..cc8b1b522741 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -215,7 +215,7 @@  AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  5'], [0], [dnl
   port  5: rx pkts=1, bytes=98, drop=?, errs=?, frame=?, over=?, crc=?
 ])
 AT_CHECK([ovs-appctl dpif/dump-flows int-br | grep 'in_port(6081)'], [0], [dnl
-tunnel(tun_id=0x7b,src=1.1.2.92,dst=1.1.2.88,geneve({class=0xffff,type=0x80,len=4,0xa/0xf}{class=0xffff,type=0,len=4}),flags(-df-csum+key)),recirc_id(0),in_port(6081),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,recirc_id=3,rule_cookie=0,controller_id=0,max_len=65535))
+tunnel(tun_id=0x7b,src=1.1.2.92,dst=1.1.2.88,geneve({class=0xffff,type=0x80,len=4,0xa/0xf}{class=0xffff,type=0,len=4}),flags(-df-csum+key)),recirc_id(0),in_port(6081),packet_type(ns=0,id=0),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:userspace(pid=0,controller(reason=1,dont_send=0,continuation=0,recirc_id=3,rule_cookie=0,controller_id=0,max_len=65535))
 ])
 
 ovs-appctl time/warp 10000